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