Files
MapLibreParticleLab/src/storage/sqlite_store.cpp
Konstantin Käfer 214f99673f fix variable shadowing
2014-10-22 18:19:01 +02:00

228 lines
7.5 KiB
C++

#include <mbgl/storage/sqlite_store.hpp>
#include <mbgl/util/compression.hpp>
#include <mbgl/util/sqlite3.hpp>
#include <mbgl/util/uv-worker.h>
#include <cassert>
using namespace mapbox::sqlite;
std::string removeAccessTokenFromURL(const std::string &url) {
const size_t token_start = url.find("access_token=");
// Ensure that token exists, isn't at the front and is preceded by either & or ?.
if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) {
return url;
}
const size_t token_end = url.find_first_of('&', token_start);
if (token_end == std::string::npos) {
// The token is the last query argument. We slice away the "&access_token=..." part
return url.substr(0, token_start - 1);
} else {
// We slice away the "access_token=...&" part.
return url.substr(0, token_start) + url.substr(token_end + 1);
}
}
std::string convertMapboxDomainsToProtocol(const std::string &url) {
const size_t protocol_separator = url.find("://");
if (protocol_separator == std::string::npos) {
return url;
}
const std::string protocol = url.substr(0, protocol_separator);
if (!(protocol == "http" || protocol == "https")) {
return url;
}
const size_t domain_begin = protocol_separator + 3;
const size_t path_separator = url.find("/", domain_begin);
if (path_separator == std::string::npos) {
return url;
}
const std::string domain = url.substr(domain_begin, path_separator - domain_begin);
if (domain.find(".tiles.mapbox.com") != std::string::npos) {
return "mapbox://" + url.substr(path_separator + 1);
} else {
return url;
}
}
std::string unifyMapboxURLs(const std::string &url) {
return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url));
}
namespace mbgl {
SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path)
: thread_id(uv_thread_self()),
db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) {
createSchema();
worker = new uv_worker_t;
uv_worker_init(worker, loop, 1, "SQLite");
}
SQLiteStore::~SQLiteStore() {
// Nothing to do. This function needs to be here because we're forward-declaring
// Database, so we need the actual definition here to be able to properly destruct it.
if (worker) {
uv_worker_close(worker, [](uv_worker_t *worker_handle) {
delete worker_handle;
});
}
}
void SQLiteStore::createSchema() {
if (!db || !*db) {
return;
}
db->exec("CREATE TABLE IF NOT EXISTS `http_cache` ("
" `url` TEXT PRIMARY KEY NOT NULL,"
" `code` INTEGER NOT NULL,"
" `type` INTEGER NOT NULL,"
" `modified` INTEGER,"
" `etag` TEXT,"
" `expires` INTEGER,"
" `data` BLOB,"
" `compressed` INTEGER NOT NULL DEFAULT 0"
");"
"CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);");
}
struct GetBaton {
util::ptr<Database> db;
std::string path;
ResourceType type;
void *ptr = nullptr;
SQLiteStore::GetCallback callback = nullptr;
std::unique_ptr<Response> response;
};
void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr) {
assert(uv_thread_self() == thread_id);
if (!db || !*db) {
if (callback) {
callback(nullptr, ptr);
}
return;
}
GetBaton *get_baton = new GetBaton;
get_baton->db = db;
get_baton->path = path;
get_baton->ptr = ptr;
get_baton->callback = callback;
uv_worker_send(worker, get_baton, [](void *data) {
GetBaton *baton = (GetBaton *)data;
const std::string url = unifyMapboxURLs(baton->path);
// 0 1 2
Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, "
// 3 4 5 6
"`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
stmt.bind(1, url.c_str());
if (stmt.run()) {
// There is data.
baton->response = std::unique_ptr<Response>(new Response);
baton->response->code = stmt.get<int>(0);
baton->type = ResourceType(stmt.get<int>(1));
baton->response->modified = stmt.get<int64_t>(2);
baton->response->etag = stmt.get<std::string>(3);
baton->response->expires = stmt.get<int64_t>(4);
baton->response->data = stmt.get<std::string>(5);
if (stmt.get<int>(6)) { // == compressed
baton->response->data = util::decompress(baton->response->data);
}
} else {
// There is no data.
// This is a noop.
}
}, [](void *data) {
std::unique_ptr<GetBaton> baton { (GetBaton *)data };
if (baton->callback) {
baton->callback(std::move(baton->response), baton->ptr);
}
});
}
struct PutBaton {
util::ptr<Database> db;
std::string path;
ResourceType type;
Response response;
};
void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) {
assert(uv_thread_self() == thread_id);
if (!db) return;
PutBaton *put_baton = new PutBaton;
put_baton->db = db;
put_baton->path = path;
put_baton->type = type;
put_baton->response = response;
uv_worker_send(worker, put_baton, [](void *data) {
PutBaton *baton = (PutBaton *)data;
const std::string url = unifyMapboxURLs(baton->path);
Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` ("
// 1 2 3 4 5 6 7 8
"`url`, `code`, `type`, `modified`, `etag`, `expires`, `data`, `compressed`"
") VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
stmt.bind(1, url.c_str());
stmt.bind(2, int(baton->response.code));
stmt.bind(3, int(baton->type));
stmt.bind(4, baton->response.modified);
stmt.bind(5, baton->response.etag.c_str());
stmt.bind(6, baton->response.expires);
if (baton->type == ResourceType::Image) {
stmt.bind(7, baton->response.data, false); // do not retain the string internally.
stmt.bind(8, false);
} else {
stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally.
stmt.bind(8, true);
}
stmt.run();
}, [](void *data) {
delete (PutBaton *)data;
});
}
struct ExpirationBaton {
util::ptr<Database> db;
std::string path;
int64_t expires;
};
void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) {
assert(uv_thread_self() == thread_id);
if (!db || !*db) return;
ExpirationBaton *expiration_baton = new ExpirationBaton;
expiration_baton->db = db;
expiration_baton->path = path;
expiration_baton->expires = expires;
uv_worker_send(worker, expiration_baton, [](void *data) {
ExpirationBaton *baton = (ExpirationBaton *)data;
const std::string url = unifyMapboxURLs(baton->path);
Statement stmt = // 1 2
baton->db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?");
stmt.bind<int64_t>(1, baton->expires);
stmt.bind(2, url.c_str());
stmt.run();
}, [](void *data) {
delete (ExpirationBaton *)data;
});
}
}