#include #include #include #include #include #include #include #include #include #include #include namespace { const std::string assetProtocol = "asset://"; bool isAssetURL(const std::string& url) { return std::equal(assetProtocol.begin(), assetProtocol.end(), url.begin()); } } // namespace namespace mbgl { class DefaultFileSource::Impl { public: Impl(const std::string& cachePath, uint64_t maximumCacheSize) : offlineDatabase(cachePath, maximumCacheSize) { } void setAPIBaseURL(const std::string& url) { onlineFileSource.setAPIBaseURL(url); } std::string getAPIBaseURL() const{ return onlineFileSource.getAPIBaseURL(); } void setAccessToken(const std::string& accessToken) { onlineFileSource.setAccessToken(accessToken); } std::string getAccessToken() const { return onlineFileSource.getAccessToken(); } void setResourceTransform(OnlineFileSource::ResourceTransform&& transform) { onlineFileSource.setResourceTransform(std::move(transform)); } void listRegions(std::function>)> callback) { try { callback({}, offlineDatabase.listRegions()); } catch (...) { callback(std::current_exception(), {}); } } void createRegion(const OfflineRegionDefinition& definition, const OfflineRegionMetadata& metadata, std::function)> callback) { try { callback({}, offlineDatabase.createRegion(definition, metadata)); } catch (...) { callback(std::current_exception(), {}); } } void updateMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata, std::function)> callback) { try { callback({}, offlineDatabase.updateMetadata(regionID, metadata)); } catch (...) { callback(std::current_exception(), {}); } } void getRegionStatus(int64_t regionID, std::function)> callback) { try { callback({}, getDownload(regionID).getStatus()); } catch (...) { callback(std::current_exception(), {}); } } void deleteRegion(OfflineRegion&& region, std::function callback) { try { downloads.erase(region.getID()); offlineDatabase.deleteRegion(std::move(region)); callback({}); } catch (...) { callback(std::current_exception()); } } void setRegionObserver(int64_t regionID, std::unique_ptr observer) { getDownload(regionID).setObserver(std::move(observer)); } void setRegionDownloadState(int64_t regionID, OfflineRegionDownloadState state) { getDownload(regionID).setState(state); } void request(AsyncRequest* req, Resource resource, Callback callback) { Resource revalidation = resource; const bool hasPrior = resource.priorEtag || resource.priorModified || resource.priorExpires; if (!hasPrior || resource.necessity == Resource::Optional) { auto offlineResponse = offlineDatabase.get(resource); if (resource.necessity == Resource::Optional && !offlineResponse) { // Ensure there's always a response that we can send, so the caller knows that // there's no optional data available in the cache. offlineResponse.emplace(); offlineResponse->noContent = true; offlineResponse->error = std::make_unique( Response::Error::Reason::NotFound, "Not found in offline database"); } if (offlineResponse) { revalidation.priorModified = offlineResponse->modified; revalidation.priorExpires = offlineResponse->expires; revalidation.priorEtag = offlineResponse->etag; callback(*offlineResponse); } } if (resource.necessity == Resource::Required) { tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) { this->offlineDatabase.put(revalidation, onlineResponse); callback(onlineResponse); }); } } void cancel(AsyncRequest* req) { tasks.erase(req); } void setOfflineMapboxTileCountLimit(uint64_t limit) { offlineDatabase.setOfflineMapboxTileCountLimit(limit); } void put(const Resource& resource, const Response& response) { offlineDatabase.put(resource, response); } private: OfflineDownload& getDownload(int64_t regionID) { auto it = downloads.find(regionID); if (it != downloads.end()) { return *it->second; } return *downloads.emplace(regionID, std::make_unique(regionID, offlineDatabase.getRegionDefinition(regionID), offlineDatabase, onlineFileSource)).first->second; } OfflineDatabase offlineDatabase; OnlineFileSource onlineFileSource; std::unordered_map> tasks; std::unordered_map> downloads; }; DefaultFileSource::DefaultFileSource(const std::string& cachePath, const std::string& assetRoot, uint64_t maximumCacheSize) : thread(std::make_unique>(util::ThreadContext{"DefaultFileSource", util::ThreadPriority::Low}, cachePath, maximumCacheSize)), assetFileSource(std::make_unique(assetRoot)), localFileSource(std::make_unique()) { } DefaultFileSource::~DefaultFileSource() = default; void DefaultFileSource::setAPIBaseURL(const std::string& baseURL) { thread->invoke(&Impl::setAPIBaseURL, baseURL); cachedBaseURL = baseURL; } std::string DefaultFileSource::getAPIBaseURL() const { return cachedBaseURL; } void DefaultFileSource::setAccessToken(const std::string& accessToken) { thread->invoke(&Impl::setAccessToken, accessToken); cachedAccessToken = accessToken; } std::string DefaultFileSource::getAccessToken() const { return cachedAccessToken; } void DefaultFileSource::setResourceTransform(std::function transform) { auto loop = util::RunLoop::Get(); thread->invoke(&Impl::setResourceTransform, [loop, transform](Resource::Kind kind_, std::string&& url_, auto callback_) { return loop->invokeWithCallback([transform](Resource::Kind kind, std::string&& url, auto callback) { callback(transform(kind, std::move(url))); }, kind_, std::move(url_), callback_); }); } std::unique_ptr DefaultFileSource::request(const Resource& resource, Callback callback) { class DefaultFileRequest : public AsyncRequest { public: DefaultFileRequest(Resource resource_, FileSource::Callback callback_, util::Thread& thread_) : thread(thread_), workRequest(thread.invokeWithCallback(&DefaultFileSource::Impl::request, this, resource_, callback_)) { } ~DefaultFileRequest() override { thread.invoke(&DefaultFileSource::Impl::cancel, this); } util::Thread& thread; std::unique_ptr workRequest; }; 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); } } void DefaultFileSource::listOfflineRegions(std::function>)> callback) { thread->invoke(&Impl::listRegions, callback); } void DefaultFileSource::createOfflineRegion(const OfflineRegionDefinition& definition, const OfflineRegionMetadata& metadata, std::function)> callback) { thread->invoke(&Impl::createRegion, definition, metadata, callback); } void DefaultFileSource::updateOfflineMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata, std::function)> callback) { thread->invoke(&Impl::updateMetadata, regionID, metadata, callback); } void DefaultFileSource::deleteOfflineRegion(OfflineRegion&& region, std::function callback) { thread->invoke(&Impl::deleteRegion, std::move(region), callback); } void DefaultFileSource::setOfflineRegionObserver(OfflineRegion& region, std::unique_ptr observer) { thread->invoke(&Impl::setRegionObserver, region.getID(), std::move(observer)); } void DefaultFileSource::setOfflineRegionDownloadState(OfflineRegion& region, OfflineRegionDownloadState state) { thread->invoke(&Impl::setRegionDownloadState, region.getID(), state); } void DefaultFileSource::getOfflineRegionStatus(OfflineRegion& region, std::function)> callback) const { thread->invoke(&Impl::getRegionStatus, region.getID(), callback); } void DefaultFileSource::setOfflineMapboxTileCountLimit(uint64_t limit) const { thread->invokeSync(&Impl::setOfflineMapboxTileCountLimit, limit); } void DefaultFileSource::pause() { thread->pause(); } void DefaultFileSource::resume() { thread->resume(); } // For testing only: void DefaultFileSource::put(const Resource& resource, const Response& response) { thread->invokeSync(&Impl::put, resource, response); } } // namespace mbgl