From 5619955eb79279f295e9bbce4b41cd2673059f5b Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 18 Jun 2024 14:27:04 -0700 Subject: [PATCH] success upload --- .../lib/models/backup/backup_state.model.dart | 174 ++---------------- mobile/lib/pages/backup/backup.page.dart | 16 +- .../lib/providers/backup/backup.provider.dart | 20 +- mobile/lib/services/backup.service.dart | 67 ++++++- mobile/pubspec.lock | 4 +- 5 files changed, 107 insertions(+), 174 deletions(-) diff --git a/mobile/lib/models/backup/backup_state.model.dart b/mobile/lib/models/backup/backup_state.model.dart index bb693a5b75..fafecddce3 100644 --- a/mobile/lib/models/backup/backup_state.model.dart +++ b/mobile/lib/models/backup/backup_state.model.dart @@ -1,7 +1,11 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; + +import 'package:background_downloader/background_downloader.dart'; import 'package:cancellation_token_http/http.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:immich_mobile/models/backup/available_album.model.dart'; @@ -18,178 +22,38 @@ enum BackUpProgressEnum { class BackUpState { // enum - final BackUpProgressEnum backupProgress; - final List allAssetsInDatabase; - final double progressInPercentage; - final String progressInFileSize; - final double progressInFileSpeed; - final List progressInFileSpeeds; - final DateTime progressInFileSpeedUpdateTime; - final int progressInFileSpeedUpdateSentBytes; - final double iCloudDownloadProgress; - final CancellationToken cancelToken; - final ServerDiskInfo serverInfo; - final bool autoBackup; - final bool backgroundBackup; - final bool backupRequireWifi; - final bool backupRequireCharging; - final int backupTriggerDelay; + final BackUpProgressEnum progress; - /// All available albums on the device - final List availableAlbums; - final Set selectedBackupAlbums; - final Set excludedBackupAlbums; + final List uploadTasks; - /// Assets that are not overlapping in selected backup albums and excluded backup albums - final Set allUniqueAssets; - - /// All assets from the selected albums that have been backup - final Set selectedAlbumsBackupAssetsIds; - - // Current Backup Asset - final CurrentUploadAsset currentUploadAsset; - - const BackUpState({ - required this.backupProgress, - required this.allAssetsInDatabase, - required this.progressInPercentage, - required this.progressInFileSize, - required this.progressInFileSpeed, - required this.progressInFileSpeeds, - required this.progressInFileSpeedUpdateTime, - required this.progressInFileSpeedUpdateSentBytes, - required this.iCloudDownloadProgress, - required this.cancelToken, - required this.serverInfo, - required this.autoBackup, - required this.backgroundBackup, - required this.backupRequireWifi, - required this.backupRequireCharging, - required this.backupTriggerDelay, - required this.availableAlbums, - required this.selectedBackupAlbums, - required this.excludedBackupAlbums, - required this.allUniqueAssets, - required this.selectedAlbumsBackupAssetsIds, - required this.currentUploadAsset, + BackUpState({ + required this.progress, + required this.uploadTasks, }); BackUpState copyWith({ - BackUpProgressEnum? backupProgress, - List? allAssetsInDatabase, - double? progressInPercentage, - String? progressInFileSize, - double? progressInFileSpeed, - List? progressInFileSpeeds, - DateTime? progressInFileSpeedUpdateTime, - int? progressInFileSpeedUpdateSentBytes, - double? iCloudDownloadProgress, - CancellationToken? cancelToken, - ServerDiskInfo? serverInfo, - bool? autoBackup, - bool? backgroundBackup, - bool? backupRequireWifi, - bool? backupRequireCharging, - int? backupTriggerDelay, - List? availableAlbums, - Set? selectedBackupAlbums, - Set? excludedBackupAlbums, - Set? allUniqueAssets, - Set? selectedAlbumsBackupAssetsIds, - CurrentUploadAsset? currentUploadAsset, + BackUpProgressEnum? progress, + List? uploadTasks, }) { return BackUpState( - backupProgress: backupProgress ?? this.backupProgress, - allAssetsInDatabase: allAssetsInDatabase ?? this.allAssetsInDatabase, - progressInPercentage: progressInPercentage ?? this.progressInPercentage, - progressInFileSize: progressInFileSize ?? this.progressInFileSize, - progressInFileSpeed: progressInFileSpeed ?? this.progressInFileSpeed, - progressInFileSpeeds: progressInFileSpeeds ?? this.progressInFileSpeeds, - progressInFileSpeedUpdateTime: - progressInFileSpeedUpdateTime ?? this.progressInFileSpeedUpdateTime, - progressInFileSpeedUpdateSentBytes: progressInFileSpeedUpdateSentBytes ?? - this.progressInFileSpeedUpdateSentBytes, - iCloudDownloadProgress: - iCloudDownloadProgress ?? this.iCloudDownloadProgress, - cancelToken: cancelToken ?? this.cancelToken, - serverInfo: serverInfo ?? this.serverInfo, - autoBackup: autoBackup ?? this.autoBackup, - backgroundBackup: backgroundBackup ?? this.backgroundBackup, - backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi, - backupRequireCharging: - backupRequireCharging ?? this.backupRequireCharging, - backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay, - availableAlbums: availableAlbums ?? this.availableAlbums, - selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums, - excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums, - allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets, - selectedAlbumsBackupAssetsIds: - selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds, - currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset, + progress: progress ?? this.progress, + uploadTasks: uploadTasks ?? this.uploadTasks, ); } @override - String toString() { - return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, progressInFileSize: $progressInFileSize, progressInFileSpeed: $progressInFileSpeed, progressInFileSpeeds: $progressInFileSpeeds, progressInFileSpeedUpdateTime: $progressInFileSpeedUpdateTime, progressInFileSpeedUpdateSentBytes: $progressInFileSpeedUpdateSentBytes, iCloudDownloadProgress: $iCloudDownloadProgress, cancelToken: $cancelToken, serverInfo: $serverInfo, autoBackup: $autoBackup, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)'; - } + String toString() => + 'BackUpState(progress: $progress, uploadTasks: $uploadTasks)'; @override bool operator ==(covariant BackUpState other) { if (identical(this, other)) return true; - final collectionEquals = const DeepCollectionEquality().equals; + final listEquals = const DeepCollectionEquality().equals; - return other.backupProgress == backupProgress && - collectionEquals(other.allAssetsInDatabase, allAssetsInDatabase) && - other.progressInPercentage == progressInPercentage && - other.progressInFileSize == progressInFileSize && - other.progressInFileSpeed == progressInFileSpeed && - collectionEquals(other.progressInFileSpeeds, progressInFileSpeeds) && - other.progressInFileSpeedUpdateTime == progressInFileSpeedUpdateTime && - other.progressInFileSpeedUpdateSentBytes == - progressInFileSpeedUpdateSentBytes && - other.iCloudDownloadProgress == iCloudDownloadProgress && - other.cancelToken == cancelToken && - other.serverInfo == serverInfo && - other.autoBackup == autoBackup && - other.backgroundBackup == backgroundBackup && - other.backupRequireWifi == backupRequireWifi && - other.backupRequireCharging == backupRequireCharging && - other.backupTriggerDelay == backupTriggerDelay && - collectionEquals(other.availableAlbums, availableAlbums) && - collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) && - collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) && - collectionEquals(other.allUniqueAssets, allUniqueAssets) && - collectionEquals( - other.selectedAlbumsBackupAssetsIds, - selectedAlbumsBackupAssetsIds, - ) && - other.currentUploadAsset == currentUploadAsset; + return other.progress == progress && + listEquals(other.uploadTasks, uploadTasks); } @override - int get hashCode { - return backupProgress.hashCode ^ - allAssetsInDatabase.hashCode ^ - progressInPercentage.hashCode ^ - progressInFileSize.hashCode ^ - progressInFileSpeed.hashCode ^ - progressInFileSpeeds.hashCode ^ - progressInFileSpeedUpdateTime.hashCode ^ - progressInFileSpeedUpdateSentBytes.hashCode ^ - iCloudDownloadProgress.hashCode ^ - cancelToken.hashCode ^ - serverInfo.hashCode ^ - autoBackup.hashCode ^ - backgroundBackup.hashCode ^ - backupRequireWifi.hashCode ^ - backupRequireCharging.hashCode ^ - backupTriggerDelay.hashCode ^ - availableAlbums.hashCode ^ - selectedBackupAlbums.hashCode ^ - excludedBackupAlbums.hashCode ^ - allUniqueAssets.hashCode ^ - selectedAlbumsBackupAssetsIds.hashCode ^ - currentUploadAsset.hashCode; - } + int get hashCode => progress.hashCode ^ uploadTasks.hashCode; } diff --git a/mobile/lib/pages/backup/backup.page.dart b/mobile/lib/pages/backup/backup.page.dart index d35e90ec68..c3bbb51349 100644 --- a/mobile/lib/pages/backup/backup.page.dart +++ b/mobile/lib/pages/backup/backup.page.dart @@ -15,21 +15,31 @@ class BackupPage extends StatefulHookConsumerWidget { class _BackupPageState extends ConsumerState { @override void initState() { - ref.read(backupNotifierProvider.notifier).backup(); + ref.read(backupNotifierProvider.notifier).getBackupCandidates(); super.initState(); } @override Widget build(BuildContext context) { + final provider = ref.watch(backupNotifierProvider); + return Scaffold( appBar: AppBar( title: const Text("Backup"), + actions: [ + IconButton( + icon: const Icon(Icons.play_circle_outline_rounded), + onPressed: () { + ref.read(backupNotifierProvider.notifier).startBackup(); + }, + ), + ], ), - body: const Center( + body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("Test"), + Text("Test ${provider.uploadTasks.length}"), ], ), ), diff --git a/mobile/lib/providers/backup/backup.provider.dart b/mobile/lib/providers/backup/backup.provider.dart index 823fd99a6d..defa0d6364 100644 --- a/mobile/lib/providers/backup/backup.provider.dart +++ b/mobile/lib/providers/backup/backup.provider.dart @@ -1,25 +1,35 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/services/backup.service.dart'; import 'package:logging/logging.dart'; -class BackupNotifier extends StateNotifier { +class BackupNotifier extends StateNotifier { BackupNotifier( this._backupService, ) : super( - true, + BackUpState( + progress: BackUpProgressEnum.idle, + uploadTasks: [], + ), ); final log = Logger('BackupNotifier'); final BackupService _backupService; - Future backup() async { - _backupService.buildBackupCandidates(); + Future getBackupCandidates() async { + state = state.copyWith( + uploadTasks: await _backupService.getBackupCandidates(), + ); + } + + Future startBackup() async { + await _backupService.startBackup(state.uploadTasks); } } final backupNotifierProvider = - StateNotifierProvider((ref) { + StateNotifierProvider((ref) { return BackupNotifier(ref.watch(backupServiceProvider)); }); diff --git a/mobile/lib/services/backup.service.dart b/mobile/lib/services/backup.service.dart index 383e11139f..b8a6753917 100644 --- a/mobile/lib/services/backup.service.dart +++ b/mobile/lib/services/backup.service.dart @@ -30,9 +30,23 @@ class BackupService { final _fileDownloader = FileDownloader(); final ApiService _apiService; - BackupService(this._apiService); + BackupService(this._apiService) { + debugPrint("BackupService created"); + // final subscription = FileDownloader().updates.listen((update) { + // switch (update) { + // case TaskStatusUpdate(): + // print( + // 'Status update for ${update.task} with status ${update.status}', + // ); + // case TaskProgressUpdate(): + // print( + // 'Progress update for ${update.task} with progress ${update.progress}', + // ); + // } + // }); + } - buildBackupCandidates() async { + Future> getBackupCandidates() async { // Get assets on devices final albums = await PhotoManager.getAssetPathList(hasAll: true); List assetsOnDevice = await _getAssetsOnDevice(albums); @@ -45,13 +59,15 @@ class BackupService { // Get assets not on server if (assetsOnServer != null && assetsOnServer.isNotEmpty) { assetsOnDevice.removeWhere( - (asset) => !assetsOnServer.contains(asset.id), + (asset) => assetsOnServer.contains(asset.id), ); } + // Remvove duplicate + assetsOnDevice = assetsOnDevice.toSet().toList(); + // Build UploadTask - final uploadTasks = await _constructUploadTask(assetsOnDevice); - print(uploadTasks); + return await _constructUploadTask(assetsOnDevice); } Future> _constructUploadTask( @@ -61,7 +77,7 @@ class BackupService { final String savedEndpoint = Store.get(StoreKey.serverEndpoint); final String deviceId = Store.get(StoreKey.deviceId); - final url = '$savedEndpoint/asset/upload'; + final url = '$savedEndpoint/assets'; final headers = { 'x-immich-user-token': Store.get(StoreKey.accessToken), 'Transfer-Encoding': 'chunked', @@ -69,8 +85,17 @@ class BackupService { for (final entity in assets) { final file = await entity.originFile; - final fileName = await entity.titleAsync; - final (baseDirectory, directory, _) = await Task.split(file: file); + if (file == null) { + continue; + } + + final originalFileName = await entity.titleAsync; + + // final filePath = file.path; + // final split = filePath.split('/'); + // final filename = split.last; + // final directory = split.take(split.length - 1).join('/'); + final (baseDirectory, directory, filename) = await Task.split(file: file); final fields = { 'deviceAssetId': entity.id, @@ -79,13 +104,14 @@ class BackupService { 'fileModifiedAt': entity.modifiedDateTime.toUtc().toIso8601String(), 'isFavorite': entity.isFavorite.toString(), 'duration': entity.videoDuration.toString(), + 'originalFileName': originalFileName, }; final task = UploadTask( url: url, baseDirectory: baseDirectory, directory: directory, - filename: fileName, + filename: filename, group: 'backup', fileField: 'assetData', taskId: entity.id, @@ -125,4 +151,27 @@ class BackupService { return result; } + + Future startBackup(List tasks) async { + try { + debugPrint("Start backup ${tasks.length} tasks"); + + for (final task in tasks) { + final result = await _fileDownloader.upload( + task, + onProgress: (percent) => print('${percent * 100} done'), + onStatus: (status) => print('Status for ${task.taskId} is $status)'), + onElapsedTime: (t) => print('time is $t'), + elapsedTimeInterval: const Duration(seconds: 1), + ); + print("--------------------"); + print('Result Status Code ${result.responseStatusCode}'); + print('$result is done with ${result.status}'); + print('result ${result.responseBody}'); + print('result ${result.responseHeaders}'); + } + } catch (e) { + debugPrint("Error uploading tasks: $e"); + } + } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index dfdf37972a..55bca576dd 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -341,10 +341,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.6" dartx: dependency: transitive description: