asset.provider.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import 'package:flutter/material.dart';
  2. import 'package:hooks_riverpod/hooks_riverpod.dart';
  3. import 'package:immich_mobile/modules/album/services/album.service.dart';
  4. import 'package:immich_mobile/shared/models/exif_info.dart';
  5. import 'package:immich_mobile/shared/models/store.dart';
  6. import 'package:immich_mobile/shared/models/user.dart';
  7. import 'package:immich_mobile/shared/providers/db.provider.dart';
  8. import 'package:immich_mobile/shared/services/asset.service.dart';
  9. import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
  10. import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
  11. import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
  12. import 'package:immich_mobile/shared/models/asset.dart';
  13. import 'package:immich_mobile/shared/services/sync.service.dart';
  14. import 'package:immich_mobile/shared/services/user.service.dart';
  15. import 'package:immich_mobile/utils/db.dart';
  16. import 'package:isar/isar.dart';
  17. import 'package:logging/logging.dart';
  18. import 'package:openapi/api.dart';
  19. import 'package:photo_manager/photo_manager.dart';
  20. class AssetNotifier extends StateNotifier<bool> {
  21. final AssetService _assetService;
  22. final AlbumService _albumService;
  23. final UserService _userService;
  24. final SyncService _syncService;
  25. final Isar _db;
  26. final log = Logger('AssetNotifier');
  27. bool _getAllAssetInProgress = false;
  28. bool _deleteInProgress = false;
  29. bool _getPartnerAssetsInProgress = false;
  30. AssetNotifier(
  31. this._assetService,
  32. this._albumService,
  33. this._userService,
  34. this._syncService,
  35. this._db,
  36. ) : super(false);
  37. Future<void> getAllAsset({bool clear = false}) async {
  38. if (_getAllAssetInProgress || _deleteInProgress) {
  39. // guard against multiple calls to this method while it's still working
  40. return;
  41. }
  42. final stopwatch = Stopwatch()..start();
  43. try {
  44. _getAllAssetInProgress = true;
  45. state = true;
  46. if (clear) {
  47. await clearAssetsAndAlbums(_db);
  48. log.info("Manual refresh requested, cleared assets and albums from db");
  49. }
  50. final bool newRemote = await _assetService.refreshRemoteAssets();
  51. final bool newLocal = await _albumService.refreshDeviceAlbums();
  52. debugPrint("newRemote: $newRemote, newLocal: $newLocal");
  53. log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
  54. } finally {
  55. _getAllAssetInProgress = false;
  56. state = false;
  57. }
  58. }
  59. Future<void> getPartnerAssets([User? partner]) async {
  60. if (_getPartnerAssetsInProgress) return;
  61. try {
  62. final stopwatch = Stopwatch()..start();
  63. _getPartnerAssetsInProgress = true;
  64. if (partner == null) {
  65. await _userService.refreshUsers();
  66. final List<User> partners =
  67. await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
  68. for (User u in partners) {
  69. await _assetService.refreshRemoteAssets(u);
  70. }
  71. } else {
  72. await _assetService.refreshRemoteAssets(partner);
  73. }
  74. log.info("Load partner assets: ${stopwatch.elapsedMilliseconds}ms");
  75. } finally {
  76. _getPartnerAssetsInProgress = false;
  77. }
  78. }
  79. Future<void> clearAllAsset() {
  80. return clearAssetsAndAlbums(_db);
  81. }
  82. Future<void> onNewAssetUploaded(Asset newAsset) async {
  83. // eTag on device is not valid after partially modifying the assets
  84. Store.delete(StoreKey.assetETag);
  85. await _syncService.syncNewAssetToDb(newAsset);
  86. }
  87. Future<void> deleteAssets(Iterable<Asset> deleteAssets) async {
  88. _deleteInProgress = true;
  89. state = true;
  90. try {
  91. final localDeleted = await _deleteLocalAssets(deleteAssets);
  92. final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
  93. if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
  94. final dbIds = deleteAssets.map((e) => e.id).toList();
  95. await _db.writeTxn(() async {
  96. await _db.exifInfos.deleteAll(dbIds);
  97. await _db.assets.deleteAll(dbIds);
  98. });
  99. }
  100. } finally {
  101. _deleteInProgress = false;
  102. state = false;
  103. }
  104. }
  105. Future<List<String>> _deleteLocalAssets(
  106. Iterable<Asset> assetsToDelete,
  107. ) async {
  108. final List<String> local =
  109. assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
  110. // Delete asset from device
  111. if (local.isNotEmpty) {
  112. try {
  113. return await PhotoManager.editor.deleteWithIds(local);
  114. } catch (e, stack) {
  115. log.severe("Failed to delete asset from device", e, stack);
  116. }
  117. }
  118. return [];
  119. }
  120. Future<Iterable<String>> _deleteRemoteAssets(
  121. Iterable<Asset> assetsToDelete,
  122. ) async {
  123. final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
  124. final List<DeleteAssetResponseDto> deleteAssetResult =
  125. await _assetService.deleteAssets(remote) ?? [];
  126. return deleteAssetResult
  127. .where((a) => a.status == DeleteAssetStatus.SUCCESS)
  128. .map((a) => a.id);
  129. }
  130. Future<void> toggleFavorite(List<Asset> assets, bool status) async {
  131. final newAssets = await _assetService.changeFavoriteStatus(assets, status);
  132. for (Asset? newAsset in newAssets) {
  133. if (newAsset == null) {
  134. log.severe("Change favorite status failed for asset");
  135. continue;
  136. }
  137. }
  138. }
  139. Future<void> toggleArchive(List<Asset> assets, bool status) async {
  140. final newAssets = await _assetService.changeArchiveStatus(assets, status);
  141. int i = 0;
  142. for (Asset oldAsset in assets) {
  143. final newAsset = newAssets[i++];
  144. if (newAsset == null) {
  145. log.severe("Change archive status failed for asset ${oldAsset.id}");
  146. continue;
  147. }
  148. }
  149. }
  150. }
  151. final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
  152. return AssetNotifier(
  153. ref.watch(assetServiceProvider),
  154. ref.watch(albumServiceProvider),
  155. ref.watch(userServiceProvider),
  156. ref.watch(syncServiceProvider),
  157. ref.watch(dbProvider),
  158. );
  159. });
  160. final assetDetailProvider =
  161. StreamProvider.autoDispose.family<Asset, Asset>((ref, asset) async* {
  162. yield await ref.watch(assetServiceProvider).loadExif(asset);
  163. final db = ref.watch(dbProvider);
  164. await for (final a in db.assets.watchObject(asset.id)) {
  165. if (a != null) yield await ref.watch(assetServiceProvider).loadExif(a);
  166. }
  167. });
  168. final assetsProvider =
  169. StreamProvider.family<RenderList, int?>((ref, userId) async* {
  170. if (userId == null) return;
  171. final query = ref
  172. .watch(dbProvider)
  173. .assets
  174. .where()
  175. .ownerIdEqualToAnyChecksum(userId)
  176. .filter()
  177. .isArchivedEqualTo(false)
  178. .sortByFileCreatedAtDesc();
  179. final settings = ref.watch(appSettingsServiceProvider);
  180. final groupBy =
  181. GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
  182. yield await RenderList.fromQuery(query, groupBy);
  183. await for (final _ in query.watchLazy()) {
  184. yield await RenderList.fromQuery(query, groupBy);
  185. }
  186. });
  187. final remoteAssetsProvider =
  188. StreamProvider.family<RenderList, int?>((ref, userId) async* {
  189. if (userId == null) return;
  190. final query = ref
  191. .watch(dbProvider)
  192. .assets
  193. .where()
  194. .remoteIdIsNotNull()
  195. .filter()
  196. .ownerIdEqualTo(userId)
  197. .sortByFileCreatedAtDesc();
  198. final settings = ref.watch(appSettingsServiceProvider);
  199. final groupBy =
  200. GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
  201. yield await RenderList.fromQuery(query, groupBy);
  202. await for (final _ in query.watchLazy()) {
  203. yield await RenderList.fromQuery(query, groupBy);
  204. }
  205. });