fix: android background backups (#21795)

* upload using dart client

* add connectivity api

* respect backup network setting

* comment as to why we need to wait for setForegroundAsync call

* log assets skipped due to network constraint

* dynamic spawning -> false

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-09-11 22:31:15 +05:30 committed by GitHub
parent 39c1ebf698
commit 722a464e23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 755 additions and 27 deletions

View file

@ -1,10 +1,15 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:background_downloader/background_downloader.dart';
import 'package:cancellation_token_http/http.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
import 'package:immich_mobile/platform/background_worker_api.g.dart';
@ -13,11 +18,13 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/localization.service.dart';
import 'package:immich_mobile/services/server_info.service.dart';
import 'package:immich_mobile/services/upload.service.dart';
import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart';
@ -42,6 +49,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
final Drift _drift;
final DriftLogger _driftLogger;
final BackgroundWorkerBgHostApi _backgroundHostApi;
final CancellationToken _cancellationToken = CancellationToken();
final Logger _logger = Logger('BackgroundWorkerBgService');
bool _isCleanedUp = false;
@ -87,6 +95,13 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
configureFileDownloaderNotifications();
if (Platform.isAndroid) {
await _backgroundHostApi.showNotification(
IntlKeys.uploading_media.t(),
IntlKeys.backup_background_service_in_progress_notification.t(),
);
}
// Notify the host that the background worker service has been initialized and is ready to use
_backgroundHostApi.onInitialized();
} catch (error, stack) {
@ -102,7 +117,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
final sw = Stopwatch()..start();
await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6));
await _handleBackup(processBulk: false);
await _handleBackup();
sw.stop();
_logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s");
@ -155,9 +170,13 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
try {
_isCleanedUp = true;
_cancellationToken.cancel();
_logger.info("Cleaning up background worker");
final cleanupFutures = [
workerManager.dispose(),
workerManager.dispose().catchError((_) async {
// Discard any errors on the dispose call
return;
}),
_drift.close(),
_driftLogger.close(),
_ref.read(backgroundSyncProvider).cancel(),
@ -175,7 +194,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
}
}
Future<void> _handleBackup({bool processBulk = true}) async {
Future<void> _handleBackup() async {
if (!_isBackupEnabled || _isCleanedUp) {
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
return;
@ -189,19 +208,22 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
return;
}
if (processBulk) {
_logger.info("[_handleBackup 4] Resume backup from background");
_logger.info("[_handleBackup 4] Resume backup from background");
if (Platform.isIOS) {
return _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
}
final activeTask = await _ref.read(uploadServiceProvider).getActiveTasks(currentUser.id);
if (activeTask.isNotEmpty) {
_logger.info("[_handleBackup 5] Resuming backup for active tasks from background");
await _ref.read(uploadServiceProvider).resumeBackup();
} else {
_logger.info("[_handleBackup 6] Starting serial backup for new tasks from background");
await _ref.read(uploadServiceProvider).startBackupSerial(currentUser.id);
final canPing = await _ref.read(serverInfoServiceProvider).ping();
if (!canPing) {
_logger.warning("[_handleBackup 5] Server is not reachable. Skipping backup from background");
return;
}
final networkCapabilities = await _ref.read(connectivityApiProvider).getCapabilities();
return _ref
.read(uploadServiceProvider)
.startBackupWithHttpClient(currentUser.id, networkCapabilities.hasWifi, _cancellationToken);
}
Future<void> _syncAssets({Duration? hashTimeout}) async {