diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 45c84ce3e6..537cdba8d8 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -235,7 +235,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe @@ -275,6 +274,6 @@ SPEC CHECKSUMS: url_launcher_ios: 694010445543906933d732453a59da0a173ae33d wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 -PODFILE CHECKSUM: 04655a9b6714fa7a2b4fb559982ee8bed4b3b718 +PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45 COCOAPODS: 1.16.2 diff --git a/mobile/lib/interfaces/upload.interface.dart b/mobile/lib/interfaces/upload.interface.dart index 1208fef13f..455e588cbc 100644 --- a/mobile/lib/interfaces/upload.interface.dart +++ b/mobile/lib/interfaces/upload.interface.dart @@ -4,8 +4,11 @@ abstract interface class IUploadRepository { void Function(TaskStatusUpdate)? onUploadStatus; void Function(TaskProgressUpdate)? onTaskProgress; - void enqueue(UploadTask task); + void enqueueAll(List tasks); Future cancel(String id); + void cancelAll(); + Future pauseAll(); Future deleteAllTrackingRecords(); Future deleteRecordsWithIds(List id); + Future> getRecords([TaskStatus? status]); } diff --git a/mobile/lib/pages/backup/backup_controller.page.dart b/mobile/lib/pages/backup/backup_controller.page.dart index 6cbf172ce5..18655a0be9 100644 --- a/mobile/lib/pages/backup/backup_controller.page.dart +++ b/mobile/lib/pages/backup/backup_controller.page.dart @@ -16,6 +16,7 @@ import 'package:immich_mobile/providers/backup/ios_background_settings.provider. import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -333,6 +334,32 @@ class BackupControllerPage extends HookConsumerWidget { const CurrentUploadingAssetInfoBox(), if (!hasExclusiveAccess) buildBackgroundBackupInfo(), buildBackupButton(), + ElevatedButton( + onPressed: () { + ref.watch(uploadServiceProvider).getRecords(); + }, + child: const Text( + "get record", + ), + ), + ElevatedButton( + onPressed: () { + ref + .watch(uploadServiceProvider) + .deleteAllUploadTasks(); + }, + child: const Text( + "clear records", + ), + ), + ElevatedButton( + onPressed: () { + ref.watch(uploadServiceProvider).cancelAllUpload(); + }, + child: const Text( + "cancel all uploads", + ), + ), ] : [ buildFolderSelectionTile(), diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart index ed2c485b13..f4eba6af88 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -134,7 +134,7 @@ class ShareIntentUploadStateNotifier } Future upload(File file) { - return _uploadService.upload(file); + return _uploadService.buildUploadTask(file); } Future cancelUpload(String id) { diff --git a/mobile/lib/providers/backup/backup.provider.dart b/mobile/lib/providers/backup/backup.provider.dart index d5910b59d7..5c82ed27c8 100644 --- a/mobile/lib/providers/backup/backup.provider.dart +++ b/mobile/lib/providers/backup/backup.provider.dart @@ -580,15 +580,15 @@ class BackupNotifier extends StateNotifier { switch (update.status) { case TaskStatus.complete: - if (update.responseStatusCode == 200) { - if (kDebugMode) { - debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE"); - } - } else { - if (kDebugMode) { - debugPrint("[COMPLETE] ${update.task.taskId}"); - } - } + // if (update.responseStatusCode == 200) { + // if (kDebugMode) { + // debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE"); + // } + // } else { + // if (kDebugMode) { + // debugPrint("[COMPLETE] ${update.task.taskId}"); + // } + // } break; default: diff --git a/mobile/lib/repositories/upload.repository.dart b/mobile/lib/repositories/upload.repository.dart index 463b358346..b08c107ed3 100644 --- a/mobile/lib/repositories/upload.repository.dart +++ b/mobile/lib/repositories/upload.repository.dart @@ -1,4 +1,5 @@ import 'package:background_downloader/background_downloader.dart'; +import 'package:flutter_cache_manager/file.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/interfaces/upload.interface.dart'; import 'package:immich_mobile/utils/upload.dart'; @@ -19,15 +20,15 @@ class UploadRepository implements IUploadRepository { taskQueue.maxConcurrent = 5; FileDownloader().addTaskQueue(taskQueue); FileDownloader().registerCallbacks( - group: uploadGroup, + group: kUploadGroup, taskStatusCallback: (update) => onUploadStatus?.call(update), taskProgressCallback: (update) => onTaskProgress?.call(update), ); } @override - void enqueue(UploadTask task) { - taskQueue.add(task); + void enqueueAll(List tasks) { + taskQueue.addAll(tasks); } @override @@ -40,8 +41,27 @@ class UploadRepository implements IUploadRepository { return FileDownloader().cancelTaskWithId(id); } + @override + void cancelAll() { + return taskQueue.removeAll(); + } + + @override + Future pauseAll() { + return FileDownloader().pauseAll(group: kUploadGroup); + } + @override Future deleteRecordsWithIds(List ids) { return FileDownloader().database.deleteRecordsWithIds(ids); } + + @override + Future> getRecords([TaskStatus? status]) { + if (status == null) { + return FileDownloader().database.allRecords(group: kUploadGroup); + } + + return FileDownloader().database.allRecordsWithStatus(status); + } } diff --git a/mobile/lib/services/backup.service.dart b/mobile/lib/services/backup.service.dart index 41d29a496b..3678c573b3 100644 --- a/mobile/lib/services/backup.service.dart +++ b/mobile/lib/services/backup.service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:background_downloader/background_downloader.dart'; import 'package:cancellation_token_http/http.dart' as http; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -262,6 +263,8 @@ class BackupService { } List candidates = assets.toList(); + print("Found ${candidates.length} assets to upload"); + List uploadTasks = []; for (final candidate in candidates) { final Asset asset = candidate.asset; File? file; @@ -278,13 +281,24 @@ class BackupService { await _assetMediaRepository.getOriginalFilename(asset.localId!) ?? asset.fileName; - await _uploadService.upload( + final task = await _uploadService.buildUploadTask( file, originalFileName: originalFileName, deviceAssetId: asset.localId, ); + + uploadTasks.add(task); } } + + if (uploadTasks.isEmpty) { + debugPrint("No assets to upload"); + return false; + } + + print("Uploading ${uploadTasks.length} assets"); + + _uploadService.upload(uploadTasks); } Future backupAsset( diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart index 190b46777c..4f5673dee9 100644 --- a/mobile/lib/services/upload.service.dart +++ b/mobile/lib/services/upload.service.dart @@ -42,23 +42,47 @@ class UploadService { return FileDownloader().cancelTaskWithId(id); } - Future upload( + void cancelAllUpload() { + return _uploadRepository.cancelAll(); + } + + Future pauseAllUploads() { + return _uploadRepository.pauseAll(); + } + + Future deleteAllUploadTasks() { + return _uploadRepository.deleteAllTrackingRecords(); + } + + Future> getRecords() async { + final all = await _uploadRepository.getRecords(); + print('all record: all: ${all.length} records found'); + final enqueue = await _uploadRepository.getRecords(TaskStatus.enqueued); + print( + 'all record: enqueue: ${enqueue.length} records found', + ); + return all; + } + + void upload(List tasks) { + _uploadRepository.enqueueAll(tasks); + } + + Future buildUploadTask( File file, { Map? fields, String? originalFileName, String? deviceAssetId, }) async { - final task = await _buildUploadTask( + return _buildTask( deviceAssetId ?? hash(file.path).toString(), file, fields: fields, originalFileName: originalFileName, ); - - _uploadRepository.enqueue(task); } - Future _buildUploadTask( + Future _buildTask( String id, File file, { Map? fields, @@ -95,7 +119,7 @@ class UploadService { baseDirectory: baseDirectory, directory: directory, fileField: 'assetData', - group: uploadGroup, + group: kUploadGroup, updates: Updates.statusAndProgress, ); } diff --git a/mobile/lib/utils/upload.dart b/mobile/lib/utils/upload.dart index a0b77f1d93..ee0837766a 100644 --- a/mobile/lib/utils/upload.dart +++ b/mobile/lib/utils/upload.dart @@ -1 +1 @@ -const uploadGroup = 'upload_group'; +const kUploadGroup = 'upload_group'; diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 6f6d9aaf43..23539de642 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -102,6 +102,10 @@ export class FileUploadInterceptor implements NestInterceptor { } private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { + console.log( + 'FileUploadInterceptor.filename called with file:', + this.assetService.getUploadFilename(asRequest(request, file)), + ); return callbackify( () => this.assetService.getUploadFilename(asRequest(request, file)), callback as Callback,