浏览代码

Replace ObjectBox with Isar

vishnukvmd 1 年之前
父节点
当前提交
22fd204e11

+ 2 - 2
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();

+ 55 - 0
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<void> init() async {
+    final dir = await getApplicationDocumentsDirectory();
+    _isar = await Isar.open(
+      [EmbeddingSchema],
+      directory: dir.path,
+    );
+  }
+
+  Future<void> clearTable() async {
+    await _isar.clear();
+  }
+
+  Stream<List<Embedding>> getStream(Model model) {
+    return _isar.embeddings.filter().modelEqualTo(model).watch();
+  }
+
+  Future<List<Embedding>> getAll(Model model) async {
+    return _isar.embeddings.filter().modelEqualTo(model).findAll();
+  }
+
+  Future<void> put(Embedding embedding) {
+    return _isar.writeTxn(() async {
+      await _isar.embeddings.put(embedding);
+    });
+  }
+
+  Future<void> putMany(List<Embedding> embeddings) {
+    return _isar.writeTxn(() async {
+      await _isar.embeddings.putAll(embeddings);
+    });
+  }
+
+  Future<List<Embedding>> getUnsyncedEmbeddings() async {
+    return await _isar.embeddings.filter().updationTimeEqualTo(null).findAll();
+  }
+
+  Future<void> 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());
+    });
+  }
+}

+ 0 - 27
lib/db/object_box.dart

@@ -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<void> init() async {
-    final docsDir = await getApplicationDocumentsDirectory();
-    // Future<Store> openStore() {...} is defined in the generated objectbox.g.dart
-    store = await openStore(directory: p.join(docsDir.path, "object-box-store"));
-  }
-
-  Future<void> clearTable() async {
-    getEmbeddingBox().removeAll();
-  }
-
-  Box<Embedding> getEmbeddingBox() {
-    return store.box<Embedding>();
-  }
-}

+ 38 - 4
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<double> 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');
+  }
+}

+ 756 - 0
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<Embedding> 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<int> offsets,
+  Map<Type, List<int>> allOffsets,
+) {
+  var bytesCount = offsets.last;
+  bytesCount += 3 + object.embedding.length * 8;
+  return bytesCount;
+}
+
+void _embeddingSerialize(
+  Embedding object,
+  IsarWriter writer,
+  List<int> offsets,
+  Map<Type, List<int>> 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<int> offsets,
+  Map<Type, List<int>> 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<P>(
+  IsarReader reader,
+  int propertyId,
+  int offset,
+  Map<Type, List<int>> 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<IsarLinkBase<dynamic>> _embeddingGetLinks(Embedding object) {
+  return [];
+}
+
+void _embeddingAttach(IsarCollection<dynamic> col, Id id, Embedding object) {
+  object.id = id;
+}
+
+extension EmbeddingQueryWhereSort
+    on QueryBuilder<Embedding, Embedding, QWhere> {
+  QueryBuilder<Embedding, Embedding, QAfterWhere> anyId() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addWhereClause(const IdWhereClause.any());
+    });
+  }
+}
+
+extension EmbeddingQueryWhere
+    on QueryBuilder<Embedding, Embedding, QWhereClause> {
+  QueryBuilder<Embedding, Embedding, QAfterWhereClause> idEqualTo(Id id) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addWhereClause(IdWhereClause.between(
+        lower: id,
+        upper: id,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterWhereClause> 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<Embedding, Embedding, QAfterWhereClause> idGreaterThan(Id id,
+      {bool include = false}) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addWhereClause(
+        IdWhereClause.greaterThan(lower: id, includeLower: include),
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterWhereClause> idLessThan(Id id,
+      {bool include = false}) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addWhereClause(
+        IdWhereClause.lessThan(upper: id, includeUpper: include),
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterWhereClause> 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<Embedding, Embedding, QFilterCondition> {
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      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<Embedding, Embedding, QAfterFilterCondition>
+      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<Embedding, Embedding, QAfterFilterCondition>
+      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<Embedding, Embedding, QAfterFilterCondition>
+      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<Embedding, Embedding, QAfterFilterCondition>
+      embeddingLengthEqualTo(int length) {
+    return QueryBuilder.apply(this, (query) {
+      return query.listLength(
+        r'embedding',
+        length,
+        true,
+        length,
+        true,
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> embeddingIsEmpty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.listLength(
+        r'embedding',
+        0,
+        true,
+        0,
+        true,
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      embeddingIsNotEmpty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.listLength(
+        r'embedding',
+        0,
+        false,
+        999999,
+        true,
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      embeddingLengthLessThan(
+    int length, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.listLength(
+        r'embedding',
+        0,
+        true,
+        length,
+        include,
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      embeddingLengthGreaterThan(
+    int length, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.listLength(
+        r'embedding',
+        length,
+        include,
+        999999,
+        true,
+      );
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      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<Embedding, Embedding, QAfterFilterCondition> fileIDEqualTo(
+      int value) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.equalTo(
+        property: r'fileID',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDGreaterThan(
+    int value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.greaterThan(
+        include: include,
+        property: r'fileID',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDLessThan(
+    int value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.lessThan(
+        include: include,
+        property: r'fileID',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> 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<Embedding, Embedding, QAfterFilterCondition> idEqualTo(
+      Id value) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.equalTo(
+        property: r'id',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idGreaterThan(
+    Id value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.greaterThan(
+        include: include,
+        property: r'id',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idLessThan(
+    Id value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.lessThan(
+        include: include,
+        property: r'id',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> 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<Embedding, Embedding, QAfterFilterCondition> modelEqualTo(
+      Model value) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.equalTo(
+        property: r'model',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelGreaterThan(
+    Model value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.greaterThan(
+        include: include,
+        property: r'model',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelLessThan(
+    Model value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.lessThan(
+        include: include,
+        property: r'model',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> 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<Embedding, Embedding, QAfterFilterCondition>
+      updationTimeIsNull() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(const FilterCondition.isNull(
+        property: r'updationTime',
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      updationTimeIsNotNull() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(const FilterCondition.isNotNull(
+        property: r'updationTime',
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> updationTimeEqualTo(
+      int? value) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.equalTo(
+        property: r'updationTime',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      updationTimeGreaterThan(
+    int? value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.greaterThan(
+        include: include,
+        property: r'updationTime',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
+      updationTimeLessThan(
+    int? value, {
+    bool include = false,
+  }) {
+    return QueryBuilder.apply(this, (query) {
+      return query.addFilterCondition(FilterCondition.lessThan(
+        include: include,
+        property: r'updationTime',
+        value: value,
+      ));
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterFilterCondition> 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<Embedding, Embedding, QFilterCondition> {}
+
+extension EmbeddingQueryLinks
+    on QueryBuilder<Embedding, Embedding, QFilterCondition> {}
+
+extension EmbeddingQuerySortBy on QueryBuilder<Embedding, Embedding, QSortBy> {
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByFileID() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'fileID', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByFileIDDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'fileID', Sort.desc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByModel() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'model', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByModelDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'model', Sort.desc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByUpdationTime() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'updationTime', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByUpdationTimeDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'updationTime', Sort.desc);
+    });
+  }
+}
+
+extension EmbeddingQuerySortThenBy
+    on QueryBuilder<Embedding, Embedding, QSortThenBy> {
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByFileID() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'fileID', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByFileIDDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'fileID', Sort.desc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenById() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'id', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByIdDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'id', Sort.desc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByModel() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'model', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByModelDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'model', Sort.desc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByUpdationTime() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'updationTime', Sort.asc);
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByUpdationTimeDesc() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addSortBy(r'updationTime', Sort.desc);
+    });
+  }
+}
+
+extension EmbeddingQueryWhereDistinct
+    on QueryBuilder<Embedding, Embedding, QDistinct> {
+  QueryBuilder<Embedding, Embedding, QDistinct> distinctByEmbedding() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addDistinctBy(r'embedding');
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QDistinct> distinctByFileID() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addDistinctBy(r'fileID');
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QDistinct> distinctByModel() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addDistinctBy(r'model');
+    });
+  }
+
+  QueryBuilder<Embedding, Embedding, QDistinct> distinctByUpdationTime() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addDistinctBy(r'updationTime');
+    });
+  }
+}
+
+extension EmbeddingQueryProperty
+    on QueryBuilder<Embedding, Embedding, QQueryProperty> {
+  QueryBuilder<Embedding, int, QQueryOperations> idProperty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addPropertyName(r'id');
+    });
+  }
+
+  QueryBuilder<Embedding, List<double>, QQueryOperations> embeddingProperty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addPropertyName(r'embedding');
+    });
+  }
+
+  QueryBuilder<Embedding, int, QQueryOperations> fileIDProperty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addPropertyName(r'fileID');
+    });
+  }
+
+  QueryBuilder<Embedding, Model, QQueryOperations> modelProperty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addPropertyName(r'model');
+    });
+  }
+
+  QueryBuilder<Embedding, int?, QQueryOperations> updationTimeProperty() {
+    return QueryBuilder.apply(this, (query) {
+      return query.addPropertyName(r'updationTime');
+    });
+  }
+}

+ 0 - 47
lib/objectbox-model.json

@@ -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
-}

+ 0 - 171
lib/objectbox.g.dart

@@ -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>[
-  ModelEntity(
-    id: const IdUid(1, 4067035246682038114),
-    name: 'Embedding',
-    lastPropertyId: const IdUid(4, 7974898435327252398),
-    flags: 0,
-    properties: <ModelProperty>[
-      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: <ModelRelation>[],
-    backlinks: <ModelBacklink>[],
-  ),
-];
-
-/// 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<Store> 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 = <Type, EntityDefinition>{
-    Embedding: EntityDefinition<Embedding>(
-      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<double>(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<Embedding>(_entities[0].properties[0]);
-
-  /// see [Embedding.model]
-  static final model =
-      QueryStringProperty<Embedding>(_entities[0].properties[1]);
-
-  /// see [Embedding.embedding]
-  static final embedding =
-      QueryDoubleVectorProperty<Embedding>(_entities[0].properties[2]);
-
-  /// see [Embedding.updationTime]
-  static final updationTime =
-      QueryIntegerProperty<Embedding>(_entities[0].properties[3]);
-}

+ 8 - 14
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<void> 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<void> 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<List<Embedding>> decodeEmbeddings(Map<String, dynamic> args) async {
     embeddings.add(
       Embedding(
         fileID: input.embedding.fileID,
-        model: input.embedding.model,
+        model: deserialize(input.embedding.model),
         embedding: decodedEmbedding,
         updationTime: input.embedding.updatedAt,
       ),

+ 0 - 5
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() {

+ 0 - 3
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();
 

+ 0 - 5
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;

+ 20 - 27
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<EnteFile>();
   final _cachedEmbeddings = <Embedding>[];
-  final _mlFramework = ONNX();
+  final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML();
   final _frameworkInitialization = Completer<void>();
 
   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<DiffSyncCompleteEvent>().listen((event) {
       // Diff sync is complete, we can now pull embeddings from remote
       unawaited(sync());
@@ -132,29 +133,21 @@ class SemanticSearchService {
   }
 
   Future<void> 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<void> _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(

+ 0 - 40
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:

+ 0 - 3
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: