ente/lib/services/semantic_search/embedding_store.dart

151 lines
4.6 KiB
Dart
Raw Normal View History

2023-10-03 17:56:51 +00:00
import "dart:typed_data";
2023-10-03 17:24:29 +00:00
import "package:logging/logging.dart";
import "package:photos/core/network/network.dart";
2023-10-03 17:56:51 +00:00
import "package:photos/db/files_db.dart";
import "package:photos/models/embedding.dart";
2023-10-03 18:13:08 +00:00
import "package:photos/models/file/file.dart";
2023-10-03 17:08:18 +00:00
import "package:photos/services/semantic_search/remote_embedding.dart";
2023-10-03 17:56:51 +00:00
import "package:photos/utils/crypto_util.dart";
import "package:photos/utils/file_download_util.dart";
2023-10-03 17:08:18 +00:00
import "package:shared_preferences/shared_preferences.dart";
class EmbeddingStore {
EmbeddingStore._privateConstructor();
static final EmbeddingStore instance = EmbeddingStore._privateConstructor();
2023-10-03 17:24:29 +00:00
static const kEmbeddingsSyncTimeKey = "embeddings_sync_time";
final _logger = Logger("EmbeddingStore");
final _dio = NetworkClient.instance.enteDio;
2023-10-03 17:08:18 +00:00
late SharedPreferences _preferences;
bool isSyncing = false;
2023-10-03 17:08:18 +00:00
Future<void> init(SharedPreferences preferences) async {
_preferences = preferences;
}
Future<void> pullEmbeddings() async {
if (isSyncing) {
return;
}
isSyncing = true;
var remoteEmbeddings = await _getRemoteEmbeddings();
await _storeRemoteEmbeddings(remoteEmbeddings.embeddings);
while (remoteEmbeddings.hasMore) {
remoteEmbeddings = await _getRemoteEmbeddings();
await _storeRemoteEmbeddings(remoteEmbeddings.embeddings);
}
isSyncing = false;
}
Future<void> pushEmbeddings() async {
final pendingItems = await FilesDB.instance.getUnSyncedEmbeddings();
for (final item in pendingItems) {
final file = await FilesDB.instance.getAnyUploadedFile(item.fileID);
await _pushEmbedding(file!, item);
}
}
2023-10-03 18:13:08 +00:00
Future<void> storeEmbedding(EnteFile file, Embedding embedding) async {
await FilesDB.instance.upsertEmbedding(embedding);
2023-10-13 14:54:20 +00:00
_pushEmbedding(file, embedding);
}
Future<void> _pushEmbedding(EnteFile file, Embedding embedding) async {
2023-10-03 18:13:08 +00:00
final encryptionKey = getFileKey(file);
final embeddingData =
Uint8List.view(Float64List.fromList(embedding.embedding).buffer);
final encryptedEmbeddingData = await CryptoUtil.encryptChaCha(
embeddingData,
encryptionKey,
);
final encryptedData =
CryptoUtil.bin2base64(encryptedEmbeddingData.encryptedData!);
final header = CryptoUtil.bin2base64(encryptedEmbeddingData.header!);
try {
final response = await _dio.put(
2023-10-24 08:42:22 +00:00
"/embeddings",
2023-10-03 18:13:08 +00:00
data: {
"fileID": embedding.fileID,
"model": embedding.model,
"encryptedEmbedding": encryptedData,
"decryptionHeader": header,
},
);
final updationTime = response.data["updationTime"];
embedding.updationTime = updationTime;
await FilesDB.instance.upsertEmbedding(embedding);
2023-10-03 18:13:08 +00:00
} catch (e, s) {
_logger.severe(e, s);
}
}
Future<RemoteEmbeddings> _getRemoteEmbeddings({
int limit = 500,
}) async {
final remoteEmbeddings = <RemoteEmbedding>[];
2023-10-03 17:24:29 +00:00
try {
final sinceTime = _preferences.getInt(kEmbeddingsSyncTimeKey) ?? 0;
_logger.info("Fetching embeddings since $sinceTime");
2023-10-03 17:24:29 +00:00
final response = await _dio.get(
"/embeddings/diff",
queryParameters: {
"sinceTime": sinceTime,
2023-10-03 17:24:29 +00:00
"limit": limit,
},
);
final diff = response.data["diff"] as List;
for (var entry in diff) {
final embedding = RemoteEmbedding.fromMap(entry);
remoteEmbeddings.add(embedding);
}
} catch (e, s) {
_logger.severe(e, s);
}
_logger.info("${remoteEmbeddings.length} embeddings fetched");
return RemoteEmbeddings(
remoteEmbeddings,
remoteEmbeddings.length == limit,
);
}
Future<void> _storeRemoteEmbeddings(
List<RemoteEmbedding> remoteEmbeddings,
) async {
2023-10-03 17:56:51 +00:00
final embeddings = <Embedding>[];
for (final embedding in remoteEmbeddings) {
final file = await FilesDB.instance.getAnyUploadedFile(embedding.fileID);
if (file == null) {
continue;
}
final fileKey = getFileKey(file);
2023-10-03 18:25:41 +00:00
final embeddingData = await CryptoUtil.decryptChaCha(
2023-10-03 17:56:51 +00:00
CryptoUtil.base642bin(embedding.encryptedEmbedding),
fileKey,
CryptoUtil.base642bin(embedding.decryptionHeader),
);
2023-10-03 18:25:41 +00:00
final decodedEmbedding = Float64List.view(embeddingData.buffer);
2023-10-03 17:56:51 +00:00
embeddings.add(
Embedding(
embedding.fileID,
embedding.model,
decodedEmbedding,
updationTime: embedding.updatedAt,
2023-10-03 17:56:51 +00:00
),
);
}
await FilesDB.instance.insertEmbeddings(embeddings);
_logger.info("${embeddings.length} embeddings stored");
await _preferences.setInt(
kEmbeddingsSyncTimeKey,
embeddings.last.updationTime!,
);
2023-10-03 17:08:18 +00:00
}
}