summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/test-files.cmake1
-rw-r--r--include/mbgl/storage/default_file_source.hpp1
-rw-r--r--platform/default/default_file_source.cpp6
-rw-r--r--platform/default/local_file_source.cpp66
-rw-r--r--src/mbgl/storage/local_file_source.hpp25
-rw-r--r--test/storage/local_file_source.test.cpp124
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();
+}