From bcbc8db569935e7dc6675a175cca0d633930b41f Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 19:46:11 +0530 Subject: [PATCH 01/23] Add dependency on Isar --- pubspec.lock | 82 +++++++++++++++++++++++++++++++++++++++++----------- pubspec.yaml | 8 +++-- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index f6af1e1ed..dcac2c4d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,10 +141,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" build_runner_core: dependency: transitive description: @@ -347,6 +347,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" dbus: dependency: transitive description: @@ -1055,6 +1063,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + isar: + dependency: "direct main" + description: + name: isar + sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_flutter_libs: + dependency: "direct main" + description: + name: isar_flutter_libs + sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_generator: + dependency: "direct dev" + description: + name: isar_generator + sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" js: dependency: transitive description: @@ -1470,50 +1502,50 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.1" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.1" pedantic: dependency: "direct main" description: @@ -2063,6 +2095,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.3" + time: + dependency: transitive + description: + name: time + sha256: "83427e11d9072e038364a5e4da559e85869b227cf699a541be0da74f14140124" + url: "https://pub.dev" + source: hosted + version: "2.1.3" timezone: dependency: transitive description: @@ -2329,13 +2369,13 @@ packages: source: hosted version: "0.0.2" watcher: - dependency: transitive + dependency: "direct overridden" description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web: dependency: transitive description: @@ -2432,6 +2472,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" + source: hosted + version: "1.0.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 40f2e3163..419fd64be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,8 @@ dependencies: image_editor: ^1.3.0 in_app_purchase: ^3.0.7 intl: ^0.18.0 + isar: ^3.1.0+1 + isar_flutter_libs: ^3.1.0+1 json_annotation: ^4.8.0 latlong2: ^0.9.0 like_button: ^2.0.2 @@ -124,7 +126,7 @@ dependencies: page_transition: ^2.0.2 password_strength: ^0.2.0 path: #dart - path_provider: ^2.0.1 + path_provider: ^2.1.1 pedantic: ^1.9.2 photo_manager: ^2.5.0 photo_view: ^0.14.0 @@ -175,6 +177,7 @@ dependency_overrides: url: https://github.com/ente-io/packages.git ref: android_video_roation_fix path: packages/video_player/video_player/ + watcher: ^1.1.0 flutter_intl: enabled: true @@ -182,7 +185,7 @@ flutter_intl: dev_dependencies: - build_runner: ^2.4.6 + build_runner: ^2.4.7 flutter_driver: sdk: flutter flutter_lints: ^2.0.1 @@ -191,6 +194,7 @@ dev_dependencies: freezed: ^2.3.2 integration_test: sdk: flutter + isar_generator: ^3.1.0+1 json_serializable: ^6.6.1 objectbox_generator: any test: ^1.22.0 From 3eca6e44a7053c676c232e98e6655937202bf4f4 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 20:21:59 +0530 Subject: [PATCH 02/23] Update iOS config --- ios/Podfile.lock | 8 +++++++- ios/Runner.xcodeproj/project.pbxproj | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b77a5b801..878a0627d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -99,6 +99,8 @@ PODS: - FlutterMacOS - integration_test (0.0.1): - Flutter + - isar_flutter_libs (1.0.0): + - Flutter - libwebp (1.3.2): - libwebp/demux (= 1.3.2) - libwebp/mux (= 1.3.2) @@ -218,6 +220,7 @@ DEPENDENCIES: - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) - integration_test (from `.symlinks/plugins/integration_test/ios`) + - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - media_extension (from `.symlinks/plugins/media_extension/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) @@ -308,6 +311,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" integration_test: :path: ".symlinks/plugins/integration_test/ios" + isar_flutter_libs: + :path: ".symlinks/plugins/isar_flutter_libs/ios" local_auth_ios: :path: ".symlinks/plugins/local_auth_ios/ios" media_extension: @@ -387,6 +392,7 @@ SPEC CHECKSUMS: image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 integration_test: 13825b8a9334a850581300559b8839134b124670 + isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d @@ -405,7 +411,7 @@ SPEC CHECKSUMS: open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d003810dd..4c137dda2 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -16,7 +16,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; }; + DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -213,7 +213,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - DA6BE5E826B3BC8600656280 /* (null) in Resources */, + DA6BE5E826B3BC8600656280 /* BuildFile in Resources */, 277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -291,6 +291,7 @@ "${BUILT_PRODUCTS_DIR}/image_editor_common/image_editor_common.framework", "${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework", "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", + "${BUILT_PRODUCTS_DIR}/isar_flutter_libs/isar_flutter_libs.framework", "${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework", "${BUILT_PRODUCTS_DIR}/local_auth_ios/local_auth_ios.framework", "${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework", @@ -371,6 +372,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor_common.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/isar_flutter_libs.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework", From 22fd204e11234b66a4e918eb17b84ff40cbab40c Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 20:25:16 +0530 Subject: [PATCH 03/23] Replace ObjectBox with Isar --- lib/core/configuration.dart | 4 +- lib/db/embeddings_db.dart | 55 ++ lib/db/object_box.dart | 27 - lib/models/embedding.dart | 42 +- lib/models/embedding.g.dart | 756 ++++++++++++++++++ lib/objectbox-model.json | 47 -- lib/objectbox.g.dart | 171 ---- .../semantic_search/embedding_store.dart | 22 +- .../semantic_search/frameworks/ggml.dart | 5 - .../frameworks/ml_framework.dart | 3 - .../semantic_search/frameworks/onnx/onnx.dart | 5 - .../semantic_search_service.dart | 47 +- pubspec.lock | 40 - pubspec.yaml | 3 - 14 files changed, 879 insertions(+), 348 deletions(-) create mode 100644 lib/db/embeddings_db.dart delete mode 100644 lib/db/object_box.dart create mode 100644 lib/models/embedding.g.dart delete mode 100644 lib/objectbox-model.json delete mode 100644 lib/objectbox.g.dart diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index a77099dc3..6b4e1b0ea 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -10,9 +10,9 @@ import 'package:photos/core/constants.dart'; import 'package:photos/core/error-reporting/super_logging.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/collections_db.dart'; +import "package:photos/db/embeddings_db.dart"; import 'package:photos/db/files_db.dart'; import 'package:photos/db/memories_db.dart'; -import "package:photos/db/object_box.dart"; import 'package:photos/db/public_keys_db.dart'; import 'package:photos/db/trash_db.dart'; import 'package:photos/db/upload_locks_db.dart'; @@ -159,7 +159,7 @@ class Configuration { _secretKey = null; await FilesDB.instance.clearTable(); SemanticSearchService.instance.hasInitialized - ? await ObjectBox.instance.clearTable() + ? await EmbeddingsDB.instance.clearTable() : null; await CollectionsDB.instance.clearTable(); await MemoriesDB.instance.clearTable(); diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart new file mode 100644 index 000000000..4d030a6cf --- /dev/null +++ b/lib/db/embeddings_db.dart @@ -0,0 +1,55 @@ +import "package:isar/isar.dart"; +import 'package:path_provider/path_provider.dart'; +import "package:photos/models/embedding.dart"; + +class EmbeddingsDB { + late final Isar _isar; + + EmbeddingsDB._privateConstructor(); + + static final EmbeddingsDB instance = EmbeddingsDB._privateConstructor(); + + Future init() async { + final dir = await getApplicationDocumentsDirectory(); + _isar = await Isar.open( + [EmbeddingSchema], + directory: dir.path, + ); + } + + Future clearTable() async { + await _isar.clear(); + } + + Stream> getStream(Model model) { + return _isar.embeddings.filter().modelEqualTo(model).watch(); + } + + Future> getAll(Model model) async { + return _isar.embeddings.filter().modelEqualTo(model).findAll(); + } + + Future put(Embedding embedding) { + return _isar.writeTxn(() async { + await _isar.embeddings.put(embedding); + }); + } + + Future putMany(List embeddings) { + return _isar.writeTxn(() async { + await _isar.embeddings.putAll(embeddings); + }); + } + + Future> getUnsyncedEmbeddings() async { + return await _isar.embeddings.filter().updationTimeEqualTo(null).findAll(); + } + + Future deleteAllForModel(Model model) async { + await _isar.writeTxn(() async { + final embeddings = + await _isar.embeddings.filter().modelEqualTo(model).findAll(); + await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); + }); + } +} diff --git a/lib/db/object_box.dart b/lib/db/object_box.dart deleted file mode 100644 index 635810092..000000000 --- a/lib/db/object_box.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; -import "package:photos/models/embedding.dart"; -import "package:photos/objectbox.g.dart"; // created by `flutter pub run build_runner build` - -class ObjectBox { - /// The Store of this app. - late final Store store; - - ObjectBox._privateConstructor(); - - static final ObjectBox instance = ObjectBox._privateConstructor(); - - Future init() async { - final docsDir = await getApplicationDocumentsDirectory(); - // Future openStore() {...} is defined in the generated objectbox.g.dart - store = await openStore(directory: p.join(docsDir.path, "object-box-store")); - } - - Future clearTable() async { - getEmbeddingBox().removeAll(); - } - - Box getEmbeddingBox() { - return store.box(); - } -} diff --git a/lib/models/embedding.dart b/lib/models/embedding.dart index 4e35bce99..ddf6c33f8 100644 --- a/lib/models/embedding.dart +++ b/lib/models/embedding.dart @@ -1,12 +1,15 @@ import "dart:convert"; -import "package:objectbox/objectbox.dart"; +import "package:isar/isar.dart"; -@Entity() +part 'embedding.g.dart'; + +@collection class Embedding { - @Id(assignable: true) + Id id = Isar.autoIncrement; // you can also use id = null to auto increment final int fileID; - final String model; + @enumerated + final Model model; final List embedding; int? updationTime; @@ -25,3 +28,34 @@ class Embedding { return jsonEncode(embedding); } } + +enum Model { + onnxClip, + ggmlClip, +} + +extension ModelExtension on Model { + String get name => serialize(this); +} + +String serialize(Model model) { + switch (model) { + case Model.onnxClip: + return 'onnx-clip'; + case Model.ggmlClip: + return 'ggml-clip'; + default: + throw Exception('$model is not a valid Model'); + } +} + +Model deserialize(String model) { + switch (model) { + case 'onnx-clip': + return Model.onnxClip; + case 'ggml-clip': + return Model.ggmlClip; + default: + throw Exception('$model is not a valid Model'); + } +} diff --git a/lib/models/embedding.g.dart b/lib/models/embedding.g.dart new file mode 100644 index 000000000..3f8fcfa07 --- /dev/null +++ b/lib/models/embedding.g.dart @@ -0,0 +1,756 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'embedding.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetEmbeddingCollection on Isar { + IsarCollection get embeddings => this.collection(); +} + +const EmbeddingSchema = CollectionSchema( + name: r'Embedding', + id: -8064100183150254587, + properties: { + r'embedding': PropertySchema( + id: 0, + name: r'embedding', + type: IsarType.doubleList, + ), + r'fileID': PropertySchema( + id: 1, + name: r'fileID', + type: IsarType.long, + ), + r'model': PropertySchema( + id: 2, + name: r'model', + type: IsarType.byte, + enumMap: _EmbeddingmodelEnumValueMap, + ), + r'updationTime': PropertySchema( + id: 3, + name: r'updationTime', + type: IsarType.long, + ) + }, + estimateSize: _embeddingEstimateSize, + serialize: _embeddingSerialize, + deserialize: _embeddingDeserialize, + deserializeProp: _embeddingDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _embeddingGetId, + getLinks: _embeddingGetLinks, + attach: _embeddingAttach, + version: '3.1.0+1', +); + +int _embeddingEstimateSize( + Embedding object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.embedding.length * 8; + return bytesCount; +} + +void _embeddingSerialize( + Embedding object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeDoubleList(offsets[0], object.embedding); + writer.writeLong(offsets[1], object.fileID); + writer.writeByte(offsets[2], object.model.index); + writer.writeLong(offsets[3], object.updationTime); +} + +Embedding _embeddingDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Embedding( + embedding: reader.readDoubleList(offsets[0]) ?? [], + fileID: reader.readLong(offsets[1]), + model: _EmbeddingmodelValueEnumMap[reader.readByteOrNull(offsets[2])] ?? + Model.onnxClip, + updationTime: reader.readLongOrNull(offsets[3]), + ); + object.id = id; + return object; +} + +P _embeddingDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readDoubleList(offset) ?? []) as P; + case 1: + return (reader.readLong(offset)) as P; + case 2: + return (_EmbeddingmodelValueEnumMap[reader.readByteOrNull(offset)] ?? + Model.onnxClip) as P; + case 3: + return (reader.readLongOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _EmbeddingmodelEnumValueMap = { + 'onnxClip': 0, + 'ggmlClip': 1, +}; +const _EmbeddingmodelValueEnumMap = { + 0: Model.onnxClip, + 1: Model.ggmlClip, +}; + +Id _embeddingGetId(Embedding object) { + return object.id; +} + +List> _embeddingGetLinks(Embedding object) { + return []; +} + +void _embeddingAttach(IsarCollection col, Id id, Embedding object) { + object.id = id; +} + +extension EmbeddingQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension EmbeddingQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension EmbeddingQueryFilter + on QueryBuilder { + QueryBuilder + embeddingElementEqualTo( + double value, { + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'embedding', + value: value, + epsilon: epsilon, + )); + }); + } + + QueryBuilder + embeddingElementGreaterThan( + double value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'embedding', + value: value, + epsilon: epsilon, + )); + }); + } + + QueryBuilder + embeddingElementLessThan( + double value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'embedding', + value: value, + epsilon: epsilon, + )); + }); + } + + QueryBuilder + embeddingElementBetween( + double lower, + double upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'embedding', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + epsilon: epsilon, + )); + }); + } + + QueryBuilder + embeddingLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder embeddingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder + embeddingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder + embeddingLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + embeddingLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder + embeddingLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'embedding', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder fileIDEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'fileID', + value: value, + )); + }); + } + + QueryBuilder fileIDGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'fileID', + value: value, + )); + }); + } + + QueryBuilder fileIDLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'fileID', + value: value, + )); + }); + } + + QueryBuilder fileIDBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'fileID', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder modelEqualTo( + Model value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'model', + value: value, + )); + }); + } + + QueryBuilder modelGreaterThan( + Model value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'model', + value: value, + )); + }); + } + + QueryBuilder modelLessThan( + Model value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'model', + value: value, + )); + }); + } + + QueryBuilder modelBetween( + Model lower, + Model upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'model', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + updationTimeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'updationTime', + )); + }); + } + + QueryBuilder + updationTimeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'updationTime', + )); + }); + } + + QueryBuilder updationTimeEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'updationTime', + value: value, + )); + }); + } + + QueryBuilder + updationTimeGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'updationTime', + value: value, + )); + }); + } + + QueryBuilder + updationTimeLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'updationTime', + value: value, + )); + }); + } + + QueryBuilder updationTimeBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'updationTime', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension EmbeddingQueryObject + on QueryBuilder {} + +extension EmbeddingQueryLinks + on QueryBuilder {} + +extension EmbeddingQuerySortBy on QueryBuilder { + QueryBuilder sortByFileID() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileID', Sort.asc); + }); + } + + QueryBuilder sortByFileIDDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileID', Sort.desc); + }); + } + + QueryBuilder sortByModel() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.asc); + }); + } + + QueryBuilder sortByModelDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.desc); + }); + } + + QueryBuilder sortByUpdationTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updationTime', Sort.asc); + }); + } + + QueryBuilder sortByUpdationTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updationTime', Sort.desc); + }); + } +} + +extension EmbeddingQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByFileID() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileID', Sort.asc); + }); + } + + QueryBuilder thenByFileIDDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileID', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByModel() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.asc); + }); + } + + QueryBuilder thenByModelDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.desc); + }); + } + + QueryBuilder thenByUpdationTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updationTime', Sort.asc); + }); + } + + QueryBuilder thenByUpdationTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updationTime', Sort.desc); + }); + } +} + +extension EmbeddingQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByEmbedding() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'embedding'); + }); + } + + QueryBuilder distinctByFileID() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fileID'); + }); + } + + QueryBuilder distinctByModel() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'model'); + }); + } + + QueryBuilder distinctByUpdationTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'updationTime'); + }); + } +} + +extension EmbeddingQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder, QQueryOperations> embeddingProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'embedding'); + }); + } + + QueryBuilder fileIDProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fileID'); + }); + } + + QueryBuilder modelProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'model'); + }); + } + + QueryBuilder updationTimeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'updationTime'); + }); + } +} diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json deleted file mode 100644 index 72d32c621..000000000 --- a/lib/objectbox-model.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", - "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", - "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", - "entities": [ - { - "id": "1:4067035246682038114", - "lastPropertyId": "4:7974898435327252398", - "name": "Embedding", - "properties": [ - { - "id": "1:2902120230153008095", - "name": "fileID", - "type": 6, - "flags": 129 - }, - { - "id": "2:5644004076892986076", - "name": "model", - "type": 9 - }, - { - "id": "3:4818114203635230783", - "name": "embedding", - "type": 29 - }, - { - "id": "4:7974898435327252398", - "name": "updationTime", - "type": 6 - } - ], - "relations": [] - } - ], - "lastEntityId": "1:4067035246682038114", - "lastIndexId": "0:0", - "lastRelationId": "0:0", - "lastSequenceId": "0:0", - "modelVersion": 5, - "modelVersionParserMinimum": 5, - "retiredEntityUids": [], - "retiredIndexUids": [], - "retiredPropertyUids": [], - "retiredRelationUids": [], - "version": 1 -} \ No newline at end of file diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart deleted file mode 100644 index 9b0fc67c8..000000000 --- a/lib/objectbox.g.dart +++ /dev/null @@ -1,171 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// This code was generated by ObjectBox. To update it run the generator again: -// With a Flutter package, run `flutter pub run build_runner build`. -// With a Dart package, run `dart run build_runner build`. -// See also https://docs.objectbox.io/getting-started#generate-objectbox-code - -// ignore_for_file: camel_case_types, depend_on_referenced_packages -// coverage:ignore-file - -import 'dart:typed_data'; - -import 'package:flat_buffers/flat_buffers.dart' as fb; -import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality -import 'package:objectbox/objectbox.dart'; -import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; - -import 'package:photos/models/embedding.dart'; - -export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file - -final _entities = [ - ModelEntity( - id: const IdUid(1, 4067035246682038114), - name: 'Embedding', - lastPropertyId: const IdUid(4, 7974898435327252398), - flags: 0, - properties: [ - ModelProperty( - id: const IdUid(1, 2902120230153008095), - name: 'fileID', - type: 6, - flags: 129, - ), - ModelProperty( - id: const IdUid(2, 5644004076892986076), - name: 'model', - type: 9, - flags: 0, - ), - ModelProperty( - id: const IdUid(3, 4818114203635230783), - name: 'embedding', - type: 29, - flags: 0, - ), - ModelProperty( - id: const IdUid(4, 7974898435327252398), - name: 'updationTime', - type: 6, - flags: 0, - ), - ], - relations: [], - backlinks: [], - ), -]; - -/// Shortcut for [Store.new] that passes [getObjectBoxModel] and for Flutter -/// apps by default a [directory] using `defaultStoreDirectory()` from the -/// ObjectBox Flutter library. -/// -/// Note: for desktop apps it is recommended to specify a unique [directory]. -/// -/// See [Store.new] for an explanation of all parameters. -Future openStore({ - String? directory, - int? maxDBSizeInKB, - int? fileMode, - int? maxReaders, - bool queriesCaseSensitiveDefault = true, - String? macosApplicationGroup, -}) async => - Store( - getObjectBoxModel(), - directory: directory ?? (await defaultStoreDirectory()).path, - maxDBSizeInKB: maxDBSizeInKB, - fileMode: fileMode, - maxReaders: maxReaders, - queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, - macosApplicationGroup: macosApplicationGroup, - ); - -/// Returns the ObjectBox model definition for this project for use with -/// [Store.new]. -ModelDefinition getObjectBoxModel() { - final model = ModelInfo( - entities: _entities, - lastEntityId: const IdUid(1, 4067035246682038114), - lastIndexId: const IdUid(0, 0), - lastRelationId: const IdUid(0, 0), - lastSequenceId: const IdUid(0, 0), - retiredEntityUids: const [], - retiredIndexUids: const [], - retiredPropertyUids: const [], - retiredRelationUids: const [], - modelVersion: 5, - modelVersionParserMinimum: 5, - version: 1, - ); - - final bindings = { - Embedding: EntityDefinition( - model: _entities[0], - toOneRelations: (Embedding object) => [], - toManyRelations: (Embedding object) => {}, - getId: (Embedding object) => object.fileID, - setId: (Embedding object, int id) { - if (object.fileID != id) { - throw ArgumentError('Field Embedding.fileID is read-only ' - '(final or getter-only) and it was declared to be self-assigned. ' - 'However, the currently inserted object (.fileID=${object.fileID}) ' - "doesn't match the inserted ID (ID $id). " - 'You must assign an ID before calling [box.put()].'); - } - }, - objectToFB: (Embedding object, fb.Builder fbb) { - final modelOffset = fbb.writeString(object.model); - final embeddingOffset = fbb.writeListFloat64(object.embedding); - fbb.startTable(5); - fbb.addInt64(0, object.fileID); - fbb.addOffset(1, modelOffset); - fbb.addOffset(2, embeddingOffset); - fbb.addInt64(3, object.updationTime); - fbb.finish(fbb.endTable()); - return object.fileID; - }, - objectFromFB: (Store store, ByteData fbData) { - final buffer = fb.BufferContext(fbData); - final rootOffset = buffer.derefObject(0); - final fileIDParam = - const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); - final modelParam = const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 6, ''); - final embeddingParam = - const fb.ListReader(fb.Float64Reader(), lazy: false) - .vTableGet(buffer, rootOffset, 8, []); - final updationTimeParam = - const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 10); - final object = Embedding( - fileID: fileIDParam, - model: modelParam, - embedding: embeddingParam, - updationTime: updationTimeParam, - ); - - return object; - }, - ), - }; - - return ModelDefinition(model, bindings); -} - -/// [Embedding] entity fields to define ObjectBox queries. -class Embedding_ { - /// see [Embedding.fileID] - static final fileID = - QueryIntegerProperty(_entities[0].properties[0]); - - /// see [Embedding.model] - static final model = - QueryStringProperty(_entities[0].properties[1]); - - /// see [Embedding.embedding] - static final embedding = - QueryDoubleVectorProperty(_entities[0].properties[2]); - - /// see [Embedding.updationTime] - static final updationTime = - QueryIntegerProperty(_entities[0].properties[3]); -} diff --git a/lib/services/semantic_search/embedding_store.dart b/lib/services/semantic_search/embedding_store.dart index e269ccd14..a1a9b6821 100644 --- a/lib/services/semantic_search/embedding_store.dart +++ b/lib/services/semantic_search/embedding_store.dart @@ -5,11 +5,10 @@ import "dart:typed_data"; import "package:computer/computer.dart"; import "package:logging/logging.dart"; import "package:photos/core/network/network.dart"; +import "package:photos/db/embeddings_db.dart"; import "package:photos/db/files_db.dart"; -import "package:photos/db/object_box.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; -import "package:photos/objectbox.g.dart"; import "package:photos/services/semantic_search/remote_embedding.dart"; import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/file_download_util.dart"; @@ -20,7 +19,7 @@ class EmbeddingStore { static final EmbeddingStore instance = EmbeddingStore._privateConstructor(); - static const kEmbeddingsSyncTimeKey = "sync_time_embeddings"; + static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v2"; final _logger = Logger("EmbeddingStore"); final _dio = NetworkClient.instance.enteDio; @@ -50,12 +49,7 @@ class EmbeddingStore { } Future pushEmbeddings() async { - final query = (ObjectBox.instance - .getEmbeddingBox() - .query(Embedding_.updationTime.isNull())) - .build(); - final pendingItems = query.find(); - query.close(); + final pendingItems = await EmbeddingsDB.instance.getUnsyncedEmbeddings(); for (final item in pendingItems) { final file = await FilesDB.instance.getAnyUploadedFile(item.fileID); await _pushEmbedding(file!, item); @@ -63,7 +57,7 @@ class EmbeddingStore { } Future storeEmbedding(EnteFile file, Embedding embedding) async { - ObjectBox.instance.getEmbeddingBox().put(embedding); + await EmbeddingsDB.instance.put(embedding); unawaited(_pushEmbedding(file, embedding)); } @@ -82,14 +76,14 @@ class EmbeddingStore { "/embeddings", data: { "fileID": embedding.fileID, - "model": embedding.model, + "model": embedding.model.name, "encryptedEmbedding": encryptedData, "decryptionHeader": header, }, ); final updationTime = response.data["updationTime"]; embedding.updationTime = updationTime; - ObjectBox.instance.getEmbeddingBox().put(embedding); + await EmbeddingsDB.instance.put(embedding); } catch (e, s) { _logger.severe(e, s); } @@ -148,7 +142,7 @@ class EmbeddingStore { }, ); _logger.info("${embeddings.length} embeddings decoded"); - await ObjectBox.instance.getEmbeddingBox().putManyAsync(embeddings); + await EmbeddingsDB.instance.putMany(embeddings); await _preferences.setInt( kEmbeddingsSyncTimeKey, embeddings.last.updationTime!, @@ -179,7 +173,7 @@ Future> decodeEmbeddings(Map args) async { embeddings.add( Embedding( fileID: input.embedding.fileID, - model: input.embedding.model, + model: deserialize(input.embedding.model), embedding: decodedEmbedding, updationTime: input.embedding.updatedAt, ), diff --git a/lib/services/semantic_search/frameworks/ggml.dart b/lib/services/semantic_search/frameworks/ggml.dart index eaf7b1871..e4903091c 100644 --- a/lib/services/semantic_search/frameworks/ggml.dart +++ b/lib/services/semantic_search/frameworks/ggml.dart @@ -10,11 +10,6 @@ class GGML extends MLFramework { final _computer = Computer.shared(); final _logger = Logger("GGML"); - - @override - String getFrameworkName() { - return "ggml"; - } @override String getImageModelRemotePath() { diff --git a/lib/services/semantic_search/frameworks/ml_framework.dart b/lib/services/semantic_search/frameworks/ml_framework.dart index dead6ba7d..ffb4dbaee 100644 --- a/lib/services/semantic_search/frameworks/ml_framework.dart +++ b/lib/services/semantic_search/frameworks/ml_framework.dart @@ -11,9 +11,6 @@ abstract class MLFramework { final _logger = Logger("MLFramework"); - /// Returns the name of the framework - String getFrameworkName(); - /// Returns the path of the Image Model hosted remotely String getImageModelRemotePath(); diff --git a/lib/services/semantic_search/frameworks/onnx/onnx.dart b/lib/services/semantic_search/frameworks/onnx/onnx.dart index 9b12249be..0a18c66e6 100644 --- a/lib/services/semantic_search/frameworks/onnx/onnx.dart +++ b/lib/services/semantic_search/frameworks/onnx/onnx.dart @@ -17,11 +17,6 @@ class ONNX extends MLFramework { int _textEncoderAddress = 0; int _imageEncoderAddress = 0; - @override - String getFrameworkName() { - return "onnx"; - } - @override String getImageModelRemotePath() { return kModelBucketEndpoint + kImageModel; diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 7c4771898..5b076e058 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -6,15 +6,15 @@ import "package:logging/logging.dart"; import "package:photos/core/cache/lru_map.dart"; import "package:photos/core/configuration.dart"; import "package:photos/core/event_bus.dart"; +import "package:photos/db/embeddings_db.dart"; import "package:photos/db/files_db.dart"; -import "package:photos/db/object_box.dart"; import "package:photos/events/diff_sync_complete_event.dart"; import 'package:photos/events/embedding_updated_event.dart'; import "package:photos/events/file_uploaded_event.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; -import "package:photos/objectbox.g.dart"; import "package:photos/services/semantic_search/embedding_store.dart"; +import "package:photos/services/semantic_search/frameworks/ggml.dart"; import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; import 'package:photos/services/semantic_search/frameworks/onnx/onnx.dart'; import "package:photos/utils/local_settings.dart"; @@ -32,11 +32,12 @@ class SemanticSearchService { static const kEmbeddingLength = 512; static const kScoreThreshold = 0.23; static const kShouldPushEmbeddings = true; + static const kCurrentModel = Model.onnxClip; final _logger = Logger("SemanticSearchService"); final _queue = Queue(); final _cachedEmbeddings = []; - final _mlFramework = ONNX(); + final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML(); final _frameworkInitialization = Completer(); bool _hasInitialized = false; @@ -56,9 +57,9 @@ class SemanticSearchService { return; } _hasInitialized = true; - await ObjectBox.instance.init(); + await EmbeddingsDB.instance.init(); await EmbeddingStore.instance.init(); - _setupCachedEmbeddings(); + await _setupCachedEmbeddings(); Bus.instance.on().listen((event) { // Diff sync is complete, we can now pull embeddings from remote unawaited(sync()); @@ -132,29 +133,21 @@ class SemanticSearchService { } Future clearIndexes() async { - await ObjectBox.instance - .getEmbeddingBox() - .query( - Embedding_.model.equals( - _mlFramework.getFrameworkName() + "-" + kModelName, - ), - ) - .build() - .removeAsync(); - _logger.info("Indexes cleared for ${_mlFramework.getFrameworkName()}"); + await EmbeddingsDB.instance.deleteAllForModel(kCurrentModel); + _logger.info("Indexes cleared for $kCurrentModel"); } - void _setupCachedEmbeddings() { - ObjectBox.instance - .getEmbeddingBox() - .query( - Embedding_.model.equals( - _mlFramework.getFrameworkName() + "-" + kModelName, - ), - ) - .watch(triggerImmediately: true) - .map((query) => query.find()) - .listen((embeddings) { + Future _setupCachedEmbeddings() async { + _logger.info("Setting up cached embeddings"); + final startTime = DateTime.now(); + final cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); + final endTime = DateTime.now(); + _logger.info( + "Loading ${cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", + ); + _cachedEmbeddings.addAll(cachedEmbeddings); + _logger.info("Cached embeddings: " + _cachedEmbeddings.length.toString()); + EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) { _logger.info("Updated embeddings: " + embeddings.length.toString()); _cachedEmbeddings.clear(); _cachedEmbeddings.addAll(embeddings); @@ -279,7 +272,7 @@ class SemanticSearchService { final embedding = Embedding( fileID: file.uploadedFileID!, - model: _mlFramework.getFrameworkName() + "-" + kModelName, + model: kCurrentModel, embedding: result, ); await EmbeddingStore.instance.storeEmbedding( diff --git a/pubspec.lock b/pubspec.lock index dcac2c4d2..5129dcf80 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -315,14 +315,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - cryptography: - dependency: transitive - description: - name: cryptography - sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 - url: "https://pub.dev" - source: hosted - version: "2.5.0" csslib: dependency: transitive description: @@ -596,14 +588,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - flat_buffers: - dependency: transitive - description: - name: flat_buffers - sha256: "23e2ced0d8e8ecdffbd9f267f49a668c74438393b9acaeac1c724123e3764263" - url: "https://pub.dev" - source: hosted - version: "2.0.5" flutter: dependency: "direct main" description: flutter @@ -1385,30 +1369,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - objectbox: - dependency: "direct main" - description: - name: objectbox - sha256: "4b645c71771b87188442143a50c55ab238a8e60fe367b6a0968c0842292ffb30" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - objectbox_flutter_libs: - dependency: "direct main" - description: - name: objectbox_flutter_libs - sha256: e9a3d8e3ce0d47d6fc942921ef0444a238cd4258e8fcefe13b994cf984a8bf61 - url: "https://pub.dev" - source: hosted - version: "2.3.1" - objectbox_generator: - dependency: "direct dev" - description: - name: objectbox_generator - sha256: aaffef7eb51b4d911bb00a7c52b19b55fe3e5a69de8ec56552cf35550a1e9beb - url: "https://pub.dev" - source: hosted - version: "2.3.1" octo_image: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 419fd64be..91faedaf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -117,8 +117,6 @@ dependencies: move_to_background: ^1.0.2 # open_file: ^3.2.1 - objectbox: ^2.3.1 - objectbox_flutter_libs: any onnxruntime: git: "https://github.com/ente-io/onnxruntime.git" open_mail_app: ^0.4.5 @@ -196,7 +194,6 @@ dev_dependencies: sdk: flutter isar_generator: ^3.1.0+1 json_serializable: ^6.6.1 - objectbox_generator: any test: ^1.22.0 flutter_icons: From b756f33f18e992b97c4b03cc724fc142880863ab Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 20:50:18 +0530 Subject: [PATCH 04/23] TODO: Clear deprecated store --- lib/db/embeddings_db.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart index 4d030a6cf..78f3c4420 100644 --- a/lib/db/embeddings_db.dart +++ b/lib/db/embeddings_db.dart @@ -1,3 +1,5 @@ +import "dart:io"; + import "package:isar/isar.dart"; import 'package:path_provider/path_provider.dart'; import "package:photos/models/embedding.dart"; @@ -15,6 +17,7 @@ class EmbeddingsDB { [EmbeddingSchema], directory: dir.path, ); + // TODO: _clearDeprecatedStore(dir); } Future clearTable() async { @@ -52,4 +55,11 @@ class EmbeddingsDB { await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); }); } + + Future _clearDeprecatedStore(Directory dir) async { + final deprecatedStore = Directory(dir.path + "/object-box-store"); + if (await deprecatedStore.exists()) { + await deprecatedStore.delete(recursive: true); + } + } } From 15b3621ab421e30180d07ce61048b425a9d9aff9 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 20:50:38 +0530 Subject: [PATCH 05/23] v0.8.30 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 94c64e20f..d0a4fd9d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.28+548 +version: 0.8.30+550 environment: sdk: ">=3.0.0 <4.0.0" From 4ffa600da640a9853d68d2a4a7934434dea115aa Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 4 Jan 2024 20:52:07 +0530 Subject: [PATCH 06/23] Enable SSS in the background --- lib/main.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 81982fce5..8e553280b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -193,9 +193,7 @@ Future _init(bool isBackground, {String via = ''}) async { }); } unawaited(FeatureFlagService.instance.init()); - if (!isBackground) { - unawaited(SemanticSearchService.instance.init()); - } + unawaited(SemanticSearchService.instance.init()); // Can not including existing tf/ml binaries as they are not being built // from source. // See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819 From 3554dc382760d152ec45b6bd10be027300028208 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 10:59:25 +0530 Subject: [PATCH 07/23] Remove debug code --- lib/services/semantic_search/semantic_search_service.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 5b076e058..156924f5e 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -265,10 +265,6 @@ class SemanticSearchService { _logger.severe("Discovered incorrect embedding for $file - $result"); return; } - // dev.log(result.toString()); - // dev.log(computeScore(result, webEmbedding).toString()); - // dev.log(computeScore(result, pyEmbedding).toString()); - // dev.log(computeScore(pyEmbedding, webEmbedding).toString()); final embedding = Embedding( fileID: file.uploadedFileID!, From 30904d0d5988c6a58fc603daf0f271e226305d15 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 10:59:31 +0530 Subject: [PATCH 08/23] Update iOS config files --- ios/Podfile.lock | 10 ---------- ios/Runner.xcodeproj/project.pbxproj | 4 ---- 2 files changed, 14 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 878a0627d..ee3a1d132 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -135,10 +135,6 @@ PODS: - nanopb/encode (= 2.30909.1) - nanopb/decode (2.30909.1) - nanopb/encode (2.30909.1) - - ObjectBox (1.9.0) - - objectbox_flutter_libs (0.0.1): - - Flutter - - ObjectBox (= 1.9.0) - onnxruntime (0.0.1): - Flutter - onnxruntime-objc (= 1.15.1) @@ -228,7 +224,6 @@ DEPENDENCIES: - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - motionphoto (from `.symlinks/plugins/motionphoto/ios`) - move_to_background (from `.symlinks/plugins/move_to_background/ios`) - - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) - onnxruntime (from `.symlinks/plugins/onnxruntime/ios`) - open_mail_app (from `.symlinks/plugins/open_mail_app/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) @@ -260,7 +255,6 @@ SPEC REPOS: - libwebp - Mantle - nanopb - - ObjectBox - onnxruntime-c - onnxruntime-objc - OrderedSet @@ -327,8 +321,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/motionphoto/ios" move_to_background: :path: ".symlinks/plugins/move_to_background/ios" - objectbox_flutter_libs: - :path: ".symlinks/plugins/objectbox_flutter_libs/ios" onnxruntime: :path: ".symlinks/plugins/onnxruntime/ios" open_mail_app: @@ -403,8 +395,6 @@ SPEC CHECKSUMS: motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16 move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 - ObjectBox: e7ff611291a0663380e0736b46786bcd077294ff - objectbox_flutter_libs: 0948d6feb7de4f7edaebc7a898b9e85b7fc2bc89 onnxruntime: e9346181d75b8dea8733bdae512a22c298962e00 onnxruntime-c: ebdcfd8650bcbd10121c125262f99dea681b92a3 onnxruntime-objc: ae7acec7a3d03eaf072d340afed7a35635c1c2a6 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4c137dda2..03a95b930 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -301,7 +301,6 @@ "${BUILT_PRODUCTS_DIR}/motionphoto/motionphoto.framework", "${BUILT_PRODUCTS_DIR}/move_to_background/move_to_background.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", - "${BUILT_PRODUCTS_DIR}/objectbox_flutter_libs/objectbox_flutter_libs.framework", "${BUILT_PRODUCTS_DIR}/open_mail_app/open_mail_app.framework", "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", @@ -318,7 +317,6 @@ "${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework", "${BUILT_PRODUCTS_DIR}/volume_controller/volume_controller.framework", "${BUILT_PRODUCTS_DIR}/wakelock_plus/wakelock_plus.framework", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ObjectBox/ObjectBox.framework/ObjectBox", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Ass.framework/Ass", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Avcodec.framework/Avcodec", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Avfilter.framework/Avfilter", @@ -382,7 +380,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motionphoto.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/move_to_background.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/objectbox_flutter_libs.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_mail_app.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", @@ -399,7 +396,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/volume_controller.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock_plus.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectBox.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Ass.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Avcodec.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Avfilter.framework", From 51af506f14bbe8f4df9ec0c319c73aa0a67e44f3 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 11:00:50 +0530 Subject: [PATCH 09/23] Handle cases where we could not fetch the thumbnail --- lib/services/semantic_search/semantic_search_service.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 156924f5e..edc3d6eab 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -258,7 +258,12 @@ class SemanticSearchService { return; } try { - final filePath = (await getThumbnailForUploadedFile(file))!.path; + final thumbnail = await getThumbnailForUploadedFile(file); + if (thumbnail == null) { + _logger.warning("Could not get thumbnail for $file"); + return; + } + final filePath = thumbnail.path; _logger.info("Running clip over $file"); final result = await _mlFramework.getImageEmbedding(filePath); if (result.length != kEmbeddingLength) { From 09d274778fb450b99a669ed1dde365ed79d2c5e4 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 15:14:50 +0530 Subject: [PATCH 10/23] Fetch only ONNX embeddings --- lib/services/semantic_search/embedding_store.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/semantic_search/embedding_store.dart b/lib/services/semantic_search/embedding_store.dart index a1a9b6821..7b80e4153 100644 --- a/lib/services/semantic_search/embedding_store.dart +++ b/lib/services/semantic_search/embedding_store.dart @@ -10,6 +10,7 @@ import "package:photos/db/files_db.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; import "package:photos/services/semantic_search/remote_embedding.dart"; +import "package:photos/services/semantic_search/semantic_search_service.dart"; import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/file_download_util.dart"; import "package:shared_preferences/shared_preferences.dart"; @@ -99,6 +100,7 @@ class EmbeddingStore { final response = await _dio.get( "/embeddings/diff", queryParameters: { + "model": SemanticSearchService.kCurrentModel.name, "sinceTime": sinceTime, "limit": limit, }, From bbed1c052a442940201b8a173356f761a5a9cec9 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 15:16:27 +0530 Subject: [PATCH 11/23] Inject model from upstream --- lib/services/semantic_search/embedding_store.dart | 12 ++++++------ .../semantic_search/semantic_search_service.dart | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/services/semantic_search/embedding_store.dart b/lib/services/semantic_search/embedding_store.dart index 7b80e4153..143cc59d6 100644 --- a/lib/services/semantic_search/embedding_store.dart +++ b/lib/services/semantic_search/embedding_store.dart @@ -10,7 +10,6 @@ import "package:photos/db/files_db.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; import "package:photos/services/semantic_search/remote_embedding.dart"; -import "package:photos/services/semantic_search/semantic_search_service.dart"; import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/file_download_util.dart"; import "package:shared_preferences/shared_preferences.dart"; @@ -34,15 +33,15 @@ class EmbeddingStore { _preferences = await SharedPreferences.getInstance(); } - Future pullEmbeddings() async { + Future pullEmbeddings(Model model) async { if (_syncStatus != null) { return _syncStatus!.future; } _syncStatus = Completer(); - var remoteEmbeddings = await _getRemoteEmbeddings(); + var remoteEmbeddings = await _getRemoteEmbeddings(model); await _storeRemoteEmbeddings(remoteEmbeddings.embeddings); while (remoteEmbeddings.hasMore) { - remoteEmbeddings = await _getRemoteEmbeddings(); + remoteEmbeddings = await _getRemoteEmbeddings(model); await _storeRemoteEmbeddings(remoteEmbeddings.embeddings); } _syncStatus!.complete(); @@ -90,7 +89,8 @@ class EmbeddingStore { } } - Future _getRemoteEmbeddings({ + Future _getRemoteEmbeddings( + Model model, { int limit = 500, }) async { final remoteEmbeddings = []; @@ -100,7 +100,7 @@ class EmbeddingStore { final response = await _dio.get( "/embeddings/diff", queryParameters: { - "model": SemanticSearchService.kCurrentModel.name, + "model": model.name, "sinceTime": sinceTime, "limit": limit, }, diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index edc3d6eab..5f21292fb 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -92,7 +92,7 @@ class SemanticSearchService { return; } _isSyncing = true; - await EmbeddingStore.instance.pullEmbeddings(); + await EmbeddingStore.instance.pullEmbeddings(kCurrentModel); await _backFill(); _isSyncing = false; } From f24faa040476b04ab452cfb5d5e13732bcee87d7 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 15:19:49 +0530 Subject: [PATCH 12/23] Subscribe to DB updates only when app is in foreground --- lib/main.dart | 4 +++- .../semantic_search_service.dart | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8e553280b..13522cc09 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -193,7 +193,9 @@ Future _init(bool isBackground, {String via = ''}) async { }); } unawaited(FeatureFlagService.instance.init()); - unawaited(SemanticSearchService.instance.init()); + unawaited( + SemanticSearchService.instance.init(isAppInForeground: !isBackground), + ); // Can not including existing tf/ml binaries as they are not being built // from source. // See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819 diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 5f21292fb..cdbfd9e83 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -48,7 +48,7 @@ class SemanticSearchService { get hasInitialized => _hasInitialized; - Future init() async { + Future init({bool isAppInForeground = true}) async { if (!LocalSettings.instance.hasEnabledMagicSearch()) { return; } @@ -59,7 +59,7 @@ class SemanticSearchService { _hasInitialized = true; await EmbeddingsDB.instance.init(); await EmbeddingStore.instance.init(); - await _setupCachedEmbeddings(); + await _setupCachedEmbeddings(isAppInForeground); Bus.instance.on().listen((event) { // Diff sync is complete, we can now pull embeddings from remote unawaited(sync()); @@ -137,7 +137,7 @@ class SemanticSearchService { _logger.info("Indexes cleared for $kCurrentModel"); } - Future _setupCachedEmbeddings() async { + Future _setupCachedEmbeddings(bool shouldListenForUpdates) async { _logger.info("Setting up cached embeddings"); final startTime = DateTime.now(); final cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); @@ -147,12 +147,14 @@ class SemanticSearchService { ); _cachedEmbeddings.addAll(cachedEmbeddings); _logger.info("Cached embeddings: " + _cachedEmbeddings.length.toString()); - EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) { - _logger.info("Updated embeddings: " + embeddings.length.toString()); - _cachedEmbeddings.clear(); - _cachedEmbeddings.addAll(embeddings); - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); + if (shouldListenForUpdates) { + EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) { + _logger.info("Updated embeddings: " + embeddings.length.toString()); + _cachedEmbeddings.clear(); + _cachedEmbeddings.addAll(embeddings); + Bus.instance.fire(EmbeddingUpdatedEvent()); + }); + } } Future _backFill() async { From 39affa085293329d0852b61228afb5b9a9967015 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 15:59:58 +0530 Subject: [PATCH 13/23] Add retrials for model downloads --- .../frameworks/ml_framework.dart | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/services/semantic_search/frameworks/ml_framework.dart b/lib/services/semantic_search/frameworks/ml_framework.dart index ffb4dbaee..2bf415881 100644 --- a/lib/services/semantic_search/frameworks/ml_framework.dart +++ b/lib/services/semantic_search/frameworks/ml_framework.dart @@ -8,6 +8,7 @@ import "package:photos/core/network/network.dart"; abstract class MLFramework { static const kImageEncoderEnabled = true; + static const kMaximumRetrials = 3; final _logger = Logger("MLFramework"); @@ -97,13 +98,26 @@ abstract class MLFramework { basename(getTextModelRemotePath()); } - Future _downloadFile(String url, String savePath) async { + Future _downloadFile( + String url, + String savePath, { + int trialCount = 1, + }) async { _logger.info("Downloading " + url); final existingFile = File(savePath); if (await existingFile.exists()) { await existingFile.delete(); } - await NetworkClient.instance.getDio().download(url, savePath); + try { + await NetworkClient.instance.getDio().download(url, savePath); + } catch (e, s) { + _logger.severe(e, s); + if (trialCount < kMaximumRetrials) { + return _downloadFile(url, savePath, trialCount: trialCount + 1); + } else { + rethrow; + } + } } Future getAccessiblePathForAsset( From 87fd7813040dd528fcd0f9bcf65c4e1e65aa6c36 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:05:03 +0530 Subject: [PATCH 14/23] Reassign variable instead of iterating over the list --- .../semantic_search/semantic_search_service.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index cdbfd9e83..5249e6a00 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -36,7 +36,6 @@ class SemanticSearchService { final _logger = Logger("SemanticSearchService"); final _queue = Queue(); - final _cachedEmbeddings = []; final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML(); final _frameworkInitialization = Completer(); @@ -44,6 +43,7 @@ class SemanticSearchService { bool _isComputingEmbeddings = false; bool _isSyncing = false; Future>? _ongoingRequest; + List _cachedEmbeddings = []; PendingQuery? _nextQuery; get hasInitialized => _hasInitialized; @@ -140,18 +140,16 @@ class SemanticSearchService { Future _setupCachedEmbeddings(bool shouldListenForUpdates) async { _logger.info("Setting up cached embeddings"); final startTime = DateTime.now(); - final cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); + _cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); final endTime = DateTime.now(); _logger.info( - "Loading ${cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", + "Loading ${_cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", ); - _cachedEmbeddings.addAll(cachedEmbeddings); _logger.info("Cached embeddings: " + _cachedEmbeddings.length.toString()); if (shouldListenForUpdates) { EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) { _logger.info("Updated embeddings: " + embeddings.length.toString()); - _cachedEmbeddings.clear(); - _cachedEmbeddings.addAll(embeddings); + _cachedEmbeddings = embeddings; Bus.instance.fire(EmbeddingUpdatedEvent()); }); } From 1d7e609bbe19cddfcf85b152f837c5ad13b91942 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:05:22 +0530 Subject: [PATCH 15/23] Remove unused variable --- lib/services/semantic_search/semantic_search_service.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 5249e6a00..b78951be8 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -28,7 +28,6 @@ class SemanticSearchService { static final Computer _computer = Computer.shared(); static final LRUMap> _queryCache = LRUMap(20); - static const kModelName = "clip"; static const kEmbeddingLength = 512; static const kScoreThreshold = 0.23; static const kShouldPushEmbeddings = true; From e50e6dc013d9252e8adddba36242de982597c129 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:18:46 +0530 Subject: [PATCH 16/23] Add log line --- lib/services/semantic_search/semantic_search_service.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index b78951be8..203729990 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -117,6 +117,7 @@ class SemanticSearchService { return _ongoingRequest!; } else { // If there's an ongoing request, create or replace the nextCompleter. + _logger.info("Queuing query $query"); await _nextQuery?.completer.future .timeout(const Duration(seconds: 0)); // Cancels the previous future. _nextQuery = PendingQuery(query, Completer>()); From 637f3c0f6652d301b3447862f95980b092469d8e Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:51:54 +0530 Subject: [PATCH 17/23] Remove reliance on Isar's watcher --- lib/db/embeddings_db.dart | 8 +++---- lib/main.dart | 4 +--- .../semantic_search_service.dart | 24 ++++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart index 78f3c4420..1d3ae16f6 100644 --- a/lib/db/embeddings_db.dart +++ b/lib/db/embeddings_db.dart @@ -2,6 +2,8 @@ import "dart:io"; import "package:isar/isar.dart"; import 'package:path_provider/path_provider.dart'; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/embedding_updated_event.dart"; import "package:photos/models/embedding.dart"; class EmbeddingsDB { @@ -24,10 +26,6 @@ class EmbeddingsDB { await _isar.clear(); } - Stream> getStream(Model model) { - return _isar.embeddings.filter().modelEqualTo(model).watch(); - } - Future> getAll(Model model) async { return _isar.embeddings.filter().modelEqualTo(model).findAll(); } @@ -35,12 +33,14 @@ class EmbeddingsDB { Future put(Embedding embedding) { return _isar.writeTxn(() async { await _isar.embeddings.put(embedding); + Bus.instance.fire(EmbeddingUpdatedEvent()); }); } Future putMany(List embeddings) { return _isar.writeTxn(() async { await _isar.embeddings.putAll(embeddings); + Bus.instance.fire(EmbeddingUpdatedEvent()); }); } diff --git a/lib/main.dart b/lib/main.dart index 13522cc09..8e553280b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -193,9 +193,7 @@ Future _init(bool isBackground, {String via = ''}) async { }); } unawaited(FeatureFlagService.instance.init()); - unawaited( - SemanticSearchService.instance.init(isAppInForeground: !isBackground), - ); + unawaited(SemanticSearchService.instance.init()); // Can not including existing tf/ml binaries as they are not being built // from source. // See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819 diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 203729990..d92f0a11a 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -17,6 +17,7 @@ import "package:photos/services/semantic_search/embedding_store.dart"; import "package:photos/services/semantic_search/frameworks/ggml.dart"; import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; import 'package:photos/services/semantic_search/frameworks/onnx/onnx.dart'; +import "package:photos/utils/debouncer.dart"; import "package:photos/utils/local_settings.dart"; import "package:photos/utils/thumbnail_util.dart"; @@ -32,11 +33,14 @@ class SemanticSearchService { static const kScoreThreshold = 0.23; static const kShouldPushEmbeddings = true; static const kCurrentModel = Model.onnxClip; + static const kDebounceDuration = Duration(milliseconds: 10000); final _logger = Logger("SemanticSearchService"); final _queue = Queue(); final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML(); final _frameworkInitialization = Completer(); + final _embeddingLoaderDebouncer = + Debouncer(kDebounceDuration, executionInterval: kDebounceDuration); bool _hasInitialized = false; bool _isComputingEmbeddings = false; @@ -47,7 +51,7 @@ class SemanticSearchService { get hasInitialized => _hasInitialized; - Future init({bool isAppInForeground = true}) async { + Future init() async { if (!LocalSettings.instance.hasEnabledMagicSearch()) { return; } @@ -58,7 +62,12 @@ class SemanticSearchService { _hasInitialized = true; await EmbeddingsDB.instance.init(); await EmbeddingStore.instance.init(); - await _setupCachedEmbeddings(isAppInForeground); + await _loadEmbeddings(); + Bus.instance.on().listen((event) { + _embeddingLoaderDebouncer.run(() async { + await _loadEmbeddings(); + }); + }); Bus.instance.on().listen((event) { // Diff sync is complete, we can now pull embeddings from remote unawaited(sync()); @@ -137,8 +146,8 @@ class SemanticSearchService { _logger.info("Indexes cleared for $kCurrentModel"); } - Future _setupCachedEmbeddings(bool shouldListenForUpdates) async { - _logger.info("Setting up cached embeddings"); + Future _loadEmbeddings() async { + _logger.info("Pulling cached embeddings"); final startTime = DateTime.now(); _cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); final endTime = DateTime.now(); @@ -146,13 +155,6 @@ class SemanticSearchService { "Loading ${_cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", ); _logger.info("Cached embeddings: " + _cachedEmbeddings.length.toString()); - if (shouldListenForUpdates) { - EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) { - _logger.info("Updated embeddings: " + embeddings.length.toString()); - _cachedEmbeddings = embeddings; - Bus.instance.fire(EmbeddingUpdatedEvent()); - }); - } } Future _backFill() async { From add4ad27004a4316f3f1a10a0a8c4297eee1eeb1 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:55:03 +0530 Subject: [PATCH 18/23] Reduce debounce duration --- lib/services/semantic_search/semantic_search_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index d92f0a11a..13cb66f9c 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -33,7 +33,7 @@ class SemanticSearchService { static const kScoreThreshold = 0.23; static const kShouldPushEmbeddings = true; static const kCurrentModel = Model.onnxClip; - static const kDebounceDuration = Duration(milliseconds: 10000); + static const kDebounceDuration = Duration(milliseconds: 4000); final _logger = Logger("SemanticSearchService"); final _queue = Queue(); From 4edebb9501a74fc4e64bb8086dd79a355e39c078 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 16:55:15 +0530 Subject: [PATCH 19/23] Update machine learning settings page --- lib/generated/intl/messages_en.dart | 2 +- lib/generated/l10n.dart | 4 ++-- lib/l10n/intl_en.arb | 2 +- lib/ui/settings/machine_learning_settings_page.dart | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 1872064d5..f0fc982cb 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -856,7 +856,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Machine learning"), "magicSearch": MessageLookupByLibrary.simpleMessage("Magic search"), "magicSearchDescription": MessageLookupByLibrary.simpleMessage( - "Please use our Desktop app to index the pending items in your library."), + "Please note that this will result in a higher bandwidth and battery usage until all items are indexed."), "manage": MessageLookupByLibrary.simpleMessage("Manage"), "manageDeviceStorage": MessageLookupByLibrary.simpleMessage("Manage device storage"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index c4b5a40ea..301641f61 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2866,10 +2866,10 @@ class S { ); } - /// `Please use our Desktop app to index the pending items in your library.` + /// `Please note that this will result in a higher bandwidth and battery usage until all items are indexed.` String get magicSearchDescription { return Intl.message( - 'Please use our Desktop app to index the pending items in your library.', + 'Please note that this will result in a higher bandwidth and battery usage until all items are indexed.', name: 'magicSearchDescription', desc: '', args: [], diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2f26f7da5..371801517 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -408,7 +408,7 @@ "manageDeviceStorage": "Manage device storage", "machineLearning": "Machine learning", "magicSearch": "Magic search", - "magicSearchDescription": "Please use our Desktop app to index the pending items in your library.", + "magicSearchDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.", "status": "Status", "indexedItems": "Indexed items", "pendingItems": "Pending items", diff --git a/lib/ui/settings/machine_learning_settings_page.dart b/lib/ui/settings/machine_learning_settings_page.dart index 158abd0f2..de40c7947 100644 --- a/lib/ui/settings/machine_learning_settings_page.dart +++ b/lib/ui/settings/machine_learning_settings_page.dart @@ -192,12 +192,12 @@ class _MagicSearchIndexStatsWidgetState Row( children: [ MenuSectionTitle(title: S.of(context).status), - // Expanded(child: Container()), - // _status!.pendingItems > 0 - // ? EnteLoadingWidget( - // color: getEnteColorScheme(context).fillMuted, - // ) - // : const SizedBox.shrink(), + Expanded(child: Container()), + _status!.pendingItems > 0 + ? EnteLoadingWidget( + color: getEnteColorScheme(context).fillMuted, + ) + : const SizedBox.shrink(), ], ), MenuItemWidget( From 529ad88a7067389d273f7cc7ed1db375169284ca Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 18:08:23 +0530 Subject: [PATCH 20/23] Render model loading status --- lib/generated/intl/messages_en.dart | 2 + lib/generated/l10n.dart | 10 +++++ lib/l10n/intl_en.arb | 6 +-- .../semantic_search_service.dart | 8 +++- .../machine_learning_settings_page.dart | 37 ++++++++++++++++++- 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index f0fc982cb..3febd6407 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -832,6 +832,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Loading gallery..."), "loadingMessage": MessageLookupByLibrary.simpleMessage("Loading your photos..."), + "loadingModel": + MessageLookupByLibrary.simpleMessage("Downloading models..."), "localGallery": MessageLookupByLibrary.simpleMessage("Local gallery"), "location": MessageLookupByLibrary.simpleMessage("Location"), "locationName": MessageLookupByLibrary.simpleMessage("Location name"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 301641f61..9f3136f8a 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2876,6 +2876,16 @@ class S { ); } + /// `Downloading models...` + String get loadingModel { + return Intl.message( + 'Downloading models...', + name: 'loadingModel', + desc: '', + args: [], + ); + } + /// `Status` String get status { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 371801517..20571d2e4 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -409,6 +409,7 @@ "machineLearning": "Machine learning", "magicSearch": "Magic search", "magicSearchDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.", + "loadingModel": "Downloading models...", "status": "Status", "indexedItems": "Indexed items", "pendingItems": "Pending items", @@ -704,7 +705,7 @@ "deleteAlbumsDialogBody": "This will delete all empty albums. This is useful when you want to reduce the clutter in your album list.", "deleteProgress": "Deleting {currentlyDeleting} / {totalCount}", "genericProgress": "Processing {currentlyProcessing} / {totalCount}", - "@genericProgress" : { + "@genericProgress": { "description": "Generic progress text to display when processing multiple items", "type": "text", "placeholders": { @@ -718,7 +719,6 @@ } } }, - "permanentlyDelete": "Permanently delete", "canOnlyCreateLinkForFilesOwnedByYou": "Can only create link for files owned by you", "publicLinkCreated": "Public link created", @@ -1187,4 +1187,4 @@ "changeLocationOfSelectedItems": "Change location of selected items?", "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", "cleanUncategorized": "Clean Uncategorized" -} \ No newline at end of file +} diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index 13cb66f9c..06f56d94e 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -38,7 +38,7 @@ class SemanticSearchService { final _logger = Logger("SemanticSearchService"); final _queue = Queue(); final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML(); - final _frameworkInitialization = Completer(); + final _frameworkInitialization = Completer(); final _embeddingLoaderDebouncer = Debouncer(kDebounceDuration, executionInterval: kDebounceDuration); @@ -141,6 +141,10 @@ class SemanticSearchService { ); } + Future getFrameworkInitializationStatus() { + return _frameworkInitialization.future; + } + Future clearIndexes() async { await EmbeddingsDB.instance.deleteAllForModel(kCurrentModel); _logger.info("Indexes cleared for $kCurrentModel"); @@ -232,7 +236,7 @@ class SemanticSearchService { _logger.info("Initializing ML framework"); try { await _mlFramework.init(); - _frameworkInitialization.complete(); + _frameworkInitialization.complete(true); } catch (e, s) { _logger.severe("ML framework initialization failed", e, s); } diff --git a/lib/ui/settings/machine_learning_settings_page.dart b/lib/ui/settings/machine_learning_settings_page.dart index de40c7947..3bb38f874 100644 --- a/lib/ui/settings/machine_learning_settings_page.dart +++ b/lib/ui/settings/machine_learning_settings_page.dart @@ -115,7 +115,17 @@ class _MachineLearningSettingsPageState hasEnabled ? Column( children: [ - const MagicSearchIndexStatsWidget(), + FutureBuilder( + future: SemanticSearchService.instance + .getFrameworkInitializationStatus(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return const MagicSearchIndexStatsWidget(); + } else { + return const ModelLoadingState(); + } + }, + ), const SizedBox( height: 12, ), @@ -144,6 +154,31 @@ class _MachineLearningSettingsPageState } } +class ModelLoadingState extends StatelessWidget { + const ModelLoadingState({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MenuSectionTitle(title: S.of(context).status), + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: S.of(context).loadingModel, + ), + trailingWidget: EnteLoadingWidget( + size: 12, + color: getEnteColorScheme(context).fillMuted, + ), + singleBorderRadius: 8, + alignCaptionedTextToLeft: true, + isGestureDetectorDisabled: true, + ), + ], + ); + } +} + class MagicSearchIndexStatsWidget extends StatefulWidget { const MagicSearchIndexStatsWidget({ super.key, From 3d9126ab48b11205c06c4d23cc0deda0057b2b29 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Fri, 5 Jan 2024 18:09:01 +0530 Subject: [PATCH 21/23] v0.8.31 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index d0a4fd9d1..f2de3c249 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.30+550 +version: 0.8.31+551 environment: sdk: ">=3.0.0 <4.0.0" From 827003b965b215b9a421ac23b8ca6fbef52ae4aa Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 6 Jan 2024 01:12:15 +0530 Subject: [PATCH 22/23] Do the dew --- lib/db/embeddings_db.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart index 1d3ae16f6..3a6dc541a 100644 --- a/lib/db/embeddings_db.dart +++ b/lib/db/embeddings_db.dart @@ -19,7 +19,7 @@ class EmbeddingsDB { [EmbeddingSchema], directory: dir.path, ); - // TODO: _clearDeprecatedStore(dir); + await _clearDeprecatedStore(dir); } Future clearTable() async { From fff3191f6b8c0a61e135c67b487de013421d8819 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 6 Jan 2024 01:15:49 +0530 Subject: [PATCH 23/23] Fire event in case of deletions --- lib/db/embeddings_db.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart index 3a6dc541a..539137c96 100644 --- a/lib/db/embeddings_db.dart +++ b/lib/db/embeddings_db.dart @@ -53,6 +53,7 @@ class EmbeddingsDB { final embeddings = await _isar.embeddings.filter().modelEqualTo(model).findAll(); await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); + Bus.instance.fire(EmbeddingUpdatedEvent()); }); }