mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-15 16:54:09 -05:00
Compare commits
18 Commits
3a78453a02
...
396cfa734a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
396cfa734a | ||
|
|
a1fdf6d77a | ||
|
|
07ae17105b | ||
|
|
b8772f60bf | ||
|
|
733b9c9919 | ||
|
|
0b0c056bcb | ||
|
|
aff95bfc37 | ||
|
|
00bc3e39b6 | ||
|
|
e9c2cc49a6 | ||
|
|
ea310ba54b | ||
|
|
72347f45cc | ||
|
|
1e73e526c6 | ||
|
|
8fd0fdba08 | ||
|
|
4b61d375ed | ||
|
|
3cf4cf6cf8 | ||
|
|
74ade8a3a3 | ||
|
|
3bb2e4329e | ||
|
|
33ec15c136 |
@@ -172,7 +172,7 @@ server.maxLogFolderSize = "100mb"
|
|||||||
server.extensionStores = []
|
server.extensionStores = []
|
||||||
server.maxSourcesInParallel = 6
|
server.maxSourcesInParallel = 6
|
||||||
```
|
```
|
||||||
- `server.extensionStores` is a list of extension stores(previously called repositories) for custom sources. Uses the same format as Mihon; each entry is expected to be a string URL pointing to a JSON or PROTOBUF file representing the repository.
|
- `server.extensionStores` is a list of extension stores (previously called repositories) for custom sources. Uses the same format as Mihon; each entry is expected to be a string URL pointing to a JSON or PROTOBUF file representing the repository.
|
||||||
- `server.maxSourcesInParallel = 6` sets how many sources can do requests (updates, downloads) in parallel. Updates/downloads are grouped by source and all mangas of a source are updated/downloaded synchronously. Range: 1 <= n <= 20.
|
- `server.maxSourcesInParallel = 6` sets how many sources can do requests (updates, downloads) in parallel. Updates/downloads are grouped by source and all mangas of a source are updated/downloaded synchronously. Range: 1 <= n <= 20.
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|||||||
@@ -287,9 +287,10 @@ class ServerConfig(
|
|||||||
replaceWith = "extensionStores",
|
replaceWith = "extensionStores",
|
||||||
message = "Replaced with extensionStores",
|
message = "Replaced with extensionStores",
|
||||||
migrateConfigValue = {
|
migrateConfigValue = {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
(it.unwrapped() as? List<String>)
|
(it.unwrapped() as? List<String>)
|
||||||
?.map {
|
?.map {
|
||||||
if (it.contains("github")) {
|
if (it.contains("github.com")) {
|
||||||
it.replace(repoMatchRegex) {
|
it.replace(repoMatchRegex) {
|
||||||
"https://raw.githubusercontent.com/${it.groupValues[2]}/${it.groupValues[3]}/" +
|
"https://raw.githubusercontent.com/${it.groupValues[2]}/${it.groupValues[3]}/" +
|
||||||
(it.groupValues.getOrNull(4)?.ifBlank { null } ?: "repo") +
|
(it.groupValues.getOrNull(4)?.ifBlank { null } ?: "repo") +
|
||||||
@@ -303,7 +304,7 @@ class ServerConfig(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
readMigrated = { extensionStores.value },
|
readMigrated = { extensionStores.value },
|
||||||
setMigrated = { extensionStores.value = it },
|
setMigrated = { extensionStores.value = (extensionStores.value + it).distinct() },
|
||||||
typeInfo =
|
typeInfo =
|
||||||
SettingsRegistry.PartialTypeInfo(
|
SettingsRegistry.PartialTypeInfo(
|
||||||
specificType = "List<String>",
|
specificType = "List<String>",
|
||||||
@@ -1117,7 +1118,7 @@ class ServerConfig(
|
|||||||
val extensionStores: MutableStateFlow<List<String>> by ListSetting<String>(
|
val extensionStores: MutableStateFlow<List<String>> by ListSetting<String>(
|
||||||
protoNumber = 97,
|
protoNumber = 97,
|
||||||
group = SettingGroup.EXTENSION,
|
group = SettingGroup.EXTENSION,
|
||||||
privacySafe = false,
|
privacySafe = true,
|
||||||
defaultValue = emptyList(),
|
defaultValue = emptyList(),
|
||||||
itemValidator = { url ->
|
itemValidator = { url ->
|
||||||
if (url.isNotEmpty()) {
|
if (url.isNotEmpty()) {
|
||||||
@@ -1127,11 +1128,7 @@ class ServerConfig(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemToValidValue = { url ->
|
itemToValidValue = { url ->
|
||||||
if (url.isNotEmpty()) {
|
url.ifEmpty { null }
|
||||||
url
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
typeInfo =
|
typeInfo =
|
||||||
SettingsRegistry.PartialTypeInfo(
|
SettingsRegistry.PartialTypeInfo(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package eu.kanade.tachiyomi.source.model
|
package eu.kanade.tachiyomi.source.model
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.lang.EMPTY
|
||||||
|
|
||||||
class SChapterImpl : SChapter {
|
class SChapterImpl : SChapter {
|
||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
@@ -15,5 +16,5 @@ class SChapterImpl : SChapter {
|
|||||||
|
|
||||||
override var date_upload: Long = 0
|
override var date_upload: Long = 0
|
||||||
|
|
||||||
override var memo: JsonObject = JsonObject(emptyMap())
|
override var memo: JsonObject = JsonObject.EMPTY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package eu.kanade.tachiyomi.source.model
|
package eu.kanade.tachiyomi.source.model
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.lang.EMPTY
|
||||||
|
|
||||||
class SMangaImpl : SManga {
|
class SMangaImpl : SManga {
|
||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
@@ -25,5 +26,5 @@ class SMangaImpl : SManga {
|
|||||||
|
|
||||||
override var initialized: Boolean = false
|
override var initialized: Boolean = false
|
||||||
|
|
||||||
override var memo: JsonObject = JsonObject(emptyMap())
|
override var memo: JsonObject = JsonObject.EMPTY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,26 @@ import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
|
class ExtensionStoreDataLoader : KotlinDataLoader<String, ExtensionStoreType> {
|
||||||
|
override val dataLoaderName = "ExtensionStoreDataLoader"
|
||||||
|
|
||||||
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionStoreType> =
|
||||||
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
|
future {
|
||||||
|
transaction {
|
||||||
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
val manga =
|
||||||
|
ExtensionStoreTable
|
||||||
|
.selectAll()
|
||||||
|
.where { ExtensionStoreTable.indexUrl inList ids }
|
||||||
|
.map { ExtensionStoreType(it) }
|
||||||
|
.associateBy { it.indexUrl }
|
||||||
|
ids.map { manga[it] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ExtensionStoreForExtension : KotlinDataLoader<String, ExtensionStoreType> {
|
class ExtensionStoreForExtension : KotlinDataLoader<String, ExtensionStoreType> {
|
||||||
override val dataLoaderName = "ExtensionStoreForExtension"
|
override val dataLoaderName = "ExtensionStoreForExtension"
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import suwayomi.tachidesk.graphql.types.ChapterType
|
|||||||
import suwayomi.tachidesk.graphql.types.MetaInput
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
|
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
import suwayomi.tachidesk.manga.impl.Chapter
|
||||||
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
|
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
|
||||||
import suwayomi.tachidesk.manga.impl.sync.KoreaderSyncService
|
import suwayomi.tachidesk.manga.impl.sync.KoreaderSyncService
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
|
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
|
||||||
@@ -173,7 +174,7 @@ class ChapterMutation {
|
|||||||
val (clientMutationId, mangaId) = input
|
val (clientMutationId, mangaId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
Chapter.fetchChapterList(mangaId)
|
Manga.updateMangaAndChapters(mangaId, updateManga = false)
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ import suwayomi.tachidesk.graphql.types.ChapterType
|
|||||||
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
import suwayomi.tachidesk.graphql.types.MetaInput
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
|
||||||
import suwayomi.tachidesk.manga.impl.Library
|
import suwayomi.tachidesk.manga.impl.Library
|
||||||
import suwayomi.tachidesk.manga.impl.Manga
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -156,7 +154,7 @@ class MangaMutation {
|
|||||||
val (clientMutationId, id) = input
|
val (clientMutationId, id) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
Manga.fetchManga(id)
|
Manga.updateMangaAndChapters(id, updateChapters = false)
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
@@ -187,20 +185,11 @@ class MangaMutation {
|
|||||||
val (clientMutationId, id, fetchManga, fetchChapters) = input
|
val (clientMutationId, id, fetchManga, fetchChapters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
var mangaEntry =
|
Manga.updateMangaAndChapters(
|
||||||
transaction { MangaTable.selectAll().where { MangaTable.id eq id }.first() }
|
mangaId = id,
|
||||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
updateManga = fetchManga,
|
||||||
val sMangaUpdate =
|
updateChapters = fetchChapters,
|
||||||
Manga.fetchMangaAndChapters(
|
)
|
||||||
mangaEntry = mangaEntry,
|
|
||||||
source = source,
|
|
||||||
fetchDetails = fetchManga,
|
|
||||||
fetchChapters = fetchChapters,
|
|
||||||
)
|
|
||||||
|
|
||||||
Manga.updateMangaDatabase(mangaEntry, source, sMangaUpdate.manga)
|
|
||||||
mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq id }.first() }
|
|
||||||
Chapter.updateChapterListDatabase(mangaEntry, sMangaUpdate.chapters, source)
|
|
||||||
|
|
||||||
val (manga, chapters) =
|
val (manga, chapters) =
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.jetbrains.exposed.v1.jdbc.selectAll
|
|||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.IntFilter
|
import suwayomi.tachidesk.graphql.queries.filter.IntFilter
|
||||||
@@ -28,6 +29,7 @@ import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
|||||||
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEnum
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||||
@@ -126,7 +128,10 @@ class ExtensionQuery {
|
|||||||
opAnd.eq(versionCode?.toLong(), ExtensionTable.versionCode)
|
opAnd.eq(versionCode?.toLong(), ExtensionTable.versionCode)
|
||||||
opAnd.eq(versionCodeLong, ExtensionTable.versionCode)
|
opAnd.eq(versionCodeLong, ExtensionTable.versionCode)
|
||||||
opAnd.eq(lang, ExtensionTable.lang)
|
opAnd.eq(lang, ExtensionTable.lang)
|
||||||
opAnd.eq(isNsfw?.let { if (it) 3 else 0 }, ExtensionTable.contentRating)
|
opAnd.eq(
|
||||||
|
isNsfw?.let { if (it) ContentRating.PORNOGRAPHIC.ordinal else ContentRating.SAFE.ordinal },
|
||||||
|
ExtensionTable.contentRating,
|
||||||
|
)
|
||||||
opAnd.eq(contentRating?.ordinal, ExtensionTable.contentRating)
|
opAnd.eq(contentRating?.ordinal, ExtensionTable.contentRating)
|
||||||
opAnd.eq(isInstalled, ExtensionTable.isInstalled)
|
opAnd.eq(isInstalled, ExtensionTable.isInstalled)
|
||||||
opAnd.eq(hasUpdate, ExtensionTable.hasUpdate)
|
opAnd.eq(hasUpdate, ExtensionTable.hasUpdate)
|
||||||
@@ -153,7 +158,7 @@ class ExtensionQuery {
|
|||||||
val lang: StringFilter? = null,
|
val lang: StringFilter? = null,
|
||||||
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
|
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
|
||||||
val isNsfw: BooleanFilter? = null,
|
val isNsfw: BooleanFilter? = null,
|
||||||
// val contentRating: EnumFilter<ContentRating>? = null,
|
val contentRating: ContentRatingFilter? = null,
|
||||||
val isInstalled: BooleanFilter? = null,
|
val isInstalled: BooleanFilter? = null,
|
||||||
val hasUpdate: BooleanFilter? = null,
|
val hasUpdate: BooleanFilter? = null,
|
||||||
val isObsolete: BooleanFilter? = null,
|
val isObsolete: BooleanFilter? = null,
|
||||||
@@ -174,7 +179,7 @@ class ExtensionQuery {
|
|||||||
andFilterWithCompareString(ExtensionTable.versionName, versionName),
|
andFilterWithCompareString(ExtensionTable.versionName, versionName),
|
||||||
andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong),
|
andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong),
|
||||||
andFilterWithCompareString(ExtensionTable.lang, lang),
|
andFilterWithCompareString(ExtensionTable.lang, lang),
|
||||||
// andFilterWithCompareEnum(ExtensionTable.contentRating, contentRating),
|
andFilterWithCompareEnum(ExtensionTable.contentRating, contentRating),
|
||||||
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
|
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
|
||||||
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
|
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
|
||||||
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete),
|
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete),
|
||||||
|
|||||||
@@ -7,33 +7,185 @@ package suwayomi.tachidesk.graphql.queries
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import org.jetbrains.exposed.v1.core.eq
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
|
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||||
|
import graphql.schema.DataFetchingEnvironment
|
||||||
|
import org.jetbrains.exposed.v1.core.Column
|
||||||
|
import org.jetbrains.exposed.v1.core.Op
|
||||||
|
import org.jetbrains.exposed.v1.core.SortOrder
|
||||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.Order
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.OrderBy
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.QueryResults
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.applyBeforeAfter
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.greaterNotUnique
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.lessNotUnique
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.maybeSwap
|
||||||
|
import suwayomi.tachidesk.graphql.types.ExtensionStoreNodeList
|
||||||
import suwayomi.tachidesk.graphql.types.ExtensionStoreType
|
import suwayomi.tachidesk.graphql.types.ExtensionStoreType
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class ExtensionStoreQuery {
|
class ExtensionStoreQuery {
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun extensionStore(indexUrl: String): CompletableFuture<ExtensionStoreType?> =
|
fun extensionStore(
|
||||||
CompletableFuture.supplyAsync {
|
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||||
transaction {
|
indexUrl: String,
|
||||||
ExtensionStoreTable
|
): CompletableFuture<ExtensionStoreType> = dataFetchingEnvironment.getValueFromDataLoader("ExtensionStoreDataLoader", indexUrl)
|
||||||
.selectAll()
|
|
||||||
.where { ExtensionStoreTable.indexUrl eq indexUrl }
|
enum class ExtensionStoreOrderBy(
|
||||||
.firstOrNull()
|
override val column: Column<*>,
|
||||||
?.let { ExtensionStoreType(it) }
|
) : OrderBy<ExtensionStoreType> {
|
||||||
|
NAME(ExtensionStoreTable.name),
|
||||||
|
INDEX_URL(ExtensionStoreTable.indexUrl),
|
||||||
|
;
|
||||||
|
|
||||||
|
override fun greater(cursor: Cursor): Op<Boolean> =
|
||||||
|
when (this) {
|
||||||
|
NAME -> greaterNotUnique(ExtensionStoreTable.name, ExtensionStoreTable.id, cursor, String::toString)
|
||||||
|
INDEX_URL -> greaterNotUnique(ExtensionStoreTable.indexUrl, ExtensionStoreTable.id, cursor, String::toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun less(cursor: Cursor): Op<Boolean> =
|
||||||
|
when (this) {
|
||||||
|
NAME -> lessNotUnique(ExtensionStoreTable.name, ExtensionStoreTable.id, cursor, String::toString)
|
||||||
|
INDEX_URL -> lessNotUnique(ExtensionStoreTable.indexUrl, ExtensionStoreTable.id, cursor, String::toString)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun asCursor(type: ExtensionStoreType): Cursor {
|
||||||
|
val value =
|
||||||
|
when (this) {
|
||||||
|
INDEX_URL -> type.indexUrl
|
||||||
|
NAME -> type.indexUrl + "-" + type.name
|
||||||
|
}
|
||||||
|
return Cursor(value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExtensionStoreOrder(
|
||||||
|
override val by: ExtensionStoreOrderBy,
|
||||||
|
override val byType: SortOrder? = null,
|
||||||
|
) : Order<ExtensionStoreOrderBy>
|
||||||
|
|
||||||
|
data class ExtensionStoreCondition(
|
||||||
|
val id: Int? = null,
|
||||||
|
val indexUrl: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
) : HasGetOp {
|
||||||
|
override fun getOp(): Op<Boolean>? {
|
||||||
|
val opAnd = OpAnd()
|
||||||
|
opAnd.eq(id, ExtensionStoreTable.id)
|
||||||
|
opAnd.eq(indexUrl, ExtensionStoreTable.indexUrl)
|
||||||
|
opAnd.eq(name, ExtensionStoreTable.name)
|
||||||
|
|
||||||
|
return opAnd.op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExtensionStoreFilter(
|
||||||
|
val indexUrl: StringFilter? = null,
|
||||||
|
val name: StringFilter? = null,
|
||||||
|
override val and: List<ExtensionStoreFilter>? = null,
|
||||||
|
override val or: List<ExtensionStoreFilter>? = null,
|
||||||
|
override val not: ExtensionStoreFilter? = null,
|
||||||
|
) : Filter<ExtensionStoreFilter> {
|
||||||
|
override fun getOpList(): List<Op<Boolean>> =
|
||||||
|
listOfNotNull(
|
||||||
|
andFilterWithCompareString(ExtensionStoreTable.indexUrl, indexUrl),
|
||||||
|
andFilterWithCompareString(ExtensionStoreTable.name, name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun extensionStores(): List<ExtensionStoreType> =
|
fun extensionStores(
|
||||||
transaction {
|
condition: ExtensionStoreCondition? = null,
|
||||||
ExtensionStoreTable
|
filter: ExtensionStoreFilter? = null,
|
||||||
.selectAll()
|
order: List<ExtensionStoreOrder>? = null,
|
||||||
.toList()
|
before: Cursor? = null,
|
||||||
.map { ExtensionStoreType(it) }
|
after: Cursor? = null,
|
||||||
}
|
first: Int? = null,
|
||||||
|
last: Int? = null,
|
||||||
|
offset: Int? = null,
|
||||||
|
): ExtensionStoreNodeList {
|
||||||
|
val queryResults =
|
||||||
|
transaction {
|
||||||
|
val res = ExtensionStoreTable.selectAll()
|
||||||
|
|
||||||
|
res.applyOps(condition, filter)
|
||||||
|
|
||||||
|
if (order != null || (last != null || before != null)) {
|
||||||
|
val baseSort = listOf(ExtensionStoreOrder(ExtensionStoreOrderBy.INDEX_URL, SortOrder.ASC))
|
||||||
|
val actualSort = (order.orEmpty() + baseSort)
|
||||||
|
actualSort.forEach { (orderBy, orderByType) ->
|
||||||
|
val orderByColumn = orderBy.column
|
||||||
|
val orderType = orderByType.maybeSwap(last ?: before)
|
||||||
|
|
||||||
|
res.orderBy(orderByColumn to orderType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val total = res.count()
|
||||||
|
val firstResult = res.firstOrNull()?.get(ExtensionStoreTable.indexUrl)
|
||||||
|
val lastResult = res.lastOrNull()?.get(ExtensionStoreTable.indexUrl)
|
||||||
|
|
||||||
|
res.applyBeforeAfter(
|
||||||
|
before = before,
|
||||||
|
after = after,
|
||||||
|
orderBy = order?.firstOrNull()?.by ?: ExtensionStoreOrderBy.INDEX_URL,
|
||||||
|
orderByType = order?.firstOrNull()?.byType,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (first != null) {
|
||||||
|
res.limit(first).offset(offset?.toLong() ?: 0)
|
||||||
|
} else if (last != null) {
|
||||||
|
res.limit(last)
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryResults(total, firstResult, lastResult, res.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
val getAsCursor: (ExtensionStoreType) -> Cursor = (order?.firstOrNull()?.by ?: ExtensionStoreOrderBy.INDEX_URL)::asCursor
|
||||||
|
|
||||||
|
val resultsAsType = queryResults.results.map { ExtensionStoreType(it) }
|
||||||
|
|
||||||
|
return ExtensionStoreNodeList(
|
||||||
|
resultsAsType,
|
||||||
|
if (resultsAsType.isEmpty()) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
listOfNotNull(
|
||||||
|
resultsAsType.firstOrNull()?.let {
|
||||||
|
ExtensionStoreNodeList.ExtensionStoreEdge(
|
||||||
|
getAsCursor(it),
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
resultsAsType.lastOrNull()?.let {
|
||||||
|
ExtensionStoreNodeList.ExtensionStoreEdge(
|
||||||
|
getAsCursor(it),
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
pageInfo =
|
||||||
|
PageInfo(
|
||||||
|
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.indexUrl,
|
||||||
|
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.indexUrl,
|
||||||
|
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||||
|
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||||
|
),
|
||||||
|
totalCount = queryResults.total.toInt(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,20 @@ import org.jetbrains.exposed.v1.core.Op
|
|||||||
import org.jetbrains.exposed.v1.core.SortOrder
|
import org.jetbrains.exposed.v1.core.SortOrder
|
||||||
import org.jetbrains.exposed.v1.core.eq
|
import org.jetbrains.exposed.v1.core.eq
|
||||||
import org.jetbrains.exposed.v1.core.greater
|
import org.jetbrains.exposed.v1.core.greater
|
||||||
import org.jetbrains.exposed.v1.core.greaterEq
|
|
||||||
import org.jetbrains.exposed.v1.core.less
|
import org.jetbrains.exposed.v1.core.less
|
||||||
|
import org.jetbrains.exposed.v1.core.neq
|
||||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
||||||
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEnum
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||||
@@ -93,7 +95,7 @@ class SourceQuery {
|
|||||||
val id: Long? = null,
|
val id: Long? = null,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val lang: String? = null,
|
val lang: String? = null,
|
||||||
@GraphQLDeprecated("replace with contentRating == 3", ReplaceWith("contentRating"))
|
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating"))
|
||||||
val isNsfw: Boolean? = null,
|
val isNsfw: Boolean? = null,
|
||||||
val contentRating: ContentRating? = null,
|
val contentRating: ContentRating? = null,
|
||||||
) : HasGetOp {
|
) : HasGetOp {
|
||||||
@@ -102,7 +104,14 @@ class SourceQuery {
|
|||||||
opAnd.eq(id, SourceTable.id)
|
opAnd.eq(id, SourceTable.id)
|
||||||
opAnd.eq(name, SourceTable.name)
|
opAnd.eq(name, SourceTable.name)
|
||||||
opAnd.eq(lang, SourceTable.lang)
|
opAnd.eq(lang, SourceTable.lang)
|
||||||
opAnd.andWhere(isNsfw) { if (it) SourceTable.contentRating greaterEq 3 else SourceTable.contentRating less 3 }
|
opAnd.andWhere(isNsfw) {
|
||||||
|
if (it) {
|
||||||
|
SourceTable.contentRating eq ContentRating.PORNOGRAPHIC.ordinal
|
||||||
|
} else {
|
||||||
|
SourceTable.contentRating neq
|
||||||
|
ContentRating.PORNOGRAPHIC.ordinal
|
||||||
|
}
|
||||||
|
}
|
||||||
opAnd.andWhere(contentRating) { SourceTable.contentRating eq it.getValue() }
|
opAnd.andWhere(contentRating) { SourceTable.contentRating eq it.getValue() }
|
||||||
|
|
||||||
return opAnd.op
|
return opAnd.op
|
||||||
@@ -113,9 +122,9 @@ class SourceQuery {
|
|||||||
val id: LongFilter? = null,
|
val id: LongFilter? = null,
|
||||||
val name: StringFilter? = null,
|
val name: StringFilter? = null,
|
||||||
val lang: StringFilter? = null,
|
val lang: StringFilter? = null,
|
||||||
@GraphQLDeprecated("replace with contentRating == 3", ReplaceWith("contentRating"))
|
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating"))
|
||||||
val isNsfw: BooleanFilter? = null,
|
val isNsfw: BooleanFilter? = null,
|
||||||
// val contentRating: EnumFilter<ContentRating>? = null,
|
val contentRating: ContentRatingFilter? = null,
|
||||||
override val and: List<SourceFilter>? = null,
|
override val and: List<SourceFilter>? = null,
|
||||||
override val or: List<SourceFilter>? = null,
|
override val or: List<SourceFilter>? = null,
|
||||||
override val not: SourceFilter? = null,
|
override val not: SourceFilter? = null,
|
||||||
@@ -125,7 +134,7 @@ class SourceQuery {
|
|||||||
andFilterWithCompareEntity(SourceTable.id, id),
|
andFilterWithCompareEntity(SourceTable.id, id),
|
||||||
andFilterWithCompareString(SourceTable.name, name),
|
andFilterWithCompareString(SourceTable.name, name),
|
||||||
andFilterWithCompareString(SourceTable.lang, lang),
|
andFilterWithCompareString(SourceTable.lang, lang),
|
||||||
// andFilterWithCompareEnum(SourceTable.contentRating, contentRating)
|
andFilterWithCompareEnum(SourceTable.contentRating, contentRating),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.jetbrains.exposed.v1.core.upperCase
|
|||||||
import org.jetbrains.exposed.v1.core.wrap
|
import org.jetbrains.exposed.v1.core.wrap
|
||||||
import org.jetbrains.exposed.v1.jdbc.Query
|
import org.jetbrains.exposed.v1.jdbc.Query
|
||||||
import org.jetbrains.exposed.v1.jdbc.andWhere
|
import org.jetbrains.exposed.v1.jdbc.andWhere
|
||||||
|
import suwayomi.tachidesk.graphql.types.ContentRating
|
||||||
|
|
||||||
class ILikeEscapeOp(
|
class ILikeEscapeOp(
|
||||||
expr1: Expression<*>,
|
expr1: Expression<*>,
|
||||||
@@ -329,23 +330,23 @@ data class DoubleFilter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EnumFilter<T : Enum<T>>(
|
data class ContentRatingFilter(
|
||||||
override val isNull: Boolean? = null,
|
override val isNull: Boolean? = null,
|
||||||
override val equalTo: T? = null,
|
override val equalTo: ContentRating? = null,
|
||||||
override val notEqualTo: T? = null,
|
override val notEqualTo: ContentRating? = null,
|
||||||
override val notEqualToAll: List<T>? = null,
|
override val notEqualToAll: List<ContentRating>? = null,
|
||||||
override val notEqualToAny: List<T>? = null,
|
override val notEqualToAny: List<ContentRating>? = null,
|
||||||
override val distinctFrom: T? = null,
|
override val distinctFrom: ContentRating? = null,
|
||||||
override val distinctFromAll: List<T>? = null,
|
override val distinctFromAll: List<ContentRating>? = null,
|
||||||
override val distinctFromAny: List<T>? = null,
|
override val distinctFromAny: List<ContentRating>? = null,
|
||||||
override val notDistinctFrom: T? = null,
|
override val notDistinctFrom: ContentRating? = null,
|
||||||
override val `in`: List<T>? = null,
|
override val `in`: List<ContentRating>? = null,
|
||||||
override val notIn: List<T>? = null,
|
override val notIn: List<ContentRating>? = null,
|
||||||
override val lessThan: T? = null,
|
override val lessThan: ContentRating? = null,
|
||||||
override val lessThanOrEqualTo: T? = null,
|
override val lessThanOrEqualTo: ContentRating? = null,
|
||||||
override val greaterThan: T? = null,
|
override val greaterThan: ContentRating? = null,
|
||||||
override val greaterThanOrEqualTo: T? = null,
|
override val greaterThanOrEqualTo: ContentRating? = null,
|
||||||
) : ComparableScalarFilter<T>
|
) : ComparableScalarFilter<ContentRating>
|
||||||
|
|
||||||
data class StringFilter(
|
data class StringFilter(
|
||||||
override val isNull: Boolean? = null,
|
override val isNull: Boolean? = null,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import suwayomi.tachidesk.graphql.dataLoaders.DownloadedChapterCountForMangaData
|
|||||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForExtensionStore
|
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForExtensionStore
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader
|
||||||
|
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreForExtension
|
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreForExtension
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader
|
||||||
@@ -81,6 +82,7 @@ class TachideskDataLoaderRegistryFactory {
|
|||||||
ExtensionDataLoader(),
|
ExtensionDataLoader(),
|
||||||
ExtensionForSourceDataLoader(),
|
ExtensionForSourceDataLoader(),
|
||||||
ExtensionForExtensionStore(),
|
ExtensionForExtensionStore(),
|
||||||
|
ExtensionStoreDataLoader(),
|
||||||
ExtensionStoreForExtension(),
|
ExtensionStoreForExtension(),
|
||||||
TrackerDataLoader(),
|
TrackerDataLoader(),
|
||||||
TrackerStatusesDataLoader(),
|
TrackerStatusesDataLoader(),
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ package suwayomi.tachidesk.graphql.types
|
|||||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
import org.jetbrains.exposed.v1.core.ResultRow
|
import org.jetbrains.exposed.v1.core.ResultRow
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.Edge
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.NodeList
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
@@ -36,3 +40,45 @@ class ExtensionStoreType(
|
|||||||
fun extension(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> =
|
fun extension(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> =
|
||||||
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionForExtensionStore", indexUrl)
|
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionForExtensionStore", indexUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ExtensionStoreNodeList(
|
||||||
|
override val nodes: List<ExtensionStoreType>,
|
||||||
|
override val edges: List<ExtensionStoreEdge>,
|
||||||
|
override val pageInfo: PageInfo,
|
||||||
|
override val totalCount: Int,
|
||||||
|
) : NodeList() {
|
||||||
|
data class ExtensionStoreEdge(
|
||||||
|
override val cursor: Cursor,
|
||||||
|
override val node: ExtensionStoreType,
|
||||||
|
) : Edge()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun List<ExtensionStoreType>.toNodeList(): ExtensionStoreNodeList =
|
||||||
|
ExtensionStoreNodeList(
|
||||||
|
nodes = this,
|
||||||
|
edges = getEdges(),
|
||||||
|
pageInfo =
|
||||||
|
PageInfo(
|
||||||
|
hasNextPage = false,
|
||||||
|
hasPreviousPage = false,
|
||||||
|
startCursor = Cursor(0.toString()),
|
||||||
|
endCursor = Cursor(lastIndex.toString()),
|
||||||
|
),
|
||||||
|
totalCount = size,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun List<ExtensionStoreType>.getEdges(): List<ExtensionStoreEdge> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
return listOf(
|
||||||
|
ExtensionStoreEdge(
|
||||||
|
cursor = Cursor("0"),
|
||||||
|
node = first(),
|
||||||
|
),
|
||||||
|
ExtensionStoreEdge(
|
||||||
|
cursor = Cursor(lastIndex.toString()),
|
||||||
|
node = last(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ExtensionType(
|
|||||||
versionCode = row[ExtensionTable.versionCode].toInt(),
|
versionCode = row[ExtensionTable.versionCode].toInt(),
|
||||||
versionCodeLong = row[ExtensionTable.versionCode],
|
versionCodeLong = row[ExtensionTable.versionCode],
|
||||||
lang = row[ExtensionTable.lang],
|
lang = row[ExtensionTable.lang],
|
||||||
isNsfw = row[ExtensionTable.contentRating] == 3,
|
isNsfw = row[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
|
||||||
contentRating = ContentRating.valueOf(row[ExtensionTable.contentRating]),
|
contentRating = ContentRating.valueOf(row[ExtensionTable.contentRating]),
|
||||||
isInstalled = row[ExtensionTable.isInstalled],
|
isInstalled = row[ExtensionTable.isInstalled],
|
||||||
hasUpdate = row[ExtensionTable.hasUpdate],
|
hasUpdate = row[ExtensionTable.hasUpdate],
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SourceType(
|
|||||||
iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
|
iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
|
||||||
supportsLatest = catalogueSource.supportsLatest,
|
supportsLatest = catalogueSource.supportsLatest,
|
||||||
isConfigurable = catalogueSource is ConfigurableSource,
|
isConfigurable = catalogueSource is ConfigurableSource,
|
||||||
isNsfw = row[SourceTable.contentRating] >= 3,
|
isNsfw = row[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
|
||||||
displayName = catalogueSource.toString(),
|
displayName = catalogueSource.toString(),
|
||||||
homeUrl = runCatching { (catalogueSource as? HttpSource)?.getHomeUrl() }.getOrNull(),
|
homeUrl = runCatching { (catalogueSource as? HttpSource)?.getHomeUrl() }.getOrNull(),
|
||||||
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ object UpdateController {
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class made for handling return type in the documentation for [recentChapters],
|
* Class made for handling return type in the documentation for [UpdateController.recentChapters],
|
||||||
* since OpenApi cannot handle runtime generics.
|
* since OpenApi cannot handle runtime generics.
|
||||||
*/
|
*/
|
||||||
private class PagedMangaChapterListDataClass : PaginatedList<MangaChapterDataClass>(emptyList(), false)
|
private class PagedMangaChapterListDataClass : PaginatedList<MangaChapterDataClass>(emptyList(), false)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ object Manga {
|
|||||||
return if (!onlineFetch && mangaEntry[MangaTable.initialized]) {
|
return if (!onlineFetch && mangaEntry[MangaTable.initialized]) {
|
||||||
MangaTable.toDataClass(mangaEntry)
|
MangaTable.toDataClass(mangaEntry)
|
||||||
} else { // initialize manga
|
} else { // initialize manga
|
||||||
fetchManga(mangaId) ?: return MangaTable.toDataClass(mangaEntry)
|
updateMangaAndChapters(mangaId, updateChapters = false)
|
||||||
|
|
||||||
mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
|
mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
|
||||||
|
|
||||||
@@ -152,6 +152,38 @@ object Manga {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateMangaAndChapters(
|
||||||
|
mangaId: Int,
|
||||||
|
updateManga: Boolean = true,
|
||||||
|
updateChapters: Boolean = true,
|
||||||
|
) {
|
||||||
|
mangaInfoMutex.get(mangaId) { Mutex() }.withLock {
|
||||||
|
var mangaEntry =
|
||||||
|
transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
|
||||||
|
val source =
|
||||||
|
getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
|
||||||
|
?: throw NullPointerException("Missing source ${mangaEntry[MangaTable.sourceReference]}")
|
||||||
|
val mangaUpdate =
|
||||||
|
fetchMangaAndChapters(
|
||||||
|
mangaEntry,
|
||||||
|
source,
|
||||||
|
fetchDetails = updateManga,
|
||||||
|
fetchChapters = updateChapters,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (updateManga) {
|
||||||
|
updateMangaDatabase(mangaEntry, source, mangaUpdate.manga)
|
||||||
|
mangaEntry =
|
||||||
|
transaction {
|
||||||
|
MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateChapters) {
|
||||||
|
Chapter.updateChapterListDatabase(mangaEntry, mangaUpdate.chapters, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateMangaDatabase(
|
fun updateMangaDatabase(
|
||||||
mangaEntry: ResultRow,
|
mangaEntry: ResultRow,
|
||||||
source: CatalogueSource,
|
source: CatalogueSource,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
|
|||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
|
||||||
|
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
||||||
@@ -52,7 +53,7 @@ object Source {
|
|||||||
iconUrl = proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
|
iconUrl = proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
|
||||||
supportsLatest = catalogueSource.supportsLatest,
|
supportsLatest = catalogueSource.supportsLatest,
|
||||||
isConfigurable = catalogueSource is ConfigurableSource,
|
isConfigurable = catalogueSource is ConfigurableSource,
|
||||||
isNsfw = it[SourceTable.contentRating] >= 3,
|
isNsfw = it[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
|
||||||
displayName = catalogueSource.toString(),
|
displayName = catalogueSource.toString(),
|
||||||
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
||||||
)
|
)
|
||||||
@@ -73,7 +74,7 @@ object Source {
|
|||||||
iconUrl = proxyExtensionIconUrl(extension[ExtensionTable.pkgName]),
|
iconUrl = proxyExtensionIconUrl(extension[ExtensionTable.pkgName]),
|
||||||
supportsLatest = catalogueSource.supportsLatest,
|
supportsLatest = catalogueSource.supportsLatest,
|
||||||
isConfigurable = catalogueSource is ConfigurableSource,
|
isConfigurable = catalogueSource is ConfigurableSource,
|
||||||
isNsfw = source[SourceTable.contentRating] >= 3,
|
isNsfw = source[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
|
||||||
displayName = catalogueSource.toString(),
|
displayName = catalogueSource.toString(),
|
||||||
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.jetbrains.exposed.v1.jdbc.select
|
|||||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import org.jetbrains.exposed.v1.jdbc.update
|
import org.jetbrains.exposed.v1.jdbc.update
|
||||||
|
import suwayomi.tachidesk.graphql.types.ContentRating
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools
|
import suwayomi.tachidesk.manga.impl.util.PackageTools
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
|
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
|
||||||
@@ -197,7 +198,13 @@ object Extension {
|
|||||||
it[versionName] = packageInfo.versionName
|
it[versionName] = packageInfo.versionName
|
||||||
it[versionCode] = packageInfo.versionCode.toLong()
|
it[versionCode] = packageInfo.versionCode.toLong()
|
||||||
it[lang] = extensionLang
|
it[lang] = extensionLang
|
||||||
it[contentRating] = if (isNsfw) 3 else 0 // todo will change
|
// todo will change
|
||||||
|
it[contentRating] =
|
||||||
|
if (isNsfw) {
|
||||||
|
ContentRating.PORNOGRAPHIC.ordinal
|
||||||
|
} else {
|
||||||
|
ContentRating.SAFE.ordinal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +229,7 @@ object Extension {
|
|||||||
it[name] = httpSource.name
|
it[name] = httpSource.name
|
||||||
it[lang] = httpSource.lang
|
it[lang] = httpSource.lang
|
||||||
it[extension] = extensionId
|
it[extension] = extensionId
|
||||||
it[contentRating] = if (isNsfw) 3 else 0
|
it[contentRating] = if (isNsfw) ContentRating.PORNOGRAPHIC.ordinal else ContentRating.SAFE.ordinal
|
||||||
}
|
}
|
||||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,44 +49,41 @@ object ExtensionStoreService {
|
|||||||
): ExtensionStore {
|
): ExtensionStore {
|
||||||
var updatedIndexUrl = indexUrl
|
var updatedIndexUrl = indexUrl
|
||||||
return try {
|
return try {
|
||||||
val response = network.client.newCall(GET(indexUrl)).awaitSuccess()
|
network.client.newCall(GET(indexUrl)).awaitSuccess().body.source().use { source ->
|
||||||
response.body
|
try {
|
||||||
.source()
|
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray())
|
||||||
.use { source ->
|
} catch (e: IllegalArgumentException) {
|
||||||
|
logger.debug { "Failed to decode as protobuf, trying JSON" }
|
||||||
try {
|
try {
|
||||||
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray())
|
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
logger.debug { "Failed to decode as protobuf, trying JSON" }
|
|
||||||
if (forceV2) throw e
|
if (forceV2) throw e
|
||||||
try {
|
logger.debug { "Failed to decode as NetworkExtensionStore, trying LegacyExtensionRepo" }
|
||||||
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek())
|
val legacyIndex =
|
||||||
} catch (_: IllegalArgumentException) {
|
try {
|
||||||
logger.debug { "Failed to decode as NetworkExtensionStore, trying LegacyExtensionRepo" }
|
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(source.peek())
|
||||||
val legacyIndex =
|
} catch (e: IllegalArgumentException) {
|
||||||
try {
|
if (!indexUrl.endsWith("/index.min.json")) {
|
||||||
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(source.peek())
|
throw e
|
||||||
} catch (e: IllegalArgumentException) {
|
}
|
||||||
if (!indexUrl.endsWith("/index.min.json")) {
|
logger.debug { "Checking for new repo.json format" }
|
||||||
throw e
|
updatedIndexUrl = indexUrl.replace("/index.min.json", "/repo.json")
|
||||||
}
|
network.client.newCall(GET(updatedIndexUrl)).awaitSuccess().body.source().use {
|
||||||
logger.debug { "Retrying with /index.min.json" }
|
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(it)
|
||||||
updatedIndexUrl = indexUrl.replace("/index.min.json", "/repo.json")
|
|
||||||
network.client.newCall(GET(updatedIndexUrl)).awaitSuccess().body.source().use {
|
|
||||||
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (legacyIndex.indexV2 != null) {
|
|
||||||
return fetch(legacyIndex.indexV2, forceV2 = true)
|
|
||||||
} else {
|
|
||||||
legacyIndex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (legacyIndex.indexV2 != null) {
|
||||||
|
return fetch(legacyIndex.indexV2, forceV2 = true)
|
||||||
|
} else {
|
||||||
|
legacyIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.toExtensionStore(updatedIndexUrl)
|
}.toExtensionStore(updatedIndexUrl)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
logger.debug(e) { "Failed to fetch extension store '$indexUrl'" }
|
logger.error(e) { "Failed to fetch extension store '$indexUrl'" }
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,24 +124,27 @@ object ExtensionStoreService {
|
|||||||
transaction {
|
transaction {
|
||||||
ExtensionStoreTable.selectAll().toList()
|
ExtensionStoreTable.selectAll().toList()
|
||||||
}
|
}
|
||||||
return stores.mapNotNull { storeRow ->
|
var needsPrefUpdate = false
|
||||||
|
val updateStores = stores.mapNotNull { storeRow ->
|
||||||
val oldIndexUrl = storeRow[ExtensionStoreTable.indexUrl]
|
val oldIndexUrl = storeRow[ExtensionStoreTable.indexUrl]
|
||||||
val oldName = storeRow[ExtensionStoreTable.name]
|
val oldName = storeRow[ExtensionStoreTable.name]
|
||||||
try {
|
try {
|
||||||
val store = fetch(oldIndexUrl)
|
val store = fetch(oldIndexUrl)
|
||||||
upsert(store)
|
|
||||||
if (store.indexUrl != oldIndexUrl) {
|
if (store.indexUrl != oldIndexUrl) {
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl eq oldIndexUrl }
|
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl eq oldIndexUrl }
|
||||||
}
|
}
|
||||||
syncDbToPrefs()
|
needsPrefUpdate = true
|
||||||
}
|
}
|
||||||
|
upsert(store)
|
||||||
store
|
store
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn(e) { "Failed to fetch extension store '$oldName ($oldIndexUrl)'" }
|
logger.warn(e) { "Failed to fetch extension store '$oldName ($oldIndexUrl)'" }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (needsPrefUpdate) syncDbToPrefs()
|
||||||
|
return updateStores
|
||||||
}
|
}
|
||||||
|
|
||||||
fun syncDbToPrefs() {
|
fun syncDbToPrefs() {
|
||||||
@@ -178,22 +178,33 @@ object ExtensionStoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val toAdd = prefUrls - dbStores.keys
|
val toAdd = prefUrls - dbStores.keys
|
||||||
|
val toRemove = (dbStores.keys - prefUrls).toMutableSet()
|
||||||
|
var needsPrefUpdate = toRemove.isNotEmpty()
|
||||||
|
|
||||||
toAdd.forEach { url ->
|
toAdd.forEach { url ->
|
||||||
try {
|
try {
|
||||||
val store = fetch(url)
|
val store = fetch(url)
|
||||||
|
if (store.indexUrl != url) {
|
||||||
|
transaction {
|
||||||
|
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl eq url }
|
||||||
|
}
|
||||||
|
needsPrefUpdate = true
|
||||||
|
toRemove -= store.indexUrl
|
||||||
|
}
|
||||||
upsert(store)
|
upsert(store)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn(e) { "Failed to sync preference store '$url' to database" }
|
logger.warn(e) { "Failed to sync preference store '$url' to database" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val toRemove = dbStores.keys - prefUrls
|
|
||||||
if (toRemove.isNotEmpty()) {
|
if (toRemove.isNotEmpty()) {
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl inList toRemove.toList() }
|
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl inList toRemove.toList() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (needsPrefUpdate) {
|
||||||
|
syncDbToPrefs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getExtensions(store: ExtensionStore): List<ExtensionInfo> {
|
suspend fun getExtensions(store: ExtensionStore): List<ExtensionInfo> {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.jetbrains.exposed.v1.jdbc.statements.toExecutable
|
|||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
import org.jetbrains.exposed.v1.jdbc.update
|
import org.jetbrains.exposed.v1.jdbc.update
|
||||||
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
|
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
|
||||||
|
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
|
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
@@ -80,7 +81,7 @@ object ExtensionsList {
|
|||||||
versionName = it[ExtensionTable.versionName],
|
versionName = it[ExtensionTable.versionName],
|
||||||
versionCode = it[ExtensionTable.versionCode].toInt(),
|
versionCode = it[ExtensionTable.versionCode].toInt(),
|
||||||
lang = it[ExtensionTable.lang],
|
lang = it[ExtensionTable.lang],
|
||||||
isNsfw = it[ExtensionTable.contentRating] == 3,
|
isNsfw = it[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
|
||||||
installed = it[ExtensionTable.isInstalled],
|
installed = it[ExtensionTable.isInstalled],
|
||||||
hasUpdate = it[ExtensionTable.hasUpdate],
|
hasUpdate = it[ExtensionTable.hasUpdate],
|
||||||
obsolete = it[ExtensionTable.isObsolete],
|
obsolete = it[ExtensionTable.isObsolete],
|
||||||
|
|||||||
@@ -121,6 +121,14 @@ fun NetworkExtensionStore.toExtensionInfos(store: ExtensionStore): List<Extensio
|
|||||||
name = source.name,
|
name = source.name,
|
||||||
lang = source.language,
|
lang = source.language,
|
||||||
homeUrl = source.homeUrl,
|
homeUrl = source.homeUrl,
|
||||||
|
message = source.message,
|
||||||
|
contentRating =
|
||||||
|
when (source.contentRating) {
|
||||||
|
NetworkExtensionStore.ContentRating.SAFE -> ContentRating.SAFE
|
||||||
|
NetworkExtensionStore.ContentRating.SUGGESTIVE -> ContentRating.SUGGESTIVE
|
||||||
|
NetworkExtensionStore.ContentRating.EROTICA -> ContentRating.EROTICA
|
||||||
|
NetworkExtensionStore.ContentRating.PORNOGRAPHIC -> ContentRating.PORNOGRAPHIC
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ fun NetworkLegacyExtension.toExtensionInfo(
|
|||||||
name = name,
|
name = name,
|
||||||
lang = lang,
|
lang = lang,
|
||||||
homeUrl = "",
|
homeUrl = "",
|
||||||
|
message = null,
|
||||||
|
contentRating = if (nsfw == 1) ContentRating.PORNOGRAPHIC else ContentRating.SAFE,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -67,6 +69,8 @@ fun NetworkLegacyExtension.toExtensionInfo(
|
|||||||
name = source.name,
|
name = source.name,
|
||||||
lang = source.lang,
|
lang = source.lang,
|
||||||
homeUrl = source.baseUrl,
|
homeUrl = source.baseUrl,
|
||||||
|
message = null,
|
||||||
|
contentRating = if (nsfw == 1) ContentRating.PORNOGRAPHIC else ContentRating.SAFE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import kotlinx.coroutines.sync.withPermit
|
|||||||
import suwayomi.tachidesk.global.impl.sync.SyncManager
|
import suwayomi.tachidesk.global.impl.sync.SyncManager
|
||||||
import suwayomi.tachidesk.manga.impl.Category
|
import suwayomi.tachidesk.manga.impl.Category
|
||||||
import suwayomi.tachidesk.manga.impl.CategoryManga
|
import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
|
||||||
import suwayomi.tachidesk.manga.impl.Manga
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
|
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
|
||||||
@@ -311,10 +310,10 @@ class Updater : IUpdater {
|
|||||||
tracker[job.manga.id] =
|
tracker[job.manga.id] =
|
||||||
try {
|
try {
|
||||||
logger.info { "Updating ${job.manga}" }
|
logger.info { "Updating ${job.manga}" }
|
||||||
if (serverConfig.updateMangas.value || !job.manga.initialized) {
|
Manga.updateMangaAndChapters(
|
||||||
Manga.getManga(job.manga.id, true)
|
job.manga.id,
|
||||||
}
|
updateManga = serverConfig.updateMangas.value || !job.manga.initialized,
|
||||||
Chapter.getChapterList(job.manga.id, true)
|
)
|
||||||
job.copy(status = JobStatus.COMPLETE)
|
job.copy(status = JobStatus.COMPLETE)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error(e) { "Error while updating ${job.manga}" }
|
logger.error(e) { "Error while updating ${job.manga}" }
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ object ImageResponse {
|
|||||||
/**
|
/**
|
||||||
* Get a cached image response
|
* Get a cached image response
|
||||||
*
|
*
|
||||||
* Note: The caller should also call [clearCachedImage] when appropriate
|
* Note: The caller should also call [ImageResponse.clearCachedImage] when appropriate
|
||||||
*
|
*
|
||||||
* @param cacheSavePath where to save the cached image. Caller should decide to use perma cache or temp cache (OS temp dir)
|
* @param cacheSavePath where to save the cached image. Caller should decide to use perma cache or temp cache (OS temp dir)
|
||||||
* @param fileName what the saved cache file should be named
|
* @param fileName what the saved cache file should be named
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ data class ExtensionSource(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val lang: String,
|
val lang: String,
|
||||||
val homeUrl: String,
|
val homeUrl: String,
|
||||||
|
val message: String?,
|
||||||
|
val contentRating: ContentRating,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ContentRating {
|
enum class ContentRating {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package suwayomi.tachidesk.opds.impl
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.jetbrains.exposed.v1.core.SortOrder
|
import org.jetbrains.exposed.v1.core.SortOrder
|
||||||
import suwayomi.tachidesk.i18n.MR
|
import suwayomi.tachidesk.i18n.MR
|
||||||
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.opds.constants.OpdsConstants
|
import suwayomi.tachidesk.opds.constants.OpdsConstants
|
||||||
@@ -17,7 +18,6 @@ import suwayomi.tachidesk.opds.repository.ChapterRepository
|
|||||||
import suwayomi.tachidesk.opds.repository.MangaRepository
|
import suwayomi.tachidesk.opds.repository.MangaRepository
|
||||||
import suwayomi.tachidesk.opds.repository.NavigationRepository
|
import suwayomi.tachidesk.opds.repository.NavigationRepository
|
||||||
import suwayomi.tachidesk.opds.util.OpdsDateUtil
|
import suwayomi.tachidesk.opds.util.OpdsDateUtil
|
||||||
import suwayomi.tachidesk.opds.util.OpdsStringUtil
|
|
||||||
import suwayomi.tachidesk.opds.util.OpdsXmlUtil
|
import suwayomi.tachidesk.opds.util.OpdsXmlUtil
|
||||||
import suwayomi.tachidesk.server.serverConfig
|
import suwayomi.tachidesk.server.serverConfig
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -647,8 +647,7 @@ object OpdsFeedBuilder {
|
|||||||
// If no chapters are found in the database, attempt to fetch them from the source.
|
// If no chapters are found in the database, attempt to fetch them from the source.
|
||||||
if (chapterEntries.isEmpty() && totalChapters == 0L) {
|
if (chapterEntries.isEmpty() && totalChapters == 0L) {
|
||||||
try {
|
try {
|
||||||
suwayomi.tachidesk.manga.impl.Chapter
|
Manga.updateMangaAndChapters(mangaId, updateManga = false)
|
||||||
.fetchChapterList(mangaId)
|
|
||||||
|
|
||||||
// Re-query after fetching.
|
// Re-query after fetching.
|
||||||
val (refetchedChapters, refetchedTotal) =
|
val (refetchedChapters, refetchedTotal) =
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package suwayomi.tachidesk.server.database.migration
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import de.neonew.exposed.migrations.helpers.SQLMigration
|
import de.neonew.exposed.migrations.helpers.SQLMigration
|
||||||
|
import suwayomi.tachidesk.graphql.types.ContentRating
|
||||||
import suwayomi.tachidesk.graphql.types.DatabaseType
|
import suwayomi.tachidesk.graphql.types.DatabaseType
|
||||||
import suwayomi.tachidesk.server.database.migration.helpers.MAYBE_TYPE_PREFIX
|
import suwayomi.tachidesk.server.database.migration.helpers.MAYBE_TYPE_PREFIX
|
||||||
import suwayomi.tachidesk.server.database.migration.helpers.UNLIMITED_TEXT
|
import suwayomi.tachidesk.server.database.migration.helpers.UNLIMITED_TEXT
|
||||||
@@ -43,11 +44,11 @@ class M0057_AddNewExtensionApiFields : SQLMigration() {
|
|||||||
}
|
}
|
||||||
ALTER TABLE EXTENSION ADD COLUMN apk_url VARCHAR(2048);
|
ALTER TABLE EXTENSION ADD COLUMN apk_url VARCHAR(2048);
|
||||||
ALTER TABLE EXTENSION ADD COLUMN content_rating INTEGER DEFAULT 0;
|
ALTER TABLE EXTENSION ADD COLUMN content_rating INTEGER DEFAULT 0;
|
||||||
UPDATE EXTENSION SET content_rating = 3 WHERE is_nsfw = TRUE;
|
UPDATE EXTENSION SET content_rating = ${ContentRating.PORNOGRAPHIC.ordinal} WHERE is_nsfw = TRUE;
|
||||||
ALTER TABLE EXTENSION DROP COLUMN is_nsfw;
|
ALTER TABLE EXTENSION DROP COLUMN is_nsfw;
|
||||||
ALTER TABLE SOURCE ADD COLUMN message $UNLIMITED_TEXT;
|
ALTER TABLE SOURCE ADD COLUMN message $UNLIMITED_TEXT;
|
||||||
ALTER TABLE SOURCE ADD COLUMN content_rating INTEGER DEFAULT 0;
|
ALTER TABLE SOURCE ADD COLUMN content_rating INTEGER DEFAULT 0;
|
||||||
UPDATE SOURCE SET content_rating = 3 WHERE is_nsfw = TRUE;
|
UPDATE SOURCE SET content_rating = ${ContentRating.PORNOGRAPHIC.ordinal} WHERE is_nsfw = TRUE;
|
||||||
ALTER TABLE SOURCE DROP COLUMN is_nsfw;
|
ALTER TABLE SOURCE DROP COLUMN is_nsfw;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.junit.jupiter.api.AfterAll
|
|||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import rx.Observable
|
|
||||||
import suwayomi.tachidesk.manga.impl.Search.FilterChange
|
import suwayomi.tachidesk.manga.impl.Search.FilterChange
|
||||||
import suwayomi.tachidesk.manga.impl.Search.FilterObject
|
import suwayomi.tachidesk.manga.impl.Search.FilterObject
|
||||||
import suwayomi.tachidesk.manga.impl.Search.SerializableGroup
|
import suwayomi.tachidesk.manga.impl.Search.SerializableGroup
|
||||||
@@ -41,12 +40,11 @@ class SearchTest : ApplicationTest() {
|
|||||||
) : StubSource(id) {
|
) : StubSource(id) {
|
||||||
var mangas: List<SManga> = emptyList()
|
var mangas: List<SManga> = emptyList()
|
||||||
|
|
||||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
|
override suspend fun getSearchManga(
|
||||||
override fun fetchSearchManga(
|
|
||||||
page: Int,
|
page: Int,
|
||||||
query: String,
|
query: String,
|
||||||
filters: FilterList,
|
filters: FilterList,
|
||||||
): Observable<MangasPage> = Observable.just(MangasPage(mangas, false))
|
): MangasPage = MangasPage(mangas, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sourceId = 1L
|
private val sourceId = 1L
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ fun createChapters(
|
|||||||
this[ChapterTable.sourceOrder] = it
|
this[ChapterTable.sourceOrder] = it
|
||||||
this[ChapterTable.isRead] = read
|
this[ChapterTable.isRead] = read
|
||||||
this[ChapterTable.manga] = mangaId
|
this[ChapterTable.manga] = mangaId
|
||||||
|
this[ChapterTable.memo] = "{}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user