feat: new mobile upload

This commit is contained in:
Alex 2025-05-28 13:57:21 -05:00
parent aa68588b1d
commit 054aa8e386
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
10 changed files with 115 additions and 24 deletions

View File

@ -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

View File

@ -4,8 +4,11 @@ abstract interface class IUploadRepository {
void Function(TaskStatusUpdate)? onUploadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
void enqueue(UploadTask task);
void enqueueAll(List<UploadTask> tasks);
Future<bool> cancel(String id);
void cancelAll();
Future<void> pauseAll();
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
Future<List<TaskRecord>> getRecords([TaskStatus? status]);
}

View File

@ -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(),

View File

@ -134,7 +134,7 @@ class ShareIntentUploadStateNotifier
}
Future<void> upload(File file) {
return _uploadService.upload(file);
return _uploadService.buildUploadTask(file);
}
Future<bool> cancelUpload(String id) {

View File

@ -580,15 +580,15 @@ class BackupNotifier extends StateNotifier<BackUpState> {
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:

View File

@ -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<UploadTask> 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<void> pauseAll() {
return FileDownloader().pauseAll(group: kUploadGroup);
}
@override
Future<void> deleteRecordsWithIds(List<String> ids) {
return FileDownloader().database.deleteRecordsWithIds(ids);
}
@override
Future<List<TaskRecord>> getRecords([TaskStatus? status]) {
if (status == null) {
return FileDownloader().database.allRecords(group: kUploadGroup);
}
return FileDownloader().database.allRecordsWithStatus(status);
}
}

View File

@ -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<BackupCandidate> candidates = assets.toList();
print("Found ${candidates.length} assets to upload");
List<UploadTask> 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<bool> backupAsset(

View File

@ -42,23 +42,47 @@ class UploadService {
return FileDownloader().cancelTaskWithId(id);
}
Future<void> upload(
void cancelAllUpload() {
return _uploadRepository.cancelAll();
}
Future<void> pauseAllUploads() {
return _uploadRepository.pauseAll();
}
Future<void> deleteAllUploadTasks() {
return _uploadRepository.deleteAllTrackingRecords();
}
Future<List<TaskRecord>> 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<UploadTask> tasks) {
_uploadRepository.enqueueAll(tasks);
}
Future<UploadTask> buildUploadTask(
File file, {
Map<String, String>? 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<UploadTask> _buildUploadTask(
Future<UploadTask> _buildTask(
String id,
File file, {
Map<String, String>? fields,
@ -95,7 +119,7 @@ class UploadService {
baseDirectory: baseDirectory,
directory: directory,
fileField: 'assetData',
group: uploadGroup,
group: kUploadGroup,
updates: Updates.statusAndProgress,
);
}

View File

@ -1 +1 @@
const uploadGroup = 'upload_group';
const kUploadGroup = 'upload_group';

View File

@ -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<string>,