diff --git a/lib/controller/backup_controller.dart b/lib/controller/backup_controller.dart index 02eae46d..efef3914 100644 --- a/lib/controller/backup_controller.dart +++ b/lib/controller/backup_controller.dart @@ -27,13 +27,49 @@ class BackupController { final RxBool isRestoringBackup = false.obs; String get _backupDirectoryPath => settings.defaultBackupLocation.value; + int get _defaultAutoBackupInterval => settings.autoBackupIntervalDays.value; + + Future checkForAutoBackup() async { + final interval = _defaultAutoBackupInterval; + if (interval <= 0) return; + + if (!await requestManageStoragePermission()) return; + + final sortedBackupFiles = await _getBackupFilesSorted.thready(_backupDirectoryPath); + final latestBackup = sortedBackupFiles.firstOrNull; + if (latestBackup != null) { + final lastModified = await latestBackup.stat().then((value) => value.modified); + final diff = DateTime.now().difference(lastModified).abs().inDays; + if (diff > interval) { + final itemsToBackup = [ + AppPaths.TRACKS, + AppPaths.TRACKS_STATS, + AppPaths.TOTAL_LISTEN_TIME, + AppPaths.VIDEOS_CACHE, + AppPaths.VIDEOS_LOCAL, + AppPaths.FAVOURITES_PLAYLIST, + AppPaths.SETTINGS, + AppPaths.LATEST_QUEUE, + AppPaths.YT_LIKES_PLAYLIST, + AppDirs.PLAYLISTS, + AppDirs.HISTORY_PLAYLIST, + AppDirs.QUEUES, + AppDirs.YT_DOWNLOAD_TASKS, + AppDirs.YT_STATS, + AppDirs.YT_PLAYLISTS, + AppDirs.YT_HISTORY_PLAYLIST, + ]; + + await createBackupFile(itemsToBackup); + } + } + } Future createBackupFile(List backupItemsPaths) async { if (isCreatingBackup.value) return snackyy(title: lang.NOTE, message: lang.ANOTHER_PROCESS_IS_RUNNING); - if (!await requestManageStoragePermission()) { - return; - } + if (!await requestManageStoragePermission()) return; + isCreatingBackup.value = true; // formats date diff --git a/lib/controller/settings_controller.dart b/lib/controller/settings_controller.dart index e1a08cc6..72ee109e 100644 --- a/lib/controller/settings_controller.dart +++ b/lib/controller/settings_controller.dart @@ -93,6 +93,7 @@ class SettingsController { final RxBool preventDuplicatedTracks = false.obs; final RxBool respectNoMedia = false.obs; final RxString defaultBackupLocation = AppDirs.BACKUPS.obs; + final RxInt autoBackupIntervalDays = 2.obs; final RxString defaultFolderStartupLocation = kStoragePaths.first.obs; final RxString ytDownloadLocation = AppDirs.YOUTUBE_DOWNLOADS_DEFAULT.obs; final RxBool enableFoldersHierarchy = true.obs; @@ -401,6 +402,7 @@ class SettingsController { preventDuplicatedTracks.value = json['preventDuplicatedTracks'] ?? preventDuplicatedTracks.value; respectNoMedia.value = json['respectNoMedia'] ?? respectNoMedia.value; defaultBackupLocation.value = json['defaultBackupLocation'] ?? defaultBackupLocation.value; + autoBackupIntervalDays.value = json['autoBackupIntervalDays'] ?? autoBackupIntervalDays.value; defaultFolderStartupLocation.value = json['defaultFolderStartupLocation'] ?? defaultFolderStartupLocation.value; ytDownloadLocation.value = json['ytDownloadLocation'] ?? ytDownloadLocation.value; enableFoldersHierarchy.value = json['enableFoldersHierarchy'] ?? enableFoldersHierarchy.value; @@ -638,6 +640,7 @@ class SettingsController { 'preventDuplicatedTracks': preventDuplicatedTracks.value, 'respectNoMedia': respectNoMedia.value, 'defaultBackupLocation': defaultBackupLocation.value, + 'autoBackupIntervalDays': autoBackupIntervalDays.value, 'defaultFolderStartupLocation': defaultFolderStartupLocation.value, 'ytDownloadLocation': ytDownloadLocation.value, 'enableFoldersHierarchy': enableFoldersHierarchy.value, @@ -830,6 +833,7 @@ class SettingsController { bool? preventDuplicatedTracks, bool? respectNoMedia, String? defaultBackupLocation, + int? autoBackupIntervalDays, String? defaultFolderStartupLocation, String? ytDownloadLocation, bool? enableFoldersHierarchy, @@ -1171,6 +1175,9 @@ class SettingsController { if (defaultBackupLocation != null) { this.defaultBackupLocation.value = defaultBackupLocation; } + if (autoBackupIntervalDays != null) { + this.autoBackupIntervalDays.value = autoBackupIntervalDays; + } if (defaultFolderStartupLocation != null) { this.defaultFolderStartupLocation.value = defaultFolderStartupLocation; } diff --git a/lib/core/translations/keys.dart b/lib/core/translations/keys.dart index fc08c813..766514f0 100644 --- a/lib/core/translations/keys.dart +++ b/lib/core/translations/keys.dart @@ -53,6 +53,7 @@ abstract class LanguageKeys { String get AUDIO => _getKey('AUDIO'); String get AUDIO_CACHE => _getKey('AUDIO_CACHE'); String get AUDIO_ONLY => _getKey('AUDIO_ONLY'); + String get AUTO_BACKUP_INTERVAL => _getKey('AUTO_BACKUP_INTERVAL'); String get AUTO_COLORING_SUBTITLE => _getKey('AUTO_COLORING_SUBTITLE'); String get AUTO_COLORING => _getKey('AUTO_COLORING'); String get AUTO_EXTRACT_TAGS_FROM_FILENAME => _getKey('AUTO_EXTRACT_TAGS_FROM_FILENAME'); diff --git a/lib/main.dart b/lib/main.dart index 51b74e38..a67f0dad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:namida/controller/backup_controller.dart'; import 'package:namida/controller/connectivity.dart'; import 'package:namida/controller/current_color.dart'; import 'package:namida/controller/folders_controller.dart'; @@ -124,6 +125,9 @@ void main() async { if (!shouldShowOnBoarding && settings.refreshOnStartup.value) { Indexer.inst.refreshLibraryAndCheckForDiff(allowDeletion: false, showFinishedSnackbar: false); } + if (!shouldShowOnBoarding) { + BackupController.inst.checkForAutoBackup(); + } QueueController.inst.prepareAllQueuesFile(); diff --git a/lib/ui/widgets/settings/backup_restore_settings.dart b/lib/ui/widgets/settings/backup_restore_settings.dart index a6735b23..ab52975f 100644 --- a/lib/ui/widgets/settings/backup_restore_settings.dart +++ b/lib/ui/widgets/settings/backup_restore_settings.dart @@ -26,6 +26,7 @@ enum _BackupAndRestoreKeys { create, restore, defaultLocation, + autoBackupInterval, importYT, importLastfm, } @@ -41,6 +42,7 @@ class BackupAndRestore extends SettingSubpageProvider { _BackupAndRestoreKeys.create: [lang.CREATE_BACKUP], _BackupAndRestoreKeys.restore: [lang.RESTORE_BACKUP], _BackupAndRestoreKeys.defaultLocation: [lang.DEFAULT_BACKUP_LOCATION], + _BackupAndRestoreKeys.autoBackupInterval: [lang.AUTO_BACKUP_INTERVAL], _BackupAndRestoreKeys.importYT: [lang.IMPORT_YOUTUBE_HISTORY], _BackupAndRestoreKeys.importLastfm: [lang.IMPORT_LAST_FM_HISTORY], }; @@ -516,6 +518,31 @@ class BackupAndRestore extends SettingSubpageProvider { // -- Default Backup Location getDefaultBackupLocationWidget(), + // -- Auto backup interval + getItemWrapper( + key: _BackupAndRestoreKeys.autoBackupInterval, + child: CustomListTile( + bgColor: getBgColor(_BackupAndRestoreKeys.autoBackupInterval), + title: lang.AUTO_BACKUP_INTERVAL, + icon: Broken.timer, + trailing: Obx( + () { + final days = settings.autoBackupIntervalDays.value; + return NamidaWheelSlider( + totalCount: 14, + squeeze: 1, + initValue: days, + itemSize: 5, + onValueChanged: (val) { + settings.save(autoBackupIntervalDays: val); + }, + text: days == 0 ? lang.NONE : "$days ${days == 1 ? lang.DAY : lang.DAYS}", + ); + }, + ), + ), + ), + // -- Import Youtube History getItemWrapper( key: _BackupAndRestoreKeys.importYT,