1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-16 16:56:46 +01:00

feat(mobile): render assets on device by default (#10470)

* feat(mobile): render asset on device by default

* remove unused service
This commit is contained in:
Alex 2024-06-22 09:13:05 -07:00 committed by GitHub
parent 6164640575
commit 32da9d90e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 97 deletions

View file

@ -1,13 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
@ -28,7 +23,6 @@ final albumServiceProvider = Provider(
ref.watch(userServiceProvider), ref.watch(userServiceProvider),
ref.watch(syncServiceProvider), ref.watch(syncServiceProvider),
ref.watch(dbProvider), ref.watch(dbProvider),
ref.watch(backupServiceProvider),
), ),
); );
@ -37,7 +31,6 @@ class AlbumService {
final UserService _userService; final UserService _userService;
final SyncService _syncService; final SyncService _syncService;
final Isar _db; final Isar _db;
final BackupService _backupService;
final Logger _log = Logger('AlbumService'); final Logger _log = Logger('AlbumService');
Completer<bool> _localCompleter = Completer()..complete(false); Completer<bool> _localCompleter = Completer()..complete(false);
Completer<bool> _remoteCompleter = Completer()..complete(false); Completer<bool> _remoteCompleter = Completer()..complete(false);
@ -47,7 +40,6 @@ class AlbumService {
this._userService, this._userService,
this._syncService, this._syncService,
this._db, this._db,
this._backupService,
); );
/// Checks all selected device albums for changes of albums and their assets /// Checks all selected device albums for changes of albums and their assets
@ -62,60 +54,14 @@ class AlbumService {
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
bool changes = false; bool changes = false;
try { try {
final List<String> excludedIds =
await _backupService.excludedAlbumsQuery().idProperty().findAll();
final List<String> selectedIds =
await _backupService.selectedAlbumsQuery().idProperty().findAll();
if (selectedIds.isEmpty) {
final numLocal = await _db.albums.where().localIdIsNotNull().count();
if (numLocal > 0) {
_syncService.removeAllLocalAlbumsAndAssets();
}
return false;
}
final List<AssetPathEntity> onDevice = final List<AssetPathEntity> onDevice =
await PhotoManager.getAssetPathList( await PhotoManager.getAssetPathList(
hasAll: true, hasAll: true,
filterOption: FilterOptionGroup(containsPathModified: true), filterOption: FilterOptionGroup(containsPathModified: true),
); );
_log.info("Found ${onDevice.length} device albums"); _log.info("Found ${onDevice.length} device albums");
Set<String>? excludedAssets;
if (excludedIds.isNotEmpty) { changes = await _syncService.syncLocalAlbumAssetsToDb(onDevice);
if (Platform.isIOS) {
// iOS and Android device album working principle differ significantly
// on iOS, an asset can be in multiple albums
// on Android, an asset can only be in exactly one album (folder!) at the same time
// thus, on Android, excluding an album can be done by ignoring that album
// however, on iOS, it it necessary to load the assets from all excluded
// albums and check every asset from any selected album against the set
// of excluded assets
excludedAssets = await _loadExcludedAssetIds(onDevice, excludedIds);
_log.info("Found ${excludedAssets.length} assets to exclude");
}
// remove all excluded albums
onDevice.removeWhere((e) => excludedIds.contains(e.id));
_log.info(
"Ignoring ${excludedIds.length} excluded albums resulting in ${onDevice.length} device albums",
);
}
final hasAll = selectedIds
.map((id) => onDevice.firstWhereOrNull((a) => a.id == id))
.whereNotNull()
.any((a) => a.isAll);
if (hasAll) {
if (Platform.isAndroid) {
// remove the virtual "Recent" album and keep and individual albums
// on Android, the virtual "Recent" `lastModified` value is always null
onDevice.removeWhere((e) => e.isAll);
_log.info("'Recents' is selected, keeping all individual albums");
}
} else {
// keep only the explicitly selected albums
onDevice.removeWhere((e) => !selectedIds.contains(e.id));
_log.info("'Recents' is not selected, keeping only selected albums");
}
changes =
await _syncService.syncLocalAlbumAssetsToDb(onDevice, excludedAssets);
_log.info("Syncing completed. Changes: $changes"); _log.info("Syncing completed. Changes: $changes");
} finally { } finally {
_localCompleter.complete(changes); _localCompleter.complete(changes);
@ -124,21 +70,6 @@ class AlbumService {
return changes; return changes;
} }
Future<Set<String>> _loadExcludedAssetIds(
List<AssetPathEntity> albums,
List<String> excludedAlbumIds,
) async {
final Set<String> result = HashSet<String>();
for (AssetPathEntity a in albums) {
if (excludedAlbumIds.contains(a.id)) {
final List<AssetEntity> assets =
await a.getAssetListRange(start: 0, end: 0x7fffffffffffffff);
result.addAll(assets.map((e) => e.id));
}
}
return result;
}
/// Checks remote albums (owned if `isShared` is false) for changes, /// Checks remote albums (owned if `isShared` is false) for changes,
/// updates the local database and returns `true` if there were any changes /// updates the local database and returns `true` if there were any changes
Future<bool> refreshRemoteAlbums({required bool isShared}) async { Future<bool> refreshRemoteAlbums({required bool isShared}) async {

View file

@ -24,13 +24,9 @@ class HashService {
AssetPathEntity album, { AssetPathEntity album, {
int start = 0, int start = 0,
int end = 0x7fffffffffffffff, int end = 0x7fffffffffffffff,
Set<String>? excludedAssets,
}) async { }) async {
final entities = await album.getAssetListRange(start: start, end: end); final entities = await album.getAssetListRange(start: start, end: end);
final filtered = excludedAssets == null return _hashAssets(entities);
? entities
: entities.where((e) => !excludedAssets.contains(e.id)).toList();
return _hashAssets(filtered);
} }
/// Converts a list of [AssetEntity]s to [Asset]s including only those /// Converts a list of [AssetEntity]s to [Asset]s including only those

View file

@ -68,10 +68,9 @@ class SyncService {
/// Syncs all device albums and their assets to the database /// Syncs all device albums and their assets to the database
/// Returns `true` if there were any changes /// Returns `true` if there were any changes
Future<bool> syncLocalAlbumAssetsToDb( Future<bool> syncLocalAlbumAssetsToDb(
List<AssetPathEntity> onDevice, [ List<AssetPathEntity> onDevice,
Set<String>? excludedAssets, ) =>
]) => _lock.run(() => _syncLocalAlbumAssetsToDb(onDevice));
_lock.run(() => _syncLocalAlbumAssetsToDb(onDevice, excludedAssets));
/// returns all Asset IDs that are not contained in the existing list /// returns all Asset IDs that are not contained in the existing list
List<int> sharedAssetsToRemove( List<int> sharedAssetsToRemove(
@ -492,9 +491,8 @@ class SyncService {
/// Syncs all device albums and their assets to the database /// Syncs all device albums and their assets to the database
/// Returns `true` if there were any changes /// Returns `true` if there were any changes
Future<bool> _syncLocalAlbumAssetsToDb( Future<bool> _syncLocalAlbumAssetsToDb(
List<AssetPathEntity> onDevice, [ List<AssetPathEntity> onDevice,
Set<String>? excludedAssets, ) async {
]) async {
onDevice.sort((a, b) => a.id.compareTo(b.id)); onDevice.sort((a, b) => a.id.compareTo(b.id));
final inDb = final inDb =
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll(); await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();
@ -510,10 +508,8 @@ class SyncService {
album, album,
deleteCandidates, deleteCandidates,
existing, existing,
excludedAssets,
), ),
onlyFirst: (AssetPathEntity ape) => onlyFirst: (AssetPathEntity ape) => _addAlbumFromDevice(ape, existing),
_addAlbumFromDevice(ape, existing, excludedAssets),
onlySecond: (Album a) => _removeAlbumFromDb(a, deleteCandidates), onlySecond: (Album a) => _removeAlbumFromDb(a, deleteCandidates),
); );
_log.fine( _log.fine(
@ -545,16 +541,13 @@ class SyncService {
Album album, Album album,
List<Asset> deleteCandidates, List<Asset> deleteCandidates,
List<Asset> existing, [ List<Asset> existing, [
Set<String>? excludedAssets,
bool forceRefresh = false, bool forceRefresh = false,
]) async { ]) async {
if (!forceRefresh && !await _hasAssetPathEntityChanged(ape, album)) { if (!forceRefresh && !await _hasAssetPathEntityChanged(ape, album)) {
_log.fine("Local album ${ape.name} has not changed. Skipping sync."); _log.fine("Local album ${ape.name} has not changed. Skipping sync.");
return false; return false;
} }
if (!forceRefresh && if (!forceRefresh && await _syncDeviceAlbumFast(ape, album)) {
excludedAssets == null &&
await _syncDeviceAlbumFast(ape, album)) {
return true; return true;
} }
@ -566,8 +559,7 @@ class SyncService {
.findAll(); .findAll();
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
final int assetCountOnDevice = await ape.assetCountAsync; final int assetCountOnDevice = await ape.assetCountAsync;
final List<Asset> onDevice = final List<Asset> onDevice = await _hashService.getHashedAssets(ape);
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
_removeDuplicates(onDevice); _removeDuplicates(onDevice);
// _removeDuplicates sorts `onDevice` by checksum // _removeDuplicates sorts `onDevice` by checksum
final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb); final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb);
@ -678,13 +670,11 @@ class SyncService {
/// assets already existing in the database to the list of `existing` assets /// assets already existing in the database to the list of `existing` assets
Future<void> _addAlbumFromDevice( Future<void> _addAlbumFromDevice(
AssetPathEntity ape, AssetPathEntity ape,
List<Asset> existing, [ List<Asset> existing,
Set<String>? excludedAssets, ) async {
]) async {
_log.info("Syncing a new local album to DB: ${ape.name}"); _log.info("Syncing a new local album to DB: ${ape.name}");
final Album a = Album.local(ape); final Album a = Album.local(ape);
final assets = final assets = await _hashService.getHashedAssets(ape);
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
_removeDuplicates(assets); _removeDuplicates(assets);
final (existingInDb, updated) = await _linkWithExistingFromDb(assets); final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
_log.info( _log.info(