From 1bf12e20a65a3e0e8212e0c24db5a136096896e7 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Wed, 28 Sep 2016 16:01:06 +0200 Subject: [PATCH] [core] LocalFileSource implementation and tests --- cmake/test-files.cmake | 1 + include/mbgl/storage/default_file_source.hpp | 1 + platform/default/default_file_source.cpp | 6 +- platform/default/local_file_source.cpp | 66 ++++++++++ src/mbgl/storage/local_file_source.hpp | 25 ++++ test/storage/local_file_source.test.cpp | 124 +++++++++++++++++++ 6 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 platform/default/local_file_source.cpp create mode 100644 src/mbgl/storage/local_file_source.hpp create mode 100644 test/storage/local_file_source.test.cpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index f460758a0..74dc60503 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 4340496af..b8f5e1167 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> thread; const std::unique_ptr assetFileSource; + const std::unique_ptr localFileSource; }; } // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 099890a03..7b52637c1 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -164,7 +165,8 @@ DefaultFileSource::DefaultFileSource(const std::string& cachePath, uint64_t maximumCacheSize) : thread(std::make_unique>(util::ThreadContext{"DefaultFileSource", util::ThreadPriority::Low}, cachePath, maximumCacheSize)), - assetFileSource(std::make_unique(assetRoot)) { + assetFileSource(std::make_unique(assetRoot)), + localFileSource(std::make_unique()) { } DefaultFileSource::~DefaultFileSource() = default; @@ -203,6 +205,8 @@ std::unique_ptr 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(resource, callback, *thread); } diff --git a/platform/default/local_file_source.cpp b/platform/default/local_file_source.cpp new file mode 100644 index 000000000..5686b453d --- /dev/null +++ b/platform/default/local_file_source.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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::Reason::NotFound); + } else if (result == -1 && errno == ENOENT) { + response.error = std::make_unique(Response::Error::Reason::NotFound); + } else { + try { + response.data = std::make_shared(util::read_file(path)); + } catch (...) { + response.error = std::make_unique( + Response::Error::Reason::Other, + util::toString(std::current_exception())); + } + } + + callback(response); + } + +}; + +LocalFileSource::LocalFileSource() + : thread(std::make_unique>(util::ThreadContext{"LocalFileSource", util::ThreadPriority::Low})) { +} + +LocalFileSource::~LocalFileSource() = default; + +std::unique_ptr 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 000000000..5d665c384 --- /dev/null +++ b/src/mbgl/storage/local_file_source.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace mbgl { + +namespace util { +template class Thread; +} // namespace util + +class LocalFileSource : public FileSource { +public: + LocalFileSource(); + ~LocalFileSource() override; + + std::unique_ptr request(const Resource&, Callback) override; + + static bool acceptsURL(const std::string& url); + +private: + class Impl; + std::unique_ptr> 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 000000000..afb418d73 --- /dev/null +++ b/test/storage/local_file_source.test.cpp @@ -0,0 +1,124 @@ +#include +#include +#include + +#include +#include +#include + +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 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 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 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 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 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 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(); +}