Handle nonexistent mbtiles files, add MBTilesFileSource tests (#292)
* Handle nonexistent mbtiles files, add MBTilesFileSource tests * minor formatting fix
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
test/fixtures/storage/mbtiles/geography-class-png.mbtiles
vendored
Normal file
BIN
test/fixtures/storage/mbtiles/geography-class-png.mbtiles
vendored
Normal file
Binary file not shown.
142
test/storage/mbtiles_file_source.test.cpp
Normal file
142
test/storage/mbtiles_file_source.test.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user