Migrate saved search and feed saved search to SQLDelight

This commit is contained in:
Jobobby04 2022-04-22 19:19:50 -04:00
parent 4a115785eb
commit 26b30adf4a
18 changed files with 469 additions and 558 deletions

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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()

View File

@ -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<BackupSavedSearch> {
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<BackupSavedSearch>) {
val currentSavedSearches = databaseHelper.getSavedSearches()
.executeAsBlocking()
internal suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
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 == "[]" },
)
}
}
}

View File

@ -76,7 +76,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
}
// SY -->
private fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
private suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
backupManager.restoreSavedSearches(backupSavedSearches)
restoreProgress += 1

View File

@ -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<JsonObject>(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<JsonObject>(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)
}
}
}

View File

@ -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

View File

@ -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.
*/

View File

@ -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()
}
}
/**

View File

@ -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<FeedController>() {
@ -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<CatalogueSource> {
@ -93,33 +99,38 @@ open class FeedPresenter(
return list.sortedBy { it.id.toString() !in pinnedSources }
}
fun getSourceSavedSearches(source: CatalogueSource): List<SavedSearch> {
return db.getSavedSearches(source.id).executeAsBlocking()
suspend fun getSourceSavedSearches(source: CatalogueSource): List<SavedSearch> {
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<Pair<FeedSavedSearch, SavedSearch?>> {
val savedSearches = db.getGlobalSavedSearchesFeed().executeAsBlocking()
.associateBy { it.id!! }
return db.getGlobalFeedSavedSearches().executeAsBlocking()
private suspend fun getSourcesToGetFeed(): List<Pair<FeedSavedSearch, SavedSearch?>> {
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()

View File

@ -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() }

View File

@ -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<BrowseSourceController>() {
@ -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<JsonArray>(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<JsonArray>(it) }
?: return@runCatching null,
)
originalFilters
}.getOrNull(),
)
}
}
fun loadSearches(searches: List<SavedSearch> = db.getSavedSearches(source.id).executeAsBlocking()): List<EXHSavedSearch> {
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<JsonArray>(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<SavedSearch>? = null): List<EXHSavedSearch> {
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<JsonArray>(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 <--

View File

@ -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

View File

@ -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<SourceFeedController>() {
@ -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<SavedSearch> {
return db.getSavedSearches(source.id).executeAsBlocking()
suspend fun getSourceSavedSearches(): List<SavedSearch> {
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<SourceFeed> {
val savedSearches = db.getSourceSavedSearchesFeed(source.id).executeAsBlocking()
private suspend fun getSourcesToGetFeed(): List<SourceFeed> {
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<JsonArray>(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<JsonArray>(it) }
?: return@runCatching null,
)
originalFilters
}.getOrNull(),
)
}
}
fun loadSearches(): List<EXHSavedSearch> {
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<JsonArray>(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<EXHSavedSearch> {
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<JsonArray>(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,
)
}
}
}
}

View File

@ -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<JsonObject>(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<JsonObject>(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")

View File

@ -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()})"

View File

@ -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<FeedSavedSearch>) = 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<SavedSearch>) {
db.inTransaction {
deleteSavedSearches(mergedMangaId).executeAsBlocking()
mergedMangases.chunked(100) { chunk ->
insertSavedSearches(chunk).executeAsBlocking()
}
}
}*/
}
interface FeedSavedSearchQueries : DbProvider

View File

@ -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<Long>) = 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<SavedSearch>) = 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<SavedSearch>) {
db.inTransaction {
deleteSavedSearches(mergedMangaId).executeAsBlocking()
mergedMangases.chunked(100) { chunk ->
insertSavedSearches(chunk).executeAsBlocking()
}
}
}*/
}
interface SavedSearchQueries : DbProvider