mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
new upload
This commit is contained in:
parent
5e68f8c519
commit
05070450c1
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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, {
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user