1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-04-11 10:36:24 +02:00
immich/mobile/lib/providers/asset.provider.dart
Alex 9d4aee36e2
refactor(mobile): asset provider ()
* refactor(mobile): asset provider

* wip

* wip: delete local assets

* wip: delete remote assets

* wip: deletion logic

* refactor

* pr feedback
2025-02-18 13:10:55 -06:00

246 lines
7.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/locale_provider.dart';
import 'package:immich_mobile/providers/memory.provider.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/asset.service.dart';
import 'package:immich_mobile/services/etag.service.dart';
import 'package:immich_mobile/services/exif.service.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/services/user.service.dart';
import 'package:immich_mobile/utils/renderlist_generator.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
return AssetNotifier(
ref.watch(assetServiceProvider),
ref.watch(albumServiceProvider),
ref.watch(userServiceProvider),
ref.watch(syncServiceProvider),
ref.watch(etagServiceProvider),
ref.watch(exifServiceProvider),
ref,
);
});
class AssetNotifier extends StateNotifier<bool> {
final AssetService _assetService;
final AlbumService _albumService;
final UserService _userService;
final SyncService _syncService;
final ETagService _etagService;
final ExifService _exifService;
final StateNotifierProviderRef _ref;
final log = Logger('AssetNotifier');
bool _getAllAssetInProgress = false;
bool _deleteInProgress = false;
AssetNotifier(
this._assetService,
this._albumService,
this._userService,
this._syncService,
this._etagService,
this._exifService,
this._ref,
) : super(false);
Future<void> getAllAsset({bool clear = false}) async {
if (_getAllAssetInProgress || _deleteInProgress) {
// guard against multiple calls to this method while it's still working
return;
}
final stopwatch = Stopwatch()..start();
try {
_getAllAssetInProgress = true;
state = true;
if (clear) {
await clearAllAssets();
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(
"changedUsers: $changedUsers, newRemote: $newRemote, newLocal: $newLocal",
);
if (newRemote) {
_ref.invalidate(memoryFutureProvider);
}
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
} finally {
_getAllAssetInProgress = false;
state = false;
}
}
Future<void> clearAllAssets() async {
await Store.delete(StoreKey.assetETag);
await Future.wait([
_assetService.clearTable(),
_exifService.clearTable(),
_albumService.clearTable(),
_userService.clearTable(),
_etagService.clearTable(),
]);
}
Future<void> onNewAssetUploaded(Asset newAsset) async {
// eTag on device is not valid after partially modifying the assets
Store.delete(StoreKey.assetETag);
await _syncService.syncNewAssetToDb(newAsset);
}
Future<bool> deleteLocalAssets(List<Asset> assets) async {
_deleteInProgress = true;
state = true;
try {
await _assetService.deleteLocalAssets(assets);
return true;
} catch (error) {
log.severe("Failed to delete local assets", error);
return false;
} finally {
_deleteInProgress = false;
state = false;
}
}
/// Delete remote asset only
///
/// Default behavior is trashing the asset
Future<bool> deleteRemoteAssets(
Iterable<Asset> deleteAssets, {
bool shouldDeletePermanently = false,
}) async {
_deleteInProgress = true;
state = true;
try {
await _assetService.deleteRemoteAssets(
deleteAssets,
shouldDeletePermanently: shouldDeletePermanently,
);
return true;
} catch (error) {
log.severe("Failed to delete remote assets", error);
return false;
} finally {
_deleteInProgress = false;
state = false;
}
}
Future<bool> deleteAssets(
Iterable<Asset> deleteAssets, {
bool force = false,
}) async {
_deleteInProgress = true;
state = true;
try {
await _assetService.deleteAssets(
deleteAssets,
shouldDeletePermanently: force,
);
return true;
} catch (error) {
log.severe("Failed to delete assets", error);
return false;
} finally {
_deleteInProgress = false;
state = false;
}
}
Future<void> toggleFavorite(List<Asset> assets, [bool? status]) {
status ??= !assets.every((a) => a.isFavorite);
return _assetService.changeFavoriteStatus(assets, status);
}
Future<void> toggleArchive(List<Asset> assets, [bool? status]) {
status ??= !assets.every((a) => a.isArchived);
return _assetService.changeArchiveStatus(assets, status);
}
}
final assetDetailProvider =
StreamProvider.autoDispose.family<Asset, Asset>((ref, asset) async* {
final assetService = ref.watch(assetServiceProvider);
yield await assetService.loadExif(asset);
await for (final asset in assetService.watchAsset(asset.id)) {
if (asset != null) {
yield await ref.watch(assetServiceProvider).loadExif(asset);
}
}
});
final assetWatcher =
StreamProvider.autoDispose.family<Asset?, Asset>((ref, asset) {
final assetService = ref.watch(assetServiceProvider);
return assetService.watchAsset(asset.id, fireImmediately: true);
});
final assetsProvider = StreamProvider.family<RenderList, int?>(
(ref, userId) {
if (userId == null) return const Stream.empty();
ref.watch(localeProvider);
final query = ref
.watch(dbProvider)
.assets
.where()
.ownerIdEqualToAnyChecksum(userId)
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdIsNull()
.sortByFileCreatedAtDesc();
return renderListGenerator(query, ref);
},
dependencies: [localeProvider],
);
final multiUserAssetsProvider = StreamProvider.family<RenderList, List<int>>(
(ref, userIds) {
if (userIds.isEmpty) return const Stream.empty();
ref.watch(localeProvider);
final query = ref
.watch(dbProvider)
.assets
.where()
.anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u))
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdIsNull()
.sortByFileCreatedAtDesc();
return renderListGenerator(query, ref);
},
dependencies: [localeProvider],
);
QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
final userId = ref.watch(currentUserProvider)?.isarId;
if (userId == null) {
return null;
}
return ref
.watch(dbProvider)
.assets
.where()
.remoteIdIsNotNull()
.filter()
.ownerIdEqualTo(userId)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdIsNull()
.sortByFileCreatedAtDesc();
}