diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 283915166e..06c70b86e6 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -257,6 +257,7 @@ class SyncStreamService { _logger.info('Processing batch of ${batchData.length} AssetEditReadyV1 events'); final List assets = []; + final List assetEdits = []; try { for (final data in batchData) { @@ -266,20 +267,33 @@ class SyncStreamService { final payload = data; final assetData = payload['asset']; + final editData = payload['edit']; - if (assetData == null) { + if (assetData == null || editData == null) { continue; } final asset = SyncAssetV1.fromJson(assetData); + final edits = (editData as List) + .map((e) => SyncAssetEditV1.fromJson(e)) + .whereType() + .toList(); if (asset != null) { assets.add(asset); + assetEdits.addAll(edits); } } if (assets.isNotEmpty) { await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-edit'); + + // edits that are sent replace previous edits, so we delete existing ones first + await _syncStreamRepository.deleteAssetEditsV1( + assets.map((asset) => SyncAssetEditDeleteV1(assetId: asset.id)).toList(), + debugLabel: 'websocket-edit', + ); + await _syncStreamRepository.updateAssetEditsV1(assetEdits, debugLabel: 'websocket-edit'); _logger.info('Successfully processed ${assets.length} edited assets'); } } catch (error, stackTrace) { diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index 472f1212e5..44bc6412ed 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -279,7 +279,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { } } - Future updateAssetEditsV1(Iterable data) async { + Future updateAssetEditsV1(Iterable data, {String debugLabel = 'user'}) async { try { await _db.batch((batch) { for (final edit in data) { @@ -295,12 +295,12 @@ class SyncStreamRepository extends DriftDatabaseRepository { } }); } catch (error, stack) { - _logger.severe('Error: updateAssetEditsV1', error, stack); + _logger.severe('Error: updateAssetEditsV1 - $debugLabel', error, stack); rethrow; } } - Future deleteAssetEditsV1(Iterable data) async { + Future deleteAssetEditsV1(Iterable data, {String debugLabel = 'user'}) async { try { await _db.batch((batch) { for (final edit in data) { @@ -308,7 +308,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { } }); } catch (error, stack) { - _logger.severe('Error: deleteAssetEditsV1', error, stack); + _logger.severe('Error: deleteAssetEditsV1 - $debugLabel', error, stack); rethrow; } } diff --git a/server/src/repositories/asset-edit.repository.ts b/server/src/repositories/asset-edit.repository.ts index 088cb1ccff..791a3dfa5d 100644 --- a/server/src/repositories/asset-edit.repository.ts +++ b/server/src/repositories/asset-edit.repository.ts @@ -39,4 +39,16 @@ export class AssetEditRepository { .orderBy('sequence', 'asc') .execute() as Promise; } + + @GenerateSql({ + params: [DummyValue.UUID], + }) + getWithSyncInfo(assetId: string) { + return this.db + .selectFrom('asset_edit') + .select(['id', 'assetId', 'sequence', 'action', 'parameters']) + .where('assetId', '=', assetId) + .orderBy('sequence', 'asc') + .execute(); + } } diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts index bfed556895..235d2f2a84 100644 --- a/server/src/repositories/websocket.repository.ts +++ b/server/src/repositories/websocket.repository.ts @@ -11,7 +11,7 @@ import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { NotificationDto } from 'src/dtos/notification.dto'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; +import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { handlePromiseError } from 'src/utils/misc'; @@ -37,7 +37,7 @@ export interface ClientEventMap { AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; AppRestartV1: [AppRestartEvent]; - AssetEditReadyV1: [{ asset: SyncAssetV1 }]; + AssetEditReadyV1: [{ asset: SyncAssetV1; edit: SyncAssetEditV1[] }]; } export type AuthFn = (client: Socket) => Promise; diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 2616a6baf5..3fcd71ab1e 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -540,6 +540,7 @@ export class AssetService extends BaseService { async getAssetEdits(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); const edits = await this.assetEditRepository.getAll(id); + return { assetId: id, edits, diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 2a47745a6c..7c9581ff9a 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -98,6 +98,7 @@ export class JobService extends BaseService { case JobName.AssetEditThumbnailGeneration: { const asset = await this.assetRepository.getById(item.data.id); + const edits = await this.assetEditRepository.getWithSyncInfo(item.data.id); if (asset) { this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, { @@ -122,6 +123,7 @@ export class JobService extends BaseService { height: asset.height, isEdited: asset.isEdited, }, + edit: edits, }); } diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts index 5e197fbb3f..a881bdca3f 100644 --- a/web/src/lib/stores/websocket.ts +++ b/web/src/lib/stores/websocket.ts @@ -40,7 +40,7 @@ export interface Events { AppRestartV1: (event: AppRestartEvent) => void; MaintenanceStatusV1: (event: MaintenanceStatusResponseDto) => void; - AssetEditReadyV1: (data: { asset: { id: string } }) => void; + AssetEditReadyV1: (data: { asset: { id: string }; edits: object }) => void; } const websocket: Socket = io({