Replace ObjectBox with Isar

This commit is contained in:
vishnukvmd 2024-01-04 20:25:16 +05:30
parent 3eca6e44a7
commit 22fd204e11
14 changed files with 879 additions and 348 deletions

View file

@ -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
lib/db/embeddings_db.dart Normal file
View file

@ -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());
});
}
}

View file

@ -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>();
}
}

View file

@ -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
lib/models/embedding.g.dart Normal file
View file

@ -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');
});
}
}

View file

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

View file

@ -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]);
}

View file

@ -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,
),

View file

@ -10,11 +10,6 @@ class GGML extends MLFramework {
final _computer = Computer.shared();
final _logger = Logger("GGML");
@override
String getFrameworkName() {
return "ggml";
}
@override
String getImageModelRemotePath() {

View file

@ -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();

View file

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

View file

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

View file

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

View file

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