From fe581137264bda1f312ddabe0a52cc78af3b0124 Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Wed, 22 Jan 2025 16:04:59 +0000 Subject: [PATCH 1/8] Reorganised ObjectBox backend into own directory --- lib/src/backend/export_external.dart | 4 ++-- .../impls/objectbox/{ => native}/backend/backend.dart | 6 +++--- .../impls/objectbox/{ => native}/backend/internal.dart | 0 .../{ => native}/backend/internal_workers/shared.dart | 0 .../backend/internal_workers/standard/cmd_type.dart | 0 .../backend/internal_workers/standard/worker.dart | 0 .../backend/internal_workers/thread_safe.dart | 0 .../{ => native}/models/generated/objectbox-model.json | 0 .../{ => native}/models/generated/objectbox.g.dart | 10 +++++----- .../objectbox/{ => native}/models/src/recovery.dart | 2 +- .../{ => native}/models/src/recovery_region.dart | 2 +- .../impls/objectbox/{ => native}/models/src/root.dart | 0 .../impls/objectbox/{ => native}/models/src/store.dart | 0 .../impls/objectbox/{ => native}/models/src/tile.dart | 2 +- .../impls/{ => objectbox}/web_noop/backend.dart | 2 +- pubspec.yaml | 2 +- 16 files changed, 15 insertions(+), 15 deletions(-) rename lib/src/backend/impls/objectbox/{ => native}/backend/backend.dart (95%) rename lib/src/backend/impls/objectbox/{ => native}/backend/internal.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/backend/internal_workers/shared.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/backend/internal_workers/standard/cmd_type.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/backend/internal_workers/standard/worker.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/backend/internal_workers/thread_safe.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/models/generated/objectbox-model.json (100%) rename lib/src/backend/impls/objectbox/{ => native}/models/generated/objectbox.g.dart (98%) rename lib/src/backend/impls/objectbox/{ => native}/models/src/recovery.dart (97%) rename lib/src/backend/impls/objectbox/{ => native}/models/src/recovery_region.dart (98%) rename lib/src/backend/impls/objectbox/{ => native}/models/src/root.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/models/src/store.dart (100%) rename lib/src/backend/impls/objectbox/{ => native}/models/src/tile.dart (94%) rename lib/src/backend/impls/{ => objectbox}/web_noop/backend.dart (97%) diff --git a/lib/src/backend/export_external.dart b/lib/src/backend/export_external.dart index d7fc003a..ff113bc9 100644 --- a/lib/src/backend/export_external.dart +++ b/lib/src/backend/export_external.dart @@ -2,8 +2,8 @@ // 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/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/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 5bb750cc..30b1d451 100644 --- a/lib/src/backend/impls/objectbox/backend/backend.dart +++ b/lib/src/backend/impls/objectbox/native/backend/backend.dart @@ -15,9 +15,9 @@ import 'package:meta/meta.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'; 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 100% 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 diff --git a/lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart b/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart similarity index 98% rename from lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart rename to lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart index 9966f6a1..3cdb2f66 100644 --- a/lib/src/backend/impls/objectbox/models/generated/objectbox.g.dart +++ b/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart @@ -14,11 +14,11 @@ import 'package:objectbox/internal.dart' 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'; +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 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 199e1fc6..13782793 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,4 +50,4 @@ dev_dependencies: flutter: null objectbox: - output_dir: src/backend/impls/objectbox/models/generated + output_dir: src/backend/impls/objectbox/native/models/generated From 32a1770c58ff50cfed025ea5e1aca7f7d2dd1433 Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Thu, 23 Jan 2025 20:31:06 +0000 Subject: [PATCH 2/8] Implemented base Drift schemas --- .vscode/settings.json | 9 + build.yaml | 13 + example/android/app/build.gradle.kts | 1 + .../impls/drift/native/backend/backend.dart | 90 ++ .../impls/drift/native/backend/internal.dart | 690 +++++++++ .../impls/drift/native/database/database.dart | 33 + .../drift/native/database/database.drift.dart | 62 + .../native/database/models/recovery.dart | 21 + .../database/models/recovery.drift.dart | 714 ++++++++++ .../database/models/recovery_region.dart | 30 + .../models/recovery_region.drift.dart | 1240 +++++++++++++++++ .../drift/native/database/models/root.dart | 15 + .../native/database/models/root.drift.dart | 369 +++++ .../drift/native/database/models/store.dart | 20 + .../native/database/models/store.drift.dart | 594 ++++++++ .../native/database/models/store_tile.dart | 18 + .../database/models/store_tile.drift.dart | 546 ++++++++ .../drift/native/database/models/tile.dart | 14 + .../native/database/models/tile.drift.dart | 400 ++++++ pubspec.yaml | 4 + 20 files changed, 4883 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 build.yaml create mode 100644 lib/src/backend/impls/drift/native/backend/backend.dart create mode 100644 lib/src/backend/impls/drift/native/backend/internal.dart create mode 100644 lib/src/backend/impls/drift/native/database/database.dart create mode 100644 lib/src/backend/impls/drift/native/database/database.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/recovery.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/recovery.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/recovery_region.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/recovery_region.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/root.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/root.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/store.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/store.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/store_tile.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/store_tile.drift.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/tile.dart create mode 100644 lib/src/backend/impls/drift/native/database/models/tile.drift.dart diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6121fc08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "yaml.schemas": { + "https://json.schemastore.org/dart-build": [ + "build.yaml", + "*.build.yaml", + "build.*.yaml" + ] + } +} \ 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/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..12566362 --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/backend.dart @@ -0,0 +1,90 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.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 '../models/generated/objectbox.g.dart'; +import '../models/src/recovery.dart'; +import '../models/src/recovery_region.dart'; +import '../models/src/root.dart'; +import '../database/models/store.dart'; +import '../models/src/tile.dart'; + +export 'package:objectbox/objectbox.dart' show StorageException; + +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 +/// +/// On web, this redirects to a no-op implementation that throws +/// [UnsupportedError]s when attempting to use [initialise] or [uninitialise], +/// and [RootUnavailable] when trying to use any other method. +final class FMTCObjectBoxBackend implements FMTCBackend { + /// {@macro fmtc.backend.initialise} + /// + /// --- + /// + /// [maxDatabaseSize] is the maximum size the database file can grow + /// to, in KB. Exceeding it throws [DbFullException] (from + /// 'package:objectbox') on write operations. Defaults to 10 GB (10000000 KB). + /// + /// [macosApplicationGroup] should be set when creating a sandboxed macOS app, + /// specify the application group (of less than 20 chars). See + /// [the ObjectBox docs](https://docs.objectbox.io/getting-started) for + /// details. + /// + /// [rootIsolateToken] should only be used in exceptional circumstances where + /// this backend is being initialised in a seperate isolate (or background) + /// thread. + /// + /// Avoid using [useInMemoryDatabase] outside of testing purposes. + @override + Future initialise({ + String? rootDirectory, + int maxDatabaseSize = 10000000, + String? macosApplicationGroup, + RootIsolateToken? rootIsolateToken, + @visibleForTesting bool useInMemoryDatabase = false, + }) => + FMTCObjectBoxBackendInternal._instance.initialise( + rootDirectory: rootDirectory, + maxDatabaseSize: maxDatabaseSize, + macosApplicationGroup: macosApplicationGroup, + useInMemoryDatabase: useInMemoryDatabase, + rootIsolateToken: rootIsolateToken, + ); + + /// {@macro fmtc.backend.uninitialise} + /// + /// If [immediate] is `true`, any operations currently underway will be lost, + /// as the worker will be killed as quickly as possible (not necessarily + /// instantly). + /// If `false`, all operations currently underway will be allowed to complete, + /// but any operations started after this method call will be lost. + @override + Future uninitialise({ + bool deleteRoot = false, + bool immediate = false, + }) => + FMTCObjectBoxBackendInternal._instance + .uninitialise(deleteRoot: deleteRoot, immediate: immediate); +} 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..b6e63090 --- /dev/null +++ b/lib/src/backend/impls/drift/native/backend/internal.dart @@ -0,0 +1,690 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +part of 'backend.dart'; + +/// Internal implementation of [FMTCBackend] that uses ObjectBox as the storage +/// database +/// +/// Actual implementation performed by `_worker` via `_ObjectBoxBackendImpl`. +abstract interface class FMTCObjectBoxBackendInternal + implements FMTCBackendInternal { + static final _instance = _ObjectBoxBackendImpl._(); +} + +class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { + _ObjectBoxBackendImpl._(); + + @override + String get friendlyIdentifier => 'ObjectBox'; + + void get expectInitialised => _sendPort ?? (throw RootUnavailable()); + + late String rootDirectory; + + // Worker communication protocol storage + + SendPort? _sendPort; + final _workerResOneShot = ?>>{}; + final _workerResStreamed = ?>>{}; + int _workerId = smallestInt; + late Completer _workerComplete; + late StreamSubscription _workerHandler; + + // `removeOldestTilesAboveLimit` tracking & debouncing + + Timer? _rotalDebouncer; + int? _rotalStoresHash; + Completer>? _rotalResultCompleter; + + // Define communicators + + Future?> _sendCmdOneShot({ + required _CmdType type, + Map args = const {}, + }) async { + expectInitialised; + + final id = ++_workerId; // Create new unique ID + _workerResOneShot[id] = Completer(); // Will be completed by direct handler + _sendPort!.send((id: id, type: type, args: args)); // Send cmd + + try { + return await _workerResOneShot[id]!.future; // Await response + } catch (err, stackTrace) { + Error.throwWithStackTrace( + err, + StackTrace.fromString( + '$stackTrace\n${StackTrace.current}' + '#+ [FMTC Debug Info] $type: $args\n', + ), + ); + } finally { + _workerResOneShot.remove(id); // Free memory + } + } + + Stream?> _sendCmdStreamed({ + required _CmdType type, + Map args = const {}, + }) async* { + expectInitialised; + + final id = ++_workerId; // Create new unique ID + final controller = StreamController?>( + onCancel: () async { + _workerResStreamed.remove(id); // Free memory + // Cancel the worker stream if the worker is alive + if ((type.hasInternalStreamSub ?? false) && + !_workerComplete.isCompleted) { + await _sendCmdOneShot( + type: _CmdType.cancelInternalStreamSub, + args: {'id': id}, + ); + } + }, + ); + _workerResStreamed[id] = + controller.sink; // Will be inserted into by direct handler + _sendPort!.send((id: id, type: type, args: args)); // Send cmd + + // Efficienctly forward resulting stream, but add extra debug info to any + // errors + yield* controller.stream.handleError( + (err, stackTrace) => Error.throwWithStackTrace( + err, + StackTrace.fromString( + '$stackTrace\n#+ [FMTC Debug Info] ' + ' Unable to attach final `StackTrace` when streaming results\n' + '\n#+ [FMTC Debug Info] ' + '$type: $args\n', + ), + ), + ); + + // Goto `onCancel` once output listening cancelled + await controller.close(); + } + + // Lifecycle implementations + + Future initialise({ + required String? rootDirectory, + required int maxDatabaseSize, + required String? macosApplicationGroup, + required bool useInMemoryDatabase, + required RootIsolateToken? rootIsolateToken, + }) async { + if (_sendPort != null) throw RootAlreadyInitialised(); + + // Obtain the `RootIsolateToken` to enable the worker isolate to use + // ObjectBox + rootIsolateToken ??= ServicesBinding.rootIsolateToken ?? + (throw StateError( + 'Unable to start FMTC in a background thread without access to a ' + '`RootIsolateToken`', + )); + + // Construct the root directory path + if (useInMemoryDatabase) { + this.rootDirectory = Store.inMemoryPrefix + (rootDirectory ?? 'fmtc'); + } else { + await Directory( + this.rootDirectory = path.join( + rootDirectory ?? + (await getApplicationDocumentsDirectory()).absolute.path, + 'fmtc', + ), + ).create(recursive: true); + } + + // Prepare to recieve `SendPort` from worker + _workerResOneShot[0] = Completer(); + final workerInitialRes = _workerResOneShot[0]! + .future // Completed directly by handler below + .then<({Object err, StackTrace stackTrace})?>( + (res) { + _workerResOneShot.remove(0); + _sendPort = res!['sendPort']; + return null; + }, + onError: (err, stackTrace) { + _workerHandler.cancel(); + _workerComplete.complete(); + + _workerId = 0; + _workerResOneShot.clear(); + _workerResStreamed.clear(); + + return (err: err, stackTrace: stackTrace); + }, + ); + + // Setup worker comms/response handler + final receivePort = ReceivePort(); + _workerComplete = Completer(); + _workerHandler = receivePort.listen( + (evt) { + evt as ({int id, Map? data})?; + + // Killed forcefully by environment (eg. hot restart) + if (evt == null) { + _workerHandler.cancel(); // Ensure this handler is cancelled on return + _workerComplete.complete(); + // Doesn't require full cleanup, because hot restart has done that + return; + } + + final isStreamedResult = evt.data?['expectStream'] == true; + + // Handle errors + if (evt.data?['error'] case final err?) { + final stackTrace = evt.data!['stackTrace']; + if (isStreamedResult) { + _workerResStreamed[evt.id]?.addError(err, stackTrace); + } else { + _workerResOneShot[evt.id]!.completeError(err, stackTrace); + } + return; + } + + if (isStreamedResult) { + // May be `null` if cmd was streamed result, but has no way to prevent + // future results even after the listener has stopped + // + // See `_WorkerCmdType.hasInternalStreamSub` for info. + _workerResStreamed[evt.id]?.add(evt.data); + } else { + _workerResOneShot[evt.id]!.complete(evt.data); + } + }, + onDone: () => _workerComplete.complete(), + ); + + // Spawn worker isolate + try { + await Isolate.spawn( + _worker, + ( + sendPort: receivePort.sendPort, + rootDirectory: this.rootDirectory, + maxDatabaseSize: maxDatabaseSize, + macosApplicationGroup: macosApplicationGroup, + rootIsolateToken: rootIsolateToken, + ), + onExit: receivePort.sendPort, + debugName: '[FMTC] ObjectBox Backend Worker', + ); + } catch (e) { + receivePort.close(); + _sendPort = null; + rethrow; + } + + // Check whether initialisation was successful after initial response + if (await workerInitialRes case (:final err, :final stackTrace)) { + Error.throwWithStackTrace(err, stackTrace); + } + + FMTCBackendAccess.internal = this; + FMTCBackendAccessThreadSafe.internal = + _ObjectBoxBackendThreadSafeImpl._(rootDirectory: this.rootDirectory); + } + + Future uninitialise({ + required bool deleteRoot, + required bool immediate, + }) async { + expectInitialised; + + // Wait for all currently underway operations to complete before destroying + // the isolate (if not `immediate`) + if (!immediate) { + await Future.wait(_workerResOneShot.values.map((e) => e.future)); + } + + // Send self-destruct cmd to worker, and wait for response and exit + await _sendCmdOneShot( + type: _CmdType.destroy, + args: {'deleteRoot': deleteRoot}, + ); + await _workerComplete.future; + + // Destroy remaining worker refs + _sendPort = null; // Indicate ready for re-init + await _workerHandler.cancel(); // Stop response handler + + // Kill any remaining operations with an error (they'll never recieve a + // response from the worker) + for (final completer in _workerResOneShot.values) { + completer.complete({'error': RootUnavailable()}); + } + for (final streamController in List.of(_workerResStreamed.values)) { + await streamController.close(); + } + + // Reset state + _workerId = 0; + _workerResOneShot.clear(); + _workerResStreamed.clear(); + _rotalDebouncer?.cancel(); + _rotalDebouncer = null; + _rotalStoresHash = null; + _rotalResultCompleter?.completeError(RootUnavailable()); + _rotalResultCompleter = null; + + FMTCBackendAccess.internal = null; + FMTCBackendAccessThreadSafe.internal = null; + } + + // Implementation & worker connectors + + @override + Future realSize() async => + (await _sendCmdOneShot(type: _CmdType.realSize))!['size']; + + @override + Future rootSize() async => + (await _sendCmdOneShot(type: _CmdType.rootSize))!['size']; + + @override + Future rootLength() async => + (await _sendCmdOneShot(type: _CmdType.rootLength))!['length']; + + @override + Future> listStores() async => + (await _sendCmdOneShot(type: _CmdType.listStores))!['stores']; + + @override + Future storeGetMaxLength({ + required String storeName, + }) async => + (await _sendCmdOneShot( + type: _CmdType.storeGetMaxLength, + args: {'storeName': storeName}, + ))!['maxLength']; + + @override + Future storeSetMaxLength({ + required String storeName, + required int? newMaxLength, + }) => + _sendCmdOneShot( + type: _CmdType.storeSetMaxLength, + args: {'storeName': storeName, 'newMaxLength': newMaxLength}, + ); + + @override + Future storeExists({ + required String storeName, + }) async => + (await _sendCmdOneShot( + type: _CmdType.storeExists, + args: {'storeName': storeName}, + ))!['exists']; + + @override + Future createStore({ + required String storeName, + required int? maxLength, + }) => + _sendCmdOneShot( + type: _CmdType.createStore, + args: {'storeName': storeName, 'maxLength': maxLength}, + ); + + @override + Future resetStore({ + required String storeName, + }) => + _sendCmdOneShot( + type: _CmdType.resetStore, + args: {'storeName': storeName}, + ); + + @override + Future renameStore({ + required String currentStoreName, + required String newStoreName, + }) => + _sendCmdOneShot( + type: _CmdType.renameStore, + args: { + 'currentStoreName': currentStoreName, + 'newStoreName': newStoreName, + }, + ); + + @override + Future deleteStore({ + required String storeName, + }) => + _sendCmdOneShot( + type: _CmdType.deleteStore, + args: {'storeName': storeName}, + ); + + @override + Future<({double size, int length, int hits, int misses})> getStoreStats({ + required String storeName, + }) async => + (await _sendCmdOneShot( + type: _CmdType.getStoreStats, + args: {'storeName': storeName}, + ))!['stats']; + + @override + Future tileExists({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async => + (await _sendCmdOneShot( + type: _CmdType.tileExists, + args: {'url': url, 'storeNames': storeNames}, + ))!['exists']; + + @override + Future< + ({ + BackendTile? tile, + List intersectedStoreNames, + List allStoreNames, + })> readTile({ + required String url, + required ({bool includeOrExclude, List storeNames}) storeNames, + }) async { + final res = (await _sendCmdOneShot( + type: _CmdType.readTile, + args: {'url': url, 'storeNames': storeNames}, + ))!; + return ( + tile: res['tile'] as BackendTile?, + intersectedStoreNames: res['intersectedStoreNames'] as List, + allStoreNames: res['allStoreNames'] as List, + ); + } + + @override + Future readLatestTile({ + required String storeName, + }) async => + (await _sendCmdOneShot( + type: _CmdType.readLatestTile, + args: {'storeName': storeName}, + ))!['tile']; + + @override + Future> writeTile({ + required String url, + required Uint8List bytes, + required List storeNames, + required List? writeAllNotIn, + }) async => + (await _sendCmdOneShot( + type: _CmdType.writeTile, + args: { + 'storeNames': storeNames, + 'writeAllNotIn': writeAllNotIn, + 'url': url, + 'bytes': bytes, + }, + ))!['result']; + + @override + Future deleteTile({ + required String storeName, + required String url, + }) async => + (await _sendCmdOneShot( + type: _CmdType.deleteTile, + args: {'storeName': storeName, 'url': url}, + ))!['wasOrphan']; + + @override + Future incrementStoreHits({ + required List storeNames, + }) => + _sendCmdOneShot( + type: _CmdType.incrementStoreHits, + args: {'storeNames': storeNames}, + ); + + @override + Future incrementStoreMisses({ + required ({bool includeOrExclude, List storeNames}) storeNames, + }) => + _sendCmdOneShot( + type: _CmdType.incrementStoreMisses, + args: {'storeNames': storeNames}, + ); + + @override + Future> removeOldestTilesAboveLimit({ + required List storeNames, + }) async { + // By sharing a single completer, all invocations of this method during the + // debounce period will return the same result at the same time + if (_rotalResultCompleter?.isCompleted ?? true) { + _rotalResultCompleter = Completer>(); + } + void sendCmdAndComplete() => _rotalResultCompleter!.complete( + _sendCmdOneShot( + type: _CmdType.removeOldestTilesAboveLimit, + args: {'storeNames': storeNames}, + ).then((v) => v!['orphansCounts']), + ); + + // If the store has changed, failing to reset the batch/queue will mean + // tiles are removed from the wrong store + if (_rotalStoresHash != storeNames.hashCode) { + _rotalStoresHash = storeNames.hashCode; + if (_rotalDebouncer?.isActive ?? false) { + _rotalDebouncer!.cancel(); + sendCmdAndComplete(); + return _rotalResultCompleter!.future; + } + } + + // If the timer is already running, debouncing is required: cancel the + // current timer, and start a new one with a shorter timeout + final isAlreadyActive = _rotalDebouncer?.isActive ?? false; + if (isAlreadyActive) _rotalDebouncer!.cancel(); + _rotalDebouncer = Timer( + Duration(milliseconds: isAlreadyActive ? 500 : 1000), + sendCmdAndComplete, + ); + + return _rotalResultCompleter!.future; + } + + @override + Future removeTilesOlderThan({ + required String storeName, + required DateTime expiry, + }) async => + (await _sendCmdOneShot( + type: _CmdType.removeTilesOlderThan, + args: {'storeName': storeName, 'expiry': expiry}, + ))!['orphansCount']; + + @override + Future> readMetadata({ + required String storeName, + }) async => + (await _sendCmdOneShot( + type: _CmdType.readMetadata, + args: {'storeName': storeName}, + ))!['metadata']; + + @override + Future setMetadata({ + required String storeName, + required String key, + required String value, + }) => + _sendCmdOneShot( + type: _CmdType.setBulkMetadata, + args: { + 'storeName': storeName, + 'kvs': {key: value}, + }, + ); + + @override + Future setBulkMetadata({ + required String storeName, + required Map kvs, + }) => + _sendCmdOneShot( + type: _CmdType.setBulkMetadata, + args: {'storeName': storeName, 'kvs': kvs}, + ); + + @override + Future removeMetadata({ + required String storeName, + required String key, + }) async => + (await _sendCmdOneShot( + type: _CmdType.removeMetadata, + args: {'storeName': storeName, 'key': key}, + ))!['removedValue']; + + @override + Future resetMetadata({ + required String storeName, + }) => + _sendCmdOneShot( + type: _CmdType.resetMetadata, + args: {'storeName': storeName}, + ); + + @override + Future> listRecoverableRegions() async => + (await _sendCmdOneShot( + type: _CmdType.listRecoverableRegions, + ))!['recoverableRegions']; + + @override + Future getRecoverableRegion({ + required int id, + }) async => + (await _sendCmdOneShot( + type: _CmdType.getRecoverableRegion, + ))!['recoverableRegion']; + + @override + Future cancelRecovery({ + required int id, + }) => + _sendCmdOneShot(type: _CmdType.cancelRecovery, args: {'id': id}); + + @override + Stream watchRecovery({ + required bool triggerImmediately, + }) => + _sendCmdStreamed( + type: _CmdType.watchRecovery, + args: {'triggerImmediately': triggerImmediately}, + ); + + @override + Stream watchStores({ + required List storeNames, + required bool triggerImmediately, + }) => + _sendCmdStreamed( + type: _CmdType.watchStores, + args: { + 'storeNames': storeNames, + 'triggerImmediately': triggerImmediately, + }, + ); + + @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(); + } + + return (await _sendCmdOneShot( + type: _CmdType.exportStores, + args: {'storeNames': storeNames, 'outputPath': path}, + ))!['numExportedTiles']; + } + + @override + ImportResult importStores({ + required String path, + required ImportConflictStrategy strategy, + required List? storeNames, + }) { + Stream?> checkTypeAndStartImport() async* { + await _checkImportPathType(path); + yield* _sendCmdStreamed( + type: _CmdType.importStores, + args: {'path': path, 'strategy': strategy, 'stores': storeNames}, + ); + } + + final storesToStates = Completer(); + final complete = Completer(); + + late final StreamSubscription?> listener; + listener = checkTypeAndStartImport().listen( + (evt) { + if (evt!.containsKey('storesToStates')) { + storesToStates.complete(evt['storesToStates']); + } + if (evt.containsKey('complete')) { + complete.complete(evt['complete']); + listener.cancel(); + } + }, + onError: (err, stackTrace) { + if (!storesToStates.isCompleted) { + storesToStates.completeError(err, stackTrace); + } + if (!complete.isCompleted) { + complete.completeError(err, stackTrace); + } + }, + cancelOnError: true, + ); + + return ( + storesToStates: storesToStates.future, + complete: complete.future, + ); + } + + @override + Future> listImportableStores({ + required String path, + }) async { + await _checkImportPathType(path); + + return (await _sendCmdOneShot( + type: _CmdType.listImportableStores, + args: {'path': path}, + ))!['stores']; + } + + 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(); + } + } +} 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..34aafbd9 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/database.dart @@ -0,0 +1,33 @@ +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, + ], +) +class DriftFMTCDatabase extends $DriftFMTCDatabase { + DriftFMTCDatabase(QueryExecutor connection) : super(connection); + + @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..dd8b9de5 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/database.drift.dart @@ -0,0 +1,62 @@ +// 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 + ]; + @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..c35262d7 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery.dart @@ -0,0 +1,21 @@ +import 'package:drift/drift.dart'; + +import 'store.dart'; + +class DriftRecovery extends Table { + late final id = integer()(); + late final store = text().references(DriftStore, #name)(); + + late final creationTime = dateTime().withDefault(currentDateAndTime)(); + + late final minZoom = integer()(); + late final maxZoom = integer()(); + late final startTile = integer()(); + 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..c562f98e --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart @@ -0,0 +1,714 @@ +// 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 { + final int id; + final String store; + final DateTime creationTime; + final int minZoom; + final int maxZoom; + final int startTile; + 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..9d98b68c --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery_region.dart @@ -0,0 +1,30 @@ +import 'package:drift/drift.dart'; + +import 'recovery.dart'; + +class DriftRecoveryRegion extends Table { + late final id = integer().autoIncrement()(); + late final recovery = integer().references(DriftRecovery, #id)(); + + late final Column typeId = + integer().check(typeId.isBetweenValues(0, 3))(); + + late final rectNwLat = real().nullable()(); + late final rectNwLng = real().nullable()(); + late final rectSeLat = real().nullable()(); + late final rectSeLng = real().nullable()(); + + late final circleCenterLat = real().nullable()(); + late final circleCenterLng = real().nullable()(); + late final circleRadius = real().nullable()(); + + late final Column lineLats = text().nullable()(); + late final Column lineLngs = text().nullable()(); + late final lineRadius = real().nullable()(); + + late final Column customPolygonLats = text().nullable()(); + 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..5258808c --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/recovery_region.drift.dart @@ -0,0 +1,1240 @@ +// 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, + 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 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 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 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 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 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, + 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, + 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, + 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 _typeIdMeta = + const i0.VerificationMeta('typeId'); + @override + late final i0.GeneratedColumn typeId = i0.GeneratedColumn( + 'type_id', aliasedName, false, + check: () => i3.ComparableExpr(typeId).isBetweenValues(0, 3), + 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, + 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('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'])!, + 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 { + final int id; + final int recovery; + final int typeId; + final double? rectNwLat; + final double? rectNwLng; + final double? rectSeLat; + final double? rectSeLng; + final double? circleCenterLat; + final double? circleCenterLng; + final double? circleRadius; + final String? lineLats; + final String? lineLngs; + final double? lineRadius; + final String? customPolygonLats; + final String? customPolygonLngs; + const DriftRecoveryRegionData( + {required this.id, + required this.recovery, + 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); + 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), + 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']), + 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), + '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, + 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, + 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, + 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('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, + 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.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 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.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, + 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? 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 (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? 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, + 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 (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('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..9a17c493 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/root.dart @@ -0,0 +1,15 @@ +import 'package:drift/drift.dart'; + +class DriftRoot extends Table { + late final Column id = + integer().check(id.equals(0)).withDefault(const Constant(0))(); + + late final length = integer().withDefault(const Constant(0))(); + 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..5cd9373e --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/root.drift.dart @@ -0,0 +1,369 @@ +// 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 { + final int id; + final int length; + 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..6b1afdca --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store.dart @@ -0,0 +1,20 @@ +import 'package:drift/drift.dart'; + +class DriftStore extends Table { + late final name = text()(); + late final maxLength = integer().nullable()(); + late final length = integer().withDefault(const Constant(0))(); + late final size = integer().withDefault(const Constant(0))(); + late final hits = integer().withDefault(const Constant(0))(); + late final misses = integer().withDefault(const Constant(0))(); + 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..21985d41 --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store.drift.dart @@ -0,0 +1,594 @@ +// 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 { + final String name; + final int? maxLength; + final int length; + final int size; + final int hits; + final int misses; + 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..88e6daff --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store_tile.dart @@ -0,0 +1,18 @@ +import 'package:drift/drift.dart'; + +import 'store.dart'; +import 'tile.dart'; + +class DriftStoreTile extends Table { + late final store = text().references(DriftStore, #name)(); + late final tile = text().references(DriftTile, #uid)(); + + @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..859a385a --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/store_tile.drift.dart @@ -0,0 +1,546 @@ +// 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})>; + +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, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES drift_store (name)')); + 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, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('REFERENCES drift_tile (uid)')); + @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 { + final String store; + 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..f3b229ee --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/tile.dart @@ -0,0 +1,14 @@ +import 'package:drift/drift.dart'; + +@TableIndex(name: 'last_modified', columns: {#lastModified}) +class DriftTile extends Table { + late final uid = text()(); + late final bytes = blob()(); + 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..baafc87b --- /dev/null +++ b/lib/src/backend/impls/drift/native/database/models/tile.drift.dart @@ -0,0 +1,400 @@ +// 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 { + final String uid; + final i2.Uint8List bytes; + 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/pubspec.yaml b/pubspec.yaml index 13782793..ec381f4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ dependencies: async: ^2.11.0 collection: ^1.18.0 dart_earcut: ^1.1.0 + drift: ^2.24.0 + drift_flutter: ^0.2.4 flat_buffers: ^23.5.26 flutter: sdk: flutter @@ -44,7 +46,9 @@ dependencies: dev_dependencies: build_runner: ^2.4.14 + drift_dev: ^2.24.0 objectbox_generator: ^4.0.3 + pigeon_generator: test: ^1.25.14 flutter: null From 522c6cc585ef898d5784699eef90df93e50ce85a Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 07:59:41 +0530 Subject: [PATCH 3/8] feat: add `foundation.dart` import to backend implementation. --- lib/src/backend/impls/objectbox/native/backend/backend.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/backend/impls/objectbox/native/backend/backend.dart b/lib/src/backend/impls/objectbox/native/backend/backend.dart index 30b1d451..c04f6971 100644 --- a/lib/src/backend/impls/objectbox/native/backend/backend.dart +++ b/lib/src/backend/impls/objectbox/native/backend/backend.dart @@ -9,6 +9,7 @@ import 'dart:isolate'; import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; From f6812489040cc1f5f253da5dcc83b64b879b78d5 Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 08:40:35 +0530 Subject: [PATCH 4/8] feat: Implement a thread-safe Drift backend, add comprehensive documentation to Drift models, and update the database schema. --- .vscode/settings.json | 10 +- .../recovery_regions/recovery_regions.dart | 4 +- lib/src/backend/export_external.dart | 1 + .../impls/drift/native/backend/backend.dart | 1524 ++++++++++++++++- .../impls/drift/native/backend/internal.dart | 690 +------- .../backend/models/drift_backend_tile.dart | 25 + .../drift/native/backend/thread_safe.dart | 292 ++++ .../backend/utils/region_serialization.dart | 111 ++ .../impls/drift/native/database/database.dart | 5 +- .../drift/native/database/database.drift.dart | 30 +- .../native/database/models/recovery.dart | 12 + .../database/models/recovery_region.dart | 30 +- .../models/recovery_region.drift.dart | 66 +- .../drift/native/database/models/root.dart | 5 + .../drift/native/database/models/store.dart | 14 + .../native/database/models/store_tile.dart | 16 +- .../database/models/store_tile.drift.dart | 10 +- .../drift/native/database/models/tile.dart | 7 + .../objectbox/native/backend/backend.dart | 6 +- .../models/generated/objectbox-model.json | 18 +- .../native/models/generated/objectbox.g.dart | 1451 +++++++++------- pubspec.yaml | 13 +- 22 files changed, 2938 insertions(+), 1402 deletions(-) create mode 100644 lib/src/backend/impls/drift/native/backend/models/drift_backend_tile.dart create mode 100644 lib/src/backend/impls/drift/native/backend/thread_safe.dart create mode 100644 lib/src/backend/impls/drift/native/backend/utils/region_serialization.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 6121fc08..5d9238ae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,13 @@ "*.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/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/lib/src/backend/export_external.dart b/lib/src/backend/export_external.dart index ff113bc9..2c6adec9 100644 --- a/lib/src/backend/export_external.dart +++ b/lib/src/backend/export_external.dart @@ -2,6 +2,7 @@ // A full license can be found at .\LICENSE export 'errors/errors.dart'; +export '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'; diff --git a/lib/src/backend/impls/drift/native/backend/backend.dart b/lib/src/backend/impls/drift/native/backend/backend.dart index 6d218b00..24b58c95 100644 --- a/lib/src/backend/impls/drift/native/backend/backend.dart +++ b/lib/src/backend/impls/drift/native/backend/backend.dart @@ -2,88 +2,1498 @@ // A full license can be found at .\LICENSE import 'dart:async'; -import 'dart:collection'; import 'dart:convert'; import 'dart:io'; -import 'dart:isolate'; -import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; -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 '../models/generated/objectbox.g.dart'; -import '../models/src/recovery.dart'; -import '../models/src/recovery_region.dart'; -import '../models/src/root.dart'; -import '../database/models/store.dart'; -import '../models/src/tile.dart'; - -export 'package:objectbox/objectbox.dart' show StorageException; - -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 -/// -/// On web, this redirects to a no-op implementation that throws -/// [UnsupportedError]s when attempting to use [initialise] or [uninitialise], -/// and [RootUnavailable] when trying to use any other method. -final class FMTCObjectBoxBackend implements FMTCBackend { +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} /// - /// --- - /// - /// [maxDatabaseSize] is the maximum size the database file can grow - /// to, in KB. Exceeding it throws [DbFullException] (from - /// 'package:objectbox') on write operations. Defaults to 10 GB (10000000 KB). - /// - /// [macosApplicationGroup] should be set when creating a sandboxed macOS app, - /// specify the application group (of less than 20 chars). See - /// [the ObjectBox docs](https://docs.objectbox.io/getting-started) for - /// details. - /// - /// [rootIsolateToken] should only be used in exceptional circumstances where - /// this backend is being initialised in a seperate isolate (or background) - /// thread. - /// /// Avoid using [useInMemoryDatabase] outside of testing purposes. @override Future initialise({ String? rootDirectory, - int maxDatabaseSize = 10000000, - String? macosApplicationGroup, - RootIsolateToken? rootIsolateToken, @visibleForTesting bool useInMemoryDatabase = false, }) => - FMTCObjectBoxBackendInternal._instance.initialise( + FMTCDriftBackendInternal._instance.initialise( rootDirectory: rootDirectory, - maxDatabaseSize: maxDatabaseSize, - macosApplicationGroup: macosApplicationGroup, useInMemoryDatabase: useInMemoryDatabase, - rootIsolateToken: rootIsolateToken, ); /// {@macro fmtc.backend.uninitialise} - /// - /// If [immediate] is `true`, any operations currently underway will be lost, - /// as the worker will be killed as quickly as possible (not necessarily - /// instantly). - /// If `false`, all operations currently underway will be allowed to complete, - /// but any operations started after this method call will be lost. @override Future uninitialise({ bool deleteRoot = false, - bool immediate = false, }) => - FMTCObjectBoxBackendInternal._instance - .uninitialise(deleteRoot: deleteRoot, immediate: immediate); + 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 index b6e63090..a14da83c 100644 --- a/lib/src/backend/impls/drift/native/backend/internal.dart +++ b/lib/src/backend/impls/drift/native/backend/internal.dart @@ -1,690 +1,6 @@ // Copyright © Luka S (JaffaKetchup) under GPL-v3 // A full license can be found at .\LICENSE -part of 'backend.dart'; - -/// Internal implementation of [FMTCBackend] that uses ObjectBox as the storage -/// database -/// -/// Actual implementation performed by `_worker` via `_ObjectBoxBackendImpl`. -abstract interface class FMTCObjectBoxBackendInternal - implements FMTCBackendInternal { - static final _instance = _ObjectBoxBackendImpl._(); -} - -class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { - _ObjectBoxBackendImpl._(); - - @override - String get friendlyIdentifier => 'ObjectBox'; - - void get expectInitialised => _sendPort ?? (throw RootUnavailable()); - - late String rootDirectory; - - // Worker communication protocol storage - - SendPort? _sendPort; - final _workerResOneShot = ?>>{}; - final _workerResStreamed = ?>>{}; - int _workerId = smallestInt; - late Completer _workerComplete; - late StreamSubscription _workerHandler; - - // `removeOldestTilesAboveLimit` tracking & debouncing - - Timer? _rotalDebouncer; - int? _rotalStoresHash; - Completer>? _rotalResultCompleter; - - // Define communicators - - Future?> _sendCmdOneShot({ - required _CmdType type, - Map args = const {}, - }) async { - expectInitialised; - - final id = ++_workerId; // Create new unique ID - _workerResOneShot[id] = Completer(); // Will be completed by direct handler - _sendPort!.send((id: id, type: type, args: args)); // Send cmd - - try { - return await _workerResOneShot[id]!.future; // Await response - } catch (err, stackTrace) { - Error.throwWithStackTrace( - err, - StackTrace.fromString( - '$stackTrace\n${StackTrace.current}' - '#+ [FMTC Debug Info] $type: $args\n', - ), - ); - } finally { - _workerResOneShot.remove(id); // Free memory - } - } - - Stream?> _sendCmdStreamed({ - required _CmdType type, - Map args = const {}, - }) async* { - expectInitialised; - - final id = ++_workerId; // Create new unique ID - final controller = StreamController?>( - onCancel: () async { - _workerResStreamed.remove(id); // Free memory - // Cancel the worker stream if the worker is alive - if ((type.hasInternalStreamSub ?? false) && - !_workerComplete.isCompleted) { - await _sendCmdOneShot( - type: _CmdType.cancelInternalStreamSub, - args: {'id': id}, - ); - } - }, - ); - _workerResStreamed[id] = - controller.sink; // Will be inserted into by direct handler - _sendPort!.send((id: id, type: type, args: args)); // Send cmd - - // Efficienctly forward resulting stream, but add extra debug info to any - // errors - yield* controller.stream.handleError( - (err, stackTrace) => Error.throwWithStackTrace( - err, - StackTrace.fromString( - '$stackTrace\n#+ [FMTC Debug Info] ' - ' Unable to attach final `StackTrace` when streaming results\n' - '\n#+ [FMTC Debug Info] ' - '$type: $args\n', - ), - ), - ); - - // Goto `onCancel` once output listening cancelled - await controller.close(); - } - - // Lifecycle implementations - - Future initialise({ - required String? rootDirectory, - required int maxDatabaseSize, - required String? macosApplicationGroup, - required bool useInMemoryDatabase, - required RootIsolateToken? rootIsolateToken, - }) async { - if (_sendPort != null) throw RootAlreadyInitialised(); - - // Obtain the `RootIsolateToken` to enable the worker isolate to use - // ObjectBox - rootIsolateToken ??= ServicesBinding.rootIsolateToken ?? - (throw StateError( - 'Unable to start FMTC in a background thread without access to a ' - '`RootIsolateToken`', - )); - - // Construct the root directory path - if (useInMemoryDatabase) { - this.rootDirectory = Store.inMemoryPrefix + (rootDirectory ?? 'fmtc'); - } else { - await Directory( - this.rootDirectory = path.join( - rootDirectory ?? - (await getApplicationDocumentsDirectory()).absolute.path, - 'fmtc', - ), - ).create(recursive: true); - } - - // Prepare to recieve `SendPort` from worker - _workerResOneShot[0] = Completer(); - final workerInitialRes = _workerResOneShot[0]! - .future // Completed directly by handler below - .then<({Object err, StackTrace stackTrace})?>( - (res) { - _workerResOneShot.remove(0); - _sendPort = res!['sendPort']; - return null; - }, - onError: (err, stackTrace) { - _workerHandler.cancel(); - _workerComplete.complete(); - - _workerId = 0; - _workerResOneShot.clear(); - _workerResStreamed.clear(); - - return (err: err, stackTrace: stackTrace); - }, - ); - - // Setup worker comms/response handler - final receivePort = ReceivePort(); - _workerComplete = Completer(); - _workerHandler = receivePort.listen( - (evt) { - evt as ({int id, Map? data})?; - - // Killed forcefully by environment (eg. hot restart) - if (evt == null) { - _workerHandler.cancel(); // Ensure this handler is cancelled on return - _workerComplete.complete(); - // Doesn't require full cleanup, because hot restart has done that - return; - } - - final isStreamedResult = evt.data?['expectStream'] == true; - - // Handle errors - if (evt.data?['error'] case final err?) { - final stackTrace = evt.data!['stackTrace']; - if (isStreamedResult) { - _workerResStreamed[evt.id]?.addError(err, stackTrace); - } else { - _workerResOneShot[evt.id]!.completeError(err, stackTrace); - } - return; - } - - if (isStreamedResult) { - // May be `null` if cmd was streamed result, but has no way to prevent - // future results even after the listener has stopped - // - // See `_WorkerCmdType.hasInternalStreamSub` for info. - _workerResStreamed[evt.id]?.add(evt.data); - } else { - _workerResOneShot[evt.id]!.complete(evt.data); - } - }, - onDone: () => _workerComplete.complete(), - ); - - // Spawn worker isolate - try { - await Isolate.spawn( - _worker, - ( - sendPort: receivePort.sendPort, - rootDirectory: this.rootDirectory, - maxDatabaseSize: maxDatabaseSize, - macosApplicationGroup: macosApplicationGroup, - rootIsolateToken: rootIsolateToken, - ), - onExit: receivePort.sendPort, - debugName: '[FMTC] ObjectBox Backend Worker', - ); - } catch (e) { - receivePort.close(); - _sendPort = null; - rethrow; - } - - // Check whether initialisation was successful after initial response - if (await workerInitialRes case (:final err, :final stackTrace)) { - Error.throwWithStackTrace(err, stackTrace); - } - - FMTCBackendAccess.internal = this; - FMTCBackendAccessThreadSafe.internal = - _ObjectBoxBackendThreadSafeImpl._(rootDirectory: this.rootDirectory); - } - - Future uninitialise({ - required bool deleteRoot, - required bool immediate, - }) async { - expectInitialised; - - // Wait for all currently underway operations to complete before destroying - // the isolate (if not `immediate`) - if (!immediate) { - await Future.wait(_workerResOneShot.values.map((e) => e.future)); - } - - // Send self-destruct cmd to worker, and wait for response and exit - await _sendCmdOneShot( - type: _CmdType.destroy, - args: {'deleteRoot': deleteRoot}, - ); - await _workerComplete.future; - - // Destroy remaining worker refs - _sendPort = null; // Indicate ready for re-init - await _workerHandler.cancel(); // Stop response handler - - // Kill any remaining operations with an error (they'll never recieve a - // response from the worker) - for (final completer in _workerResOneShot.values) { - completer.complete({'error': RootUnavailable()}); - } - for (final streamController in List.of(_workerResStreamed.values)) { - await streamController.close(); - } - - // Reset state - _workerId = 0; - _workerResOneShot.clear(); - _workerResStreamed.clear(); - _rotalDebouncer?.cancel(); - _rotalDebouncer = null; - _rotalStoresHash = null; - _rotalResultCompleter?.completeError(RootUnavailable()); - _rotalResultCompleter = null; - - FMTCBackendAccess.internal = null; - FMTCBackendAccessThreadSafe.internal = null; - } - - // Implementation & worker connectors - - @override - Future realSize() async => - (await _sendCmdOneShot(type: _CmdType.realSize))!['size']; - - @override - Future rootSize() async => - (await _sendCmdOneShot(type: _CmdType.rootSize))!['size']; - - @override - Future rootLength() async => - (await _sendCmdOneShot(type: _CmdType.rootLength))!['length']; - - @override - Future> listStores() async => - (await _sendCmdOneShot(type: _CmdType.listStores))!['stores']; - - @override - Future storeGetMaxLength({ - required String storeName, - }) async => - (await _sendCmdOneShot( - type: _CmdType.storeGetMaxLength, - args: {'storeName': storeName}, - ))!['maxLength']; - - @override - Future storeSetMaxLength({ - required String storeName, - required int? newMaxLength, - }) => - _sendCmdOneShot( - type: _CmdType.storeSetMaxLength, - args: {'storeName': storeName, 'newMaxLength': newMaxLength}, - ); - - @override - Future storeExists({ - required String storeName, - }) async => - (await _sendCmdOneShot( - type: _CmdType.storeExists, - args: {'storeName': storeName}, - ))!['exists']; - - @override - Future createStore({ - required String storeName, - required int? maxLength, - }) => - _sendCmdOneShot( - type: _CmdType.createStore, - args: {'storeName': storeName, 'maxLength': maxLength}, - ); - - @override - Future resetStore({ - required String storeName, - }) => - _sendCmdOneShot( - type: _CmdType.resetStore, - args: {'storeName': storeName}, - ); - - @override - Future renameStore({ - required String currentStoreName, - required String newStoreName, - }) => - _sendCmdOneShot( - type: _CmdType.renameStore, - args: { - 'currentStoreName': currentStoreName, - 'newStoreName': newStoreName, - }, - ); - - @override - Future deleteStore({ - required String storeName, - }) => - _sendCmdOneShot( - type: _CmdType.deleteStore, - args: {'storeName': storeName}, - ); - - @override - Future<({double size, int length, int hits, int misses})> getStoreStats({ - required String storeName, - }) async => - (await _sendCmdOneShot( - type: _CmdType.getStoreStats, - args: {'storeName': storeName}, - ))!['stats']; - - @override - Future tileExists({ - required String url, - required ({bool includeOrExclude, List storeNames}) storeNames, - }) async => - (await _sendCmdOneShot( - type: _CmdType.tileExists, - args: {'url': url, 'storeNames': storeNames}, - ))!['exists']; - - @override - Future< - ({ - BackendTile? tile, - List intersectedStoreNames, - List allStoreNames, - })> readTile({ - required String url, - required ({bool includeOrExclude, List storeNames}) storeNames, - }) async { - final res = (await _sendCmdOneShot( - type: _CmdType.readTile, - args: {'url': url, 'storeNames': storeNames}, - ))!; - return ( - tile: res['tile'] as BackendTile?, - intersectedStoreNames: res['intersectedStoreNames'] as List, - allStoreNames: res['allStoreNames'] as List, - ); - } - - @override - Future readLatestTile({ - required String storeName, - }) async => - (await _sendCmdOneShot( - type: _CmdType.readLatestTile, - args: {'storeName': storeName}, - ))!['tile']; - - @override - Future> writeTile({ - required String url, - required Uint8List bytes, - required List storeNames, - required List? writeAllNotIn, - }) async => - (await _sendCmdOneShot( - type: _CmdType.writeTile, - args: { - 'storeNames': storeNames, - 'writeAllNotIn': writeAllNotIn, - 'url': url, - 'bytes': bytes, - }, - ))!['result']; - - @override - Future deleteTile({ - required String storeName, - required String url, - }) async => - (await _sendCmdOneShot( - type: _CmdType.deleteTile, - args: {'storeName': storeName, 'url': url}, - ))!['wasOrphan']; - - @override - Future incrementStoreHits({ - required List storeNames, - }) => - _sendCmdOneShot( - type: _CmdType.incrementStoreHits, - args: {'storeNames': storeNames}, - ); - - @override - Future incrementStoreMisses({ - required ({bool includeOrExclude, List storeNames}) storeNames, - }) => - _sendCmdOneShot( - type: _CmdType.incrementStoreMisses, - args: {'storeNames': storeNames}, - ); - - @override - Future> removeOldestTilesAboveLimit({ - required List storeNames, - }) async { - // By sharing a single completer, all invocations of this method during the - // debounce period will return the same result at the same time - if (_rotalResultCompleter?.isCompleted ?? true) { - _rotalResultCompleter = Completer>(); - } - void sendCmdAndComplete() => _rotalResultCompleter!.complete( - _sendCmdOneShot( - type: _CmdType.removeOldestTilesAboveLimit, - args: {'storeNames': storeNames}, - ).then((v) => v!['orphansCounts']), - ); - - // If the store has changed, failing to reset the batch/queue will mean - // tiles are removed from the wrong store - if (_rotalStoresHash != storeNames.hashCode) { - _rotalStoresHash = storeNames.hashCode; - if (_rotalDebouncer?.isActive ?? false) { - _rotalDebouncer!.cancel(); - sendCmdAndComplete(); - return _rotalResultCompleter!.future; - } - } - - // If the timer is already running, debouncing is required: cancel the - // current timer, and start a new one with a shorter timeout - final isAlreadyActive = _rotalDebouncer?.isActive ?? false; - if (isAlreadyActive) _rotalDebouncer!.cancel(); - _rotalDebouncer = Timer( - Duration(milliseconds: isAlreadyActive ? 500 : 1000), - sendCmdAndComplete, - ); - - return _rotalResultCompleter!.future; - } - - @override - Future removeTilesOlderThan({ - required String storeName, - required DateTime expiry, - }) async => - (await _sendCmdOneShot( - type: _CmdType.removeTilesOlderThan, - args: {'storeName': storeName, 'expiry': expiry}, - ))!['orphansCount']; - - @override - Future> readMetadata({ - required String storeName, - }) async => - (await _sendCmdOneShot( - type: _CmdType.readMetadata, - args: {'storeName': storeName}, - ))!['metadata']; - - @override - Future setMetadata({ - required String storeName, - required String key, - required String value, - }) => - _sendCmdOneShot( - type: _CmdType.setBulkMetadata, - args: { - 'storeName': storeName, - 'kvs': {key: value}, - }, - ); - - @override - Future setBulkMetadata({ - required String storeName, - required Map kvs, - }) => - _sendCmdOneShot( - type: _CmdType.setBulkMetadata, - args: {'storeName': storeName, 'kvs': kvs}, - ); - - @override - Future removeMetadata({ - required String storeName, - required String key, - }) async => - (await _sendCmdOneShot( - type: _CmdType.removeMetadata, - args: {'storeName': storeName, 'key': key}, - ))!['removedValue']; - - @override - Future resetMetadata({ - required String storeName, - }) => - _sendCmdOneShot( - type: _CmdType.resetMetadata, - args: {'storeName': storeName}, - ); - - @override - Future> listRecoverableRegions() async => - (await _sendCmdOneShot( - type: _CmdType.listRecoverableRegions, - ))!['recoverableRegions']; - - @override - Future getRecoverableRegion({ - required int id, - }) async => - (await _sendCmdOneShot( - type: _CmdType.getRecoverableRegion, - ))!['recoverableRegion']; - - @override - Future cancelRecovery({ - required int id, - }) => - _sendCmdOneShot(type: _CmdType.cancelRecovery, args: {'id': id}); - - @override - Stream watchRecovery({ - required bool triggerImmediately, - }) => - _sendCmdStreamed( - type: _CmdType.watchRecovery, - args: {'triggerImmediately': triggerImmediately}, - ); - - @override - Stream watchStores({ - required List storeNames, - required bool triggerImmediately, - }) => - _sendCmdStreamed( - type: _CmdType.watchStores, - args: { - 'storeNames': storeNames, - 'triggerImmediately': triggerImmediately, - }, - ); - - @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(); - } - - return (await _sendCmdOneShot( - type: _CmdType.exportStores, - args: {'storeNames': storeNames, 'outputPath': path}, - ))!['numExportedTiles']; - } - - @override - ImportResult importStores({ - required String path, - required ImportConflictStrategy strategy, - required List? storeNames, - }) { - Stream?> checkTypeAndStartImport() async* { - await _checkImportPathType(path); - yield* _sendCmdStreamed( - type: _CmdType.importStores, - args: {'path': path, 'strategy': strategy, 'stores': storeNames}, - ); - } - - final storesToStates = Completer(); - final complete = Completer(); - - late final StreamSubscription?> listener; - listener = checkTypeAndStartImport().listen( - (evt) { - if (evt!.containsKey('storesToStates')) { - storesToStates.complete(evt['storesToStates']); - } - if (evt.containsKey('complete')) { - complete.complete(evt['complete']); - listener.cancel(); - } - }, - onError: (err, stackTrace) { - if (!storesToStates.isCompleted) { - storesToStates.completeError(err, stackTrace); - } - if (!complete.isCompleted) { - complete.completeError(err, stackTrace); - } - }, - cancelOnError: true, - ); - - return ( - storesToStates: storesToStates.future, - complete: complete.future, - ); - } - - @override - Future> listImportableStores({ - required String path, - }) async { - await _checkImportPathType(path); - - return (await _sendCmdOneShot( - type: _CmdType.listImportableStores, - args: {'path': path}, - ))!['stores']; - } - - 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(); - } - } -} +// 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 index 34aafbd9..6dbd9fc3 100644 --- a/lib/src/backend/impls/drift/native/database/database.dart +++ b/lib/src/backend/impls/drift/native/database/database.dart @@ -18,8 +18,11 @@ import 'models/tile.dart'; DriftRecoveryRegion, ], ) + +/// The main Drift database class for FMTC's native SQLite backend class DriftFMTCDatabase extends $DriftFMTCDatabase { - DriftFMTCDatabase(QueryExecutor connection) : super(connection); + /// Creates a [DriftFMTCDatabase] using the given [connection] + DriftFMTCDatabase(super.connection); @override int get schemaVersion => 1; diff --git a/lib/src/backend/impls/drift/native/database/database.drift.dart b/lib/src/backend/impls/drift/native/database/database.drift.dart index dd8b9de5..72214218 100644 --- a/lib/src/backend/impls/drift/native/database/database.drift.dart +++ b/lib/src/backend/impls/drift/native/database/database.drift.dart @@ -37,9 +37,37 @@ abstract class $DriftFMTCDatabase extends i0.GeneratedDatabase { driftRoot, driftRecovery, driftRecoveryRegion, - i1.lastModified + 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); } diff --git a/lib/src/backend/impls/drift/native/database/models/recovery.dart b/lib/src/backend/impls/drift/native/database/models/recovery.dart index c35262d7..0154cc58 100644 --- a/lib/src/backend/impls/drift/native/database/models/recovery.dart +++ b/lib/src/backend/impls/drift/native/database/models/recovery.dart @@ -2,15 +2,27 @@ 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 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 index 9d98b68c..dc26e895 100644 --- a/lib/src/backend/impls/drift/native/database/models/recovery_region.dart +++ b/lib/src/backend/impls/drift/native/database/models/recovery_region.dart @@ -2,27 +2,55 @@ 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, 3))(); + 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 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 index 5258808c..8dacf115 100644 --- 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 @@ -14,6 +14,7 @@ typedef $$DriftRecoveryRegionTableCreateCompanionBuilder = i1.DriftRecoveryRegionCompanion Function({ i0.Value id, required int recovery, + i0.Value parentRegionId, required int typeId, i0.Value rectNwLat, i0.Value rectNwLng, @@ -32,6 +33,7 @@ typedef $$DriftRecoveryRegionTableUpdateCompanionBuilder = i1.DriftRecoveryRegionCompanion Function({ i0.Value id, i0.Value recovery, + i0.Value parentRegionId, i0.Value typeId, i0.Value rectNwLat, i0.Value rectNwLng, @@ -94,6 +96,10 @@ class $$DriftRecoveryRegionTableFilterComposer 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)); @@ -173,6 +179,10 @@ class $$DriftRecoveryRegionTableOrderingComposer 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)); @@ -257,6 +267,9 @@ class $$DriftRecoveryRegionTableAnnotationComposer 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); @@ -347,6 +360,7 @@ class $$DriftRecoveryRegionTableTableManager extends i0.RootTableManager< 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(), @@ -364,6 +378,7 @@ class $$DriftRecoveryRegionTableTableManager extends i0.RootTableManager< i1.DriftRecoveryRegionCompanion( id: id, recovery: recovery, + parentRegionId: parentRegionId, typeId: typeId, rectNwLat: rectNwLat, rectNwLng: rectNwLng, @@ -381,6 +396,7 @@ class $$DriftRecoveryRegionTableTableManager extends i0.RootTableManager< 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(), @@ -398,6 +414,7 @@ class $$DriftRecoveryRegionTableTableManager extends i0.RootTableManager< i1.DriftRecoveryRegionCompanion.insert( id: id, recovery: recovery, + parentRegionId: parentRegionId, typeId: typeId, rectNwLat: rectNwLat, rectNwLng: rectNwLng, @@ -495,12 +512,18 @@ class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion 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, 3), + check: () => i3.ComparableExpr(typeId).isBetweenValues(0, 4), type: i0.DriftSqlType.int, requiredDuringInsert: true); static const i0.VerificationMeta _rectNwLatMeta = @@ -579,6 +602,7 @@ class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion List get $columns => [ id, recovery, + parentRegionId, typeId, rectNwLat, rectNwLng, @@ -613,6 +637,12 @@ class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion } 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)); @@ -701,6 +731,8 @@ class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion .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 @@ -745,6 +777,9 @@ class DriftRecoveryRegionData extends i0.DataClass implements i0.Insertable { final int id; final int recovery; + + /// For MultiRegion sub-regions, references the parent DriftRecoveryRegion.id + final int? parentRegionId; final int typeId; final double? rectNwLat; final double? rectNwLng; @@ -761,6 +796,7 @@ class DriftRecoveryRegionData extends i0.DataClass const DriftRecoveryRegionData( {required this.id, required this.recovery, + this.parentRegionId, required this.typeId, this.rectNwLat, this.rectNwLng, @@ -779,6 +815,9 @@ class DriftRecoveryRegionData extends i0.DataClass 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); @@ -823,6 +862,9 @@ class DriftRecoveryRegionData extends i0.DataClass 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() @@ -869,6 +911,7 @@ class DriftRecoveryRegionData extends i0.DataClass 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']), @@ -892,6 +935,7 @@ class DriftRecoveryRegionData extends i0.DataClass 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), @@ -911,6 +955,7 @@ class DriftRecoveryRegionData extends i0.DataClass 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(), @@ -927,6 +972,8 @@ class DriftRecoveryRegionData extends i0.DataClass 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, @@ -955,6 +1002,9 @@ class DriftRecoveryRegionData extends i0.DataClass 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, @@ -987,6 +1037,7 @@ class DriftRecoveryRegionData extends i0.DataClass return (StringBuffer('DriftRecoveryRegionData(') ..write('id: $id, ') ..write('recovery: $recovery, ') + ..write('parentRegionId: $parentRegionId, ') ..write('typeId: $typeId, ') ..write('rectNwLat: $rectNwLat, ') ..write('rectNwLng: $rectNwLng, ') @@ -1008,6 +1059,7 @@ class DriftRecoveryRegionData extends i0.DataClass int get hashCode => Object.hash( id, recovery, + parentRegionId, typeId, rectNwLat, rectNwLng, @@ -1027,6 +1079,7 @@ class DriftRecoveryRegionData extends i0.DataClass (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 && @@ -1046,6 +1099,7 @@ 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; @@ -1062,6 +1116,7 @@ class DriftRecoveryRegionCompanion 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(), @@ -1079,6 +1134,7 @@ class DriftRecoveryRegionCompanion 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(), @@ -1097,6 +1153,7 @@ class DriftRecoveryRegionCompanion static i0.Insertable custom({ i0.Expression? id, i0.Expression? recovery, + i0.Expression? parentRegionId, i0.Expression? typeId, i0.Expression? rectNwLat, i0.Expression? rectNwLng, @@ -1114,6 +1171,7 @@ class DriftRecoveryRegionCompanion 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, @@ -1133,6 +1191,7 @@ class DriftRecoveryRegionCompanion i1.DriftRecoveryRegionCompanion copyWith( {i0.Value? id, i0.Value? recovery, + i0.Value? parentRegionId, i0.Value? typeId, i0.Value? rectNwLat, i0.Value? rectNwLng, @@ -1149,6 +1208,7 @@ class DriftRecoveryRegionCompanion 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, @@ -1174,6 +1234,9 @@ class DriftRecoveryRegionCompanion 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); } @@ -1221,6 +1284,7 @@ class DriftRecoveryRegionCompanion return (StringBuffer('DriftRecoveryRegionCompanion(') ..write('id: $id, ') ..write('recovery: $recovery, ') + ..write('parentRegionId: $parentRegionId, ') ..write('typeId: $typeId, ') ..write('rectNwLat: $rectNwLat, ') ..write('rectNwLng: $rectNwLng, ') diff --git a/lib/src/backend/impls/drift/native/database/models/root.dart b/lib/src/backend/impls/drift/native/database/models/root.dart index 9a17c493..afee8d8f 100644 --- a/lib/src/backend/impls/drift/native/database/models/root.dart +++ b/lib/src/backend/impls/drift/native/database/models/root.dart @@ -1,10 +1,15 @@ 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 diff --git a/lib/src/backend/impls/drift/native/database/models/store.dart b/lib/src/backend/impls/drift/native/database/models/store.dart index 6b1afdca..4f39abe1 100644 --- a/lib/src/backend/impls/drift/native/database/models/store.dart +++ b/lib/src/backend/impls/drift/native/database/models/store.dart @@ -1,12 +1,26 @@ 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 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 index 88e6daff..a76cfaab 100644 --- a/lib/src/backend/impls/drift/native/database/models/store_tile.dart +++ b/lib/src/backend/impls/drift/native/database/models/store_tile.dart @@ -1,11 +1,23 @@ 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 { - late final store = text().references(DriftStore, #name)(); - late final tile = text().references(DriftTile, #uid)(); + /// 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}; 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 index 859a385a..e7df7799 100644 --- 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 @@ -347,6 +347,8 @@ typedef $$DriftStoreTileTableProcessedTableManager = i0.ProcessedTableManager< (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> { @@ -361,8 +363,8 @@ class $DriftStoreTileTable extends i2.DriftStoreTile 'store', aliasedName, false, type: i0.DriftSqlType.string, requiredDuringInsert: true, - defaultConstraints: i0.GeneratedColumn.constraintIsAlways( - 'REFERENCES drift_store (name)')); + $customConstraints: + 'REFERENCES drift_store(name) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL'); static const i0.VerificationMeta _tileMeta = const i0.VerificationMeta('tile'); @override @@ -370,8 +372,8 @@ class $DriftStoreTileTable extends i2.DriftStoreTile 'tile', aliasedName, false, type: i0.DriftSqlType.string, requiredDuringInsert: true, - defaultConstraints: - i0.GeneratedColumn.constraintIsAlways('REFERENCES drift_tile (uid)')); + $customConstraints: + 'REFERENCES drift_tile(uid) ON DELETE CASCADE NOT NULL'); @override List get $columns => [store, tile]; @override diff --git a/lib/src/backend/impls/drift/native/database/models/tile.dart b/lib/src/backend/impls/drift/native/database/models/tile.dart index f3b229ee..d6aa7016 100644 --- a/lib/src/backend/impls/drift/native/database/models/tile.dart +++ b/lib/src/backend/impls/drift/native/database/models/tile.dart @@ -1,9 +1,16 @@ 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 diff --git a/lib/src/backend/impls/objectbox/native/backend/backend.dart b/lib/src/backend/impls/objectbox/native/backend/backend.dart index c04f6971..546a433a 100644 --- a/lib/src/backend/impls/objectbox/native/backend/backend.dart +++ b/lib/src/backend/impls/objectbox/native/backend/backend.dart @@ -10,9 +10,7 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -28,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/native/models/generated/objectbox-model.json b/lib/src/backend/impls/objectbox/native/models/generated/objectbox-model.json index a7397a4e..94774d18 100644 --- a/lib/src/backend/impls/objectbox/native/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 index 3cdb2f66..84bd9fd6 100644 --- a/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart +++ b/lib/src/backend/impls/objectbox/native/models/generated/objectbox.g.dart @@ -24,259 +24,308 @@ export 'package:objectbox/objectbox.dart'; // so that callers only have to impor 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: []), + 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') - ]), + 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: []), + 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: []), + 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: []) + 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 @@ -290,335 +339,443 @@ final _entities = [ /// 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 { +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); + 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); + // 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; - }), + 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; - }), + 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; - }), + 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; - }), + 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; - }) + 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); @@ -627,182 +784,222 @@ obx_int.ModelDefinition getObjectBoxModel() { /// [ObjectBoxRecovery] entity fields to define ObjectBox queries. class ObjectBoxRecovery_ { /// See [ObjectBoxRecovery.id]. - static final id = - obx.QueryIntegerProperty(_entities[0].properties[0]); + static final id = obx.QueryIntegerProperty( + _entities[0].properties[0], + ); /// See [ObjectBoxRecovery.refId]. - static final refId = - obx.QueryIntegerProperty(_entities[0].properties[1]); + static final refId = obx.QueryIntegerProperty( + _entities[0].properties[1], + ); /// See [ObjectBoxRecovery.storeName]. - static final storeName = - obx.QueryStringProperty(_entities[0].properties[2]); + static final storeName = obx.QueryStringProperty( + _entities[0].properties[2], + ); /// See [ObjectBoxRecovery.creationTime]. - static final creationTime = - obx.QueryDateProperty(_entities[0].properties[3]); + static final creationTime = obx.QueryDateProperty( + _entities[0].properties[3], + ); /// See [ObjectBoxRecovery.minZoom]. - static final minZoom = - obx.QueryIntegerProperty(_entities[0].properties[4]); + static final minZoom = obx.QueryIntegerProperty( + _entities[0].properties[4], + ); /// See [ObjectBoxRecovery.maxZoom]. - static final maxZoom = - obx.QueryIntegerProperty(_entities[0].properties[5]); + static final maxZoom = obx.QueryIntegerProperty( + _entities[0].properties[5], + ); /// See [ObjectBoxRecovery.startTile]. - static final startTile = - obx.QueryIntegerProperty(_entities[0].properties[6]); + static final startTile = obx.QueryIntegerProperty( + _entities[0].properties[6], + ); /// See [ObjectBoxRecovery.endTile]. - static final endTile = - obx.QueryIntegerProperty(_entities[0].properties[7]); + static final endTile = obx.QueryIntegerProperty( + _entities[0].properties[7], + ); /// See [ObjectBoxRecovery.region]. static final region = obx.QueryRelationToOne( - _entities[0].properties[8]); + _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]); + static final id = obx.QueryIntegerProperty( + _entities[1].properties[0], + ); /// See [ObjectBoxStore.name]. - static final name = - obx.QueryStringProperty(_entities[1].properties[1]); + static final name = obx.QueryStringProperty( + _entities[1].properties[1], + ); /// See [ObjectBoxStore.length]. - static final length = - obx.QueryIntegerProperty(_entities[1].properties[2]); + static final length = obx.QueryIntegerProperty( + _entities[1].properties[2], + ); /// See [ObjectBoxStore.size]. - static final size = - obx.QueryIntegerProperty(_entities[1].properties[3]); + static final size = obx.QueryIntegerProperty( + _entities[1].properties[3], + ); /// See [ObjectBoxStore.hits]. - static final hits = - obx.QueryIntegerProperty(_entities[1].properties[4]); + static final hits = obx.QueryIntegerProperty( + _entities[1].properties[4], + ); /// See [ObjectBoxStore.misses]. - static final misses = - obx.QueryIntegerProperty(_entities[1].properties[5]); + static final misses = obx.QueryIntegerProperty( + _entities[1].properties[5], + ); /// See [ObjectBoxStore.metadataJson]. - static final metadataJson = - obx.QueryStringProperty(_entities[1].properties[6]); + static final metadataJson = obx.QueryStringProperty( + _entities[1].properties[6], + ); /// See [ObjectBoxStore.maxLength]. - static final maxLength = - obx.QueryIntegerProperty(_entities[1].properties[7]); + 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]); + static final id = obx.QueryIntegerProperty( + _entities[2].properties[0], + ); /// See [ObjectBoxTile.url]. - static final url = - obx.QueryStringProperty(_entities[2].properties[1]); + static final url = obx.QueryStringProperty( + _entities[2].properties[1], + ); /// See [ObjectBoxTile.bytes]. - static final bytes = - obx.QueryByteVectorProperty(_entities[2].properties[2]); + static final bytes = obx.QueryByteVectorProperty( + _entities[2].properties[2], + ); /// See [ObjectBoxTile.lastModified]. - static final lastModified = - obx.QueryDateProperty(_entities[2].properties[3]); + static final lastModified = obx.QueryDateProperty( + _entities[2].properties[3], + ); /// see [ObjectBoxTile.stores] static final stores = obx.QueryRelationToMany( - _entities[2].relations[0]); + _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]); + static final id = obx.QueryIntegerProperty( + _entities[3].properties[0], + ); /// See [ObjectBoxRoot.length]. - static final length = - obx.QueryIntegerProperty(_entities[3].properties[1]); + static final length = obx.QueryIntegerProperty( + _entities[3].properties[1], + ); /// See [ObjectBoxRoot.size]. - static final size = - obx.QueryIntegerProperty(_entities[3].properties[2]); + 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]); + _entities[4].properties[0], + ); /// See [ObjectBoxRecoveryRegion.typeId]. static final typeId = obx.QueryIntegerProperty( - _entities[4].properties[1]); + _entities[4].properties[1], + ); /// See [ObjectBoxRecoveryRegion.rectNwLat]. static final rectNwLat = obx.QueryDoubleProperty( - _entities[4].properties[2]); + _entities[4].properties[2], + ); /// See [ObjectBoxRecoveryRegion.rectNwLng]. static final rectNwLng = obx.QueryDoubleProperty( - _entities[4].properties[3]); + _entities[4].properties[3], + ); /// See [ObjectBoxRecoveryRegion.rectSeLat]. static final rectSeLat = obx.QueryDoubleProperty( - _entities[4].properties[4]); + _entities[4].properties[4], + ); /// See [ObjectBoxRecoveryRegion.rectSeLng]. static final rectSeLng = obx.QueryDoubleProperty( - _entities[4].properties[5]); + _entities[4].properties[5], + ); /// See [ObjectBoxRecoveryRegion.circleCenterLat]. static final circleCenterLat = obx.QueryDoubleProperty( - _entities[4].properties[6]); + _entities[4].properties[6], + ); /// See [ObjectBoxRecoveryRegion.circleCenterLng]. static final circleCenterLng = obx.QueryDoubleProperty( - _entities[4].properties[7]); + _entities[4].properties[7], + ); /// See [ObjectBoxRecoveryRegion.circleRadius]. static final circleRadius = obx.QueryDoubleProperty( - _entities[4].properties[8]); + _entities[4].properties[8], + ); /// See [ObjectBoxRecoveryRegion.lineLats]. static final lineLats = obx.QueryDoubleVectorProperty( - _entities[4].properties[9]); + _entities[4].properties[9], + ); /// See [ObjectBoxRecoveryRegion.lineLngs]. static final lineLngs = obx.QueryDoubleVectorProperty( - _entities[4].properties[10]); + _entities[4].properties[10], + ); /// See [ObjectBoxRecoveryRegion.lineRadius]. static final lineRadius = obx.QueryDoubleProperty( - _entities[4].properties[11]); + _entities[4].properties[11], + ); /// See [ObjectBoxRecoveryRegion.customPolygonLats]. static final customPolygonLats = obx.QueryDoubleVectorProperty( - _entities[4].properties[12]); + _entities[4].properties[12], + ); /// See [ObjectBoxRecoveryRegion.customPolygonLngs]. static final customPolygonLngs = obx.QueryDoubleVectorProperty( - _entities[4].properties[13]); + _entities[4].properties[13], + ); /// see [ObjectBoxRecoveryRegion.multiLinkedRegions] static final multiLinkedRegions = obx.QueryRelationToMany( - _entities[4].relations[0]); + _entities[4].relations[0], + ); } diff --git a/pubspec.yaml b/pubspec.yaml index befad8ec..4b3d95a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,26 +32,27 @@ dependencies: dart_earcut: ^1.2.0 drift: ^2.31.0 drift_flutter: ^0.2.8 - flat_buffers: ^23.5.26 + flat_buffers: ^25.9.23 flutter: sdk: flutter flutter_map: ^8.2.2 http: ^1.6.0 latlong2: ^0.9.1 meta: ^1.17.0 - objectbox: ^4.3.1 - objectbox_flutter_libs: ^4.3.1 + objectbox: ^5.2.0 + objectbox_flutter_libs: ^5.2.0 path: ^1.9.1 path_provider: ^2.1.5 dev_dependencies: - build_runner: ^2.7.1 + build_runner: ^2.11.1 drift_dev: ^2.31.0 - objectbox_generator: ^4.3.1 - pigeon_generator: ^3.0.1 + objectbox_generator: ^5.2.0 + pigeon_generator: ^3.1.1 test: ^1.29.0 flutter: null objectbox: output_dir: src/backend/impls/objectbox/native/models/generated + From 17c833479171179b458d744c14c500eed02f6076 Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 08:41:44 +0530 Subject: [PATCH 5/8] feat: add detailed implementation plan for Drift backend and rename a database constructor parameter. --- lib/src/backend/impls/drift/native/database/database.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/backend/impls/drift/native/database/database.dart b/lib/src/backend/impls/drift/native/database/database.dart index 6dbd9fc3..9277798f 100644 --- a/lib/src/backend/impls/drift/native/database/database.dart +++ b/lib/src/backend/impls/drift/native/database/database.dart @@ -22,7 +22,7 @@ import 'models/tile.dart'; /// The main Drift database class for FMTC's native SQLite backend class DriftFMTCDatabase extends $DriftFMTCDatabase { /// Creates a [DriftFMTCDatabase] using the given [connection] - DriftFMTCDatabase(super.connection); + DriftFMTCDatabase(super.e); @override int get schemaVersion => 1; From 67b81ab2ac360eb1b5de301410c8ce639de5ec92 Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 08:42:48 +0530 Subject: [PATCH 6/8] feat: add a detailed implementation plan for the Drift backend and enhance documentation for its generated model files. --- .../database/models/recovery.drift.dart | 13 +++++++++ .../models/recovery_region.drift.dart | 29 +++++++++++++++++++ .../native/database/models/root.drift.dart | 5 ++++ .../native/database/models/store.drift.dart | 13 +++++++++ .../database/models/store_tile.drift.dart | 3 ++ .../native/database/models/tile.drift.dart | 5 ++++ 6 files changed, 68 insertions(+) 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 index c562f98e..87b2149a 100644 --- a/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart +++ b/lib/src/backend/impls/drift/native/database/models/recovery.drift.dart @@ -474,12 +474,25 @@ class $DriftRecoveryTable extends i2.DriftRecovery 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, 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 index 8dacf115..9f15f33f 100644 --- 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 @@ -775,23 +775,52 @@ class $DriftRecoveryRegionTable extends i2.DriftRecoveryRegion 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, 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 index 5cd9373e..8e867c08 100644 --- a/lib/src/backend/impls/drift/native/database/models/root.drift.dart +++ b/lib/src/backend/impls/drift/native/database/models/root.drift.dart @@ -231,8 +231,13 @@ class $DriftRootTable extends i2.DriftRoot 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}); 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 index 21985d41..d010599e 100644 --- a/lib/src/backend/impls/drift/native/database/models/store.drift.dart +++ b/lib/src/backend/impls/drift/native/database/models/store.drift.dart @@ -354,12 +354,25 @@ class $DriftStoreTable extends i2.DriftStore 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, 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 index e7df7799..988abfb3 100644 --- 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 @@ -428,7 +428,10 @@ class $DriftStoreTileTable extends i2.DriftStoreTile 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 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 index baafc87b..23f81bea 100644 --- a/lib/src/backend/impls/drift/native/database/models/tile.drift.dart +++ b/lib/src/backend/impls/drift/native/database/models/tile.drift.dart @@ -244,8 +244,13 @@ class $DriftTileTable extends i3.DriftTile 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}); From 2622591c5bff1edb2a77de4864b6d287b9871ede Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 08:53:27 +0530 Subject: [PATCH 7/8] feat: add Drift web backend using WasmDatabase for Flutter Web support - Created drift/web/backend.dart with full FMTCBackendInternal implementation using WasmDatabase - Uses WasmSqlite3.loadFromUrl for in-memory databases, WasmDatabase.open for persistent - Import/export throws UnsupportedError on web (no file system access) - Thread-safe impl shares DB instance (web has no true isolates) - Updated export_external.dart with conditional Drift import (web vs native) - Added sqlite3 ^2.7.5 dependency for WASM support --- lib/src/backend/export_external.dart | 3 +- lib/src/backend/impls/drift/web/backend.dart | 1354 ++++++++++++++++++ pubspec.yaml | 1 + 3 files changed, 1357 insertions(+), 1 deletion(-) create mode 100644 lib/src/backend/impls/drift/web/backend.dart diff --git a/lib/src/backend/export_external.dart b/lib/src/backend/export_external.dart index 2c6adec9..ee50b812 100644 --- a/lib/src/backend/export_external.dart +++ b/lib/src/backend/export_external.dart @@ -2,7 +2,8 @@ // A full license can be found at .\LICENSE export 'errors/errors.dart'; -export 'impls/drift/native/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'; 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..e7dccde7 --- /dev/null +++ b/lib/src/backend/impls/drift/web/backend.dart @@ -0,0 +1,1354 @@ +// 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 + + @override + Future realSize() async { + // On web, realSize isn't meaningful (no file on disk). Return rootSize. + return 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/pubspec.yaml b/pubspec.yaml index 4b3d95a0..6da9f910 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: objectbox_flutter_libs: ^5.2.0 path: ^1.9.1 path_provider: ^2.1.5 + sqlite3: ^2.7.5 dev_dependencies: build_runner: ^2.11.1 From 87c80bbe91db4e30ddcd21ce136a3cc44d163510 Mon Sep 17 00:00:00 2001 From: Gaurav <269mehta@gmail.com> Date: Mon, 23 Feb 2026 08:59:38 +0530 Subject: [PATCH 8/8] feat: Add detailed implementation plan for the Drift backend and simplify the web backend's `realSize` method. --- lib/src/backend/impls/drift/web/backend.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/backend/impls/drift/web/backend.dart b/lib/src/backend/impls/drift/web/backend.dart index e7dccde7..c86bd631 100644 --- a/lib/src/backend/impls/drift/web/backend.dart +++ b/lib/src/backend/impls/drift/web/backend.dart @@ -127,11 +127,9 @@ class _FMTCDriftBackendInternalWeb implements FMTCDriftBackendInternal { // Root stats + // On web, realSize isn't meaningful (no file on disk). Return rootSize. @override - Future realSize() async { - // On web, realSize isn't meaningful (no file on disk). Return rootSize. - return rootSize(); - } + Future realSize() => rootSize(); @override Future rootSize() async {