mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	fix: notify mobile app when live photos are linked (#5504)
* fix(mobile): album thumbnail list tile overflow on large album title * fix: notify clients about live photo linked event * refactor: notify clients during meta extraction --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									2814de4420
								
							
						
					
					
						commit
						f53b70571b
					
				@ -68,46 +68,46 @@ class AlbumThumbnailListTile extends StatelessWidget {
 | 
				
			|||||||
          crossAxisAlignment: CrossAxisAlignment.start,
 | 
					          crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            ClipRRect(
 | 
					            ClipRRect(
 | 
				
			||||||
              borderRadius: BorderRadius.circular(8),
 | 
					              borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
				
			||||||
              child: album.thumbnail.value == null
 | 
					              child: album.thumbnail.value == null
 | 
				
			||||||
                  ? buildEmptyThumbnail()
 | 
					                  ? buildEmptyThumbnail()
 | 
				
			||||||
                  : buildAlbumThumbnail(),
 | 
					                  : buildAlbumThumbnail(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Padding(
 | 
					            Expanded(
 | 
				
			||||||
              padding: const EdgeInsets.only(
 | 
					              child: Padding(
 | 
				
			||||||
                left: 8.0,
 | 
					                padding: const EdgeInsets.symmetric(horizontal: 8.0),
 | 
				
			||||||
                right: 8.0,
 | 
					                child: Column(
 | 
				
			||||||
              ),
 | 
					                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
              child: Column(
 | 
					                  children: [
 | 
				
			||||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                    Text(
 | 
				
			||||||
                children: [
 | 
					                      album.name,
 | 
				
			||||||
                  Text(
 | 
					                      overflow: TextOverflow.ellipsis,
 | 
				
			||||||
                    album.name,
 | 
					                      style: const TextStyle(
 | 
				
			||||||
                    style: const TextStyle(
 | 
					                        fontWeight: FontWeight.bold,
 | 
				
			||||||
                      fontWeight: FontWeight.bold,
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                    Row(
 | 
				
			||||||
                  Row(
 | 
					                      mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
                    mainAxisSize: MainAxisSize.min,
 | 
					                      children: [
 | 
				
			||||||
                    children: [
 | 
					                        Text(
 | 
				
			||||||
                      Text(
 | 
					                          album.assetCount == 1
 | 
				
			||||||
                        album.assetCount == 1
 | 
					                              ? 'album_thumbnail_card_item'
 | 
				
			||||||
                            ? 'album_thumbnail_card_item'
 | 
					                              : 'album_thumbnail_card_items',
 | 
				
			||||||
                            : 'album_thumbnail_card_items',
 | 
					                          style: const TextStyle(
 | 
				
			||||||
                        style: const TextStyle(
 | 
					 | 
				
			||||||
                          fontSize: 12,
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ).tr(args: ['${album.assetCount}']),
 | 
					 | 
				
			||||||
                      if (album.shared)
 | 
					 | 
				
			||||||
                        const Text(
 | 
					 | 
				
			||||||
                          'album_thumbnail_card_shared',
 | 
					 | 
				
			||||||
                          style: TextStyle(
 | 
					 | 
				
			||||||
                            fontSize: 12,
 | 
					                            fontSize: 12,
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                        ).tr(),
 | 
					                        ).tr(args: ['${album.assetCount}']),
 | 
				
			||||||
                    ],
 | 
					                        if (album.shared)
 | 
				
			||||||
                  ),
 | 
					                          const Text(
 | 
				
			||||||
                ],
 | 
					                            'album_thumbnail_card_shared',
 | 
				
			||||||
 | 
					                            style: TextStyle(
 | 
				
			||||||
 | 
					                              fontSize: 12,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ).tr(),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,13 @@
 | 
				
			|||||||
 | 
					import 'package:collection/collection.dart';
 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 | 
					import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/asset.dart';
 | 
					import 'package:immich_mobile/shared/models/asset.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
 | 
					import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/store.dart';
 | 
					import 'package:immich_mobile/shared/models/store.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/db.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
 | 
					import 'package:immich_mobile/shared/services/sync.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/utils/debounce.dart';
 | 
					import 'package:immich_mobile/utils/debounce.dart';
 | 
				
			||||||
@ -14,13 +17,33 @@ import 'package:socket_io_client/socket_io_client.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
enum PendingAction {
 | 
					enum PendingAction {
 | 
				
			||||||
  assetDelete,
 | 
					  assetDelete,
 | 
				
			||||||
 | 
					  assetUploaded,
 | 
				
			||||||
 | 
					  assetHidden,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PendingChange {
 | 
					class PendingChange {
 | 
				
			||||||
 | 
					  final String id;
 | 
				
			||||||
  final PendingAction action;
 | 
					  final PendingAction action;
 | 
				
			||||||
  final dynamic value;
 | 
					  final dynamic value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const PendingChange(this.action, this.value);
 | 
					  const PendingChange(
 | 
				
			||||||
 | 
					    this.id,
 | 
				
			||||||
 | 
					    this.action,
 | 
				
			||||||
 | 
					    this.value,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String toString() => 'PendingChange(id: $id, action: $action, value: $value)';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    if (identical(this, other)) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return other is PendingChange && other.id == id && other.action == action;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode => id.hashCode ^ action.hashCode;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WebsocketState {
 | 
					class WebsocketState {
 | 
				
			||||||
@ -131,6 +154,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
 | 
				
			|||||||
        socket.on('on_asset_trash', _handleServerUpdates);
 | 
					        socket.on('on_asset_trash', _handleServerUpdates);
 | 
				
			||||||
        socket.on('on_asset_restore', _handleServerUpdates);
 | 
					        socket.on('on_asset_restore', _handleServerUpdates);
 | 
				
			||||||
        socket.on('on_asset_update', _handleServerUpdates);
 | 
					        socket.on('on_asset_update', _handleServerUpdates);
 | 
				
			||||||
 | 
					        socket.on('on_asset_hidden', _handleOnAssetHidden);
 | 
				
			||||||
        socket.on('on_new_release', _handleReleaseUpdates);
 | 
					        socket.on('on_new_release', _handleReleaseUpdates);
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
 | 
					        debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
 | 
				
			||||||
@ -163,35 +187,78 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void addPendingChange(PendingAction action, dynamic value) {
 | 
					  void addPendingChange(PendingAction action, dynamic value) {
 | 
				
			||||||
 | 
					    final now = DateTime.now();
 | 
				
			||||||
    state = state.copyWith(
 | 
					    state = state.copyWith(
 | 
				
			||||||
      pendingChanges: [...state.pendingChanges, PendingChange(action, value)],
 | 
					      pendingChanges: [
 | 
				
			||||||
 | 
					        ...state.pendingChanges,
 | 
				
			||||||
 | 
					        PendingChange(now.millisecondsSinceEpoch.toString(), action, value),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    _debounce(handlePendingChanges);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void handlePendingChanges() {
 | 
					  Future<void> _handlePendingDeletes() async {
 | 
				
			||||||
    final deleteChanges = state.pendingChanges
 | 
					    final deleteChanges = state.pendingChanges
 | 
				
			||||||
        .where((c) => c.action == PendingAction.assetDelete)
 | 
					        .where((c) => c.action == PendingAction.assetDelete)
 | 
				
			||||||
        .toList();
 | 
					        .toList();
 | 
				
			||||||
    if (deleteChanges.isNotEmpty) {
 | 
					    if (deleteChanges.isNotEmpty) {
 | 
				
			||||||
      List<String> remoteIds =
 | 
					      List<String> remoteIds =
 | 
				
			||||||
          deleteChanges.map((a) => a.value.toString()).toList();
 | 
					          deleteChanges.map((a) => a.value.toString()).toList();
 | 
				
			||||||
      _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
 | 
					      await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
 | 
				
			||||||
      state = state.copyWith(
 | 
					      state = state.copyWith(
 | 
				
			||||||
        pendingChanges: state.pendingChanges
 | 
					        pendingChanges: state.pendingChanges
 | 
				
			||||||
            .where((c) => c.action != PendingAction.assetDelete)
 | 
					            .whereNot((c) => deleteChanges.contains(c))
 | 
				
			||||||
            .toList(),
 | 
					            .toList(),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _handleOnUploadSuccess(dynamic data) {
 | 
					  Future<void> _handlePendingUploaded() async {
 | 
				
			||||||
    final dto = AssetResponseDto.fromJson(data);
 | 
					    final uploadedChanges = state.pendingChanges
 | 
				
			||||||
    if (dto != null) {
 | 
					        .where((c) => c.action == PendingAction.assetUploaded)
 | 
				
			||||||
      final newAsset = Asset.remote(dto);
 | 
					        .toList();
 | 
				
			||||||
      _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
 | 
					    if (uploadedChanges.isNotEmpty) {
 | 
				
			||||||
 | 
					      List<AssetResponseDto?> remoteAssets = uploadedChanges
 | 
				
			||||||
 | 
					          .map((a) => AssetResponseDto.fromJson(a.value))
 | 
				
			||||||
 | 
					          .toList();
 | 
				
			||||||
 | 
					      for (final dto in remoteAssets) {
 | 
				
			||||||
 | 
					        if (dto != null) {
 | 
				
			||||||
 | 
					          final newAsset = Asset.remote(dto);
 | 
				
			||||||
 | 
					          await _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      state = state.copyWith(
 | 
				
			||||||
 | 
					        pendingChanges: state.pendingChanges
 | 
				
			||||||
 | 
					            .whereNot((c) => uploadedChanges.contains(c))
 | 
				
			||||||
 | 
					            .toList(),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _handlingPendingHidden() async {
 | 
				
			||||||
 | 
					    final hiddenChanges = state.pendingChanges
 | 
				
			||||||
 | 
					        .where((c) => c.action == PendingAction.assetHidden)
 | 
				
			||||||
 | 
					        .toList();
 | 
				
			||||||
 | 
					    if (hiddenChanges.isNotEmpty) {
 | 
				
			||||||
 | 
					      List<String> remoteIds =
 | 
				
			||||||
 | 
					          hiddenChanges.map((a) => a.value.toString()).toList();
 | 
				
			||||||
 | 
					      final db = _ref.watch(dbProvider);
 | 
				
			||||||
 | 
					      await db.writeTxn(() => db.assets.deleteAllByRemoteId(remoteIds));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      state = state.copyWith(
 | 
				
			||||||
 | 
					        pendingChanges: state.pendingChanges
 | 
				
			||||||
 | 
					            .whereNot((c) => hiddenChanges.contains(c))
 | 
				
			||||||
 | 
					            .toList(),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void handlePendingChanges() async {
 | 
				
			||||||
 | 
					    await _handlePendingUploaded();
 | 
				
			||||||
 | 
					    await _handlePendingDeletes();
 | 
				
			||||||
 | 
					    await _handlingPendingHidden();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _handleOnConfigUpdate(dynamic _) {
 | 
					  void _handleOnConfigUpdate(dynamic _) {
 | 
				
			||||||
    _ref.read(serverInfoProvider.notifier).getServerFeatures();
 | 
					    _ref.read(serverInfoProvider.notifier).getServerFeatures();
 | 
				
			||||||
    _ref.read(serverInfoProvider.notifier).getServerConfig();
 | 
					    _ref.read(serverInfoProvider.notifier).getServerConfig();
 | 
				
			||||||
@ -202,10 +269,14 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
 | 
				
			|||||||
    _ref.read(assetProvider.notifier).getAllAsset();
 | 
					    _ref.read(assetProvider.notifier).getAllAsset();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _handleOnAssetDelete(dynamic data) {
 | 
					  void _handleOnUploadSuccess(dynamic data) =>
 | 
				
			||||||
    addPendingChange(PendingAction.assetDelete, data);
 | 
					      addPendingChange(PendingAction.assetUploaded, data);
 | 
				
			||||||
    _debounce(handlePendingChanges);
 | 
					
 | 
				
			||||||
  }
 | 
					  void _handleOnAssetDelete(dynamic data) =>
 | 
				
			||||||
 | 
					      addPendingChange(PendingAction.assetDelete, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _handleOnAssetHidden(dynamic data) =>
 | 
				
			||||||
 | 
					      addPendingChange(PendingAction.assetHidden, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _handleReleaseUpdates(dynamic data) {
 | 
					  _handleReleaseUpdates(dynamic data) {
 | 
				
			||||||
    // Json guard
 | 
					    // Json guard
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import {
 | 
				
			|||||||
  assetStub,
 | 
					  assetStub,
 | 
				
			||||||
  newAlbumRepositoryMock,
 | 
					  newAlbumRepositoryMock,
 | 
				
			||||||
  newAssetRepositoryMock,
 | 
					  newAssetRepositoryMock,
 | 
				
			||||||
 | 
					  newCommunicationRepositoryMock,
 | 
				
			||||||
  newCryptoRepositoryMock,
 | 
					  newCryptoRepositoryMock,
 | 
				
			||||||
  newJobRepositoryMock,
 | 
					  newJobRepositoryMock,
 | 
				
			||||||
  newMediaRepositoryMock,
 | 
					  newMediaRepositoryMock,
 | 
				
			||||||
@ -19,8 +20,10 @@ import { constants } from 'fs/promises';
 | 
				
			|||||||
import { when } from 'jest-when';
 | 
					import { when } from 'jest-when';
 | 
				
			||||||
import { JobName } from '../job';
 | 
					import { JobName } from '../job';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  CommunicationEvent,
 | 
				
			||||||
  IAlbumRepository,
 | 
					  IAlbumRepository,
 | 
				
			||||||
  IAssetRepository,
 | 
					  IAssetRepository,
 | 
				
			||||||
 | 
					  ICommunicationRepository,
 | 
				
			||||||
  ICryptoRepository,
 | 
					  ICryptoRepository,
 | 
				
			||||||
  IJobRepository,
 | 
					  IJobRepository,
 | 
				
			||||||
  IMediaRepository,
 | 
					  IMediaRepository,
 | 
				
			||||||
@ -46,6 +49,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
  let mediaMock: jest.Mocked<IMediaRepository>;
 | 
					  let mediaMock: jest.Mocked<IMediaRepository>;
 | 
				
			||||||
  let personMock: jest.Mocked<IPersonRepository>;
 | 
					  let personMock: jest.Mocked<IPersonRepository>;
 | 
				
			||||||
  let storageMock: jest.Mocked<IStorageRepository>;
 | 
					  let storageMock: jest.Mocked<IStorageRepository>;
 | 
				
			||||||
 | 
					  let communicationMock: jest.Mocked<ICommunicationRepository>;
 | 
				
			||||||
  let sut: MetadataService;
 | 
					  let sut: MetadataService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
@ -57,6 +61,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
    metadataMock = newMetadataRepositoryMock();
 | 
					    metadataMock = newMetadataRepositoryMock();
 | 
				
			||||||
    moveMock = newMoveRepositoryMock();
 | 
					    moveMock = newMoveRepositoryMock();
 | 
				
			||||||
    personMock = newPersonRepositoryMock();
 | 
					    personMock = newPersonRepositoryMock();
 | 
				
			||||||
 | 
					    communicationMock = newCommunicationRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    mediaMock = newMediaRepositoryMock();
 | 
					    mediaMock = newMediaRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -70,6 +75,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
      configMock,
 | 
					      configMock,
 | 
				
			||||||
      mediaMock,
 | 
					      mediaMock,
 | 
				
			||||||
      moveMock,
 | 
					      moveMock,
 | 
				
			||||||
 | 
					      communicationMock,
 | 
				
			||||||
      personMock,
 | 
					      personMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@ -172,6 +178,23 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
      expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
 | 
					      expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
 | 
				
			||||||
      expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
 | 
					      expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should notify clients on live photo link', async () => {
 | 
				
			||||||
 | 
					      assetMock.getByIds.mockResolvedValue([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          ...assetStub.livePhotoStillAsset,
 | 
				
			||||||
 | 
					          exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					      assetMock.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await expect(sut.handleLivePhotoLinking({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe(true);
 | 
				
			||||||
 | 
					      expect(communicationMock.send).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					        CommunicationEvent.ASSET_HIDDEN,
 | 
				
			||||||
 | 
					        assetStub.livePhotoMotionAsset.ownerId,
 | 
				
			||||||
 | 
					        assetStub.livePhotoMotionAsset.id,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('handleQueueMetadataExtraction', () => {
 | 
					  describe('handleQueueMetadataExtraction', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,9 +9,11 @@ import { Subscription } from 'rxjs';
 | 
				
			|||||||
import { usePagination } from '../domain.util';
 | 
					import { usePagination } from '../domain.util';
 | 
				
			||||||
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
 | 
					import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  CommunicationEvent,
 | 
				
			||||||
  ExifDuration,
 | 
					  ExifDuration,
 | 
				
			||||||
  IAlbumRepository,
 | 
					  IAlbumRepository,
 | 
				
			||||||
  IAssetRepository,
 | 
					  IAssetRepository,
 | 
				
			||||||
 | 
					  ICommunicationRepository,
 | 
				
			||||||
  ICryptoRepository,
 | 
					  ICryptoRepository,
 | 
				
			||||||
  IJobRepository,
 | 
					  IJobRepository,
 | 
				
			||||||
  IMediaRepository,
 | 
					  IMediaRepository,
 | 
				
			||||||
@ -104,6 +106,7 @@ export class MetadataService {
 | 
				
			|||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
    @Inject(IMediaRepository) private mediaRepository: IMediaRepository,
 | 
					    @Inject(IMediaRepository) private mediaRepository: IMediaRepository,
 | 
				
			||||||
    @Inject(IMoveRepository) moveRepository: IMoveRepository,
 | 
					    @Inject(IMoveRepository) moveRepository: IMoveRepository,
 | 
				
			||||||
 | 
					    @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
 | 
				
			||||||
    @Inject(IPersonRepository) personRepository: IPersonRepository,
 | 
					    @Inject(IPersonRepository) personRepository: IPersonRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.configCore = SystemConfigCore.create(configRepository);
 | 
				
			||||||
@ -167,6 +170,9 @@ export class MetadataService {
 | 
				
			|||||||
    await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
 | 
					    await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
 | 
				
			||||||
    await this.albumRepository.removeAsset(motionAsset.id);
 | 
					    await this.albumRepository.removeAsset(motionAsset.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Notify clients to hide the linked live photo asset
 | 
				
			||||||
 | 
					    this.communicationRepository.send(CommunicationEvent.ASSET_HIDDEN, motionAsset.ownerId, motionAsset.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ export enum CommunicationEvent {
 | 
				
			|||||||
  ASSET_DELETE = 'on_asset_delete',
 | 
					  ASSET_DELETE = 'on_asset_delete',
 | 
				
			||||||
  ASSET_TRASH = 'on_asset_trash',
 | 
					  ASSET_TRASH = 'on_asset_trash',
 | 
				
			||||||
  ASSET_UPDATE = 'on_asset_update',
 | 
					  ASSET_UPDATE = 'on_asset_update',
 | 
				
			||||||
 | 
					  ASSET_HIDDEN = 'on_asset_hidden',
 | 
				
			||||||
  ASSET_RESTORE = 'on_asset_restore',
 | 
					  ASSET_RESTORE = 'on_asset_restore',
 | 
				
			||||||
  PERSON_THUMBNAIL = 'on_person_thumbnail',
 | 
					  PERSON_THUMBNAIL = 'on_person_thumbnail',
 | 
				
			||||||
  SERVER_VERSION = 'on_server_version',
 | 
					  SERVER_VERSION = 'on_server_version',
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user