From a54744abc652cba68d8fa59e38401f2159e9bbec Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 20 Dec 2024 12:30:48 +0200 Subject: [PATCH] Option to clear manga database --- .../koitharu/kotatsu/core/db/dao/MangaDao.kt | 13 +++++++++++++ .../core/parser/MangaDataRepository.kt | 12 +++++++++++- .../kotatsu/core/prefs/AppSettings.kt | 1 + .../kotatsu/history/data/HistoryRepository.kt | 8 ++++---- .../userdata/UserDataSettingsFragment.kt | 7 ++++++- .../userdata/UserDataSettingsViewModel.kt | 19 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/pref_user_data.xml | 6 ++++++ 8 files changed, 61 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt index 57fec66a1..acc0eaa74 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt @@ -58,6 +58,19 @@ abstract class MangaDao { @Delete abstract suspend fun delete(subjects: Collection) + @Query( + """ + DELETE FROM manga WHERE NOT EXISTS(SELECT * FROM history WHERE history.manga_id == manga.manga_id) + AND NOT EXISTS(SELECT * FROM favourites WHERE favourites.manga_id == manga.manga_id) + AND NOT EXISTS(SELECT * FROM bookmarks WHERE bookmarks.manga_id == manga.manga_id) + AND NOT EXISTS(SELECT * FROM suggestions WHERE suggestions.manga_id == manga.manga_id) + AND NOT EXISTS(SELECT * FROM scrobblings WHERE scrobblings.manga_id == manga.manga_id) + AND NOT EXISTS(SELECT * FROM local_index WHERE local_index.manga_id == manga.manga_id) + AND manga.manga_id NOT IN (:idsToKeep) + """, + ) + abstract suspend fun cleanup(idsToKeep: Set) + @Transaction open suspend fun upsert(manga: MangaEntity, tags: Iterable? = null) { upsert(manga) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt index adb2c544f..521b2536f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt @@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.nav.MangaIntent +import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.util.ext.toFileOrNull import org.koitharu.kotatsu.parsers.model.Manga @@ -28,6 +29,7 @@ import javax.inject.Provider class MangaDataRepository @Inject constructor( private val db: MangaDatabase, private val resolverProvider: Provider, + private val appShortcutManagerProvider: Provider, ) { suspend fun saveReaderMode(manga: Manga, mode: ReaderMode) { @@ -119,7 +121,7 @@ class MangaDataRepository @Inject constructor( } } - suspend fun gcChapters() { + suspend fun gcChaptersCache() { db.getChaptersDao().gc() } @@ -136,6 +138,14 @@ class MangaDataRepository @Inject constructor( } } + suspend fun cleanupDatabase() { + db.withTransaction { + gcChaptersCache() + val idsFromShortcuts = appShortcutManagerProvider.get().getMangaShortcuts() + db.getMangaDao().cleanup(idsFromShortcuts) + } + } + private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? { return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale) { ReaderColorFilter(cfBrightness, cfContrast, cfInvert, cfGrayscale) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 683a4118d..8025694fc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -740,6 +740,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_HANDLE_LINKS = "handle_links" const val KEY_BACKUP_TG_OPEN = "backup_periodic_tg_open" const val KEY_BACKUP_TG_TEST = "backup_periodic_tg_test" + const val KEY_CLEAR_MANGA_DATA = "manga_data_clear" // old keys are for migration only private const val KEY_IMAGES_PROXY_OLD = "images_proxy" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt index 38a7a4bf3..13aaf137b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt @@ -158,17 +158,17 @@ class HistoryRepository @Inject constructor( suspend fun delete(manga: Manga) = db.withTransaction { db.getHistoryDao().delete(manga.id) - mangaRepository.gcChapters() + mangaRepository.gcChaptersCache() } suspend fun deleteAfter(minDate: Long) = db.withTransaction { db.getHistoryDao().deleteAfter(minDate) - mangaRepository.gcChapters() + mangaRepository.gcChaptersCache() } suspend fun deleteNotFavorite() = db.withTransaction { db.getHistoryDao().deleteNotFavorite() - mangaRepository.gcChapters() + mangaRepository.gcChaptersCache() } suspend fun delete(ids: Collection): ReversibleHandle { @@ -176,7 +176,7 @@ class HistoryRepository @Inject constructor( for (id in ids) { db.getHistoryDao().delete(id) } - mangaRepository.gcChapters() + mangaRepository.gcChaptersCache() } return ReversibleHandle { recover(ids) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt index b3188c3a4..6cb013de8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt @@ -107,7 +107,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac viewModel.loadingKeys.observe(viewLifecycleOwner) { keys -> loadingPrefs.addAll(keys) loadingPrefs.forEach { prefKey -> - findPreference(prefKey)!!.isEnabled = prefKey !in keys + findPreference(prefKey)?.isEnabled = prefKey !in keys } } viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this)) @@ -153,6 +153,11 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac true } + AppSettings.KEY_CLEAR_MANGA_DATA -> { + viewModel.clearMangaData() + true + } + AppSettings.KEY_UPDATES_FEED_CLEAR -> { viewModel.clearUpdatesFeed() true diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsViewModel.kt index ed87944c0..ad46ef202 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsViewModel.kt @@ -4,7 +4,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -13,6 +12,7 @@ import kotlinx.coroutines.runInterruptible import okhttp3.Cache import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar +import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.ui.BaseViewModel @@ -27,6 +27,7 @@ import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository import java.util.EnumMap import javax.inject.Inject +import javax.inject.Provider @HiltViewModel class UserDataSettingsViewModel @Inject constructor( @@ -37,6 +38,7 @@ class UserDataSettingsViewModel @Inject constructor( private val cookieJar: MutableCookieJar, private val settings: AppSettings, private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase, + private val mangaDataRepositoryProvider: Provider, ) : BaseViewModel() { val onActionDone = MutableEventFlow() @@ -139,6 +141,21 @@ class UserDataSettingsViewModel @Inject constructor( } } + fun clearMangaData() { + launchJob(Dispatchers.Default) { + try { + loadingKeys.update { it + AppSettings.KEY_CLEAR_MANGA_DATA } + trackingRepository.gc() + val repository = mangaDataRepositoryProvider.get() + repository.cleanupLocalManga() + repository.cleanupDatabase() + onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null)) + } finally { + loadingKeys.update { it - AppSettings.KEY_CLEAR_MANGA_DATA } + } + } + } + fun cleanupChapters() { launchJob(Dispatchers.Default) { try { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4d67dd5d..add6cdec8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -787,4 +787,6 @@ Test connection Enter the chat ID where backups should be sent Press to open chat with Kotatsu Backup Bot + Clear database + Delete information about manga that is not used diff --git a/app/src/main/res/xml/pref_user_data.xml b/app/src/main/res/xml/pref_user_data.xml index 38eacc270..b00bc6f1d 100644 --- a/app/src/main/res/xml/pref_user_data.xml +++ b/app/src/main/res/xml/pref_user_data.xml @@ -89,6 +89,12 @@ android:summary="@string/loading_" android:title="@string/clear_network_cache" /> + +