Refactor backup/restore

Close #575
This commit is contained in:
MM20
2023-10-28 17:15:04 +02:00
parent c9d9b3a4e9
commit 47cf4e9483
24 changed files with 93 additions and 329 deletions
@@ -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<ImmutableList<SavableSearchable>>
@@ -32,9 +33,6 @@ interface CustomAttributesRepository {
fun setTags(searchable: SavableSearchable, tags: List<String>)
fun getTags(searchable: SavableSearchable): Flow<List<String>>
suspend fun export(toDir: File)
suspend fun import(fromDir: File)
fun getAllTags(startsWith: String? = null): Flow<List<String>>
fun getItemsForTag(tag: String): Flow<List<SavableSearchable>>
fun setItemsForTag(tag: String, items: List<SavableSearchable>): 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()
@@ -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<CustomAttributesRepository> { CustomAttributesRepositoryImpl(get(), get()) }
factory<Backupable>(named<CustomAttributesRepository>()) { CustomAttributesRepositoryImpl(get(), get()) }
factory<CustomAttributesRepository> { CustomAttributesRepositoryImpl(get(), get()) }
}
@@ -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<SearchActionRepository> { SearchActionRepositoryImpl(androidContext(), get()) }
factory<Backupable>(named<SearchActionRepository>()) { SearchActionRepositoryImpl(androidContext(), get()) }
factory<SearchActionRepository> { SearchActionRepositoryImpl(androidContext(), get()) }
single<SearchActionService> { SearchActionServiceImpl(androidContext(), get(), TextClassifierImpl()) }
}
@@ -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<List<SearchActionBuilder>>
fun getBuiltinSearchActionBuilders(): List<SearchActionBuilder>
fun saveSearchActionBuilders(builders: List<SearchActionBuilder>)
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<List<SearchActionBuilder>> {
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<SearchActionBuilder> {
@@ -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<SearchActionEntity>()
@@ -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 <Backupable>(named<SavableSearchableRepository>()) { SavableSearchableRepositoryImpl(androidContext(), get(), get()) }
factory <SavableSearchableRepository> { SavableSearchableRepositoryImpl(androidContext(), get(), get()) }
factory<SearchableDeserializer>(named(Tag.Domain)) { TagDeserializer() }
}
@@ -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<String>): List<SavableSearchable>
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()
@@ -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<Backupable>(named<ThemeRepository>()) { ThemeRepository(get(), get()) }
factory { ThemeRepository(get(), get()) }
}
@@ -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<List<Theme>> {
@@ -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()
@@ -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<WidgetRepository> { WidgetRepositoryImpl(get()) }
factory<Backupable>(named<WidgetRepository>()) { WidgetRepositoryImpl(get()) }
factory<WidgetRepository> { WidgetRepositoryImpl(get()) }
}
@@ -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<List<Widget>>
fun update(widget: Widget)
fun create(widget: Widget, position: Int, parentId: UUID? = null)
@@ -22,9 +23,6 @@ interface WidgetRepository {
fun exists(type: String): Flow<Boolean>
fun count(type: String): Flow<Int>
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()