From 63675bd18ac9765b454e367f3c0d1d4d34ec0163 Mon Sep 17 00:00:00 2001 From: stopflock Date: Sat, 9 Aug 2025 22:44:24 -0500 Subject: [PATCH] tbh not sure, left for a bit. sorry. pretty sure closer than last commit.. --- lib/app_state.dart | 1 + lib/services/map_data_provider.dart | 16 +++-- .../map_data_submodules/tiles_from_osm.dart | 6 ++ lib/services/offline_area_service.dart | 16 ++++- .../offline_area_service_tile_fetch.dart | 49 ++----------- lib/services/overpass_service.dart | 70 ------------------- 6 files changed, 36 insertions(+), 122 deletions(-) delete mode 100644 lib/services/overpass_service.dart diff --git a/lib/app_state.dart b/lib/app_state.dart index f892ffa..04cfdb2 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -131,6 +131,7 @@ class AppState extends ChangeNotifier { } // Ensure AuthService follows loaded mode _auth.setUploadMode(_uploadMode); + print('AppState: AuthService mode now updated to $_uploadMode'); await _loadQueue(); diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index f6f5c93..3c21a93 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -21,10 +21,11 @@ class MapDataProvider { factory MapDataProvider() => _instance; MapDataProvider._(); - bool _offlineMode = false; - bool get isOfflineMode => _offlineMode; + AppState get _appState => AppState(); // Use singleton for now + + bool get isOfflineMode => _appState.offlineMode; void setOfflineMode(bool enabled) { - _offlineMode = enabled; + _appState.setOfflineMode(enabled); } /// Fetch cameras from OSM/Overpass or local storage, depending on source/offline mode. @@ -34,8 +35,10 @@ class MapDataProvider { UploadMode uploadMode = UploadMode.production, MapSource source = MapSource.auto, }) async { + print('[MapDataProvider] getCameras called, source=$source, offlineMode=$isOfflineMode'); // Resolve source: - if (_offlineMode && source != MapSource.local) { + if (isOfflineMode && source != MapSource.local) { + print('[MapDataProvider] BLOCKED by offlineMode for getCameras'); throw OfflineModeException("Cannot fetch remote cameras in offline mode."); } if (source == MapSource.local) { @@ -53,8 +56,9 @@ class MapDataProvider { required int y, MapSource source = MapSource.auto, }) async { - print('[MapDataProvider] getTile called for $z/$x/$y'); - if (_offlineMode && source != MapSource.local) { + print('[MapDataProvider] getTile called for $z/$x/$y, source=$source, offlineMode=$isOfflineMode'); + if (isOfflineMode && source != MapSource.local) { + print('[MapDataProvider] BLOCKED by offlineMode for $z/$x/$y'); throw OfflineModeException("Cannot fetch remote tiles in offline mode."); } if (source == MapSource.local) { diff --git a/lib/services/map_data_submodules/tiles_from_osm.dart b/lib/services/map_data_submodules/tiles_from_osm.dart index 67dfa7d..8c23579 100644 --- a/lib/services/map_data_submodules/tiles_from_osm.dart +++ b/lib/services/map_data_submodules/tiles_from_osm.dart @@ -9,11 +9,17 @@ final _tileFetchSemaphore = _SimpleSemaphore(4); // Max 4 concurrent /// Fetches a tile from OSM, with in-memory retries/backoff, and global concurrency limit. /// Returns tile image bytes, or throws on persistent failure. +import '../../app_state.dart'; + Future> fetchOSMTile({ required int z, required int x, required int y, }) async { + if (AppState().offlineMode) { + print('[fetchOSMTile] BLOCKED by offline mode ($z/$x/$y)'); + throw Exception('Offline mode enabled—cannot fetch OSM tile.'); + } final url = 'https://tile.openstreetmap.org/$z/$x/$y.png'; const int maxAttempts = 3; int attempt = 0; diff --git a/lib/services/offline_area_service.dart b/lib/services/offline_area_service.dart index 9e8c4b5..6da76b8 100644 --- a/lib/services/offline_area_service.dart +++ b/lib/services/offline_area_service.dart @@ -6,8 +6,11 @@ import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import 'package:path_provider/path_provider.dart'; import 'offline_areas/offline_area_models.dart'; import 'offline_areas/offline_tile_utils.dart'; -import 'offline_areas/offline_area_service_tile_fetch.dart'; +import 'offline_areas/offline_area_service_tile_fetch.dart'; // Only used for file IO during area downloads. import '../models/osm_camera_node.dart'; +import '../app_state.dart'; +import 'map_data_provider.dart'; +import 'map_data_submodules/cameras_from_overpass.dart'; /// Service for managing download, storage, and retrieval of offline map areas and cameras. class OfflineAreaService { @@ -213,7 +216,11 @@ class OfflineAreaService { for (final tile in tilesToFetch) { if (area.status == OfflineAreaStatus.cancelled) break; try { - await downloadTile(tile[0], tile[1], tile[2], directory); + final bytes = await MapDataProvider().getTile( + z: tile[0], x: tile[1], y: tile[2], source: MapSource.remote); + if (bytes.isNotEmpty) { + await saveTileBytes(tile[0], tile[1], tile[2], directory, bytes); + } totalDone++; doneThisPass++; area.tilesDownloaded = totalDone; @@ -238,7 +245,10 @@ class OfflineAreaService { } if (!area.isPermanent) { - final cameras = await downloadAllCameras(bounds); + final cameras = await camerasFromOverpass( + bounds: bounds, + profiles: AppState().enabledProfiles, + ); area.cameras = cameras; await saveCameras(cameras, directory); } else { diff --git a/lib/services/offline_areas/offline_area_service_tile_fetch.dart b/lib/services/offline_areas/offline_area_service_tile_fetch.dart index c0ba7bc..6da9410 100644 --- a/lib/services/offline_areas/offline_area_service_tile_fetch.dart +++ b/lib/services/offline_areas/offline_area_service_tile_fetch.dart @@ -1,55 +1,18 @@ -import 'dart:math'; import 'dart:io'; import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:latlong2/latlong.dart'; import '../../models/osm_camera_node.dart'; -import 'package:flutter_map/flutter_map.dart' show LatLngBounds; -Future downloadTile(int z, int x, int y, String baseDir) async { - final url = 'https://tile.openstreetmap.org/$z/$x/$y.png'; +/// Disk IO utilities for offline area file management ONLY. No network requests should occur here. + +/// Save-to-disk for a tile that has already been fetched elsewhere. +Future saveTileBytes(int z, int x, int y, String baseDir, List bytes) async { final dir = Directory('$baseDir/tiles/$z/$x'); await dir.create(recursive: true); final file = File('${dir.path}/$y.png'); - if (await file.exists()) return; // already downloaded - const int maxAttempts = 3; - int attempt = 0; - final random = Random(); - final delays = [0, 3000 + random.nextInt(1000) - 500, 10000 + random.nextInt(4000) - 2000]; - while (true) { - try { - attempt++; - final resp = await http.get(Uri.parse(url)); - if (resp.statusCode == 200) { - await file.writeAsBytes(resp.bodyBytes); - return; - } else { - throw Exception('Failed to download tile $z/$x/$y (status \\${resp.statusCode})'); - } - } catch (e) { - if (attempt >= maxAttempts) { - throw Exception("Failed to download tile $z/$x/$y after $attempt attempts: $e"); - } - final delay = delays[attempt-1].clamp(0, 60000); - await Future.delayed(Duration(milliseconds: delay)); - } - } -} - -Future> downloadAllCameras(LatLngBounds bounds) async { - final sw = bounds.southWest; - final ne = bounds.northEast; - final bbox = [sw.latitude, sw.longitude, ne.latitude, ne.longitude].join(','); - final query = '[out:json][timeout:60];node["man_made"="surveillance"]["camera:mount"="pole"]($bbox);out body;'; - final url = 'https://overpass-api.de/api/interpreter'; - final resp = await http.post(Uri.parse(url), body: { 'data': query }); - if (resp.statusCode != 200) { - throw Exception('Failed to fetch cameras'); - } - final data = jsonDecode(resp.body); - return (data['elements'] as List?)?.map((e) => OsmCameraNode.fromJson(e)).toList() ?? []; + await file.writeAsBytes(bytes); } +/// Save-to-disk for cameras.json; called only by OfflineAreaService during area download Future saveCameras(List cams, String dir) async { final file = File('$dir/cameras.json'); await file.writeAsString(jsonEncode(cams.map((c) => c.toJson()).toList())); diff --git a/lib/services/overpass_service.dart b/lib/services/overpass_service.dart deleted file mode 100644 index 10db57e..0000000 --- a/lib/services/overpass_service.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; - -import '../models/camera_profile.dart'; -import '../models/osm_camera_node.dart'; - -import '../app_state.dart'; - -class OverpassService { - static const _prodEndpoint = 'https://overpass-api.de/api/interpreter'; - static const _sandboxEndpoint = 'https://overpass-api.dev.openstreetmap.org/api/interpreter'; - - // You can pass UploadMode, or use production by default - Future> fetchCameras( - LatLngBounds bbox, - List profiles, - {UploadMode uploadMode = UploadMode.production} - ) async { - if (profiles.isEmpty) return []; - - // Build one node query per enabled profile (each with all its tags required) - final nodeClauses = profiles.map((profile) { - final tagFilters = profile.tags.entries - .map((e) => '["${e.key}"="${e.value}"]') - .join('\n '); - return '''node\n $tagFilters\n (${bbox.southWest.latitude},${bbox.southWest.longitude},\n ${bbox.northEast.latitude},${bbox.northEast.longitude});'''; - }).join('\n '); - - final query = ''' - [out:json][timeout:25]; - ( - $nodeClauses - ); - out body 250; - '''; - - Future> fetchFromUri(String endpoint, String query) async { - try { - print('[Overpass] Querying $endpoint'); - print('[Overpass] Query:\n$query'); - final resp = await http.post(Uri.parse(endpoint), body: {'data': query.trim()}); - print('[Overpass] Status: \\${resp.statusCode}, Length: \\${resp.body.length}'); - if (resp.statusCode != 200) { - print('[Overpass] Failed: \\${resp.body}'); - return []; - } - final data = jsonDecode(resp.body) as Map; - final elements = data['elements'] as List; - print('[Overpass] Retrieved elements: \\${elements.length}'); - return elements.whereType>().map((e) { - return OsmCameraNode( - id: e['id'], - coord: LatLng(e['lat'], e['lon']), - tags: Map.from(e['tags'] ?? {}), - ); - }).toList(); - } catch (e) { - print('[Overpass] Exception: \\${e}'); - // Network error – return empty list silently - return []; - } - } - - // Fetch from production Overpass for all modes. - return await fetchFromUri(_prodEndpoint, query); - } -} -