lot of changes, got rid of custom cache stuff, now stepping in the way of http fetch instead of screwing with flutter map.

This commit is contained in:
stopflock
2025-08-23 17:42:53 -05:00
parent a2bc3309c0
commit a21e807d88
8 changed files with 164 additions and 216 deletions
+5
View File
@@ -144,4 +144,9 @@ class MapDataProvider {
}
}
}
/// Clear any queued tile requests (call when map view changes significantly)
void clearTileQueue() {
clearOSMTileQueue();
}
}
@@ -9,15 +9,10 @@ import '../network_status.dart';
/// Global semaphore to limit simultaneous tile fetches
final _tileFetchSemaphore = _SimpleSemaphore(4); // Max 4 concurrent
/// Cancellation token to invalidate all pending requests
int _globalCancelToken = 0;
/// Clear queued tile requests and cancel all retries
/// Clear queued tile requests when map view changes significantly
void clearOSMTileQueue() {
final oldToken = _globalCancelToken;
_globalCancelToken++; // Invalidate all pending requests and retries
final clearedCount = _tileFetchSemaphore.clearQueue();
debugPrint('[OSMTiles] Cancel token: $oldToken -> $_globalCancelToken, cleared $clearedCount queued');
debugPrint('[OSMTiles] Cleared $clearedCount queued tile requests');
}
/// Fetches a tile from OSM, with in-memory retries/backoff, and global concurrency limit.
@@ -37,17 +32,7 @@ Future<List<int>> fetchOSMTile({
kTileFetchThirdDelayMs + random.nextInt(kTileFetchJitter3Ms),
];
// Remember the cancel token when we start this request
final requestCancelToken = _globalCancelToken;
print('[fetchOSMTile] START $z/$x/$y with token $requestCancelToken (global: $_globalCancelToken)');
while (true) {
// Check if this request was cancelled
if (requestCancelToken != _globalCancelToken) {
print('[fetchOSMTile] CANCELLED $z/$x/$y (token: $requestCancelToken vs $_globalCancelToken)');
throw Exception('Tile request cancelled');
}
await _tileFetchSemaphore.acquire();
try {
print('[fetchOSMTile] FETCH $z/$x/$y');
@@ -55,12 +40,6 @@ Future<List<int>> fetchOSMTile({
final resp = await http.get(Uri.parse(url));
print('[fetchOSMTile] HTTP ${resp.statusCode} for $z/$x/$y, length=${resp.bodyBytes.length}');
// Check cancellation after HTTP request completes - this is the key check!
if (requestCancelToken != _globalCancelToken) {
print('[fetchOSMTile] CANCELLED $z/$x/$y after HTTP (token: $requestCancelToken vs $_globalCancelToken)');
throw Exception('Tile request cancelled');
}
if (resp.statusCode == 200 && resp.bodyBytes.isNotEmpty) {
print('[fetchOSMTile] SUCCESS $z/$x/$y');
NetworkStatus.instance.reportOsmTileSuccess();
@@ -71,11 +50,6 @@ Future<List<int>> fetchOSMTile({
throw HttpException('Failed to fetch tile $z/$x/$y: status ${resp.statusCode}');
}
} catch (e) {
// Don't retry cancelled requests
if (e.toString().contains('cancelled')) {
rethrow;
}
print('[fetchOSMTile] Exception $z/$x/$y: $e');
// Report network issues on connection errors
@@ -92,19 +66,7 @@ Future<List<int>> fetchOSMTile({
final delay = delays[attempt - 1].clamp(0, 60000);
print("[fetchOSMTile] Attempt $attempt for $z/$x/$y failed: $e. Retrying in ${delay}ms.");
// Check cancellation before and after delay
if (requestCancelToken != _globalCancelToken) {
print('[fetchOSMTile] CANCELLED $z/$x/$y before retry');
throw Exception('Tile request cancelled');
}
await Future.delayed(Duration(milliseconds: delay));
if (requestCancelToken != _globalCancelToken) {
print('[fetchOSMTile] CANCELLED $z/$x/$y after retry delay');
throw Exception('Tile request cancelled');
}
} finally {
_tileFetchSemaphore.release();
}
+99
View File
@@ -0,0 +1,99 @@
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'map_data_provider.dart';
/// Simple HTTP client that routes tile requests through the centralized MapDataProvider.
/// This ensures all tile fetching (offline/online routing, retries, etc.) is in one place.
class SimpleTileHttpClient extends http.BaseClient {
final http.Client _inner = http.Client();
final MapDataProvider _mapDataProvider = MapDataProvider();
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
// Only intercept tile requests to OSM
if (request.url.host == 'tile.openstreetmap.org') {
return _handleTileRequest(request);
}
// Pass through all other requests
return _inner.send(request);
}
Future<http.StreamedResponse> _handleTileRequest(http.BaseRequest request) async {
final pathSegments = request.url.pathSegments;
// Parse z/x/y from URL like: /15/5242/12666.png
if (pathSegments.length == 3) {
final z = int.tryParse(pathSegments[0]);
final x = int.tryParse(pathSegments[1]);
final yPng = pathSegments[2];
final y = int.tryParse(yPng.replaceAll('.png', ''));
if (z != null && x != null && y != null) {
return _getTile(z, x, y);
}
}
// Malformed tile URL - pass through to OSM
return _inner.send(request);
}
Future<http.StreamedResponse> _getTile(int z, int x, int y) async {
try {
// Use centralized tile fetching from MapDataProvider
final tileBytes = await _mapDataProvider.getTile(z: z, x: x, y: y);
debugPrint('[SimpleTileService] Serving tile $z/$x/$y via MapDataProvider');
return http.StreamedResponse(
Stream.value(tileBytes),
200,
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=604800', // 1 week cache
'Expires': _httpDateFormat(DateTime.now().add(Duration(days: 7))),
'Last-Modified': _httpDateFormat(DateTime.now().subtract(Duration(hours: 1))),
},
);
} catch (e) {
debugPrint('[SimpleTileService] Tile fetch failed for $z/$x/$y: $e');
// Return 404 for any failure - let flutter_map handle gracefully
return http.StreamedResponse(
Stream.value(<int>[]),
404,
reasonPhrase: 'Tile not available: $e',
);
}
}
/// Clear any queued tile requests when map view changes
void clearTileQueue() {
_mapDataProvider.clearTileQueue();
}
/// Format date for HTTP headers (RFC 7231)
String _httpDateFormat(DateTime date) {
final utc = date.toUtc();
final weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
final weekday = weekdays[utc.weekday - 1];
final day = utc.day.toString().padLeft(2, '0');
final month = months[utc.month - 1];
final year = utc.year;
final hour = utc.hour.toString().padLeft(2, '0');
final minute = utc.minute.toString().padLeft(2, '0');
final second = utc.second.toString().padLeft(2, '0');
return '$weekday, $day $month $year $hour:$minute:$second GMT';
}
@override
void close() {
_inner.close();
super.close();
}
}