implement Source Filters

This commit is contained in:
Aria Moradi
2021-11-02 04:14:49 +03:30
parent 64ea8416b2
commit d90b986d19
11 changed files with 440 additions and 66 deletions
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.source.model
sealed class Filter<T>(val name: String, var state: T) {
// The class is originally sealed, Tachidesk adds new subclasses for serialization
// sealed class Filter<T>(val name: String, var state: T) {
open class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
@@ -44,7 +44,8 @@ object MangaAPI {
get("{sourceId}/preferences", SourceController::getPreferences)
post("{sourceId}/preferences", SourceController::setPreference)
get("{sourceId}/filters", SourceController::filters)
get("{sourceId}/filters", SourceController::getFilters)
patch("{sourceId}/filters", SourceController::setFilter)
get("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle)
// get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal)
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.manga.controller
import io.javalin.http.Context
import suwayomi.tachidesk.manga.impl.MangaList
import suwayomi.tachidesk.manga.impl.Search
import suwayomi.tachidesk.manga.impl.Search.FilterChange
import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange
import suwayomi.tachidesk.server.JavalinSetup.future
@@ -54,7 +55,7 @@ object SourceController {
ctx.json(Source.getSourcePreferences(sourceId))
}
/** fetch preferences of source with id `sourceId` */
/** set one preference of source with id `sourceId` */
fun setPreference(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong()
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
@@ -62,11 +63,18 @@ object SourceController {
}
/** fetch filters of source with id `sourceId` */
fun filters(ctx: Context) {
fun getFilters(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong()
val reset = ctx.queryParam("reset")?.toBoolean() ?: false
ctx.json(Search.getFilterList(sourceId, reset))
}
ctx.json(Search.getInitialFilterList(sourceId, reset))
/** set one filter of source with id `sourceId` */
fun setFilter(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong()
val filterChange = ctx.bodyAsClass(FilterChange::class.java)
ctx.json(Search.setFilter(sourceId, filterChange))
}
/** single source search */
@@ -7,8 +7,13 @@ package suwayomi.tachidesk.manga.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import io.javalin.plugin.json.JsonMapper
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
@@ -17,89 +22,102 @@ import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
object Search {
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
val source = getCatalogueSourceOrStub(sourceId)
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle()
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(source)).awaitSingle()
return searchManga.processEntries(sourceId)
}
private val filterListCache = mutableMapOf<Long, FilterList>()
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList {
if (reset || !filterListCache.containsKey(sourceId)) {
filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList()
private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList {
if (reset || !filterListCache.containsKey(source.id)) {
filterListCache[source.id] = source.getFilterList()
}
return filterListCache[sourceId]!!
return filterListCache[source.id]!!
}
fun getInitialFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
return getFilterListOf(sourceId, reset).list.map {
fun getFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
val source = getCatalogueSourceOrStub(sourceId)
return getFilterListOf(source, reset).list.map {
FilterObject(
when (it) {
is Filter.Header -> "Header"
is Filter.Separator -> "Separator"
is Filter.Select<*> -> "Select"
is Filter.Text -> "Text"
is Filter.CheckBox -> "CheckBox"
is Filter.TriState -> "TriState"
is Filter.Text -> "Text"
is Filter.Select<*> -> "Select"
is Filter.Group<*> -> "Group"
is Filter.Sort -> "Sort"
else -> throw RuntimeException("sealed class Cannot have more Subtypes!")
},
// when (it) {
// is Filter.Select<*> -> it.getValuesType()
// else -> null
// },
it
when (it) {
is Filter.Group<*> -> {
SerializableGroup(
it.name,
it.state.map { item ->
when (item) {
is Filter.CheckBox -> FilterObject("CheckBox", item)
is Filter.TriState -> FilterObject("TriState", item)
is Filter.Text -> FilterObject("Text", item)
is Filter.Select<*> -> FilterObject("Select", item)
else -> throw RuntimeException("Illegal Group item type!")
}
}
)
}
else -> it
}
)
}
}
// private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName
private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName
class SerializableGroup(name: String, state: List<FilterObject>) : Filter<List<FilterObject>>(name, state)
data class FilterObject(
val type: String,
val filter: Filter<*>
val filter: Filter<*>,
)
fun setFilter(sourceId: Long, change: FilterChange) {
val source = getCatalogueSourceOrStub(sourceId)
val filterList = getFilterListOf(source, false)
when (val filter = filterList[change.position]) {
is Filter.Header -> {
// NOOP
}
is Filter.Separator -> {
// NOOP
}
is Filter.Select<*> -> filter.state = change.state.toInt()
is Filter.Text -> filter.state = change.state
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict()
is Filter.TriState -> filter.state = change.state.toInt()
is Filter.Group<*> -> {
val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java)
when (val groupFilter = filter.state[groupChange.position]) {
is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict()
is Filter.TriState -> groupFilter.state = groupChange.state.toInt()
is Filter.Text -> groupFilter.state = groupChange.state
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
}
}
is Filter.Sort -> filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
}
}
private val jsonMapper by DI.global.instance<JsonMapper>()
data class FilterChange(
val position: Int,
val state: String
)
@Suppress("UNUSED_PARAMETER")
fun sourceGlobalSearch(searchTerm: String) {
// TODO
}
/**
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
*/
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
// return mapNotNull { filter ->
// when (filter) {
// is Filter.Header -> FilterWrapper("Header",filter)
// is Filter.Separator -> FilterWrapper("Separator",filter)
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
// is Filter.TriState -> FilterWrapper("TriState",filter)
// is Filter.Text -> FilterWrapper("Text",filter)
// is Filter.Select<*> -> FilterWrapper("Select",filter)
// is Filter.Group<*> -> {
// val group = GroupItem(filter)
// val subItems = filter.state.mapNotNull {
// when (it) {
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
// is Filter.TriState -> FilterWrapper("TriState",filter)
// is Filter.Text -> FilterWrapper("Text",filter)
// is Filter.Select<*> -> FilterWrapper("Select",filter)
// else -> null
// } as? ISectionable<*, *>
// }
// subItems.forEach { it.header = group }
// group.subItems = subItems
// group
// }
// is Filter.Sort -> {
// val group = SortGroup(filter)
// val subItems = filter.values.map {
// SortItem(it, group)
// }
// group.subItems = subItems
// group
// }
// }
// }
// }
}
@@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import rx.Observable
class StubSource(override val id: Long) : CatalogueSource {
open class StubSource(override val id: Long) : CatalogueSource {
override val lang: String = "other"
override val supportsLatest: Boolean = false
override val name: String
@@ -9,6 +9,8 @@ package suwayomi.tachidesk.server
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.source.local.LocalSource
import io.javalin.plugin.json.JavalinJackson
import io.javalin.plugin.json.JsonMapper
import mu.KotlinLogging
import org.kodein.di.DI
import org.kodein.di.bind
@@ -53,6 +55,7 @@ fun applicationSetup() {
DI.global.addImport(
DI.Module("Server") {
bind<ApplicationDirs>() with singleton { applicationDirs }
bind<JsonMapper>() with singleton { JavalinJackson() }
}
)
@@ -7,10 +7,13 @@ package suwayomi.tachidesk.server.util
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import io.javalin.plugin.json.JavalinJackson
import io.javalin.plugin.json.JsonMapper
import mu.KotlinLogging
import okhttp3.OkHttpClient
import okhttp3.Request.Builder
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.global.impl.AboutDataClass
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.util.Browser.openInBrowser
@@ -30,6 +33,8 @@ object AppMutex {
private val appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip
private val jsonMapper by DI.global.instance<JsonMapper>()
private fun checkAppMutex(): AppMutexState {
val client = OkHttpClient.Builder()
.connectTimeout(200, TimeUnit.MILLISECONDS)
@@ -46,7 +51,7 @@ object AppMutex {
}
return try {
JavalinJackson().fromJsonString(response, AboutDataClass::class.java)
jsonMapper.fromJsonString(response, AboutDataClass::class.java)
AppMutexState.TachideskInstanceRunning
} catch (e: IOException) {
AppMutexState.OtherApplicationRunning