new upload

This commit is contained in:
Alex 2025-04-16 22:06:44 -05:00
parent 5e68f8c519
commit 05070450c1
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
8 changed files with 157 additions and 29 deletions

View File

@ -45,7 +45,7 @@ post_install do |installer|
installer.generated_projects.each do |project|
project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6'
end
end
end

View File

@ -224,7 +224,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
background_downloader: b42a56120f5348bff70e74222f0e9e6f7f1a1537
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
@ -261,6 +261,6 @@ SPEC CHECKSUMS:
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
PODFILE CHECKSUM: 03b7eead4ee77b9e778179eeb0f3b5513617451c
PODFILE CHECKSUM: 04655a9b6714fa7a2b4fb559982ee8bed4b3b718
COCOAPODS: 1.16.2

View File

@ -546,7 +546,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -690,7 +690,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -720,7 +720,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@ -4,7 +4,7 @@ abstract interface class IUploadRepository {
void Function(TaskStatusUpdate)? onUploadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
Future<bool> upload(UploadTask task);
void enqueue(UploadTask task);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);

View File

@ -1,10 +1,12 @@
import 'dart:io';
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:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart';
@ -20,6 +22,7 @@ import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
@ -30,6 +33,7 @@ import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/backup_album.service.dart';
import 'package:immich_mobile/services/server_info.service.dart';
import 'package:immich_mobile/services/upload.service.dart';
import 'package:immich_mobile/utils/backup_progress.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:logging/logging.dart';
@ -47,6 +51,7 @@ final backupProvider =
ref.watch(albumMediaRepositoryProvider),
ref.watch(fileMediaRepositoryProvider),
ref.watch(backupAlbumServiceProvider),
ref.watch(uploadServiceProvider),
ref,
);
});
@ -61,6 +66,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
this._albumMediaRepository,
this._fileMediaRepository,
this._backupAlbumService,
this._uploadService,
this.ref,
) : super(
BackUpState(
@ -100,7 +106,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
),
iCloudDownloadProgress: 0.0,
),
);
) {
_uploadService.onUploadStatus = _uploadStatusCallback;
_uploadService.onTaskProgress = _taskProgressCallback;
}
final log = Logger('BackupNotifier');
final BackupService _backupService;
@ -111,6 +120,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final IAlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository;
final BackupAlbumService _backupAlbumService;
final UploadService _uploadService;
final Ref ref;
///
@ -488,7 +498,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
Future<void> startBackupProcess() async {
debugPrint("Start backup process");
assert(state.backupProgress == BackUpProgressEnum.idle);
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
// state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
await getBackupInfo();
@ -522,21 +532,89 @@ class BackupNotifier extends StateNotifier<BackUpState> {
state = state.copyWith(iCloudDownloadProgress: progress);
});
await _backupService.backupAsset(
assetsWillBeBackup,
state.cancelToken,
pmProgressHandler: pmProgressHandler,
onSuccess: _onAssetUploaded,
onProgress: _onUploadProgress,
onCurrentAsset: _onSetCurrentBackupAsset,
onError: _onBackupError,
);
// await _backupService.backupAsset(
// assetsWillBeBackup,
// state.cancelToken,
// pmProgressHandler: pmProgressHandler,
// onSuccess: _onAssetUploaded,
// onProgress: _onUploadProgress,
// onCurrentAsset: _onSetCurrentBackupAsset,
// onError: _onBackupError,
// );
await _backupService.uploadAssets(assetsWillBeBackup);
await notifyBackgroundServiceCanRun();
} else {
openAppSettings();
}
}
void _updateUploadStatus(TaskStatusUpdate task, TaskStatus status) async {
if (status == TaskStatus.canceled) {
return;
}
final taskId = task.task.taskId;
final uploadStatus = switch (task.status) {
TaskStatus.complete => UploadStatus.complete,
TaskStatus.failed => UploadStatus.failed,
TaskStatus.canceled => UploadStatus.canceled,
TaskStatus.enqueued => UploadStatus.enqueued,
TaskStatus.running => UploadStatus.running,
TaskStatus.paused => UploadStatus.paused,
TaskStatus.notFound => UploadStatus.notFound,
TaskStatus.waitingToRetry => UploadStatus.waitingtoRetry
};
// state = [
// for (final attachment in state)
// if (attachment.id == taskId.toInt())
// attachment.copyWith(status: uploadStatus)
// else
// attachment,
// ];
}
void _uploadStatusCallback(TaskStatusUpdate update) {
_updateUploadStatus(update, update.status);
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}");
}
}
break;
default:
break;
}
}
void _taskProgressCallback(TaskProgressUpdate update) {
// Ignore if the task is canceled or completed
if (update.progress == downloadFailed ||
update.progress == downloadCompleted) {
return;
}
// print("[_taskProgressCallback] $update");
final taskId = update.task.taskId;
// state = [
// for (final attachment in state)
// if (attachment.id == taskId.toInt())
// attachment.copyWith(uploadProgress: update.progress)
// else
// attachment,
// ];
}
void setAvailableAlbums(availableAlbums) {
state = state.copyWith(
availableAlbums: availableAlbums,

View File

@ -12,7 +12,12 @@ class UploadRepository implements IUploadRepository {
@override
void Function(TaskProgressUpdate)? onTaskProgress;
final taskQueue = MemoryTaskQueue();
UploadRepository() {
// taskQueue.minInterval = const Duration(milliseconds: 5);
// taskQueue.maxConcurrent = 2;
FileDownloader().addTaskQueue(taskQueue);
FileDownloader().registerCallbacks(
group: uploadGroup,
taskStatusCallback: (update) => onUploadStatus?.call(update),
@ -21,8 +26,8 @@ class UploadRepository implements IUploadRepository {
}
@override
Future<bool> upload(UploadTask task) {
return FileDownloader().enqueue(task);
void enqueue(UploadTask task) {
taskQueue.add(task);
}
@override

View File

@ -28,6 +28,7 @@ import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/upload.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:path/path.dart' as p;
@ -43,6 +44,7 @@ final backupServiceProvider = Provider(
ref.watch(fileMediaRepositoryProvider),
ref.watch(assetRepositoryProvider),
ref.watch(assetMediaRepositoryProvider),
ref.watch(uploadServiceProvider),
),
);
@ -56,6 +58,7 @@ class BackupService {
final IFileMediaRepository _fileMediaRepository;
final IAssetRepository _assetRepository;
final IAssetMediaRepository _assetMediaRepository;
final UploadService _uploadService;
BackupService(
this._apiService,
@ -65,6 +68,7 @@ class BackupService {
this._fileMediaRepository,
this._assetRepository,
this._assetMediaRepository,
this._uploadService,
);
Future<List<String>?> getDeviceBackupAsset() async {
@ -249,6 +253,42 @@ class BackupService {
);
}
uploadAssets(
Iterable<BackupCandidate> assets,
) async {
final hasPermission = await _checkPermissions();
if (!hasPermission) {
return false;
}
List<BackupCandidate> candidates = assets.toList();
for (final candidate in candidates) {
final Asset asset = candidate.asset;
File? file;
File? livePhotoFile;
file = await asset.local!.originFile.timeout(const Duration(seconds: 5));
if (asset.local!.isLivePhoto) {
livePhotoFile = await asset.local!.originFileWithSubtype
.timeout(const Duration(seconds: 5));
}
if (file != null) {
String? originalFileName =
await _assetMediaRepository.getOriginalFilename(asset.localId!) ??
asset.fileName;
await _uploadService.upload(
file,
originalFileName: originalFileName,
deviceAssetId: asset.localId,
);
}
break;
}
}
Future<bool> backupAsset(
Iterable<BackupCandidate> assets,
http.CancellationToken cancelToken, {

View File

@ -42,19 +42,27 @@ class UploadService {
return FileDownloader().cancelTaskWithId(id);
}
Future<void> upload(File file) async {
Future<void> upload(
File file, {
Map<String, String>? fields,
String? originalFileName,
String? deviceAssetId,
}) async {
final task = await _buildUploadTask(
hash(file.path).toString(),
deviceAssetId ?? hash(file.path).toString(),
file,
fields: fields,
originalFileName: originalFileName,
);
await _uploadRepository.upload(task);
_uploadRepository.enqueue(task);
}
Future<UploadTask> _buildUploadTask(
String id,
File file, {
Map<String, String>? fields,
String? originalFileName,
}) async {
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final url = Uri.parse('$serverEndpoint/assets').toString();
@ -66,9 +74,8 @@ class UploadService {
final stats = await file.stat();
final fileCreatedAt = stats.changed;
final fileModifiedAt = stats.modified;
final fieldsMap = {
'filename': filename,
'filename': originalFileName ?? filename,
'deviceAssetId': id,
'deviceId': deviceId,
'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(),
@ -78,15 +85,13 @@ class UploadService {
if (fields != null) ...fields,
};
return UploadTask(
return UploadTask.fromFile(
file: file,
taskId: id,
httpRequestMethod: 'POST',
url: url,
headers: headers,
filename: filename,
fields: fieldsMap,
baseDirectory: baseDirectory,
directory: directory,
fileField: 'assetData',
group: uploadGroup,
updates: Updates.statusAndProgress,