Handle nonexistent mbtiles files, add MBTilesFileSource tests (#292)

* Handle nonexistent mbtiles files, add MBTilesFileSource tests

* minor formatting fix
This commit is contained in:
Brendan Ward
2022-05-27 14:57:25 -07:00
committed by GitHub
parent c762d231ea
commit 834a198c98
4 changed files with 177 additions and 14 deletions

View File

@@ -17,6 +17,9 @@
#include <mbgl/storage/sqlite3.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(__QT__) && (defined(_WIN32) || defined(__EMSCRIPTEN__))
#include <QtZlib/zlib.h>
#else
@@ -27,11 +30,16 @@ namespace {
bool acceptsURL(const std::string& url) {
return 0 == url.rfind(mbgl::util::MBTILES_PROTOCOL, 0);
}
std::string url_to_path(const std::string &url) {
return mbgl::util::percentDecode(url.substr(std::char_traits<char>::length(mbgl::util::MBTILES_PROTOCOL)));
}
} // namespace
namespace mbgl {
using namespace rapidjson;
class MBTilesFileSource::Impl {
public:
explicit Impl(const ActorRef<Impl>&, const ResourceOptions& options): resourceOptions (options.clone()) {}
@@ -61,9 +69,7 @@ public:
return std::string(buffer.GetString(), buffer.GetSize());
}
std::string url_to_path(const std::string &url) {
return mbgl::util::percentDecode(url.substr(std::char_traits<char>::length(util::MBTILES_PROTOCOL)));
}
std::string db_path(const std::string &path) {
return path.substr(0, path.find('?'));
@@ -263,24 +269,38 @@ MBTilesFileSource::MBTilesFileSource(const ResourceOptions& options) :
thread(std::make_unique<util::Thread<Impl>>(
util::makeThreadPrioritySetter(platform::EXPERIMENTAL_THREAD_PRIORITY_FILE), "MBTilesFileSource", options.clone())) {}
std::unique_ptr<AsyncRequest> MBTilesFileSource::request(const Resource &resource, FileSource::Callback callback) {
auto req = std::make_unique<FileSourceRequest>(std::move(callback));
// assume if there is a tile request, that the mbtiles file has been validated
if (resource.kind == Resource::Tile) {
thread->actor().invoke(&Impl::request_tile, resource, req->actor());
return req;
}
if (resource.url.find(":///") == std::string::npos) {
Response response;
response.noContent = true;
response.error = std::make_unique<Response::Error>(Response::Error::Reason::Other,
"MBTilesFileSource only supports absolute path urls");
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other, "MBTilesFileSource only supports absolute path urls");
req->actor().invoke(&FileSourceRequest::setResponse, response);
} else {
if (resource.kind == Resource::Tile) {
thread->actor().invoke(&Impl::request_tile, resource, req->actor());
} else {
thread->actor().invoke(&Impl::request_tilejson, resource, req->actor());
}
return req;
}
// file must exist
auto path = url_to_path(resource.url);
struct stat buffer;
int result = stat(path.c_str(), &buffer);
if (result == -1 && errno == ENOENT) {
Response response;
response.noContent = true;
response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound, "path not found: "+path);
req->actor().invoke(&FileSourceRequest::setResponse, response);
return req;
}
// return TileJSON
thread->actor().invoke(&Impl::request_tilejson, resource, req->actor());
return req;
}

View File

@@ -35,6 +35,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/storage/http_file_source.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/local_file_source.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/main_resource_loader.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/mbtiles_file_source.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/offline.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/offline_database.test.cpp
${PROJECT_SOURCE_DIR}/test/storage/offline_download.test.cpp
@@ -106,7 +107,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/util/timer.test.cpp
${PROJECT_SOURCE_DIR}/test/util/token.test.cpp
${PROJECT_SOURCE_DIR}/test/util/url.test.cpp
${PROJECT_SOURCE_DIR}/test/util/tile_server_options.test.cpp
${PROJECT_SOURCE_DIR}/test/util/tile_server_options.test.cpp
)
# MapSnapshotter uses headless backend that is rendering image on a background thread. QT / macOS adaptation for headless backend creates

Binary file not shown.

View File

@@ -0,0 +1,142 @@
#include <mbgl/storage/mbtiles_file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/run_loop.hpp>
#include <climits>
#include <gtest/gtest.h>
#if defined(WIN32)
# include <Windows.h>
# ifndef PATH_MAX
# define PATH_MAX MAX_PATH
# endif /* PATH_MAX */
#else
# include <unistd.h>
#endif
namespace {
std::string toAbsoluteURL(const std::string &fileName) {
char buff[PATH_MAX + 1];
#ifdef _MSC_VER
char *cwd = _getcwd(buff, PATH_MAX + 1);
#else
char *cwd = getcwd(buff, PATH_MAX + 1);
#endif
std::string url
= {"mbtiles://" + std::string(cwd) + "/test/fixtures/storage/mbtiles/" + fileName};
assert(url.size() <= PATH_MAX);
return url;
}
} // namespace
using namespace mbgl;
TEST(MBTilesFileSource, AcceptsURL) {
MBTilesFileSource mbtiles(ResourceOptions::Default());
EXPECT_TRUE(mbtiles.canRequest(Resource::style("mbtiles:///test")));
EXPECT_FALSE(mbtiles.canRequest(Resource::style("mbtile://test")));
EXPECT_FALSE(mbtiles.canRequest(Resource::style("mbtiles:")));
EXPECT_FALSE(mbtiles.canRequest(Resource::style("")));
}
// mbtiles paths must be absolute
TEST(MBTilesFileSource, AbsolutePath) {
util::RunLoop loop;
MBTilesFileSource mbtiles(ResourceOptions::Default());
std::unique_ptr<AsyncRequest> req = mbtiles.request(
{Resource::Unknown, "mbtiles://not_absolute"}, [&](Response res) {
req.reset();
ASSERT_NE(nullptr, res.error);
EXPECT_EQ(Response::Error::Reason::Other, res.error->reason);
EXPECT_NE((res.error->message).find("absolute"), std::string::npos);
ASSERT_FALSE(res.data.get());
loop.stop();
});
loop.run();
}
// Nonexistent mbtiles file raises error
TEST(MBTilesFileSource, NonExistentFile) {
util::RunLoop loop;
MBTilesFileSource mbtiles(ResourceOptions::Default());
std::unique_ptr<AsyncRequest> req = mbtiles.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);
EXPECT_NE((res.error->message).find("path not found"), std::string::npos);
ASSERT_FALSE(res.data.get());
loop.stop();
});
loop.run();
}
// Existing mbtiles file default request returns TileJSON
TEST(MBTilesFileSource, TileJSON) {
util::RunLoop loop;
MBTilesFileSource mbtiles(ResourceOptions::Default());
std::unique_ptr<AsyncRequest> req = mbtiles.request(
{Resource::Unknown, toAbsoluteURL("geography-class-png.mbtiles")}, [&](Response res) {
req.reset();
EXPECT_EQ(nullptr, res.error);
ASSERT_TRUE(res.data.get());
// basic test that TileJSON included a tile URL
EXPECT_NE((*res.data).find("geography-class-png.mbtiles?file={x}/{y}/{z}"),
std::string::npos);
loop.stop();
});
loop.run();
}
// Existing tiles return tile data
TEST(MBTilesFileSource, Tile) {
util::RunLoop loop;
MBTilesFileSource mbtiles(ResourceOptions::Default());
std::unique_ptr<AsyncRequest> req = mbtiles.request(
Resource::tile(toAbsoluteURL("geography-class-png.mbtiles?file={z}/{x}/{y}.png"), 1.0, 0, 0, 0, Tileset::Scheme::XYZ),
[&](Response res) {
req.reset();
EXPECT_EQ(nullptr, res.error);
ASSERT_TRUE(res.data.get());
ASSERT_EQ(res.noContent, false);
loop.stop();
});
loop.run();
}
// Nonexistent tiles do not raise errors, they simply return no content
TEST(MBTilesFileSource, NonExistentTile) {
util::RunLoop loop;
MBTilesFileSource mbtiles(ResourceOptions::Default());
std::unique_ptr<AsyncRequest> req = mbtiles.request(
Resource::tile(toAbsoluteURL("geography-class-png.mbtiles?file={z}/{x}/{y}.png"), 1.0, 0, 0, 4, Tileset::Scheme::XYZ),
[&](Response res) {
req.reset();
EXPECT_EQ(nullptr, res.error);
ASSERT_FALSE(res.data.get());
ASSERT_EQ(res.noContent, true);
loop.stop();
});
loop.run();
}