mirror of
https://github.com/immich-app/immich.git
synced 2026-07-01 10:35:13 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2db907239f |
@@ -446,6 +446,7 @@ class SyncStreamService {
|
|||||||
|
|
||||||
await _syncStreamRepository.updateAssetsV1([asset], debugLabel: 'websocket-edit');
|
await _syncStreamRepository.updateAssetsV1([asset], debugLabel: 'websocket-edit');
|
||||||
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
||||||
|
await _refreshAssetOcrAndFaces(asset.id);
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
'Successfully processed AssetEditReadyV1 event for asset ${asset.id} with ${assetEdits.length} edits',
|
'Successfully processed AssetEditReadyV1 event for asset ${asset.id} with ${assetEdits.length} edits',
|
||||||
@@ -484,6 +485,7 @@ class SyncStreamService {
|
|||||||
|
|
||||||
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
|
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
|
||||||
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
||||||
|
await _refreshAssetOcrAndFaces(asset.id);
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
|
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
|
||||||
@@ -493,6 +495,22 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshAssetOcrAndFaces(String assetId) async {
|
||||||
|
try {
|
||||||
|
final ocr = await _api.assetsApi.getAssetOcr(assetId);
|
||||||
|
await _syncStreamRepository.replaceAssetOcr(assetId, ocr ?? const []);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error refreshing OCR for asset $assetId", error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final faces = await _api.facesApi.getFaces(assetId);
|
||||||
|
await _syncStreamRepository.replaceAssetFaces(assetId, faces ?? const []);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error refreshing faces for asset $assetId", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
||||||
if (remoteIds.isEmpty) {
|
if (remoteIds.isEmpty) {
|
||||||
return Future.value();
|
return Future.value();
|
||||||
|
|||||||
@@ -896,6 +896,71 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces all OCR rows for [assetId] with [data] (e.g. after an asset edit re-runs OCR).
|
||||||
|
Future<void> replaceAssetOcr(String assetId, Iterable<AssetOcrResponseDto> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
batch.deleteWhere(_db.assetOcrEntity, (row) => row.assetId.equals(assetId));
|
||||||
|
|
||||||
|
for (final ocr in data) {
|
||||||
|
batch.insert(
|
||||||
|
_db.assetOcrEntity,
|
||||||
|
AssetOcrEntityCompanion(
|
||||||
|
id: Value(ocr.id),
|
||||||
|
assetId: Value(ocr.assetId),
|
||||||
|
recognizedText: Value(ocr.text),
|
||||||
|
x1: Value(ocr.x1),
|
||||||
|
y1: Value(ocr.y1),
|
||||||
|
x2: Value(ocr.x2),
|
||||||
|
y2: Value(ocr.y2),
|
||||||
|
x3: Value(ocr.x3),
|
||||||
|
y3: Value(ocr.y3),
|
||||||
|
x4: Value(ocr.x4),
|
||||||
|
y4: Value(ocr.y4),
|
||||||
|
boxScore: Value(ocr.boxScore),
|
||||||
|
textScore: Value(ocr.textScore),
|
||||||
|
isVisible: const Value(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: replaceAssetOcr', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> replaceAssetFaces(String assetId, Iterable<AssetFaceResponseDto> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
batch.deleteWhere(_db.assetFaceEntity, (row) => row.assetId.equals(assetId));
|
||||||
|
|
||||||
|
for (final face in data) {
|
||||||
|
batch.insert(
|
||||||
|
_db.assetFaceEntity,
|
||||||
|
AssetFaceEntityCompanion(
|
||||||
|
id: Value(face.id),
|
||||||
|
assetId: Value(assetId),
|
||||||
|
personId: Value(face.person?.id),
|
||||||
|
imageWidth: Value(face.imageWidth),
|
||||||
|
imageHeight: Value(face.imageHeight),
|
||||||
|
boundingBoxX1: Value(face.boundingBoxX1),
|
||||||
|
boundingBoxY1: Value(face.boundingBoxY1),
|
||||||
|
boundingBoxX2: Value(face.boundingBoxX2),
|
||||||
|
boundingBoxY2: Value(face.boundingBoxY2),
|
||||||
|
sourceType: Value(face.sourceType.orElse(null)?.value ?? SourceType.machineLearning.value),
|
||||||
|
isVisible: const Value(true),
|
||||||
|
deletedAt: const Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: replaceAssetFaces', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> pruneAssets() async {
|
Future<void> pruneAssets() async {
|
||||||
try {
|
try {
|
||||||
await _db.transaction(() async {
|
await _db.transaction(() async {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
|
|||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
@@ -181,11 +182,34 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetEditReadyV1(dynamic data) {
|
void _handleSyncAssetEditReadyV1(dynamic data) {
|
||||||
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data));
|
final assetId = _assetIdFromEditReady(data);
|
||||||
|
unawaited(
|
||||||
|
_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data).whenComplete(() => _onAssetEditApplied(assetId)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetEditReadyV2(dynamic data) {
|
void _handleSyncAssetEditReadyV2(dynamic data) {
|
||||||
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data));
|
final assetId = _assetIdFromEditReady(data);
|
||||||
|
unawaited(
|
||||||
|
_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data).whenComplete(() => _onAssetEditApplied(assetId)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _assetIdFromEditReady(dynamic data) {
|
||||||
|
if (data is Map && data['asset'] is Map) {
|
||||||
|
final id = (data['asset'] as Map)['id'];
|
||||||
|
return id is String ? id : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The edit handler refreshes OCR/faces in the drift DB from a background isolate,
|
||||||
|
/// so the main-isolate UI providers must be invalidated here to re-read the new data.
|
||||||
|
void _onAssetEditApplied(String? assetId) {
|
||||||
|
if (assetId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ref.invalidate(ocrAssetProvider(assetId));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _processBatchedAssetUploadReadyV1() {
|
void _processBatchedAssetUploadReadyV1() {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class ApiService {
|
|||||||
late MemoriesApi memoriesApi;
|
late MemoriesApi memoriesApi;
|
||||||
late SessionsApi sessionsApi;
|
late SessionsApi sessionsApi;
|
||||||
late TagsApi tagsApi;
|
late TagsApi tagsApi;
|
||||||
|
late FacesApi facesApi;
|
||||||
|
|
||||||
ApiService() {
|
ApiService() {
|
||||||
// The below line ensures that the api clients are initialized when the service is instantiated
|
// The below line ensures that the api clients are initialized when the service is instantiated
|
||||||
@@ -77,6 +78,7 @@ class ApiService {
|
|||||||
memoriesApi = MemoriesApi(_apiClient);
|
memoriesApi = MemoriesApi(_apiClient);
|
||||||
sessionsApi = SessionsApi(_apiClient);
|
sessionsApi = SessionsApi(_apiClient);
|
||||||
tagsApi = TagsApi(_apiClient);
|
tagsApi = TagsApi(_apiClient);
|
||||||
|
facesApi = FacesApi(_apiClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user