diff --git a/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt b/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt new file mode 100644 index 000000000..430699d07 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt @@ -0,0 +1,13 @@ +package eu.kanade.data.exh + +import exh.savedsearches.models.FeedSavedSearch + +val feedSavedSearchMapper: (Long, Long, Long?, Boolean) -> FeedSavedSearch = + { id, source, savedSearch, global -> + FeedSavedSearch( + id = id, + source = source, + savedSearch = savedSearch, + global = global + ) + } diff --git a/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt b/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt new file mode 100644 index 000000000..a31d4c787 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt @@ -0,0 +1,14 @@ +package eu.kanade.data.exh + +import exh.savedsearches.models.SavedSearch + +val savedSearchMapper: (Long, Long, String, String?, String?) -> SavedSearch = + { id, source, name, query, filtersJson -> + SavedSearch( + id = id, + source = source, + name = name, + query = query, + filtersJson = filtersJson + ) + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt index 6d03f2d42..66a12f4ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri +import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga @@ -20,6 +21,7 @@ import uy.kohesive.injekt.injectLazy abstract class AbstractBackupManager(protected val context: Context) { internal val databaseHelper: DatabaseHelper by injectLazy() + internal val databaseHandler: DatabaseHandler by injectLazy() internal val sourceManager: SourceManager by injectLazy() internal val trackManager: TrackManager by injectLazy() protected val preferences: PreferencesHelper by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 28b111a21..4820c17d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup.full import android.content.Context import android.net.Uri import com.hippo.unifile.UniFile +import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY @@ -39,11 +40,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.logcat import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.insertFlatMetadataAsync -import exh.savedsearches.models.SavedSearch import exh.source.MERGED_SOURCE_ID import exh.source.getMainSource import exh.util.executeOnIO import exh.util.nullIfBlank +import kotlinx.coroutines.runBlocking import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer @@ -167,13 +168,15 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @return list of [BackupSavedSearch] to be backed up */ private fun backupSavedSearches(): List { - return databaseHelper.getSavedSearches().executeAsBlocking().map { - BackupSavedSearch( - it.name, - it.query.orEmpty(), - it.filtersJson ?: "[]", - it.source, - ) + return runBlocking { + databaseHandler.awaitList { saved_searchQueries.selectAll(savedSearchMapper) }.map { + BackupSavedSearch( + it.name, + it.query.orEmpty(), + it.filtersJson ?: "[]", + it.source, + ) + } } } // SY <-- @@ -431,25 +434,24 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } // SY --> - internal fun restoreSavedSearches(backupSavedSearches: List) { - val currentSavedSearches = databaseHelper.getSavedSearches() - .executeAsBlocking() + internal suspend fun restoreSavedSearches(backupSavedSearches: List) { + val currentSavedSearches = databaseHandler.awaitList { + saved_searchQueries.selectAll(savedSearchMapper) + } - val newSavedSearches = backupSavedSearches.filter { backupSavedSearch -> - currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } - }.map { - SavedSearch( - id = null, - it.source, - it.name, - it.query.nullIfBlank(), - filtersJson = it.filterList.nullIfBlank() - ?.takeUnless { it == "[]" }, - ) - }.ifEmpty { null } - - if (newSavedSearches != null) { - databaseHelper.insertSavedSearches(newSavedSearches) + databaseHandler.await(true) { + backupSavedSearches.filter { backupSavedSearch -> + currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } + }.forEach { + saved_searchQueries.insertSavedSearch( + _id = null, + source = it.source, + name = it.name, + query = it.query.nullIfBlank(), + filters_json = it.filterList.nullIfBlank() + ?.takeUnless { it == "[]" }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt index 8c7267ed5..06791f37a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt @@ -76,7 +76,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa } // SY --> - private fun restoreSavedSearches(backupSavedSearches: List) { + private suspend fun restoreSavedSearches(backupSavedSearches: List) { backupManager.restoreSavedSearches(backupSavedSearches) restoreProgress += 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt index c291f134b..d86844fab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup.legacy import android.content.Context import android.net.Uri +import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION @@ -289,28 +290,37 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } // SY --> - internal fun restoreSavedSearches(jsonSavedSearches: String) { + internal suspend fun restoreSavedSearches(jsonSavedSearches: String) { val backupSavedSearches = jsonSavedSearches.split("***").toSet() - val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking() + val currentSavedSearches = databaseHandler.awaitList { + saved_searchQueries.selectAll(savedSearchMapper) + } - val newSavedSearches = backupSavedSearches.mapNotNull { - runCatching { - val content = parser.decodeFromString(it.substringAfter(':')) - SavedSearch( - id = null, - source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, - content["name"]!!.jsonPrimitive.content, - content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), - Json.encodeToString(content["filters"]!!.jsonArray), + databaseHandler.await(true) { + backupSavedSearches.mapNotNull { + runCatching { + val content = parser.decodeFromString(it.substringAfter(':')) + SavedSearch( + id = null, + source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, + content["name"]!!.jsonPrimitive.content, + content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), + Json.encodeToString(content["filters"]!!.jsonArray), + ) + }.getOrNull() + }.filter { backupSavedSearch -> + currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } + }.forEach { + saved_searchQueries.insertSavedSearch( + _id = null, + source = it.source, + name = it.name, + query = it.query.nullIfBlank(), + filters_json = it.filtersJson.nullIfBlank() + ?.takeUnless { it == "[]" }, ) - }.getOrNull() - }.filter { backupSavedSearch -> - currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } - }.ifEmpty { null } - - if (newSavedSearches != null) { - databaseHelper.insertSavedSearches(newSavedSearches) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt index 2f8f5d0f3..9e39605df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt @@ -73,7 +73,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract } // SY --> - private fun restoreSavedSearches(savedSearches: String) { + private suspend fun restoreSavedSearches(savedSearches: String) { backupManager.restoreSavedSearches(savedSearches) restoreProgress += 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 8327e0c2e..15e3c0e5b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -1,8 +1,6 @@ package eu.kanade.tachiyomi.data.database.queries import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver -import exh.savedsearches.tables.FeedSavedSearchTable -import exh.savedsearches.tables.SavedSearchTable import exh.source.MERGED_SOURCE_ID import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter @@ -76,32 +74,6 @@ fun getReadMangaNotInLibraryQuery() = ) """ -/** - * Query to get the global feed saved searches - */ -fun getGlobalFeedSavedSearchQuery() = - """ - SELECT ${SavedSearchTable.TABLE}.* - FROM ( - SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 1 - ) AS M - JOIN ${SavedSearchTable.TABLE} - ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} -""" - -/** - * Query to get the source feed saved searches - */ -fun getSourceFeedSavedSearchQuery() = - """ - SELECT ${SavedSearchTable.TABLE}.* - FROM ( - SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 0 AND ${FeedSavedSearchTable.COL_SOURCE} = ? - ) AS M - JOIN ${SavedSearchTable.TABLE} - ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} -""" - /** * Query to get the manga from the library, with their categories, read and unread count. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt index 5ebc38bfb..6a750ede4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt @@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.toast import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch @@ -66,41 +67,45 @@ open class FeedController : } private fun addFeed() { - if (presenter.hasTooManyFeeds()) { - activity?.toast(R.string.too_many_in_feed) - return - } - val items = presenter.getEnabledSources() - val itemsStrings = items.map { it.toString() } - var selectedIndex = 0 + viewScope.launchUI { + if (presenter.hasTooManyFeeds()) { + activity?.toast(R.string.too_many_in_feed) + return@launchUI + } + val items = presenter.getEnabledSources() + val itemsStrings = items.map { it.toString() } + var selectedIndex = 0 - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> - selectedIndex = which - } - .setPositiveButton(android.R.string.ok) { _, _ -> - addFeedSearch(items[selectedIndex]) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton(android.R.string.ok) { _, _ -> + addFeedSearch(items[selectedIndex]) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } } private fun addFeedSearch(source: CatalogueSource) { - val items = presenter.getSourceSavedSearches(source) - val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name } - var selectedIndex = 0 + viewScope.launchUI { + val items = presenter.getSourceSavedSearches(source) + val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name } + var selectedIndex = 0 - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> - selectedIndex = which - } - .setPositiveButton(android.R.string.ok) { _, _ -> - presenter.createFeed(source, items.getOrNull(selectedIndex - 1)) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton(android.R.string.ok) { _, _ -> + presenter.createFeed(source, items.getOrNull(selectedIndex - 1)) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt index 21c6b932f..2fb86e947 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt @@ -1,6 +1,9 @@ package eu.kanade.tachiyomi.ui.browse.feed import android.os.Bundle +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.exh.feedSavedSearchMapper +import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -15,9 +18,12 @@ import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.runAsObservable +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import logcat.LogPriority @@ -35,11 +41,12 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer * Function calls should be done from here. UI calls should be done from the controller. * * @param sourceManager manages the different sources. - * @param db manages the database calls. + * @param database manages the database calls. * @param preferences manages the preference calls. */ open class FeedPresenter( val sourceManager: SourceManager = Injekt.get(), + val database: DatabaseHandler = Injekt.get(), val db: DatabaseHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), ) : BasePresenter() { @@ -62,14 +69,11 @@ open class FeedPresenter( override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - db.getGlobalFeedSavedSearches() - .asRxObservable() - .observeOn(AndroidSchedulers.mainThread()) - .doOnEach { + database.subscribeToList { feed_saved_searchQueries.selectAllGlobal() } + .onEach { getFeed() } - .subscribe() - .let(::add) + .launchIn(presenterScope) } override fun onDestroy() { @@ -78,8 +82,10 @@ open class FeedPresenter( super.onDestroy() } - fun hasTooManyFeeds(): Boolean { - return db.getGlobalFeedSavedSearches().executeAsBlocking().size > 10 + suspend fun hasTooManyFeeds(): Boolean { + return withIOContext { + database.awaitList { feed_saved_searchQueries.selectAllGlobal() }.size > 10 + } } fun getEnabledSources(): List { @@ -93,33 +99,38 @@ open class FeedPresenter( return list.sortedBy { it.id.toString() !in pinnedSources } } - fun getSourceSavedSearches(source: CatalogueSource): List { - return db.getSavedSearches(source.id).executeAsBlocking() + suspend fun getSourceSavedSearches(source: CatalogueSource): List { + return withIOContext { + database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } + } } fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) { launchIO { - db.insertFeedSavedSearch( - FeedSavedSearch( - id = null, + database.await { + feed_saved_searchQueries.insertFeedSavedSearch( + _id = null, source = source.id, - savedSearch = savedSearch?.id, + saved_search = savedSearch?.id, global = true, - ), - ).executeAsBlocking() + ) + } } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - db.deleteFeedSavedSearch(feed).executeAsBlocking() + database.await { + feed_saved_searchQueries.deleteById(feed.id ?: return@await) + } } } - private fun getSourcesToGetFeed(): List> { - val savedSearches = db.getGlobalSavedSearchesFeed().executeAsBlocking() - .associateBy { it.id!! } - return db.getGlobalFeedSavedSearches().executeAsBlocking() + private suspend fun getSourcesToGetFeed(): List> { + val savedSearches = database.awaitList { + feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper) + }.associateBy { it.id } + return database.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) } .map { it to savedSearches[it.savedSearch] } } @@ -138,7 +149,7 @@ open class FeedPresenter( /** * Initiates get manga per feed. */ - fun getFeed() { + suspend fun getFeed() { // Create image fetch subscription initializeFetchImageSubscription() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt index 9ebf73302..aff2553df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt @@ -44,6 +44,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.preference.asImmediateFlow import eu.kanade.tachiyomi.util.system.connectivityManager import eu.kanade.tachiyomi.util.system.openInBrowser @@ -201,7 +202,7 @@ open class BrowseSourceController(bundle: Bundle) : // SY --> this, presenter.source, - presenter.loadSearches(), + emptyList(), // SY <-- onFilterClicked = { showProgressBar() @@ -216,54 +217,58 @@ open class BrowseSourceController(bundle: Bundle) : }, // EXH --> onSaveClicked = { - filterSheet?.context?.let { - val names = presenter.loadSearches().map { it.name } - var searchName = "" - MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search) - .setTextInput(hint = it.getString(R.string.save_search_hint)) { input -> - searchName = input - } - .setPositiveButton(R.string.action_save) { _, _ -> - if (searchName.isNotBlank() && searchName !in names) { - presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters) - } else { - it.toast(R.string.save_search_invalid_name) - } - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - }, - onSavedSearchClicked = cb@{ idOfSearch -> - val search = presenter.loadSearch(idOfSearch) - - if (search == null) { + viewScope.launchUI { filterSheet?.context?.let { + val names = presenter.loadSearches().map { it.name } + var searchName = "" MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search_failed_to_load) - .setMessage(R.string.save_search_failed_to_load_message) + .setTitle(R.string.save_search) + .setTextInput(hint = it.getString(R.string.save_search_hint)) { input -> + searchName = input + } + .setPositiveButton(R.string.action_save) { _, _ -> + if (searchName.isNotBlank() && searchName !in names) { + presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters) + } else { + it.toast(R.string.save_search_invalid_name) + } + } + .setNegativeButton(R.string.action_cancel, null) .show() } - return@cb } - - if (search.filterList == null) { - activity?.toast(R.string.save_search_invalid) - return@cb - } - - presenter.sourceFilters = FilterList(search.filterList) - filterSheet?.setFilters(presenter.filterItems) - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - - showProgressBar() - adapter?.clear() - filterSheet?.dismiss() - presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) - activity?.invalidateOptionsMenu() }, - onSavedSearchDeleteClicked = cb@{ idToDelete, name -> + onSavedSearchClicked = { idOfSearch -> + viewScope.launchUI { + val search = presenter.loadSearch(idOfSearch) + + if (search == null) { + filterSheet?.context?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.save_search_failed_to_load) + .setMessage(R.string.save_search_failed_to_load_message) + .show() + } + return@launchUI + } + + if (search.filterList == null) { + activity?.toast(R.string.save_search_invalid) + return@launchUI + } + + presenter.sourceFilters = FilterList(search.filterList) + filterSheet?.setFilters(presenter.filterItems) + val allDefault = presenter.sourceFilters == presenter.source.getFilterList() + + showProgressBar() + adapter?.clear() + filterSheet?.dismiss() + presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) + activity?.invalidateOptionsMenu() + } + }, + onSavedSearchDeleteClicked = { idToDelete, name -> filterSheet?.context?.let { MaterialAlertDialogBuilder(it) .setTitle(R.string.save_search_delete) @@ -277,6 +282,9 @@ open class BrowseSourceController(bundle: Bundle) : }, // EXH <-- ) + launchUI { + filterSheet?.setSavedSearches(presenter.loadSearches()) + } filterSheet?.setFilters(presenter.filterItems) filterSheet?.setOnShowListener { actionFab?.hide() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index b0759389e..1cd6ad975 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category @@ -37,6 +39,7 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.system.logcat @@ -50,8 +53,10 @@ import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -78,6 +83,7 @@ open class BrowseSourcePresenter( // SY <-- private val sourceManager: SourceManager = Injekt.get(), private val db: DatabaseHelper = Injekt.get(), + private val database: DatabaseHandler = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), ) : BasePresenter() { @@ -140,7 +146,11 @@ open class BrowseSourcePresenter( val jsonFilters = filters if (savedSearchFilters != null) { runCatching { - val savedSearch = db.getSavedSearch(savedSearchFilters).executeAsBlocking() ?: return@runCatching + val savedSearch = runBlocking { + database.awaitOneOrNull { + saved_searchQueries.selectById(savedSearchFilters, savedSearchMapper) + } + } ?: return@runCatching query = savedSearch.query.orEmpty() val filtersJson = savedSearch.filtersJson ?: return@runCatching @@ -156,18 +166,14 @@ open class BrowseSourcePresenter( } } - db.getSavedSearches(source.id) - .asRxObservable() - .map { - loadSearches(it) + database.subscribeToList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } + .map { loadSearches(it) } + .onEach { + withUIContext { + view?.setSavedSearches(it) + } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeLatestCache( - { controller, savedSearches -> - controller.setSavedSearches(savedSearches) - }, - ) + .launchIn(presenterScope) // SY <-- if (savedState != null) { @@ -485,83 +491,90 @@ open class BrowseSourcePresenter( fun saveSearch(name: String, query: String, filterList: FilterList) { launchIO { kotlin.runCatching { - val savedSearch = SavedSearch( - id = null, - source = source.id, - name = name.trim(), - query = query.nullIfBlank(), - filtersJson = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }, - ) - - db.insertSavedSearch(savedSearch).executeAsBlocking() + database.await { + saved_searchQueries.insertSavedSearch( + _id = null, + source = source.id, + name = name.trim(), + query = query.nullIfBlank(), + filters_json = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }, + ) + } } } } fun deleteSearch(searchId: Long) { launchIO { - db.deleteSavedSearch(searchId).executeAsBlocking() + database.await { saved_searchQueries.deleteById(searchId) } } } - fun loadSearch(searchId: Long): EXHSavedSearch? { - val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null - return EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), - ) + suspend fun loadSearch(searchId: Long): EXHSavedSearch? { + return withIOContext { + val search = database.awaitOneOrNull { + saved_searchQueries.selectById(searchId, savedSearchMapper) + } ?: return@withIOContext null + EXHSavedSearch( + id = search.id!!, + name = search.name, + query = search.query.orEmpty(), + filterList = runCatching { + val originalFilters = source.getFilterList() + filterSerializer.deserialize( + filters = originalFilters, + json = search.filtersJson + ?.let { Json.decodeFromString(it) } + ?: return@runCatching null, + ) + originalFilters + }.getOrNull(), + ) + } } - fun loadSearches(searches: List = db.getSavedSearches(source.id).executeAsBlocking()): List { - return searches.map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - xLogE("Failed to load saved search!", e) - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) + suspend fun loadSearches(searches: List? = null): List { + return withIOContext { + (searches ?: (database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) })) + .map { + val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + val filters = try { + Json.decodeFromString(filtersJson) + } catch (e: Exception) { + xLogE("Failed to load saved search!", e) + null + } ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - } + try { + val originalFilters = source.getFilterList() + filterSerializer.deserialize(originalFilters, filters) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = originalFilters, + ) + } catch (t: RuntimeException) { + // Load failed + xLogE("Failed to load saved search!", t) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + } + } } } // EXH <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt index 491a9e59b..973c2c4c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt @@ -24,6 +24,8 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.lang.launchUI +import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.system.toast import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch @@ -184,7 +186,7 @@ open class SourceFeedController : // SY --> this, presenter.source, - presenter.loadSearches(), + emptyList(), // SY <-- onFilterClicked = { val allDefault = presenter.sourceFilters == presenter.source.getFilterList() @@ -202,51 +204,60 @@ open class SourceFeedController : }, onResetClicked = {}, onSaveClicked = {}, - onSavedSearchClicked = cb@{ idOfSearch -> - val search = presenter.loadSearch(idOfSearch) + onSavedSearchClicked = { idOfSearch -> + viewScope.launchUI { + val search = presenter.loadSearch(idOfSearch) - if (search == null) { - filterSheet?.context?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search_failed_to_load) - .setMessage(R.string.save_search_failed_to_load_message) + if (search == null) { + filterSheet?.context?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.save_search_failed_to_load) + .setMessage(R.string.save_search_failed_to_load_message) + .show() + } + return@launchUI + } + + if (search.filterList == null) { + activity?.toast(R.string.save_search_invalid) + return@launchUI + } + + presenter.sourceFilters = FilterList(search.filterList) + filterSheet?.setFilters(presenter.filterItems) + val allDefault = presenter.sourceFilters == presenter.source.getFilterList() + filterSheet?.dismiss() + + if (!allDefault) { + onBrowseClick( + search = presenter.query.nullIfBlank(), + savedSearch = search.id, + ) + } + } + }, + onSavedSearchDeleteClicked = { idOfSearch, name -> + viewScope.launchUI { + if (presenter.hasTooManyFeeds()) { + activity?.toast(R.string.too_many_in_feed) + return@launchUI + } + withUIContext { + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setMessage(activity!!.getString(R.string.feed_add, name)) + .setPositiveButton(R.string.action_add) { _, _ -> + presenter.createFeed(idOfSearch) + } + .setNegativeButton(android.R.string.cancel, null) .show() } - return@cb } - - if (search.filterList == null) { - activity?.toast(R.string.save_search_invalid) - return@cb - } - - presenter.sourceFilters = FilterList(search.filterList) - filterSheet?.setFilters(presenter.filterItems) - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - filterSheet?.dismiss() - - if (!allDefault) { - onBrowseClick( - search = presenter.query.nullIfBlank(), - savedSearch = search.id, - ) - } - }, - onSavedSearchDeleteClicked = cb@{ idOfSearch, name -> - if (presenter.hasTooManyFeeds()) { - activity?.toast(R.string.too_many_in_feed) - return@cb - } - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setMessage(activity!!.getString(R.string.feed_add, name)) - .setPositiveButton(R.string.action_add) { _, _ -> - presenter.createFeed(idOfSearch) - } - .setNegativeButton(android.R.string.cancel, null) - .show() }, ) + launchUI { + filterSheet?.setSavedSearches(presenter.loadSearches()) + } filterSheet?.setFilters(presenter.filterItems) // TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt index 7729e6945..f4492b8f9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt @@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.source.feed import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.exh.feedSavedSearchMapper +import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -16,11 +19,15 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.runAsObservable +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat import exh.log.xLogE import exh.savedsearches.EXHSavedSearch import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray @@ -46,11 +53,12 @@ sealed class SourceFeed { * Function calls should be done from here. UI calls should be done from the controller. * * @param source the source. - * @param db manages the database calls. + * @param database manages the database calls. * @param preferences manages the preference calls. */ open class SourceFeedPresenter( val source: CatalogueSource, + val database: DatabaseHandler = Injekt.get(), val db: DatabaseHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), ) : BasePresenter() { @@ -90,14 +98,11 @@ open class SourceFeedPresenter( sourceFilters = source.getFilterList() - db.getSourceFeedSavedSearches(source.id) - .asRxObservable() - .observeOn(AndroidSchedulers.mainThread()) - .doOnEach { + database.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } + .onEach { getFeed() } - .subscribe() - .let(::add) + .launchIn(presenterScope) } override fun onDestroy() { @@ -106,35 +111,39 @@ open class SourceFeedPresenter( super.onDestroy() } - fun hasTooManyFeeds(): Boolean { - return db.getSourceFeedSavedSearches(source.id).executeAsBlocking().size > 10 + suspend fun hasTooManyFeeds(): Boolean { + return withIOContext { + database.awaitList { + feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id) + }.size > 10 + } } - fun getSourceSavedSearches(): List { - return db.getSavedSearches(source.id).executeAsBlocking() + suspend fun getSourceSavedSearches(): List { + return database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } } fun createFeed(savedSearchId: Long) { launchIO { - db.insertFeedSavedSearch( - FeedSavedSearch( - id = null, + database.await { + feed_saved_searchQueries.insertFeedSavedSearch( + _id = null, source = source.id, - savedSearch = savedSearchId, - global = false, - ), - ).executeAsBlocking() + saved_search = savedSearchId, + global = false + ) + } } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - db.deleteFeedSavedSearch(feed).executeAsBlocking() + database.await { feed_saved_searchQueries.deleteById(feed.id ?: return@await) } } } - private fun getSourcesToGetFeed(): List { - val savedSearches = db.getSourceSavedSearchesFeed(source.id).executeAsBlocking() + private suspend fun getSourcesToGetFeed(): List { + val savedSearches = database.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } .associateBy { it.id!! } return listOfNotNull( @@ -142,7 +151,7 @@ open class SourceFeedPresenter( SourceFeed.Latest } else null, SourceFeed.Browse, - ) + db.getSourceFeedSavedSearches(source.id).executeAsBlocking() + ) + database.awaitList { feed_saved_searchQueries.selectBySource(source.id, feedSavedSearchMapper) } .map { SourceFeed.SourceSavedSearch(it, savedSearches[it.savedSearch]!!) } } @@ -159,7 +168,7 @@ open class SourceFeedPresenter( /** * Initiates get manga per feed. */ - fun getFeed() { + suspend fun getFeed() { // Create image fetch subscription initializeFetchImageSubscription() @@ -300,62 +309,69 @@ open class SourceFeedPresenter( return localManga } - fun loadSearch(searchId: Long): EXHSavedSearch? { - val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null - return EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), - ) + suspend fun loadSearch(searchId: Long): EXHSavedSearch? { + return withIOContext { + val search = database.awaitOneOrNull { + saved_searchQueries.selectById(searchId, savedSearchMapper) + } ?: return@withIOContext null + EXHSavedSearch( + id = search.id!!, + name = search.name, + query = search.query.orEmpty(), + filterList = runCatching { + val originalFilters = source.getFilterList() + filterSerializer.deserialize( + filters = originalFilters, + json = search.filtersJson + ?.let { Json.decodeFromString(it) } + ?: return@runCatching null, + ) + originalFilters + }.getOrNull(), + ) + } } - fun loadSearches(): List { - return db.getSavedSearches(source.id).executeAsBlocking().map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( + suspend fun loadSearches(): List { + return withIOContext { + database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }.map { + val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( id = it.id!!, name = it.name, query = it.query.orEmpty(), filterList = null, ) + val filters = try { + Json.decodeFromString(filtersJson) + } catch (e: Exception) { + if (e is CancellationException) throw e + null + } ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + + try { + val originalFilters = source.getFilterList() + filterSerializer.deserialize(originalFilters, filters) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = originalFilters, + ) + } catch (t: RuntimeException) { + // Load failed + xLogE("Failed to load saved search!", t) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + } } } } diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index 8af8f23b0..5cab95914 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -6,6 +6,7 @@ import androidx.preference.PreferenceManager import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.Query import com.pushtorefresh.storio.sqlite.queries.RawQuery +import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -40,8 +41,6 @@ import exh.eh.EHentaiUpdateWorker import exh.log.xLogE import exh.log.xLogW import exh.merged.sql.models.MergedMangaReference -import exh.savedsearches.models.FeedSavedSearch -import exh.savedsearches.models.SavedSearch import exh.source.BlacklistedSources import exh.source.EH_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID @@ -51,6 +50,7 @@ import exh.source.PERV_EDEN_IT_SOURCE_ID import exh.source.TSUMINO_SOURCE_ID import exh.util.nullIfBlank import exh.util.under +import kotlinx.coroutines.runBlocking import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -69,6 +69,7 @@ import java.net.URISyntaxException object EXHMigrations { private val db: DatabaseHelper by injectLazy() + private val database: DatabaseHandler by injectLazy() private val sourceManager: SourceManager by injectLazy() /** @@ -404,31 +405,31 @@ object EXHMigrations { BackupCreatorJob.setupTask(context) } if (oldVersion under 31) { - val savedSearches = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull { - kotlin.runCatching { - val content = Json.decodeFromString(it.substringAfter(':')) - SavedSearch( - id = null, - source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, - content["name"]!!.jsonPrimitive.content, - content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), - Json.encodeToString(content["filters"]!!.jsonArray), - ) - }.getOrNull() - }?.ifEmpty { null } - if (savedSearches != null) { - db.insertSavedSearches(savedSearches).executeAsBlocking() - } - val feed = prefs.getStringSet("latest_tab_sources", emptySet())?.map { - FeedSavedSearch( - id = null, - source = it.toLong(), - savedSearch = null, - global = true, - ) - }?.ifEmpty { null } - if (feed != null) { - db.insertFeedSavedSearches(feed).executeAsBlocking() + runBlocking { + database.await(true) { + prefs.getStringSet("eh_saved_searches", emptySet())?.forEach { + kotlin.runCatching { + val content = Json.decodeFromString(it.substringAfter(':')) + saved_searchQueries.insertSavedSearch( + _id = null, + source = it.substringBefore(':').toLongOrNull() ?: return@forEach, + name = content["name"]!!.jsonPrimitive.content, + query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), + filters_json = Json.encodeToString(content["filters"]!!.jsonArray) + ) + } + } + } + database.await(true) { + prefs.getStringSet("latest_tab_sources", emptySet())?.forEach { + feed_saved_searchQueries.insertFeedSavedSearch( + _id = null, + source = it.toLong(), + saved_search = null, + global = true, + ) + } + } } prefs.edit(commit = true) { remove("eh_saved_searches") diff --git a/app/src/main/java/exh/debug/DebugFunctions.kt b/app/src/main/java/exh/debug/DebugFunctions.kt index 0892efec1..5aea14182 100644 --- a/app/src/main/java/exh/debug/DebugFunctions.kt +++ b/app/src/main/java/exh/debug/DebugFunctions.kt @@ -3,6 +3,7 @@ package exh.debug import android.app.Application import androidx.work.WorkManager import com.pushtorefresh.storio.sqlite.queries.RawQuery +import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.tables.MangaTable @@ -34,6 +35,7 @@ import java.util.UUID object DebugFunctions { val app: Application by injectLazy() val db: DatabaseHelper by injectLazy() + val database: DatabaseHandler by injectLazy() val prefs: PreferencesHelper by injectLazy() val sourceManager: SourceManager by injectLazy() @@ -164,7 +166,7 @@ object DebugFunctions { it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null } - fun clearSavedSearches() = db.deleteAllSavedSearches().executeAsBlocking() + fun clearSavedSearches() = runBlocking { database.await { saved_searchQueries.deleteAll() } } fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") { "${it.id}: ${it.name} (${it.lang.uppercase()})" diff --git a/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt b/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt index fb67ddfce..fd1db5461 100644 --- a/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt +++ b/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt @@ -1,86 +1,5 @@ package exh.savedsearches.queries -import com.pushtorefresh.storio.sqlite.queries.DeleteQuery -import com.pushtorefresh.storio.sqlite.queries.Query -import com.pushtorefresh.storio.sqlite.queries.RawQuery import eu.kanade.tachiyomi.data.database.DbProvider -import eu.kanade.tachiyomi.data.database.queries.getGlobalFeedSavedSearchQuery -import eu.kanade.tachiyomi.data.database.queries.getSourceFeedSavedSearchQuery -import exh.savedsearches.models.FeedSavedSearch -import exh.savedsearches.models.SavedSearch -import exh.savedsearches.tables.FeedSavedSearchTable -interface FeedSavedSearchQueries : DbProvider { - fun getGlobalFeedSavedSearches() = db.get() - .listOfObjects(FeedSavedSearch::class.java) - .withQuery( - Query.builder() - .table(FeedSavedSearchTable.TABLE) - .where("${FeedSavedSearchTable.COL_GLOBAL} = 1") - .orderBy(FeedSavedSearchTable.COL_ID) - .build(), - ) - .prepare() - - fun getSourceFeedSavedSearches(sourceId: Long) = db.get() - .listOfObjects(FeedSavedSearch::class.java) - .withQuery( - Query.builder() - .table(FeedSavedSearchTable.TABLE) - .where("${FeedSavedSearchTable.COL_SOURCE} = ? AND ${FeedSavedSearchTable.COL_GLOBAL} = 0") - .whereArgs(sourceId) - .orderBy(FeedSavedSearchTable.COL_ID) - .build(), - ) - .prepare() - - fun insertFeedSavedSearch(savedSearch: FeedSavedSearch) = db.put().`object`(savedSearch).prepare() - - fun insertFeedSavedSearches(savedSearches: List) = db.put().objects(savedSearches).prepare() - - fun deleteFeedSavedSearch(savedSearch: FeedSavedSearch) = db.delete().`object`(savedSearch).prepare() - - fun deleteFeedSavedSearch(id: Long) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(FeedSavedSearchTable.TABLE) - .where("${FeedSavedSearchTable.COL_ID} = ?") - .whereArgs(id) - .build(), - ).prepare() - - fun deleteAllFeedSavedSearches() = db.delete().byQuery( - DeleteQuery.builder() - .table(FeedSavedSearchTable.TABLE) - .build(), - ) - .prepare() - - fun getGlobalSavedSearchesFeed() = db.get() - .listOfObjects(SavedSearch::class.java) - .withQuery( - RawQuery.builder() - .query(getGlobalFeedSavedSearchQuery()) - .build(), - ) - .prepare() - - fun getSourceSavedSearchesFeed(sourceId: Long) = db.get() - .listOfObjects(SavedSearch::class.java) - .withQuery( - RawQuery.builder() - .query(getSourceFeedSavedSearchQuery()) - .args(sourceId) - .build(), - ) - .prepare() - - /*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List) { - db.inTransaction { - deleteSavedSearches(mergedMangaId).executeAsBlocking() - mergedMangases.chunked(100) { chunk -> - insertSavedSearches(chunk).executeAsBlocking() - } - } - }*/ -} +interface FeedSavedSearchQueries : DbProvider diff --git a/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt b/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt index 558c80c69..34d9f616e 100644 --- a/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt +++ b/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt @@ -1,93 +1,5 @@ package exh.savedsearches.queries -import com.pushtorefresh.storio.sqlite.queries.DeleteQuery -import com.pushtorefresh.storio.sqlite.queries.Query import eu.kanade.tachiyomi.data.database.DbProvider -import exh.savedsearches.models.SavedSearch -import exh.savedsearches.tables.SavedSearchTable -interface SavedSearchQueries : DbProvider { - fun getSavedSearches(source: Long) = db.get() - .listOfObjects(SavedSearch::class.java) - .withQuery( - Query.builder() - .table(SavedSearchTable.TABLE) - .where("${SavedSearchTable.COL_SOURCE} = ?") - .whereArgs(source) - .build(), - ) - .prepare() - - fun deleteSavedSearches(source: Long) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(SavedSearchTable.TABLE) - .where("${SavedSearchTable.COL_SOURCE} = ?") - .whereArgs(source) - .build(), - ) - .prepare() - - fun getSavedSearches() = db.get() - .listOfObjects(SavedSearch::class.java) - .withQuery( - Query.builder() - .table(SavedSearchTable.TABLE) - .orderBy(SavedSearchTable.COL_ID) - .build(), - ) - .prepare() - - fun getSavedSearch(id: Long) = db.get() - .`object`(SavedSearch::class.java) - .withQuery( - Query.builder() - .table(SavedSearchTable.TABLE) - .where("${SavedSearchTable.COL_ID} = ?") - .whereArgs(id) - .build(), - ) - .prepare() - - fun getSavedSearches(ids: List) = db.get() - .listOfObjects(SavedSearch::class.java) - .withQuery( - Query.builder() - .table(SavedSearchTable.TABLE) - .where("${SavedSearchTable.COL_ID} IN (?)") - .whereArgs(ids.joinToString()) - .build(), - ) - .prepare() - - fun insertSavedSearch(savedSearch: SavedSearch) = db.put().`object`(savedSearch).prepare() - - fun insertSavedSearches(savedSearches: List) = db.put().objects(savedSearches).prepare() - - fun deleteSavedSearch(savedSearch: SavedSearch) = db.delete().`object`(savedSearch).prepare() - - fun deleteSavedSearch(id: Long) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(SavedSearchTable.TABLE) - .where("${SavedSearchTable.COL_ID} = ?") - .whereArgs(id) - .build(), - ).prepare() - - fun deleteAllSavedSearches() = db.delete().byQuery( - DeleteQuery.builder() - .table(SavedSearchTable.TABLE) - .build(), - ) - .prepare() - - /*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List) { - db.inTransaction { - deleteSavedSearches(mergedMangaId).executeAsBlocking() - mergedMangases.chunked(100) { chunk -> - insertSavedSearches(chunk).executeAsBlocking() - } - } - }*/ -} +interface SavedSearchQueries : DbProvider