From beaf5cc0d5b2e06ce28d3dfa147808514bc1ec88 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 26 Oct 2025 17:35:29 +0200 Subject: [PATCH] Remove SavedFilterBackup class --- .../kotatsu/backups/data/BackupRepository.kt | 462 +++++++++--------- .../backups/data/model/SavedFilterBackup.kt | 34 -- .../filter/data/SavedFiltersRepository.kt | 12 +- 3 files changed, 238 insertions(+), 270 deletions(-) delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/SavedFilterBackup.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/BackupRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/BackupRepository.kt index fd99b82e8..ced72c0fe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/BackupRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/BackupRepository.kt @@ -26,7 +26,6 @@ import org.koitharu.kotatsu.backups.data.model.CategoryBackup import org.koitharu.kotatsu.backups.data.model.FavouriteBackup import org.koitharu.kotatsu.backups.data.model.HistoryBackup import org.koitharu.kotatsu.backups.data.model.MangaBackup -import org.koitharu.kotatsu.backups.data.model.SavedFilterBackup import org.koitharu.kotatsu.backups.data.model.ScrobblingBackup import org.koitharu.kotatsu.backups.data.model.SourceBackup import org.koitharu.kotatsu.backups.data.model.StatisticBackup @@ -36,6 +35,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.CompositeResult import org.koitharu.kotatsu.core.util.progress.Progress import org.koitharu.kotatsu.explore.data.MangaSourcesRepository +import org.koitharu.kotatsu.filter.data.PersistableFilter import org.koitharu.kotatsu.filter.data.SavedFiltersRepository import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.reader.data.TapGridSettings @@ -48,271 +48,267 @@ import javax.inject.Inject @Reusable class BackupRepository @Inject constructor( - private val database: MangaDatabase, - private val settings: AppSettings, - private val tapGridSettings: TapGridSettings, - private val mangaSourcesRepository: MangaSourcesRepository, - private val savedFiltersRepository: SavedFiltersRepository, + private val database: MangaDatabase, + private val settings: AppSettings, + private val tapGridSettings: TapGridSettings, + private val mangaSourcesRepository: MangaSourcesRepository, + private val savedFiltersRepository: SavedFiltersRepository, ) { - private val json = Json { - allowSpecialFloatingPointValues = true - coerceInputValues = true - encodeDefaults = true - ignoreUnknownKeys = true - useAlternativeNames = false - } + private val json = Json { + allowSpecialFloatingPointValues = true + coerceInputValues = true + encodeDefaults = true + ignoreUnknownKeys = true + useAlternativeNames = false + } - suspend fun createBackup( - output: ZipOutputStream, - progress: FlowCollector?, - ) { - progress?.emit(Progress.INDETERMINATE) - var commonProgress = Progress(0, BackupSection.entries.size) - for (section in BackupSection.entries) { - when (section) { - BackupSection.INDEX -> output.writeJsonArray( - section = BackupSection.INDEX, - data = flowOf(BackupIndex()), - serializer = serializer(), - ) + suspend fun createBackup( + output: ZipOutputStream, + progress: FlowCollector?, + ) { + progress?.emit(Progress.INDETERMINATE) + var commonProgress = Progress(0, BackupSection.entries.size) + for (section in BackupSection.entries) { + when (section) { + BackupSection.INDEX -> output.writeJsonArray( + section = BackupSection.INDEX, + data = flowOf(BackupIndex()), + serializer = serializer(), + ) - BackupSection.HISTORY -> output.writeJsonArray( - section = BackupSection.HISTORY, - data = database.getHistoryDao().dump().map { HistoryBackup(it) }, - serializer = serializer(), - ) + BackupSection.HISTORY -> output.writeJsonArray( + section = BackupSection.HISTORY, + data = database.getHistoryDao().dump().map { HistoryBackup(it) }, + serializer = serializer(), + ) - BackupSection.CATEGORIES -> output.writeJsonArray( - section = BackupSection.CATEGORIES, - data = database.getFavouriteCategoriesDao().findAll().asFlow().map { CategoryBackup(it) }, - serializer = serializer(), - ) + BackupSection.CATEGORIES -> output.writeJsonArray( + section = BackupSection.CATEGORIES, + data = database.getFavouriteCategoriesDao().findAll().asFlow().map { CategoryBackup(it) }, + serializer = serializer(), + ) - BackupSection.FAVOURITES -> output.writeJsonArray( - section = BackupSection.FAVOURITES, - data = database.getFavouritesDao().dump().map { FavouriteBackup(it) }, - serializer = serializer(), - ) + BackupSection.FAVOURITES -> output.writeJsonArray( + section = BackupSection.FAVOURITES, + data = database.getFavouritesDao().dump().map { FavouriteBackup(it) }, + serializer = serializer(), + ) - BackupSection.SETTINGS -> output.writeString( - section = BackupSection.SETTINGS, - data = dumpSettings(), - ) + BackupSection.SETTINGS -> output.writeString( + section = BackupSection.SETTINGS, + data = dumpSettings(), + ) - BackupSection.SETTINGS_READER_GRID -> output.writeString( - section = BackupSection.SETTINGS_READER_GRID, - data = dumpReaderGridSettings(), - ) + BackupSection.SETTINGS_READER_GRID -> output.writeString( + section = BackupSection.SETTINGS_READER_GRID, + data = dumpReaderGridSettings(), + ) - BackupSection.BOOKMARKS -> output.writeJsonArray( - section = BackupSection.BOOKMARKS, - data = database.getBookmarksDao().dump().map { BookmarkBackup(it.first, it.second) }, - serializer = serializer(), - ) + BackupSection.BOOKMARKS -> output.writeJsonArray( + section = BackupSection.BOOKMARKS, + data = database.getBookmarksDao().dump().map { BookmarkBackup(it.first, it.second) }, + serializer = serializer(), + ) - BackupSection.SOURCES -> output.writeJsonArray( - section = BackupSection.SOURCES, - data = database.getSourcesDao().dumpEnabled().map { SourceBackup(it) }, - serializer = serializer(), - ) + BackupSection.SOURCES -> output.writeJsonArray( + section = BackupSection.SOURCES, + data = database.getSourcesDao().dumpEnabled().map { SourceBackup(it) }, + serializer = serializer(), + ) - BackupSection.SCROBBLING -> output.writeJsonArray( - section = BackupSection.SCROBBLING, - data = database.getScrobblingDao().dumpEnabled().map { ScrobblingBackup(it) }, - serializer = serializer(), - ) + BackupSection.SCROBBLING -> output.writeJsonArray( + section = BackupSection.SCROBBLING, + data = database.getScrobblingDao().dumpEnabled().map { ScrobblingBackup(it) }, + serializer = serializer(), + ) - BackupSection.STATS -> output.writeJsonArray( - section = BackupSection.STATS, - data = database.getStatsDao().dumpEnabled().map { StatisticBackup(it) }, - serializer = serializer(), - ) + BackupSection.STATS -> output.writeJsonArray( + section = BackupSection.STATS, + data = database.getStatsDao().dumpEnabled().map { StatisticBackup(it) }, + serializer = serializer(), + ) - BackupSection.SAVED_FILTERS -> { - val sources = mangaSourcesRepository.getEnabledSources() - val filters = sources.flatMap { source -> - savedFiltersRepository.getAll(source) - } - output.writeJsonArray( - section = BackupSection.SAVED_FILTERS, - data = filters.asFlow().map { SavedFilterBackup(it) }, - serializer = serializer(), - ) - } - } - progress?.emit(commonProgress) - commonProgress++ - } - progress?.emit(commonProgress) - } + BackupSection.SAVED_FILTERS -> { + val sources = mangaSourcesRepository.getEnabledSources() + val filters = sources.flatMap { source -> + savedFiltersRepository.getAll(source) + } + output.writeJsonArray( + section = BackupSection.SAVED_FILTERS, + data = filters.asFlow(), + serializer = serializer(), + ) + } + } + progress?.emit(commonProgress) + commonProgress++ + } + progress?.emit(commonProgress) + } - suspend fun restoreBackup( - input: ZipInputStream, - sections: Set, - progress: FlowCollector?, - ): CompositeResult { - progress?.emit(Progress.INDETERMINATE) - var commonProgress = Progress(0, sections.size) - var entry = input.nextEntry - var result = CompositeResult.EMPTY - while (entry != null) { - val section = BackupSection.of(entry) - if (section in sections) { - result = result + when (section) { - BackupSection.INDEX -> CompositeResult.EMPTY // useless in our case - BackupSection.HISTORY -> input.readJsonArray(serializer()).restoreToDb { - upsertManga(it.manga) - getHistoryDao().upsert(it.toEntity()) - } + suspend fun restoreBackup( + input: ZipInputStream, + sections: Set, + progress: FlowCollector?, + ): CompositeResult { + progress?.emit(Progress.INDETERMINATE) + var commonProgress = Progress(0, sections.size) + var entry = input.nextEntry + var result = CompositeResult.EMPTY + while (entry != null) { + val section = BackupSection.of(entry) + if (section in sections) { + result += when (section) { + BackupSection.INDEX -> CompositeResult.EMPTY // useless in our case + BackupSection.HISTORY -> input.readJsonArray(serializer()).restoreToDb { + upsertManga(it.manga) + getHistoryDao().upsert(it.toEntity()) + } - BackupSection.CATEGORIES -> input.readJsonArray(serializer()).restoreToDb { - getFavouriteCategoriesDao().upsert(it.toEntity()) - } + BackupSection.CATEGORIES -> input.readJsonArray(serializer()).restoreToDb { + getFavouriteCategoriesDao().upsert(it.toEntity()) + } - BackupSection.FAVOURITES -> input.readJsonArray(serializer()).restoreToDb { - upsertManga(it.manga) - getFavouritesDao().upsert(it.toEntity()) - } + BackupSection.FAVOURITES -> input.readJsonArray(serializer()).restoreToDb { + upsertManga(it.manga) + getFavouritesDao().upsert(it.toEntity()) + } - BackupSection.SETTINGS -> input.readMap().let { - settings.upsertAll(it) - CompositeResult.success() - } + BackupSection.SETTINGS -> input.readMap().let { + settings.upsertAll(it) + CompositeResult.success() + } - BackupSection.SETTINGS_READER_GRID -> input.readMap().let { - tapGridSettings.upsertAll(it) - CompositeResult.success() - } + BackupSection.SETTINGS_READER_GRID -> input.readMap().let { + tapGridSettings.upsertAll(it) + CompositeResult.success() + } - BackupSection.BOOKMARKS -> input.readJsonArray(serializer()).restoreToDb { - upsertManga(it.manga) - getBookmarksDao().upsert(it.bookmarks.map { b -> b.toEntity() }) - } + BackupSection.BOOKMARKS -> input.readJsonArray(serializer()).restoreToDb { + upsertManga(it.manga) + getBookmarksDao().upsert(it.bookmarks.map { b -> b.toEntity() }) + } - BackupSection.SOURCES -> input.readJsonArray(serializer()).restoreToDb { - getSourcesDao().upsert(it.toEntity()) - } + BackupSection.SOURCES -> input.readJsonArray(serializer()).restoreToDb { + getSourcesDao().upsert(it.toEntity()) + } - BackupSection.SCROBBLING -> input.readJsonArray(serializer()).restoreToDb { - getScrobblingDao().upsert(it.toEntity()) - } + BackupSection.SCROBBLING -> input.readJsonArray(serializer()).restoreToDb { + getScrobblingDao().upsert(it.toEntity()) + } - BackupSection.STATS -> input.readJsonArray(serializer()).restoreToDb { - getStatsDao().upsert(it.toEntity()) - } + BackupSection.STATS -> input.readJsonArray(serializer()).restoreToDb { + getStatsDao().upsert(it.toEntity()) + } - BackupSection.SAVED_FILTERS -> input.readJsonArray(serializer()) - .restoreWithoutTransaction { - savedFiltersRepository.save( - source = it.source, - name = it.name, - filter = it.filter, - ) - } + BackupSection.SAVED_FILTERS -> input.readJsonArray(serializer()) + .restoreWithoutTransaction { + savedFiltersRepository.save(it) + } - null -> CompositeResult.EMPTY // skip unknown entries - } - progress?.emit(commonProgress) - commonProgress++ - } - input.closeEntry() - entry = input.nextEntry - } - progress?.emit(commonProgress) - return result - } + null -> CompositeResult.EMPTY // skip unknown entries + } + progress?.emit(commonProgress) + commonProgress++ + } + input.closeEntry() + entry = input.nextEntry + } + progress?.emit(commonProgress) + return result + } - private suspend fun ZipOutputStream.writeJsonArray( - section: BackupSection, - data: Flow, - serializer: SerializationStrategy, - ) { - data.onStart { - putNextEntry(ZipEntry(section.entryName)) - write("[") - }.onCompletion { error -> - if (error == null) { - write("]") - } - closeEntry() - flush() - }.collectIndexed { index, value -> - if (index > 0) { - write(",") - } - json.encodeToStream(serializer, value, this) - } - } + private suspend fun ZipOutputStream.writeJsonArray( + section: BackupSection, + data: Flow, + serializer: SerializationStrategy, + ) { + data.onStart { + putNextEntry(ZipEntry(section.entryName)) + write("[") + }.onCompletion { error -> + if (error == null) { + write("]") + } + closeEntry() + flush() + }.collectIndexed { index, value -> + if (index > 0) { + write(",") + } + json.encodeToStream(serializer, value, this) + } + } - private fun InputStream.readJsonArray( - serializer: DeserializationStrategy, - ): Sequence = json.decodeToSequence(this, serializer, DecodeSequenceMode.ARRAY_WRAPPED) + private fun InputStream.readJsonArray( + serializer: DeserializationStrategy, + ): Sequence = json.decodeToSequence(this, serializer, DecodeSequenceMode.ARRAY_WRAPPED) - private fun InputStream.readMap(): Map { - val jo = JSONArray(readString()).getJSONObject(0) - val map = ArrayMap(jo.length()) - val keys = jo.keys() - while (keys.hasNext()) { - val key = keys.next() - map[key] = jo.get(key) - } - return map - } + private fun InputStream.readMap(): Map { + val jo = JSONArray(readString()).getJSONObject(0) + val map = ArrayMap(jo.length()) + val keys = jo.keys() + while (keys.hasNext()) { + val key = keys.next() + map[key] = jo.get(key) + } + return map + } - private fun ZipOutputStream.writeString( - section: BackupSection, - data: String, - ) { - putNextEntry(ZipEntry(section.entryName)) - try { - write("[") - write(data) - write("]") - } finally { - closeEntry() - flush() - } - } + private fun ZipOutputStream.writeString( + section: BackupSection, + data: String, + ) { + putNextEntry(ZipEntry(section.entryName)) + try { + write("[") + write(data) + write("]") + } finally { + closeEntry() + flush() + } + } - private fun OutputStream.write(str: String) = write(str.toByteArray()) + private fun OutputStream.write(str: String) = write(str.toByteArray()) - private fun InputStream.readString(): String = readBytes().decodeToString() + private fun InputStream.readString(): String = readBytes().decodeToString() - private fun dumpSettings(): String { - val map = settings.getAllValues().toMutableMap() - map.remove(AppSettings.KEY_APP_PASSWORD) - map.remove(AppSettings.KEY_PROXY_PASSWORD) - map.remove(AppSettings.KEY_PROXY_LOGIN) - map.remove(AppSettings.KEY_INCOGNITO_MODE) - return JSONObject(map).toString() - } + private fun dumpSettings(): String { + val map = settings.getAllValues().toMutableMap() + map.remove(AppSettings.KEY_APP_PASSWORD) + map.remove(AppSettings.KEY_PROXY_PASSWORD) + map.remove(AppSettings.KEY_PROXY_LOGIN) + map.remove(AppSettings.KEY_INCOGNITO_MODE) + return JSONObject(map).toString() + } - private fun dumpReaderGridSettings(): String { - return JSONObject(tapGridSettings.getAllValues()).toString() - } + private fun dumpReaderGridSettings(): String { + return JSONObject(tapGridSettings.getAllValues()).toString() + } - private suspend fun MangaDatabase.upsertManga(manga: MangaBackup) { - val tags = manga.tags.map { it.toEntity() } - getTagsDao().upsert(tags) - getMangaDao().upsert(manga.toEntity(), tags) - } + private suspend fun MangaDatabase.upsertManga(manga: MangaBackup) { + val tags = manga.tags.map { it.toEntity() } + getTagsDao().upsert(tags) + getMangaDao().upsert(manga.toEntity(), tags) + } - private suspend inline fun Sequence.restoreToDb(crossinline block: suspend MangaDatabase.(T) -> Unit): CompositeResult { - return fold(CompositeResult.EMPTY) { result, item -> - result + runCatchingCancellable { - database.withTransaction { - database.block(item) - } - } - } - } + private suspend inline fun Sequence.restoreToDb(crossinline block: suspend MangaDatabase.(T) -> Unit): CompositeResult { + return fold(CompositeResult.EMPTY) { result, item -> + result + runCatchingCancellable { + database.withTransaction { + database.block(item) + } + } + } + } - private suspend inline fun Sequence.restoreWithoutTransaction(crossinline block: suspend (T) -> Unit): CompositeResult { - return fold(CompositeResult.EMPTY) { result, item -> - result + runCatchingCancellable { - block(item) - } - } - } + private suspend inline fun Sequence.restoreWithoutTransaction(crossinline block: suspend (T) -> Unit): CompositeResult { + return fold(CompositeResult.EMPTY) { result, item -> + result + runCatchingCancellable { + block(item) + } + } + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/SavedFilterBackup.kt b/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/SavedFilterBackup.kt deleted file mode 100644 index 6099e2de4..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/SavedFilterBackup.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.koitharu.kotatsu.backups.data.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import org.koitharu.kotatsu.core.model.MangaSourceSerializer -import org.koitharu.kotatsu.filter.data.MangaListFilterSerializer -import org.koitharu.kotatsu.filter.data.PersistableFilter -import org.koitharu.kotatsu.parsers.model.MangaListFilter -import org.koitharu.kotatsu.parsers.model.MangaSource - -@Serializable -data class SavedFilterBackup( - @SerialName("name") - val name: String, - @Serializable(with = MangaSourceSerializer::class) - @SerialName("source") - val source: MangaSource, - @Serializable(with = MangaListFilterSerializer::class) - @SerialName("filter") - val filter: MangaListFilter, -) { - - constructor(persistableFilter: PersistableFilter) : this( - name = persistableFilter.name, - source = persistableFilter.source, - filter = persistableFilter.filter, - ) - - fun toPersistableFilter() = PersistableFilter( - name = name, - source = source, - filter = filter, - ) -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/data/SavedFiltersRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/data/SavedFiltersRepository.kt index 87971f7e0..4d4885765 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/data/SavedFiltersRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/data/SavedFiltersRepository.kt @@ -57,10 +57,16 @@ class SavedFiltersRepository @Inject constructor( source = source, filter = filter, ) - persist(source, persistableFilter) + persist(persistableFilter) persistableFilter } + suspend fun save( + filter: PersistableFilter, + ) = withContext(Dispatchers.Default) { + persist(filter) + } + suspend fun rename(source: MangaSource, id: Int, newName: String) = withContext(Dispatchers.Default) { val filter = load(source, id) ?: return@withContext val newFilter = filter.copy(name = newName) @@ -79,8 +85,8 @@ class SavedFiltersRepository @Inject constructor( } } - private fun persist(source: MangaSource, persistableFilter: PersistableFilter) { - val prefs = getPrefs(source) + private fun persist(persistableFilter: PersistableFilter) { + val prefs = getPrefs(persistableFilter.source) val json = Json.encodeToString(persistableFilter) prefs.edit(commit = true) { putString(key(persistableFilter.id), json)