diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5d9238ae --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "yaml.schemas": { + "https://json.schemastore.org/dart-build": [ + "build.yaml", + "*.build.yaml", + "build.*.yaml" + ] + }, + "[dart]": { + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + } + }, +} \ No newline at end of file diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..7df22abb --- /dev/null +++ b/build.yaml @@ -0,0 +1,13 @@ +targets: + $default: + builders: + drift_dev: + enabled: false + drift_dev:analyzer: + enabled: true + options: &options + named_parameters: true + store_date_time_values_as_text: true + drift_dev:modular: + enabled: true + options: *options diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 8e7323ff..84008219 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -26,6 +26,7 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64' } buildTypes { diff --git a/example/lib/src/screens/main/map_view/components/recovery_regions/recovery_regions.dart b/example/lib/src/screens/main/map_view/components/recovery_regions/recovery_regions.dart index 6566c6d8..134875b9 100644 --- a/example/lib/src/screens/main/map_view/components/recovery_regions/recovery_regions.dart +++ b/example/lib/src/screens/main/map_view/components/recovery_regions/recovery_regions.dart @@ -38,7 +38,9 @@ class RecoveryRegions extends StatelessWidget { borderColor: e.key.toColor(), borderStrokeWidth: 2, label: region.label, - labelPlacement: PolygonLabelPlacement.polylabel, + labelPlacementCalculator: + const PolygonLabelPlacementCalculator + .polylabel(), ), ), ) diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index e84cfb8e..951c9890 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index 62043cae..c4d0fe49 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST objectbox_flutter_libs share_plus + sqlite3_flutter_libs url_launcher_windows ) diff --git a/lib/src/backend/export_external.dart b/lib/src/backend/export_external.dart index d7fc003a..ee50b812 100644 --- a/lib/src/backend/export_external.dart +++ b/lib/src/backend/export_external.dart @@ -2,8 +2,10 @@ // A full license can be found at .\LICENSE export 'errors/errors.dart'; -export 'impls/web_noop/backend.dart' - if (dart.library.ffi) 'impls/objectbox/backend/backend.dart'; +export 'impls/drift/web/backend.dart' + if (dart.library.ffi) 'impls/drift/native/backend/backend.dart'; +export 'impls/objectbox/web_noop/backend.dart' + if (dart.library.ffi) 'impls/objectbox/native/backend/backend.dart'; export 'interfaces/backend/backend.dart'; export 'interfaces/backend/internal.dart'; export 'interfaces/backend/internal_thread_safe.dart'; diff --git a/lib/src/backend/impls/drift/native/backend/backend.dart b/lib/src/backend/impls/drift/native/backend/backend.dart new file mode 100644 index 00000000..24b58c95 --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/backend.dart @@ -0,0 +1,1499 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +import '../../../../../../flutter_map_tile_caching.dart'; +import '../../../../export_internal.dart'; +import '../database/database.dart'; +import '../database/models/root.drift.dart'; +import '../database/models/store.drift.dart'; +import '../database/models/store_tile.drift.dart' show DriftStoreTileCompanion; +import '../database/models/tile.drift.dart'; +import 'models/drift_backend_tile.dart'; +import 'thread_safe.dart'; +import 'utils/region_serialization.dart'; + +/// Implementation of [FMTCBackend] that uses Drift (SQLite) as the storage +/// database +final class FMTCDriftBackend implements FMTCBackend { + /// {@macro fmtc.backend.initialise} + /// + /// Avoid using [useInMemoryDatabase] outside of testing purposes. + @override + Future initialise({ + String? rootDirectory, + @visibleForTesting bool useInMemoryDatabase = false, + }) => + FMTCDriftBackendInternal._instance.initialise( + rootDirectory: rootDirectory, + useInMemoryDatabase: useInMemoryDatabase, + ); + + /// {@macro fmtc.backend.uninitialise} + @override + Future uninitialise({ + bool deleteRoot = false, + }) => + FMTCDriftBackendInternal._instance.uninitialise(deleteRoot: deleteRoot); +} + +/// Internal implementation of [FMTCBackend] that uses Drift as the storage +/// database +/// +/// Unlike ObjectBox, Drift handles background isolate operations internally +/// via `NativeDatabase.createInBackground()`, so no manual worker isolate +/// management is needed. +abstract interface class FMTCDriftBackendInternal + implements FMTCBackendInternal { + static final _instance = _FMTCDriftBackendInternal._(); +} + +class _FMTCDriftBackendInternal implements FMTCDriftBackendInternal { + _FMTCDriftBackendInternal._(); + + @override + String get friendlyIdentifier => 'Drift'; + + DriftFMTCDatabase? _db; + DriftFMTCDatabase get _expectDb => _db ?? (throw RootUnavailable()); + + late String rootDirectory; + late String _databasePath; + + // `removeOldestTilesAboveLimit` tracking & debouncing + Timer? _rotalDebouncer; + int? _rotalStoresHash; + Completer>? _rotalResultCompleter; + + // Lifecycle + + Future initialise({ + required String? rootDirectory, + required bool useInMemoryDatabase, + }) async { + if (_db != null) throw RootAlreadyInitialised(); + + if (useInMemoryDatabase) { + this.rootDirectory = ':memory:'; + _databasePath = ':memory:'; + _db = DriftFMTCDatabase(NativeDatabase.memory()); + } else { + final dir = rootDirectory ?? + (await getApplicationDocumentsDirectory()).absolute.path; + this.rootDirectory = path.join(dir, 'fmtc'); + _databasePath = path.join(this.rootDirectory, 'fmtc_drift.db'); + + await Directory(this.rootDirectory).create(recursive: true); + + _db = DriftFMTCDatabase( + NativeDatabase.createInBackground(File(_databasePath)), + ); + } + + // Ensure the singleton root stats row exists + await _db!.into(_db!.driftRoot).insertOnConflictUpdate( + DriftRootCompanion.insert(), + ); + + FMTCBackendAccess.internal = this; + FMTCBackendAccessThreadSafe.internal = + FMTCDriftBackendInternalThreadSafe.fromPath(_databasePath); + } + + Future uninitialise({required bool deleteRoot}) async { + _expectDb; + + await _db!.close(); + _db = null; + + if (deleteRoot && rootDirectory != ':memory:') { + final dir = Directory(rootDirectory); + if (await dir.exists()) { + await dir.delete(recursive: true); + } + } + + _rotalDebouncer?.cancel(); + _rotalDebouncer = null; + _rotalStoresHash = null; + _rotalResultCompleter?.completeError(RootUnavailable()); + _rotalResultCompleter = null; + + FMTCBackendAccess.internal = null; + FMTCBackendAccessThreadSafe.internal = null; + } + + // Root stats + + @override + Future realSize() async { + _expectDb; + if (_databasePath == ':memory:') return 0; + final file = File(_databasePath); + if (!await file.exists()) return 0; + return (await file.length()) / 1024; + } + + @override + Future rootSize() async { + final root = await (_expectDb.select(_expectDb.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + return root.size / 1024; + } + + @override + Future rootLength() async { + final root = await (_expectDb.select(_expectDb.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + return root.length; + } + + // Store management + + @override + Future> listStores() async { + final query = _expectDb.select(_expectDb.driftStore) + ..addColumns([_expectDb.driftStore.name]); + final results = await query.get(); + return results.map((r) => r.name).toList(); + } + + @override + Future storeExists({required String storeName}) async { + final query = _expectDb.select(_expectDb.driftStore) + ..where((s) => s.name.equals(storeName)); + final result = await query.getSingleOrNull(); + return result != null; + } + + @override + Future createStore({ + required String storeName, + required int? maxLength, + }) async { + await _expectDb.into(_expectDb.driftStore).insertOnConflictUpdate( + DriftStoreCompanion.insert( + name: storeName, + maxLength: Value(maxLength), + ), + ); + } + + @override + Future deleteStore({required String storeName}) async { + final db = _expectDb; + + await db.transaction(() async { + // Get all tiles that belong to this store + final tilesToCheck = await (db.select(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .get(); + final tileUids = tilesToCheck.map((st) => st.tile).toSet(); + + // Delete all junction entries for this store + await (db.delete(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .go(); + + // Find orphaned tiles (no longer in any store) + int orphanedSize = 0; + int orphanedCount = 0; + + for (final tileUid in tileUids) { + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tileUid))) + .get(); + if (remaining.isEmpty) { + // Tile is orphaned — get its size and delete it + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(tileUid))) + .getSingleOrNull(); + if (tile != null) { + orphanedSize += tile.bytes.lengthInBytes; + orphanedCount++; + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tileUid))) + .go(); + } + } + } + + // Update root stats + if (orphanedCount > 0) { + await _updateRootStats( + db, + deltaLength: -orphanedCount, + deltaSize: -orphanedSize, + ); + } + + // Delete the store itself + await (db.delete(db.driftStore)..where((s) => s.name.equals(storeName))) + .go(); + }); + } + + @override + Future resetStore({required String storeName}) async { + final db = _expectDb; + + final storeRow = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (storeRow == null) throw StoreNotExists(storeName: storeName); + + await db.transaction(() async { + // Get all tiles that belong to this store + final tilesToCheck = await (db.select(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .get(); + final tileUids = tilesToCheck.map((st) => st.tile).toSet(); + + // Delete all junction entries for this store + await (db.delete(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .go(); + + // Find and delete orphaned tiles + int orphanedSize = 0; + int orphanedCount = 0; + + for (final tileUid in tileUids) { + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tileUid))) + .get(); + if (remaining.isEmpty) { + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(tileUid))) + .getSingleOrNull(); + if (tile != null) { + orphanedSize += tile.bytes.lengthInBytes; + orphanedCount++; + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tileUid))) + .go(); + } + } + } + + // Update root stats + if (orphanedCount > 0) { + await _updateRootStats( + db, + deltaLength: -orphanedCount, + deltaSize: -orphanedSize, + ); + } + + // Reset store stats + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + const DriftStoreCompanion( + length: Value(0), + size: Value(0), + hits: Value(0), + misses: Value(0), + ), + ); + }); + } + + @override + Future renameStore({ + required String currentStoreName, + required String newStoreName, + }) async { + final db = _expectDb; + + final storeRow = await (db.select(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .getSingleOrNull(); + if (storeRow == null) throw StoreNotExists(storeName: currentStoreName); + + // CASCADE on DriftStoreTile handles junction updates automatically + await (db.update(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .write(DriftStoreCompanion(name: Value(newStoreName))); + } + + @override + Future storeGetMaxLength({required String storeName}) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + return store.maxLength; + } + + @override + Future storeSetMaxLength({ + required String storeName, + required int? newMaxLength, + }) async { + final db = _expectDb; + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(maxLength: Value(newMaxLength))); + } + + @override + Future<({double size, int length, int hits, int misses})> getStoreStats({ + required String storeName, + }) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + return ( + size: store.size / 1024, + length: store.length, + hits: store.hits, + misses: store.misses, + ); + } + + // Tile CRUD + + @override + Future tileExists({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftTile.uid.equals(url) & + db.driftStoreTile.store.isIn(resolvedStores), + ); + + final result = await query.getSingleOrNull(); + return result != null; + } + + @override + Future< + ({ + BackendTile? tile, + List intersectedStoreNames, + List allStoreNames, + })> readTile({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + // First, check if the tile exists in any of the resolved stores + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftTile.uid.equals(url) & + db.driftStoreTile.store.isIn(resolvedStores), + ); + + final result = await query.getSingleOrNull(); + + if (result == null) { + return ( + tile: null, + intersectedStoreNames: const [], + allStoreNames: const [], + ); + } + + final tileData = result.readTable(db.driftTile); + + // Get all stores this tile belongs to + final allStoresQuery = db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url)); + final allStoresResult = await allStoresQuery.get(); + final allStoreNames = + allStoresResult.map((st) => st.store).toList(growable: false); + final intersectedStoreNames = + allStoreNames.where(resolvedStores.contains).toList(growable: false); + + return ( + tile: DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ), + intersectedStoreNames: intersectedStoreNames, + allStoreNames: allStoreNames, + ); + } + + @override + Future readLatestTile({required String storeName}) async { + final db = _expectDb; + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName)) + ..orderBy([OrderingTerm.desc(db.driftTile.lastModified)]) + ..limit(1); + + final result = await query.getSingleOrNull(); + if (result == null) return null; + + final tileData = result.readTable(db.driftTile); + return DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ); + } + + @override + Future> writeTile({ + required String url, + required Uint8List bytes, + required List storeNames, + required List? writeAllNotIn, + }) async { + final db = _expectDb; + + // Resolve all available store names + final allStoreNames = await (db.select(db.driftStore) + ..addColumns([db.driftStore.name])) + .get() + .then((rows) => rows.map((r) => r.name).toList()); + + // Validate requested stores exist + for (final storeName in storeNames) { + if (!allStoreNames.contains(storeName)) { + throw StoreNotExists(storeName: storeName); + } + } + + // Compile final store list + final compiledStoreNames = writeAllNotIn == null + ? storeNames + : [ + ...storeNames, + ...allStoreNames.whereNot( + (e) => writeAllNotIn.contains(e) || storeNames.contains(e), + ), + ]; + + if (compiledStoreNames.isEmpty) return const {}; + + final result = { + for (final storeName in compiledStoreNames) storeName: false, + }; + + await db.transaction(() async { + // Check for existing tile + final existingTile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + + if (existingTile != null) { + final sizeDelta = + bytes.lengthInBytes - existingTile.bytes.lengthInBytes; + + // Update root stats for size change + await _updateRootStats(db, deltaSize: sizeDelta); + + // Get all stores this tile currently belongs to + final currentStores = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + final currentStoreNames = currentStores.map((st) => st.store).toSet(); + + // Update size for all currently related stores + for (final currentStoreName in currentStoreNames) { + await (db.update(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .write( + DriftStoreCompanion( + size: Value( + await _getStoreSize(db, currentStoreName) + sizeDelta, + ), + ), + ); + } + + // Add to new stores + for (final storeName in compiledStoreNames) { + if (!currentStoreNames.contains(storeName)) { + result[storeName] = true; + await db.into(db.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + // Update store length and size + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + } + + // Update the tile data + await (db.update(db.driftTile)..where((t) => t.uid.equals(url))).write( + DriftTileCompanion( + bytes: Value(bytes), + lastModified: Value(DateTime.timestamp()), + ), + ); + } else { + // New tile — update root stats + await _updateRootStats( + db, + deltaLength: 1, + deltaSize: bytes.lengthInBytes, + ); + + // Insert the tile + await db.into(db.driftTile).insert( + DriftTileCompanion.insert( + uid: url, + bytes: bytes, + ), + ); + + // Link to all target stores and update their stats + for (final storeName in compiledStoreNames) { + result[storeName] = true; + + await db.into(db.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + } + }); + + return result; + } + + @override + Future deleteTile({ + required String storeName, + required String url, + }) async { + final db = _expectDb; + + return db.transaction(() async { + // Check tile exists + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + if (tile == null) return null; + + // Check if tile is in this store + final junction = await (db.select(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .getSingleOrNull(); + if (junction == null) return null; + + // Remove from store + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .go(); + + // Update store stats + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length - 1), + size: Value(store.size - tile.bytes.lengthInBytes), + ), + ); + + // Check if tile is now orphaned + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + + if (remaining.isEmpty) { + // Tile is orphaned — delete it and update root stats + await (db.delete(db.driftTile)..where((t) => t.uid.equals(url))).go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + return true; + } + + return false; + }); + } + + // Statistics + + @override + Future incrementStoreHits({ + required List storeNames, + }) async { + final db = _expectDb; + await db.transaction(() async { + for (final storeName in storeNames) { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(hits: Value(store.hits + 1))); + } + }); + } + + @override + Future incrementStoreMisses({ + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + await db.transaction(() async { + for (final storeName in resolvedStores) { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(misses: Value(store.misses + 1))); + } + }); + } + + @override + Future> removeOldestTilesAboveLimit({ + required List storeNames, + }) async { + // Port the same debouncing logic from ObjectBox + if (_rotalResultCompleter?.isCompleted ?? true) { + _rotalResultCompleter = Completer>(); + } + + void doRemoval() => _rotalResultCompleter!.complete( + _doRemoveOldestTilesAboveLimit(storeNames), + ); + + if (_rotalStoresHash != storeNames.hashCode) { + _rotalStoresHash = storeNames.hashCode; + if (_rotalDebouncer?.isActive ?? false) { + _rotalDebouncer!.cancel(); + doRemoval(); + return _rotalResultCompleter!.future; + } + } + + final isAlreadyActive = _rotalDebouncer?.isActive ?? false; + if (isAlreadyActive) _rotalDebouncer!.cancel(); + _rotalDebouncer = Timer( + Duration(milliseconds: isAlreadyActive ? 500 : 1000), + doRemoval, + ); + + return _rotalResultCompleter!.future; + } + + Future> _doRemoveOldestTilesAboveLimit( + List storeNames, + ) async { + final db = _expectDb; + final result = {}; + + for (final storeName in storeNames) { + final store = await (db.select(db.driftStore) + ..where( + (s) => s.name.equals(storeName) & s.maxLength.isNotNull(), + )) + .getSingleOrNull(); + if (store == null) continue; + + final numToRemove = store.length - store.maxLength!; + if (numToRemove <= 0) continue; + + // Get oldest tiles in this store + final oldestTilesQuery = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName)) + ..orderBy([OrderingTerm.asc(db.driftTile.lastModified)]) + ..limit(numToRemove); + + final oldestTiles = await oldestTilesQuery.get(); + int orphansCount = 0; + + await db.transaction(() async { + for (final row in oldestTiles) { + final tile = row.readTable(db.driftTile); + + // Remove from this store + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(tile.uid), + )) + .go(); + + // Update store stats + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value( + (await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle()) + .length - + 1, + ), + size: Value( + (await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle()) + .size - + tile.bytes.lengthInBytes, + ), + ), + ); + + // Check if orphaned + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tile.uid))) + .get(); + + if (remaining.isEmpty) { + await (db.delete(db.driftTile) + ..where((t) => t.uid.equals(tile.uid))) + .go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + orphansCount++; + } + } + }); + + result[storeName] = orphansCount; + } + + return result; + } + + @override + Future removeTilesOlderThan({ + required String storeName, + required DateTime expiry, + }) async { + final db = _expectDb; + + // Get tiles older than expiry in this store + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftStoreTile.store.equals(storeName) & + db.driftTile.lastModified.isSmallerThanValue(expiry), + ); + + final expiredTiles = await query.get(); + int orphansCount = 0; + + await db.transaction(() async { + for (final row in expiredTiles) { + final tile = row.readTable(db.driftTile); + + // Remove from store + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(tile.uid), + )) + .go(); + + // Update store stats + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length - 1), + size: Value(store.size - tile.bytes.lengthInBytes), + ), + ); + + // Check if orphaned + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tile.uid))) + .get(); + + if (remaining.isEmpty) { + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tile.uid))) + .go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + orphansCount++; + } + } + }); + + return orphansCount; + } + + // Metadata + + @override + Future> readMetadata({ + required String storeName, + }) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + return (jsonDecode(store.metadataJson) as Map) + .cast(); + } + + @override + Future setMetadata({ + required String storeName, + required String key, + required String value, + }) => + setBulkMetadata(storeName: storeName, kvs: {key: value}); + + @override + Future setBulkMetadata({ + required String storeName, + required Map kvs, + }) async { + final db = _expectDb; + + await db.transaction(() async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + final metadata = jsonDecode(store.metadataJson) as Map + ..addAll(kvs); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion(metadataJson: Value(jsonEncode(metadata))), + ); + }); + } + + @override + Future removeMetadata({ + required String storeName, + required String key, + }) async { + final db = _expectDb; + + return db.transaction(() async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + final metadata = jsonDecode(store.metadataJson) as Map; + final removedVal = metadata.remove(key) as String?; + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion(metadataJson: Value(jsonEncode(metadata))), + ); + + return removedVal; + }); + } + + @override + Future resetMetadata({required String storeName}) async { + final db = _expectDb; + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(const DriftStoreCompanion(metadataJson: Value('{}'))); + } + + // Recovery + + @override + Future> listRecoverableRegions() async { + final db = _expectDb; + final recoveries = await db.select(db.driftRecovery).get(); + final allRegions = await db.select(db.driftRecoveryRegion).get(); + + return recoveries.map((r) { + // Find the root region (no parentRegionId) + final rootRegion = allRegions.firstWhere( + (rr) => rr.recovery == r.id && rr.parentRegionId == null, + ); + + return RecoveredRegion( + id: r.id, + storeName: r.store, + time: r.creationTime, + minZoom: r.minZoom, + maxZoom: r.maxZoom, + start: r.startTile, + end: r.endTile, + region: dataToRegion(rootRegion, allRegions), + ); + }).toList(growable: false); + } + + @override + Future getRecoverableRegion({required int id}) async { + final db = _expectDb; + final recovery = await (db.select(db.driftRecovery) + ..where((r) => r.id.equals(id))) + .getSingle(); + final allRegions = await (db.select(db.driftRecoveryRegion) + ..where((rr) => rr.recovery.equals(id))) + .get(); + + final rootRegion = allRegions.firstWhere( + (rr) => rr.parentRegionId == null, + ); + + return RecoveredRegion( + id: recovery.id, + storeName: recovery.store, + time: recovery.creationTime, + minZoom: recovery.minZoom, + maxZoom: recovery.maxZoom, + start: recovery.startTile, + end: recovery.endTile, + region: dataToRegion(rootRegion, allRegions), + ); + } + + @override + Future cancelRecovery({required int id}) async { + final db = _expectDb; + await db.transaction(() async { + await (db.delete(db.driftRecoveryRegion) + ..where((rr) => rr.recovery.equals(id))) + .go(); + await (db.delete(db.driftRecovery)..where((r) => r.id.equals(id))).go(); + }); + } + + @override + Stream watchRecovery({required bool triggerImmediately}) { + final db = _expectDb; + final query = db.select(db.driftRecovery); + final stream = query.watch().map((_) {}); + if (triggerImmediately) { + return Stream.multi((controller) { + controller.add(null); + stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close, + ); + }); + } + return stream; + } + + // Watchers + + @override + Stream watchStores({ + required List storeNames, + required bool triggerImmediately, + }) { + final db = _expectDb; + final query = db.select(db.driftStore) + ..where((s) => s.name.isIn(storeNames)); + final stream = query.watch().map((_) {}); + if (triggerImmediately) { + return Stream.multi((controller) { + controller.add(null); + stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close, + ); + }); + } + return stream; + } + + // Import/Export + + @override + Future exportStores({ + required List storeNames, + required String path, + }) async { + if (storeNames.isEmpty) { + throw ArgumentError.value(storeNames, 'storeNames', 'must not be empty'); + } + + final type = await FileSystemEntity.type(path); + if (type == FileSystemEntityType.directory) { + throw ImportExportPathNotFile(); + } + + final db = _expectDb; + + // Create a temporary database for export + final exportFile = File(path); + final exportDb = DriftFMTCDatabase(NativeDatabase(exportFile)); + + try { + // Ensure root row exists in export db + await exportDb.into(exportDb.driftRoot).insertOnConflictUpdate( + DriftRootCompanion.insert(), + ); + + int totalTiles = 0; + + for (final storeName in storeNames) { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) continue; + + // Copy store + await exportDb.into(exportDb.driftStore).insertOnConflictUpdate( + DriftStoreCompanion.insert( + name: store.name, + maxLength: Value(store.maxLength), + length: Value(store.length), + size: Value(store.size), + hits: Value(store.hits), + misses: Value(store.misses), + metadataJson: Value(store.metadataJson), + ), + ); + + // Copy tiles + final tiles = await (db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName))) + .get(); + + for (final row in tiles) { + final tile = row.readTable(db.driftTile); + await exportDb.into(exportDb.driftTile).insertOnConflictUpdate( + DriftTileCompanion.insert( + uid: tile.uid, + bytes: tile.bytes, + lastModified: Value(tile.lastModified), + ), + ); + await exportDb.into(exportDb.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: storeName, + tile: tile.uid, + ), + ); + totalTiles++; + } + } + + await exportDb.close(); + + // Append FMTC footer signature + exportFile.openSync(mode: FileMode.append) + ..writeFromSync([0xFF, 0xFF]) // separator + ..writeStringSync('Drift') + ..writeFromSync([0xFF, 0xFF]) // separator + ..writeStringSync('FMTC') + ..closeSync(); + + return totalTiles; + } catch (e) { + await exportDb.close(); + rethrow; + } + } + + @override + ImportResult importStores({ + required String path, + required ImportConflictStrategy strategy, + required List? storeNames, + }) { + final storesToStates = Completer(); + final complete = Completer(); + + _doImport( + path: path, + strategy: strategy, + storeNames: storeNames, + storesToStates: storesToStates, + complete: complete, + ); + + return ( + storesToStates: storesToStates.future, + complete: complete.future, + ); + } + + Future _doImport({ + required String path, + required ImportConflictStrategy strategy, + required List? storeNames, + required Completer storesToStates, + required Completer complete, + }) async { + try { + await _checkImportPathType(path); + + final importFile = File(path); + _verifyImportableArchive(importFile); + + final importDb = DriftFMTCDatabase(NativeDatabase(importFile)); + final db = _expectDb; + + try { + final availableStores = + await importDb.select(importDb.driftStore).get(); + final targetStores = storeNames == null + ? availableStores + : availableStores + .where((s) => storeNames.contains(s.name)) + .toList(); + + final states = {}; + + for (final importStore in targetStores) { + final existingStore = await (db.select(db.driftStore) + ..where((s) => s.name.equals(importStore.name))) + .getSingleOrNull(); + + final hadConflict = existingStore != null; + String? finalName = importStore.name; + + if (hadConflict) { + switch (strategy) { + case ImportConflictStrategy.skip: + states[importStore.name] = (name: null, hadConflict: true); + continue; + case ImportConflictStrategy.rename: + int suffix = 1; + while (await (db.select(db.driftStore) + ..where( + (s) => s.name.equals('${importStore.name}_$suffix'), + )) + .getSingleOrNull() != + null) { + suffix++; + } + finalName = '${importStore.name}_$suffix'; + case ImportConflictStrategy.replace: + await deleteStore(storeName: importStore.name); + case ImportConflictStrategy.merge: + break; + } + } + + states[importStore.name] = + (name: finalName, hadConflict: hadConflict); + + // Create/ensure store exists + await db.into(db.driftStore).insertOnConflictUpdate( + DriftStoreCompanion.insert( + name: finalName, + maxLength: Value(importStore.maxLength), + ), + ); + + // Copy tiles + final tiles = await (importDb.select(importDb.driftTile).join([ + innerJoin( + importDb.driftStoreTile, + importDb.driftStoreTile.tile.equalsExp(importDb.driftTile.uid), + ), + ]) + ..where(importDb.driftStoreTile.store.equals(importStore.name))) + .get(); + + for (final row in tiles) { + final tile = row.readTable(importDb.driftTile); + await db.into(db.driftTile).insertOnConflictUpdate( + DriftTileCompanion.insert( + uid: tile.uid, + bytes: tile.bytes, + lastModified: Value(tile.lastModified), + ), + ); + await db.into(db.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: finalName, + tile: tile.uid, + ), + ); + } + } + + if (!storesToStates.isCompleted) { + storesToStates.complete(states); + } + + // Recalculate stats for all affected stores + for (final entry in states.entries) { + final name = entry.value.name; + if (name == null) continue; + await _recalculateStoreStats(db, name); + } + await _recalculateRootStats(db); + + await importDb.close(); + + if (!complete.isCompleted) { + final totalTiles = states.values.where((s) => s.name != null).length; + complete.complete(totalTiles); + } + } catch (e) { + await importDb.close(); + rethrow; + } + } catch (e, st) { + if (!storesToStates.isCompleted) { + storesToStates.completeError(e, st); + } + if (!complete.isCompleted) { + complete.completeError(e, st); + } + } + } + + @override + Future> listImportableStores({required String path}) async { + await _checkImportPathType(path); + + final importFile = File(path); + _verifyImportableArchive(importFile); + + final importDb = DriftFMTCDatabase(NativeDatabase(importFile)); + + try { + final stores = await importDb.select(importDb.driftStore).get(); + await importDb.close(); + return stores.map((s) => s.name).toList(); + } catch (e) { + await importDb.close(); + rethrow; + } + } + + // Helper methods + + Future _getStoreSize(DriftFMTCDatabase db, String storeName) async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + return store.size; + } + + Future _updateRootStats( + DriftFMTCDatabase db, { + int deltaLength = 0, + int deltaSize = 0, + }) async { + final root = await (db.select(db.driftRoot)..where((r) => r.id.equals(0))) + .getSingle(); + await (db.update(db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion( + length: Value(root.length + deltaLength), + size: Value(root.size + deltaSize), + ), + ); + } + + Future> _resolveReadableStoresFormat( + DriftFMTCDatabase db, + ({bool includeOrExclude, List storeNames}) readableStores, + ) async { + if (!readableStores.includeOrExclude) { + final allStores = await db.select(db.driftStore).get(); + final allNames = allStores.map((s) => s.name); + return allNames + .whereNot((e) => readableStores.storeNames.contains(e)) + .toList(growable: false); + } + + // Verify all stores exist + for (final storeName in readableStores.storeNames) { + final exists = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (exists == null) throw StoreNotExists(storeName: storeName); + } + + return readableStores.storeNames; + } + + Future _checkImportPathType(String path) async { + final type = await FileSystemEntity.type(path); + if (type == FileSystemEntityType.notFound) { + throw ImportPathNotExists(path: path); + } + if (type == FileSystemEntityType.directory) { + throw ImportExportPathNotFile(); + } + } + + void _verifyImportableArchive(File importFile) { + final ram = importFile.openSync(mode: FileMode.append); + try { + int cursorPos = ram.positionSync() - 1; + ram.setPositionSync(cursorPos); + + // Check for FMTC footer signature ("**FMTC") + const signature = [255, 255, 70, 77, 84, 67]; + for (int i = 5; i >= 0; i--) { + if (signature[i] != ram.readByteSync()) { + throw ImportFileNotFMTCStandard(); + } + ram.setPositionSync(--cursorPos); + } + + // Check for expected backend identifier ("**Drift") + const id = [255, 255, 68, 114, 105, 102, 116]; + for (int i = 6; i >= 0; i--) { + if (id[i] != ram.readByteSync()) { + throw ImportFileNotBackendCompatible(); + } + ram.setPositionSync(--cursorPos); + } + + ram.truncateSync(--cursorPos); + } catch (e) { + ram.closeSync(); + rethrow; + } + ram.closeSync(); + } + + Future _recalculateStoreStats( + DriftFMTCDatabase db, + String storeName, + ) async { + final tiles = await (db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName))) + .get(); + + int totalSize = 0; + for (final row in tiles) { + totalSize += row.readTable(db.driftTile).bytes.lengthInBytes; + } + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(tiles.length), + size: Value(totalSize), + ), + ); + } + + Future _recalculateRootStats(DriftFMTCDatabase db) async { + final tileCount = await db.select(db.driftTile).get(); + + int totalSize = 0; + for (final tile in tileCount) { + totalSize += tile.bytes.lengthInBytes; + } + + await (db.update(db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion( + length: Value(tileCount.length), + size: Value(totalSize), + ), + ); + } +} diff --git a/lib/src/backend/impls/drift/native/backend/internal.dart b/lib/src/backend/impls/drift/native/backend/internal.dart new file mode 100644 index 00000000..a14da83c --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/internal.dart @@ -0,0 +1,6 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +// This file is intentionally empty. +// The Drift backend implementation is fully contained in backend.dart. +// Unlike ObjectBox, Drift doesn't require a separate part file for internals. diff --git a/lib/src/backend/impls/drift/native/backend/models/drift_backend_tile.dart b/lib/src/backend/impls/drift/native/backend/models/drift_backend_tile.dart new file mode 100644 index 00000000..6eb4b1e5 --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/models/drift_backend_tile.dart @@ -0,0 +1,25 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:typed_data'; + +import '../../../../../interfaces/models.dart'; + +/// Drift-specific implementation of [BackendTile] +base class DriftBackendTile extends BackendTile { + /// Create a Drift-specific implementation of [BackendTile] + DriftBackendTile({ + required this.url, + required this.bytes, + required this.lastModified, + }); + + @override + final String url; + + @override + final Uint8List bytes; + + @override + final DateTime lastModified; +} diff --git a/lib/src/backend/impls/drift/native/backend/thread_safe.dart b/lib/src/backend/impls/drift/native/backend/thread_safe.dart new file mode 100644 index 00000000..2bf5b588 --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/thread_safe.dart @@ -0,0 +1,292 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; + +import '../../../../../../flutter_map_tile_caching.dart'; +import '../../../../export_internal.dart'; +import '../database/database.dart'; +import '../database/models/recovery.drift.dart'; +import '../database/models/root.drift.dart'; +import '../database/models/store.drift.dart' show DriftStoreCompanion; +import '../database/models/store_tile.drift.dart'; +import '../database/models/tile.drift.dart'; +import 'models/drift_backend_tile.dart'; +import 'utils/region_serialization.dart'; + +/// Thread-safe implementation for Drift backend, used by bulk download isolates +/// +/// Unlike ObjectBox's `Store.attach()`, Drift uses standard SQLite connections. +/// Each isolate gets its own connection to the same database file. +/// WAL mode (default in modern SQLite) supports concurrent readers. +class FMTCDriftBackendInternalThreadSafe + implements FMTCBackendInternalThreadSafe { + /// Creates an instance from the given [databasePath] to the SQLite file + FMTCDriftBackendInternalThreadSafe.fromPath(String databasePath) + : _databasePath = databasePath; + + @override + String get friendlyIdentifier => 'Drift'; + + final String _databasePath; + DriftFMTCDatabase? _db; + DriftFMTCDatabase get _expectDb => _db ?? (throw RootUnavailable()); + + @override + void initialise() { + if (_db != null) throw RootAlreadyInitialised(); + if (_databasePath == ':memory:') { + _db = DriftFMTCDatabase(NativeDatabase.memory()); + } else { + _db = DriftFMTCDatabase(NativeDatabase(File(_databasePath))); + } + } + + @override + void uninitialise() { + _expectDb; + _db!.close(); + _db = null; + } + + @override + FMTCDriftBackendInternalThreadSafe duplicate() => + FMTCDriftBackendInternalThreadSafe.fromPath(_databasePath); + + @override + Future readTile({ + required String url, + String? storeName, + }) async { + final db = _expectDb; + + if (storeName != null) { + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftTile.uid.equals(url) & + db.driftStoreTile.store.equals(storeName), + ); + + final result = await query.getSingleOrNull(); + if (result == null) return null; + + final tileData = result.readTable(db.driftTile); + return DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ); + } else { + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + if (tile == null) return null; + + return DriftBackendTile( + url: tile.uid, + bytes: tile.bytes, + lastModified: tile.lastModified, + ); + } + } + + @override + Future writeTile({ + required String storeName, + required String url, + required Uint8List bytes, + }) async { + final db = _expectDb; + + await db.transaction(() async { + final existingTile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + if (existingTile != null) { + final sizeDelta = + bytes.lengthInBytes - existingTile.bytes.lengthInBytes; + + // Check if tile is already in this store + final junction = await (db.select(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .getSingleOrNull(); + + // Update all related stores' sizes + final relatedStores = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + for (final rel in relatedStores) { + final relStore = await (db.select(db.driftStore) + ..where((s) => s.name.equals(rel.store))) + .getSingle(); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(rel.store))) + .write( + DriftStoreCompanion( + size: Value(relStore.size + sizeDelta), + ), + ); + } + + // Update root stats for size change + final root = await (db.select(db.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + await (db.update(db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion(size: Value(root.size + sizeDelta)), + ); + + if (junction == null) { + // Add to this store + await db.into(db.driftStoreTile).insert( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + + // Update tile bytes + await (db.update(db.driftTile)..where((t) => t.uid.equals(url))).write( + DriftTileCompanion( + bytes: Value(bytes), + lastModified: Value(DateTime.timestamp()), + ), + ); + } else { + // New tile + await db.into(db.driftTile).insert( + DriftTileCompanion.insert( + uid: url, + bytes: bytes, + ), + ); + await db.into(db.driftStoreTile).insert( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + + // Update root stats + final root = await (db.select(db.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + await (db.update(db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion( + length: Value(root.length + 1), + size: Value(root.size + bytes.lengthInBytes), + ), + ); + } + }); + } + + @override + Future writeTiles({ + required String storeName, + required List urls, + required List bytess, + }) async { + final db = _expectDb; + + await db.transaction(() async { + for (int i = 0; i < urls.length; i++) { + await writeTile( + storeName: storeName, + url: urls[i], + bytes: bytess[i], + ); + } + }); + } + + @override + Future startRecovery({ + required int id, + required String storeName, + required DownloadableRegion region, + required int tilesCount, + }) async { + final db = _expectDb; + + await db.transaction(() async { + // Insert recovery entry + await db.into(db.driftRecovery).insert( + DriftRecoveryCompanion.insert( + id: Value(id), + store: storeName, + minZoom: region.minZoom, + maxZoom: region.maxZoom, + startTile: region.start, + endTile: region.end ?? (region.start - 1 + tilesCount), + ), + ); + + // Recursively insert recovery regions + Future insertRegion( + BaseRegion baseRegion, + int? parentId, + ) async { + final companion = regionToCompanion( + region: baseRegion, + recoveryId: id, + parentRegionId: parentId, + ); + + final regionId = + await db.into(db.driftRecoveryRegion).insert(companion); + + if (baseRegion case final MultiRegion multiRegion) { + for (final subRegion in multiRegion.regions) { + await insertRegion(subRegion, regionId); + } + } + } + + await insertRegion(region.originalRegion, null); + }); + } + + @override + Future updateRecovery({ + required int id, + required int newStartTile, + }) async { + final db = _expectDb; + + await (db.update(db.driftRecovery)..where((r) => r.id.equals(id))) + .write(DriftRecoveryCompanion(startTile: Value(newStartTile))); + } +} diff --git a/lib/src/backend/impls/drift/native/backend/utils/region_serialization.dart b/lib/src/backend/impls/drift/native/backend/utils/region_serialization.dart new file mode 100644 index 00000000..01b3273e --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/utils/region_serialization.dart @@ -0,0 +1,111 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'package:drift/drift.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +import '../../../../../../../flutter_map_tile_caching.dart'; +import '../../database/models/recovery_region.drift.dart'; + +/// Convert a [BaseRegion] to a [DriftRecoveryRegionCompanion] for insertion +DriftRecoveryRegionCompanion regionToCompanion({ + required BaseRegion region, + required int recoveryId, + int? parentRegionId, +}) { + final typeId = switch (region) { + RectangleRegion() => 0, + CircleRegion() => 1, + LineRegion() => 2, + CustomPolygonRegion() => 3, + MultiRegion() => 4, + }; + + return DriftRecoveryRegionCompanion.insert( + recovery: recoveryId, + parentRegionId: Value(parentRegionId), + typeId: typeId, + rectNwLat: Value( + region is RectangleRegion ? region.bounds.northWest.latitude : null, + ), + rectNwLng: Value( + region is RectangleRegion ? region.bounds.northWest.longitude : null, + ), + rectSeLat: Value( + region is RectangleRegion ? region.bounds.southEast.latitude : null, + ), + rectSeLng: Value( + region is RectangleRegion ? region.bounds.southEast.longitude : null, + ), + circleCenterLat: Value( + region is CircleRegion ? region.center.latitude : null, + ), + circleCenterLng: Value( + region is CircleRegion ? region.center.longitude : null, + ), + circleRadius: Value(region is CircleRegion ? region.radius : null), + lineLats: Value( + region is LineRegion + ? region.line.map((c) => c.latitude).join(',') + : null, + ), + lineLngs: Value( + region is LineRegion + ? region.line.map((c) => c.longitude).join(',') + : null, + ), + lineRadius: Value(region is LineRegion ? region.radius : null), + customPolygonLats: Value( + region is CustomPolygonRegion + ? region.outline.map((c) => c.latitude).join(',') + : null, + ), + customPolygonLngs: Value( + region is CustomPolygonRegion + ? region.outline.map((c) => c.longitude).join(',') + : null, + ), + ); +} + +/// Convert a [DriftRecoveryRegionData] and its children to a [BaseRegion] +BaseRegion dataToRegion( + DriftRecoveryRegionData data, + List allRegionData, +) => + switch (data.typeId) { + 0 => RectangleRegion( + LatLngBounds( + LatLng(data.rectNwLat!, data.rectNwLng!), + LatLng(data.rectSeLat!, data.rectSeLng!), + ), + ), + 1 => CircleRegion( + LatLng(data.circleCenterLat!, data.circleCenterLng!), + data.circleRadius!, + ), + 2 => LineRegion( + _parseCoordList(data.lineLats!, data.lineLngs!), + data.lineRadius!, + ), + 3 => CustomPolygonRegion( + _parseCoordList(data.customPolygonLats!, data.customPolygonLngs!), + ), + 4 => MultiRegion( + allRegionData + .where((child) => child.parentRegionId == data.id) + .map((child) => dataToRegion(child, allRegionData)) + .toList(growable: false), + ), + _ => throw UnimplementedError('Unknown region typeId: ${data.typeId}'), + }; + +List _parseCoordList(String lats, String lngs) { + final latList = lats.split(',').map(double.parse).toList(); + final lngList = lngs.split(',').map(double.parse).toList(); + return List.generate( + latList.length, + (i) => LatLng(latList[i], lngList[i]), + ); +} diff --git a/lib/src/backend/impls/drift/native/database/database.dart b/lib/src/backend/impls/drift/native/database/database.dart new file mode 100644 index 00000000..9277798f --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/database.dart @@ -0,0 +1,36 @@ +import 'package:drift/drift.dart'; + +import 'database.drift.dart'; +import 'models/recovery.dart'; +import 'models/recovery_region.dart'; +import 'models/root.dart'; +import 'models/store.dart'; +import 'models/store_tile.dart'; +import 'models/tile.dart'; + +@DriftDatabase( + tables: [ + DriftTile, + DriftStore, + DriftStoreTile, + DriftRoot, + DriftRecovery, + DriftRecoveryRegion, + ], +) + +/// The main Drift database class for FMTC's native SQLite backend +class DriftFMTCDatabase extends $DriftFMTCDatabase { + /// Creates a [DriftFMTCDatabase] using the given [connection] + DriftFMTCDatabase(super.e); + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration => MigrationStrategy( + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + }, + ); +} diff --git a/lib/src/backend/impls/drift/native/database/database.drift.dart b/lib/src/backend/impls/drift/native/database/database.drift.dart new file mode 100644 index 00000000..72214218 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/database.drift.dart @@ -0,0 +1,90 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/tile.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store.drift.dart' + as i2; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store_tile.drift.dart' + as i3; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/root.drift.dart' + as i4; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery.drift.dart' + as i5; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery_region.drift.dart' + as i6; + +abstract class $DriftFMTCDatabase extends i0.GeneratedDatabase { + $DriftFMTCDatabase(i0.QueryExecutor e) : super(e); + $DriftFMTCDatabaseManager get managers => $DriftFMTCDatabaseManager(this); + late final i1.$DriftTileTable driftTile = i1.$DriftTileTable(this); + late final i2.$DriftStoreTable driftStore = i2.$DriftStoreTable(this); + late final i3.$DriftStoreTileTable driftStoreTile = + i3.$DriftStoreTileTable(this); + late final i4.$DriftRootTable driftRoot = i4.$DriftRootTable(this); + late final i5.$DriftRecoveryTable driftRecovery = + i5.$DriftRecoveryTable(this); + late final i6.$DriftRecoveryRegionTable driftRecoveryRegion = + i6.$DriftRecoveryRegionTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + driftTile, + driftStore, + driftStoreTile, + driftRoot, + driftRecovery, + driftRecoveryRegion, + i1.lastModified, + i3.idxStoreTileTile + ]; + @override + i0.StreamQueryUpdateRules get streamUpdateRules => + const i0.StreamQueryUpdateRules( + [ + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('drift_store', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('drift_store_tile', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('drift_store', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('drift_store_tile', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('drift_tile', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('drift_store_tile', kind: i0.UpdateKind.delete), + ], + ), + ], + ); + @override + i0.DriftDatabaseOptions get options => + const i0.DriftDatabaseOptions(storeDateTimeAsText: true); +} + +class $DriftFMTCDatabaseManager { + final $DriftFMTCDatabase _db; + $DriftFMTCDatabaseManager(this._db); + i1.$$DriftTileTableTableManager get driftTile => + i1.$$DriftTileTableTableManager(_db, _db.driftTile); + i2.$$DriftStoreTableTableManager get driftStore => + i2.$$DriftStoreTableTableManager(_db, _db.driftStore); + i3.$$DriftStoreTileTableTableManager get driftStoreTile => + i3.$$DriftStoreTileTableTableManager(_db, _db.driftStoreTile); + i4.$$DriftRootTableTableManager get driftRoot => + i4.$$DriftRootTableTableManager(_db, _db.driftRoot); + i5.$$DriftRecoveryTableTableManager get driftRecovery => + i5.$$DriftRecoveryTableTableManager(_db, _db.driftRecovery); + i6.$$DriftRecoveryRegionTableTableManager get driftRecoveryRegion => + i6.$$DriftRecoveryRegionTableTableManager(_db, _db.driftRecoveryRegion); +} diff --git a/lib/src/backend/impls/drift/native/database/models/recovery.dart b/lib/src/backend/impls/drift/native/database/models/recovery.dart new file mode 100644 index 00000000..0154cc58 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery.dart @@ -0,0 +1,33 @@ +import 'package:drift/drift.dart'; + +import 'store.dart'; + +/// Drift table tracking in-progress download recovery sessions +class DriftRecovery extends Table { + /// Unique ID for the recovery session + late final id = integer()(); + + /// The name of the store being downloaded when the failure occurred + late final store = text().references(DriftStore, #name)(); + + /// The time the recovery session was created + late final creationTime = dateTime().withDefault(currentDateAndTime)(); + + /// Minimum zoom level of the download + late final minZoom = integer()(); + + /// Maximum zoom level of the download + late final maxZoom = integer()(); + + /// The tile index the download started from + late final startTile = integer()(); + + /// The tile index the download ended at + late final endTile = integer()(); + + @override + Set> get primaryKey => {id}; + + @override + bool get isStrict => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart b/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart new file mode 100644 index 00000000..87b2149a --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart @@ -0,0 +1,727 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; + +typedef $$DriftRecoveryTableCreateCompanionBuilder = i1.DriftRecoveryCompanion + Function({ + i0.Value id, + required String store, + i0.Value creationTime, + required int minZoom, + required int maxZoom, + required int startTile, + required int endTile, +}); +typedef $$DriftRecoveryTableUpdateCompanionBuilder = i1.DriftRecoveryCompanion + Function({ + i0.Value id, + i0.Value store, + i0.Value creationTime, + i0.Value minZoom, + i0.Value maxZoom, + i0.Value startTile, + i0.Value endTile, +}); + +final class $$DriftRecoveryTableReferences extends i0.BaseReferences< + i0.GeneratedDatabase, i1.$DriftRecoveryTable, i1.DriftRecoveryData> { + $$DriftRecoveryTableReferences( + super.$_db, super.$_table, super.$_typedResult); + + static i4.$DriftStoreTable _storeTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('drift_store') + .createAlias(i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet('drift_recovery') + .store, + i5.ReadDatabaseContainer(db) + .resultSet('drift_store') + .name)); + + i4.$$DriftStoreTableProcessedTableManager get store { + final $_column = $_itemColumn('store')!; + + final manager = i4 + .$$DriftStoreTableTableManager( + $_db, + i5.ReadDatabaseContainer($_db) + .resultSet('drift_store')) + .filter((f) => f.name.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_storeTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + +class $$DriftRecoveryTableFilterComposer + extends i0.Composer { + $$DriftRecoveryTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get creationTime => $composableBuilder( + column: $table.creationTime, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get minZoom => $composableBuilder( + column: $table.minZoom, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get maxZoom => $composableBuilder( + column: $table.maxZoom, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get startTile => $composableBuilder( + column: $table.startTile, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get endTile => $composableBuilder( + column: $table.endTile, builder: (column) => i0.ColumnFilters(column)); + + i4.$$DriftStoreTableFilterComposer get store { + final i4.$$DriftStoreTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftStoreTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryTableOrderingComposer + extends i0.Composer { + $$DriftRecoveryTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get creationTime => $composableBuilder( + column: $table.creationTime, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get minZoom => $composableBuilder( + column: $table.minZoom, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get maxZoom => $composableBuilder( + column: $table.maxZoom, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get startTile => $composableBuilder( + column: $table.startTile, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get endTile => $composableBuilder( + column: $table.endTile, builder: (column) => i0.ColumnOrderings(column)); + + i4.$$DriftStoreTableOrderingComposer get store { + final i4.$$DriftStoreTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftStoreTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryTableAnnotationComposer + extends i0.Composer { + $$DriftRecoveryTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get creationTime => $composableBuilder( + column: $table.creationTime, builder: (column) => column); + + i0.GeneratedColumn get minZoom => + $composableBuilder(column: $table.minZoom, builder: (column) => column); + + i0.GeneratedColumn get maxZoom => + $composableBuilder(column: $table.maxZoom, builder: (column) => column); + + i0.GeneratedColumn get startTile => + $composableBuilder(column: $table.startTile, builder: (column) => column); + + i0.GeneratedColumn get endTile => + $composableBuilder(column: $table.endTile, builder: (column) => column); + + i4.$$DriftStoreTableAnnotationComposer get store { + final i4.$$DriftStoreTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftStoreTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftRecoveryTable, + i1.DriftRecoveryData, + i1.$$DriftRecoveryTableFilterComposer, + i1.$$DriftRecoveryTableOrderingComposer, + i1.$$DriftRecoveryTableAnnotationComposer, + $$DriftRecoveryTableCreateCompanionBuilder, + $$DriftRecoveryTableUpdateCompanionBuilder, + (i1.DriftRecoveryData, i1.$$DriftRecoveryTableReferences), + i1.DriftRecoveryData, + i0.PrefetchHooks Function({bool store})> { + $$DriftRecoveryTableTableManager( + i0.GeneratedDatabase db, i1.$DriftRecoveryTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$DriftRecoveryTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftRecoveryTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$DriftRecoveryTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value store = const i0.Value.absent(), + i0.Value creationTime = const i0.Value.absent(), + i0.Value minZoom = const i0.Value.absent(), + i0.Value maxZoom = const i0.Value.absent(), + i0.Value startTile = const i0.Value.absent(), + i0.Value endTile = const i0.Value.absent(), + }) => + i1.DriftRecoveryCompanion( + id: id, + store: store, + creationTime: creationTime, + minZoom: minZoom, + maxZoom: maxZoom, + startTile: startTile, + endTile: endTile, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String store, + i0.Value creationTime = const i0.Value.absent(), + required int minZoom, + required int maxZoom, + required int startTile, + required int endTile, + }) => + i1.DriftRecoveryCompanion.insert( + id: id, + store: store, + creationTime: creationTime, + minZoom: minZoom, + maxZoom: maxZoom, + startTile: startTile, + endTile: endTile, + ), + withReferenceMapper: (p0) => p0 + .map((e) => ( + e.readTable(table), + i1.$$DriftRecoveryTableReferences(db, table, e) + )) + .toList(), + prefetchHooksCallback: ({store = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (store) { + state = state.withJoin( + currentTable: table, + currentColumn: table.store, + referencedTable: + i1.$$DriftRecoveryTableReferences._storeTable(db), + referencedColumn: + i1.$$DriftRecoveryTableReferences._storeTable(db).name, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + )); +} + +typedef $$DriftRecoveryTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftRecoveryTable, + i1.DriftRecoveryData, + i1.$$DriftRecoveryTableFilterComposer, + i1.$$DriftRecoveryTableOrderingComposer, + i1.$$DriftRecoveryTableAnnotationComposer, + $$DriftRecoveryTableCreateCompanionBuilder, + $$DriftRecoveryTableUpdateCompanionBuilder, + (i1.DriftRecoveryData, i1.$$DriftRecoveryTableReferences), + i1.DriftRecoveryData, + i0.PrefetchHooks Function({bool store})>; + +class $DriftRecoveryTable extends i2.DriftRecovery + with i0.TableInfo<$DriftRecoveryTable, i1.DriftRecoveryData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftRecoveryTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: false); + static const i0.VerificationMeta _storeMeta = + const i0.VerificationMeta('store'); + @override + late final i0.GeneratedColumn store = i0.GeneratedColumn( + 'store', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES drift_store (name)')); + static const i0.VerificationMeta _creationTimeMeta = + const i0.VerificationMeta('creationTime'); + @override + late final i0.GeneratedColumn creationTime = + i0.GeneratedColumn('creation_time', aliasedName, false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime); + static const i0.VerificationMeta _minZoomMeta = + const i0.VerificationMeta('minZoom'); + @override + late final i0.GeneratedColumn minZoom = i0.GeneratedColumn( + 'min_zoom', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: true); + static const i0.VerificationMeta _maxZoomMeta = + const i0.VerificationMeta('maxZoom'); + @override + late final i0.GeneratedColumn maxZoom = i0.GeneratedColumn( + 'max_zoom', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: true); + static const i0.VerificationMeta _startTileMeta = + const i0.VerificationMeta('startTile'); + @override + late final i0.GeneratedColumn startTile = i0.GeneratedColumn( + 'start_tile', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: true); + static const i0.VerificationMeta _endTileMeta = + const i0.VerificationMeta('endTile'); + @override + late final i0.GeneratedColumn endTile = i0.GeneratedColumn( + 'end_tile', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: true); + @override + List get $columns => + [id, store, creationTime, minZoom, maxZoom, startTile, endTile]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_recovery'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('store')) { + context.handle( + _storeMeta, store.isAcceptableOrUnknown(data['store']!, _storeMeta)); + } else if (isInserting) { + context.missing(_storeMeta); + } + if (data.containsKey('creation_time')) { + context.handle( + _creationTimeMeta, + creationTime.isAcceptableOrUnknown( + data['creation_time']!, _creationTimeMeta)); + } + if (data.containsKey('min_zoom')) { + context.handle(_minZoomMeta, + minZoom.isAcceptableOrUnknown(data['min_zoom']!, _minZoomMeta)); + } else if (isInserting) { + context.missing(_minZoomMeta); + } + if (data.containsKey('max_zoom')) { + context.handle(_maxZoomMeta, + maxZoom.isAcceptableOrUnknown(data['max_zoom']!, _maxZoomMeta)); + } else if (isInserting) { + context.missing(_maxZoomMeta); + } + if (data.containsKey('start_tile')) { + context.handle(_startTileMeta, + startTile.isAcceptableOrUnknown(data['start_tile']!, _startTileMeta)); + } else if (isInserting) { + context.missing(_startTileMeta); + } + if (data.containsKey('end_tile')) { + context.handle(_endTileMeta, + endTile.isAcceptableOrUnknown(data['end_tile']!, _endTileMeta)); + } else if (isInserting) { + context.missing(_endTileMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.DriftRecoveryData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftRecoveryData( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + store: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}store'])!, + creationTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}creation_time'])!, + minZoom: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}min_zoom'])!, + maxZoom: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}max_zoom'])!, + startTile: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}start_tile'])!, + endTile: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}end_tile'])!, + ); + } + + @override + $DriftRecoveryTable createAlias(String alias) { + return $DriftRecoveryTable(attachedDatabase, alias); + } + + @override + bool get isStrict => true; +} + +class DriftRecoveryData extends i0.DataClass + implements i0.Insertable { + /// Unique ID for the recovery session + final int id; + + /// The name of the store being downloaded when the failure occurred + final String store; + + /// The time the recovery session was created + final DateTime creationTime; + + /// Minimum zoom level of the download + final int minZoom; + + /// Maximum zoom level of the download + final int maxZoom; + + /// The tile index the download started from + final int startTile; + + /// The tile index the download ended at + final int endTile; + const DriftRecoveryData( + {required this.id, + required this.store, + required this.creationTime, + required this.minZoom, + required this.maxZoom, + required this.startTile, + required this.endTile}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['store'] = i0.Variable(store); + map['creation_time'] = i0.Variable(creationTime); + map['min_zoom'] = i0.Variable(minZoom); + map['max_zoom'] = i0.Variable(maxZoom); + map['start_tile'] = i0.Variable(startTile); + map['end_tile'] = i0.Variable(endTile); + return map; + } + + i1.DriftRecoveryCompanion toCompanion(bool nullToAbsent) { + return i1.DriftRecoveryCompanion( + id: i0.Value(id), + store: i0.Value(store), + creationTime: i0.Value(creationTime), + minZoom: i0.Value(minZoom), + maxZoom: i0.Value(maxZoom), + startTile: i0.Value(startTile), + endTile: i0.Value(endTile), + ); + } + + factory DriftRecoveryData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftRecoveryData( + id: serializer.fromJson(json['id']), + store: serializer.fromJson(json['store']), + creationTime: serializer.fromJson(json['creationTime']), + minZoom: serializer.fromJson(json['minZoom']), + maxZoom: serializer.fromJson(json['maxZoom']), + startTile: serializer.fromJson(json['startTile']), + endTile: serializer.fromJson(json['endTile']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'store': serializer.toJson(store), + 'creationTime': serializer.toJson(creationTime), + 'minZoom': serializer.toJson(minZoom), + 'maxZoom': serializer.toJson(maxZoom), + 'startTile': serializer.toJson(startTile), + 'endTile': serializer.toJson(endTile), + }; + } + + i1.DriftRecoveryData copyWith( + {int? id, + String? store, + DateTime? creationTime, + int? minZoom, + int? maxZoom, + int? startTile, + int? endTile}) => + i1.DriftRecoveryData( + id: id ?? this.id, + store: store ?? this.store, + creationTime: creationTime ?? this.creationTime, + minZoom: minZoom ?? this.minZoom, + maxZoom: maxZoom ?? this.maxZoom, + startTile: startTile ?? this.startTile, + endTile: endTile ?? this.endTile, + ); + DriftRecoveryData copyWithCompanion(i1.DriftRecoveryCompanion data) { + return DriftRecoveryData( + id: data.id.present ? data.id.value : this.id, + store: data.store.present ? data.store.value : this.store, + creationTime: data.creationTime.present + ? data.creationTime.value + : this.creationTime, + minZoom: data.minZoom.present ? data.minZoom.value : this.minZoom, + maxZoom: data.maxZoom.present ? data.maxZoom.value : this.maxZoom, + startTile: data.startTile.present ? data.startTile.value : this.startTile, + endTile: data.endTile.present ? data.endTile.value : this.endTile, + ); + } + + @override + String toString() { + return (StringBuffer('DriftRecoveryData(') + ..write('id: $id, ') + ..write('store: $store, ') + ..write('creationTime: $creationTime, ') + ..write('minZoom: $minZoom, ') + ..write('maxZoom: $maxZoom, ') + ..write('startTile: $startTile, ') + ..write('endTile: $endTile') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, store, creationTime, minZoom, maxZoom, startTile, endTile); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftRecoveryData && + other.id == this.id && + other.store == this.store && + other.creationTime == this.creationTime && + other.minZoom == this.minZoom && + other.maxZoom == this.maxZoom && + other.startTile == this.startTile && + other.endTile == this.endTile); +} + +class DriftRecoveryCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value store; + final i0.Value creationTime; + final i0.Value minZoom; + final i0.Value maxZoom; + final i0.Value startTile; + final i0.Value endTile; + const DriftRecoveryCompanion({ + this.id = const i0.Value.absent(), + this.store = const i0.Value.absent(), + this.creationTime = const i0.Value.absent(), + this.minZoom = const i0.Value.absent(), + this.maxZoom = const i0.Value.absent(), + this.startTile = const i0.Value.absent(), + this.endTile = const i0.Value.absent(), + }); + DriftRecoveryCompanion.insert({ + this.id = const i0.Value.absent(), + required String store, + this.creationTime = const i0.Value.absent(), + required int minZoom, + required int maxZoom, + required int startTile, + required int endTile, + }) : store = i0.Value(store), + minZoom = i0.Value(minZoom), + maxZoom = i0.Value(maxZoom), + startTile = i0.Value(startTile), + endTile = i0.Value(endTile); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? store, + i0.Expression? creationTime, + i0.Expression? minZoom, + i0.Expression? maxZoom, + i0.Expression? startTile, + i0.Expression? endTile, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (store != null) 'store': store, + if (creationTime != null) 'creation_time': creationTime, + if (minZoom != null) 'min_zoom': minZoom, + if (maxZoom != null) 'max_zoom': maxZoom, + if (startTile != null) 'start_tile': startTile, + if (endTile != null) 'end_tile': endTile, + }); + } + + i1.DriftRecoveryCompanion copyWith( + {i0.Value? id, + i0.Value? store, + i0.Value? creationTime, + i0.Value? minZoom, + i0.Value? maxZoom, + i0.Value? startTile, + i0.Value? endTile}) { + return i1.DriftRecoveryCompanion( + id: id ?? this.id, + store: store ?? this.store, + creationTime: creationTime ?? this.creationTime, + minZoom: minZoom ?? this.minZoom, + maxZoom: maxZoom ?? this.maxZoom, + startTile: startTile ?? this.startTile, + endTile: endTile ?? this.endTile, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (store.present) { + map['store'] = i0.Variable(store.value); + } + if (creationTime.present) { + map['creation_time'] = i0.Variable(creationTime.value); + } + if (minZoom.present) { + map['min_zoom'] = i0.Variable(minZoom.value); + } + if (maxZoom.present) { + map['max_zoom'] = i0.Variable(maxZoom.value); + } + if (startTile.present) { + map['start_tile'] = i0.Variable(startTile.value); + } + if (endTile.present) { + map['end_tile'] = i0.Variable(endTile.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftRecoveryCompanion(') + ..write('id: $id, ') + ..write('store: $store, ') + ..write('creationTime: $creationTime, ') + ..write('minZoom: $minZoom, ') + ..write('maxZoom: $maxZoom, ') + ..write('startTile: $startTile, ') + ..write('endTile: $endTile') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/native/database/models/recovery_region.dart b/lib/src/backend/impls/drift/native/database/models/recovery_region.dart new file mode 100644 index 00000000..dc26e895 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery_region.dart @@ -0,0 +1,58 @@ +import 'package:drift/drift.dart'; + +import 'recovery.dart'; + +/// Drift table storing the serialized region geometry for each recovery session +class DriftRecoveryRegion extends Table { + /// Auto-incremented primary key + late final id = integer().autoIncrement()(); + + /// The recovery session this region belongs to + late final recovery = integer().references(DriftRecovery, #id)(); + + /// For MultiRegion sub-regions, references the parent DriftRecoveryRegion.id + late final parentRegionId = integer().nullable()(); + + /// Integer discriminator identifying the region type (0–4) + late final Column typeId = + integer().check(typeId.isBetweenValues(0, 4))(); + + /// North-west latitude of a rectangular region + late final rectNwLat = real().nullable()(); + + /// North-west longitude of a rectangular region + late final rectNwLng = real().nullable()(); + + /// South-east latitude of a rectangular region + late final rectSeLat = real().nullable()(); + + /// South-east longitude of a rectangular region + late final rectSeLng = real().nullable()(); + + /// Center latitude of a circular region + late final circleCenterLat = real().nullable()(); + + /// Center longitude of a circular region + late final circleCenterLng = real().nullable()(); + + /// Radius in meters of a circular region + late final circleRadius = real().nullable()(); + + /// JSON-encoded list of latitudes for a line region + late final Column lineLats = text().nullable()(); + + /// JSON-encoded list of longitudes for a line region + late final Column lineLngs = text().nullable()(); + + /// Buffer radius in meters for a line region + late final lineRadius = real().nullable()(); + + /// JSON-encoded list of latitudes for a custom polygon region + late final Column customPolygonLats = text().nullable()(); + + /// JSON-encoded list of longitudes for a custom polygon region + late final Column customPolygonLngs = text().nullable()(); + + @override + bool get isStrict => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/recovery_region.drift.dart b/lib/src/backend/impls/drift/native/database/models/recovery_region.drift.dart new file mode 100644 index 00000000..9f15f33f --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery_region.drift.dart @@ -0,0 +1,1333 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery_region.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery_region.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/recovery.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; + +typedef $$DriftRecoveryRegionTableCreateCompanionBuilder + = i1.DriftRecoveryRegionCompanion Function({ + i0.Value id, + required int recovery, + i0.Value parentRegionId, + required int typeId, + i0.Value rectNwLat, + i0.Value rectNwLng, + i0.Value rectSeLat, + i0.Value rectSeLng, + i0.Value circleCenterLat, + i0.Value circleCenterLng, + i0.Value circleRadius, + i0.Value lineLats, + i0.Value lineLngs, + i0.Value lineRadius, + i0.Value customPolygonLats, + i0.Value customPolygonLngs, +}); +typedef $$DriftRecoveryRegionTableUpdateCompanionBuilder + = i1.DriftRecoveryRegionCompanion Function({ + i0.Value id, + i0.Value recovery, + i0.Value parentRegionId, + i0.Value typeId, + i0.Value rectNwLat, + i0.Value rectNwLng, + i0.Value rectSeLat, + i0.Value rectSeLng, + i0.Value circleCenterLat, + i0.Value circleCenterLng, + i0.Value circleRadius, + i0.Value lineLats, + i0.Value lineLngs, + i0.Value lineRadius, + i0.Value customPolygonLats, + i0.Value customPolygonLngs, +}); + +final class $$DriftRecoveryRegionTableReferences extends i0.BaseReferences< + i0.GeneratedDatabase, + i1.$DriftRecoveryRegionTable, + i1.DriftRecoveryRegionData> { + $$DriftRecoveryRegionTableReferences( + super.$_db, super.$_table, super.$_typedResult); + + static i4.$DriftRecoveryTable _recoveryTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('drift_recovery') + .createAlias(i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet( + 'drift_recovery_region') + .recovery, + i5.ReadDatabaseContainer(db) + .resultSet('drift_recovery') + .id)); + + i4.$$DriftRecoveryTableProcessedTableManager get recovery { + final $_column = $_itemColumn('recovery')!; + + final manager = i4 + .$$DriftRecoveryTableTableManager( + $_db, + i5.ReadDatabaseContainer($_db) + .resultSet('drift_recovery')) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_recoveryTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + +class $$DriftRecoveryRegionTableFilterComposer + extends i0.Composer { + $$DriftRecoveryRegionTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get parentRegionId => $composableBuilder( + column: $table.parentRegionId, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get typeId => $composableBuilder( + column: $table.typeId, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get rectNwLat => $composableBuilder( + column: $table.rectNwLat, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get rectNwLng => $composableBuilder( + column: $table.rectNwLng, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get rectSeLat => $composableBuilder( + column: $table.rectSeLat, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get rectSeLng => $composableBuilder( + column: $table.rectSeLng, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get circleCenterLat => $composableBuilder( + column: $table.circleCenterLat, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get circleCenterLng => $composableBuilder( + column: $table.circleCenterLng, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get circleRadius => $composableBuilder( + column: $table.circleRadius, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get lineLats => $composableBuilder( + column: $table.lineLats, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get lineLngs => $composableBuilder( + column: $table.lineLngs, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get lineRadius => $composableBuilder( + column: $table.lineRadius, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get customPolygonLats => $composableBuilder( + column: $table.customPolygonLats, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get customPolygonLngs => $composableBuilder( + column: $table.customPolygonLngs, + builder: (column) => i0.ColumnFilters(column)); + + i4.$$DriftRecoveryTableFilterComposer get recovery { + final i4.$$DriftRecoveryTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recovery, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftRecoveryTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryRegionTableOrderingComposer + extends i0.Composer { + $$DriftRecoveryRegionTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get parentRegionId => $composableBuilder( + column: $table.parentRegionId, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get typeId => $composableBuilder( + column: $table.typeId, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get rectNwLat => $composableBuilder( + column: $table.rectNwLat, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get rectNwLng => $composableBuilder( + column: $table.rectNwLng, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get rectSeLat => $composableBuilder( + column: $table.rectSeLat, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get rectSeLng => $composableBuilder( + column: $table.rectSeLng, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get circleCenterLat => $composableBuilder( + column: $table.circleCenterLat, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get circleCenterLng => $composableBuilder( + column: $table.circleCenterLng, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get circleRadius => $composableBuilder( + column: $table.circleRadius, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get lineLats => $composableBuilder( + column: $table.lineLats, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get lineLngs => $composableBuilder( + column: $table.lineLngs, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get lineRadius => $composableBuilder( + column: $table.lineRadius, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get customPolygonLats => $composableBuilder( + column: $table.customPolygonLats, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get customPolygonLngs => $composableBuilder( + column: $table.customPolygonLngs, + builder: (column) => i0.ColumnOrderings(column)); + + i4.$$DriftRecoveryTableOrderingComposer get recovery { + final i4.$$DriftRecoveryTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recovery, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftRecoveryTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryRegionTableAnnotationComposer + extends i0.Composer { + $$DriftRecoveryRegionTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get parentRegionId => $composableBuilder( + column: $table.parentRegionId, builder: (column) => column); + + i0.GeneratedColumn get typeId => + $composableBuilder(column: $table.typeId, builder: (column) => column); + + i0.GeneratedColumn get rectNwLat => + $composableBuilder(column: $table.rectNwLat, builder: (column) => column); + + i0.GeneratedColumn get rectNwLng => + $composableBuilder(column: $table.rectNwLng, builder: (column) => column); + + i0.GeneratedColumn get rectSeLat => + $composableBuilder(column: $table.rectSeLat, builder: (column) => column); + + i0.GeneratedColumn get rectSeLng => + $composableBuilder(column: $table.rectSeLng, builder: (column) => column); + + i0.GeneratedColumn get circleCenterLat => $composableBuilder( + column: $table.circleCenterLat, builder: (column) => column); + + i0.GeneratedColumn get circleCenterLng => $composableBuilder( + column: $table.circleCenterLng, builder: (column) => column); + + i0.GeneratedColumn get circleRadius => $composableBuilder( + column: $table.circleRadius, builder: (column) => column); + + i0.GeneratedColumn get lineLats => + $composableBuilder(column: $table.lineLats, builder: (column) => column); + + i0.GeneratedColumn get lineLngs => + $composableBuilder(column: $table.lineLngs, builder: (column) => column); + + i0.GeneratedColumn get lineRadius => $composableBuilder( + column: $table.lineRadius, builder: (column) => column); + + i0.GeneratedColumn get customPolygonLats => $composableBuilder( + column: $table.customPolygonLats, builder: (column) => column); + + i0.GeneratedColumn get customPolygonLngs => $composableBuilder( + column: $table.customPolygonLngs, builder: (column) => column); + + i4.$$DriftRecoveryTableAnnotationComposer get recovery { + final i4.$$DriftRecoveryTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recovery, + referencedTable: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i4.$$DriftRecoveryTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer($db) + .resultSet('drift_recovery'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftRecoveryRegionTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftRecoveryRegionTable, + i1.DriftRecoveryRegionData, + i1.$$DriftRecoveryRegionTableFilterComposer, + i1.$$DriftRecoveryRegionTableOrderingComposer, + i1.$$DriftRecoveryRegionTableAnnotationComposer, + $$DriftRecoveryRegionTableCreateCompanionBuilder, + $$DriftRecoveryRegionTableUpdateCompanionBuilder, + (i1.DriftRecoveryRegionData, i1.$$DriftRecoveryRegionTableReferences), + i1.DriftRecoveryRegionData, + i0.PrefetchHooks Function({bool recovery})> { + $$DriftRecoveryRegionTableTableManager( + i0.GeneratedDatabase db, i1.$DriftRecoveryRegionTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => i1 + .$$DriftRecoveryRegionTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftRecoveryRegionTableOrderingComposer( + $db: db, $table: table), + createComputedFieldComposer: () => + i1.$$DriftRecoveryRegionTableAnnotationComposer( + $db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value recovery = const i0.Value.absent(), + i0.Value parentRegionId = const i0.Value.absent(), + i0.Value typeId = const i0.Value.absent(), + i0.Value rectNwLat = const i0.Value.absent(), + i0.Value rectNwLng = const i0.Value.absent(), + i0.Value rectSeLat = const i0.Value.absent(), + i0.Value rectSeLng = const i0.Value.absent(), + i0.Value circleCenterLat = const i0.Value.absent(), + i0.Value circleCenterLng = const i0.Value.absent(), + i0.Value circleRadius = const i0.Value.absent(), + i0.Value lineLats = const i0.Value.absent(), + i0.Value lineLngs = const i0.Value.absent(), + i0.Value lineRadius = const i0.Value.absent(), + i0.Value customPolygonLats = const i0.Value.absent(), + i0.Value customPolygonLngs = const i0.Value.absent(), + }) => + i1.DriftRecoveryRegionCompanion( + id: id, + recovery: recovery, + parentRegionId: parentRegionId, + typeId: typeId, + rectNwLat: rectNwLat, + rectNwLng: rectNwLng, + rectSeLat: rectSeLat, + rectSeLng: rectSeLng, + circleCenterLat: circleCenterLat, + circleCenterLng: circleCenterLng, + circleRadius: circleRadius, + lineLats: lineLats, + lineLngs: lineLngs, + lineRadius: lineRadius, + customPolygonLats: customPolygonLats, + customPolygonLngs: customPolygonLngs, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required int recovery, + i0.Value parentRegionId = const i0.Value.absent(), + required int typeId, + i0.Value rectNwLat = const i0.Value.absent(), + i0.Value rectNwLng = const i0.Value.absent(), + i0.Value rectSeLat = const i0.Value.absent(), + i0.Value rectSeLng = const i0.Value.absent(), + i0.Value circleCenterLat = const i0.Value.absent(), + i0.Value circleCenterLng = const i0.Value.absent(), + i0.Value circleRadius = const i0.Value.absent(), + i0.Value lineLats = const i0.Value.absent(), + i0.Value lineLngs = const i0.Value.absent(), + i0.Value lineRadius = const i0.Value.absent(), + i0.Value customPolygonLats = const i0.Value.absent(), + i0.Value customPolygonLngs = const i0.Value.absent(), + }) => + i1.DriftRecoveryRegionCompanion.insert( + id: id, + recovery: recovery, + parentRegionId: parentRegionId, + typeId: typeId, + rectNwLat: rectNwLat, + rectNwLng: rectNwLng, + rectSeLat: rectSeLat, + rectSeLng: rectSeLng, + circleCenterLat: circleCenterLat, + circleCenterLng: circleCenterLng, + circleRadius: circleRadius, + lineLats: lineLats, + lineLngs: lineLngs, + lineRadius: lineRadius, + customPolygonLats: customPolygonLats, + customPolygonLngs: customPolygonLngs, + ), + withReferenceMapper: (p0) => p0 + .map((e) => ( + e.readTable(table), + i1.$$DriftRecoveryRegionTableReferences(db, table, e) + )) + .toList(), + prefetchHooksCallback: ({recovery = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (recovery) { + state = state.withJoin( + currentTable: table, + currentColumn: table.recovery, + referencedTable: i1.$$DriftRecoveryRegionTableReferences + ._recoveryTable(db), + referencedColumn: i1.$$DriftRecoveryRegionTableReferences + ._recoveryTable(db) + .id, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + )); +} + +typedef $$DriftRecoveryRegionTableProcessedTableManager + = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftRecoveryRegionTable, + i1.DriftRecoveryRegionData, + i1.$$DriftRecoveryRegionTableFilterComposer, + i1.$$DriftRecoveryRegionTableOrderingComposer, + i1.$$DriftRecoveryRegionTableAnnotationComposer, + $$DriftRecoveryRegionTableCreateCompanionBuilder, + $$DriftRecoveryRegionTableUpdateCompanionBuilder, + (i1.DriftRecoveryRegionData, i1.$$DriftRecoveryRegionTableReferences), + i1.DriftRecoveryRegionData, + i0.PrefetchHooks Function({bool recovery})>; + +class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion + with i0.TableInfo<$DriftRecoveryRegionTable, i1.DriftRecoveryRegionData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftRecoveryRegionTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _recoveryMeta = + const i0.VerificationMeta('recovery'); + @override + late final i0.GeneratedColumn recovery = i0.GeneratedColumn( + 'recovery', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES drift_recovery (id)')); + static const i0.VerificationMeta _parentRegionIdMeta = + const i0.VerificationMeta('parentRegionId'); + @override + late final i0.GeneratedColumn parentRegionId = i0.GeneratedColumn( + 'parent_region_id', aliasedName, true, + type: i0.DriftSqlType.int, requiredDuringInsert: false); + static const i0.VerificationMeta _typeIdMeta = + const i0.VerificationMeta('typeId'); + @override + late final i0.GeneratedColumn typeId = i0.GeneratedColumn( + 'type_id', aliasedName, false, + check: () => i3.ComparableExpr(typeId).isBetweenValues(0, 4), + type: i0.DriftSqlType.int, + requiredDuringInsert: true); + static const i0.VerificationMeta _rectNwLatMeta = + const i0.VerificationMeta('rectNwLat'); + @override + late final i0.GeneratedColumn rectNwLat = i0.GeneratedColumn( + 'rect_nw_lat', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _rectNwLngMeta = + const i0.VerificationMeta('rectNwLng'); + @override + late final i0.GeneratedColumn rectNwLng = i0.GeneratedColumn( + 'rect_nw_lng', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _rectSeLatMeta = + const i0.VerificationMeta('rectSeLat'); + @override + late final i0.GeneratedColumn rectSeLat = i0.GeneratedColumn( + 'rect_se_lat', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _rectSeLngMeta = + const i0.VerificationMeta('rectSeLng'); + @override + late final i0.GeneratedColumn rectSeLng = i0.GeneratedColumn( + 'rect_se_lng', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _circleCenterLatMeta = + const i0.VerificationMeta('circleCenterLat'); + @override + late final i0.GeneratedColumn circleCenterLat = + i0.GeneratedColumn('circle_center_lat', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _circleCenterLngMeta = + const i0.VerificationMeta('circleCenterLng'); + @override + late final i0.GeneratedColumn circleCenterLng = + i0.GeneratedColumn('circle_center_lng', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _circleRadiusMeta = + const i0.VerificationMeta('circleRadius'); + @override + late final i0.GeneratedColumn circleRadius = + i0.GeneratedColumn('circle_radius', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _lineLatsMeta = + const i0.VerificationMeta('lineLats'); + @override + late final i0.GeneratedColumn lineLats = i0.GeneratedColumn( + 'line_lats', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + static const i0.VerificationMeta _lineLngsMeta = + const i0.VerificationMeta('lineLngs'); + @override + late final i0.GeneratedColumn lineLngs = i0.GeneratedColumn( + 'line_lngs', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + static const i0.VerificationMeta _lineRadiusMeta = + const i0.VerificationMeta('lineRadius'); + @override + late final i0.GeneratedColumn lineRadius = i0.GeneratedColumn( + 'line_radius', aliasedName, true, + type: i0.DriftSqlType.double, requiredDuringInsert: false); + static const i0.VerificationMeta _customPolygonLatsMeta = + const i0.VerificationMeta('customPolygonLats'); + @override + late final i0.GeneratedColumn customPolygonLats = + i0.GeneratedColumn('custom_polygon_lats', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + static const i0.VerificationMeta _customPolygonLngsMeta = + const i0.VerificationMeta('customPolygonLngs'); + @override + late final i0.GeneratedColumn customPolygonLngs = + i0.GeneratedColumn('custom_polygon_lngs', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => [ + id, + recovery, + parentRegionId, + typeId, + rectNwLat, + rectNwLng, + rectSeLat, + rectSeLng, + circleCenterLat, + circleCenterLng, + circleRadius, + lineLats, + lineLngs, + lineRadius, + customPolygonLats, + customPolygonLngs + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_recovery_region'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('recovery')) { + context.handle(_recoveryMeta, + recovery.isAcceptableOrUnknown(data['recovery']!, _recoveryMeta)); + } else if (isInserting) { + context.missing(_recoveryMeta); + } + if (data.containsKey('parent_region_id')) { + context.handle( + _parentRegionIdMeta, + parentRegionId.isAcceptableOrUnknown( + data['parent_region_id']!, _parentRegionIdMeta)); + } + if (data.containsKey('type_id')) { + context.handle(_typeIdMeta, + typeId.isAcceptableOrUnknown(data['type_id']!, _typeIdMeta)); + } else if (isInserting) { + context.missing(_typeIdMeta); + } + if (data.containsKey('rect_nw_lat')) { + context.handle( + _rectNwLatMeta, + rectNwLat.isAcceptableOrUnknown( + data['rect_nw_lat']!, _rectNwLatMeta)); + } + if (data.containsKey('rect_nw_lng')) { + context.handle( + _rectNwLngMeta, + rectNwLng.isAcceptableOrUnknown( + data['rect_nw_lng']!, _rectNwLngMeta)); + } + if (data.containsKey('rect_se_lat')) { + context.handle( + _rectSeLatMeta, + rectSeLat.isAcceptableOrUnknown( + data['rect_se_lat']!, _rectSeLatMeta)); + } + if (data.containsKey('rect_se_lng')) { + context.handle( + _rectSeLngMeta, + rectSeLng.isAcceptableOrUnknown( + data['rect_se_lng']!, _rectSeLngMeta)); + } + if (data.containsKey('circle_center_lat')) { + context.handle( + _circleCenterLatMeta, + circleCenterLat.isAcceptableOrUnknown( + data['circle_center_lat']!, _circleCenterLatMeta)); + } + if (data.containsKey('circle_center_lng')) { + context.handle( + _circleCenterLngMeta, + circleCenterLng.isAcceptableOrUnknown( + data['circle_center_lng']!, _circleCenterLngMeta)); + } + if (data.containsKey('circle_radius')) { + context.handle( + _circleRadiusMeta, + circleRadius.isAcceptableOrUnknown( + data['circle_radius']!, _circleRadiusMeta)); + } + if (data.containsKey('line_lats')) { + context.handle(_lineLatsMeta, + lineLats.isAcceptableOrUnknown(data['line_lats']!, _lineLatsMeta)); + } + if (data.containsKey('line_lngs')) { + context.handle(_lineLngsMeta, + lineLngs.isAcceptableOrUnknown(data['line_lngs']!, _lineLngsMeta)); + } + if (data.containsKey('line_radius')) { + context.handle( + _lineRadiusMeta, + lineRadius.isAcceptableOrUnknown( + data['line_radius']!, _lineRadiusMeta)); + } + if (data.containsKey('custom_polygon_lats')) { + context.handle( + _customPolygonLatsMeta, + customPolygonLats.isAcceptableOrUnknown( + data['custom_polygon_lats']!, _customPolygonLatsMeta)); + } + if (data.containsKey('custom_polygon_lngs')) { + context.handle( + _customPolygonLngsMeta, + customPolygonLngs.isAcceptableOrUnknown( + data['custom_polygon_lngs']!, _customPolygonLngsMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.DriftRecoveryRegionData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftRecoveryRegionData( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + recovery: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}recovery'])!, + parentRegionId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, data['${effectivePrefix}parent_region_id']), + typeId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}type_id'])!, + rectNwLat: attachedDatabase.typeMapping + .read(i0.DriftSqlType.double, data['${effectivePrefix}rect_nw_lat']), + rectNwLng: attachedDatabase.typeMapping + .read(i0.DriftSqlType.double, data['${effectivePrefix}rect_nw_lng']), + rectSeLat: attachedDatabase.typeMapping + .read(i0.DriftSqlType.double, data['${effectivePrefix}rect_se_lat']), + rectSeLng: attachedDatabase.typeMapping + .read(i0.DriftSqlType.double, data['${effectivePrefix}rect_se_lng']), + circleCenterLat: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, data['${effectivePrefix}circle_center_lat']), + circleCenterLng: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, data['${effectivePrefix}circle_center_lng']), + circleRadius: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, data['${effectivePrefix}circle_radius']), + lineLats: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}line_lats']), + lineLngs: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}line_lngs']), + lineRadius: attachedDatabase.typeMapping + .read(i0.DriftSqlType.double, data['${effectivePrefix}line_radius']), + customPolygonLats: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}custom_polygon_lats']), + customPolygonLngs: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}custom_polygon_lngs']), + ); + } + + @override + $DriftRecoveryRegionTable createAlias(String alias) { + return $DriftRecoveryRegionTable(attachedDatabase, alias); + } + + @override + bool get isStrict => true; +} + +class DriftRecoveryRegionData extends i0.DataClass + implements i0.Insertable { + /// Auto-incremented primary key + final int id; + + /// The recovery session this region belongs to + final int recovery; + + /// For MultiRegion sub-regions, references the parent DriftRecoveryRegion.id + final int? parentRegionId; + + /// Integer discriminator identifying the region type (0–4) + final int typeId; + + /// North-west latitude of a rectangular region + final double? rectNwLat; + + /// North-west longitude of a rectangular region + final double? rectNwLng; + + /// South-east latitude of a rectangular region + final double? rectSeLat; + + /// South-east longitude of a rectangular region + final double? rectSeLng; + + /// Center latitude of a circular region + final double? circleCenterLat; + + /// Center longitude of a circular region + final double? circleCenterLng; + + /// Radius in meters of a circular region + final double? circleRadius; + + /// JSON-encoded list of latitudes for a line region + final String? lineLats; + + /// JSON-encoded list of longitudes for a line region + final String? lineLngs; + + /// Buffer radius in meters for a line region + final double? lineRadius; + + /// JSON-encoded list of latitudes for a custom polygon region + final String? customPolygonLats; + + /// JSON-encoded list of longitudes for a custom polygon region + final String? customPolygonLngs; + const DriftRecoveryRegionData( + {required this.id, + required this.recovery, + this.parentRegionId, + required this.typeId, + this.rectNwLat, + this.rectNwLng, + this.rectSeLat, + this.rectSeLng, + this.circleCenterLat, + this.circleCenterLng, + this.circleRadius, + this.lineLats, + this.lineLngs, + this.lineRadius, + this.customPolygonLats, + this.customPolygonLngs}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['recovery'] = i0.Variable(recovery); + if (!nullToAbsent || parentRegionId != null) { + map['parent_region_id'] = i0.Variable(parentRegionId); + } + map['type_id'] = i0.Variable(typeId); + if (!nullToAbsent || rectNwLat != null) { + map['rect_nw_lat'] = i0.Variable(rectNwLat); + } + if (!nullToAbsent || rectNwLng != null) { + map['rect_nw_lng'] = i0.Variable(rectNwLng); + } + if (!nullToAbsent || rectSeLat != null) { + map['rect_se_lat'] = i0.Variable(rectSeLat); + } + if (!nullToAbsent || rectSeLng != null) { + map['rect_se_lng'] = i0.Variable(rectSeLng); + } + if (!nullToAbsent || circleCenterLat != null) { + map['circle_center_lat'] = i0.Variable(circleCenterLat); + } + if (!nullToAbsent || circleCenterLng != null) { + map['circle_center_lng'] = i0.Variable(circleCenterLng); + } + if (!nullToAbsent || circleRadius != null) { + map['circle_radius'] = i0.Variable(circleRadius); + } + if (!nullToAbsent || lineLats != null) { + map['line_lats'] = i0.Variable(lineLats); + } + if (!nullToAbsent || lineLngs != null) { + map['line_lngs'] = i0.Variable(lineLngs); + } + if (!nullToAbsent || lineRadius != null) { + map['line_radius'] = i0.Variable(lineRadius); + } + if (!nullToAbsent || customPolygonLats != null) { + map['custom_polygon_lats'] = i0.Variable(customPolygonLats); + } + if (!nullToAbsent || customPolygonLngs != null) { + map['custom_polygon_lngs'] = i0.Variable(customPolygonLngs); + } + return map; + } + + i1.DriftRecoveryRegionCompanion toCompanion(bool nullToAbsent) { + return i1.DriftRecoveryRegionCompanion( + id: i0.Value(id), + recovery: i0.Value(recovery), + parentRegionId: parentRegionId == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(parentRegionId), + typeId: i0.Value(typeId), + rectNwLat: rectNwLat == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(rectNwLat), + rectNwLng: rectNwLng == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(rectNwLng), + rectSeLat: rectSeLat == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(rectSeLat), + rectSeLng: rectSeLng == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(rectSeLng), + circleCenterLat: circleCenterLat == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(circleCenterLat), + circleCenterLng: circleCenterLng == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(circleCenterLng), + circleRadius: circleRadius == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(circleRadius), + lineLats: lineLats == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(lineLats), + lineLngs: lineLngs == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(lineLngs), + lineRadius: lineRadius == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(lineRadius), + customPolygonLats: customPolygonLats == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(customPolygonLats), + customPolygonLngs: customPolygonLngs == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(customPolygonLngs), + ); + } + + factory DriftRecoveryRegionData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftRecoveryRegionData( + id: serializer.fromJson(json['id']), + recovery: serializer.fromJson(json['recovery']), + parentRegionId: serializer.fromJson(json['parentRegionId']), + typeId: serializer.fromJson(json['typeId']), + rectNwLat: serializer.fromJson(json['rectNwLat']), + rectNwLng: serializer.fromJson(json['rectNwLng']), + rectSeLat: serializer.fromJson(json['rectSeLat']), + rectSeLng: serializer.fromJson(json['rectSeLng']), + circleCenterLat: serializer.fromJson(json['circleCenterLat']), + circleCenterLng: serializer.fromJson(json['circleCenterLng']), + circleRadius: serializer.fromJson(json['circleRadius']), + lineLats: serializer.fromJson(json['lineLats']), + lineLngs: serializer.fromJson(json['lineLngs']), + lineRadius: serializer.fromJson(json['lineRadius']), + customPolygonLats: + serializer.fromJson(json['customPolygonLats']), + customPolygonLngs: + serializer.fromJson(json['customPolygonLngs']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'recovery': serializer.toJson(recovery), + 'parentRegionId': serializer.toJson(parentRegionId), + 'typeId': serializer.toJson(typeId), + 'rectNwLat': serializer.toJson(rectNwLat), + 'rectNwLng': serializer.toJson(rectNwLng), + 'rectSeLat': serializer.toJson(rectSeLat), + 'rectSeLng': serializer.toJson(rectSeLng), + 'circleCenterLat': serializer.toJson(circleCenterLat), + 'circleCenterLng': serializer.toJson(circleCenterLng), + 'circleRadius': serializer.toJson(circleRadius), + 'lineLats': serializer.toJson(lineLats), + 'lineLngs': serializer.toJson(lineLngs), + 'lineRadius': serializer.toJson(lineRadius), + 'customPolygonLats': serializer.toJson(customPolygonLats), + 'customPolygonLngs': serializer.toJson(customPolygonLngs), + }; + } + + i1.DriftRecoveryRegionData copyWith( + {int? id, + int? recovery, + i0.Value parentRegionId = const i0.Value.absent(), + int? typeId, + i0.Value rectNwLat = const i0.Value.absent(), + i0.Value rectNwLng = const i0.Value.absent(), + i0.Value rectSeLat = const i0.Value.absent(), + i0.Value rectSeLng = const i0.Value.absent(), + i0.Value circleCenterLat = const i0.Value.absent(), + i0.Value circleCenterLng = const i0.Value.absent(), + i0.Value circleRadius = const i0.Value.absent(), + i0.Value lineLats = const i0.Value.absent(), + i0.Value lineLngs = const i0.Value.absent(), + i0.Value lineRadius = const i0.Value.absent(), + i0.Value customPolygonLats = const i0.Value.absent(), + i0.Value customPolygonLngs = const i0.Value.absent()}) => + i1.DriftRecoveryRegionData( + id: id ?? this.id, + recovery: recovery ?? this.recovery, + parentRegionId: + parentRegionId.present ? parentRegionId.value : this.parentRegionId, + typeId: typeId ?? this.typeId, + rectNwLat: rectNwLat.present ? rectNwLat.value : this.rectNwLat, + rectNwLng: rectNwLng.present ? rectNwLng.value : this.rectNwLng, + rectSeLat: rectSeLat.present ? rectSeLat.value : this.rectSeLat, + rectSeLng: rectSeLng.present ? rectSeLng.value : this.rectSeLng, + circleCenterLat: circleCenterLat.present + ? circleCenterLat.value + : this.circleCenterLat, + circleCenterLng: circleCenterLng.present + ? circleCenterLng.value + : this.circleCenterLng, + circleRadius: + circleRadius.present ? circleRadius.value : this.circleRadius, + lineLats: lineLats.present ? lineLats.value : this.lineLats, + lineLngs: lineLngs.present ? lineLngs.value : this.lineLngs, + lineRadius: lineRadius.present ? lineRadius.value : this.lineRadius, + customPolygonLats: customPolygonLats.present + ? customPolygonLats.value + : this.customPolygonLats, + customPolygonLngs: customPolygonLngs.present + ? customPolygonLngs.value + : this.customPolygonLngs, + ); + DriftRecoveryRegionData copyWithCompanion( + i1.DriftRecoveryRegionCompanion data) { + return DriftRecoveryRegionData( + id: data.id.present ? data.id.value : this.id, + recovery: data.recovery.present ? data.recovery.value : this.recovery, + parentRegionId: data.parentRegionId.present + ? data.parentRegionId.value + : this.parentRegionId, + typeId: data.typeId.present ? data.typeId.value : this.typeId, + rectNwLat: data.rectNwLat.present ? data.rectNwLat.value : this.rectNwLat, + rectNwLng: data.rectNwLng.present ? data.rectNwLng.value : this.rectNwLng, + rectSeLat: data.rectSeLat.present ? data.rectSeLat.value : this.rectSeLat, + rectSeLng: data.rectSeLng.present ? data.rectSeLng.value : this.rectSeLng, + circleCenterLat: data.circleCenterLat.present + ? data.circleCenterLat.value + : this.circleCenterLat, + circleCenterLng: data.circleCenterLng.present + ? data.circleCenterLng.value + : this.circleCenterLng, + circleRadius: data.circleRadius.present + ? data.circleRadius.value + : this.circleRadius, + lineLats: data.lineLats.present ? data.lineLats.value : this.lineLats, + lineLngs: data.lineLngs.present ? data.lineLngs.value : this.lineLngs, + lineRadius: + data.lineRadius.present ? data.lineRadius.value : this.lineRadius, + customPolygonLats: data.customPolygonLats.present + ? data.customPolygonLats.value + : this.customPolygonLats, + customPolygonLngs: data.customPolygonLngs.present + ? data.customPolygonLngs.value + : this.customPolygonLngs, + ); + } + + @override + String toString() { + return (StringBuffer('DriftRecoveryRegionData(') + ..write('id: $id, ') + ..write('recovery: $recovery, ') + ..write('parentRegionId: $parentRegionId, ') + ..write('typeId: $typeId, ') + ..write('rectNwLat: $rectNwLat, ') + ..write('rectNwLng: $rectNwLng, ') + ..write('rectSeLat: $rectSeLat, ') + ..write('rectSeLng: $rectSeLng, ') + ..write('circleCenterLat: $circleCenterLat, ') + ..write('circleCenterLng: $circleCenterLng, ') + ..write('circleRadius: $circleRadius, ') + ..write('lineLats: $lineLats, ') + ..write('lineLngs: $lineLngs, ') + ..write('lineRadius: $lineRadius, ') + ..write('customPolygonLats: $customPolygonLats, ') + ..write('customPolygonLngs: $customPolygonLngs') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + recovery, + parentRegionId, + typeId, + rectNwLat, + rectNwLng, + rectSeLat, + rectSeLng, + circleCenterLat, + circleCenterLng, + circleRadius, + lineLats, + lineLngs, + lineRadius, + customPolygonLats, + customPolygonLngs); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftRecoveryRegionData && + other.id == this.id && + other.recovery == this.recovery && + other.parentRegionId == this.parentRegionId && + other.typeId == this.typeId && + other.rectNwLat == this.rectNwLat && + other.rectNwLng == this.rectNwLng && + other.rectSeLat == this.rectSeLat && + other.rectSeLng == this.rectSeLng && + other.circleCenterLat == this.circleCenterLat && + other.circleCenterLng == this.circleCenterLng && + other.circleRadius == this.circleRadius && + other.lineLats == this.lineLats && + other.lineLngs == this.lineLngs && + other.lineRadius == this.lineRadius && + other.customPolygonLats == this.customPolygonLats && + other.customPolygonLngs == this.customPolygonLngs); +} + +class DriftRecoveryRegionCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value recovery; + final i0.Value parentRegionId; + final i0.Value typeId; + final i0.Value rectNwLat; + final i0.Value rectNwLng; + final i0.Value rectSeLat; + final i0.Value rectSeLng; + final i0.Value circleCenterLat; + final i0.Value circleCenterLng; + final i0.Value circleRadius; + final i0.Value lineLats; + final i0.Value lineLngs; + final i0.Value lineRadius; + final i0.Value customPolygonLats; + final i0.Value customPolygonLngs; + const DriftRecoveryRegionCompanion({ + this.id = const i0.Value.absent(), + this.recovery = const i0.Value.absent(), + this.parentRegionId = const i0.Value.absent(), + this.typeId = const i0.Value.absent(), + this.rectNwLat = const i0.Value.absent(), + this.rectNwLng = const i0.Value.absent(), + this.rectSeLat = const i0.Value.absent(), + this.rectSeLng = const i0.Value.absent(), + this.circleCenterLat = const i0.Value.absent(), + this.circleCenterLng = const i0.Value.absent(), + this.circleRadius = const i0.Value.absent(), + this.lineLats = const i0.Value.absent(), + this.lineLngs = const i0.Value.absent(), + this.lineRadius = const i0.Value.absent(), + this.customPolygonLats = const i0.Value.absent(), + this.customPolygonLngs = const i0.Value.absent(), + }); + DriftRecoveryRegionCompanion.insert({ + this.id = const i0.Value.absent(), + required int recovery, + this.parentRegionId = const i0.Value.absent(), + required int typeId, + this.rectNwLat = const i0.Value.absent(), + this.rectNwLng = const i0.Value.absent(), + this.rectSeLat = const i0.Value.absent(), + this.rectSeLng = const i0.Value.absent(), + this.circleCenterLat = const i0.Value.absent(), + this.circleCenterLng = const i0.Value.absent(), + this.circleRadius = const i0.Value.absent(), + this.lineLats = const i0.Value.absent(), + this.lineLngs = const i0.Value.absent(), + this.lineRadius = const i0.Value.absent(), + this.customPolygonLats = const i0.Value.absent(), + this.customPolygonLngs = const i0.Value.absent(), + }) : recovery = i0.Value(recovery), + typeId = i0.Value(typeId); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? recovery, + i0.Expression? parentRegionId, + i0.Expression? typeId, + i0.Expression? rectNwLat, + i0.Expression? rectNwLng, + i0.Expression? rectSeLat, + i0.Expression? rectSeLng, + i0.Expression? circleCenterLat, + i0.Expression? circleCenterLng, + i0.Expression? circleRadius, + i0.Expression? lineLats, + i0.Expression? lineLngs, + i0.Expression? lineRadius, + i0.Expression? customPolygonLats, + i0.Expression? customPolygonLngs, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (recovery != null) 'recovery': recovery, + if (parentRegionId != null) 'parent_region_id': parentRegionId, + if (typeId != null) 'type_id': typeId, + if (rectNwLat != null) 'rect_nw_lat': rectNwLat, + if (rectNwLng != null) 'rect_nw_lng': rectNwLng, + if (rectSeLat != null) 'rect_se_lat': rectSeLat, + if (rectSeLng != null) 'rect_se_lng': rectSeLng, + if (circleCenterLat != null) 'circle_center_lat': circleCenterLat, + if (circleCenterLng != null) 'circle_center_lng': circleCenterLng, + if (circleRadius != null) 'circle_radius': circleRadius, + if (lineLats != null) 'line_lats': lineLats, + if (lineLngs != null) 'line_lngs': lineLngs, + if (lineRadius != null) 'line_radius': lineRadius, + if (customPolygonLats != null) 'custom_polygon_lats': customPolygonLats, + if (customPolygonLngs != null) 'custom_polygon_lngs': customPolygonLngs, + }); + } + + i1.DriftRecoveryRegionCompanion copyWith( + {i0.Value? id, + i0.Value? recovery, + i0.Value? parentRegionId, + i0.Value? typeId, + i0.Value? rectNwLat, + i0.Value? rectNwLng, + i0.Value? rectSeLat, + i0.Value? rectSeLng, + i0.Value? circleCenterLat, + i0.Value? circleCenterLng, + i0.Value? circleRadius, + i0.Value? lineLats, + i0.Value? lineLngs, + i0.Value? lineRadius, + i0.Value? customPolygonLats, + i0.Value? customPolygonLngs}) { + return i1.DriftRecoveryRegionCompanion( + id: id ?? this.id, + recovery: recovery ?? this.recovery, + parentRegionId: parentRegionId ?? this.parentRegionId, + typeId: typeId ?? this.typeId, + rectNwLat: rectNwLat ?? this.rectNwLat, + rectNwLng: rectNwLng ?? this.rectNwLng, + rectSeLat: rectSeLat ?? this.rectSeLat, + rectSeLng: rectSeLng ?? this.rectSeLng, + circleCenterLat: circleCenterLat ?? this.circleCenterLat, + circleCenterLng: circleCenterLng ?? this.circleCenterLng, + circleRadius: circleRadius ?? this.circleRadius, + lineLats: lineLats ?? this.lineLats, + lineLngs: lineLngs ?? this.lineLngs, + lineRadius: lineRadius ?? this.lineRadius, + customPolygonLats: customPolygonLats ?? this.customPolygonLats, + customPolygonLngs: customPolygonLngs ?? this.customPolygonLngs, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (recovery.present) { + map['recovery'] = i0.Variable(recovery.value); + } + if (parentRegionId.present) { + map['parent_region_id'] = i0.Variable(parentRegionId.value); + } + if (typeId.present) { + map['type_id'] = i0.Variable(typeId.value); + } + if (rectNwLat.present) { + map['rect_nw_lat'] = i0.Variable(rectNwLat.value); + } + if (rectNwLng.present) { + map['rect_nw_lng'] = i0.Variable(rectNwLng.value); + } + if (rectSeLat.present) { + map['rect_se_lat'] = i0.Variable(rectSeLat.value); + } + if (rectSeLng.present) { + map['rect_se_lng'] = i0.Variable(rectSeLng.value); + } + if (circleCenterLat.present) { + map['circle_center_lat'] = i0.Variable(circleCenterLat.value); + } + if (circleCenterLng.present) { + map['circle_center_lng'] = i0.Variable(circleCenterLng.value); + } + if (circleRadius.present) { + map['circle_radius'] = i0.Variable(circleRadius.value); + } + if (lineLats.present) { + map['line_lats'] = i0.Variable(lineLats.value); + } + if (lineLngs.present) { + map['line_lngs'] = i0.Variable(lineLngs.value); + } + if (lineRadius.present) { + map['line_radius'] = i0.Variable(lineRadius.value); + } + if (customPolygonLats.present) { + map['custom_polygon_lats'] = i0.Variable(customPolygonLats.value); + } + if (customPolygonLngs.present) { + map['custom_polygon_lngs'] = i0.Variable(customPolygonLngs.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftRecoveryRegionCompanion(') + ..write('id: $id, ') + ..write('recovery: $recovery, ') + ..write('parentRegionId: $parentRegionId, ') + ..write('typeId: $typeId, ') + ..write('rectNwLat: $rectNwLat, ') + ..write('rectNwLng: $rectNwLng, ') + ..write('rectSeLat: $rectSeLat, ') + ..write('rectSeLng: $rectSeLng, ') + ..write('circleCenterLat: $circleCenterLat, ') + ..write('circleCenterLng: $circleCenterLng, ') + ..write('circleRadius: $circleRadius, ') + ..write('lineLats: $lineLats, ') + ..write('lineLngs: $lineLngs, ') + ..write('lineRadius: $lineRadius, ') + ..write('customPolygonLats: $customPolygonLats, ') + ..write('customPolygonLngs: $customPolygonLngs') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/native/database/models/root.dart b/lib/src/backend/impls/drift/native/database/models/root.dart new file mode 100644 index 00000000..afee8d8f --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/root.dart @@ -0,0 +1,20 @@ +import 'package:drift/drift.dart'; + +/// Drift table for the FMTC root statistics singleton row +class DriftRoot extends Table { + /// Singleton row ID (always 0) + late final Column id = + integer().check(id.equals(0)).withDefault(const Constant(0))(); + + /// Total number of tiles across all stores + late final length = integer().withDefault(const Constant(0))(); + + /// Total size in bytes across all stores + late final size = integer().withDefault(const Constant(0))(); + + @override + Set> get primaryKey => {id}; + + @override + bool get isStrict => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/root.drift.dart b/lib/src/backend/impls/drift/native/database/models/root.drift.dart new file mode 100644 index 00000000..8e867c08 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/root.drift.dart @@ -0,0 +1,374 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/root.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/root.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; + +typedef $$DriftRootTableCreateCompanionBuilder = i1.DriftRootCompanion + Function({ + i0.Value id, + i0.Value length, + i0.Value size, +}); +typedef $$DriftRootTableUpdateCompanionBuilder = i1.DriftRootCompanion + Function({ + i0.Value id, + i0.Value length, + i0.Value size, +}); + +class $$DriftRootTableFilterComposer + extends i0.Composer { + $$DriftRootTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get length => $composableBuilder( + column: $table.length, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get size => $composableBuilder( + column: $table.size, builder: (column) => i0.ColumnFilters(column)); +} + +class $$DriftRootTableOrderingComposer + extends i0.Composer { + $$DriftRootTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get length => $composableBuilder( + column: $table.length, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get size => $composableBuilder( + column: $table.size, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$DriftRootTableAnnotationComposer + extends i0.Composer { + $$DriftRootTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get length => + $composableBuilder(column: $table.length, builder: (column) => column); + + i0.GeneratedColumn get size => + $composableBuilder(column: $table.size, builder: (column) => column); +} + +class $$DriftRootTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftRootTable, + i1.DriftRootData, + i1.$$DriftRootTableFilterComposer, + i1.$$DriftRootTableOrderingComposer, + i1.$$DriftRootTableAnnotationComposer, + $$DriftRootTableCreateCompanionBuilder, + $$DriftRootTableUpdateCompanionBuilder, + ( + i1.DriftRootData, + i0.BaseReferences + ), + i1.DriftRootData, + i0.PrefetchHooks Function()> { + $$DriftRootTableTableManager( + i0.GeneratedDatabase db, i1.$DriftRootTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$DriftRootTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftRootTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$DriftRootTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value length = const i0.Value.absent(), + i0.Value size = const i0.Value.absent(), + }) => + i1.DriftRootCompanion( + id: id, + length: length, + size: size, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value length = const i0.Value.absent(), + i0.Value size = const i0.Value.absent(), + }) => + i1.DriftRootCompanion.insert( + id: id, + length: length, + size: size, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$DriftRootTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftRootTable, + i1.DriftRootData, + i1.$$DriftRootTableFilterComposer, + i1.$$DriftRootTableOrderingComposer, + i1.$$DriftRootTableAnnotationComposer, + $$DriftRootTableCreateCompanionBuilder, + $$DriftRootTableUpdateCompanionBuilder, + ( + i1.DriftRootData, + i0.BaseReferences + ), + i1.DriftRootData, + i0.PrefetchHooks Function()>; + +class $DriftRootTable extends i2.DriftRoot + with i0.TableInfo<$DriftRootTable, i1.DriftRootData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftRootTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + check: () => id.equals(0), + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _lengthMeta = + const i0.VerificationMeta('length'); + @override + late final i0.GeneratedColumn length = i0.GeneratedColumn( + 'length', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _sizeMeta = + const i0.VerificationMeta('size'); + @override + late final i0.GeneratedColumn size = i0.GeneratedColumn( + 'size', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + @override + List get $columns => [id, length, size]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_root'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('length')) { + context.handle(_lengthMeta, + length.isAcceptableOrUnknown(data['length']!, _lengthMeta)); + } + if (data.containsKey('size')) { + context.handle( + _sizeMeta, size.isAcceptableOrUnknown(data['size']!, _sizeMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.DriftRootData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftRootData( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + length: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}length'])!, + size: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}size'])!, + ); + } + + @override + $DriftRootTable createAlias(String alias) { + return $DriftRootTable(attachedDatabase, alias); + } + + @override + bool get isStrict => true; +} + +class DriftRootData extends i0.DataClass + implements i0.Insertable { + /// Singleton row ID (always 0) + final int id; + + /// Total number of tiles across all stores + final int length; + + /// Total size in bytes across all stores + final int size; + const DriftRootData( + {required this.id, required this.length, required this.size}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['length'] = i0.Variable(length); + map['size'] = i0.Variable(size); + return map; + } + + i1.DriftRootCompanion toCompanion(bool nullToAbsent) { + return i1.DriftRootCompanion( + id: i0.Value(id), + length: i0.Value(length), + size: i0.Value(size), + ); + } + + factory DriftRootData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftRootData( + id: serializer.fromJson(json['id']), + length: serializer.fromJson(json['length']), + size: serializer.fromJson(json['size']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'length': serializer.toJson(length), + 'size': serializer.toJson(size), + }; + } + + i1.DriftRootData copyWith({int? id, int? length, int? size}) => + i1.DriftRootData( + id: id ?? this.id, + length: length ?? this.length, + size: size ?? this.size, + ); + DriftRootData copyWithCompanion(i1.DriftRootCompanion data) { + return DriftRootData( + id: data.id.present ? data.id.value : this.id, + length: data.length.present ? data.length.value : this.length, + size: data.size.present ? data.size.value : this.size, + ); + } + + @override + String toString() { + return (StringBuffer('DriftRootData(') + ..write('id: $id, ') + ..write('length: $length, ') + ..write('size: $size') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, length, size); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftRootData && + other.id == this.id && + other.length == this.length && + other.size == this.size); +} + +class DriftRootCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value length; + final i0.Value size; + const DriftRootCompanion({ + this.id = const i0.Value.absent(), + this.length = const i0.Value.absent(), + this.size = const i0.Value.absent(), + }); + DriftRootCompanion.insert({ + this.id = const i0.Value.absent(), + this.length = const i0.Value.absent(), + this.size = const i0.Value.absent(), + }); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? length, + i0.Expression? size, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (length != null) 'length': length, + if (size != null) 'size': size, + }); + } + + i1.DriftRootCompanion copyWith( + {i0.Value? id, i0.Value? length, i0.Value? size}) { + return i1.DriftRootCompanion( + id: id ?? this.id, + length: length ?? this.length, + size: size ?? this.size, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (length.present) { + map['length'] = i0.Variable(length.value); + } + if (size.present) { + map['size'] = i0.Variable(size.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftRootCompanion(') + ..write('id: $id, ') + ..write('length: $length, ') + ..write('size: $size') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/native/database/models/store.dart b/lib/src/backend/impls/drift/native/database/models/store.dart new file mode 100644 index 00000000..4f39abe1 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store.dart @@ -0,0 +1,34 @@ +import 'package:drift/drift.dart'; + +/// Drift table for FMTC tile stores +class DriftStore extends Table { + /// The unique name of the store + late final name = text()(); + + /// Maximum number of tiles allowed in the store (null = unlimited) + late final maxLength = integer().nullable()(); + + /// Current number of tiles in the store + late final length = integer().withDefault(const Constant(0))(); + + /// Total size of all tiles in bytes + late final size = integer().withDefault(const Constant(0))(); + + /// Number of cache hits recorded for this store + late final hits = integer().withDefault(const Constant(0))(); + + /// Number of cache misses recorded for this store + late final misses = integer().withDefault(const Constant(0))(); + + /// JSON-encoded key-value metadata for this store + late final metadataJson = text().withDefault(const Constant('{}'))(); + + @override + Set> get primaryKey => {name}; + + @override + bool get isStrict => true; + + @override + bool get withoutRowId => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/store.drift.dart b/lib/src/backend/impls/drift/native/database/models/store.drift.dart new file mode 100644 index 00000000..d010599e --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store.drift.dart @@ -0,0 +1,607 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; + +typedef $$DriftStoreTableCreateCompanionBuilder = i1.DriftStoreCompanion + Function({ + required String name, + i0.Value maxLength, + i0.Value length, + i0.Value size, + i0.Value hits, + i0.Value misses, + i0.Value metadataJson, +}); +typedef $$DriftStoreTableUpdateCompanionBuilder = i1.DriftStoreCompanion + Function({ + i0.Value name, + i0.Value maxLength, + i0.Value length, + i0.Value size, + i0.Value hits, + i0.Value misses, + i0.Value metadataJson, +}); + +class $$DriftStoreTableFilterComposer + extends i0.Composer { + $$DriftStoreTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get maxLength => $composableBuilder( + column: $table.maxLength, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get length => $composableBuilder( + column: $table.length, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get size => $composableBuilder( + column: $table.size, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get hits => $composableBuilder( + column: $table.hits, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get misses => $composableBuilder( + column: $table.misses, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get metadataJson => $composableBuilder( + column: $table.metadataJson, + builder: (column) => i0.ColumnFilters(column)); +} + +class $$DriftStoreTableOrderingComposer + extends i0.Composer { + $$DriftStoreTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get maxLength => $composableBuilder( + column: $table.maxLength, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get length => $composableBuilder( + column: $table.length, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get size => $composableBuilder( + column: $table.size, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get hits => $composableBuilder( + column: $table.hits, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get misses => $composableBuilder( + column: $table.misses, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get metadataJson => $composableBuilder( + column: $table.metadataJson, + builder: (column) => i0.ColumnOrderings(column)); +} + +class $$DriftStoreTableAnnotationComposer + extends i0.Composer { + $$DriftStoreTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get maxLength => + $composableBuilder(column: $table.maxLength, builder: (column) => column); + + i0.GeneratedColumn get length => + $composableBuilder(column: $table.length, builder: (column) => column); + + i0.GeneratedColumn get size => + $composableBuilder(column: $table.size, builder: (column) => column); + + i0.GeneratedColumn get hits => + $composableBuilder(column: $table.hits, builder: (column) => column); + + i0.GeneratedColumn get misses => + $composableBuilder(column: $table.misses, builder: (column) => column); + + i0.GeneratedColumn get metadataJson => $composableBuilder( + column: $table.metadataJson, builder: (column) => column); +} + +class $$DriftStoreTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftStoreTable, + i1.DriftStoreData, + i1.$$DriftStoreTableFilterComposer, + i1.$$DriftStoreTableOrderingComposer, + i1.$$DriftStoreTableAnnotationComposer, + $$DriftStoreTableCreateCompanionBuilder, + $$DriftStoreTableUpdateCompanionBuilder, + ( + i1.DriftStoreData, + i0.BaseReferences + ), + i1.DriftStoreData, + i0.PrefetchHooks Function()> { + $$DriftStoreTableTableManager( + i0.GeneratedDatabase db, i1.$DriftStoreTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$DriftStoreTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftStoreTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$DriftStoreTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value name = const i0.Value.absent(), + i0.Value maxLength = const i0.Value.absent(), + i0.Value length = const i0.Value.absent(), + i0.Value size = const i0.Value.absent(), + i0.Value hits = const i0.Value.absent(), + i0.Value misses = const i0.Value.absent(), + i0.Value metadataJson = const i0.Value.absent(), + }) => + i1.DriftStoreCompanion( + name: name, + maxLength: maxLength, + length: length, + size: size, + hits: hits, + misses: misses, + metadataJson: metadataJson, + ), + createCompanionCallback: ({ + required String name, + i0.Value maxLength = const i0.Value.absent(), + i0.Value length = const i0.Value.absent(), + i0.Value size = const i0.Value.absent(), + i0.Value hits = const i0.Value.absent(), + i0.Value misses = const i0.Value.absent(), + i0.Value metadataJson = const i0.Value.absent(), + }) => + i1.DriftStoreCompanion.insert( + name: name, + maxLength: maxLength, + length: length, + size: size, + hits: hits, + misses: misses, + metadataJson: metadataJson, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$DriftStoreTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftStoreTable, + i1.DriftStoreData, + i1.$$DriftStoreTableFilterComposer, + i1.$$DriftStoreTableOrderingComposer, + i1.$$DriftStoreTableAnnotationComposer, + $$DriftStoreTableCreateCompanionBuilder, + $$DriftStoreTableUpdateCompanionBuilder, + ( + i1.DriftStoreData, + i0.BaseReferences + ), + i1.DriftStoreData, + i0.PrefetchHooks Function()>; + +class $DriftStoreTable extends i2.DriftStore + with i0.TableInfo<$DriftStoreTable, i1.DriftStoreData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftStoreTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _nameMeta = + const i0.VerificationMeta('name'); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _maxLengthMeta = + const i0.VerificationMeta('maxLength'); + @override + late final i0.GeneratedColumn maxLength = i0.GeneratedColumn( + 'max_length', aliasedName, true, + type: i0.DriftSqlType.int, requiredDuringInsert: false); + static const i0.VerificationMeta _lengthMeta = + const i0.VerificationMeta('length'); + @override + late final i0.GeneratedColumn length = i0.GeneratedColumn( + 'length', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _sizeMeta = + const i0.VerificationMeta('size'); + @override + late final i0.GeneratedColumn size = i0.GeneratedColumn( + 'size', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _hitsMeta = + const i0.VerificationMeta('hits'); + @override + late final i0.GeneratedColumn hits = i0.GeneratedColumn( + 'hits', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _missesMeta = + const i0.VerificationMeta('misses'); + @override + late final i0.GeneratedColumn misses = i0.GeneratedColumn( + 'misses', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(0)); + static const i0.VerificationMeta _metadataJsonMeta = + const i0.VerificationMeta('metadataJson'); + @override + late final i0.GeneratedColumn metadataJson = + i0.GeneratedColumn('metadata_json', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const i3.Constant('{}')); + @override + List get $columns => + [name, maxLength, length, size, hits, misses, metadataJson]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_store'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('max_length')) { + context.handle(_maxLengthMeta, + maxLength.isAcceptableOrUnknown(data['max_length']!, _maxLengthMeta)); + } + if (data.containsKey('length')) { + context.handle(_lengthMeta, + length.isAcceptableOrUnknown(data['length']!, _lengthMeta)); + } + if (data.containsKey('size')) { + context.handle( + _sizeMeta, size.isAcceptableOrUnknown(data['size']!, _sizeMeta)); + } + if (data.containsKey('hits')) { + context.handle( + _hitsMeta, hits.isAcceptableOrUnknown(data['hits']!, _hitsMeta)); + } + if (data.containsKey('misses')) { + context.handle(_missesMeta, + misses.isAcceptableOrUnknown(data['misses']!, _missesMeta)); + } + if (data.containsKey('metadata_json')) { + context.handle( + _metadataJsonMeta, + metadataJson.isAcceptableOrUnknown( + data['metadata_json']!, _metadataJsonMeta)); + } + return context; + } + + @override + Set get $primaryKey => {name}; + @override + i1.DriftStoreData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftStoreData( + name: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!, + maxLength: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}max_length']), + length: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}length'])!, + size: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}size'])!, + hits: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}hits'])!, + misses: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}misses'])!, + metadataJson: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}metadata_json'])!, + ); + } + + @override + $DriftStoreTable createAlias(String alias) { + return $DriftStoreTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class DriftStoreData extends i0.DataClass + implements i0.Insertable { + /// The unique name of the store + final String name; + + /// Maximum number of tiles allowed in the store (null = unlimited) + final int? maxLength; + + /// Current number of tiles in the store + final int length; + + /// Total size of all tiles in bytes + final int size; + + /// Number of cache hits recorded for this store + final int hits; + + /// Number of cache misses recorded for this store + final int misses; + + /// JSON-encoded key-value metadata for this store + final String metadataJson; + const DriftStoreData( + {required this.name, + this.maxLength, + required this.length, + required this.size, + required this.hits, + required this.misses, + required this.metadataJson}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = i0.Variable(name); + if (!nullToAbsent || maxLength != null) { + map['max_length'] = i0.Variable(maxLength); + } + map['length'] = i0.Variable(length); + map['size'] = i0.Variable(size); + map['hits'] = i0.Variable(hits); + map['misses'] = i0.Variable(misses); + map['metadata_json'] = i0.Variable(metadataJson); + return map; + } + + i1.DriftStoreCompanion toCompanion(bool nullToAbsent) { + return i1.DriftStoreCompanion( + name: i0.Value(name), + maxLength: maxLength == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(maxLength), + length: i0.Value(length), + size: i0.Value(size), + hits: i0.Value(hits), + misses: i0.Value(misses), + metadataJson: i0.Value(metadataJson), + ); + } + + factory DriftStoreData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftStoreData( + name: serializer.fromJson(json['name']), + maxLength: serializer.fromJson(json['maxLength']), + length: serializer.fromJson(json['length']), + size: serializer.fromJson(json['size']), + hits: serializer.fromJson(json['hits']), + misses: serializer.fromJson(json['misses']), + metadataJson: serializer.fromJson(json['metadataJson']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'maxLength': serializer.toJson(maxLength), + 'length': serializer.toJson(length), + 'size': serializer.toJson(size), + 'hits': serializer.toJson(hits), + 'misses': serializer.toJson(misses), + 'metadataJson': serializer.toJson(metadataJson), + }; + } + + i1.DriftStoreData copyWith( + {String? name, + i0.Value maxLength = const i0.Value.absent(), + int? length, + int? size, + int? hits, + int? misses, + String? metadataJson}) => + i1.DriftStoreData( + name: name ?? this.name, + maxLength: maxLength.present ? maxLength.value : this.maxLength, + length: length ?? this.length, + size: size ?? this.size, + hits: hits ?? this.hits, + misses: misses ?? this.misses, + metadataJson: metadataJson ?? this.metadataJson, + ); + DriftStoreData copyWithCompanion(i1.DriftStoreCompanion data) { + return DriftStoreData( + name: data.name.present ? data.name.value : this.name, + maxLength: data.maxLength.present ? data.maxLength.value : this.maxLength, + length: data.length.present ? data.length.value : this.length, + size: data.size.present ? data.size.value : this.size, + hits: data.hits.present ? data.hits.value : this.hits, + misses: data.misses.present ? data.misses.value : this.misses, + metadataJson: data.metadataJson.present + ? data.metadataJson.value + : this.metadataJson, + ); + } + + @override + String toString() { + return (StringBuffer('DriftStoreData(') + ..write('name: $name, ') + ..write('maxLength: $maxLength, ') + ..write('length: $length, ') + ..write('size: $size, ') + ..write('hits: $hits, ') + ..write('misses: $misses, ') + ..write('metadataJson: $metadataJson') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(name, maxLength, length, size, hits, misses, metadataJson); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftStoreData && + other.name == this.name && + other.maxLength == this.maxLength && + other.length == this.length && + other.size == this.size && + other.hits == this.hits && + other.misses == this.misses && + other.metadataJson == this.metadataJson); +} + +class DriftStoreCompanion extends i0.UpdateCompanion { + final i0.Value name; + final i0.Value maxLength; + final i0.Value length; + final i0.Value size; + final i0.Value hits; + final i0.Value misses; + final i0.Value metadataJson; + const DriftStoreCompanion({ + this.name = const i0.Value.absent(), + this.maxLength = const i0.Value.absent(), + this.length = const i0.Value.absent(), + this.size = const i0.Value.absent(), + this.hits = const i0.Value.absent(), + this.misses = const i0.Value.absent(), + this.metadataJson = const i0.Value.absent(), + }); + DriftStoreCompanion.insert({ + required String name, + this.maxLength = const i0.Value.absent(), + this.length = const i0.Value.absent(), + this.size = const i0.Value.absent(), + this.hits = const i0.Value.absent(), + this.misses = const i0.Value.absent(), + this.metadataJson = const i0.Value.absent(), + }) : name = i0.Value(name); + static i0.Insertable custom({ + i0.Expression? name, + i0.Expression? maxLength, + i0.Expression? length, + i0.Expression? size, + i0.Expression? hits, + i0.Expression? misses, + i0.Expression? metadataJson, + }) { + return i0.RawValuesInsertable({ + if (name != null) 'name': name, + if (maxLength != null) 'max_length': maxLength, + if (length != null) 'length': length, + if (size != null) 'size': size, + if (hits != null) 'hits': hits, + if (misses != null) 'misses': misses, + if (metadataJson != null) 'metadata_json': metadataJson, + }); + } + + i1.DriftStoreCompanion copyWith( + {i0.Value? name, + i0.Value? maxLength, + i0.Value? length, + i0.Value? size, + i0.Value? hits, + i0.Value? misses, + i0.Value? metadataJson}) { + return i1.DriftStoreCompanion( + name: name ?? this.name, + maxLength: maxLength ?? this.maxLength, + length: length ?? this.length, + size: size ?? this.size, + hits: hits ?? this.hits, + misses: misses ?? this.misses, + metadataJson: metadataJson ?? this.metadataJson, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (maxLength.present) { + map['max_length'] = i0.Variable(maxLength.value); + } + if (length.present) { + map['length'] = i0.Variable(length.value); + } + if (size.present) { + map['size'] = i0.Variable(size.value); + } + if (hits.present) { + map['hits'] = i0.Variable(hits.value); + } + if (misses.present) { + map['misses'] = i0.Variable(misses.value); + } + if (metadataJson.present) { + map['metadata_json'] = i0.Variable(metadataJson.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftStoreCompanion(') + ..write('name: $name, ') + ..write('maxLength: $maxLength, ') + ..write('length: $length, ') + ..write('size: $size, ') + ..write('hits: $hits, ') + ..write('misses: $misses, ') + ..write('metadataJson: $metadataJson') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/native/database/models/store_tile.dart b/lib/src/backend/impls/drift/native/database/models/store_tile.dart new file mode 100644 index 00000000..a76cfaab --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store_tile.dart @@ -0,0 +1,30 @@ +import 'package:drift/drift.dart'; + +// ignore: unused_import -- required for drift_dev FK resolution +import 'store.dart'; +// ignore: unused_import -- required for drift_dev FK resolution +import 'tile.dart'; + +@TableIndex(name: 'idx_store_tile_tile', columns: {#tile}) + +/// Drift junction table linking tiles to their containing stores +class DriftStoreTile extends Table { + /// Foreign key to [DriftStore] with cascade on update/delete + late final store = text().customConstraint( + 'REFERENCES drift_store(name) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL', + )(); + + /// Foreign key to [DriftTile] with cascade on delete + late final tile = text().customConstraint( + 'REFERENCES drift_tile(uid) ON DELETE CASCADE NOT NULL', + )(); + + @override + Set> get primaryKey => {store, tile}; + + @override + bool get isStrict => true; + + @override + bool get withoutRowId => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/store_tile.drift.dart b/lib/src/backend/impls/drift/native/database/models/store_tile.drift.dart new file mode 100644 index 00000000..988abfb3 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store_tile.drift.dart @@ -0,0 +1,551 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store_tile.drift.dart' + as i1; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store_tile.dart' + as i2; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/store.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/tile.drift.dart' + as i5; + +typedef $$DriftStoreTileTableCreateCompanionBuilder = i1.DriftStoreTileCompanion + Function({ + required String store, + required String tile, +}); +typedef $$DriftStoreTileTableUpdateCompanionBuilder = i1.DriftStoreTileCompanion + Function({ + i0.Value store, + i0.Value tile, +}); + +final class $$DriftStoreTileTableReferences extends i0.BaseReferences< + i0.GeneratedDatabase, i1.$DriftStoreTileTable, i1.DriftStoreTileData> { + $$DriftStoreTileTableReferences( + super.$_db, super.$_table, super.$_typedResult); + + static i3.$DriftStoreTable _storeTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('drift_store') + .createAlias(i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet('drift_store_tile') + .store, + i4.ReadDatabaseContainer(db) + .resultSet('drift_store') + .name)); + + i3.$$DriftStoreTableProcessedTableManager get store { + final $_column = $_itemColumn('store')!; + + final manager = i3 + .$$DriftStoreTableTableManager( + $_db, + i4.ReadDatabaseContainer($_db) + .resultSet('drift_store')) + .filter((f) => f.name.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_storeTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } + + static i5.$DriftTileTable _tileTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('drift_tile') + .createAlias(i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet('drift_store_tile') + .tile, + i4.ReadDatabaseContainer(db) + .resultSet('drift_tile') + .uid)); + + i5.$$DriftTileTableProcessedTableManager get tile { + final $_column = $_itemColumn('tile')!; + + final manager = i5 + .$$DriftTileTableTableManager( + $_db, + i4.ReadDatabaseContainer($_db) + .resultSet('drift_tile')) + .filter((f) => f.uid.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_tileTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + +class $$DriftStoreTileTableFilterComposer + extends i0.Composer { + $$DriftStoreTileTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$DriftStoreTableFilterComposer get store { + final i3.$$DriftStoreTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i3.$$DriftStoreTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i5.$$DriftTileTableFilterComposer get tile { + final i5.$$DriftTileTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.tile, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + getReferencedColumn: (t) => t.uid, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i5.$$DriftTileTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftStoreTileTableOrderingComposer + extends i0.Composer { + $$DriftStoreTileTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$DriftStoreTableOrderingComposer get store { + final i3.$$DriftStoreTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i3.$$DriftStoreTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i5.$$DriftTileTableOrderingComposer get tile { + final i5.$$DriftTileTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.tile, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + getReferencedColumn: (t) => t.uid, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i5.$$DriftTileTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftStoreTileTableAnnotationComposer + extends i0.Composer { + $$DriftStoreTileTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$DriftStoreTableAnnotationComposer get store { + final i3.$$DriftStoreTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.store, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + getReferencedColumn: (t) => t.name, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i3.$$DriftStoreTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_store'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i5.$$DriftTileTableAnnotationComposer get tile { + final i5.$$DriftTileTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.tile, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + getReferencedColumn: (t) => t.uid, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i5.$$DriftTileTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('drift_tile'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$DriftStoreTileTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftStoreTileTable, + i1.DriftStoreTileData, + i1.$$DriftStoreTileTableFilterComposer, + i1.$$DriftStoreTileTableOrderingComposer, + i1.$$DriftStoreTileTableAnnotationComposer, + $$DriftStoreTileTableCreateCompanionBuilder, + $$DriftStoreTileTableUpdateCompanionBuilder, + (i1.DriftStoreTileData, i1.$$DriftStoreTileTableReferences), + i1.DriftStoreTileData, + i0.PrefetchHooks Function({bool store, bool tile})> { + $$DriftStoreTileTableTableManager( + i0.GeneratedDatabase db, i1.$DriftStoreTileTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$DriftStoreTileTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftStoreTileTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$DriftStoreTileTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value store = const i0.Value.absent(), + i0.Value tile = const i0.Value.absent(), + }) => + i1.DriftStoreTileCompanion( + store: store, + tile: tile, + ), + createCompanionCallback: ({ + required String store, + required String tile, + }) => + i1.DriftStoreTileCompanion.insert( + store: store, + tile: tile, + ), + withReferenceMapper: (p0) => p0 + .map((e) => ( + e.readTable(table), + i1.$$DriftStoreTileTableReferences(db, table, e) + )) + .toList(), + prefetchHooksCallback: ({store = false, tile = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (store) { + state = state.withJoin( + currentTable: table, + currentColumn: table.store, + referencedTable: + i1.$$DriftStoreTileTableReferences._storeTable(db), + referencedColumn: + i1.$$DriftStoreTileTableReferences._storeTable(db).name, + ) as T; + } + if (tile) { + state = state.withJoin( + currentTable: table, + currentColumn: table.tile, + referencedTable: + i1.$$DriftStoreTileTableReferences._tileTable(db), + referencedColumn: + i1.$$DriftStoreTileTableReferences._tileTable(db).uid, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + )); +} + +typedef $$DriftStoreTileTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftStoreTileTable, + i1.DriftStoreTileData, + i1.$$DriftStoreTileTableFilterComposer, + i1.$$DriftStoreTileTableOrderingComposer, + i1.$$DriftStoreTileTableAnnotationComposer, + $$DriftStoreTileTableCreateCompanionBuilder, + $$DriftStoreTileTableUpdateCompanionBuilder, + (i1.DriftStoreTileData, i1.$$DriftStoreTileTableReferences), + i1.DriftStoreTileData, + i0.PrefetchHooks Function({bool store, bool tile})>; +i0.Index get idxStoreTileTile => i0.Index('idx_store_tile_tile', + 'CREATE INDEX idx_store_tile_tile ON drift_store_tile (tile)'); + +class $DriftStoreTileTable extends i2.DriftStoreTile + with i0.TableInfo<$DriftStoreTileTable, i1.DriftStoreTileData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftStoreTileTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _storeMeta = + const i0.VerificationMeta('store'); + @override + late final i0.GeneratedColumn store = i0.GeneratedColumn( + 'store', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'REFERENCES drift_store(name) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL'); + static const i0.VerificationMeta _tileMeta = + const i0.VerificationMeta('tile'); + @override + late final i0.GeneratedColumn tile = i0.GeneratedColumn( + 'tile', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'REFERENCES drift_tile(uid) ON DELETE CASCADE NOT NULL'); + @override + List get $columns => [store, tile]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_store_tile'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('store')) { + context.handle( + _storeMeta, store.isAcceptableOrUnknown(data['store']!, _storeMeta)); + } else if (isInserting) { + context.missing(_storeMeta); + } + if (data.containsKey('tile')) { + context.handle( + _tileMeta, tile.isAcceptableOrUnknown(data['tile']!, _tileMeta)); + } else if (isInserting) { + context.missing(_tileMeta); + } + return context; + } + + @override + Set get $primaryKey => {store, tile}; + @override + i1.DriftStoreTileData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftStoreTileData( + store: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}store'])!, + tile: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}tile'])!, + ); + } + + @override + $DriftStoreTileTable createAlias(String alias) { + return $DriftStoreTileTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class DriftStoreTileData extends i0.DataClass + implements i0.Insertable { + /// Foreign key to [DriftStore] with cascade on update/delete + final String store; + + /// Foreign key to [DriftTile] with cascade on delete + final String tile; + const DriftStoreTileData({required this.store, required this.tile}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['store'] = i0.Variable(store); + map['tile'] = i0.Variable(tile); + return map; + } + + i1.DriftStoreTileCompanion toCompanion(bool nullToAbsent) { + return i1.DriftStoreTileCompanion( + store: i0.Value(store), + tile: i0.Value(tile), + ); + } + + factory DriftStoreTileData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftStoreTileData( + store: serializer.fromJson(json['store']), + tile: serializer.fromJson(json['tile']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'store': serializer.toJson(store), + 'tile': serializer.toJson(tile), + }; + } + + i1.DriftStoreTileData copyWith({String? store, String? tile}) => + i1.DriftStoreTileData( + store: store ?? this.store, + tile: tile ?? this.tile, + ); + DriftStoreTileData copyWithCompanion(i1.DriftStoreTileCompanion data) { + return DriftStoreTileData( + store: data.store.present ? data.store.value : this.store, + tile: data.tile.present ? data.tile.value : this.tile, + ); + } + + @override + String toString() { + return (StringBuffer('DriftStoreTileData(') + ..write('store: $store, ') + ..write('tile: $tile') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(store, tile); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftStoreTileData && + other.store == this.store && + other.tile == this.tile); +} + +class DriftStoreTileCompanion + extends i0.UpdateCompanion { + final i0.Value store; + final i0.Value tile; + const DriftStoreTileCompanion({ + this.store = const i0.Value.absent(), + this.tile = const i0.Value.absent(), + }); + DriftStoreTileCompanion.insert({ + required String store, + required String tile, + }) : store = i0.Value(store), + tile = i0.Value(tile); + static i0.Insertable custom({ + i0.Expression? store, + i0.Expression? tile, + }) { + return i0.RawValuesInsertable({ + if (store != null) 'store': store, + if (tile != null) 'tile': tile, + }); + } + + i1.DriftStoreTileCompanion copyWith( + {i0.Value? store, i0.Value? tile}) { + return i1.DriftStoreTileCompanion( + store: store ?? this.store, + tile: tile ?? this.tile, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (store.present) { + map['store'] = i0.Variable(store.value); + } + if (tile.present) { + map['tile'] = i0.Variable(tile.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftStoreTileCompanion(') + ..write('store: $store, ') + ..write('tile: $tile') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/native/database/models/tile.dart b/lib/src/backend/impls/drift/native/database/models/tile.dart new file mode 100644 index 00000000..d6aa7016 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/tile.dart @@ -0,0 +1,21 @@ +import 'package:drift/drift.dart'; + +@TableIndex(name: 'last_modified', columns: {#lastModified}) + +/// Drift table for cached map tiles +class DriftTile extends Table { + /// The URL of the tile, used as a unique identifier + late final uid = text()(); + + /// The raw image bytes of the tile + late final bytes = blob()(); + + /// The time the tile was last written or updated + late final lastModified = dateTime().withDefault(currentDateAndTime)(); + + @override + Set> get primaryKey => {uid}; + + @override + bool get isStrict => true; +} diff --git a/lib/src/backend/impls/drift/native/database/models/tile.drift.dart b/lib/src/backend/impls/drift/native/database/models/tile.drift.dart new file mode 100644 index 00000000..23f81bea --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/tile.drift.dart @@ -0,0 +1,405 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/tile.drift.dart' + as i1; +import 'dart:typed_data' as i2; +import 'package:flutter_map_tile_caching/src/backend/impls/drift/native/database/models/tile.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$DriftTileTableCreateCompanionBuilder = i1.DriftTileCompanion + Function({ + required String uid, + required i2.Uint8List bytes, + i0.Value lastModified, + i0.Value rowid, +}); +typedef $$DriftTileTableUpdateCompanionBuilder = i1.DriftTileCompanion + Function({ + i0.Value uid, + i0.Value bytes, + i0.Value lastModified, + i0.Value rowid, +}); + +class $$DriftTileTableFilterComposer + extends i0.Composer { + $$DriftTileTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get uid => $composableBuilder( + column: $table.uid, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get bytes => $composableBuilder( + column: $table.bytes, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get lastModified => $composableBuilder( + column: $table.lastModified, + builder: (column) => i0.ColumnFilters(column)); +} + +class $$DriftTileTableOrderingComposer + extends i0.Composer { + $$DriftTileTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get uid => $composableBuilder( + column: $table.uid, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get bytes => $composableBuilder( + column: $table.bytes, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get lastModified => $composableBuilder( + column: $table.lastModified, + builder: (column) => i0.ColumnOrderings(column)); +} + +class $$DriftTileTableAnnotationComposer + extends i0.Composer { + $$DriftTileTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get uid => + $composableBuilder(column: $table.uid, builder: (column) => column); + + i0.GeneratedColumn get bytes => + $composableBuilder(column: $table.bytes, builder: (column) => column); + + i0.GeneratedColumn get lastModified => $composableBuilder( + column: $table.lastModified, builder: (column) => column); +} + +class $$DriftTileTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$DriftTileTable, + i1.DriftTileData, + i1.$$DriftTileTableFilterComposer, + i1.$$DriftTileTableOrderingComposer, + i1.$$DriftTileTableAnnotationComposer, + $$DriftTileTableCreateCompanionBuilder, + $$DriftTileTableUpdateCompanionBuilder, + ( + i1.DriftTileData, + i0.BaseReferences + ), + i1.DriftTileData, + i0.PrefetchHooks Function()> { + $$DriftTileTableTableManager( + i0.GeneratedDatabase db, i1.$DriftTileTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$DriftTileTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$DriftTileTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$DriftTileTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value uid = const i0.Value.absent(), + i0.Value bytes = const i0.Value.absent(), + i0.Value lastModified = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.DriftTileCompanion( + uid: uid, + bytes: bytes, + lastModified: lastModified, + rowid: rowid, + ), + createCompanionCallback: ({ + required String uid, + required i2.Uint8List bytes, + i0.Value lastModified = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.DriftTileCompanion.insert( + uid: uid, + bytes: bytes, + lastModified: lastModified, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$DriftTileTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$DriftTileTable, + i1.DriftTileData, + i1.$$DriftTileTableFilterComposer, + i1.$$DriftTileTableOrderingComposer, + i1.$$DriftTileTableAnnotationComposer, + $$DriftTileTableCreateCompanionBuilder, + $$DriftTileTableUpdateCompanionBuilder, + ( + i1.DriftTileData, + i0.BaseReferences + ), + i1.DriftTileData, + i0.PrefetchHooks Function()>; +i0.Index get lastModified => i0.Index('last_modified', + 'CREATE INDEX last_modified ON drift_tile (last_modified)'); + +class $DriftTileTable extends i3.DriftTile + with i0.TableInfo<$DriftTileTable, i1.DriftTileData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $DriftTileTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _uidMeta = const i0.VerificationMeta('uid'); + @override + late final i0.GeneratedColumn uid = i0.GeneratedColumn( + 'uid', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _bytesMeta = + const i0.VerificationMeta('bytes'); + @override + late final i0.GeneratedColumn bytes = + i0.GeneratedColumn('bytes', aliasedName, false, + type: i0.DriftSqlType.blob, requiredDuringInsert: true); + static const i0.VerificationMeta _lastModifiedMeta = + const i0.VerificationMeta('lastModified'); + @override + late final i0.GeneratedColumn lastModified = + i0.GeneratedColumn('last_modified', aliasedName, false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime); + @override + List get $columns => [uid, bytes, lastModified]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'drift_tile'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('uid')) { + context.handle( + _uidMeta, uid.isAcceptableOrUnknown(data['uid']!, _uidMeta)); + } else if (isInserting) { + context.missing(_uidMeta); + } + if (data.containsKey('bytes')) { + context.handle( + _bytesMeta, bytes.isAcceptableOrUnknown(data['bytes']!, _bytesMeta)); + } else if (isInserting) { + context.missing(_bytesMeta); + } + if (data.containsKey('last_modified')) { + context.handle( + _lastModifiedMeta, + lastModified.isAcceptableOrUnknown( + data['last_modified']!, _lastModifiedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {uid}; + @override + i1.DriftTileData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.DriftTileData( + uid: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}uid'])!, + bytes: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}bytes'])!, + lastModified: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}last_modified'])!, + ); + } + + @override + $DriftTileTable createAlias(String alias) { + return $DriftTileTable(attachedDatabase, alias); + } + + @override + bool get isStrict => true; +} + +class DriftTileData extends i0.DataClass + implements i0.Insertable { + /// The URL of the tile, used as a unique identifier + final String uid; + + /// The raw image bytes of the tile + final i2.Uint8List bytes; + + /// The time the tile was last written or updated + final DateTime lastModified; + const DriftTileData( + {required this.uid, required this.bytes, required this.lastModified}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['uid'] = i0.Variable(uid); + map['bytes'] = i0.Variable(bytes); + map['last_modified'] = i0.Variable(lastModified); + return map; + } + + i1.DriftTileCompanion toCompanion(bool nullToAbsent) { + return i1.DriftTileCompanion( + uid: i0.Value(uid), + bytes: i0.Value(bytes), + lastModified: i0.Value(lastModified), + ); + } + + factory DriftTileData.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return DriftTileData( + uid: serializer.fromJson(json['uid']), + bytes: serializer.fromJson(json['bytes']), + lastModified: serializer.fromJson(json['lastModified']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'uid': serializer.toJson(uid), + 'bytes': serializer.toJson(bytes), + 'lastModified': serializer.toJson(lastModified), + }; + } + + i1.DriftTileData copyWith( + {String? uid, i2.Uint8List? bytes, DateTime? lastModified}) => + i1.DriftTileData( + uid: uid ?? this.uid, + bytes: bytes ?? this.bytes, + lastModified: lastModified ?? this.lastModified, + ); + DriftTileData copyWithCompanion(i1.DriftTileCompanion data) { + return DriftTileData( + uid: data.uid.present ? data.uid.value : this.uid, + bytes: data.bytes.present ? data.bytes.value : this.bytes, + lastModified: data.lastModified.present + ? data.lastModified.value + : this.lastModified, + ); + } + + @override + String toString() { + return (StringBuffer('DriftTileData(') + ..write('uid: $uid, ') + ..write('bytes: $bytes, ') + ..write('lastModified: $lastModified') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(uid, i0.$driftBlobEquality.hash(bytes), lastModified); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.DriftTileData && + other.uid == this.uid && + i0.$driftBlobEquality.equals(other.bytes, this.bytes) && + other.lastModified == this.lastModified); +} + +class DriftTileCompanion extends i0.UpdateCompanion { + final i0.Value uid; + final i0.Value bytes; + final i0.Value lastModified; + final i0.Value rowid; + const DriftTileCompanion({ + this.uid = const i0.Value.absent(), + this.bytes = const i0.Value.absent(), + this.lastModified = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + DriftTileCompanion.insert({ + required String uid, + required i2.Uint8List bytes, + this.lastModified = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : uid = i0.Value(uid), + bytes = i0.Value(bytes); + static i0.Insertable custom({ + i0.Expression? uid, + i0.Expression? bytes, + i0.Expression? lastModified, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (uid != null) 'uid': uid, + if (bytes != null) 'bytes': bytes, + if (lastModified != null) 'last_modified': lastModified, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.DriftTileCompanion copyWith( + {i0.Value? uid, + i0.Value? bytes, + i0.Value? lastModified, + i0.Value? rowid}) { + return i1.DriftTileCompanion( + uid: uid ?? this.uid, + bytes: bytes ?? this.bytes, + lastModified: lastModified ?? this.lastModified, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (uid.present) { + map['uid'] = i0.Variable(uid.value); + } + if (bytes.present) { + map['bytes'] = i0.Variable(bytes.value); + } + if (lastModified.present) { + map['last_modified'] = i0.Variable(lastModified.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DriftTileCompanion(') + ..write('uid: $uid, ') + ..write('bytes: $bytes, ') + ..write('lastModified: $lastModified, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} diff --git a/lib/src/backend/impls/drift/web/backend.dart b/lib/src/backend/impls/drift/web/backend.dart new file mode 100644 index 00000000..c86bd631 --- /dev/null +++ b/lib/src/backend/impls/drift/web/backend.dart @@ -0,0 +1,1352 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:async'; +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/wasm.dart'; +import 'package:flutter/foundation.dart'; +import 'package:sqlite3/wasm.dart'; + +import '../../../../../flutter_map_tile_caching.dart'; +import '../../../export_internal.dart'; +import '../native/backend/models/drift_backend_tile.dart'; +import '../native/backend/utils/region_serialization.dart'; +import '../native/database/database.dart'; +import '../native/database/models/recovery.drift.dart'; +import '../native/database/models/root.drift.dart'; +import '../native/database/models/store.drift.dart'; +import '../native/database/models/store_tile.drift.dart' + show DriftStoreTileCompanion; +import '../native/database/models/tile.drift.dart'; + +/// Implementation of [FMTCBackend] that uses Drift (SQLite via WASM) on web +final class FMTCDriftBackend implements FMTCBackend { + /// {@macro fmtc.backend.initialise} + @override + Future initialise({ + String? rootDirectory, + @visibleForTesting bool useInMemoryDatabase = false, + }) => + FMTCDriftBackendInternal._instance.initialise( + useInMemoryDatabase: useInMemoryDatabase, + ); + + /// {@macro fmtc.backend.uninitialise} + @override + Future uninitialise({ + bool deleteRoot = false, + }) => + FMTCDriftBackendInternal._instance.uninitialise(deleteRoot: deleteRoot); +} + +/// Internal implementation of [FMTCBackend] for web using Drift (WASM) +abstract interface class FMTCDriftBackendInternal + implements FMTCBackendInternal { + static final _instance = _FMTCDriftBackendInternalWeb._(); +} + +class _FMTCDriftBackendInternalWeb implements FMTCDriftBackendInternal { + _FMTCDriftBackendInternalWeb._(); + + @override + String get friendlyIdentifier => 'Drift (Web)'; + + DriftFMTCDatabase? _db; + DriftFMTCDatabase get _expectDb => _db ?? (throw RootUnavailable()); + + // `removeOldestTilesAboveLimit` tracking & debouncing + Timer? _rotalDebouncer; + int? _rotalStoresHash; + Completer>? _rotalResultCompleter; + + // Lifecycle + + Future initialise({ + required bool useInMemoryDatabase, + }) async { + if (_db != null) throw RootAlreadyInitialised(); + + if (useInMemoryDatabase) { + final sqlite3 = await WasmSqlite3.loadFromUrl( + Uri.parse('sqlite3.wasm'), + ); + sqlite3.registerVirtualFileSystem( + InMemoryFileSystem(), + makeDefault: true, + ); + _db = DriftFMTCDatabase(WasmDatabase.inMemory(sqlite3)); + } else { + final result = await WasmDatabase.open( + databaseName: 'fmtc_drift', + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('drift_worker.dart.js'), + ); + _db = DriftFMTCDatabase(result.resolvedExecutor); + } + + // Ensure the singleton root stats row exists + await _db!.into(_db!.driftRoot).insertOnConflictUpdate( + DriftRootCompanion.insert(), + ); + + FMTCBackendAccess.internal = this; + FMTCBackendAccessThreadSafe.internal = + _FMTCDriftBackendInternalThreadSafeWeb._(_db!); + } + + Future uninitialise({required bool deleteRoot}) async { + _expectDb; + + if (deleteRoot) { + // On web, delete all data from tables + await _db!.transaction(() async { + await _db!.delete(_db!.driftStoreTile).go(); + await _db!.delete(_db!.driftTile).go(); + await _db!.delete(_db!.driftRecoveryRegion).go(); + await _db!.delete(_db!.driftRecovery).go(); + await _db!.delete(_db!.driftStore).go(); + await _db!.delete(_db!.driftRoot).go(); + }); + } + + await _db!.close(); + _db = null; + + _rotalDebouncer?.cancel(); + _rotalDebouncer = null; + _rotalStoresHash = null; + _rotalResultCompleter?.completeError(RootUnavailable()); + _rotalResultCompleter = null; + + FMTCBackendAccess.internal = null; + FMTCBackendAccessThreadSafe.internal = null; + } + + // Root stats + + // On web, realSize isn't meaningful (no file on disk). Return rootSize. + @override + Future realSize() => rootSize(); + + @override + Future rootSize() async { + final root = await (_expectDb.select(_expectDb.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + return root.size / 1024; + } + + @override + Future rootLength() async { + final root = await (_expectDb.select(_expectDb.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + return root.length; + } + + // Store management + + @override + Future> listStores() async { + final query = _expectDb.select(_expectDb.driftStore) + ..addColumns([_expectDb.driftStore.name]); + final results = await query.get(); + return results.map((r) => r.name).toList(); + } + + @override + Future storeExists({required String storeName}) async { + final query = _expectDb.select(_expectDb.driftStore) + ..where((s) => s.name.equals(storeName)); + final result = await query.getSingleOrNull(); + return result != null; + } + + @override + Future createStore({ + required String storeName, + required int? maxLength, + }) async { + await _expectDb.into(_expectDb.driftStore).insertOnConflictUpdate( + DriftStoreCompanion.insert( + name: storeName, + maxLength: Value(maxLength), + ), + ); + } + + @override + Future deleteStore({required String storeName}) async { + final db = _expectDb; + + await db.transaction(() async { + final tilesToCheck = await (db.select(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .get(); + final tileUids = tilesToCheck.map((st) => st.tile).toSet(); + + await (db.delete(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .go(); + + int orphanedSize = 0; + int orphanedCount = 0; + + for (final tileUid in tileUids) { + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tileUid))) + .get(); + if (remaining.isEmpty) { + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(tileUid))) + .getSingleOrNull(); + if (tile != null) { + orphanedSize += tile.bytes.lengthInBytes; + orphanedCount++; + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tileUid))) + .go(); + } + } + } + + if (orphanedCount > 0) { + await _updateRootStats( + db, + deltaLength: -orphanedCount, + deltaSize: -orphanedSize, + ); + } + + await (db.delete(db.driftStore)..where((s) => s.name.equals(storeName))) + .go(); + }); + } + + @override + Future resetStore({required String storeName}) async { + final db = _expectDb; + + final storeRow = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (storeRow == null) throw StoreNotExists(storeName: storeName); + + await db.transaction(() async { + final tilesToCheck = await (db.select(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .get(); + final tileUids = tilesToCheck.map((st) => st.tile).toSet(); + + await (db.delete(db.driftStoreTile) + ..where((st) => st.store.equals(storeName))) + .go(); + + int orphanedSize = 0; + int orphanedCount = 0; + + for (final tileUid in tileUids) { + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tileUid))) + .get(); + if (remaining.isEmpty) { + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(tileUid))) + .getSingleOrNull(); + if (tile != null) { + orphanedSize += tile.bytes.lengthInBytes; + orphanedCount++; + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tileUid))) + .go(); + } + } + } + + if (orphanedCount > 0) { + await _updateRootStats( + db, + deltaLength: -orphanedCount, + deltaSize: -orphanedSize, + ); + } + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + const DriftStoreCompanion( + length: Value(0), + size: Value(0), + hits: Value(0), + misses: Value(0), + ), + ); + }); + } + + @override + Future renameStore({ + required String currentStoreName, + required String newStoreName, + }) async { + final db = _expectDb; + + final storeRow = await (db.select(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .getSingleOrNull(); + if (storeRow == null) throw StoreNotExists(storeName: currentStoreName); + + await (db.update(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .write(DriftStoreCompanion(name: Value(newStoreName))); + } + + @override + Future storeGetMaxLength({required String storeName}) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + return store.maxLength; + } + + @override + Future storeSetMaxLength({ + required String storeName, + required int? newMaxLength, + }) async { + final db = _expectDb; + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(maxLength: Value(newMaxLength))); + } + + @override + Future<({double size, int length, int hits, int misses})> getStoreStats({ + required String storeName, + }) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + return ( + size: store.size / 1024, + length: store.length, + hits: store.hits, + misses: store.misses, + ); + } + + // Tile CRUD + + @override + Future tileExists({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftTile.uid.equals(url) & + db.driftStoreTile.store.isIn(resolvedStores), + ); + + final result = await query.getSingleOrNull(); + return result != null; + } + + @override + Future< + ({ + BackendTile? tile, + List intersectedStoreNames, + List allStoreNames, + })> readTile({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftTile.uid.equals(url) & + db.driftStoreTile.store.isIn(resolvedStores), + ); + + final result = await query.getSingleOrNull(); + + if (result == null) { + return ( + tile: null, + intersectedStoreNames: const [], + allStoreNames: const [], + ); + } + + final tileData = result.readTable(db.driftTile); + + final allStoresQuery = db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url)); + final allStoresResult = await allStoresQuery.get(); + final allStoreNamesList = + allStoresResult.map((st) => st.store).toList(growable: false); + final intersectedStoreNames = allStoreNamesList + .where(resolvedStores.contains) + .toList(growable: false); + + return ( + tile: DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ), + intersectedStoreNames: intersectedStoreNames, + allStoreNames: allStoreNamesList, + ); + } + + @override + Future readLatestTile({required String storeName}) async { + final db = _expectDb; + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName)) + ..orderBy([OrderingTerm.desc(db.driftTile.lastModified)]) + ..limit(1); + + final result = await query.getSingleOrNull(); + if (result == null) return null; + + final tileData = result.readTable(db.driftTile); + return DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ); + } + + @override + Future> writeTile({ + required String url, + required Uint8List bytes, + required List storeNames, + required List? writeAllNotIn, + }) async { + final db = _expectDb; + + final allStoreNames = await (db.select(db.driftStore) + ..addColumns([db.driftStore.name])) + .get() + .then((rows) => rows.map((r) => r.name).toList()); + + for (final storeName in storeNames) { + if (!allStoreNames.contains(storeName)) { + throw StoreNotExists(storeName: storeName); + } + } + + final compiledStoreNames = writeAllNotIn == null + ? storeNames + : [ + ...storeNames, + ...allStoreNames.whereNot( + (e) => writeAllNotIn.contains(e) || storeNames.contains(e), + ), + ]; + + if (compiledStoreNames.isEmpty) return const {}; + + final result = { + for (final storeName in compiledStoreNames) storeName: false, + }; + + await db.transaction(() async { + final existingTile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + + if (existingTile != null) { + final sizeDelta = + bytes.lengthInBytes - existingTile.bytes.lengthInBytes; + + await _updateRootStats(db, deltaSize: sizeDelta); + + final currentStores = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + final currentStoreNames = currentStores.map((st) => st.store).toSet(); + + for (final currentStoreName in currentStoreNames) { + await (db.update(db.driftStore) + ..where((s) => s.name.equals(currentStoreName))) + .write( + DriftStoreCompanion( + size: Value( + await _getStoreSize(db, currentStoreName) + sizeDelta, + ), + ), + ); + } + + for (final storeName in compiledStoreNames) { + if (!currentStoreNames.contains(storeName)) { + result[storeName] = true; + await db.into(db.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + } + + await (db.update(db.driftTile)..where((t) => t.uid.equals(url))).write( + DriftTileCompanion( + bytes: Value(bytes), + lastModified: Value(DateTime.timestamp()), + ), + ); + } else { + await _updateRootStats( + db, + deltaLength: 1, + deltaSize: bytes.lengthInBytes, + ); + + await db.into(db.driftTile).insert( + DriftTileCompanion.insert( + uid: url, + bytes: bytes, + ), + ); + + for (final storeName in compiledStoreNames) { + result[storeName] = true; + + await db.into(db.driftStoreTile).insertOnConflictUpdate( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + } + }); + + return result; + } + + @override + Future deleteTile({ + required String storeName, + required String url, + }) async { + final db = _expectDb; + + return db.transaction(() async { + final tile = await (db.select(db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + if (tile == null) return null; + + final junction = await (db.select(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .getSingleOrNull(); + if (junction == null) return null; + + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .go(); + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length - 1), + size: Value(store.size - tile.bytes.lengthInBytes), + ), + ); + + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + + if (remaining.isEmpty) { + await (db.delete(db.driftTile)..where((t) => t.uid.equals(url))).go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + return true; + } + + return false; + }); + } + + // Statistics + + @override + Future incrementStoreHits({ + required List storeNames, + }) async { + final db = _expectDb; + await db.transaction(() async { + for (final storeName in storeNames) { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(hits: Value(store.hits + 1))); + } + }); + } + + @override + Future incrementStoreMisses({ + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final db = _expectDb; + final resolvedStores = await _resolveReadableStoresFormat(db, storeNames); + + await db.transaction(() async { + for (final storeName in resolvedStores) { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(DriftStoreCompanion(misses: Value(store.misses + 1))); + } + }); + } + + @override + Future> removeOldestTilesAboveLimit({ + required List storeNames, + }) async { + if (_rotalResultCompleter?.isCompleted ?? true) { + _rotalResultCompleter = Completer>(); + } + + void doRemoval() => _rotalResultCompleter!.complete( + _doRemoveOldestTilesAboveLimit(storeNames), + ); + + if (_rotalStoresHash != storeNames.hashCode) { + _rotalStoresHash = storeNames.hashCode; + if (_rotalDebouncer?.isActive ?? false) { + _rotalDebouncer!.cancel(); + doRemoval(); + return _rotalResultCompleter!.future; + } + } + + final isAlreadyActive = _rotalDebouncer?.isActive ?? false; + if (isAlreadyActive) _rotalDebouncer!.cancel(); + _rotalDebouncer = Timer( + Duration(milliseconds: isAlreadyActive ? 500 : 1000), + doRemoval, + ); + + return _rotalResultCompleter!.future; + } + + Future> _doRemoveOldestTilesAboveLimit( + List storeNames, + ) async { + final db = _expectDb; + final result = {}; + + for (final storeName in storeNames) { + final store = await (db.select(db.driftStore) + ..where( + (s) => s.name.equals(storeName) & s.maxLength.isNotNull(), + )) + .getSingleOrNull(); + if (store == null) continue; + + final numToRemove = store.length - store.maxLength!; + if (numToRemove <= 0) continue; + + final oldestTilesQuery = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where(db.driftStoreTile.store.equals(storeName)) + ..orderBy([OrderingTerm.asc(db.driftTile.lastModified)]) + ..limit(numToRemove); + + final oldestTiles = await oldestTilesQuery.get(); + int orphansCount = 0; + + await db.transaction(() async { + for (final row in oldestTiles) { + final tile = row.readTable(db.driftTile); + + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(tile.uid), + )) + .go(); + + await (db.update(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value( + (await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle()) + .length - + 1, + ), + size: Value( + (await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle()) + .size - + tile.bytes.lengthInBytes, + ), + ), + ); + + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tile.uid))) + .get(); + + if (remaining.isEmpty) { + await (db.delete(db.driftTile) + ..where((t) => t.uid.equals(tile.uid))) + .go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + orphansCount++; + } + } + }); + + result[storeName] = orphansCount; + } + + return result; + } + + @override + Future removeTilesOlderThan({ + required String storeName, + required DateTime expiry, + }) async { + final db = _expectDb; + + final query = db.select(db.driftTile).join([ + innerJoin( + db.driftStoreTile, + db.driftStoreTile.tile.equalsExp(db.driftTile.uid), + ), + ]) + ..where( + db.driftStoreTile.store.equals(storeName) & + db.driftTile.lastModified.isSmallerThanValue(expiry), + ); + + final expiredTiles = await query.get(); + int orphansCount = 0; + + await db.transaction(() async { + for (final row in expiredTiles) { + final tile = row.readTable(db.driftTile); + + await (db.delete(db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(tile.uid), + )) + .go(); + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length - 1), + size: Value(store.size - tile.bytes.lengthInBytes), + ), + ); + + final remaining = await (db.select(db.driftStoreTile) + ..where((st) => st.tile.equals(tile.uid))) + .get(); + + if (remaining.isEmpty) { + await (db.delete(db.driftTile)..where((t) => t.uid.equals(tile.uid))) + .go(); + await _updateRootStats( + db, + deltaLength: -1, + deltaSize: -tile.bytes.lengthInBytes, + ); + orphansCount++; + } + } + }); + + return orphansCount; + } + + // Metadata + + @override + Future> readMetadata({ + required String storeName, + }) async { + final db = _expectDb; + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + return (jsonDecode(store.metadataJson) as Map) + .cast(); + } + + @override + Future setMetadata({ + required String storeName, + required String key, + required String value, + }) => + setBulkMetadata(storeName: storeName, kvs: {key: value}); + + @override + Future setBulkMetadata({ + required String storeName, + required Map kvs, + }) async { + final db = _expectDb; + + await db.transaction(() async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + final metadata = jsonDecode(store.metadataJson) as Map + ..addAll(kvs); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion(metadataJson: Value(jsonEncode(metadata))), + ); + }); + } + + @override + Future removeMetadata({ + required String storeName, + required String key, + }) async { + final db = _expectDb; + + return db.transaction(() async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + final metadata = jsonDecode(store.metadataJson) as Map; + final removedVal = metadata.remove(key) as String?; + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion(metadataJson: Value(jsonEncode(metadata))), + ); + + return removedVal; + }); + } + + @override + Future resetMetadata({required String storeName}) async { + final db = _expectDb; + + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + await (db.update(db.driftStore)..where((s) => s.name.equals(storeName))) + .write(const DriftStoreCompanion(metadataJson: Value('{}'))); + } + + // Recovery + + @override + Future> listRecoverableRegions() async { + final db = _expectDb; + final recoveries = await db.select(db.driftRecovery).get(); + final allRegions = await db.select(db.driftRecoveryRegion).get(); + + return recoveries.map((r) { + final rootRegion = allRegions.firstWhere( + (rr) => rr.recovery == r.id && rr.parentRegionId == null, + ); + + return RecoveredRegion( + id: r.id, + storeName: r.store, + time: r.creationTime, + minZoom: r.minZoom, + maxZoom: r.maxZoom, + start: r.startTile, + end: r.endTile, + region: dataToRegion(rootRegion, allRegions), + ); + }).toList(growable: false); + } + + @override + Future getRecoverableRegion({required int id}) async { + final db = _expectDb; + final recovery = await (db.select(db.driftRecovery) + ..where((r) => r.id.equals(id))) + .getSingle(); + final allRegions = await (db.select(db.driftRecoveryRegion) + ..where((rr) => rr.recovery.equals(id))) + .get(); + + final rootRegion = allRegions.firstWhere( + (rr) => rr.parentRegionId == null, + ); + + return RecoveredRegion( + id: recovery.id, + storeName: recovery.store, + time: recovery.creationTime, + minZoom: recovery.minZoom, + maxZoom: recovery.maxZoom, + start: recovery.startTile, + end: recovery.endTile, + region: dataToRegion(rootRegion, allRegions), + ); + } + + @override + Future cancelRecovery({required int id}) async { + final db = _expectDb; + await db.transaction(() async { + await (db.delete(db.driftRecoveryRegion) + ..where((rr) => rr.recovery.equals(id))) + .go(); + await (db.delete(db.driftRecovery)..where((r) => r.id.equals(id))).go(); + }); + } + + @override + Stream watchRecovery({required bool triggerImmediately}) { + final db = _expectDb; + final query = db.select(db.driftRecovery); + final stream = query.watch().map((_) {}); + if (triggerImmediately) { + return Stream.multi((controller) { + controller.add(null); + stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close, + ); + }); + } + return stream; + } + + // Watchers + + @override + Stream watchStores({ + required List storeNames, + required bool triggerImmediately, + }) { + final db = _expectDb; + final query = db.select(db.driftStore) + ..where((s) => s.name.isIn(storeNames)); + final stream = query.watch().map((_) {}); + if (triggerImmediately) { + return Stream.multi((controller) { + controller.add(null); + stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close, + ); + }); + } + return stream; + } + + // Import/Export — not supported on web + + @override + Future exportStores({ + required List storeNames, + required String path, + }) => + throw UnsupportedError( + 'Import/export is not supported on web. ' + 'Use the native backend for file-based import/export.', + ); + + @override + ImportResult importStores({ + required String path, + required ImportConflictStrategy strategy, + required List? storeNames, + }) => + throw UnsupportedError( + 'Import/export is not supported on web. ' + 'Use the native backend for file-based import/export.', + ); + + @override + Future> listImportableStores({required String path}) => + throw UnsupportedError( + 'Import/export is not supported on web. ' + 'Use the native backend for file-based import/export.', + ); + + // Helper methods + + Future _getStoreSize(DriftFMTCDatabase db, String storeName) async { + final store = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingle(); + return store.size; + } + + Future _updateRootStats( + DriftFMTCDatabase db, { + int deltaLength = 0, + int deltaSize = 0, + }) async { + final root = await (db.select(db.driftRoot)..where((r) => r.id.equals(0))) + .getSingle(); + await (db.update(db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion( + length: Value(root.length + deltaLength), + size: Value(root.size + deltaSize), + ), + ); + } + + Future> _resolveReadableStoresFormat( + DriftFMTCDatabase db, + ({bool includeOrExclude, List storeNames}) readableStores, + ) async { + if (!readableStores.includeOrExclude) { + final allStores = await db.select(db.driftStore).get(); + final allNames = allStores.map((s) => s.name); + return allNames + .whereNot((e) => readableStores.storeNames.contains(e)) + .toList(growable: false); + } + + for (final storeName in readableStores.storeNames) { + final exists = await (db.select(db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (exists == null) throw StoreNotExists(storeName: storeName); + } + + return readableStores.storeNames; + } +} + +/// Web thread-safe implementation — on web there are no true isolates, +/// so this simply wraps the same database instance. +class _FMTCDriftBackendInternalThreadSafeWeb + implements FMTCBackendInternalThreadSafe { + _FMTCDriftBackendInternalThreadSafeWeb._(this._db); + + @override + String get friendlyIdentifier => 'Drift (Web)'; + + final DriftFMTCDatabase _db; + + @override + void initialise() { + // No-op on web — we share the same DB instance + } + + @override + void uninitialise() { + // No-op on web — lifecycle managed by main backend + } + + @override + FMTCBackendInternalThreadSafe duplicate() => + _FMTCDriftBackendInternalThreadSafeWeb._(_db); + + @override + Future readTile({ + required String url, + String? storeName, + }) async { + if (storeName != null) { + final query = _db.select(_db.driftTile).join([ + innerJoin( + _db.driftStoreTile, + _db.driftStoreTile.tile.equalsExp(_db.driftTile.uid), + ), + ]) + ..where( + _db.driftTile.uid.equals(url) & + _db.driftStoreTile.store.equals(storeName), + ); + + final result = await query.getSingleOrNull(); + if (result == null) return null; + + final tileData = result.readTable(_db.driftTile); + return DriftBackendTile( + url: tileData.uid, + bytes: tileData.bytes, + lastModified: tileData.lastModified, + ); + } else { + final tile = await (_db.select(_db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + if (tile == null) return null; + + return DriftBackendTile( + url: tile.uid, + bytes: tile.bytes, + lastModified: tile.lastModified, + ); + } + } + + @override + Future writeTile({ + required String storeName, + required String url, + required Uint8List bytes, + }) async { + await _db.transaction(() async { + final existingTile = await (_db.select(_db.driftTile) + ..where((t) => t.uid.equals(url))) + .getSingleOrNull(); + + final store = await (_db.select(_db.driftStore) + ..where((s) => s.name.equals(storeName))) + .getSingleOrNull(); + if (store == null) throw StoreNotExists(storeName: storeName); + + if (existingTile != null) { + final sizeDelta = + bytes.lengthInBytes - existingTile.bytes.lengthInBytes; + + final junction = await (_db.select(_db.driftStoreTile) + ..where( + (st) => st.store.equals(storeName) & st.tile.equals(url), + )) + .getSingleOrNull(); + + final relatedStores = await (_db.select(_db.driftStoreTile) + ..where((st) => st.tile.equals(url))) + .get(); + for (final rel in relatedStores) { + final relStore = await (_db.select(_db.driftStore) + ..where((s) => s.name.equals(rel.store))) + .getSingle(); + await (_db.update(_db.driftStore) + ..where((s) => s.name.equals(rel.store))) + .write( + DriftStoreCompanion(size: Value(relStore.size + sizeDelta)), + ); + } + + final root = await (_db.select(_db.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + await (_db.update(_db.driftRoot)..where((r) => r.id.equals(0))) + .write(DriftRootCompanion(size: Value(root.size + sizeDelta))); + + if (junction == null) { + await _db.into(_db.driftStoreTile).insert( + DriftStoreTileCompanion.insert( + store: storeName, + tile: url, + ), + ); + await (_db.update(_db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + } + + await (_db.update(_db.driftTile)..where((t) => t.uid.equals(url))) + .write( + DriftTileCompanion( + bytes: Value(bytes), + lastModified: Value(DateTime.timestamp()), + ), + ); + } else { + await _db.into(_db.driftTile).insert( + DriftTileCompanion.insert(uid: url, bytes: bytes), + ); + await _db.into(_db.driftStoreTile).insert( + DriftStoreTileCompanion.insert(store: storeName, tile: url), + ); + await (_db.update(_db.driftStore) + ..where((s) => s.name.equals(storeName))) + .write( + DriftStoreCompanion( + length: Value(store.length + 1), + size: Value(store.size + bytes.lengthInBytes), + ), + ); + + final root = await (_db.select(_db.driftRoot) + ..where((r) => r.id.equals(0))) + .getSingle(); + await (_db.update(_db.driftRoot)..where((r) => r.id.equals(0))).write( + DriftRootCompanion( + length: Value(root.length + 1), + size: Value(root.size + bytes.lengthInBytes), + ), + ); + } + }); + } + + @override + Future writeTiles({ + required String storeName, + required List urls, + required List bytess, + }) async { + await _db.transaction(() async { + for (int i = 0; i < urls.length; i++) { + await writeTile(storeName: storeName, url: urls[i], bytes: bytess[i]); + } + }); + } + + @override + Future startRecovery({ + required int id, + required String storeName, + required DownloadableRegion region, + required int tilesCount, + }) async { + await _db.transaction(() async { + await _db.into(_db.driftRecovery).insert( + DriftRecoveryCompanion.insert( + id: Value(id), + store: storeName, + minZoom: region.minZoom, + maxZoom: region.maxZoom, + startTile: region.start, + endTile: region.end ?? (region.start - 1 + tilesCount), + ), + ); + + Future insertRegion( + BaseRegion baseRegion, + int? parentId, + ) async { + final companion = regionToCompanion( + region: baseRegion, + recoveryId: id, + parentRegionId: parentId, + ); + + final regionId = + await _db.into(_db.driftRecoveryRegion).insert(companion); + + if (baseRegion case final MultiRegion multiRegion) { + for (final subRegion in multiRegion.regions) { + await insertRegion(subRegion, regionId); + } + } + } + + await insertRegion(region.originalRegion, null); + }); + } + + @override + Future updateRecovery({ + required int id, + required int newStartTile, + }) async { + await (_db.update(_db.driftRecovery)..where((r) => r.id.equals(id))) + .write(DriftRecoveryCompanion(startTile: Value(newStartTile))); + } +} diff --git a/lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart b/lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart deleted file mode 100644 index 9966f6a1..00000000 --- a/lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart +++ /dev/null @@ -1,808 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// This code was generated by ObjectBox. To update it run the generator again -// with `dart run build_runner build`. -// See also https://docs.objectbox.io/getting-started#generate-objectbox-code - -// ignore_for_file: camel_case_types, depend_on_referenced_packages -// coverage:ignore-file - -import 'dart:typed_data'; - -import 'package:flat_buffers/flat_buffers.dart' as fb; -import 'package:objectbox/internal.dart' - as obx_int; // generated code can access "internal" functionality -import 'package:objectbox/objectbox.dart' as obx; -import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; - -import '../../../../../../src/backend/impls/objectbox/models/src/recovery.dart'; -import '../../../../../../src/backend/impls/objectbox/models/src/recovery_region.dart'; -import '../../../../../../src/backend/impls/objectbox/models/src/root.dart'; -import '../../../../../../src/backend/impls/objectbox/models/src/store.dart'; -import '../../../../../../src/backend/impls/objectbox/models/src/tile.dart'; - -export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file - -final _entities = [ - obx_int.ModelEntity( - id: const obx_int.IdUid(1, 5472631385587455945), - name: 'ObjectBoxRecovery', - lastPropertyId: const obx_int.IdUid(22, 2247444187089993412), - flags: 0, - properties: [ - obx_int.ModelProperty( - id: const obx_int.IdUid(1, 3769282896877713230), - name: 'id', - type: 6, - flags: 1), - obx_int.ModelProperty( - id: const obx_int.IdUid(2, 2496811483091029921), - name: 'refId', - type: 6, - flags: 40, - indexId: const obx_int.IdUid(1, 1036386105099927432)), - obx_int.ModelProperty( - id: const obx_int.IdUid(3, 3612512640999075849), - name: 'storeName', - type: 9, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(4, 1095455913099058361), - name: 'creationTime', - type: 10, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(5, 1138350672456876624), - name: 'minZoom', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(6, 9040433791555820529), - name: 'maxZoom', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(7, 6819230045021667310), - name: 'startTile', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(8, 8185724925875119436), - name: 'endTile', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(22, 2247444187089993412), - name: 'regionId', - type: 11, - flags: 520, - indexId: const obx_int.IdUid(5, 2172676985778936605), - relationTarget: 'ObjectBoxRecoveryRegion') - ], - relations: [], - backlinks: []), - obx_int.ModelEntity( - id: const obx_int.IdUid(2, 632249766926720928), - name: 'ObjectBoxStore', - lastPropertyId: const obx_int.IdUid(8, 3489822621946254204), - flags: 0, - properties: [ - obx_int.ModelProperty( - id: const obx_int.IdUid(1, 1672655555406818874), - name: 'id', - type: 6, - flags: 1), - obx_int.ModelProperty( - id: const obx_int.IdUid(2, 1060752758288526798), - name: 'name', - type: 9, - flags: 2080, - indexId: const obx_int.IdUid(2, 5602852847672696920)), - obx_int.ModelProperty( - id: const obx_int.IdUid(3, 7375048950056890678), - name: 'length', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(4, 7781853256122686511), - name: 'size', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(5, 3183925806131180531), - name: 'hits', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(6, 6484030110235711573), - name: 'misses', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(7, 7028109958959828879), - name: 'metadataJson', - type: 9, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(8, 3489822621946254204), - name: 'maxLength', - type: 6, - flags: 0) - ], - relations: [], - backlinks: [ - obx_int.ModelBacklink( - name: 'tiles', srcEntity: 'ObjectBoxTile', srcField: 'stores') - ]), - obx_int.ModelEntity( - id: const obx_int.IdUid(3, 8691708694767276679), - name: 'ObjectBoxTile', - lastPropertyId: const obx_int.IdUid(4, 1172878417733380836), - flags: 0, - properties: [ - obx_int.ModelProperty( - id: const obx_int.IdUid(1, 5356545328183635928), - name: 'id', - type: 6, - flags: 1), - obx_int.ModelProperty( - id: const obx_int.IdUid(2, 4115905667778721807), - name: 'url', - type: 9, - flags: 34848, - indexId: const obx_int.IdUid(3, 4361441212367179043)), - obx_int.ModelProperty( - id: const obx_int.IdUid(3, 7508139234299399524), - name: 'bytes', - type: 23, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(4, 1172878417733380836), - name: 'lastModified', - type: 10, - flags: 8, - indexId: const obx_int.IdUid(4, 4857742396480146668)) - ], - relations: [ - obx_int.ModelRelation( - id: const obx_int.IdUid(1, 7496298295217061586), - name: 'stores', - targetId: const obx_int.IdUid(2, 632249766926720928)) - ], - backlinks: []), - obx_int.ModelEntity( - id: const obx_int.IdUid(4, 8718814737097934474), - name: 'ObjectBoxRoot', - lastPropertyId: const obx_int.IdUid(3, 6574336219794969200), - flags: 0, - properties: [ - obx_int.ModelProperty( - id: const obx_int.IdUid(1, 3527394784453371799), - name: 'id', - type: 6, - flags: 1), - obx_int.ModelProperty( - id: const obx_int.IdUid(2, 2833017356902860570), - name: 'length', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(3, 6574336219794969200), - name: 'size', - type: 6, - flags: 0) - ], - relations: [], - backlinks: []), - obx_int.ModelEntity( - id: const obx_int.IdUid(5, 5692106664767803360), - name: 'ObjectBoxRecoveryRegion', - lastPropertyId: const obx_int.IdUid(14, 2380085283533950474), - flags: 0, - properties: [ - obx_int.ModelProperty( - id: const obx_int.IdUid(1, 4629353002259573678), - name: 'id', - type: 6, - flags: 1), - obx_int.ModelProperty( - id: const obx_int.IdUid(2, 1116094237557270575), - name: 'typeId', - type: 6, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(3, 8476920990388836149), - name: 'rectNwLat', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(4, 3015129163086269263), - name: 'rectNwLng', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(5, 8302525711584098439), - name: 'rectSeLat', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(6, 1939082009138163489), - name: 'rectSeLng', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(7, 5260761364748928203), - name: 'circleCenterLat', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(8, 3329863004721648966), - name: 'circleCenterLng', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(9, 8471244801699851283), - name: 'circleRadius', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(10, 5745879403192313286), - name: 'lineLats', - type: 29, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(11, 4679809662196927204), - name: 'lineLngs', - type: 29, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(12, 8730805542251345960), - name: 'lineRadius', - type: 8, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(13, 1607230668161719129), - name: 'customPolygonLats', - type: 29, - flags: 0), - obx_int.ModelProperty( - id: const obx_int.IdUid(14, 2380085283533950474), - name: 'customPolygonLngs', - type: 29, - flags: 0) - ], - relations: [ - obx_int.ModelRelation( - id: const obx_int.IdUid(2, 6378075033578405480), - name: 'multiLinkedRegions', - targetId: const obx_int.IdUid(5, 5692106664767803360)) - ], - backlinks: []) -]; - -/// Shortcut for [obx.Store.new] that passes [getObjectBoxModel] and for Flutter -/// apps by default a [directory] using `defaultStoreDirectory()` from the -/// ObjectBox Flutter library. -/// -/// Note: for desktop apps it is recommended to specify a unique [directory]. -/// -/// See [obx.Store.new] for an explanation of all parameters. -/// -/// For Flutter apps, also calls `loadObjectBoxLibraryAndroidCompat()` from -/// the ObjectBox Flutter library to fix loading the native ObjectBox library -/// on Android 6 and older. -Future openStore( - {String? directory, - int? maxDBSizeInKB, - int? maxDataSizeInKB, - int? fileMode, - int? maxReaders, - bool queriesCaseSensitiveDefault = true, - String? macosApplicationGroup}) async { - await loadObjectBoxLibraryAndroidCompat(); - return obx.Store(getObjectBoxModel(), - directory: directory ?? (await defaultStoreDirectory()).path, - maxDBSizeInKB: maxDBSizeInKB, - maxDataSizeInKB: maxDataSizeInKB, - fileMode: fileMode, - maxReaders: maxReaders, - queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, - macosApplicationGroup: macosApplicationGroup); -} - -/// Returns the ObjectBox model definition for this project for use with -/// [obx.Store.new]. -obx_int.ModelDefinition getObjectBoxModel() { - final model = obx_int.ModelInfo( - entities: _entities, - lastEntityId: const obx_int.IdUid(5, 5692106664767803360), - lastIndexId: const obx_int.IdUid(5, 2172676985778936605), - lastRelationId: const obx_int.IdUid(2, 6378075033578405480), - lastSequenceId: const obx_int.IdUid(0, 0), - retiredEntityUids: const [], - retiredIndexUids: const [], - retiredPropertyUids: const [ - 7217406424708558740, - 5971465387225017460, - 6703340231106164623, - 741105584939284321, - 2939837278126242427, - 2393337671661697697, - 8055510540122966413, - 9110709438555760246, - 8363656194353400366, - 7008680868853575786, - 7670007285707179405, - 490933261424375687, - 3590067577930145922 - ], - retiredRelationUids: const [], - modelVersion: 5, - modelVersionParserMinimum: 5, - version: 1); - - final bindings = { - ObjectBoxRecovery: obx_int.EntityDefinition( - model: _entities[0], - toOneRelations: (ObjectBoxRecovery object) => [object.region], - toManyRelations: (ObjectBoxRecovery object) => {}, - getId: (ObjectBoxRecovery object) => object.id, - setId: (ObjectBoxRecovery object, int id) { - object.id = id; - }, - objectToFB: (ObjectBoxRecovery object, fb.Builder fbb) { - final storeNameOffset = fbb.writeString(object.storeName); - fbb.startTable(23); - fbb.addInt64(0, object.id); - fbb.addInt64(1, object.refId); - fbb.addOffset(2, storeNameOffset); - fbb.addInt64(3, object.creationTime.millisecondsSinceEpoch); - fbb.addInt64(4, object.minZoom); - fbb.addInt64(5, object.maxZoom); - fbb.addInt64(6, object.startTile); - fbb.addInt64(7, object.endTile); - fbb.addInt64(21, object.region.targetId); - fbb.finish(fbb.endTable()); - return object.id; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final refIdParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0); - final storeNameParam = const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 8, ''); - final creationTimeParam = DateTime.fromMillisecondsSinceEpoch( - const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0)); - final minZoomParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0); - final maxZoomParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0); - final startTileParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0); - final endTileParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); - final regionParam = obx.ToOne( - targetId: - const fb.Int64Reader().vTableGet(buffer, rootOffset, 46, 0)); - final object = ObjectBoxRecovery( - refId: refIdParam, - storeName: storeNameParam, - creationTime: creationTimeParam, - minZoom: minZoomParam, - maxZoom: maxZoomParam, - startTile: startTileParam, - endTile: endTileParam, - region: regionParam) - ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - object.region.attach(store); - return object; - }), - ObjectBoxStore: obx_int.EntityDefinition( - model: _entities[1], - toOneRelations: (ObjectBoxStore object) => [], - toManyRelations: (ObjectBoxStore object) => { - obx_int.RelInfo.toManyBacklink(1, object.id): - object.tiles - }, - getId: (ObjectBoxStore object) => object.id, - setId: (ObjectBoxStore object, int id) { - object.id = id; - }, - objectToFB: (ObjectBoxStore object, fb.Builder fbb) { - final nameOffset = fbb.writeString(object.name); - final metadataJsonOffset = fbb.writeString(object.metadataJson); - fbb.startTable(9); - fbb.addInt64(0, object.id); - fbb.addOffset(1, nameOffset); - fbb.addInt64(2, object.length); - fbb.addInt64(3, object.size); - fbb.addInt64(4, object.hits); - fbb.addInt64(5, object.misses); - fbb.addOffset(6, metadataJsonOffset); - fbb.addInt64(7, object.maxLength); - fbb.finish(fbb.endTable()); - return object.id; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final nameParam = const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 6, ''); - final maxLengthParam = - const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 18); - final lengthParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0); - final sizeParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); - final hitsParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0); - final missesParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0); - final metadataJsonParam = - const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 16, ''); - final object = ObjectBoxStore( - name: nameParam, - maxLength: maxLengthParam, - length: lengthParam, - size: sizeParam, - hits: hitsParam, - misses: missesParam, - metadataJson: metadataJsonParam) - ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - obx_int.InternalToManyAccess.setRelInfo( - object.tiles, - store, - obx_int.RelInfo.toManyBacklink(1, object.id)); - return object; - }), - ObjectBoxTile: obx_int.EntityDefinition( - model: _entities[2], - toOneRelations: (ObjectBoxTile object) => [], - toManyRelations: (ObjectBoxTile object) => { - obx_int.RelInfo.toMany(1, object.id): object.stores - }, - getId: (ObjectBoxTile object) => object.id, - setId: (ObjectBoxTile object, int id) { - object.id = id; - }, - objectToFB: (ObjectBoxTile object, fb.Builder fbb) { - final urlOffset = fbb.writeString(object.url); - final bytesOffset = fbb.writeListInt8(object.bytes); - fbb.startTable(5); - fbb.addInt64(0, object.id); - fbb.addOffset(1, urlOffset); - fbb.addOffset(2, bytesOffset); - fbb.addInt64(3, object.lastModified.millisecondsSinceEpoch); - fbb.finish(fbb.endTable()); - return object.id; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final urlParam = const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 6, ''); - final bytesParam = const fb.Uint8ListReader(lazy: false) - .vTableGet(buffer, rootOffset, 8, Uint8List(0)) as Uint8List; - final lastModifiedParam = DateTime.fromMillisecondsSinceEpoch( - const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0)); - final object = ObjectBoxTile( - url: urlParam, bytes: bytesParam, lastModified: lastModifiedParam) - ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - obx_int.InternalToManyAccess.setRelInfo(object.stores, - store, obx_int.RelInfo.toMany(1, object.id)); - return object; - }), - ObjectBoxRoot: obx_int.EntityDefinition( - model: _entities[3], - toOneRelations: (ObjectBoxRoot object) => [], - toManyRelations: (ObjectBoxRoot object) => {}, - getId: (ObjectBoxRoot object) => object.id, - setId: (ObjectBoxRoot object, int id) { - object.id = id; - }, - objectToFB: (ObjectBoxRoot object, fb.Builder fbb) { - fbb.startTable(4); - fbb.addInt64(0, object.id); - fbb.addInt64(1, object.length); - fbb.addInt64(2, object.size); - fbb.finish(fbb.endTable()); - return object.id; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final lengthParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0); - final sizeParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0); - final object = ObjectBoxRoot(length: lengthParam, size: sizeParam) - ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - - return object; - }), - ObjectBoxRecoveryRegion: obx_int.EntityDefinition( - model: _entities[4], - toOneRelations: (ObjectBoxRecoveryRegion object) => [], - toManyRelations: (ObjectBoxRecoveryRegion object) => { - obx_int.RelInfo.toMany(2, object.id): - object.multiLinkedRegions - }, - getId: (ObjectBoxRecoveryRegion object) => object.id, - setId: (ObjectBoxRecoveryRegion object, int id) { - object.id = id; - }, - objectToFB: (ObjectBoxRecoveryRegion object, fb.Builder fbb) { - final lineLatsOffset = object.lineLats == null - ? null - : fbb.writeListFloat64(object.lineLats!); - final lineLngsOffset = object.lineLngs == null - ? null - : fbb.writeListFloat64(object.lineLngs!); - final customPolygonLatsOffset = object.customPolygonLats == null - ? null - : fbb.writeListFloat64(object.customPolygonLats!); - final customPolygonLngsOffset = object.customPolygonLngs == null - ? null - : fbb.writeListFloat64(object.customPolygonLngs!); - fbb.startTable(15); - fbb.addInt64(0, object.id); - fbb.addInt64(1, object.typeId); - fbb.addFloat64(2, object.rectNwLat); - fbb.addFloat64(3, object.rectNwLng); - fbb.addFloat64(4, object.rectSeLat); - fbb.addFloat64(5, object.rectSeLng); - fbb.addFloat64(6, object.circleCenterLat); - fbb.addFloat64(7, object.circleCenterLng); - fbb.addFloat64(8, object.circleRadius); - fbb.addOffset(9, lineLatsOffset); - fbb.addOffset(10, lineLngsOffset); - fbb.addFloat64(11, object.lineRadius); - fbb.addOffset(12, customPolygonLatsOffset); - fbb.addOffset(13, customPolygonLngsOffset); - fbb.finish(fbb.endTable()); - return object.id; - }, - objectFromFB: (obx.Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final typeIdParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0); - final rectNwLatParam = - const fb.Float64Reader().vTableGetNullable(buffer, rootOffset, 8); - final rectNwLngParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 10); - final rectSeLatParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 12); - final rectSeLngParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 14); - final circleCenterLatParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 16); - final circleCenterLngParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 18); - final circleRadiusParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 20); - final lineLatsParam = - const fb.ListReader(fb.Float64Reader(), lazy: false) - .vTableGetNullable(buffer, rootOffset, 22); - final lineLngsParam = - const fb.ListReader(fb.Float64Reader(), lazy: false) - .vTableGetNullable(buffer, rootOffset, 24); - final lineRadiusParam = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 26); - final customPolygonLatsParam = - const fb.ListReader(fb.Float64Reader(), lazy: false) - .vTableGetNullable(buffer, rootOffset, 28); - final customPolygonLngsParam = - const fb.ListReader(fb.Float64Reader(), lazy: false) - .vTableGetNullable(buffer, rootOffset, 30); - final multiLinkedRegionsParam = obx.ToMany(); - final object = ObjectBoxRecoveryRegion( - typeId: typeIdParam, - rectNwLat: rectNwLatParam, - rectNwLng: rectNwLngParam, - rectSeLat: rectSeLatParam, - rectSeLng: rectSeLngParam, - circleCenterLat: circleCenterLatParam, - circleCenterLng: circleCenterLngParam, - circleRadius: circleRadiusParam, - lineLats: lineLatsParam, - lineLngs: lineLngsParam, - lineRadius: lineRadiusParam, - customPolygonLats: customPolygonLatsParam, - customPolygonLngs: customPolygonLngsParam, - multiLinkedRegions: multiLinkedRegionsParam) - ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - obx_int.InternalToManyAccess.setRelInfo( - object.multiLinkedRegions, - store, - obx_int.RelInfo.toMany(2, object.id)); - return object; - }) - }; - - return obx_int.ModelDefinition(model, bindings); -} - -/// [ObjectBoxRecovery] entity fields to define ObjectBox queries. -class ObjectBoxRecovery_ { - /// See [ObjectBoxRecovery.id]. - static final id = - obx.QueryIntegerProperty(_entities[0].properties[0]); - - /// See [ObjectBoxRecovery.refId]. - static final refId = - obx.QueryIntegerProperty(_entities[0].properties[1]); - - /// See [ObjectBoxRecovery.storeName]. - static final storeName = - obx.QueryStringProperty(_entities[0].properties[2]); - - /// See [ObjectBoxRecovery.creationTime]. - static final creationTime = - obx.QueryDateProperty(_entities[0].properties[3]); - - /// See [ObjectBoxRecovery.minZoom]. - static final minZoom = - obx.QueryIntegerProperty(_entities[0].properties[4]); - - /// See [ObjectBoxRecovery.maxZoom]. - static final maxZoom = - obx.QueryIntegerProperty(_entities[0].properties[5]); - - /// See [ObjectBoxRecovery.startTile]. - static final startTile = - obx.QueryIntegerProperty(_entities[0].properties[6]); - - /// See [ObjectBoxRecovery.endTile]. - static final endTile = - obx.QueryIntegerProperty(_entities[0].properties[7]); - - /// See [ObjectBoxRecovery.region]. - static final region = - obx.QueryRelationToOne( - _entities[0].properties[8]); -} - -/// [ObjectBoxStore] entity fields to define ObjectBox queries. -class ObjectBoxStore_ { - /// See [ObjectBoxStore.id]. - static final id = - obx.QueryIntegerProperty(_entities[1].properties[0]); - - /// See [ObjectBoxStore.name]. - static final name = - obx.QueryStringProperty(_entities[1].properties[1]); - - /// See [ObjectBoxStore.length]. - static final length = - obx.QueryIntegerProperty(_entities[1].properties[2]); - - /// See [ObjectBoxStore.size]. - static final size = - obx.QueryIntegerProperty(_entities[1].properties[3]); - - /// See [ObjectBoxStore.hits]. - static final hits = - obx.QueryIntegerProperty(_entities[1].properties[4]); - - /// See [ObjectBoxStore.misses]. - static final misses = - obx.QueryIntegerProperty(_entities[1].properties[5]); - - /// See [ObjectBoxStore.metadataJson]. - static final metadataJson = - obx.QueryStringProperty(_entities[1].properties[6]); - - /// See [ObjectBoxStore.maxLength]. - static final maxLength = - obx.QueryIntegerProperty(_entities[1].properties[7]); -} - -/// [ObjectBoxTile] entity fields to define ObjectBox queries. -class ObjectBoxTile_ { - /// See [ObjectBoxTile.id]. - static final id = - obx.QueryIntegerProperty(_entities[2].properties[0]); - - /// See [ObjectBoxTile.url]. - static final url = - obx.QueryStringProperty(_entities[2].properties[1]); - - /// See [ObjectBoxTile.bytes]. - static final bytes = - obx.QueryByteVectorProperty(_entities[2].properties[2]); - - /// See [ObjectBoxTile.lastModified]. - static final lastModified = - obx.QueryDateProperty(_entities[2].properties[3]); - - /// see [ObjectBoxTile.stores] - static final stores = obx.QueryRelationToMany( - _entities[2].relations[0]); -} - -/// [ObjectBoxRoot] entity fields to define ObjectBox queries. -class ObjectBoxRoot_ { - /// See [ObjectBoxRoot.id]. - static final id = - obx.QueryIntegerProperty(_entities[3].properties[0]); - - /// See [ObjectBoxRoot.length]. - static final length = - obx.QueryIntegerProperty(_entities[3].properties[1]); - - /// See [ObjectBoxRoot.size]. - static final size = - obx.QueryIntegerProperty(_entities[3].properties[2]); -} - -/// [ObjectBoxRecoveryRegion] entity fields to define ObjectBox queries. -class ObjectBoxRecoveryRegion_ { - /// See [ObjectBoxRecoveryRegion.id]. - static final id = obx.QueryIntegerProperty( - _entities[4].properties[0]); - - /// See [ObjectBoxRecoveryRegion.typeId]. - static final typeId = obx.QueryIntegerProperty( - _entities[4].properties[1]); - - /// See [ObjectBoxRecoveryRegion.rectNwLat]. - static final rectNwLat = obx.QueryDoubleProperty( - _entities[4].properties[2]); - - /// See [ObjectBoxRecoveryRegion.rectNwLng]. - static final rectNwLng = obx.QueryDoubleProperty( - _entities[4].properties[3]); - - /// See [ObjectBoxRecoveryRegion.rectSeLat]. - static final rectSeLat = obx.QueryDoubleProperty( - _entities[4].properties[4]); - - /// See [ObjectBoxRecoveryRegion.rectSeLng]. - static final rectSeLng = obx.QueryDoubleProperty( - _entities[4].properties[5]); - - /// See [ObjectBoxRecoveryRegion.circleCenterLat]. - static final circleCenterLat = - obx.QueryDoubleProperty( - _entities[4].properties[6]); - - /// See [ObjectBoxRecoveryRegion.circleCenterLng]. - static final circleCenterLng = - obx.QueryDoubleProperty( - _entities[4].properties[7]); - - /// See [ObjectBoxRecoveryRegion.circleRadius]. - static final circleRadius = obx.QueryDoubleProperty( - _entities[4].properties[8]); - - /// See [ObjectBoxRecoveryRegion.lineLats]. - static final lineLats = - obx.QueryDoubleVectorProperty( - _entities[4].properties[9]); - - /// See [ObjectBoxRecoveryRegion.lineLngs]. - static final lineLngs = - obx.QueryDoubleVectorProperty( - _entities[4].properties[10]); - - /// See [ObjectBoxRecoveryRegion.lineRadius]. - static final lineRadius = obx.QueryDoubleProperty( - _entities[4].properties[11]); - - /// See [ObjectBoxRecoveryRegion.customPolygonLats]. - static final customPolygonLats = - obx.QueryDoubleVectorProperty( - _entities[4].properties[12]); - - /// See [ObjectBoxRecoveryRegion.customPolygonLngs]. - static final customPolygonLngs = - obx.QueryDoubleVectorProperty( - _entities[4].properties[13]); - - /// see [ObjectBoxRecoveryRegion.multiLinkedRegions] - static final multiLinkedRegions = - obx.QueryRelationToMany( - _entities[4].relations[0]); -} diff --git a/lib/src/backend/impls/objectbox/backend/backend.dart b/lib/src/backend/impls/objectbox/native/backend/backend.dart similarity index 95% rename from lib/src/backend/impls/objectbox/backend/backend.dart rename to lib/src/backend/impls/objectbox/native/backend/backend.dart index 2851d559..546a433a 100644 --- a/lib/src/backend/impls/objectbox/backend/backend.dart +++ b/lib/src/backend/impls/objectbox/native/backend/backend.dart @@ -14,9 +14,9 @@ import 'package:flutter/services.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; -import '../../../../../flutter_map_tile_caching.dart'; -import '../../../../misc/int_extremes.dart'; -import '../../../export_internal.dart'; +import '../../../../../../flutter_map_tile_caching.dart'; +import '../../../../../misc/int_extremes.dart'; +import '../../../../export_internal.dart'; import '../models/generated/objectbox.g.dart'; import '../models/src/recovery.dart'; import '../models/src/recovery_region.dart'; @@ -26,11 +26,11 @@ import '../models/src/tile.dart'; export 'package:objectbox/objectbox.dart' show StorageException; +part 'internal.dart'; +part 'internal_workers/shared.dart'; part 'internal_workers/standard/cmd_type.dart'; part 'internal_workers/standard/worker.dart'; -part 'internal_workers/shared.dart'; part 'internal_workers/thread_safe.dart'; -part 'internal.dart'; /// Implementation of [FMTCBackend] that uses ObjectBox as the storage database /// diff --git a/lib/src/backend/impls/objectbox/backend/internal.dart b/lib/src/backend/impls/objectbox/native/backend/internal.dart similarity index 100% rename from lib/src/backend/impls/objectbox/backend/internal.dart rename to lib/src/backend/impls/objectbox/native/backend/internal.dart diff --git a/lib/src/backend/impls/objectbox/backend/internal_workers/shared.dart b/lib/src/backend/impls/objectbox/native/backend/internal_workers/shared.dart similarity index 100% rename from lib/src/backend/impls/objectbox/backend/internal_workers/shared.dart rename to lib/src/backend/impls/objectbox/native/backend/internal_workers/shared.dart diff --git a/lib/src/backend/impls/objectbox/backend/internal_workers/standard/cmd_type.dart b/lib/src/backend/impls/objectbox/native/backend/internal_workers/standard/cmd_type.dart similarity index 100% rename from lib/src/backend/impls/objectbox/backend/internal_workers/standard/cmd_type.dart rename to lib/src/backend/impls/objectbox/native/backend/internal_workers/standard/cmd_type.dart diff --git a/lib/src/backend/impls/objectbox/backend/internal_workers/standard/worker.dart b/lib/src/backend/impls/objectbox/native/backend/internal_workers/standard/worker.dart similarity index 100% rename from lib/src/backend/impls/objectbox/backend/internal_workers/standard/worker.dart rename to lib/src/backend/impls/objectbox/native/backend/internal_workers/standard/worker.dart diff --git a/lib/src/backend/impls/objectbox/backend/internal_workers/thread_safe.dart b/lib/src/backend/impls/objectbox/native/backend/internal_workers/thread_safe.dart similarity index 100% rename from lib/src/backend/impls/objectbox/backend/internal_workers/thread_safe.dart rename to lib/src/backend/impls/objectbox/native/backend/internal_workers/thread_safe.dart diff --git a/lib/src/backend/impls/objectbox/models/generated/objectbox-model.json b/lib/src/backend/impls/objectbox/native/models/generated/objectbox-model.json similarity index 96% rename from lib/src/backend/impls/objectbox/models/generated/objectbox-model.json rename to lib/src/backend/impls/objectbox/native/models/generated/objectbox-model.json index a7397a4e..94774d18 100644 --- a/lib/src/backend/impls/objectbox/models/generated/objectbox-model.json +++ b/lib/src/backend/impls/objectbox/native/models/generated/objectbox-model.json @@ -17,9 +17,9 @@ { "id": "2:2496811483091029921", "name": "refId", + "indexId": "1:1036386105099927432", "type": 6, - "flags": 40, - "indexId": "1:1036386105099927432" + "flags": 40 }, { "id": "3:3612512640999075849", @@ -54,9 +54,9 @@ { "id": "22:2247444187089993412", "name": "regionId", + "indexId": "5:2172676985778936605", "type": 11, "flags": 520, - "indexId": "5:2172676985778936605", "relationTarget": "ObjectBoxRecoveryRegion" } ], @@ -76,9 +76,9 @@ { "id": "2:1060752758288526798", "name": "name", + "indexId": "2:5602852847672696920", "type": 9, - "flags": 2080, - "indexId": "2:5602852847672696920" + "flags": 2080 }, { "id": "3:7375048950056890678", @@ -127,9 +127,9 @@ { "id": "2:4115905667778721807", "name": "url", + "indexId": "3:4361441212367179043", "type": 9, - "flags": 34848, - "indexId": "3:4361441212367179043" + "flags": 34848 }, { "id": "3:7508139234299399524", @@ -139,9 +139,9 @@ { "id": "4:1172878417733380836", "name": "lastModified", + "indexId": "4:4857742396480146668", "type": 10, - "flags": 8, - "indexId": "4:4857742396480146668" + "flags": 8 } ], "relations": [ diff --git a/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart b/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart new file mode 100644 index 00000000..84bd9fd6 --- /dev/null +++ b/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart @@ -0,0 +1,1005 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// This code was generated by ObjectBox. To update it run the generator again +// with `dart run build_runner build`. +// See also https://docs.objectbox.io/getting-started#generate-objectbox-code + +// ignore_for_file: camel_case_types, depend_on_referenced_packages +// coverage:ignore-file + +import 'dart:typed_data'; + +import 'package:flat_buffers/flat_buffers.dart' as fb; +import 'package:objectbox/internal.dart' + as obx_int; // generated code can access "internal" functionality +import 'package:objectbox/objectbox.dart' as obx; +import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; + +import '../../../../../../../src/backend/impls/objectbox/native/models/src/recovery.dart'; +import '../../../../../../../src/backend/impls/objectbox/native/models/src/recovery_region.dart'; +import '../../../../../../../src/backend/impls/objectbox/native/models/src/root.dart'; +import '../../../../../../../src/backend/impls/objectbox/native/models/src/store.dart'; +import '../../../../../../../src/backend/impls/objectbox/native/models/src/tile.dart'; + +export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file + +final _entities = [ + obx_int.ModelEntity( + id: const obx_int.IdUid(1, 5472631385587455945), + name: 'ObjectBoxRecovery', + lastPropertyId: const obx_int.IdUid(22, 2247444187089993412), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 3769282896877713230), + name: 'id', + type: 6, + flags: 1, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 2496811483091029921), + name: 'refId', + type: 6, + flags: 40, + indexId: const obx_int.IdUid(1, 1036386105099927432), + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 3612512640999075849), + name: 'storeName', + type: 9, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 1095455913099058361), + name: 'creationTime', + type: 10, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(5, 1138350672456876624), + name: 'minZoom', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(6, 9040433791555820529), + name: 'maxZoom', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(7, 6819230045021667310), + name: 'startTile', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(8, 8185724925875119436), + name: 'endTile', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(22, 2247444187089993412), + name: 'regionId', + type: 11, + flags: 520, + indexId: const obx_int.IdUid(5, 2172676985778936605), + relationField: 'region', + relationTarget: 'ObjectBoxRecoveryRegion', + ), + ], + relations: [], + backlinks: [], + ), + obx_int.ModelEntity( + id: const obx_int.IdUid(2, 632249766926720928), + name: 'ObjectBoxStore', + lastPropertyId: const obx_int.IdUid(8, 3489822621946254204), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 1672655555406818874), + name: 'id', + type: 6, + flags: 1, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 1060752758288526798), + name: 'name', + type: 9, + flags: 2080, + indexId: const obx_int.IdUid(2, 5602852847672696920), + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 7375048950056890678), + name: 'length', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 7781853256122686511), + name: 'size', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(5, 3183925806131180531), + name: 'hits', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(6, 6484030110235711573), + name: 'misses', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(7, 7028109958959828879), + name: 'metadataJson', + type: 9, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(8, 3489822621946254204), + name: 'maxLength', + type: 6, + flags: 0, + ), + ], + relations: [], + backlinks: [ + obx_int.ModelBacklink( + name: 'tiles', + srcEntity: 'ObjectBoxTile', + srcField: 'stores', + ), + ], + ), + obx_int.ModelEntity( + id: const obx_int.IdUid(3, 8691708694767276679), + name: 'ObjectBoxTile', + lastPropertyId: const obx_int.IdUid(4, 1172878417733380836), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 5356545328183635928), + name: 'id', + type: 6, + flags: 1, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 4115905667778721807), + name: 'url', + type: 9, + flags: 34848, + indexId: const obx_int.IdUid(3, 4361441212367179043), + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 7508139234299399524), + name: 'bytes', + type: 23, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 1172878417733380836), + name: 'lastModified', + type: 10, + flags: 8, + indexId: const obx_int.IdUid(4, 4857742396480146668), + ), + ], + relations: [ + obx_int.ModelRelation( + id: const obx_int.IdUid(1, 7496298295217061586), + name: 'stores', + targetId: const obx_int.IdUid(2, 632249766926720928), + ), + ], + backlinks: [], + ), + obx_int.ModelEntity( + id: const obx_int.IdUid(4, 8718814737097934474), + name: 'ObjectBoxRoot', + lastPropertyId: const obx_int.IdUid(3, 6574336219794969200), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 3527394784453371799), + name: 'id', + type: 6, + flags: 1, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 2833017356902860570), + name: 'length', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 6574336219794969200), + name: 'size', + type: 6, + flags: 0, + ), + ], + relations: [], + backlinks: [], + ), + obx_int.ModelEntity( + id: const obx_int.IdUid(5, 5692106664767803360), + name: 'ObjectBoxRecoveryRegion', + lastPropertyId: const obx_int.IdUid(14, 2380085283533950474), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 4629353002259573678), + name: 'id', + type: 6, + flags: 1, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 1116094237557270575), + name: 'typeId', + type: 6, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 8476920990388836149), + name: 'rectNwLat', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 3015129163086269263), + name: 'rectNwLng', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(5, 8302525711584098439), + name: 'rectSeLat', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(6, 1939082009138163489), + name: 'rectSeLng', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(7, 5260761364748928203), + name: 'circleCenterLat', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(8, 3329863004721648966), + name: 'circleCenterLng', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(9, 8471244801699851283), + name: 'circleRadius', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(10, 5745879403192313286), + name: 'lineLats', + type: 29, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(11, 4679809662196927204), + name: 'lineLngs', + type: 29, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(12, 8730805542251345960), + name: 'lineRadius', + type: 8, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(13, 1607230668161719129), + name: 'customPolygonLats', + type: 29, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(14, 2380085283533950474), + name: 'customPolygonLngs', + type: 29, + flags: 0, + ), + ], + relations: [ + obx_int.ModelRelation( + id: const obx_int.IdUid(2, 6378075033578405480), + name: 'multiLinkedRegions', + targetId: const obx_int.IdUid(5, 5692106664767803360), + ), + ], + backlinks: [], + ), +]; + +/// Shortcut for [obx.Store.new] that passes [getObjectBoxModel] and for Flutter +/// apps by default a [directory] using `defaultStoreDirectory()` from the +/// ObjectBox Flutter library. +/// +/// Note: for desktop apps it is recommended to specify a unique [directory]. +/// +/// See [obx.Store.new] for an explanation of all parameters. +/// +/// For Flutter apps, also calls `loadObjectBoxLibraryAndroidCompat()` from +/// the ObjectBox Flutter library to fix loading the native ObjectBox library +/// on Android 6 and older. +Future openStore({ + String? directory, + int? maxDBSizeInKB, + int? maxDataSizeInKB, + int? fileMode, + int? maxReaders, + bool queriesCaseSensitiveDefault = true, + String? macosApplicationGroup, +}) async { + await loadObjectBoxLibraryAndroidCompat(); + return obx.Store( + getObjectBoxModel(), + directory: directory ?? (await defaultStoreDirectory()).path, + maxDBSizeInKB: maxDBSizeInKB, + maxDataSizeInKB: maxDataSizeInKB, + fileMode: fileMode, + maxReaders: maxReaders, + queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, + macosApplicationGroup: macosApplicationGroup, + ); +} + +/// Returns the ObjectBox model definition for this project for use with +/// [obx.Store.new]. +obx_int.ModelDefinition getObjectBoxModel() { + final model = obx_int.ModelInfo( + // If this version is not found, it means that this file was generated + // with an older version of the ObjectBox Dart generator. + // Please regenerate this file with the current generator version. + // Typically, this is done with `dart run build_runner build`. + generatorVersion: obx_int.GeneratorVersion.v2025_12_16, + entities: _entities, + lastEntityId: const obx_int.IdUid(5, 5692106664767803360), + lastIndexId: const obx_int.IdUid(5, 2172676985778936605), + lastRelationId: const obx_int.IdUid(2, 6378075033578405480), + lastSequenceId: const obx_int.IdUid(0, 0), + retiredEntityUids: const [], + retiredIndexUids: const [], + retiredPropertyUids: const [ + 7217406424708558740, + 5971465387225017460, + 6703340231106164623, + 741105584939284321, + 2939837278126242427, + 2393337671661697697, + 8055510540122966413, + 9110709438555760246, + 8363656194353400366, + 7008680868853575786, + 7670007285707179405, + 490933261424375687, + 3590067577930145922, + ], + retiredRelationUids: const [], + modelVersion: 5, + modelVersionParserMinimum: 5, + version: 1, + ); + + final bindings = { + ObjectBoxRecovery: obx_int.EntityDefinition( + model: _entities[0], + toOneRelations: (ObjectBoxRecovery object) => [object.region], + toManyRelations: (ObjectBoxRecovery object) => {}, + getId: (ObjectBoxRecovery object) => object.id, + setId: (ObjectBoxRecovery object, int id) { + object.id = id; + }, + objectToFB: (ObjectBoxRecovery object, fb.Builder fbb) { + final storeNameOffset = fbb.writeString(object.storeName); + fbb.startTable(23); + fbb.addInt64(0, object.id); + fbb.addInt64(1, object.refId); + fbb.addOffset(2, storeNameOffset); + fbb.addInt64(3, object.creationTime.millisecondsSinceEpoch); + fbb.addInt64(4, object.minZoom); + fbb.addInt64(5, object.maxZoom); + fbb.addInt64(6, object.startTile); + fbb.addInt64(7, object.endTile); + fbb.addInt64(21, object.region.targetId); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final refIdParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 6, + 0, + ); + final storeNameParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 8, ''); + final creationTimeParam = DateTime.fromMillisecondsSinceEpoch( + const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0), + ); + final minZoomParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 12, + 0, + ); + final maxZoomParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 14, + 0, + ); + final startTileParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 16, + 0, + ); + final endTileParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 18, + 0, + ); + final regionParam = obx.ToOne( + targetId: const fb.Int64Reader().vTableGet(buffer, rootOffset, 46, 0), + ); + final object = ObjectBoxRecovery( + refId: refIdParam, + storeName: storeNameParam, + creationTime: creationTimeParam, + minZoom: minZoomParam, + maxZoom: maxZoomParam, + startTile: startTileParam, + endTile: endTileParam, + region: regionParam, + )..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + object.region.attach(store); + return object; + }, + ), + ObjectBoxStore: obx_int.EntityDefinition( + model: _entities[1], + toOneRelations: (ObjectBoxStore object) => [], + toManyRelations: (ObjectBoxStore object) => { + obx_int.RelInfo.toManyBacklink(1, object.id): + object.tiles, + }, + getId: (ObjectBoxStore object) => object.id, + setId: (ObjectBoxStore object, int id) { + object.id = id; + }, + objectToFB: (ObjectBoxStore object, fb.Builder fbb) { + final nameOffset = fbb.writeString(object.name); + final metadataJsonOffset = fbb.writeString(object.metadataJson); + fbb.startTable(9); + fbb.addInt64(0, object.id); + fbb.addOffset(1, nameOffset); + fbb.addInt64(2, object.length); + fbb.addInt64(3, object.size); + fbb.addInt64(4, object.hits); + fbb.addInt64(5, object.misses); + fbb.addOffset(6, metadataJsonOffset); + fbb.addInt64(7, object.maxLength); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final nameParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 6, ''); + final maxLengthParam = const fb.Int64Reader().vTableGetNullable( + buffer, + rootOffset, + 18, + ); + final lengthParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 8, + 0, + ); + final sizeParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 10, + 0, + ); + final hitsParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 12, + 0, + ); + final missesParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 14, + 0, + ); + final metadataJsonParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 16, ''); + final object = ObjectBoxStore( + name: nameParam, + maxLength: maxLengthParam, + length: lengthParam, + size: sizeParam, + hits: hitsParam, + misses: missesParam, + metadataJson: metadataJsonParam, + )..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + obx_int.InternalToManyAccess.setRelInfo( + object.tiles, + store, + obx_int.RelInfo.toManyBacklink(1, object.id), + ); + return object; + }, + ), + ObjectBoxTile: obx_int.EntityDefinition( + model: _entities[2], + toOneRelations: (ObjectBoxTile object) => [], + toManyRelations: (ObjectBoxTile object) => { + obx_int.RelInfo.toMany(1, object.id): object.stores, + }, + getId: (ObjectBoxTile object) => object.id, + setId: (ObjectBoxTile object, int id) { + object.id = id; + }, + objectToFB: (ObjectBoxTile object, fb.Builder fbb) { + final urlOffset = fbb.writeString(object.url); + final bytesOffset = fbb.writeListInt8(object.bytes); + fbb.startTable(5); + fbb.addInt64(0, object.id); + fbb.addOffset(1, urlOffset); + fbb.addOffset(2, bytesOffset); + fbb.addInt64(3, object.lastModified.millisecondsSinceEpoch); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final urlParam = const fb.StringReader( + asciiOptimization: true, + ).vTableGet(buffer, rootOffset, 6, ''); + final bytesParam = + const fb.Uint8ListReader( + lazy: false, + ).vTableGet(buffer, rootOffset, 8, Uint8List(0)) + as Uint8List; + final lastModifiedParam = DateTime.fromMillisecondsSinceEpoch( + const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0), + ); + final object = ObjectBoxTile( + url: urlParam, + bytes: bytesParam, + lastModified: lastModifiedParam, + )..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + obx_int.InternalToManyAccess.setRelInfo( + object.stores, + store, + obx_int.RelInfo.toMany(1, object.id), + ); + return object; + }, + ), + ObjectBoxRoot: obx_int.EntityDefinition( + model: _entities[3], + toOneRelations: (ObjectBoxRoot object) => [], + toManyRelations: (ObjectBoxRoot object) => {}, + getId: (ObjectBoxRoot object) => object.id, + setId: (ObjectBoxRoot object, int id) { + object.id = id; + }, + objectToFB: (ObjectBoxRoot object, fb.Builder fbb) { + fbb.startTable(4); + fbb.addInt64(0, object.id); + fbb.addInt64(1, object.length); + fbb.addInt64(2, object.size); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final lengthParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 6, + 0, + ); + final sizeParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 8, + 0, + ); + final object = ObjectBoxRoot(length: lengthParam, size: sizeParam) + ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + + return object; + }, + ), + ObjectBoxRecoveryRegion: obx_int.EntityDefinition( + model: _entities[4], + toOneRelations: (ObjectBoxRecoveryRegion object) => [], + toManyRelations: (ObjectBoxRecoveryRegion object) => { + obx_int.RelInfo.toMany(2, object.id): + object.multiLinkedRegions, + }, + getId: (ObjectBoxRecoveryRegion object) => object.id, + setId: (ObjectBoxRecoveryRegion object, int id) { + object.id = id; + }, + objectToFB: (ObjectBoxRecoveryRegion object, fb.Builder fbb) { + final lineLatsOffset = object.lineLats == null + ? null + : fbb.writeListFloat64(object.lineLats!); + final lineLngsOffset = object.lineLngs == null + ? null + : fbb.writeListFloat64(object.lineLngs!); + final customPolygonLatsOffset = object.customPolygonLats == null + ? null + : fbb.writeListFloat64(object.customPolygonLats!); + final customPolygonLngsOffset = object.customPolygonLngs == null + ? null + : fbb.writeListFloat64(object.customPolygonLngs!); + fbb.startTable(15); + fbb.addInt64(0, object.id); + fbb.addInt64(1, object.typeId); + fbb.addFloat64(2, object.rectNwLat); + fbb.addFloat64(3, object.rectNwLng); + fbb.addFloat64(4, object.rectSeLat); + fbb.addFloat64(5, object.rectSeLng); + fbb.addFloat64(6, object.circleCenterLat); + fbb.addFloat64(7, object.circleCenterLng); + fbb.addFloat64(8, object.circleRadius); + fbb.addOffset(9, lineLatsOffset); + fbb.addOffset(10, lineLngsOffset); + fbb.addFloat64(11, object.lineRadius); + fbb.addOffset(12, customPolygonLatsOffset); + fbb.addOffset(13, customPolygonLngsOffset); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final typeIdParam = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 6, + 0, + ); + final rectNwLatParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 8, + ); + final rectNwLngParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 10, + ); + final rectSeLatParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 12, + ); + final rectSeLngParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 14, + ); + final circleCenterLatParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 16, + ); + final circleCenterLngParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 18, + ); + final circleRadiusParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 20, + ); + final lineLatsParam = const fb.ListReader( + fb.Float64Reader(), + lazy: false, + ).vTableGetNullable(buffer, rootOffset, 22); + final lineLngsParam = const fb.ListReader( + fb.Float64Reader(), + lazy: false, + ).vTableGetNullable(buffer, rootOffset, 24); + final lineRadiusParam = const fb.Float64Reader().vTableGetNullable( + buffer, + rootOffset, + 26, + ); + final customPolygonLatsParam = const fb.ListReader( + fb.Float64Reader(), + lazy: false, + ).vTableGetNullable(buffer, rootOffset, 28); + final customPolygonLngsParam = const fb.ListReader( + fb.Float64Reader(), + lazy: false, + ).vTableGetNullable(buffer, rootOffset, 30); + final multiLinkedRegionsParam = obx.ToMany(); + final object = ObjectBoxRecoveryRegion( + typeId: typeIdParam, + rectNwLat: rectNwLatParam, + rectNwLng: rectNwLngParam, + rectSeLat: rectSeLatParam, + rectSeLng: rectSeLngParam, + circleCenterLat: circleCenterLatParam, + circleCenterLng: circleCenterLngParam, + circleRadius: circleRadiusParam, + lineLats: lineLatsParam, + lineLngs: lineLngsParam, + lineRadius: lineRadiusParam, + customPolygonLats: customPolygonLatsParam, + customPolygonLngs: customPolygonLngsParam, + multiLinkedRegions: multiLinkedRegionsParam, + )..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + obx_int.InternalToManyAccess.setRelInfo( + object.multiLinkedRegions, + store, + obx_int.RelInfo.toMany(2, object.id), + ); + return object; + }, + ), + }; + + return obx_int.ModelDefinition(model, bindings); +} + +/// [ObjectBoxRecovery] entity fields to define ObjectBox queries. +class ObjectBoxRecovery_ { + /// See [ObjectBoxRecovery.id]. + static final id = obx.QueryIntegerProperty( + _entities[0].properties[0], + ); + + /// See [ObjectBoxRecovery.refId]. + static final refId = obx.QueryIntegerProperty( + _entities[0].properties[1], + ); + + /// See [ObjectBoxRecovery.storeName]. + static final storeName = obx.QueryStringProperty( + _entities[0].properties[2], + ); + + /// See [ObjectBoxRecovery.creationTime]. + static final creationTime = obx.QueryDateProperty( + _entities[0].properties[3], + ); + + /// See [ObjectBoxRecovery.minZoom]. + static final minZoom = obx.QueryIntegerProperty( + _entities[0].properties[4], + ); + + /// See [ObjectBoxRecovery.maxZoom]. + static final maxZoom = obx.QueryIntegerProperty( + _entities[0].properties[5], + ); + + /// See [ObjectBoxRecovery.startTile]. + static final startTile = obx.QueryIntegerProperty( + _entities[0].properties[6], + ); + + /// See [ObjectBoxRecovery.endTile]. + static final endTile = obx.QueryIntegerProperty( + _entities[0].properties[7], + ); + + /// See [ObjectBoxRecovery.region]. + static final region = + obx.QueryRelationToOne( + _entities[0].properties[8], + ); +} + +/// [ObjectBoxStore] entity fields to define ObjectBox queries. +class ObjectBoxStore_ { + /// See [ObjectBoxStore.id]. + static final id = obx.QueryIntegerProperty( + _entities[1].properties[0], + ); + + /// See [ObjectBoxStore.name]. + static final name = obx.QueryStringProperty( + _entities[1].properties[1], + ); + + /// See [ObjectBoxStore.length]. + static final length = obx.QueryIntegerProperty( + _entities[1].properties[2], + ); + + /// See [ObjectBoxStore.size]. + static final size = obx.QueryIntegerProperty( + _entities[1].properties[3], + ); + + /// See [ObjectBoxStore.hits]. + static final hits = obx.QueryIntegerProperty( + _entities[1].properties[4], + ); + + /// See [ObjectBoxStore.misses]. + static final misses = obx.QueryIntegerProperty( + _entities[1].properties[5], + ); + + /// See [ObjectBoxStore.metadataJson]. + static final metadataJson = obx.QueryStringProperty( + _entities[1].properties[6], + ); + + /// See [ObjectBoxStore.maxLength]. + static final maxLength = obx.QueryIntegerProperty( + _entities[1].properties[7], + ); +} + +/// [ObjectBoxTile] entity fields to define ObjectBox queries. +class ObjectBoxTile_ { + /// See [ObjectBoxTile.id]. + static final id = obx.QueryIntegerProperty( + _entities[2].properties[0], + ); + + /// See [ObjectBoxTile.url]. + static final url = obx.QueryStringProperty( + _entities[2].properties[1], + ); + + /// See [ObjectBoxTile.bytes]. + static final bytes = obx.QueryByteVectorProperty( + _entities[2].properties[2], + ); + + /// See [ObjectBoxTile.lastModified]. + static final lastModified = obx.QueryDateProperty( + _entities[2].properties[3], + ); + + /// see [ObjectBoxTile.stores] + static final stores = obx.QueryRelationToMany( + _entities[2].relations[0], + ); +} + +/// [ObjectBoxRoot] entity fields to define ObjectBox queries. +class ObjectBoxRoot_ { + /// See [ObjectBoxRoot.id]. + static final id = obx.QueryIntegerProperty( + _entities[3].properties[0], + ); + + /// See [ObjectBoxRoot.length]. + static final length = obx.QueryIntegerProperty( + _entities[3].properties[1], + ); + + /// See [ObjectBoxRoot.size]. + static final size = obx.QueryIntegerProperty( + _entities[3].properties[2], + ); +} + +/// [ObjectBoxRecoveryRegion] entity fields to define ObjectBox queries. +class ObjectBoxRecoveryRegion_ { + /// See [ObjectBoxRecoveryRegion.id]. + static final id = obx.QueryIntegerProperty( + _entities[4].properties[0], + ); + + /// See [ObjectBoxRecoveryRegion.typeId]. + static final typeId = obx.QueryIntegerProperty( + _entities[4].properties[1], + ); + + /// See [ObjectBoxRecoveryRegion.rectNwLat]. + static final rectNwLat = obx.QueryDoubleProperty( + _entities[4].properties[2], + ); + + /// See [ObjectBoxRecoveryRegion.rectNwLng]. + static final rectNwLng = obx.QueryDoubleProperty( + _entities[4].properties[3], + ); + + /// See [ObjectBoxRecoveryRegion.rectSeLat]. + static final rectSeLat = obx.QueryDoubleProperty( + _entities[4].properties[4], + ); + + /// See [ObjectBoxRecoveryRegion.rectSeLng]. + static final rectSeLng = obx.QueryDoubleProperty( + _entities[4].properties[5], + ); + + /// See [ObjectBoxRecoveryRegion.circleCenterLat]. + static final circleCenterLat = + obx.QueryDoubleProperty( + _entities[4].properties[6], + ); + + /// See [ObjectBoxRecoveryRegion.circleCenterLng]. + static final circleCenterLng = + obx.QueryDoubleProperty( + _entities[4].properties[7], + ); + + /// See [ObjectBoxRecoveryRegion.circleRadius]. + static final circleRadius = obx.QueryDoubleProperty( + _entities[4].properties[8], + ); + + /// See [ObjectBoxRecoveryRegion.lineLats]. + static final lineLats = + obx.QueryDoubleVectorProperty( + _entities[4].properties[9], + ); + + /// See [ObjectBoxRecoveryRegion.lineLngs]. + static final lineLngs = + obx.QueryDoubleVectorProperty( + _entities[4].properties[10], + ); + + /// See [ObjectBoxRecoveryRegion.lineRadius]. + static final lineRadius = obx.QueryDoubleProperty( + _entities[4].properties[11], + ); + + /// See [ObjectBoxRecoveryRegion.customPolygonLats]. + static final customPolygonLats = + obx.QueryDoubleVectorProperty( + _entities[4].properties[12], + ); + + /// See [ObjectBoxRecoveryRegion.customPolygonLngs]. + static final customPolygonLngs = + obx.QueryDoubleVectorProperty( + _entities[4].properties[13], + ); + + /// see [ObjectBoxRecoveryRegion.multiLinkedRegions] + static final multiLinkedRegions = + obx.QueryRelationToMany( + _entities[4].relations[0], + ); +} diff --git a/lib/src/backend/impls/objectbox/models/src/recovery.dart b/lib/src/backend/impls/objectbox/native/models/src/recovery.dart similarity index 97% rename from lib/src/backend/impls/objectbox/models/src/recovery.dart rename to lib/src/backend/impls/objectbox/native/models/src/recovery.dart index 83c8486a..a9a85cd1 100644 --- a/lib/src/backend/impls/objectbox/models/src/recovery.dart +++ b/lib/src/backend/impls/objectbox/native/models/src/recovery.dart @@ -4,7 +4,7 @@ import 'package:meta/meta.dart'; import 'package:objectbox/objectbox.dart'; -import '../../../../../../flutter_map_tile_caching.dart'; +import '../../../../../../../flutter_map_tile_caching.dart'; import 'recovery_region.dart'; /// Represents a [RecoveredRegion] diff --git a/lib/src/backend/impls/objectbox/models/src/recovery_region.dart b/lib/src/backend/impls/objectbox/native/models/src/recovery_region.dart similarity index 98% rename from lib/src/backend/impls/objectbox/models/src/recovery_region.dart rename to lib/src/backend/impls/objectbox/native/models/src/recovery_region.dart index 73443894..6d1feaf3 100644 --- a/lib/src/backend/impls/objectbox/models/src/recovery_region.dart +++ b/lib/src/backend/impls/objectbox/native/models/src/recovery_region.dart @@ -6,7 +6,7 @@ import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; import 'package:objectbox/objectbox.dart'; -import '../../../../../../flutter_map_tile_caching.dart'; +import '../../../../../../../flutter_map_tile_caching.dart'; /// Serialised [BaseRegion] @Entity() diff --git a/lib/src/backend/impls/objectbox/models/src/root.dart b/lib/src/backend/impls/objectbox/native/models/src/root.dart similarity index 100% rename from lib/src/backend/impls/objectbox/models/src/root.dart rename to lib/src/backend/impls/objectbox/native/models/src/root.dart diff --git a/lib/src/backend/impls/objectbox/models/src/store.dart b/lib/src/backend/impls/objectbox/native/models/src/store.dart similarity index 100% rename from lib/src/backend/impls/objectbox/models/src/store.dart rename to lib/src/backend/impls/objectbox/native/models/src/store.dart diff --git a/lib/src/backend/impls/objectbox/models/src/tile.dart b/lib/src/backend/impls/objectbox/native/models/src/tile.dart similarity index 94% rename from lib/src/backend/impls/objectbox/models/src/tile.dart rename to lib/src/backend/impls/objectbox/native/models/src/tile.dart index 824e46ff..e2f72f84 100644 --- a/lib/src/backend/impls/objectbox/models/src/tile.dart +++ b/lib/src/backend/impls/objectbox/native/models/src/tile.dart @@ -5,7 +5,7 @@ import 'dart:typed_data'; import 'package:objectbox/objectbox.dart'; -import '../../../../interfaces/models.dart'; +import '../../../../../interfaces/models.dart'; import 'store.dart'; /// ObjectBox-specific implementation of [BackendTile] diff --git a/lib/src/backend/impls/web_noop/backend.dart b/lib/src/backend/impls/objectbox/web_noop/backend.dart similarity index 97% rename from lib/src/backend/impls/web_noop/backend.dart rename to lib/src/backend/impls/objectbox/web_noop/backend.dart index 2a70486a..181200b0 100644 --- a/lib/src/backend/impls/web_noop/backend.dart +++ b/lib/src/backend/impls/objectbox/web_noop/backend.dart @@ -4,7 +4,7 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; -import '../../../../flutter_map_tile_caching.dart'; +import '../../../../../flutter_map_tile_caching.dart'; /// Implementation of [FMTCBackend] that uses ObjectBox as the storage database /// diff --git a/pubspec.yaml b/pubspec.yaml index 657345f0..6da9f910 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,27 +27,33 @@ environment: flutter: ">=3.27.0" dependencies: - async: ^2.11.0 - collection: ^1.18.0 + async: ^2.13.0 + collection: ^1.19.1 dart_earcut: ^1.2.0 - flat_buffers: ^23.5.26 + drift: ^2.31.0 + drift_flutter: ^0.2.8 + flat_buffers: ^25.9.23 flutter: sdk: flutter - flutter_map: ^8.1.1 - http: ^1.2.2 + flutter_map: ^8.2.2 + http: ^1.6.0 latlong2: ^0.9.1 - meta: ^1.15.0 - objectbox: ^4.1.0 - objectbox_flutter_libs: ^4.1.0 + meta: ^1.17.0 + objectbox: ^5.2.0 + objectbox_flutter_libs: ^5.2.0 path: ^1.9.1 path_provider: ^2.1.5 + sqlite3: ^2.7.5 dev_dependencies: - build_runner: ^2.4.15 - objectbox_generator: ^4.1.0 - test: ^1.25.15 + build_runner: ^2.11.1 + drift_dev: ^2.31.0 + objectbox_generator: ^5.2.0 + pigeon_generator: ^3.1.1 + test: ^1.29.0 flutter: null objectbox: - output_dir: src/backend/impls/objectbox/models/generated + output_dir: src/backend/impls/objectbox/native/models/generated +