diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index 687355c00061c..cb09b06d1d811 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -33,7 +33,6 @@ class HomePage extends HookConsumerWidget { () { ref.read(websocketProvider.notifier).connect(); Future(() => ref.read(assetProvider.notifier).getAllAsset()); - ref.read(assetProvider.notifier).getPartnerAssets(); ref.read(albumProvider.notifier).getAllAlbums(); ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); ref.read(serverInfoProvider.notifier).getServerInfo(); @@ -85,9 +84,6 @@ class HomePage extends HookConsumerWidget { Future refreshAssets() async { final fullRefresh = refreshCount.value > 0; await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh); - if (timelineUsers.length > 1) { - await ref.read(assetProvider.notifier).getPartnerAssets(); - } if (fullRefresh) { // refresh was forced: user requested another refresh within 2 seconds refreshCount.value = 0; diff --git a/mobile/lib/modules/partner/views/partner_detail_page.dart b/mobile/lib/modules/partner/views/partner_detail_page.dart index 0c681162e17d5..cb954acceb38d 100644 --- a/mobile/lib/modules/partner/views/partner_detail_page.dart +++ b/mobile/lib/modules/partner/views/partner_detail_page.dart @@ -22,7 +22,7 @@ class PartnerDetailPage extends HookConsumerWidget { useEffect( () { - ref.read(assetProvider.notifier).getPartnerAssets(partner); + ref.read(assetProvider.notifier).getAllAsset(); return null; }, [], @@ -78,8 +78,7 @@ class PartnerDetailPage extends HookConsumerWidget { ), body: MultiselectGrid( renderListProvider: assetsProvider(partner.isarId), - onRefresh: () => - ref.read(assetProvider.notifier).getPartnerAssets(partner), + onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(), deleteEnabled: false, favoriteEnabled: false, ), diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index 196cab1db9b6b..fbdd4125e33ee 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -43,7 +43,7 @@ class TabNavigationObserver extends AutoRouterObserver { if (route.name == 'SharingRoute') { ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); - ref.read(assetProvider.notifier).getPartnerAssets(); + Future(() => ref.read(assetProvider.notifier).getAllAsset()); } if (route.name == 'LibraryRoute') { diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index 0ded4d79d2313..7a73bd7b6e5cd 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -56,11 +56,10 @@ class AppStateNotiifer extends StateNotifier { switch (_ref.read(tabProvider)) { case TabEnum.home: _ref.read(assetProvider.notifier).getAllAsset(); - _ref.read(assetProvider.notifier).getPartnerAssets(); case TabEnum.search: // nothing to do case TabEnum.sharing: - _ref.read(assetProvider.notifier).getPartnerAssets(); + _ref.read(assetProvider.notifier).getAllAsset(); _ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); case TabEnum.library: _ref.read(albumProvider.notifier).getAllAlbums(); diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index cf9a3be822522..30b6c847968aa 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -3,7 +3,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/shared/models/exif_info.dart'; import 'package:immich_mobile/shared/models/store.dart'; -import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; import 'package:immich_mobile/shared/services/asset.service.dart'; @@ -26,7 +25,6 @@ class AssetNotifier extends StateNotifier { final log = Logger('AssetNotifier'); bool _getAllAssetInProgress = false; bool _deleteInProgress = false; - bool _getPartnerAssetsInProgress = false; AssetNotifier( this._assetService, @@ -49,9 +47,11 @@ class AssetNotifier extends StateNotifier { await clearAssetsAndAlbums(_db); log.info("Manual refresh requested, cleared assets and albums from db"); } + final bool changedUsers = await _userService.refreshUsers(); final bool newRemote = await _assetService.refreshRemoteAssets(); final bool newLocal = await _albumService.refreshDeviceAlbums(); - debugPrint("newRemote: $newRemote, newLocal: $newLocal"); + debugPrint( + "changedUsers: $changedUsers, newRemote: $newRemote, newLocal: $newLocal"); log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms"); } finally { @@ -60,19 +60,6 @@ class AssetNotifier extends StateNotifier { } } - Future getPartnerAssets([User? partner]) async { - if (_getPartnerAssetsInProgress) return; - try { - final stopwatch = Stopwatch()..start(); - _getPartnerAssetsInProgress = true; - await _userService.refreshUsers(); - await _assetService.refreshRemoteAssets(); - log.info("Load partner assets: ${stopwatch.elapsedMilliseconds}ms"); - } finally { - _getPartnerAssetsInProgress = false; - } - } - Future clearAllAsset() { return clearAssetsAndAlbums(_db); } diff --git a/mobile/lib/shared/services/asset.service.dart b/mobile/lib/shared/services/asset.service.dart index 513605234cd06..36df4ba3d62fe 100644 --- a/mobile/lib/shared/services/asset.service.dart +++ b/mobile/lib/shared/services/asset.service.dart @@ -5,8 +5,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/models/etag.dart'; import 'package:immich_mobile/shared/models/exif_info.dart'; -import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; @@ -44,15 +44,16 @@ class AssetService { /// Checks the server for updated assets and updates the local database if /// required. Returns `true` if there were any changes. Future refreshRemoteAssets() async { - final List users = await _db.users - .filter() - .isPartnerSharedWithEqualTo(true) - .or() - .isarIdEqualTo(Store.get(StoreKey.currentUser).isarId) - .findAll(); + final syncedUserIds = await _db.eTags.where().idProperty().findAll(); + final List syncedUsers = syncedUserIds.isEmpty + ? [] + : await _db.users + .where() + .anyOf(syncedUserIds, (q, id) => q.idEqualTo(id)) + .findAll(); final Stopwatch sw = Stopwatch()..start(); final bool changes = await _syncService.syncRemoteAssetsToDb( - users: users, + users: syncedUsers, getChangedAssets: _getRemoteAssetChanges, loadAssets: _getRemoteAssets, refreshUsers: _userService.getUsersFromServer, @@ -64,8 +65,11 @@ class AssetService { /// Returns `(null, null)` if changes are invalid -> requires full sync Future<(List? toUpsert, List? toDelete)> _getRemoteAssetChanges(List users, DateTime since) async { - final changes = await _apiService.syncApi - .getDeltaSync(since, users.map((e) => e.id).toList()); + final dto = AssetDeltaSyncDto( + updatedAfter: since, + userIds: users.map((e) => e.id).toList(), + ); + final changes = await _apiService.syncApi.getDeltaSync(dto); return changes == null || changes.needsFullSync ? (null, null) : (changes.upserted.map(Asset.remote).toList(), changes.deleted); @@ -101,14 +105,15 @@ class AssetService { String? lastId; // will break on error or once all assets are loaded while (true) { - final List? assets = - await _apiService.syncApi.getAllForUserFullSync( - chunkSize, - until, - userId: user.id, - lastCreationDate: lastCreationDate, + final dto = AssetFullSyncDto( + limit: chunkSize, + updatedUntil: until, lastId: lastId, + lastCreationDate: lastCreationDate, + userId: user.id, ); + final List? assets = + await _apiService.syncApi.getFullSyncForUser(dto); if (assets == null) return null; allAssets.addAll(assets.map(Asset.remote)); if (assets.length < chunkSize) break; diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index 4934f3532d1b4..5e8d91076fdec 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -113,7 +113,8 @@ class SyncService { both: (User a, User b) { if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) || a.isPartnerSharedBy != b.isPartnerSharedBy || - a.isPartnerSharedWith != b.isPartnerSharedWith) { + a.isPartnerSharedWith != b.isPartnerSharedWith || + a.inTimeline != b.inTimeline) { toUpsert.add(a); return true; } @@ -163,7 +164,10 @@ class SyncService { if (since == null) return null; final DateTime now = DateTime.now(); final (toUpsert, toDelete) = await getChangedAssets(users, since); - if (toUpsert == null || toDelete == null) return null; + if (toUpsert == null || toDelete == null) { + await _clearUserAssetsETag(users); + return null; + } try { if (toDelete.isNotEmpty) { await handleRemoteAssetRemoval(toDelete); @@ -173,7 +177,7 @@ class SyncService { await upsertAssetsWithExif(updated); } if (toUpsert.isNotEmpty || toDelete.isNotEmpty) { - await _updateUserAssetsETag(currentUser, now); + await _updateUserAssetsETag(users, now); return true; } return false; @@ -252,7 +256,7 @@ class SyncService { final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true); if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) { - await _updateUserAssetsETag(user, now); + await _updateUserAssetsETag([user], now); return false; } final idsToDelete = toRemove.map((e) => e.id).toList(); @@ -262,12 +266,19 @@ class SyncService { } on IsarError catch (e) { _log.severe("Failed to sync remote assets to db", e); } - await _updateUserAssetsETag(user, now); + await _updateUserAssetsETag([user], now); return true; } - Future _updateUserAssetsETag(User user, DateTime time) => - _db.writeTxn(() => _db.eTags.put(ETag(id: user.id, time: time))); + Future _updateUserAssetsETag(List users, DateTime time) { + final etags = users.map((u) => ETag(id: u.id, time: time)).toList(); + return _db.writeTxn(() => _db.eTags.putAll(etags)); + } + + Future _clearUserAssetsETag(List users) { + final ids = users.map((u) => u.id).toList(); + return _db.writeTxn(() => _db.eTags.deleteAllById(ids)); + } /// Syncs remote albums to the database /// returns `true` if there were any changes