mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
tbh not sure, left for a bit. sorry. pretty sure closer than last commit..
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<List<int>> 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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<void> 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<void> saveTileBytes(int z, int x, int y, String baseDir, List<int> 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<List<OsmCameraNode>> 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<dynamic>?)?.map((e) => OsmCameraNode.fromJson(e)).toList() ?? [];
|
||||
await file.writeAsBytes(bytes);
|
||||
}
|
||||
|
||||
/// Save-to-disk for cameras.json; called only by OfflineAreaService during area download
|
||||
Future<void> saveCameras(List<OsmCameraNode> cams, String dir) async {
|
||||
final file = File('$dir/cameras.json');
|
||||
await file.writeAsString(jsonEncode(cams.map((c) => c.toJson()).toList()));
|
||||
|
||||
@@ -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<List<OsmCameraNode>> fetchCameras(
|
||||
LatLngBounds bbox,
|
||||
List<CameraProfile> 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<List<OsmCameraNode>> 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<String, dynamic>;
|
||||
final elements = data['elements'] as List<dynamic>;
|
||||
print('[Overpass] Retrieved elements: \\${elements.length}');
|
||||
return elements.whereType<Map<String, dynamic>>().map((e) {
|
||||
return OsmCameraNode(
|
||||
id: e['id'],
|
||||
coord: LatLng(e['lat'], e['lon']),
|
||||
tags: Map<String, String>.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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user