feature(mobile): hash assets & sync via checksum (#2592)

* compare different sha1 implementations

* remove openssl sha1

* sync via checksum

* hash assets in batches

* hash in background, show spinner in tab

* undo tmp changes

* migrate by clearing assets

* ignore duplicate assets

* error handling

* trigger sync/merge after download and update view

* review feedback improvements

* hash in background isolate on iOS

* rework linking assets with existing from DB

* fine-grained errors on unique index violation

* hash lenth validation

* revert compute in background on iOS

* ignore duplicate assets on device

* fix bug with batching based on accumulated size

---------

Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
This commit is contained in:
Fynn Petersen-Frey 2023-06-10 20:13:59 +02:00 committed by GitHub
parent 053a0482b4
commit 73075c64d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2315 additions and 507 deletions

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
@ -12,9 +13,13 @@ import 'package:immich_mobile/shared/ui/share_dialog.dart';
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
final ImageViewerService _imageViewerService;
final ShareService _shareService;
final AlbumService _albumService;
ImageViewerStateNotifier(this._imageViewerService, this._shareService)
: super(
ImageViewerStateNotifier(
this._imageViewerService,
this._shareService,
this._albumService,
) : super(
ImageViewerPageState(
downloadAssetStatus: DownloadAssetStatus.idle,
),
@ -34,6 +39,7 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
toastType: ToastType.success,
gravity: ToastGravity.BOTTOM,
);
_albumService.refreshDeviceAlbums();
} else {
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.error);
ImmichToast.show(
@ -66,5 +72,6 @@ final imageViewerStateProvider =
((ref) => ImageViewerStateNotifier(
ref.watch(imageViewerServiceProvider),
ref.watch(shareServiceProvider),
ref.watch(albumServiceProvider),
)),
);

View file

@ -72,15 +72,7 @@ class TopControlAppBar extends HookConsumerWidget {
color: Colors.grey[200],
),
),
if (!asset.isLocal)
IconButton(
onPressed: onDownloadPressed,
icon: Icon(
Icons.cloud_download_outlined,
color: Colors.grey[200],
),
),
if (asset.storage == AssetState.merged)
if (asset.storage == AssetState.remote)
IconButton(
onPressed: onDownloadPressed,
icon: Icon(

View file

@ -287,7 +287,7 @@ class GalleryViewerPage extends HookConsumerWidget {
isFavorite: asset().isFavorite,
onMoreInfoPressed: showInfo,
onFavorite: asset().isRemote ? () => toggleFavorite(asset()) : null,
onDownloadPressed: asset().storage == AssetState.local
onDownloadPressed: asset().isLocal
? null
: () =>
ref.watch(imageViewerStateProvider.notifier).downloadAsset(

View file

@ -132,6 +132,17 @@ class BackgroundService {
}
}
Future<Uint8List?> digestFile(String path) {
return _foregroundChannel.invokeMethod<Uint8List>("digestFile", [path]);
}
Future<List<Uint8List?>?> digestFiles(List<String> paths) {
return _foregroundChannel.invokeListMethod<Uint8List?>(
"digestFiles",
paths,
);
}
/// Updates the notification shown by the background service
Future<bool?> _updateNotification({
String? title,

View file

@ -47,11 +47,11 @@ class HomePage extends HookConsumerWidget {
useEffect(
() {
ref.watch(websocketProvider.notifier).connect();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(albumProvider.notifier).getAllAlbums();
ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.watch(serverInfoProvider.notifier).getServerVersion();
ref.read(websocketProvider.notifier).connect();
Future(() => ref.read(assetProvider.notifier).getAllAsset());
ref.read(albumProvider.notifier).getAllAlbums();
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.read(serverInfoProvider.notifier).getServerVersion();
selectionEnabledHook.addListener(() {
multiselectEnabled.state = selectionEnabledHook.value;
@ -144,7 +144,7 @@ class HomePage extends HookConsumerWidget {
);
if (remoteAssets.isNotEmpty) {
await ref
.watch(assetProvider.notifier)
.read(assetProvider.notifier)
.toggleArchive(remoteAssets, true);
final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset';
@ -163,7 +163,7 @@ class HomePage extends HookConsumerWidget {
void onDelete() async {
processing.value = true;
try {
await ref.watch(assetProvider.notifier).deleteAssets(selection.value);
await ref.read(assetProvider.notifier).deleteAssets(selection.value);
selectionEnabledHook.value = false;
} finally {
processing.value = false;