1- import 'dart:io' show HttpHeaders; // web safe!
1+ import 'dart:io' show HttpHeaders, HttpDate; // web safe!
2+ import 'dart:math' ;
23
34import 'package:flutter_map/flutter_map.dart' ;
45import 'package:meta/meta.dart' ;
@@ -14,26 +15,77 @@ import 'package:meta/meta.dart';
1415@immutable
1516class 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}
0 commit comments