Skip to content

Commit 507ff51

Browse files
committed
Expose calculateStaleAt via CachedMapTileMetadata.fromHttpHeaders factory
1 parent 5c78329 commit 507ff51

File tree

3 files changed

+74
-66
lines changed

3 files changed

+74
-66
lines changed

lib/src/layer/tile_layer/tile_provider/network/caching/built_in/impl/native/workers/tile_and_size_monitor_writer.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,14 @@ Future<void> tileAndSizeMonitorWriterWorker(
176176
// We store the stale-at header in 8 signed bytes...
177177
..writeFromSync(
178178
allocInt64BufferTileWrite
179-
..buffer.asInt64List()[0] = metadata.staleAtMilliseconds,
179+
..buffer.asInt64List()[0] = metadata.staleAt.millisecondsSinceEpoch,
180180
)
181181
// ...followed by the last-modified header in 8 signed bytes, or '0' if
182182
// null
183183
..writeFromSync(
184184
allocInt64BufferTileWrite
185-
..buffer.asInt64List()[0] = metadata.lastModifiedMilliseconds ?? 0,
185+
..buffer.asInt64List()[0] =
186+
metadata.lastModified?.millisecondsSinceEpoch ?? 0,
186187
);
187188

188189
// We need to read the old etag length to compare their lengths
Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import 'dart:io' show HttpHeaders; // web safe!
1+
import 'dart:io' show HttpHeaders, HttpDate; // web safe!
2+
import 'dart:math';
23

34
import 'package:flutter_map/flutter_map.dart';
45
import 'package:meta/meta.dart';
@@ -14,26 +15,77 @@ import 'package:meta/meta.dart';
1415
@immutable
1516
class CachedMapTileMetadata {
1617
/// Create new metadata
17-
CachedMapTileMetadata({
18+
const CachedMapTileMetadata({
1819
required this.staleAt,
1920
required this.lastModified,
2021
required this.etag,
21-
}) : staleAtMilliseconds = staleAt.millisecondsSinceEpoch,
22-
lastModifiedMilliseconds = lastModified?.millisecondsSinceEpoch;
22+
});
2323

24-
/// The calculated time at which this tile becomes stale
25-
final DateTime staleAt;
24+
/// Create new metadata based off an HTTP response's headers
25+
///
26+
/// Where a response does not include enough information to calculate the
27+
/// freshness age, [fallbackFreshnessAge] is used.
28+
factory CachedMapTileMetadata.fromHttpHeaders(
29+
Map<String, String> headers, {
30+
Duration fallbackFreshnessAge = const Duration(days: 7),
31+
}) {
32+
// There is no guarantee that this meets the HTTP specification - however,
33+
// it was designed with it in mind
34+
DateTime calculateStaleAt() {
35+
final addToNow = DateTime.timestamp().add;
2636

27-
/// The calculated time at which this tile becomes stale, represented in
28-
/// [DateTime.millisecondsSinceEpoch]
29-
final int staleAtMilliseconds;
37+
if (headers[HttpHeaders.cacheControlHeader]?.toLowerCase()
38+
case final cacheControl?) {
39+
final maxAge = RegExp(r'max-age=(\d+)').firstMatch(cacheControl)?[1];
3040

31-
/// If available, the value in [HttpHeaders.lastModifiedHeader]
32-
final DateTime? lastModified;
41+
if (maxAge == null) {
42+
if (headers[HttpHeaders.expiresHeader]?.toLowerCase()
43+
case final expires?) {
44+
return HttpDate.parse(expires);
45+
}
46+
47+
return addToNow(fallbackFreshnessAge);
48+
}
49+
50+
if (headers[HttpHeaders.ageHeader] case final currentAge?) {
51+
return addToNow(
52+
Duration(seconds: int.parse(maxAge) - int.parse(currentAge)),
53+
);
54+
}
55+
56+
final estimatedAge = max(
57+
0,
58+
DateTime.timestamp()
59+
.difference(HttpDate.parse(headers[HttpHeaders.dateHeader]!))
60+
.inSeconds,
61+
);
62+
return addToNow(Duration(seconds: int.parse(maxAge) - estimatedAge));
63+
}
64+
65+
return addToNow(fallbackFreshnessAge);
66+
}
3367

34-
/// If available, the value in [HttpHeaders.lastModifiedHeader], represented
35-
/// in [DateTime.millisecondsSinceEpoch]
36-
final int? lastModifiedMilliseconds;
68+
final lastModified = headers[HttpHeaders.lastModifiedHeader];
69+
final etag = headers[HttpHeaders.etagHeader];
70+
71+
return CachedMapTileMetadata(
72+
staleAt: calculateStaleAt(),
73+
lastModified: lastModified != null ? HttpDate.parse(lastModified) : null,
74+
etag: etag,
75+
);
76+
}
77+
78+
/// The calculated time at which this tile becomes stale (UTC)
79+
///
80+
/// Tile providers should use [isStale] to check whether a tile is stale,
81+
/// instead of manually comparing this to the current timestamp.
82+
///
83+
/// This may have been calculated based off an HTTP response's headers using
84+
/// [CachedMapTileMetadata.fromHttpHeaders], or it may be custom.
85+
final DateTime staleAt;
86+
87+
/// If available, the value in [HttpHeaders.lastModifiedHeader] (UTC)
88+
final DateTime? lastModified;
3789

3890
/// If available, the value in [HttpHeaders.etagHeader]
3991
final String? etag;
@@ -45,14 +97,13 @@ class CachedMapTileMetadata {
4597
bool get isStale => DateTime.timestamp().isAfter(staleAt);
4698

4799
@override
48-
int get hashCode =>
49-
Object.hash(staleAtMilliseconds, lastModifiedMilliseconds, etag);
100+
int get hashCode => Object.hash(staleAt, lastModified, etag);
50101

51102
@override
52103
bool operator ==(Object other) =>
53104
identical(this, other) ||
54105
(other is CachedMapTileMetadata &&
55-
staleAtMilliseconds == other.staleAtMilliseconds &&
56-
lastModifiedMilliseconds == other.lastModifiedMilliseconds &&
106+
staleAt == other.staleAt &&
107+
lastModified == other.lastModified &&
57108
etag == other.etag);
58109
}

lib/src/layer/tile_layer/tile_provider/network/image_provider/image_provider.dart

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:async';
22
import 'dart:io' show HttpHeaders, HttpDate, HttpStatus; // this is web safe!
3-
import 'dart:math';
43
import 'dart:ui';
54

65
import 'package:flutter/foundation.dart';
@@ -171,58 +170,15 @@ class NetworkTileImageProvider extends ImageProvider<NetworkTileImageProvider> {
171170
}
172171
}
173172

174-
// Create method to interact with cache
173+
// Create method to write response to cache when applicable
175174
void cachePut({
176175
required Uint8List? bytes,
177176
required Map<String, String> headers,
178177
}) {
179178
if (useFallback || !cachingProvider.isSupported) return;
180-
181-
final lastModified = headers[HttpHeaders.lastModifiedHeader];
182-
final etag = headers[HttpHeaders.etagHeader];
183-
184-
DateTime calculateStaleAt() {
185-
final addToNow = DateTime.timestamp().add;
186-
187-
if (headers[HttpHeaders.cacheControlHeader]?.toLowerCase()
188-
case final cacheControl?) {
189-
final maxAge = RegExp(r'max-age=(\d+)').firstMatch(cacheControl)?[1];
190-
191-
if (maxAge == null) {
192-
if (headers[HttpHeaders.expiresHeader]?.toLowerCase()
193-
case final expires?) {
194-
return HttpDate.parse(expires);
195-
}
196-
197-
return addToNow(const Duration(days: 7));
198-
}
199-
200-
if (headers[HttpHeaders.ageHeader] case final currentAge?) {
201-
return addToNow(
202-
Duration(seconds: int.parse(maxAge) - int.parse(currentAge)),
203-
);
204-
}
205-
206-
final estimatedAge = max(
207-
0,
208-
DateTime.timestamp()
209-
.difference(HttpDate.parse(headers[HttpHeaders.dateHeader]!))
210-
.inSeconds,
211-
);
212-
return addToNow(Duration(seconds: int.parse(maxAge) - estimatedAge));
213-
}
214-
215-
return addToNow(const Duration(days: 7));
216-
}
217-
218179
cachingProvider.putTile(
219180
url: resolvedUrl,
220-
metadata: CachedMapTileMetadata(
221-
staleAt: calculateStaleAt(),
222-
lastModified:
223-
lastModified != null ? HttpDate.parse(lastModified) : null,
224-
etag: etag,
225-
),
181+
metadata: CachedMapTileMetadata.fromHttpHeaders(headers),
226182
bytes: bytes,
227183
);
228184
}

0 commit comments

Comments
 (0)