From 47cf4e948335feb4b1ec6e579ca0c507069ce461 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:15:04 +0200 Subject: [PATCH] Refactor backup/restore Close #575 --- .../de/mm20/launcher2/LauncherApplication.kt | 2 +- .../launcher2/ui/common/RestoreBackupSheet.kt | 59 --------- .../ui/common/RestoreBackupSheetVM.kt | 19 +-- .../ui/settings/backup/CreateBackupSheet.kt | 117 ++---------------- .../ui/settings/backup/CreateBackupSheetVM.kt | 16 +-- .../de/mm20/launcher2/backup/Backupable.kt | 8 ++ core/i18n/src/main/res/values/strings.xml | 1 + .../launcher2/preferences/ImportExport.kt | 14 +++ .../de/mm20/launcher2/preferences/Module.kt | 3 + .../customattrs/CustomAttributesRepository.kt | 10 +- .../mm20/launcher2/data/customattrs/Module.kt | 5 +- .../de/mm20/launcher2/searchactions/Module.kt | 5 +- .../searchactions/SearchActionRepository.kt | 18 +-- .../de/mm20/launcher2/searchable/Module.kt | 2 + .../searchable/SavableSearchableRepository.kt | 10 +- .../java/de/mm20/launcher2/themes/Module.kt | 3 + .../mm20/launcher2/themes/ThemeRepository.kt | 8 +- .../java/de/mm20/launcher2/widgets/Module.kt | 5 +- .../launcher2/widgets/WidgetRepository.kt | 10 +- services/backup/build.gradle.kts | 8 +- .../mm20/launcher2/backup/BackupComponent.kt | 16 --- .../de/mm20/launcher2/backup/BackupManager.kt | 70 ++--------- .../mm20/launcher2/backup/BackupMetadata.kt | 11 +- .../java/de/mm20/launcher2/backup/Module.kt | 2 +- 24 files changed, 93 insertions(+), 329 deletions(-) create mode 100644 core/base/src/main/java/de/mm20/launcher2/backup/Backupable.kt delete mode 100644 services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt diff --git a/app/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt b/app/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt index c4f9d149..ca6ad0c1 100644 --- a/app/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt +++ b/app/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt @@ -62,7 +62,6 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory { appShortcutsModule, baseModule, calculatorModule, - backupModule, badgesModule, calendarModule, contactsModule, @@ -87,6 +86,7 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory { wikipediaModule, servicesTagsModule, widgetsServiceModule, + backupModule, ) ) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt index a5854223..d86909fb 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt @@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.common import android.net.Uri import android.text.format.DateUtils -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -13,14 +12,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.backup.BackupCompatibility -import de.mm20.launcher2.backup.BackupComponent import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.LargeMessage @@ -38,7 +35,6 @@ fun RestoreBackupSheet( } val state by viewModel.state - val selectedComponents by viewModel.selectedComponents val compatibility by viewModel.compatibility BottomSheetDialog( @@ -52,7 +48,6 @@ fun RestoreBackupSheet( if (state == RestoreBackupState.Ready && compatibility != BackupCompatibility.Incompatible) { Button( - enabled = selectedComponents.isNotEmpty(), onClick = { viewModel.restore() }) { Text(stringResource(R.string.preference_restore)) } @@ -138,60 +133,6 @@ fun RestoreBackupSheet( ) ) } - Text( - stringResource(R.string.restore_select_components), - style = MaterialTheme.typography.titleSmall, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) - ) - val components by viewModel.availableComponents - for (component in components) { - Row( - modifier = Modifier - .clickable { - viewModel.toggleComponent( - component - ) - } - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = when (component) { - BackupComponent.Favorites -> Icons.Rounded.Star - BackupComponent.Settings -> Icons.Rounded.Settings - BackupComponent.SearchActions -> Icons.Rounded.ArrowOutward - BackupComponent.Widgets -> Icons.Rounded.Widgets - BackupComponent.Customizations -> Icons.Rounded.Edit - BackupComponent.Themes -> Icons.Rounded.Palette - }, - contentDescription = null - ) - Text( - text = stringResource( - when (component) { - BackupComponent.Favorites -> R.string.backup_component_favorites - BackupComponent.Settings -> R.string.backup_component_settings - BackupComponent.SearchActions -> R.string.backup_component_searchactions - BackupComponent.Widgets -> R.string.backup_component_widgets - BackupComponent.Customizations -> R.string.backup_component_customizations - BackupComponent.Themes -> R.string.backup_component_themes - } - ), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp) - ) - Checkbox( - checked = selectedComponents.contains( - component - ), - onCheckedChange = { - viewModel.toggleComponent(component) - } - ) - } - } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheetVM.kt index f85b04ba..9c9b6613 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheetVM.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import de.mm20.launcher2.backup.BackupCompatibility -import de.mm20.launcher2.backup.BackupComponent import de.mm20.launcher2.backup.BackupManager import de.mm20.launcher2.backup.BackupMetadata import kotlinx.coroutines.launch @@ -21,9 +20,6 @@ class RestoreBackupSheetVM : ViewModel(), KoinComponent { val state = mutableStateOf(RestoreBackupState.Parsing) val metadata = mutableStateOf(null) val compatibility = mutableStateOf(null) - val selectedComponents = mutableStateOf(setOf()) - - val availableComponents = mutableStateOf(emptyList()) fun setInputUri(uri: Uri) { restoreUri = uri @@ -32,33 +28,20 @@ class RestoreBackupSheetVM : ViewModel(), KoinComponent { val metadata = backupManager.readBackupMeta(uri) if (metadata == null) { state.value = RestoreBackupState.InvalidFile - availableComponents.value = emptyList() } else { state.value = RestoreBackupState.Ready compatibility.value = backupManager.checkCompatibility(metadata) - availableComponents.value = metadata.components.toList().sortedBy { it.ordinal } } - selectedComponents.value = metadata?.components ?: emptySet() this@RestoreBackupSheetVM.metadata.value = metadata } } - fun toggleComponent(component: BackupComponent) { - val components = selectedComponents.value ?: emptySet() - if (components.contains(component)) { - selectedComponents.value = components - component - } else { - selectedComponents.value = components + component - } - } - fun restore() { - val components = selectedComponents.value ?: return val uri = restoreUri ?: return viewModelScope.launch { state.value = RestoreBackupState.Restoring - backupManager.restore(uri, components) + backupManager.restore(uri) state.value = RestoreBackupState.Restored } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt index c950c92b..e5787890 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import de.mm20.launcher2.backup.BackupComponent import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.LargeMessage @@ -33,20 +32,23 @@ fun CreateBackupSheet( val viewModel: CreateBackupSheetVM = viewModel() - LaunchedEffect(null) { - viewModel.reset() - } - - val components by viewModel.selectedComponents - val state by viewModel.state - - val backupLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("application/vnd.de.mm20.launcher2.backup"), onResult = { if (it != null) viewModel.createBackup(it) } ) + LaunchedEffect(null) { + viewModel.reset() + val fileName = "${ + ZonedDateTime.now().format( + DateTimeFormatter.ISO_INSTANT + ).replace(":", "_") + }.kvaesitso" + backupLauncher.launch(fileName) + } + + val state by viewModel.state BottomSheetDialog( onDismissRequest = onDismissRequest, @@ -58,7 +60,6 @@ fun CreateBackupSheet( confirmButton = { if (state == CreateBackupState.Ready) { Button( - enabled = components.isNotEmpty(), onClick = { val fileName = "${ ZonedDateTime.now().format( @@ -87,68 +88,7 @@ fun CreateBackupSheet( ) { when (state) { CreateBackupState.Ready -> { - Column { - Text( - stringResource(R.string.backup_select_components), - style = MaterialTheme.typography.titleSmall, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) - ) - BackupableComponent( - title = stringResource(R.string.backup_component_settings), - icon = Icons.Rounded.Settings, - checked = components.contains(BackupComponent.Settings), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.Settings) - } - ) - BackupableComponent( - title = stringResource(R.string.backup_component_favorites), - icon = Icons.Rounded.Star, - checked = components.contains(BackupComponent.Favorites), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.Favorites) - } - ) - BackupableComponent( - title = stringResource(R.string.backup_component_widgets), - icon = Icons.Rounded.Widgets, - checked = components.contains(BackupComponent.Widgets), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.Widgets) - } - ) - BackupableComponent( - title = stringResource(R.string.backup_component_customizations), - icon = Icons.Rounded.Edit, - checked = components.contains(BackupComponent.Customizations), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.Customizations) - } - ) - BackupableComponent( - title = stringResource(R.string.backup_component_searchactions), - icon = Icons.Rounded.TravelExplore, - checked = components.contains(BackupComponent.SearchActions), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.SearchActions) - } - ) - BackupableComponent( - title = stringResource(R.string.backup_component_themes), - icon = Icons.Rounded.Palette, - checked = components.contains(BackupComponent.Themes), - onCheckedChange = { - viewModel.toggleComponent(BackupComponent.Themes) - } - ) - SmallMessage( - modifier = Modifier - .padding(top = 8.dp) - .fillMaxWidth(), - icon = Icons.Rounded.Warning, - text = stringResource(R.string.backup_not_included) - ) - } + } CreateBackupState.BackingUp -> { Box( @@ -176,36 +116,3 @@ fun CreateBackupSheet( } } -@Composable -fun BackupableComponent( - title: String, - icon: ImageVector, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, -) { - Row( - modifier = Modifier - .clickable { - onCheckedChange(!checked) - } - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null - ) - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp) - ) - Checkbox( - checked = checked, - onCheckedChange = onCheckedChange - ) - } -} - diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheetVM.kt index a17a4317..9be085cf 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheetVM.kt @@ -4,7 +4,6 @@ import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import de.mm20.launcher2.backup.BackupComponent import de.mm20.launcher2.backup.BackupManager import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent @@ -16,30 +15,17 @@ class CreateBackupSheetVM : ViewModel(), KoinComponent { val state = mutableStateOf(CreateBackupState.Ready) - val selectedComponents = mutableStateOf(BackupComponent.values().toSet()) - fun reset() { state.value = CreateBackupState.Ready } fun createBackup(uri: Uri) { - val components = selectedComponents.value ?: return viewModelScope.launch { state.value = CreateBackupState.BackingUp - backupManager.backup(uri, components) + backupManager.backup(uri) state.value = CreateBackupState.BackedUp } } - - fun toggleComponent(component: BackupComponent) { - val components = selectedComponents.value ?: emptySet() - if (components.contains(component)) { - selectedComponents.value = components - component - } else { - selectedComponents.value = components + component - } - } - } enum class CreateBackupState { diff --git a/core/base/src/main/java/de/mm20/launcher2/backup/Backupable.kt b/core/base/src/main/java/de/mm20/launcher2/backup/Backupable.kt new file mode 100644 index 00000000..9837f56e --- /dev/null +++ b/core/base/src/main/java/de/mm20/launcher2/backup/Backupable.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.backup + +import java.io.File + +interface Backupable { + suspend fun backup(toDir: File) + suspend fun restore(fromDir: File) +} \ No newline at end of file diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 87fe54d8..d53bff17 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -701,6 +701,7 @@ Full in %1$s minutes Select what to backup: + The following data will be backed up: Connected accounts and 3rd party app widgets will not be backed up. Favorites & hidden apps Settings diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ImportExport.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ImportExport.kt index 80b17b17..0a5a051a 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ImportExport.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ImportExport.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.preferences import android.content.Context import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -11,6 +12,19 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.firstOrNull import java.io.File +internal class LauncherStoreBackupComponent( + private val context: Context, + private val dataStore: LauncherDataStore +): Backupable { + override suspend fun backup(toDir: File) { + dataStore.export(toDir) + } + + override suspend fun restore(fromDir: File) { + dataStore.import(context, fromDir) + } +} + suspend fun LauncherDataStore.export(toDir: File) { val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) val backupDataStore = DataStoreFactory.create( diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/Module.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/Module.kt index 573bf324..39ae797e 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/Module.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/Module.kt @@ -1,8 +1,11 @@ package de.mm20.launcher2.preferences +import de.mm20.launcher2.backup.Backupable import org.koin.android.ext.koin.androidContext +import org.koin.core.qualifier.named import org.koin.dsl.module val preferencesModule = module { single { androidContext().dataStore } + factory(named()) { LauncherStoreBackupComponent(androidContext(), get()) } } \ No newline at end of file diff --git a/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/CustomAttributesRepository.kt b/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/CustomAttributesRepository.kt index 670344bb..736c2b92 100644 --- a/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/CustomAttributesRepository.kt +++ b/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/CustomAttributesRepository.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.data.customattrs +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.CustomAttributeEntity @@ -18,7 +19,7 @@ import org.json.JSONArray import org.json.JSONException import java.io.File -interface CustomAttributesRepository { +interface CustomAttributesRepository: Backupable { fun search(query: String): Flow> @@ -32,9 +33,6 @@ interface CustomAttributesRepository { fun setTags(searchable: SavableSearchable, tags: List) fun getTags(searchable: SavableSearchable): Flow> - suspend fun export(toDir: File) - suspend fun import(fromDir: File) - fun getAllTags(startsWith: String? = null): Flow> fun getItemsForTag(tag: String): Flow> fun setItemsForTag(tag: String, items: List): Job @@ -188,7 +186,7 @@ internal class CustomAttributesRepositoryImpl( } } - override suspend fun export(toDir: File) = withContext(Dispatchers.IO) { + override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) { val dao = appDatabase.backupDao() var page = 0 do { @@ -212,7 +210,7 @@ internal class CustomAttributesRepositoryImpl( } while (customAttrs.size == 100) } - override suspend fun import(fromDir: File) = withContext(Dispatchers.IO) { + override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) { val dao = appDatabase.backupDao() dao.wipeCustomAttributes() diff --git a/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/Module.kt b/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/Module.kt index 9c73014e..7132135a 100644 --- a/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/Module.kt +++ b/data/customattrs/src/main/java/de/mm20/launcher2/data/customattrs/Module.kt @@ -1,7 +1,10 @@ package de.mm20.launcher2.data.customattrs +import de.mm20.launcher2.backup.Backupable +import org.koin.core.qualifier.named import org.koin.dsl.module val customAttrsModule = module { - single { CustomAttributesRepositoryImpl(get(), get()) } + factory(named()) { CustomAttributesRepositoryImpl(get(), get()) } + factory { CustomAttributesRepositoryImpl(get(), get()) } } \ No newline at end of file diff --git a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/Module.kt b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/Module.kt index 4425ecdf..10ca7925 100644 --- a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/Module.kt +++ b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/Module.kt @@ -1,9 +1,12 @@ package de.mm20.launcher2.searchactions +import de.mm20.launcher2.backup.Backupable import org.koin.android.ext.koin.androidContext +import org.koin.core.qualifier.named import org.koin.dsl.module val searchActionsModule = module { - single { SearchActionRepositoryImpl(androidContext(), get()) } + factory(named()) { SearchActionRepositoryImpl(androidContext(), get()) } + factory { SearchActionRepositoryImpl(androidContext(), get()) } single { SearchActionServiceImpl(androidContext(), get(), TextClassifierImpl()) } } \ No newline at end of file diff --git a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionRepository.kt b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionRepository.kt index f325d21d..16e69904 100644 --- a/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionRepository.kt +++ b/data/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionRepository.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.searchactions import android.content.Context +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.SearchActionEntity @@ -27,25 +28,23 @@ import org.json.JSONException import java.io.File import java.util.UUID -interface SearchActionRepository { +interface SearchActionRepository : Backupable { fun getSearchActionBuilders(): Flow> fun getBuiltinSearchActionBuilders(): List fun saveSearchActionBuilders(builders: List) - - suspend fun export(toDir: File) - suspend fun import(fromDir: File) } internal class SearchActionRepositoryImpl( private val context: Context, private val database: AppDatabase -): SearchActionRepository { +) : SearchActionRepository { private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) override fun getSearchActionBuilders(): Flow> { val dao = database.searchActionDao() - return dao.getSearchActions().map { it.mapNotNull { SearchActionBuilder.from(context, it) } } + return dao.getSearchActions() + .map { it.mapNotNull { SearchActionBuilder.from(context, it) } } } override fun getBuiltinSearchActionBuilders(): List { @@ -73,7 +72,7 @@ internal class SearchActionRepositoryImpl( } } - override suspend fun export(toDir: File) = withContext(Dispatchers.IO) { + override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() var page = 0 var iconCounter = 0 @@ -116,11 +115,12 @@ internal class SearchActionRepositoryImpl( } while (websearches.size == 100) } - override suspend fun import(fromDir: File) = withContext(Dispatchers.IO) { + override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() dao.wipeSearchActions() - val files = fromDir.listFiles { _, name -> name.startsWith("searchactions.") } ?: return@withContext + val files = + fromDir.listFiles { _, name -> name.startsWith("searchactions.") } ?: return@withContext for (file in files) { val searchActions = mutableListOf() diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/Module.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/Module.kt index eaa2f109..1b289ddf 100644 --- a/data/searchable/src/main/java/de/mm20/launcher2/searchable/Module.kt +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/Module.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.searchable +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.data.Tag import org.koin.android.ext.koin.androidContext @@ -7,6 +8,7 @@ import org.koin.core.qualifier.named import org.koin.dsl.module val searchableModule = module { + factory (named()) { SavableSearchableRepositoryImpl(androidContext(), get(), get()) } factory { SavableSearchableRepositoryImpl(androidContext(), get(), get()) } factory(named(Tag.Domain)) { TagDeserializer() } } \ No newline at end of file diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt index 4d4ea69d..6ad6031d 100644 --- a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.searchable import android.content.Context import android.util.Log import androidx.room.withTransaction +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.SavedSearchableEntity @@ -30,7 +31,7 @@ import org.koin.core.error.NoBeanDefFoundException import org.koin.core.qualifier.named import java.io.File -interface SavableSearchableRepository { +interface SavableSearchableRepository: Backupable { fun insert( searchable: SavableSearchable, @@ -107,9 +108,6 @@ interface SavableSearchableRepository { */ suspend fun getByKeys(keys: List): List - suspend fun export(toDir: File) - suspend fun import(fromDir: File) - /** * Remove database entries that are invalid. This includes * - entries that cannot be deserialized anymore @@ -392,7 +390,7 @@ internal class SavableSearchableRepositoryImpl( .mapNotNull { fromDatabaseEntity(it).searchable } } - override suspend fun export(toDir: File) = withContext(Dispatchers.IO) { + override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() var page = 0 do { @@ -420,7 +418,7 @@ internal class SavableSearchableRepositoryImpl( } while (favorites.size == 100) } - override suspend fun import(fromDir: File) = withContext(Dispatchers.IO) { + override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() dao.wipeFavorites() diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt index 4e9d0415..557f956b 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt @@ -1,7 +1,10 @@ package de.mm20.launcher2.themes +import de.mm20.launcher2.backup.Backupable +import org.koin.core.qualifier.named import org.koin.dsl.module val themesModule = module { + factory(named()) { ThemeRepository(get(), get()) } factory { ThemeRepository(get(), get()) } } \ No newline at end of file diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt index 52115af6..0f72ac9d 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.themes import android.content.Context +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import kotlinx.coroutines.CoroutineScope @@ -16,13 +17,12 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString import java.io.File -import java.lang.IllegalArgumentException import java.util.UUID class ThemeRepository( private val context: Context, private val database: AppDatabase, -) { +) : Backupable { private val scope = CoroutineScope(Dispatchers.IO + Job()) fun getThemes(): Flow> { @@ -89,7 +89,7 @@ class ThemeRepository( } } - suspend fun export(toDir: File) = withContext(Dispatchers.IO) { + override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) { val dao = database.themeDao() val themes = dao.getAll().first().map { Theme(it) } val data = ThemeJson.encodeToString(themes) @@ -100,7 +100,7 @@ class ThemeRepository( } } - suspend fun import(fromDir: File) = withContext(Dispatchers.IO) { + override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) { val dao = database.themeDao() dao.deleteAll() diff --git a/data/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt b/data/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt index 54375f7d..5f7d07a1 100644 --- a/data/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt +++ b/data/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt @@ -1,7 +1,10 @@ package de.mm20.launcher2.widgets +import de.mm20.launcher2.backup.Backupable +import org.koin.core.qualifier.named import org.koin.dsl.module val widgetsModule = module { - single { WidgetRepositoryImpl(get()) } + factory(named()) { WidgetRepositoryImpl(get()) } + factory { WidgetRepositoryImpl(get()) } } \ No newline at end of file diff --git a/data/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt b/data/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt index 28a3963a..092dfaa0 100644 --- a/data/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt +++ b/data/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.widgets import androidx.room.withTransaction +import de.mm20.launcher2.backup.Backupable import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.WidgetEntity @@ -13,7 +14,7 @@ import org.json.JSONException import java.io.File import java.util.UUID -interface WidgetRepository { +interface WidgetRepository: Backupable { fun get(parent: UUID? = null, limit: Int = 100, offset: Int = 0): Flow> fun update(widget: Widget) fun create(widget: Widget, position: Int, parentId: UUID? = null) @@ -22,9 +23,6 @@ interface WidgetRepository { fun exists(type: String): Flow fun count(type: String): Flow - - suspend fun export(toDir: File) - suspend fun import(fromDir: File) } internal class WidgetRepositoryImpl( @@ -93,7 +91,7 @@ internal class WidgetRepositoryImpl( } - override suspend fun export(toDir: File) = withContext(Dispatchers.IO) { + override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() var page = 0 do { @@ -119,7 +117,7 @@ internal class WidgetRepositoryImpl( } while (widgets.size == 100) } - override suspend fun import(fromDir: File) = withContext(Dispatchers.IO) { + override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) { val dao = database.backupDao() dao.wipeWidgets() diff --git a/services/backup/build.gradle.kts b/services/backup/build.gradle.kts index bbca8af7..226628b9 100644 --- a/services/backup/build.gradle.kts +++ b/services/backup/build.gradle.kts @@ -39,12 +39,6 @@ dependencies { implementation(libs.koin.android) - implementation(project(":data:searchable")) - implementation(project(":data:widgets")) - implementation(project(":data:search-actions")) - implementation(project(":core:preferences")) + implementation(project(":core:base")) implementation(project(":core:ktx")) - implementation(project(":data:customattrs")) - implementation(project(":data:themes")) - } \ No newline at end of file diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt deleted file mode 100644 index 50ae09f9..00000000 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.mm20.launcher2.backup - -enum class BackupComponent(val value: String) { - Settings("settings"), - Favorites("favorites"), - Widgets("widgets2"), - Customizations("customizations"), - SearchActions("searchactions"), - Themes("themes"); - - companion object { - fun fromValue(value: String): BackupComponent? { - return entries.firstOrNull { it.value == value } - } - } -} \ No newline at end of file diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt index 5b37e969..2dba5a5a 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt @@ -3,14 +3,6 @@ package de.mm20.launcher2.backup import android.content.Context import android.net.Uri import android.os.Build -import de.mm20.launcher2.data.customattrs.CustomAttributesRepository -import de.mm20.launcher2.searchable.SavableSearchableRepository -import de.mm20.launcher2.preferences.LauncherDataStore -import de.mm20.launcher2.preferences.export -import de.mm20.launcher2.preferences.import -import de.mm20.launcher2.searchactions.SearchActionRepository -import de.mm20.launcher2.themes.ThemeRepository -import de.mm20.launcher2.widgets.WidgetRepository import kotlinx.coroutines.* import java.io.File import java.io.InputStream @@ -21,12 +13,7 @@ import java.util.zip.ZipOutputStream class BackupManager( private val context: Context, - private val dataStore: LauncherDataStore, - private val searchableRepository: SavableSearchableRepository, - private val widgetRepository: WidgetRepository, - private val searchActionRepository: SearchActionRepository, - private val customAttrsRepository: CustomAttributesRepository, - private val themesRepository: ThemeRepository, + private val components: List, ) { private val scope = CoroutineScope(Dispatchers.Default + Job()) @@ -35,8 +22,7 @@ class BackupManager( * @return Uri to the created backup archive */ suspend fun backup( - uri: Uri, - include: Set = BackupComponent.entries.toSet() + uri: Uri ) { val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) @@ -45,7 +31,6 @@ class BackupManager( appVersionName = packageInfo.versionName, timestamp = System.currentTimeMillis(), deviceName = Build.MODEL, - components = include, format = BackupFormat, ) @@ -60,28 +45,8 @@ class BackupManager( val metaFile = File(backupDir, "meta") meta.writeToFile(metaFile) - if (include.contains(BackupComponent.Settings)) { - dataStore.export(backupDir) - } - - if (include.contains(BackupComponent.Favorites)) { - searchableRepository.export(backupDir) - } - - if (include.contains(BackupComponent.Widgets)) { - widgetRepository.export(backupDir) - } - - if (include.contains(BackupComponent.SearchActions)) { - searchActionRepository.export(backupDir) - } - - if (include.contains(BackupComponent.Customizations)) { - customAttrsRepository.export(backupDir) - } - - if (include.contains(BackupComponent.Themes)) { - themesRepository.export(backupDir) + for (component in components) { + component.backup(backupDir) } createArchive(backupDir, outputStream) @@ -92,7 +57,6 @@ class BackupManager( suspend fun restore( uri: Uri, - include: Set = BackupComponent.values().toSet() ) { val job = scope.launch { withContext(Dispatchers.IO) { @@ -105,28 +69,8 @@ class BackupManager( extractArchive(inputStream, restoreDir) inputStream.close() - if (include.contains(BackupComponent.Settings)) { - dataStore.import(context, restoreDir) - } - - if (include.contains(BackupComponent.Favorites)) { - searchableRepository.import(restoreDir) - } - - if (include.contains(BackupComponent.Widgets)) { - widgetRepository.import(restoreDir) - } - - if (include.contains(BackupComponent.SearchActions)) { - searchActionRepository.import(restoreDir) - } - - if (include.contains(BackupComponent.Customizations)) { - customAttrsRepository.import(restoreDir) - } - - if (include.contains(BackupComponent.Themes)) { - themesRepository.import(restoreDir) + for (component in components) { + component.restore(restoreDir) } } } @@ -198,7 +142,7 @@ class BackupManager( */ private const val BackupFormatMajor = 1 - private const val BackupFormatMinor = 7 + private const val BackupFormatMinor = 8 internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor" } } diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupMetadata.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupMetadata.kt index 6e4a795f..e3b88d5d 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupMetadata.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupMetadata.kt @@ -17,7 +17,6 @@ data class BackupMetadata( * Backup schema version in format x.y. */ val format: String, - val components: Set, ) { internal suspend fun writeToFile(file: File) { @@ -26,7 +25,7 @@ data class BackupMetadata( "timestamp" to timestamp, "format" to format, "versionName" to appVersionName, - "components" to JSONArray(components.map { it.value }) + "components" to JSONArray() ) withContext(Dispatchers.IO) { file.outputStream().bufferedWriter().use { @@ -46,14 +45,6 @@ data class BackupMetadata( timestamp = json.optLong("timestamp"), format = json.optString("format"), appVersionName = json.optString("versionName"), - components = json.getJSONArray("components").let { - val set = mutableSetOf() - for (i in 0 until it.length()) { - val component = BackupComponent.fromValue(it.getString(i)) - if (component != null) set.add(component) - } - set - } ) } catch (e: JSONException) { return@withContext null diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt index 904f6299..f40da926 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt @@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val backupModule = module { - single { BackupManager(androidContext(), get(), get(), get(), get(), get(), get()) } + single { BackupManager(androidContext(), getAll()) } } \ No newline at end of file