mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-06 12:47:56 +02:00
Detect config drift in cached tile providers and replace stale instances
When a user edits a tile type's URL template, max zoom, or API key without changing IDs, the cached DeflockTileProvider would keep the old frozen config. Now _getOrCreateProvider() computes a config fingerprint and replaces the provider when drift is detected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,10 @@ class DeflockTileProvider extends NetworkTileProvider {
|
||||
final models.TileType tileType;
|
||||
final String? apiKey;
|
||||
|
||||
/// Opaque fingerprint of the config this provider was created with.
|
||||
/// Used by [TileLayerManager] to detect config drift after edits.
|
||||
final String configFingerprint;
|
||||
|
||||
/// Caching provider for the offline-first path. The same instance is passed
|
||||
/// to super for the common path — we keep a reference here so we can also
|
||||
/// use it in [DeflockOfflineTileImageProvider].
|
||||
@@ -69,6 +73,7 @@ class DeflockTileProvider extends NetworkTileProvider {
|
||||
this.apiKey,
|
||||
MapCachingProvider? cachingProvider,
|
||||
this.onNetworkSuccess,
|
||||
this.configFingerprint = '',
|
||||
}) : _sharedHttpClient = httpClient,
|
||||
_cachingProvider = cachingProvider,
|
||||
super(
|
||||
@@ -87,6 +92,7 @@ class DeflockTileProvider extends NetworkTileProvider {
|
||||
String? apiKey,
|
||||
MapCachingProvider? cachingProvider,
|
||||
VoidCallback? onNetworkSuccess,
|
||||
String configFingerprint = '',
|
||||
}) {
|
||||
final client = UserAgentClient(RetryClient(Client()));
|
||||
return DeflockTileProvider._(
|
||||
@@ -96,6 +102,7 @@ class DeflockTileProvider extends NetworkTileProvider {
|
||||
apiKey: apiKey,
|
||||
cachingProvider: cachingProvider,
|
||||
onNetworkSuccess: onNetworkSuccess,
|
||||
configFingerprint: configFingerprint,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,11 @@ class ProviderTileCacheManager {
|
||||
required ServicePolicy policy,
|
||||
int? maxCacheBytes,
|
||||
}) {
|
||||
assert(_baseCacheDir != null,
|
||||
'ProviderTileCacheManager.init() must be called before getOrCreate()');
|
||||
if (_baseCacheDir == null) {
|
||||
throw StateError(
|
||||
'ProviderTileCacheManager.init() must be called before getOrCreate()',
|
||||
);
|
||||
}
|
||||
|
||||
final key = '$providerId/$tileTypeId';
|
||||
if (_stores.containsKey(key)) return _stores[key]!;
|
||||
|
||||
@@ -47,7 +47,7 @@ class ProviderTileCacheStore implements MapCachingProvider {
|
||||
|
||||
@override
|
||||
Future<CachedMapTile?> getTile(String url) async {
|
||||
final key = _keyFor(url);
|
||||
final key = keyFor(url);
|
||||
final tileFile = File(p.join(cacheDirectory, '$key.tile'));
|
||||
final metaFile = File(p.join(cacheDirectory, '$key.meta'));
|
||||
|
||||
@@ -90,7 +90,7 @@ class ProviderTileCacheStore implements MapCachingProvider {
|
||||
}) async {
|
||||
await _ensureDirectory();
|
||||
|
||||
final key = _keyFor(url);
|
||||
final key = keyFor(url);
|
||||
final tileFile = File(p.join(cacheDirectory, '$key.tile'));
|
||||
final metaFile = File(p.join(cacheDirectory, '$key.meta'));
|
||||
|
||||
@@ -158,7 +158,8 @@ class ProviderTileCacheStore implements MapCachingProvider {
|
||||
}
|
||||
|
||||
/// Generate a cache key from URL using UUID v5 (same as flutter_map built-in).
|
||||
static String _keyFor(String url) => _uuid.v5(Namespace.url.value, url);
|
||||
@visibleForTesting
|
||||
static String keyFor(String url) => _uuid.v5(Namespace.url.value, url);
|
||||
|
||||
/// Estimate total cache size (lazy, first call scans directory).
|
||||
Future<int> _getEstimatedSize() async {
|
||||
@@ -301,6 +302,7 @@ class ProviderTileCacheStore implements MapCachingProvider {
|
||||
}
|
||||
_estimatedSize = null;
|
||||
_directoryReady = null; // Allow lazy re-creation
|
||||
_lastPruneCheck = null; // Reset throttle so next write can trigger eviction
|
||||
}
|
||||
|
||||
/// Get the current estimated cache size in bytes.
|
||||
|
||||
@@ -315,10 +315,12 @@ class ServiceRateLimiter {
|
||||
static Future<void> acquire(ServiceType service) async {
|
||||
final policy = ServicePolicyResolver.resolveByType(service);
|
||||
|
||||
// Concurrency: acquire semaphore slot first, so only one caller at a
|
||||
// time proceeds to the rate-limit check. This prevents concurrent
|
||||
// callers from bypassing the min interval when _lastRequestTime is
|
||||
// still null or stale.
|
||||
// Concurrency: acquire a semaphore slot first so that at most
|
||||
// [policy.maxConcurrentRequests] callers proceed concurrently.
|
||||
// The min-interval check below is only race-free when
|
||||
// maxConcurrentRequests == 1 (currently only Nominatim). For services
|
||||
// with higher concurrency the interval is approximate, which is
|
||||
// acceptable — their policies don't specify a min interval.
|
||||
_Semaphore? semaphore;
|
||||
if (policy.maxConcurrentRequests > 0) {
|
||||
semaphore = _semaphores.putIfAbsent(
|
||||
|
||||
Reference in New Issue
Block a user