mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(mobile): configurable background backup delay (#1068)
let's the user configure how much to delay the trigger for running the backup whenever assets are changed on the device
This commit is contained in:
parent
a97b761eda
commit
c23b2479f7
@ -54,7 +54,9 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||
val requireCharging = args.get(1) as Boolean
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
||||
val triggerUpdateDelay = (args.get(2) as Number).toLong()
|
||||
val triggerMaxDelay = (args.get(3) as Number).toLong()
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging, triggerUpdateDelay, triggerMaxDelay)
|
||||
result.success(true)
|
||||
}
|
||||
"disable" -> {
|
||||
|
@ -37,6 +37,8 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
||||
const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay"
|
||||
const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay"
|
||||
|
||||
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||
|
||||
@ -62,12 +64,16 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
*/
|
||||
fun configureWork(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false) {
|
||||
requireCharging: Boolean = false,
|
||||
triggerUpdateDelay: Long = 5000,
|
||||
triggerMaxDelay: Long = 50000) {
|
||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
||||
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
||||
.apply()
|
||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||
}
|
||||
@ -106,12 +112,14 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
}
|
||||
|
||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
val constraints = Constraints.Builder()
|
||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.setTriggerContentUpdateDelay(5000, TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||
|
@ -41,6 +41,7 @@
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_backup": "Backup",
|
||||
"backup_controller_page_backup_selected": "Selected: ",
|
||||
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
||||
@ -134,6 +135,7 @@
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
|
@ -26,6 +26,7 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
||||
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
||||
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
||||
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
||||
const String backupTriggerDelay = "immichBackupTriggerDelay"; // Key 4
|
||||
|
||||
// Duplicate asset
|
||||
const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box
|
||||
|
@ -86,6 +86,8 @@ class BackgroundService {
|
||||
Future<bool> configureService({
|
||||
bool requireUnmetered = true,
|
||||
bool requireCharging = false,
|
||||
int triggerUpdateDelay = 5000,
|
||||
int triggerMaxDelay = 50000,
|
||||
}) async {
|
||||
if (!Platform.isAndroid) {
|
||||
return true;
|
||||
@ -93,7 +95,12 @@ class BackgroundService {
|
||||
try {
|
||||
final bool ok = await _foregroundChannel.invokeMethod(
|
||||
'configure',
|
||||
[requireUnmetered, requireCharging],
|
||||
[
|
||||
requireUnmetered,
|
||||
requireCharging,
|
||||
triggerUpdateDelay,
|
||||
triggerMaxDelay
|
||||
],
|
||||
);
|
||||
return ok;
|
||||
} catch (error) {
|
||||
|
@ -18,6 +18,7 @@ class BackUpState {
|
||||
final bool backgroundBackup;
|
||||
final bool backupRequireWifi;
|
||||
final bool backupRequireCharging;
|
||||
final int backupTriggerDelay;
|
||||
|
||||
/// All available albums on the device
|
||||
final List<AvailableAlbum> availableAlbums;
|
||||
@ -42,6 +43,7 @@ class BackUpState {
|
||||
required this.backgroundBackup,
|
||||
required this.backupRequireWifi,
|
||||
required this.backupRequireCharging,
|
||||
required this.backupTriggerDelay,
|
||||
required this.availableAlbums,
|
||||
required this.selectedBackupAlbums,
|
||||
required this.excludedBackupAlbums,
|
||||
@ -59,6 +61,7 @@ class BackUpState {
|
||||
bool? backgroundBackup,
|
||||
bool? backupRequireWifi,
|
||||
bool? backupRequireCharging,
|
||||
int? backupTriggerDelay,
|
||||
List<AvailableAlbum>? availableAlbums,
|
||||
Set<AvailableAlbum>? selectedBackupAlbums,
|
||||
Set<AvailableAlbum>? excludedBackupAlbums,
|
||||
@ -76,6 +79,7 @@ class BackUpState {
|
||||
backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi,
|
||||
backupRequireCharging:
|
||||
backupRequireCharging ?? this.backupRequireCharging,
|
||||
backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay,
|
||||
availableAlbums: availableAlbums ?? this.availableAlbums,
|
||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||
@ -88,7 +92,7 @@ class BackUpState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -105,6 +109,7 @@ class BackUpState {
|
||||
other.backgroundBackup == backgroundBackup &&
|
||||
other.backupRequireWifi == backupRequireWifi &&
|
||||
other.backupRequireCharging == backupRequireCharging &&
|
||||
other.backupTriggerDelay == backupTriggerDelay &&
|
||||
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||
@ -126,6 +131,7 @@ class BackUpState {
|
||||
backgroundBackup.hashCode ^
|
||||
backupRequireWifi.hashCode ^
|
||||
backupRequireCharging.hashCode ^
|
||||
backupTriggerDelay.hashCode ^
|
||||
availableAlbums.hashCode ^
|
||||
selectedBackupAlbums.hashCode ^
|
||||
excludedBackupAlbums.hashCode ^
|
||||
|
@ -38,6 +38,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
backgroundBackup: false,
|
||||
backupRequireWifi: true,
|
||||
backupRequireCharging: false,
|
||||
backupTriggerDelay: 5000,
|
||||
serverInfo: ServerInfoResponseDto(
|
||||
diskAvailable: "0",
|
||||
diskAvailableRaw: 0,
|
||||
@ -119,18 +120,26 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
bool? enabled,
|
||||
bool? requireWifi,
|
||||
bool? requireCharging,
|
||||
int? triggerDelay,
|
||||
required void Function(String msg) onError,
|
||||
required void Function() onBatteryInfo,
|
||||
}) async {
|
||||
assert(enabled != null || requireWifi != null || requireCharging != null);
|
||||
assert(
|
||||
enabled != null ||
|
||||
requireWifi != null ||
|
||||
requireCharging != null ||
|
||||
triggerDelay != null,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
final bool wasEnabled = state.backgroundBackup;
|
||||
final bool wasWifi = state.backupRequireWifi;
|
||||
final bool wasCharing = state.backupRequireCharging;
|
||||
final bool wasCharging = state.backupRequireCharging;
|
||||
final int oldTriggerDelay = state.backupTriggerDelay;
|
||||
state = state.copyWith(
|
||||
backgroundBackup: enabled,
|
||||
backupRequireWifi: requireWifi,
|
||||
backupRequireCharging: requireCharging,
|
||||
backupTriggerDelay: triggerDelay,
|
||||
);
|
||||
|
||||
if (state.backgroundBackup) {
|
||||
@ -145,17 +154,22 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await _backgroundService.configureService(
|
||||
requireUnmetered: state.backupRequireWifi,
|
||||
requireCharging: state.backupRequireCharging,
|
||||
triggerUpdateDelay: state.backupTriggerDelay,
|
||||
triggerMaxDelay: state.backupTriggerDelay * 10,
|
||||
);
|
||||
if (success) {
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireWifi, state.backupRequireWifi);
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireCharging, state.backupRequireCharging);
|
||||
final box = Hive.box(backgroundBackupInfoBox);
|
||||
await Future.wait([
|
||||
box.put(backupRequireWifi, state.backupRequireWifi),
|
||||
box.put(backupRequireCharging, state.backupRequireCharging),
|
||||
box.put(backupTriggerDelay, state.backupTriggerDelay),
|
||||
]);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
backgroundBackup: wasEnabled,
|
||||
backupRequireWifi: wasWifi,
|
||||
backupRequireCharging: wasCharing,
|
||||
backupRequireCharging: wasCharging,
|
||||
backupTriggerDelay: oldTriggerDelay,
|
||||
);
|
||||
onError("backup_controller_page_background_configure_error");
|
||||
}
|
||||
@ -602,6 +616,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
excludedBackupAlbums: excludedAlbums,
|
||||
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
||||
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
||||
backupTriggerDelay: backgroundBox.get(backupTriggerDelay),
|
||||
);
|
||||
}
|
||||
return _resumeBackup();
|
||||
|
@ -198,6 +198,46 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
final bool isWifiRequired = backupState.backupRequireWifi;
|
||||
final bool isChargingRequired = backupState.backupRequireCharging;
|
||||
final Color activeColor = Theme.of(context).primaryColor;
|
||||
|
||||
String formatBackupDelaySliderValue(double v) {
|
||||
if (v == 0.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['5']);
|
||||
} else if (v == 1.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['30']);
|
||||
} else if (v == 2.0) {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['2']);
|
||||
} else {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['10']);
|
||||
}
|
||||
}
|
||||
|
||||
int backupDelayToMilliseconds(double v) {
|
||||
if (v == 0.0) {
|
||||
return 5000;
|
||||
} else if (v == 1.0) {
|
||||
return 30000;
|
||||
} else if (v == 2.0) {
|
||||
return 120000;
|
||||
} else {
|
||||
return 600000;
|
||||
}
|
||||
}
|
||||
|
||||
double backupDelayToSliderValue(int ms) {
|
||||
if (ms == 5000) {
|
||||
return 0.0;
|
||||
} else if (ms == 30000) {
|
||||
return 1.0;
|
||||
} else if (ms == 120000) {
|
||||
return 2.0;
|
||||
} else {
|
||||
return 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
final triggerDelay =
|
||||
useState(backupDelayToSliderValue(backupState.backupTriggerDelay));
|
||||
|
||||
return ListTile(
|
||||
isThreeLine: true,
|
||||
leading: isBackgroundEnabled
|
||||
@ -264,6 +304,35 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (isBackgroundEnabled)
|
||||
ListTile(
|
||||
isThreeLine: false,
|
||||
dense: true,
|
||||
enabled: hasExclusiveAccess,
|
||||
title: const Text(
|
||||
'backup_controller_page_background_delay',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(args: [formatBackupDelaySliderValue(triggerDelay.value)]),
|
||||
subtitle: Slider(
|
||||
value: triggerDelay.value,
|
||||
onChanged: hasExclusiveAccess
|
||||
? (double v) => triggerDelay.value = v
|
||||
: null,
|
||||
onChangeEnd: (double v) => ref
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(
|
||||
triggerDelay: backupDelayToMilliseconds(v),
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
),
|
||||
max: 3.0,
|
||||
divisions: 3,
|
||||
label: formatBackupDelaySliderValue(triggerDelay.value),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||
|
Loading…
Reference in New Issue
Block a user