1 #include <mbgl/storage/default_file_source.hpp>
2 #include <mbgl/storage/asset_file_source.hpp>
3 #include <mbgl/storage/file_source_request.hpp>
4 #include <mbgl/storage/local_file_source.hpp>
5 #include <mbgl/storage/online_file_source.hpp>
6 #include <mbgl/storage/offline_database.hpp>
7 #include <mbgl/storage/offline_download.hpp>
8 #include <mbgl/storage/resource_transform.hpp>
9 
10 #include <mbgl/util/platform.hpp>
11 #include <mbgl/util/url.hpp>
12 #include <mbgl/util/thread.hpp>
13 #include <mbgl/util/work_request.hpp>
14 #include <mbgl/util/stopwatch.hpp>
15 
16 #include <cassert>
17 
18 namespace mbgl {
19 
20 class DefaultFileSource::Impl {
21 public:
Impl(std::shared_ptr<FileSource> assetFileSource_,std::string cachePath,uint64_t maximumCacheSize)22     Impl(std::shared_ptr<FileSource> assetFileSource_, std::string cachePath, uint64_t maximumCacheSize)
23             : assetFileSource(assetFileSource_)
24             , localFileSource(std::make_unique<LocalFileSource>())
25             , offlineDatabase(std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize)) {
26     }
27 
setAPIBaseURL(const std::string & url)28     void setAPIBaseURL(const std::string& url) {
29         onlineFileSource.setAPIBaseURL(url);
30     }
31 
getAPIBaseURL() const32     std::string getAPIBaseURL() const{
33         return onlineFileSource.getAPIBaseURL();
34     }
35 
setAccessToken(const std::string & accessToken)36     void setAccessToken(const std::string& accessToken) {
37         onlineFileSource.setAccessToken(accessToken);
38     }
39 
getAccessToken() const40     std::string getAccessToken() const {
41         return onlineFileSource.getAccessToken();
42     }
43 
setResourceTransform(optional<ActorRef<ResourceTransform>> && transform)44     void setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) {
45         onlineFileSource.setResourceTransform(std::move(transform));
46     }
47 
listRegions(std::function<void (std::exception_ptr,optional<std::vector<OfflineRegion>>)> callback)48     void listRegions(std::function<void (std::exception_ptr, optional<std::vector<OfflineRegion>>)> callback) {
49         try {
50             callback({}, offlineDatabase->listRegions());
51         } catch (...) {
52             callback(std::current_exception(), {});
53         }
54     }
55 
createRegion(const OfflineRegionDefinition & definition,const OfflineRegionMetadata & metadata,std::function<void (std::exception_ptr,optional<OfflineRegion>)> callback)56     void createRegion(const OfflineRegionDefinition& definition,
57                       const OfflineRegionMetadata& metadata,
58                       std::function<void (std::exception_ptr, optional<OfflineRegion>)> callback) {
59         try {
60             callback({}, offlineDatabase->createRegion(definition, metadata));
61         } catch (...) {
62             callback(std::current_exception(), {});
63         }
64     }
65 
updateMetadata(const int64_t regionID,const OfflineRegionMetadata & metadata,std::function<void (std::exception_ptr,optional<OfflineRegionMetadata>)> callback)66     void updateMetadata(const int64_t regionID,
67                       const OfflineRegionMetadata& metadata,
68                       std::function<void (std::exception_ptr, optional<OfflineRegionMetadata>)> callback) {
69         try {
70             callback({}, offlineDatabase->updateMetadata(regionID, metadata));
71         } catch (...) {
72             callback(std::current_exception(), {});
73         }
74     }
75 
getRegionStatus(int64_t regionID,std::function<void (std::exception_ptr,optional<OfflineRegionStatus>)> callback)76     void getRegionStatus(int64_t regionID, std::function<void (std::exception_ptr, optional<OfflineRegionStatus>)> callback) {
77         try {
78             callback({}, getDownload(regionID).getStatus());
79         } catch (...) {
80             callback(std::current_exception(), {});
81         }
82     }
83 
deleteRegion(OfflineRegion && region,std::function<void (std::exception_ptr)> callback)84     void deleteRegion(OfflineRegion&& region, std::function<void (std::exception_ptr)> callback) {
85         try {
86             downloads.erase(region.getID());
87             offlineDatabase->deleteRegion(std::move(region));
88             callback({});
89         } catch (...) {
90             callback(std::current_exception());
91         }
92     }
93 
setRegionObserver(int64_t regionID,std::unique_ptr<OfflineRegionObserver> observer)94     void setRegionObserver(int64_t regionID, std::unique_ptr<OfflineRegionObserver> observer) {
95         getDownload(regionID).setObserver(std::move(observer));
96     }
97 
setRegionDownloadState(int64_t regionID,OfflineRegionDownloadState state)98     void setRegionDownloadState(int64_t regionID, OfflineRegionDownloadState state) {
99         getDownload(regionID).setState(state);
100     }
101 
request(AsyncRequest * req,Resource resource,ActorRef<FileSourceRequest> ref)102     void request(AsyncRequest* req, Resource resource, ActorRef<FileSourceRequest> ref) {
103         auto callback = [ref] (const Response& res) mutable {
104             ref.invoke(&FileSourceRequest::setResponse, res);
105         };
106 
107         if (AssetFileSource::acceptsURL(resource.url)) {
108             //Asset request
109             tasks[req] = assetFileSource->request(resource, callback);
110         } else if (LocalFileSource::acceptsURL(resource.url)) {
111             //Local file request
112             tasks[req] = localFileSource->request(resource, callback);
113         } else {
114             // Try the offline database
115             if (resource.hasLoadingMethod(Resource::LoadingMethod::Cache)) {
116                 auto offlineResponse = offlineDatabase->get(resource);
117 
118                 if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) {
119                     if (!offlineResponse) {
120                         // Ensure there's always a response that we can send, so the caller knows that
121                         // there's no optional data available in the cache, when it's the only place
122                         // we're supposed to load from.
123                         offlineResponse.emplace();
124                         offlineResponse->noContent = true;
125                         offlineResponse->error = std::make_unique<Response::Error>(
126                                 Response::Error::Reason::NotFound, "Not found in offline database");
127                     } else if (!offlineResponse->isUsable()) {
128                         // Don't return resources the server requested not to show when they're stale.
129                         // Even if we can't directly use the response, we may still use it to send a
130                         // conditional HTTP request, which is why we're saving it above.
131                         offlineResponse->error = std::make_unique<Response::Error>(
132                             Response::Error::Reason::NotFound, "Cached resource is unusable");
133                     }
134                     callback(*offlineResponse);
135                 } else if (offlineResponse) {
136                     // Copy over the fields so that we can use them when making a refresh request.
137                     resource.priorModified = offlineResponse->modified;
138                     resource.priorExpires = offlineResponse->expires;
139                     resource.priorEtag = offlineResponse->etag;
140                     resource.priorData = offlineResponse->data;
141 
142                     if (offlineResponse->isUsable()) {
143                         callback(*offlineResponse);
144                     }
145                 }
146             }
147 
148             // Get from the online file source
149             if (resource.hasLoadingMethod(Resource::LoadingMethod::Network)) {
150                 MBGL_TIMING_START(watch);
151                 tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable {
152                     this->offlineDatabase->put(resource, onlineResponse);
153                     if (resource.kind == Resource::Kind::Tile) {
154                         // onlineResponse.data will be null if data not modified
155                         MBGL_TIMING_FINISH(watch,
156                                            " Action: " << "Requesting," <<
157                                            " URL: " << resource.url.c_str() <<
158                                            " Size: " << (onlineResponse.data != nullptr ? onlineResponse.data->size() : 0) << "B," <<
159                                            " Time")
160                     }
161                     callback(onlineResponse);
162                 });
163             }
164         }
165     }
166 
cancel(AsyncRequest * req)167     void cancel(AsyncRequest* req) {
168         tasks.erase(req);
169     }
170 
setOfflineMapboxTileCountLimit(uint64_t limit)171     void setOfflineMapboxTileCountLimit(uint64_t limit) {
172         offlineDatabase->setOfflineMapboxTileCountLimit(limit);
173     }
174 
setOnlineStatus(const bool status)175     void setOnlineStatus(const bool status) {
176         onlineFileSource.setOnlineStatus(status);
177     }
178 
put(const Resource & resource,const Response & response)179     void put(const Resource& resource, const Response& response) {
180         offlineDatabase->put(resource, response);
181     }
182 
183 private:
getDownload(int64_t regionID)184     OfflineDownload& getDownload(int64_t regionID) {
185         auto it = downloads.find(regionID);
186         if (it != downloads.end()) {
187             return *it->second;
188         }
189         return *downloads.emplace(regionID,
190             std::make_unique<OfflineDownload>(regionID, offlineDatabase->getRegionDefinition(regionID), *offlineDatabase, onlineFileSource)).first->second;
191     }
192 
193     // shared so that destruction is done on the creating thread
194     const std::shared_ptr<FileSource> assetFileSource;
195     const std::unique_ptr<FileSource> localFileSource;
196     std::unique_ptr<OfflineDatabase> offlineDatabase;
197     OnlineFileSource onlineFileSource;
198     std::unordered_map<AsyncRequest*, std::unique_ptr<AsyncRequest>> tasks;
199     std::unordered_map<int64_t, std::unique_ptr<OfflineDownload>> downloads;
200 };
201 
DefaultFileSource(const std::string & cachePath,const std::string & assetRoot,uint64_t maximumCacheSize)202 DefaultFileSource::DefaultFileSource(const std::string& cachePath,
203                                      const std::string& assetRoot,
204                                      uint64_t maximumCacheSize)
205     : DefaultFileSource(cachePath, std::make_unique<AssetFileSource>(assetRoot), maximumCacheSize) {
206 }
207 
DefaultFileSource(const std::string & cachePath,std::unique_ptr<FileSource> && assetFileSource_,uint64_t maximumCacheSize)208 DefaultFileSource::DefaultFileSource(const std::string& cachePath,
209                                      std::unique_ptr<FileSource>&& assetFileSource_,
210                                      uint64_t maximumCacheSize)
211         : assetFileSource(std::move(assetFileSource_))
212         , impl(std::make_unique<util::Thread<Impl>>("DefaultFileSource", assetFileSource, cachePath, maximumCacheSize)) {
213 }
214 
215 DefaultFileSource::~DefaultFileSource() = default;
216 
setAPIBaseURL(const std::string & baseURL)217 void DefaultFileSource::setAPIBaseURL(const std::string& baseURL) {
218     impl->actor().invoke(&Impl::setAPIBaseURL, baseURL);
219 
220     {
221         std::lock_guard<std::mutex> lock(cachedBaseURLMutex);
222         cachedBaseURL = baseURL;
223     }
224 }
225 
getAPIBaseURL()226 std::string DefaultFileSource::getAPIBaseURL() {
227     std::lock_guard<std::mutex> lock(cachedBaseURLMutex);
228     return cachedBaseURL;
229 }
230 
setAccessToken(const std::string & accessToken)231 void DefaultFileSource::setAccessToken(const std::string& accessToken) {
232     impl->actor().invoke(&Impl::setAccessToken, accessToken);
233 
234     {
235         std::lock_guard<std::mutex> lock(cachedAccessTokenMutex);
236         cachedAccessToken = accessToken;
237     }
238 }
239 
getAccessToken()240 std::string DefaultFileSource::getAccessToken() {
241     std::lock_guard<std::mutex> lock(cachedAccessTokenMutex);
242     return cachedAccessToken;
243 }
244 
setResourceTransform(optional<ActorRef<ResourceTransform>> && transform)245 void DefaultFileSource::setResourceTransform(optional<ActorRef<ResourceTransform>>&& transform) {
246     impl->actor().invoke(&Impl::setResourceTransform, std::move(transform));
247 }
248 
request(const Resource & resource,Callback callback)249 std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resource, Callback callback) {
250     auto req = std::make_unique<FileSourceRequest>(std::move(callback));
251 
252     req->onCancel([fs = impl->actor(), req = req.get()] () mutable { fs.invoke(&Impl::cancel, req); });
253 
254     impl->actor().invoke(&Impl::request, req.get(), resource, req->actor());
255 
256     return std::move(req);
257 }
258 
listOfflineRegions(std::function<void (std::exception_ptr,optional<std::vector<OfflineRegion>>)> callback)259 void DefaultFileSource::listOfflineRegions(std::function<void (std::exception_ptr, optional<std::vector<OfflineRegion>>)> callback) {
260     impl->actor().invoke(&Impl::listRegions, callback);
261 }
262 
createOfflineRegion(const OfflineRegionDefinition & definition,const OfflineRegionMetadata & metadata,std::function<void (std::exception_ptr,optional<OfflineRegion>)> callback)263 void DefaultFileSource::createOfflineRegion(const OfflineRegionDefinition& definition,
264                                             const OfflineRegionMetadata& metadata,
265                                             std::function<void (std::exception_ptr, optional<OfflineRegion>)> callback) {
266     impl->actor().invoke(&Impl::createRegion, definition, metadata, callback);
267 }
268 
updateOfflineMetadata(const int64_t regionID,const OfflineRegionMetadata & metadata,std::function<void (std::exception_ptr,optional<OfflineRegionMetadata>)> callback)269 void DefaultFileSource::updateOfflineMetadata(const int64_t regionID,
270                                             const OfflineRegionMetadata& metadata,
271                                             std::function<void (std::exception_ptr, optional<OfflineRegionMetadata>)> callback) {
272     impl->actor().invoke(&Impl::updateMetadata, regionID, metadata, callback);
273 }
274 
deleteOfflineRegion(OfflineRegion && region,std::function<void (std::exception_ptr)> callback)275 void DefaultFileSource::deleteOfflineRegion(OfflineRegion&& region, std::function<void (std::exception_ptr)> callback) {
276     impl->actor().invoke(&Impl::deleteRegion, std::move(region), callback);
277 }
278 
setOfflineRegionObserver(OfflineRegion & region,std::unique_ptr<OfflineRegionObserver> observer)279 void DefaultFileSource::setOfflineRegionObserver(OfflineRegion& region, std::unique_ptr<OfflineRegionObserver> observer) {
280     impl->actor().invoke(&Impl::setRegionObserver, region.getID(), std::move(observer));
281 }
282 
setOfflineRegionDownloadState(OfflineRegion & region,OfflineRegionDownloadState state)283 void DefaultFileSource::setOfflineRegionDownloadState(OfflineRegion& region, OfflineRegionDownloadState state) {
284     impl->actor().invoke(&Impl::setRegionDownloadState, region.getID(), state);
285 }
286 
getOfflineRegionStatus(OfflineRegion & region,std::function<void (std::exception_ptr,optional<OfflineRegionStatus>)> callback) const287 void DefaultFileSource::getOfflineRegionStatus(OfflineRegion& region, std::function<void (std::exception_ptr, optional<OfflineRegionStatus>)> callback) const {
288     impl->actor().invoke(&Impl::getRegionStatus, region.getID(), callback);
289 }
290 
setOfflineMapboxTileCountLimit(uint64_t limit) const291 void DefaultFileSource::setOfflineMapboxTileCountLimit(uint64_t limit) const {
292     impl->actor().invoke(&Impl::setOfflineMapboxTileCountLimit, limit);
293 }
294 
pause()295 void DefaultFileSource::pause() {
296     impl->pause();
297 }
298 
resume()299 void DefaultFileSource::resume() {
300     impl->resume();
301 }
302 
303 // For testing only:
304 
setOnlineStatus(const bool status)305 void DefaultFileSource::setOnlineStatus(const bool status) {
306     impl->actor().invoke(&Impl::setOnlineStatus, status);
307 }
308 
put(const Resource & resource,const Response & response)309 void DefaultFileSource::put(const Resource& resource, const Response& response) {
310     impl->actor().invoke(&Impl::put, resource, response);
311 }
312 
313 } // namespace mbgl
314