diff options
-rw-r--r-- | cmake/test-files.cmake | 1 | ||||
-rw-r--r-- | include/mbgl/storage/default_file_source.hpp | 1 | ||||
-rw-r--r-- | platform/default/default_file_source.cpp | 6 | ||||
-rw-r--r-- | platform/default/local_file_source.cpp | 66 | ||||
-rw-r--r-- | src/mbgl/storage/local_file_source.hpp | 25 | ||||
-rw-r--r-- | test/storage/local_file_source.test.cpp | 124 |
6 files changed, 222 insertions, 1 deletions
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index f460758a05..74dc605032 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -60,6 +60,7 @@ set(MBGL_TEST_FILES # storage test/storage/asset_file_source.test.cpp test/storage/default_file_source.test.cpp + test/storage/local_file_source.test.cpp test/storage/headers.test.cpp test/storage/http_file_source.test.cpp test/storage/offline.test.cpp diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp index 4340496af9..b8f5e1167e 100644 --- a/include/mbgl/storage/default_file_source.hpp +++ b/include/mbgl/storage/default_file_source.hpp @@ -121,6 +121,7 @@ public: private: const std::unique_ptr<util::Thread<Impl>> thread; const std::unique_ptr<FileSource> assetFileSource; + const std::unique_ptr<FileSource> localFileSource; }; } // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 099890a035..7b52637c15 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -1,5 +1,6 @@ #include <mbgl/storage/default_file_source.hpp> #include <mbgl/storage/asset_file_source.hpp> +#include <mbgl/storage/local_file_source.hpp> #include <mbgl/storage/online_file_source.hpp> #include <mbgl/storage/offline_database.hpp> #include <mbgl/storage/offline_download.hpp> @@ -164,7 +165,8 @@ DefaultFileSource::DefaultFileSource(const std::string& cachePath, uint64_t maximumCacheSize) : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"DefaultFileSource", util::ThreadPriority::Low}, cachePath, maximumCacheSize)), - assetFileSource(std::make_unique<AssetFileSource>(assetRoot)) { + assetFileSource(std::make_unique<AssetFileSource>(assetRoot)), + localFileSource(std::make_unique<LocalFileSource>()) { } DefaultFileSource::~DefaultFileSource() = default; @@ -203,6 +205,8 @@ std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resourc if (isAssetURL(resource.url)) { return assetFileSource->request(resource, callback); + } else if (LocalFileSource::acceptsURL(resource.url)) { + return localFileSource->request(resource, callback); } else { return std::make_unique<DefaultFileRequest>(resource, callback, *thread); } diff --git a/platform/default/local_file_source.cpp b/platform/default/local_file_source.cpp new file mode 100644 index 0000000000..5686b453dc --- /dev/null +++ b/platform/default/local_file_source.cpp @@ -0,0 +1,66 @@ +#include <mbgl/storage/local_file_source.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/util/thread.hpp> +#include <mbgl/util/url.hpp> +#include <mbgl/util/util.hpp> +#include <mbgl/util/io.hpp> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +namespace { + +const char* protocol = "file://"; +const std::size_t protocolLength = 7; + +} // namespace + +namespace mbgl { + +class LocalFileSource::Impl { +public: + void request(const std::string& url, FileSource::Callback callback) { + //Cut off the protocol + std::string path = mbgl::util::percentDecode(url.substr(protocolLength)); + + Response response; + + struct stat buf; + int result = stat(path.c_str(), &buf); + + if (result == 0 && S_ISDIR(buf.st_mode)) { + response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); + } else if (result == -1 && errno == ENOENT) { + response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); + } else { + try { + response.data = std::make_shared<std::string>(util::read_file(path)); + } catch (...) { + response.error = std::make_unique<Response::Error>( + Response::Error::Reason::Other, + util::toString(std::current_exception())); + } + } + + callback(response); + } + +}; + +LocalFileSource::LocalFileSource() + : thread(std::make_unique<util::Thread<Impl>>(util::ThreadContext{"LocalFileSource", util::ThreadPriority::Low})) { +} + +LocalFileSource::~LocalFileSource() = default; + +std::unique_ptr<AsyncRequest> LocalFileSource::request(const Resource& resource, Callback callback) { + return thread->invokeWithCallback(&Impl::request, resource.url, callback); +} + +bool LocalFileSource::acceptsURL(const std::string& url) { + return url.compare(0, protocolLength, protocol) == 0; +} + +} // namespace mbgl diff --git a/src/mbgl/storage/local_file_source.hpp b/src/mbgl/storage/local_file_source.hpp new file mode 100644 index 0000000000..5d665c3848 --- /dev/null +++ b/src/mbgl/storage/local_file_source.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include <mbgl/storage/file_source.hpp> + +namespace mbgl { + +namespace util { +template <typename T> class Thread; +} // namespace util + +class LocalFileSource : public FileSource { +public: + LocalFileSource(); + ~LocalFileSource() override; + + std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override; + + static bool acceptsURL(const std::string& url); + +private: + class Impl; + std::unique_ptr<util::Thread<Impl>> thread; +}; + +} // namespace mbgl diff --git a/test/storage/local_file_source.test.cpp b/test/storage/local_file_source.test.cpp new file mode 100644 index 0000000000..afb418d735 --- /dev/null +++ b/test/storage/local_file_source.test.cpp @@ -0,0 +1,124 @@ +#include <mbgl/storage/local_file_source.hpp> +#include <mbgl/platform/platform.hpp> +#include <mbgl/util/run_loop.hpp> + +#include <unistd.h> +#include <limits.h> +#include <gtest/gtest.h> + +namespace { + +std::string toAbsoluteURL(const std::string& fileName) { + char buff[PATH_MAX + 1]; + char* cwd = getcwd( buff, PATH_MAX + 1 ); + std::string url = { "file://" + std::string(cwd) + "/test/fixtures/storage/assets/" + fileName }; + assert(url.size() <= PATH_MAX); + return url; +} + +} // namespace + +using namespace mbgl; + +TEST(LocalFileSource, EmptyFile) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL("empty") }, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("", *res.data); + loop.stop(); + }); + + loop.run(); +} + +TEST(LocalFileSource, NonEmptyFile) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL("nonempty") }, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("content is here\n", *res.data); + loop.stop(); + }); + + loop.run(); +} + +TEST(LocalFileSource, NonExistentFile) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL("does_not_exist") }, [&](Response res) { + req.reset(); + ASSERT_NE(nullptr, res.error); + EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); + ASSERT_FALSE(res.data.get()); + // Do not assert on platform-specific error message. + loop.stop(); + }); + + loop.run(); +} + +TEST(LocalFileSource, ReadDirectory) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL("directory") }, [&](Response res) { + req.reset(); + ASSERT_NE(nullptr, res.error); + EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); + ASSERT_FALSE(res.data.get()); + // Do not assert on platform-specific error message. + loop.stop(); + }); + + loop.run(); +} + +TEST(LocalFileSource, URLEncoding) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL("%6eonempty") }, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("content is here\n", *res.data); + loop.stop(); + }); + + loop.run(); +} + +TEST(LocalFileSource, URLLimit) { + util::RunLoop loop; + + size_t length = PATH_MAX - toAbsoluteURL("").size(); + LocalFileSource fs; + char filename[length]; + memset(filename, 'x', length); + + std::string url(filename, length); + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, toAbsoluteURL(url) }, [&](Response res) { + req.reset(); + ASSERT_NE(nullptr, res.error); + EXPECT_EQ(Response::Error::Reason::Other, res.error->reason); + ASSERT_FALSE(res.data.get()); + loop.stop(); + }); + + loop.run(); +} |