Migrate saved searches to the db

This commit is contained in:
Jobobby04 2022-03-27 14:11:37 -04:00
parent 1ebcfc53d4
commit 5d330c4f75
20 changed files with 465 additions and 212 deletions

View File

@ -25,7 +25,7 @@ android {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
minSdk = AndroidConfig.minSdk minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk targetSdk = AndroidConfig.targetSdk
versionCode = 30 versionCode = 31
versionName = "1.8.1" versionName = "1.8.1"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View File

@ -38,13 +38,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.models.SavedSearch
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.executeOnIO import exh.util.executeOnIO
import kotlinx.serialization.decodeFromString import exh.util.nullIfBlank
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority import logcat.LogPriority
import okio.buffer import okio.buffer
@ -164,14 +162,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @return list of [BackupSavedSearch] to be backed up * @return list of [BackupSavedSearch] to be backed up
*/ */
private fun backupSavedSearches(): List<BackupSavedSearch> { private fun backupSavedSearches(): List<BackupSavedSearch> {
return preferences.savedSearches().get().mapNotNull { return databaseHelper.getSavedSearches().executeAsBlocking().map {
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
BackupSavedSearch( BackupSavedSearch(
content.name, it.name,
content.query, it.query.orEmpty(),
content.filters.toString(), it.filtersJson ?: "[]",
sourceId it.source
) )
} }
} }
@ -431,34 +427,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
// SY --> // SY -->
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) { internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
val currentSavedSearches = preferences.savedSearches().get().mapNotNull { val currentSavedSearches = databaseHelper.getSavedSearches()
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null .executeAsBlocking()
val content = try {
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
} catch (e: Exception) {
return@mapNotNull null
}
BackupSavedSearch(
content.name,
content.query,
content.filters.toString(),
sourceId
)
}
val newSavedSearches = backupSavedSearches.filter { backupSavedSearch -> val newSavedSearches = backupSavedSearches.filter { backupSavedSearch ->
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
}.map { }.map {
"${it.source}:" + Json.encodeToString( SavedSearch(
JsonSavedSearch( id = null,
it.source,
it.name, it.name,
it.query, it.query.nullIfBlank(),
Json.decodeFromString(it.filterList) filtersJson = it.filterList.nullIfBlank()
?.takeUnless { it == "[]" }
) )
) }.ifEmpty { null }
}.toSet()
preferences.savedSearches().set(newSavedSearches + preferences.savedSearches().get()) if (newSavedSearches != null) {
databaseHelper.insertSavedSearches(newSavedSearches)
}
} }
/** /**

View File

@ -28,11 +28,16 @@ import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.models.SavedSearch
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.util.nullIfBlank
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual import kotlinx.serialization.modules.contextual
import kotlin.math.max import kotlin.math.max
@ -287,34 +292,26 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
internal fun restoreSavedSearches(jsonSavedSearches: String) { internal fun restoreSavedSearches(jsonSavedSearches: String) {
val backupSavedSearches = jsonSavedSearches.split("***").toSet() val backupSavedSearches = jsonSavedSearches.split("***").toSet()
val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking()
val newSavedSearches = backupSavedSearches.mapNotNull { val newSavedSearches = backupSavedSearches.mapNotNull {
runCatching { runCatching {
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null val content = parser.decodeFromString<JsonObject>(it.substringAfter(':'))
val content = parser.decodeFromString<JsonSavedSearch>(it.substringAfter(':')) SavedSearch(
id to content id = null,
source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null,
content["name"]!!.jsonPrimitive.content,
content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
Json.encodeToString(content["filters"]!!.jsonArray)
)
}.getOrNull() }.getOrNull()
}.toMutableSet() }.filter { backupSavedSearch ->
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
}.ifEmpty { null }
val currentSources = newSavedSearches.map(Pair<Long, *>::first).toSet() if (newSavedSearches != null) {
databaseHelper.insertSavedSearches(newSavedSearches)
newSavedSearches += preferences.savedSearches().get().mapNotNull {
kotlin.runCatching {
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
val content = parser.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
id to content
}.getOrNull()
} }
val otherSerialized = preferences.savedSearches().get().mapNotNull {
val sourceId = it.substringBefore(":").toLongOrNull() ?: return@mapNotNull null
if (sourceId in currentSources) return@mapNotNull null
it
}.toSet()
val newSerialized = newSavedSearches.map { (source, savedSearch) ->
"$source:" + Json.encodeToString(savedSearch)
}.toSet()
preferences.savedSearches().set(otherSerialized + newSerialized)
} }
/** /**

View File

@ -36,13 +36,16 @@ import exh.metadata.sql.models.SearchTitle
import exh.metadata.sql.queries.SearchMetadataQueries import exh.metadata.sql.queries.SearchMetadataQueries
import exh.metadata.sql.queries.SearchTagQueries import exh.metadata.sql.queries.SearchTagQueries
import exh.metadata.sql.queries.SearchTitleQueries import exh.metadata.sql.queries.SearchTitleQueries
import exh.savedsearches.mappers.SavedSearchTypeMapping
import exh.savedsearches.models.SavedSearch
import exh.savedsearches.queries.SavedSearchQueries
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
/** /**
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, FavoriteEntryQueries /* SY <-- */ { MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, FavoriteEntryQueries, SavedSearchQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@ -63,6 +66,7 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping()) .addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping()) .addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
.addTypeMapping(SavedSearch::class.java, SavedSearchTypeMapping())
// SY <-- // SY <--
.build() .build()

View File

@ -13,6 +13,7 @@ import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable
import exh.metadata.sql.tables.SearchTitleTable import exh.metadata.sql.tables.SearchTitleTable
import exh.savedsearches.tables.SavedSearchTable
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
@ -25,7 +26,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = /* SY --> */ 12 /* SY <-- */ const val DATABASE_VERSION = /* SY --> */ 13 /* SY <-- */
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -41,6 +42,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTitleTable.createTableQuery) execSQL(SearchTitleTable.createTableQuery)
execSQL(MergedTable.createTableQuery) execSQL(MergedTable.createTableQuery)
execSQL(FavoriteEntryTable.createTableQuery) execSQL(FavoriteEntryTable.createTableQuery)
execSQL(SavedSearchTable.createTableQuery)
// SY <-- // SY <--
// DB indexes // DB indexes
@ -101,6 +103,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
if (oldVersion < 12) { if (oldVersion < 12) {
db.execSQL(FavoriteEntryTable.fixTableQuery) db.execSQL(FavoriteEntryTable.fixTableQuery)
} }
if (oldVersion < 13) {
db.execSQL(SavedSearchTable.createTableQuery)
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {

View File

@ -411,8 +411,6 @@ class PreferencesHelper(val context: Context) {
fun ehLastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0) fun ehLastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
fun savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
fun logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0) fun logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
fun enableSourceBlacklist() = flowPrefs.getBoolean("eh_enable_source_blacklist", true) fun enableSourceBlacklist() = flowPrefs.getBoolean("eh_enable_source_blacklist", true)

View File

@ -37,7 +37,6 @@ import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
import eu.kanade.tachiyomi.ui.browse.source.SourceController import eu.kanade.tachiyomi.ui.browse.source.SourceController
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet.FilterNavigationView.Companion.MAX_SAVED_SEARCHES
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
@ -84,6 +83,7 @@ open class BrowseSourceController(bundle: Bundle) :
searchQuery: String? = null, searchQuery: String? = null,
// SY --> // SY -->
smartSearchConfig: SourceController.SmartSearchConfig? = null, smartSearchConfig: SourceController.SmartSearchConfig? = null,
savedSearch: Long? = null,
filterList: String? = null filterList: String? = null
// SY <-- // SY <--
) : this( ) : this(
@ -99,6 +99,10 @@ open class BrowseSourceController(bundle: Bundle) :
putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig) putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
} }
if (savedSearch != null) {
putLong(SAVED_SEARCH_CONFIG_KEY, savedSearch)
}
if (filterList != null) { if (filterList != null) {
putString(FILTERS_CONFIG_KEY, filterList) putString(FILTERS_CONFIG_KEY, filterList)
} }
@ -154,7 +158,8 @@ open class BrowseSourceController(bundle: Bundle) :
return BrowseSourcePresenter( return BrowseSourcePresenter(
args.getLong(SOURCE_ID_KEY), args.getLong(SOURCE_ID_KEY),
args.getString(SEARCH_QUERY_KEY), args.getString(SEARCH_QUERY_KEY),
filters = args.getString(FILTERS_CONFIG_KEY) filters = args.getString(FILTERS_CONFIG_KEY),
savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L }
) )
// SY <-- // SY <--
} }
@ -179,6 +184,10 @@ open class BrowseSourceController(bundle: Bundle) :
// SY <-- // SY <--
} }
fun setSavedSearches(savedSearches: List<EXHSavedSearch>) {
filterSheet?.setSavedSearches(savedSearches)
}
open fun initFilterSheet() { open fun initFilterSheet() {
if (presenter.sourceFilters.isEmpty()) { if (presenter.sourceFilters.isEmpty()) {
// SY --> // SY -->
@ -207,6 +216,7 @@ open class BrowseSourceController(bundle: Bundle) :
// EXH --> // EXH -->
onSaveClicked = { onSaveClicked = {
filterSheet?.context?.let { filterSheet?.context?.let {
val names = presenter.loadSearches().map { it.name }
var searchName = "" var searchName = ""
MaterialAlertDialogBuilder(it) MaterialAlertDialogBuilder(it)
.setTitle(R.string.save_search) .setTitle(R.string.save_search)
@ -214,27 +224,18 @@ open class BrowseSourceController(bundle: Bundle) :
searchName = input searchName = input
} }
.setPositiveButton(R.string.action_save) { _, _ -> .setPositiveButton(R.string.action_save) { _, _ ->
val oldSavedSearches = presenter.loadSearches() if (searchName.isNotBlank() && searchName !in names) {
if (searchName.isNotBlank() && presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters)
oldSavedSearches.size < MAX_SAVED_SEARCHES } else {
) { it.toast(R.string.save_search_invalid_name)
val newSearches = oldSavedSearches + EXHSavedSearch(
searchName.trim(),
presenter.query,
presenter.sourceFilters
)
presenter.saveSearches(newSearches)
filterSheet?.setSavedSearches(newSearches)
} }
} }
.setNegativeButton(R.string.action_cancel, null) .setNegativeButton(R.string.action_cancel, null)
.show() .show()
} }
}, },
onSavedSearchClicked = cb@{ indexToSearch -> onSavedSearchClicked = cb@{ idOfSearch ->
val savedSearches = presenter.loadSearches() val search = presenter.loadSearch(idOfSearch)
val search = savedSearches.getOrNull(indexToSearch)
if (search == null) { if (search == null) {
filterSheet?.context?.let { filterSheet?.context?.let {
@ -261,32 +262,14 @@ open class BrowseSourceController(bundle: Bundle) :
presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters)
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
}, },
onSavedSearchDeleteClicked = cb@{ indexToDelete, name -> onSavedSearchDeleteClicked = cb@{ idToDelete, name ->
val savedSearches = presenter.loadSearches()
val search = savedSearches.getOrNull(indexToDelete)
if (search == null || search.name != name) {
filterSheet?.context?.let {
MaterialAlertDialogBuilder(it)
.setTitle(R.string.save_search_failed_to_delete)
.setMessage(R.string.save_search_failed_to_delete_message)
.show()
}
return@cb
}
filterSheet?.context?.let { filterSheet?.context?.let {
MaterialAlertDialogBuilder(it) MaterialAlertDialogBuilder(it)
.setTitle(R.string.save_search_delete) .setTitle(R.string.save_search_delete)
.setMessage(it.getString(R.string.save_search_delete_message, search.name)) .setMessage(it.getString(R.string.save_search_delete_message, name))
.setPositiveButton(R.string.action_cancel, null) .setPositiveButton(R.string.action_cancel, null)
.setNegativeButton(android.R.string.ok) { _, _ -> .setNegativeButton(android.R.string.ok) { _, _ ->
val newSearches = savedSearches.filterIndexed { index, _ -> presenter.deleteSearch(idToDelete)
index != indexToDelete
}
presenter.saveSearches(newSearches)
filterSheet?.setSavedSearches(newSearches)
} }
.show() .show()
} }
@ -836,6 +819,7 @@ open class BrowseSourceController(bundle: Bundle) :
// SY --> // SY -->
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig" const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
const val SAVED_SEARCH_CONFIG_KEY = "savedSearch"
const val FILTERS_CONFIG_KEY = "filters" const val FILTERS_CONFIG_KEY = "filters"
// SY <-- // SY <--
} }

View File

@ -42,8 +42,9 @@ import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import exh.log.xLogE import exh.log.xLogE
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.models.SavedSearch
import exh.source.isEhBasedSource import exh.source.isEhBasedSource
import exh.util.nullIfBlank
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -73,6 +74,7 @@ open class BrowseSourcePresenter(
searchQuery: String? = null, searchQuery: String? = null,
// SY --> // SY -->
private val filters: String? = null, private val filters: String? = null,
private val savedSearch: Long? = null,
// SY <-- // SY <--
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(), private val db: DatabaseHelper = Injekt.get(),
@ -134,21 +136,45 @@ open class BrowseSourcePresenter(
sourceFilters = source.getFilterList() sourceFilters = source.getFilterList()
// SY --> // SY -->
val savedSearchFilters = savedSearch
val jsonFilters = filters val jsonFilters = filters
if (jsonFilters != null) { if (savedSearchFilters != null) {
runCatching { runCatching {
val filters = Json.decodeFromString<JsonSavedSearch>(jsonFilters) val savedSearch = db.getSavedSearch(savedSearchFilters).executeAsBlocking() ?: return@runCatching
filterSerializer.deserialize(sourceFilters, filters.filters) query = savedSearch.query.orEmpty()
val filtersJson = savedSearch.filtersJson
?: return@runCatching
val filters = Json.decodeFromString<JsonArray>(filtersJson)
filterSerializer.deserialize(sourceFilters, filters)
appliedFilters = sourceFilters
}
} else if (jsonFilters != null) {
runCatching {
val filters = Json.decodeFromString<JsonArray>(jsonFilters)
filterSerializer.deserialize(sourceFilters, filters)
appliedFilters = sourceFilters
} }
} }
val allDefault = sourceFilters == source.getFilterList()
db.getSavedSearches(source.id)
.asRxObservable()
.map {
loadSearches(it)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(
{ controller, savedSearches ->
controller.setSavedSearches(savedSearches)
}
)
// SY <-- // SY <--
if (savedState != null) { if (savedState != null) {
query = savedState.getString(::query.name, "") query = savedState.getString(::query.name, "")
} }
restartPager(/* SY -->*/ filters = if (allDefault) this.appliedFilters else sourceFilters /* SY <--*/) restartPager()
} }
override fun onSave(state: Bundle) { override fun onSave(state: Bundle) {
@ -319,7 +345,7 @@ open class BrowseSourcePresenter(
.forEach { service -> .forEach { service ->
launchIO { launchIO {
try { try {
service.match(manga)?.let { track -> service.match(source, manga)?.let { track ->
track.manga_id = manga.id!! track.manga_id = manga.id!!
(service as TrackService).bind(track) (service as TrackService).bind(track)
db.insertTrack(track).executeAsBlocking() db.insertTrack(track).executeAsBlocking()
@ -456,48 +482,84 @@ open class BrowseSourcePresenter(
} }
// EXH --> // EXH -->
fun saveSearches(searches: List<EXHSavedSearch>) { fun saveSearch(name: String, query: String, filterList: FilterList) {
val otherSerialized = prefs.savedSearches().get().filterNot { launchIO {
it.startsWith("${source.id}:") kotlin.runCatching {
}.toSet() val savedSearch = SavedSearch(
val newSerialized = searches.map { id = null,
"${source.id}:" + Json.encodeToString( source = source.id,
JsonSavedSearch( name = name.trim(),
it.name, query = query.nullIfBlank(),
it.query, filtersJson = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }
if (it.filterList != null) {
filterSerializer.serialize(it.filterList)
} else JsonArray(emptyList())
)
) )
db.insertSavedSearch(savedSearch).executeAsBlocking()
}
} }
prefs.savedSearches().set(otherSerialized + newSerialized)
} }
fun loadSearches(): List<EXHSavedSearch> { fun deleteSearch(searchId: Long) {
return prefs.savedSearches().get().mapNotNull { launchIO {
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null db.deleteSavedSearch(searchId).executeAsBlocking()
if (id != source.id) return@mapNotNull null
val content = try {
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
} catch (e: Exception) {
return@mapNotNull null
} }
}
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()
)
}
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
)
try { try {
val originalFilters = source.getFilterList() val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content.filters) filterSerializer.deserialize(originalFilters, filters)
EXHSavedSearch( EXHSavedSearch(
content.name, id = it.id!!,
content.query, name = it.name,
originalFilters query = it.query.orEmpty(),
filterList = originalFilters
) )
} catch (t: RuntimeException) { } catch (t: RuntimeException) {
// Load failed // Load failed
xLogE("Failed to load saved search!", t) xLogE("Failed to load saved search!", t)
EXHSavedSearch( EXHSavedSearch(
content.name, id = it.id!!,
content.query, name = it.name,
null query = it.query.orEmpty(),
filterList = null
) )
} }
} }

View File

@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.under
class SourceFilterSheet( class SourceFilterSheet(
activity: Activity, activity: Activity,
@ -32,8 +31,8 @@ class SourceFilterSheet(
private val onResetClicked: () -> Unit, private val onResetClicked: () -> Unit,
// EXH --> // EXH -->
private val onSaveClicked: () -> Unit, private val onSaveClicked: () -> Unit,
var onSavedSearchClicked: (Int) -> Unit = {}, var onSavedSearchClicked: (Long) -> Unit = {},
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> } var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> }
// EXH <-- // EXH <--
) : BaseBottomSheetDialog(activity) { ) : BaseBottomSheetDialog(activity) {
@ -97,9 +96,9 @@ class SourceFilterSheet(
// SY --> // SY -->
var onSaveClicked = {} var onSaveClicked = {}
var onSavedSearchClicked: (Int) -> Unit = {} var onSavedSearchClicked: (Long) -> Unit = {}
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> } var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> }
private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches)) private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches))
// SY <-- // SY <--
@ -143,17 +142,13 @@ class SourceFilterSheet(
} }
private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> { private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> {
recycler.post { return searches
binding.saveSearchBtn.isVisible = searches.size under MAX_SAVED_SEARCHES .map { search ->
}
return searches.withIndex()
.sortedBy { it.value.name }
.map { (index, search) ->
Chip(context).apply { Chip(context).apply {
text = search.name text = search.name
setOnClickListener { onSavedSearchClicked(index) } setOnClickListener { onSavedSearchClicked(search.id) }
setOnLongClickListener { setOnLongClickListener {
onSavedSearchDeleteClicked(index, search.name); true onSavedSearchDeleteClicked(search.id, search.name); true
} }
} }
} }
@ -163,10 +158,6 @@ class SourceFilterSheet(
fun hideFilterButton() { fun hideFilterButton() {
binding.filterBtn.isVisible = false binding.filterBtn.isVisible = false
} }
companion object {
const val MAX_SAVED_SEARCHES = 500 // if you want more than this, fuck you, i guess
}
// EXH <-- // EXH <--
} }
} }

View File

@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.savedsearches.JsonSavedSearch
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -196,25 +195,21 @@ open class IndexController :
onFilterClicked = { onFilterClicked = {
val allDefault = presenter.sourceFilters == presenter.source.getFilterList() val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
filterSheet?.dismiss() filterSheet?.dismiss()
val json = if (allDefault) { if (allDefault) {
null onBrowseClick(
} else { presenter.query.nullIfBlank()
Json.encodeToString(
JsonSavedSearch(
"",
"",
filterSerializer.serialize(presenter.sourceFilters)
) )
} else {
onBrowseClick(
presenter.query.nullIfBlank(),
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters))
) )
} }
onBrowseClick(presenter.query.nullIfBlank(), json)
}, },
onResetClicked = {}, onResetClicked = {},
onSaveClicked = {}, onSaveClicked = {},
onSavedSearchClicked = cb@{ indexToSearch -> onSavedSearchClicked = cb@{ idOfSearch ->
val savedSearches = presenter.loadSearches() val search = presenter.loadSearch(idOfSearch)
val search = savedSearches.getOrNull(indexToSearch)
if (search == null) { if (search == null) {
filterSheet?.context?.let { filterSheet?.context?.let {
@ -237,14 +232,10 @@ open class IndexController :
filterSheet?.dismiss() filterSheet?.dismiss()
if (!allDefault) { if (!allDefault) {
val json = Json.encodeToString( onBrowseClick(
JsonSavedSearch( search = presenter.query.nullIfBlank(),
"", savedSearch = search.id
"",
filterSerializer.serialize(presenter.sourceFilters)
) )
)
onBrowseClick(presenter.query.nullIfBlank(), json)
} }
}, },
onSavedSearchDeleteClicked = { _, _ -> } onSavedSearchDeleteClicked = { _, _ -> }
@ -325,8 +316,8 @@ open class IndexController :
super.onDestroyView(view) super.onDestroyView(view)
} }
fun onBrowseClick(search: String? = null, filters: String? = null) { fun onBrowseClick(search: String? = null, savedSearch: Long? = null, filters: String? = null) {
router.replaceTopController(BrowseSourceController(presenter.source, search, filterList = filters).withFadeTransaction()) router.replaceTopController(BrowseSourceController(presenter.source, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
} }
private fun onLatestClick() { private fun onLatestClick() {

View File

@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import exh.log.xLogE import exh.log.xLogE
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -212,30 +212,61 @@ open class IndexPresenter(
private val filterSerializer = FilterSerializer() private val filterSerializer = FilterSerializer()
fun loadSearches(): List<EXHSavedSearch> { fun loadSearch(searchId: Long): EXHSavedSearch? {
return preferences.savedSearches().get().mapNotNull { val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null return EXHSavedSearch(
if (id != source.id) return@mapNotNull null id = search.id!!,
val content = try { name = search.name,
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':')) query = search.query.orEmpty(),
} catch (e: Exception) { filterList = runCatching {
return@mapNotNull null 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 { try {
val originalFilters = source.getFilterList() val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content.filters) filterSerializer.deserialize(originalFilters, filters)
EXHSavedSearch( EXHSavedSearch(
content.name, id = it.id!!,
content.query, name = it.name,
originalFilters query = it.query.orEmpty(),
filterList = originalFilters
) )
} catch (t: RuntimeException) { } catch (t: RuntimeException) {
// Load failed // Load failed
xLogE("Failed to load saved search!", t) xLogE("Failed to load saved search!", t)
EXHSavedSearch( EXHSavedSearch(
content.name, id = it.id!!,
content.query, name = it.name,
null query = it.query.orEmpty(),
filterList = null
) )
} }
} }

View File

@ -40,6 +40,7 @@ import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE import exh.log.xLogE
import exh.log.xLogW import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.savedsearches.models.SavedSearch
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.HBROWSE_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID
@ -47,11 +48,17 @@ import exh.source.MERGED_SOURCE_ID
import exh.source.PERV_EDEN_EN_SOURCE_ID import exh.source.PERV_EDEN_EN_SOURCE_ID
import exh.source.PERV_EDEN_IT_SOURCE_ID import exh.source.PERV_EDEN_IT_SOURCE_ID
import exh.source.TSUMINO_SOURCE_ID import exh.source.TSUMINO_SOURCE_ID
import exh.util.nullIfBlank
import exh.util.under import exh.util.under
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -395,6 +402,26 @@ object EXHMigrations {
if (oldVersion under 30) { if (oldVersion under 30) {
BackupCreatorJob.setupTask(context) 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)
}
prefs.edit(commit = true) {
remove("eh_saved_searches")
}
}
// if (oldVersion under 1) { } (1 is current release version) // if (oldVersion under 1) { } (1 is current release version)
// do stuff here when releasing changed crap // do stuff here when releasing changed crap

View File

@ -7,18 +7,15 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
import exh.EXHMigrations import exh.EXHMigrations
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.savedsearches.JsonSavedSearch
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
@ -30,11 +27,7 @@ import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.lang.RuntimeException
import java.util.UUID import java.util.UUID
@Suppress("unused") @Suppress("unused")
@ -171,7 +164,7 @@ object DebugFunctions {
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
} }
fun clearSavedSearches() = prefs.savedSearches().set(emptySet()) fun clearSavedSearches() = db.deleteAllSavedSearches().executeAsBlocking()
fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") { fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") {
"${it.id}: ${it.name} (${it.lang.uppercase()})" "${it.id}: ${it.name} (${it.lang.uppercase()})"
@ -249,7 +242,7 @@ object DebugFunctions {
) )
} }
fun copyEHentaiSavedSearchesToExhentai() { /*fun copyEHentaiSavedSearchesToExhentai() {
runBlocking { runBlocking {
val source = sourceManager.get(EH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking val source = sourceManager.get(EH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
val newSource = sourceManager.get(EXH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking val newSource = sourceManager.get(EXH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
@ -325,7 +318,7 @@ object DebugFunctions {
} }
prefs.savedSearches().set((otherSerialized + newSerialized).toSet()) prefs.savedSearches().set((otherSerialized + newSerialized).toSet())
} }
} }*/
fun fixReaderViewerBackupBug() { fun fixReaderViewerBackupBug() {
db.inTransaction { db.inTransaction {

View File

@ -3,6 +3,7 @@ package exh.savedsearches
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
data class EXHSavedSearch( data class EXHSavedSearch(
val id: Long,
val name: String, val name: String,
val query: String, val query: String,
val filterList: FilterList? val filterList: FilterList?

View File

@ -1,11 +0,0 @@
package exh.savedsearches
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
@Serializable
data class JsonSavedSearch(
val name: String,
val query: String,
val filters: JsonArray
)

View File

@ -0,0 +1,66 @@
package exh.savedsearches.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import androidx.core.database.getStringOrNull
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.savedsearches.models.SavedSearch
import exh.savedsearches.tables.SavedSearchTable.COL_FILTERS_JSON
import exh.savedsearches.tables.SavedSearchTable.COL_ID
import exh.savedsearches.tables.SavedSearchTable.COL_NAME
import exh.savedsearches.tables.SavedSearchTable.COL_QUERY
import exh.savedsearches.tables.SavedSearchTable.COL_SOURCE
import exh.savedsearches.tables.SavedSearchTable.TABLE
class SavedSearchTypeMapping : SQLiteTypeMapping<SavedSearch>(
SavedSearchPutResolver(),
SavedSearchGetResolver(),
SavedSearchDeleteResolver()
)
class SavedSearchPutResolver : DefaultPutResolver<SavedSearch>() {
override fun mapToInsertQuery(obj: SavedSearch) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SavedSearch) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SavedSearch) = contentValuesOf(
COL_ID to obj.id,
COL_SOURCE to obj.source,
COL_NAME to obj.name,
COL_QUERY to obj.query,
COL_FILTERS_JSON to obj.filtersJson
)
}
class SavedSearchGetResolver : DefaultGetResolver<SavedSearch>() {
override fun mapFromCursor(cursor: Cursor): SavedSearch = SavedSearch(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE)),
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME)),
query = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_QUERY)),
filtersJson = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_FILTERS_JSON))
)
}
class SavedSearchDeleteResolver : DefaultDeleteResolver<SavedSearch>() {
override fun mapToDeleteQuery(obj: SavedSearch) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -0,0 +1,18 @@
package exh.savedsearches.models
data class SavedSearch(
// Tag identifier, unique
var id: Long?,
// The source the saved search is for
var source: Long,
// If false the manga will not grab chapter updates
var name: String,
// The query if there is any
var query: String?,
// The filter list
var filtersJson: String?,
)

View File

@ -0,0 +1,82 @@
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 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()
}
}
}*/
}

View File

@ -0,0 +1,26 @@
package exh.savedsearches.tables
object SavedSearchTable {
const val TABLE = "saved_search"
const val COL_ID = "_id"
const val COL_SOURCE = "source"
const val COL_NAME = "name"
const val COL_QUERY = "query"
const val COL_FILTERS_JSON = "filters_json"
val createTableQuery: String
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_SOURCE INTEGER NOT NULL,
$COL_NAME TEXT NOT NULL,
$COL_QUERY TEXT,
$COL_FILTERS_JSON TEXT
)"""
}

View File

@ -355,6 +355,7 @@
<string name="save_search_delete">Delete saved search query?</string> <string name="save_search_delete">Delete saved search query?</string>
<string name="save_search_delete_message">Are you sure you wish to delete your saved search query: \'%1$s\'?</string> <string name="save_search_delete_message">Are you sure you wish to delete your saved search query: \'%1$s\'?</string>
<string name="save_search_invalid">Saved search invalid, filters have changed</string> <string name="save_search_invalid">Saved search invalid, filters have changed</string>
<string name="save_search_invalid_name">Invalid saved search name</string>
<!-- Source Categories --> <!-- Source Categories -->
<string name="no_source_categories">No source categories available</string> <string name="no_source_categories">No source categories available</string>