mirror of
https://github.com/immich-app/immich.git
synced 2025-06-14 02:54:31 -04:00
feat: new mobile upload
This commit is contained in:
parent
aa68588b1d
commit
054aa8e386
@ -235,7 +235,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
@ -275,6 +274,6 @@ SPEC CHECKSUMS:
|
|||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
|
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
|
||||||
|
|
||||||
PODFILE CHECKSUM: 04655a9b6714fa7a2b4fb559982ee8bed4b3b718
|
PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
@ -4,8 +4,11 @@ abstract interface class IUploadRepository {
|
|||||||
void Function(TaskStatusUpdate)? onUploadStatus;
|
void Function(TaskStatusUpdate)? onUploadStatus;
|
||||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||||
|
|
||||||
void enqueue(UploadTask task);
|
void enqueueAll(List<UploadTask> tasks);
|
||||||
Future<bool> cancel(String id);
|
Future<bool> cancel(String id);
|
||||||
|
void cancelAll();
|
||||||
|
Future<void> pauseAll();
|
||||||
Future<void> deleteAllTrackingRecords();
|
Future<void> deleteAllTrackingRecords();
|
||||||
Future<void> deleteRecordsWithIds(List<String> id);
|
Future<void> deleteRecordsWithIds(List<String> id);
|
||||||
|
Future<List<TaskRecord>> getRecords([TaskStatus? status]);
|
||||||
}
|
}
|
||||||
|
@ -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/backup/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.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/backup_info_card.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
@ -333,6 +334,32 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
const CurrentUploadingAssetInfoBox(),
|
const CurrentUploadingAssetInfoBox(),
|
||||||
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
||||||
buildBackupButton(),
|
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(),
|
buildFolderSelectionTile(),
|
||||||
|
@ -134,7 +134,7 @@ class ShareIntentUploadStateNotifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> upload(File file) {
|
Future<void> upload(File file) {
|
||||||
return _uploadService.upload(file);
|
return _uploadService.buildUploadTask(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> cancelUpload(String id) {
|
Future<bool> cancelUpload(String id) {
|
||||||
|
@ -580,15 +580,15 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
switch (update.status) {
|
switch (update.status) {
|
||||||
case TaskStatus.complete:
|
case TaskStatus.complete:
|
||||||
if (update.responseStatusCode == 200) {
|
// if (update.responseStatusCode == 200) {
|
||||||
if (kDebugMode) {
|
// if (kDebugMode) {
|
||||||
debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE");
|
// debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE");
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if (kDebugMode) {
|
// if (kDebugMode) {
|
||||||
debugPrint("[COMPLETE] ${update.task.taskId}");
|
// debugPrint("[COMPLETE] ${update.task.taskId}");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:flutter_cache_manager/file.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
||||||
import 'package:immich_mobile/utils/upload.dart';
|
import 'package:immich_mobile/utils/upload.dart';
|
||||||
@ -19,15 +20,15 @@ class UploadRepository implements IUploadRepository {
|
|||||||
taskQueue.maxConcurrent = 5;
|
taskQueue.maxConcurrent = 5;
|
||||||
FileDownloader().addTaskQueue(taskQueue);
|
FileDownloader().addTaskQueue(taskQueue);
|
||||||
FileDownloader().registerCallbacks(
|
FileDownloader().registerCallbacks(
|
||||||
group: uploadGroup,
|
group: kUploadGroup,
|
||||||
taskStatusCallback: (update) => onUploadStatus?.call(update),
|
taskStatusCallback: (update) => onUploadStatus?.call(update),
|
||||||
taskProgressCallback: (update) => onTaskProgress?.call(update),
|
taskProgressCallback: (update) => onTaskProgress?.call(update),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void enqueue(UploadTask task) {
|
void enqueueAll(List<UploadTask> tasks) {
|
||||||
taskQueue.add(task);
|
taskQueue.addAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,8 +41,27 @@ class UploadRepository implements IUploadRepository {
|
|||||||
return FileDownloader().cancelTaskWithId(id);
|
return FileDownloader().cancelTaskWithId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void cancelAll() {
|
||||||
|
return taskQueue.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pauseAll() {
|
||||||
|
return FileDownloader().pauseAll(group: kUploadGroup);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteRecordsWithIds(List<String> ids) {
|
Future<void> deleteRecordsWithIds(List<String> ids) {
|
||||||
return FileDownloader().database.deleteRecordsWithIds(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:cancellation_token_http/http.dart' as http;
|
import 'package:cancellation_token_http/http.dart' as http;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -262,6 +263,8 @@ class BackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<BackupCandidate> candidates = assets.toList();
|
List<BackupCandidate> candidates = assets.toList();
|
||||||
|
print("Found ${candidates.length} assets to upload");
|
||||||
|
List<UploadTask> uploadTasks = [];
|
||||||
for (final candidate in candidates) {
|
for (final candidate in candidates) {
|
||||||
final Asset asset = candidate.asset;
|
final Asset asset = candidate.asset;
|
||||||
File? file;
|
File? file;
|
||||||
@ -278,13 +281,24 @@ class BackupService {
|
|||||||
await _assetMediaRepository.getOriginalFilename(asset.localId!) ??
|
await _assetMediaRepository.getOriginalFilename(asset.localId!) ??
|
||||||
asset.fileName;
|
asset.fileName;
|
||||||
|
|
||||||
await _uploadService.upload(
|
final task = await _uploadService.buildUploadTask(
|
||||||
file,
|
file,
|
||||||
originalFileName: originalFileName,
|
originalFileName: originalFileName,
|
||||||
deviceAssetId: asset.localId,
|
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(
|
Future<bool> backupAsset(
|
||||||
|
@ -42,23 +42,47 @@ class UploadService {
|
|||||||
return FileDownloader().cancelTaskWithId(id);
|
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, {
|
File file, {
|
||||||
Map<String, String>? fields,
|
Map<String, String>? fields,
|
||||||
String? originalFileName,
|
String? originalFileName,
|
||||||
String? deviceAssetId,
|
String? deviceAssetId,
|
||||||
}) async {
|
}) async {
|
||||||
final task = await _buildUploadTask(
|
return _buildTask(
|
||||||
deviceAssetId ?? hash(file.path).toString(),
|
deviceAssetId ?? hash(file.path).toString(),
|
||||||
file,
|
file,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
originalFileName: originalFileName,
|
originalFileName: originalFileName,
|
||||||
);
|
);
|
||||||
|
|
||||||
_uploadRepository.enqueue(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadTask> _buildUploadTask(
|
Future<UploadTask> _buildTask(
|
||||||
String id,
|
String id,
|
||||||
File file, {
|
File file, {
|
||||||
Map<String, String>? fields,
|
Map<String, String>? fields,
|
||||||
@ -95,7 +119,7 @@ class UploadService {
|
|||||||
baseDirectory: baseDirectory,
|
baseDirectory: baseDirectory,
|
||||||
directory: directory,
|
directory: directory,
|
||||||
fileField: 'assetData',
|
fileField: 'assetData',
|
||||||
group: uploadGroup,
|
group: kUploadGroup,
|
||||||
updates: Updates.statusAndProgress,
|
updates: Updates.statusAndProgress,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
const uploadGroup = 'upload_group';
|
const kUploadGroup = 'upload_group';
|
||||||
|
@ -102,6 +102,10 @@ export class FileUploadInterceptor implements NestInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) {
|
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(
|
return callbackify(
|
||||||
() => this.assetService.getUploadFilename(asRequest(request, file)),
|
() => this.assetService.getUploadFilename(asRequest(request, file)),
|
||||||
callback as Callback<string>,
|
callback as Callback<string>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user