mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
background upload plugin
add schemas sync variants formatting initial implementation use existing db, wip move to separate folder fix table definitions wip wiring it up
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/platform/upload_api.g.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
@@ -196,6 +194,9 @@ final driftBackupProvider = StateNotifierProvider<DriftBackupNotifier, DriftBack
|
||||
});
|
||||
|
||||
class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
late final StreamSubscription<UploadApiTaskStatus> _taskStatusStream;
|
||||
late final StreamSubscription<UploadApiTaskProgress> _taskProgressStream;
|
||||
|
||||
DriftBackupNotifier(this._uploadService)
|
||||
: super(
|
||||
const DriftBackupState(
|
||||
@@ -211,17 +212,20 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
error: BackupError.none,
|
||||
),
|
||||
) {
|
||||
{
|
||||
_uploadService.taskStatusStream.listen(_handleTaskStatusUpdate);
|
||||
_uploadService.taskProgressStream.listen(_handleTaskProgressUpdate);
|
||||
}
|
||||
_taskStatusStream = _uploadService.taskStatusStream.listen(_handleTaskStatusUpdate);
|
||||
_taskProgressStream = _uploadService.taskProgressStream.listen(_handleTaskProgressUpdate);
|
||||
}
|
||||
|
||||
final UploadService _uploadService;
|
||||
StreamSubscription<TaskStatusUpdate>? _statusSubscription;
|
||||
StreamSubscription<TaskProgressUpdate>? _progressSubscription;
|
||||
final _logger = Logger("DriftBackupNotifier");
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_taskStatusStream.cancel());
|
||||
unawaited(_taskProgressStream.cancel());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Remove upload item from state
|
||||
void _removeUploadItem(String taskId) {
|
||||
if (state.uploadItems.containsKey(taskId)) {
|
||||
@@ -231,16 +235,12 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTaskStatusUpdate(TaskStatusUpdate update) {
|
||||
final taskId = update.task.taskId;
|
||||
void _handleTaskStatusUpdate(UploadApiTaskStatus update) {
|
||||
final taskId = update.id;
|
||||
|
||||
switch (update.status) {
|
||||
case TaskStatus.complete:
|
||||
if (update.task.group == kBackupGroup) {
|
||||
if (update.responseStatusCode == 201) {
|
||||
state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1);
|
||||
}
|
||||
}
|
||||
case UploadApiStatus.uploadComplete:
|
||||
state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1);
|
||||
|
||||
// Remove the completed task from the upload items
|
||||
if (state.uploadItems.containsKey(taskId)) {
|
||||
@@ -249,40 +249,45 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
});
|
||||
}
|
||||
|
||||
case TaskStatus.failed:
|
||||
// Ignore retry errors to avoid confusing users
|
||||
if (update.exception?.description == 'Delayed or retried enqueue failed') {
|
||||
_removeUploadItem(taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
case UploadApiStatus.uploadFailed:
|
||||
final currentItem = state.uploadItems[taskId];
|
||||
if (currentItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String? error;
|
||||
final exception = update.exception;
|
||||
if (exception != null && exception is TaskHttpException) {
|
||||
final message = tryJsonDecode(exception.description)?['message'] as String?;
|
||||
if (message != null) {
|
||||
final responseCode = exception.httpResponseCode;
|
||||
error = "${exception.exceptionType}, response code $responseCode: $message";
|
||||
}
|
||||
}
|
||||
error ??= update.exception?.toString();
|
||||
// String? error;
|
||||
// final exception = update.exception;
|
||||
// if (exception != null && exception is TaskHttpException) {
|
||||
// final message = tryJsonDecode(exception.description)?['message'] as String?;
|
||||
// if (message != null) {
|
||||
// final responseCode = exception.httpResponseCode;
|
||||
// error = "${exception.exceptionType}, response code $responseCode: $message";
|
||||
// }
|
||||
// }
|
||||
// error ??= update.exception?.toString();
|
||||
|
||||
// state = state.copyWith(
|
||||
// uploadItems: {
|
||||
// ...state.uploadItems,
|
||||
// taskId: currentItem.copyWith(isFailed: true, error: error),
|
||||
// },
|
||||
// );
|
||||
// _logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}");
|
||||
break;
|
||||
|
||||
case UploadApiStatus.uploadPending:
|
||||
state = state.copyWith(
|
||||
uploadItems: {
|
||||
...state.uploadItems,
|
||||
taskId: currentItem.copyWith(isFailed: true, error: error),
|
||||
taskId: DriftUploadStatus(
|
||||
taskId: update.id.toString(),
|
||||
filename: update.filename,
|
||||
fileSize: 0,
|
||||
networkSpeedAsString: "",
|
||||
progress: 0.0,
|
||||
),
|
||||
},
|
||||
);
|
||||
_logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}");
|
||||
break;
|
||||
|
||||
case TaskStatus.canceled:
|
||||
_removeUploadItem(update.task.taskId);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -290,42 +295,21 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTaskProgressUpdate(TaskProgressUpdate update) {
|
||||
final taskId = update.task.taskId;
|
||||
final filename = update.task.displayName;
|
||||
void _handleTaskProgressUpdate(UploadApiTaskProgress update) {
|
||||
final taskId = update.id;
|
||||
final progress = update.progress;
|
||||
final currentItem = state.uploadItems[taskId];
|
||||
if (currentItem != null) {
|
||||
if (progress == kUploadStatusCanceled) {
|
||||
_removeUploadItem(update.task.taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
uploadItems: {
|
||||
...state.uploadItems,
|
||||
taskId: update.hasExpectedFileSize
|
||||
? currentItem.copyWith(
|
||||
progress: progress,
|
||||
fileSize: update.expectedFileSize,
|
||||
networkSpeedAsString: update.networkSpeedAsString,
|
||||
)
|
||||
: currentItem.copyWith(progress: progress),
|
||||
},
|
||||
);
|
||||
|
||||
if (currentItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
uploadItems: {
|
||||
...state.uploadItems,
|
||||
taskId: DriftUploadStatus(
|
||||
taskId: taskId,
|
||||
filename: filename,
|
||||
taskId: currentItem.copyWith(
|
||||
progress: progress,
|
||||
fileSize: update.expectedFileSize,
|
||||
networkSpeedAsString: update.networkSpeedAsString,
|
||||
fileSize: update.totalBytes,
|
||||
networkSpeedAsString: update.speed?.toStringAsFixed(2) ?? "",
|
||||
),
|
||||
},
|
||||
);
|
||||
@@ -350,52 +334,18 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
state = state.copyWith(isSyncing: isSyncing);
|
||||
}
|
||||
|
||||
Future<void> startBackup(String userId) {
|
||||
Future<void> startBackup() {
|
||||
state = state.copyWith(error: BackupError.none);
|
||||
return _uploadService.startBackup(userId, _updateEnqueueCount);
|
||||
}
|
||||
|
||||
void _updateEnqueueCount(EnqueueStatus status) {
|
||||
state = state.copyWith(enqueueCount: status.enqueueCount, enqueueTotalCount: status.totalCount);
|
||||
return _uploadService.startBackup();
|
||||
}
|
||||
|
||||
Future<void> cancel() async {
|
||||
dPrint(() => "Canceling backup tasks...");
|
||||
state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true, error: BackupError.none);
|
||||
|
||||
final activeTaskCount = await _uploadService.cancelBackup();
|
||||
|
||||
if (activeTaskCount > 0) {
|
||||
dPrint(() => "$activeTaskCount tasks left, continuing to cancel...");
|
||||
await cancel();
|
||||
} else {
|
||||
dPrint(() => "All tasks canceled successfully.");
|
||||
// Clear all upload items when cancellation is complete
|
||||
state = state.copyWith(isCanceling: false, uploadItems: {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleBackupResume(String userId) async {
|
||||
_logger.info("Resuming backup tasks...");
|
||||
state = state.copyWith(error: BackupError.none);
|
||||
final tasks = await _uploadService.getActiveTasks(kBackupGroup);
|
||||
_logger.info("Found ${tasks.length} tasks");
|
||||
|
||||
if (tasks.isEmpty) {
|
||||
// Start a new backup queue
|
||||
_logger.info("Start a new backup queue");
|
||||
return startBackup(userId);
|
||||
}
|
||||
|
||||
_logger.info("Tasks to resume: ${tasks.length}");
|
||||
return _uploadService.resumeBackup();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_statusSubscription?.cancel();
|
||||
_progressSubscription?.cancel();
|
||||
super.dispose();
|
||||
await _uploadService.cancelBackup();
|
||||
dPrint(() => "All tasks canceled successfully.");
|
||||
state = state.copyWith(isCanceling: false, uploadItems: {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user