Skip to content

Commit 2f3b85f

Browse files
v7.1.0: Obscureable URL query parameters (JaffaKetchup#95)
* Added URL query params obscurer feature Added `headers` and `httpClient` parameters to `getTileProvider` Minor documentation improvements Minor bug fixes * Built Example Applications --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 2d5ded4 commit 2f3b85f

File tree

9 files changed

+110
-71
lines changed

9 files changed

+110
-71
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ Many thanks to my sponsors, no matter how much or how little they donated. Spons
1313

1414
# Changelog
1515

16+
## [7.1.0] - 2023/02/12
17+
18+
* Added URL query params obscurer feature
19+
* Added `headers` and `httpClient` parameters to `getTileProvider`
20+
* Minor documentation improvements
21+
* Minor bug fixes
22+
1623
## [7.0.2] - 2023/02/12
1724

1825
* Minor changes to example application

lib/src/providers/image_provider.dart

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// A full license can be found at .\LICENSE
33

44
import 'dart:async';
5-
import 'dart:io';
65
import 'dart:ui';
76

87
import 'package:flutter/foundation.dart';
@@ -29,12 +28,6 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
2928
/// The coordinates of the tile to be fetched
3029
final Coords<num> coords;
3130

32-
/// A HTTP client used to send requests
33-
final HttpClient httpClient;
34-
35-
/// Custom headers to send with each request
36-
final Map<String, String> headers;
37-
3831
/// Used internally to safely and efficiently enforce the `settings.maxStoreLength`
3932
static final Queue removeOldestQueue =
4033
Queue(timeout: const Duration(seconds: 1));
@@ -55,8 +48,6 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
5548
required this.provider,
5649
required this.options,
5750
required this.coords,
58-
required this.httpClient,
59-
required this.headers,
6051
}) : settings = provider.settings,
6152
_db = FMTCRegistry.instance(provider.storeDirectory.storeName);
6253

@@ -112,11 +103,8 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
112103
try {
113104
throw FMTCBrowsingError(throwError, throwErrorType!);
114105
} on FMTCBrowsingError catch (e) {
115-
if (settings.errorHandler != null) {
116-
settings.errorHandler!(e);
117-
} else {
118-
rethrow;
119-
}
106+
settings.errorHandler?.call(e);
107+
rethrow;
120108
}
121109
}
122110

@@ -125,12 +113,27 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
125113
}
126114

127115
throw ArgumentError(
128-
'`finish` was called with an invalid combination of arguments',
116+
'`finish` was called with an invalid combination of arguments, or a fall-through situation occurred.',
129117
);
130118
}
131119

132-
final String url = provider.getTileUrl(coords, options);
133-
final DbTile? existingTile = await _db.tiles.get(DatabaseTools.hash(url));
120+
final String networkUrl = provider.getTileUrl(coords, options);
121+
final String matcherUrl;
122+
123+
if (networkUrl.contains('?') &&
124+
provider.settings.obscuredQueryParams.isNotEmpty) {
125+
String secondPartUrl = networkUrl.split('?')[1];
126+
for (final r in provider.settings.obscuredQueryParams) {
127+
secondPartUrl = secondPartUrl.replaceAll(r, '');
128+
}
129+
130+
matcherUrl = '${networkUrl.split('?')[0]}?$secondPartUrl';
131+
} else {
132+
matcherUrl = networkUrl;
133+
}
134+
135+
final DbTile? existingTile =
136+
await _db.tiles.get(DatabaseTools.hash(matcherUrl));
134137

135138
// Logic to check whether the tile needs creating or updating
136139
final bool needsCreating = existingTile == null;
@@ -142,6 +145,15 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
142145
existingTile.lastModified.millisecondsSinceEpoch >
143146
settings.cachedValidDuration.inMilliseconds);
144147

148+
/* DEBUG ONLY
149+
print('---------');
150+
print(networkUrl);
151+
print(matcherUrl);
152+
print(' Existing ID: ' + (existingTile?.id ?? 'None').toString());
153+
print(' Needs Creating: ' + needsCreating.toString());
154+
print(' Needs Updating: ' + needsUpdating.toString());
155+
*/
156+
145157
// Get any existing bytes from the tile, if it exists
146158
Uint8List? bytes;
147159
if (!needsCreating) bytes = Uint8List.fromList(existingTile.bytes);
@@ -162,9 +174,10 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
162174

163175
// Try to get a response from a server, throwing an error if not possible & the tile does not exist
164176
try {
165-
final HttpClientRequest request =
166-
await httpClient.getUrl(Uri.parse(url));
167-
headers.forEach((k, v) => request.headers.add(k, v));
177+
final request = await provider.httpClient.getUrl(Uri.parse(networkUrl));
178+
provider.headers.forEach(
179+
(k, v) => request.headers.add(k, v, preserveHeaderCase: true),
180+
);
168181
response = await request.close();
169182
} catch (err) {
170183
return finish(
@@ -204,7 +217,9 @@ class FMTCImageProvider extends ImageProvider<FMTCImageProvider> {
204217

205218
// Cache the tile asynchronously
206219
unawaited(
207-
_db.writeTxn(() => _db.tiles.put(DbTile(url: url, bytes: bytes!))),
220+
_db.writeTxn(
221+
() => _db.tiles.put(DbTile(url: matcherUrl, bytes: bytes!)),
222+
),
208223
);
209224

210225
// If an new tile was created over the tile limit, delete the oldest tile

lib/src/providers/tile_provider.dart

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,56 @@
33

44
part of flutter_map_tile_caching;
55

6-
/// 'flutter_map_tile_caching's custom [TileProvider] for use in a [TileLayer]
6+
/// FMTC's custom [TileProvider] for use in a [TileLayer]
7+
///
8+
/// Create from the store directory chain, eg. [StoreDirectory.getTileProvider].
79
class FMTCTileProvider extends TileProvider {
810
/// The store directory attached to this provider
911
final StoreDirectory storeDirectory;
1012

1113
/// The tile provider settings to use
1214
///
13-
/// Defaults to the one provided by [FMTCSettings] when initialising [FlutterMapTileCaching].
15+
/// Defaults to the one provided by [FMTCSettings] when initialising
16+
/// [FlutterMapTileCaching].
1417
final FMTCTileProviderSettings settings;
1518

1619
/// Used internally for browsing-caused tile requests
1720
final HttpClient httpClient;
1821

19-
/// 'flutter_map_tile_caching's custom [TileProvider] for use in a [TileLayer]
20-
///
21-
/// Usually created from the store directory chain, eg. [StoreDirectory.getTileProvider].
22-
///
23-
/// This contains the logic for the tile provider, such as browse caching and using bulk downloaded tiles.
24-
FMTCTileProvider({
22+
FMTCTileProvider._({
2523
required this.storeDirectory,
2624
required FMTCTileProviderSettings? settings,
27-
super.headers,
25+
Map<String, String> headers = const {},
2826
HttpClient? httpClient,
2927
}) : settings =
3028
settings ?? FMTC.instance.settings.defaultTileProviderSettings,
3129
httpClient = httpClient ?? HttpClient()
32-
..userAgent = null;
30+
..userAgent = null,
31+
super(
32+
headers: {
33+
...headers,
34+
'User-Agent': headers['User-Agent'] == null
35+
? 'flutter_map_tile_caching for flutter_map (unknown)'
36+
: 'flutter_map_tile_caching for ${headers['User-Agent']}',
37+
},
38+
);
3339

34-
/// Closes the open [HttpClient] - this will make the provider unable to perform network requests
40+
/// Closes the open [HttpClient] - this will make the provider unable to
41+
/// perform network requests
3542
@override
3643
void dispose() {
37-
super.dispose();
3844
httpClient.close();
45+
super.dispose();
3946
}
4047

41-
/// Get a browsed tile as an image, paint it on the map and save it's bytes to cache for later (dependent on the [CacheBehavior])
48+
/// Get a browsed tile as an image, paint it on the map and save it's bytes to
49+
/// cache for later (dependent on the [CacheBehavior])
4250
@override
4351
ImageProvider getImage(Coords<num> coords, TileLayer options) =>
4452
FMTCImageProvider(
4553
provider: this,
4654
options: options,
4755
coords: coords,
48-
httpClient: httpClient,
49-
headers: {
50-
...headers,
51-
'User-Agent': headers['User-Agent'] == null
52-
? 'flutter_map_tile_caching for flutter_map (unknown)'
53-
: 'flutter_map_tile_caching for ${headers['User-Agent']}',
54-
},
5556
);
5657

5758
IsarCollection<DbTile> get _tiles =>
@@ -64,7 +65,8 @@ class FMTCTileProvider extends TileProvider {
6465
}) =>
6566
_tiles.getSync(DatabaseTools.hash(getTileUrl(coords, options))) != null;
6667

67-
/// Check whether a specified tile is cached in the current store asynchronously
68+
/// Check whether a specified tile is cached in the current store
69+
/// asynchronously
6870
Future<bool> checkTileCachedAsync({
6971
required Coords<num> coords,
7072
required TileLayer options,

lib/src/settings/fmtc_settings.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@
33

44
part of flutter_map_tile_caching;
55

6-
/// Global FMTC settings, used throughout or in long lasting places
6+
/// Global FMTC settings
77
class FMTCSettings {
88
/// Default settings used when creating an [FMTCTileProvider]
99
///
10-
/// Can be overridden on a case-to-case basis when actually creating the tile provider.
10+
/// Can be overridden on a case-to-case basis when actually creating the tile
11+
/// provider.
1112
final FMTCTileProviderSettings defaultTileProviderSettings;
1213

1314
/// Sets a strict upper size limit on each underlying database individually
1415
/// (of which there are multiple)
1516
///
16-
/// Prefer to set a limit on the number of tiles instead, using
17-
/// [FMTCTileProviderSettings.maxStoreLength].
17+
/// It is also recommended to set a limit on the number of tiles instead, using
18+
/// [FMTCTileProviderSettings.maxStoreLength]. If using a generous number
19+
/// there, use a larger number here as well.
1820
///
19-
/// Setting this value too low may cause errors. Setting this value too high
20-
/// and not limiting the number of tiles may result in slower operations and
21-
/// a negative user experience: a large, unknown file may be deleted by a user,
22-
/// causing significant data loss.
21+
/// Setting this value too low may cause unexpected errors when writing to the
22+
/// database. Setting this value too high may cause memory issues on certain
23+
/// older devices or emulators.
2324
///
2425
/// Defaults to 2GiB (2048MiB).
2526
final int databaseMaxSize;
@@ -39,8 +40,7 @@ class FMTCSettings {
3940
/// exporting a store will always compact it's underlying database.
4041
final DatabaseCompactCondition? databaseCompactCondition;
4142

42-
/// Create custom global FMTC settings, used throughout or in long lasting
43-
/// places
43+
/// Create custom global FMTC settings
4444
FMTCSettings({
4545
FMTCTileProviderSettings? defaultTileProviderSettings,
4646
this.databaseMaxSize = 2048,

lib/src/settings/tile_provider_settings.dart

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
// Copyright © Luka S (JaffaKetchup) under GPL-v3
22
// A full license can be found at .\LICENSE
33

4-
import 'package:isar/isar.dart';
5-
64
import '../../flutter_map_tile_caching.dart';
75
import '../providers/image_provider.dart';
86

9-
/// Multiple behaviors dictating how browse caching should be carried out
10-
///
11-
/// Check documentation on each value for more information.
7+
/// Behaviours dictating how and when browse caching should be carried out
128
enum CacheBehavior {
139
/// Only get tiles from the local cache
1410
///
15-
/// Useful for applications with dedicated 'Offline Mode'.
11+
/// Throws [FMTCBrowsingErrorType.missingInCacheOnlyMode] if a tile is not
12+
/// available.
1613
cacheOnly,
1714

18-
/// Get tiles from the local cache, going on the Internet to update the cached tile if it has expired (`cachedValidDuration` has passed)
15+
/// Get tiles from the local cache, only using the network to update the cached
16+
/// tile if it has expired ([FMTCTileProviderSettings.cachedValidDuration] has
17+
/// passed)
1918
cacheFirst,
2019

21-
/// Get tiles from the Internet and update the cache for every tile
20+
/// Get tiles from the network where possible, and update the cached tiles
21+
///
22+
/// Safely falls back to using cached tiles if the network is not available.
2223
onlineFirst,
2324
}
2425

@@ -44,26 +45,31 @@ class FMTCTileProviderSettings {
4445
/// Only applies to 'browse caching', ie. downloading regions will bypass this
4546
/// limit.
4647
///
47-
/// Note that the actual store has an un-modifiable maximum size limit of
48-
/// [Isar.defaultMaxSizeMiB] (1GB). It is unspecified what will happen if this
49-
/// limit is reached, however it is likely that an error will be thrown.
48+
/// Note that the actual store has a size limit of
49+
/// [FMTCSettings.databaseMaxSize], irrespective of this value.
5050
///
5151
/// Defaults to 0 disabled.
5252
final int maxStoreLength;
5353

54+
/// A list of keys in the query part of the source URL (after the '?'), who's
55+
/// values will not be stored
56+
///
57+
/// See the online documentation for more information.
58+
final Iterable<RegExp> obscuredQueryParams;
59+
5460
/// A custom callback that will be called when an [FMTCBrowsingError] is raised
5561
///
56-
/// Prevents the error being printed to the console, and only captures this
57-
/// type of error, unlike 'flutter_map's native solution.
62+
/// Even if this is defined, the error will still be (re)thrown.
5863
void Function(FMTCBrowsingError exception)? errorHandler;
5964

6065
/// Create settings for an [FMTCTileProvider]
6166
FMTCTileProviderSettings({
6267
this.behavior = CacheBehavior.cacheFirst,
6368
this.cachedValidDuration = const Duration(days: 16),
6469
this.maxStoreLength = 0,
70+
List<String> obscuredQueryParams = const [],
6571
this.errorHandler,
66-
});
72+
}) : obscuredQueryParams = obscuredQueryParams.map((e) => RegExp('$e=[^&]*'));
6773

6874
@override
6975
bool operator ==(Object other) =>

lib/src/store/directory.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,23 @@ class StoreDirectory {
4848
/// background downloading functionality.
4949
DownloadManagement get download => DownloadManagement._(this);
5050

51-
/// Get 'flutter_map_tile_caching's custom [TileProvider] for use in a
52-
/// [TileLayer], specific to this store
51+
/// Get the [TileProvider] suitable to connect the [TileLayer] to FMTC's
52+
/// internals
5353
///
5454
/// Uses [FMTCSettings.defaultTileProviderSettings] by default (and it's
5555
/// default if unspecified). Alternatively, override [settings] for this get
5656
/// only.
57-
FMTCTileProvider getTileProvider([FMTCTileProviderSettings? settings]) =>
58-
FMTCTileProvider(storeDirectory: this, settings: settings);
57+
FMTCTileProvider getTileProvider([
58+
FMTCTileProviderSettings? settings,
59+
Map<String, String>? headers,
60+
HttpClient? httpClient,
61+
]) =>
62+
FMTCTileProvider._(
63+
storeDirectory: this,
64+
settings: settings,
65+
headers: headers ?? {},
66+
httpClient: httpClient,
67+
);
5968

6069
@override
6170
bool operator ==(Object other) =>
-3 Bytes
Binary file not shown.
-1.28 KB
Binary file not shown.

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: flutter_map_tile_caching
22
description: Plugin for 'flutter_map' providing advanced caching functionality,
33
with ability to download map regions for offline use.
4-
version: 7.0.2
4+
version: 7.1.0
55
repository: https://github.com/JaffaKetchup/flutter_map_tile_caching
66
issue_tracker: https://github.com/JaffaKetchup/flutter_map_tile_caching/issues
77
documentation: https://fmtc.jaffaketchup.dev

0 commit comments

Comments
 (0)