1 #pragma once
2 
3 #include <mbgl/tile/tile_loader.hpp>
4 #include <mbgl/storage/file_source.hpp>
5 #include <mbgl/renderer/tile_parameters.hpp>
6 #include <mbgl/util/tileset.hpp>
7 
8 #include <cassert>
9 
10 namespace mbgl {
11 
12 template <typename T>
TileLoader(T & tile_,const OverscaledTileID & id,const TileParameters & parameters,const Tileset & tileset)13 TileLoader<T>::TileLoader(T& tile_,
14                           const OverscaledTileID& id,
15                           const TileParameters& parameters,
16                           const Tileset& tileset)
17     : tile(tile_),
18       necessity(TileNecessity::Optional),
19       resource(Resource::tile(
20         tileset.tiles.at(0),
21         parameters.pixelRatio,
22         id.canonical.x,
23         id.canonical.y,
24         id.canonical.z,
25         tileset.scheme,
26         Resource::LoadingMethod::CacheOnly)),
27       fileSource(parameters.fileSource) {
28     assert(!request);
29     if (fileSource.supportsCacheOnlyRequests()) {
30         // When supported, the first request is always optional, even if the TileLoader
31         // is marked as required. That way, we can let the first optional request continue
32         // to load when the TileLoader is later changed from required to optional. If we
33         // started out with a required request, we'd have to cancel everything, including the
34         // initial optional part of the request.
35         loadFromCache();
36     } else if (necessity == TileNecessity::Required) {
37         // When the file source doesn't support cache-only requests, and we definiitely need this
38         // data, we can start out with a network request immediately.
39         loadFromNetwork();
40     } else {
41         // When the FileSource doesn't support cache-only requests, we do nothing until the
42         // data is definitely required.
43     }
44 }
45 
46 template <typename T>
47 TileLoader<T>::~TileLoader() = default;
48 
49 template <typename T>
loadFromCache()50 void TileLoader<T>::loadFromCache() {
51     assert(!request);
52 
53     resource.loadingMethod = Resource::LoadingMethod::CacheOnly;
54     request = fileSource.request(resource, [this](Response res) {
55         request.reset();
56 
57         tile.setTriedCache();
58 
59         if (res.error && res.error->reason == Response::Error::Reason::NotFound) {
60             // When the cache-only request could not be satisfied, don't treat it as an error.
61             // A cache lookup could still return data, _and_ an error, in particular when we were
62             // able to find the data, but it is expired and the Cache-Control headers indicated that
63             // we aren't allowed to use expired responses. In this case, we still get the data which
64             // we can use in our conditional network request.
65             resource.priorModified = res.modified;
66             resource.priorExpires = res.expires;
67             resource.priorEtag = res.etag;
68             resource.priorData = res.data;
69         } else {
70             loadedData(res);
71         }
72 
73         if (necessity == TileNecessity::Required) {
74             loadFromNetwork();
75         }
76     });
77 }
78 
79 template <typename T>
makeRequired()80 void TileLoader<T>::makeRequired() {
81     if (!request) {
82         loadFromNetwork();
83     }
84 }
85 
86 template <typename T>
makeOptional()87 void TileLoader<T>::makeOptional() {
88     if (resource.loadingMethod == Resource::LoadingMethod::NetworkOnly && request) {
89         // Abort the current request, but only when we know that we're specifically querying for a
90         // network resource only.
91         request.reset();
92     }
93 }
94 
95 template <typename T>
loadedData(const Response & res)96 void TileLoader<T>::loadedData(const Response& res) {
97     if (res.error && res.error->reason != Response::Error::Reason::NotFound) {
98         tile.setError(std::make_exception_ptr(std::runtime_error(res.error->message)));
99     } else if (res.notModified) {
100         resource.priorExpires = res.expires;
101         // Do not notify the tile; when we get this message, it already has the current
102         // version of the data.
103         tile.setMetadata(res.modified, res.expires);
104     } else {
105         resource.priorModified = res.modified;
106         resource.priorExpires = res.expires;
107         resource.priorEtag = res.etag;
108         tile.setMetadata(res.modified, res.expires);
109         tile.setData(res.noContent ? nullptr : res.data);
110     }
111 }
112 
113 template <typename T>
loadFromNetwork()114 void TileLoader<T>::loadFromNetwork() {
115     assert(!request);
116 
117     // Instead of using Resource::LoadingMethod::All, we're first doing a CacheOnly, and then a
118     // NetworkOnly request.
119     resource.loadingMethod = Resource::LoadingMethod::NetworkOnly;
120     request = fileSource.request(resource, [this](Response res) { loadedData(res); });
121 }
122 
123 } // namespace mbgl
124