diff --git a/mobile/lib/domain/services/sync.service.dart b/mobile/lib/domain/services/sync.service.dart index 990f87168a..6c8b4cc593 100644 --- a/mobile/lib/domain/services/sync.service.dart +++ b/mobile/lib/domain/services/sync.service.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/interfaces/album_media.interface.dart'; import 'package:immich_mobile/domain/interfaces/local_album.interface.dart'; @@ -30,7 +31,8 @@ class SyncService { // The deviceAlbums will not have the updatedAt field // and the assetCount will be 0. They are refreshed later // after the comparison - final deviceAlbums = await _albumMediaRepository.getAll(); + final deviceAlbums = + (await _albumMediaRepository.getAll()).sortedBy((a) => a.id); final dbAlbums = await _localAlbumRepository.getAll(sortBy: SortLocalAlbumsBy.id); diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 0bd456f0bb..727ffa5147 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -2,12 +2,13 @@ import 'dart:async'; -import 'package:immich_mobile/providers/infrastructure/sync_stream.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; import 'package:immich_mobile/utils/isolate.dart'; import 'package:worker_manager/worker_manager.dart'; class BackgroundSyncManager { Cancelable? _userSyncTask; + Cancelable? _deviceAlbumSyncTask; BackgroundSyncManager(); @@ -21,6 +22,20 @@ class BackgroundSyncManager { return Future.wait(futures); } + // No need to cancel the task, as it can also be run when the user logs out + Future syncDeviceAlbums() { + if (_deviceAlbumSyncTask != null) { + return _deviceAlbumSyncTask!.future; + } + + _deviceAlbumSyncTask = runInIsolateGentle( + computation: (ref) => ref.read(syncServiceProvider).syncLocalAlbums(), + ); + return _deviceAlbumSyncTask!.whenComplete(() { + _deviceAlbumSyncTask = null; + }); + } + Future syncUsers() { if (_userSyncTask != null) { return _userSyncTask!.future; @@ -29,9 +44,9 @@ class BackgroundSyncManager { _userSyncTask = runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).syncUsers(), ); - _userSyncTask!.whenComplete(() { - _userSyncTask = null; - }); - return _userSyncTask!.future; + return _userSyncTask! + ..whenComplete(() { + _userSyncTask = null; + }); } } diff --git a/mobile/lib/infrastructure/entities/local_album.entity.dart b/mobile/lib/infrastructure/entities/local_album.entity.dart index e4307ef410..5d5f766fd4 100644 --- a/mobile/lib/infrastructure/entities/local_album.entity.dart +++ b/mobile/lib/infrastructure/entities/local_album.entity.dart @@ -22,11 +22,12 @@ class LocalAlbumEntity extends Table with DriftDefaultsMixin { } extension LocalAlbumEntityX on LocalAlbumEntityData { - LocalAlbum toDto() { + LocalAlbum toDto({int assetCount = 0}) { return LocalAlbum( id: id, name: name, updatedAt: updatedAt, + assetCount: assetCount, thumbnailId: thumbnailId, backupSelection: backupSelection, isAll: isAll, diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index 9e5a67e74b..7833d45273 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -20,11 +20,28 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository @override Future> getAll({SortLocalAlbumsBy? sortBy}) { - final query = _db.localAlbumEntity.select(); + final assetCount = _db.localAlbumAssetEntity.assetId.count(); + + final query = _db.localAlbumEntity.select().join([ + innerJoin( + _db.localAlbumAssetEntity, + _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id), + useColumns: false, + ), + ]); + query + ..addColumns([assetCount]) + ..groupBy([_db.localAlbumEntity.id]); if (sortBy == SortLocalAlbumsBy.id) { - query.orderBy([(a) => OrderingTerm.asc(a.id)]); + query.orderBy([OrderingTerm.asc(_db.localAlbumEntity.id)]); } - return query.map((a) => a.toDto()).get(); + return query + .map( + (row) => row + .readTable(_db.localAlbumEntity) + .toDto(assetCount: row.read(assetCount) ?? 0), + ) + .get(); } @override diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart new file mode 100644 index 0000000000..1f65af43a4 --- /dev/null +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -0,0 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/album_media.interface.dart'; +import 'package:immich_mobile/domain/interfaces/local_album.interface.dart'; +import 'package:immich_mobile/infrastructure/repositories/album_media.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; + +final albumMediaRepositoryProvider = + Provider((ref) => const AlbumMediaRepository()); + +final localAlbumRepository = Provider( + (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), +); diff --git a/mobile/lib/providers/infrastructure/asset.provider.dart b/mobile/lib/providers/infrastructure/asset.provider.dart new file mode 100644 index 0000000000..b383f87af0 --- /dev/null +++ b/mobile/lib/providers/infrastructure/asset.provider.dart @@ -0,0 +1,8 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; + +final localAssetProvider = Provider( + (ref) => DriftLocalAssetRepository(ref.watch(driftProvider)), +); diff --git a/mobile/lib/providers/infrastructure/sync_stream.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart similarity index 67% rename from mobile/lib/providers/infrastructure/sync_stream.provider.dart rename to mobile/lib/providers/infrastructure/sync.provider.dart index e313982a30..da776672d4 100644 --- a/mobile/lib/providers/infrastructure/sync_stream.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -1,11 +1,22 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/services/sync.service.dart'; import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +final syncServiceProvider = Provider( + (ref) => SyncService( + albumMediaRepository: ref.watch(albumMediaRepositoryProvider), + localAlbumRepository: ref.watch(localAlbumRepository), + localAssetRepository: ref.watch(localAssetProvider), + ), +); + final syncStreamServiceProvider = Provider( (ref) => SyncStreamService( syncApiRepository: ref.watch(syncApiRepositoryProvider),