This commit is contained in:
Syer10
2025-12-06 12:25:43 -05:00
parent b5664f34ad
commit 472ecb9431
36 changed files with 523 additions and 142 deletions

View File

@@ -242,13 +242,14 @@ class JavaSharedPreferences(
} }
notify(it.key) notify(it.key)
} }
is Action.Remove -> { is Action.Remove -> {
preferences.remove(it.key) preferences.remove(it.key)
/** /*
* Set<String> are stored like Set<String> are stored like
* key.0 = value1 key.0 = value1
* key.1 = value2 key.1 = value2
* key.size = 2 key.size = 2
*/ */
preferences.keys.forEach { key -> preferences.keys.forEach { key ->
if (key.startsWith(it.key + ".")) { if (key.startsWith(it.key + ".")) {
@@ -258,7 +259,10 @@ class JavaSharedPreferences(
notify(it.key) notify(it.key)
} }
Action.Clear -> preferences.clear()
Action.Clear -> {
preferences.clear()
}
} }
} }
} }

View File

@@ -72,8 +72,12 @@ internal class RateLimitInterceptor(
val request = chain.request() val request = chain.request()
when (host) { when (host) {
null, request.url.host -> {} // need rate limit // need rate limit
else -> return chain.proceed(request) null, request.url.host -> {}
else -> {
return chain.proceed(request)
}
} }
try { try {

View File

@@ -110,6 +110,7 @@ class LocalSource(
mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }) mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name })
} }
} }
is OrderBy.Latest -> { is OrderBy.Latest -> {
mangaDirs = mangaDirs =
if (filter.state!!.ascending) { if (filter.state!!.ascending) {
@@ -246,6 +247,7 @@ class LocalSource(
} }
} }
} }
is Format.Rar -> { is Format.Rar -> {
JunrarArchive(chapter).use { rar -> JunrarArchive(chapter).use { rar ->
rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
@@ -255,6 +257,7 @@ class LocalSource(
} }
} }
} }
else -> {} else -> {}
} }
} }
@@ -337,6 +340,7 @@ class LocalSource(
) )
} }
} }
is Format.Zip -> { is Format.Zip -> {
val loader = ZipPageLoader(format.file) val loader = ZipPageLoader(format.file)
val pages = loader.getPages() val pages = loader.getPages()
@@ -344,6 +348,7 @@ class LocalSource(
pages pages
} }
is Format.Rar -> { is Format.Rar -> {
val loader = RarPageLoader(format.file) val loader = RarPageLoader(format.file)
val pages = loader.getPages() val pages = loader.getPages()
@@ -351,6 +356,7 @@ class LocalSource(
pages pages
} }
is Format.Epub -> { is Format.Epub -> {
val loader = EpubPageLoader(format.file) val loader = EpubPageLoader(format.file)
val pages = loader.getPages() val pages = loader.getPages()
@@ -390,6 +396,7 @@ class LocalSource(
entry?.let { coverManager.update(manga, it.inputStream()) } entry?.let { coverManager.update(manga, it.inputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile.builder().setFile(format.file).get().use { zip -> ZipFile.builder().setFile(format.file).get().use { zip ->
val entry = val entry =
@@ -401,6 +408,7 @@ class LocalSource(
entry?.let { coverManager.update(manga, zip.getInputStream(it)) } entry?.let { coverManager.update(manga, zip.getInputStream(it)) }
} }
} }
is Format.Rar -> { is Format.Rar -> {
JunrarArchive(format.file).use { archive -> JunrarArchive(format.file).use { archive ->
val entry = val entry =
@@ -411,6 +419,7 @@ class LocalSource(
entry?.let { coverManager.update(manga, archive.getInputStream(it)) } entry?.let { coverManager.update(manga, archive.getInputStream(it)) }
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file).use { epub -> EpubFile(format.file).use { epub ->
val entry = val entry =

View File

@@ -58,6 +58,7 @@ object ChapterRecognition {
numberMatch.none() -> { numberMatch.none() -> {
return chapterNumber ?: -1.0 return chapterNumber ?: -1.0
} }
numberMatch.count() > 1 -> { numberMatch.count() > 1 -> {
// Remove unwanted tags. // Remove unwanted tags.
unwanted.replace(cleanChapterName, "").let { name -> unwanted.replace(cleanChapterName, "").let { name ->

View File

@@ -367,37 +367,127 @@ class KcefWebView {
): KeyEvent? { ): KeyEvent? {
val code = val code =
when (char.uppercaseChar()) { when (char.uppercaseChar()) {
in 'A'..'Z', in '0'..'9' -> char.uppercaseChar().code in 'A'..'Z', in '0'..'9' -> {
'&' -> KeyEvent.VK_AMPERSAND char.uppercaseChar().code
'*' -> KeyEvent.VK_ASTERISK }
'@' -> KeyEvent.VK_AT
'\\' -> KeyEvent.VK_BACK_SLASH '&' -> {
'{' -> KeyEvent.VK_BRACELEFT KeyEvent.VK_AMPERSAND
'}' -> KeyEvent.VK_BRACERIGHT }
'^' -> KeyEvent.VK_CIRCUMFLEX
']' -> KeyEvent.VK_CLOSE_BRACKET '*' -> {
':' -> KeyEvent.VK_COLON KeyEvent.VK_ASTERISK
',' -> KeyEvent.VK_COMMA }
'$' -> KeyEvent.VK_DOLLAR
'=' -> KeyEvent.VK_EQUALS '@' -> {
'€' -> KeyEvent.VK_EURO_SIGN KeyEvent.VK_AT
'!' -> KeyEvent.VK_EXCLAMATION_MARK }
'>' -> KeyEvent.VK_GREATER
'(' -> KeyEvent.VK_LEFT_PARENTHESIS '\\' -> {
'<' -> KeyEvent.VK_LESS KeyEvent.VK_BACK_SLASH
'-' -> KeyEvent.VK_MINUS }
'#' -> KeyEvent.VK_NUMBER_SIGN
'[' -> KeyEvent.VK_OPEN_BRACKET '{' -> {
'.' -> KeyEvent.VK_PERIOD KeyEvent.VK_BRACELEFT
'+' -> KeyEvent.VK_PLUS }
'\'' -> KeyEvent.VK_QUOTE
'"' -> KeyEvent.VK_QUOTEDBL '}' -> {
')' -> KeyEvent.VK_RIGHT_PARENTHESIS KeyEvent.VK_BRACERIGHT
';' -> KeyEvent.VK_SEMICOLON }
'/' -> KeyEvent.VK_SLASH
' ' -> KeyEvent.VK_SPACE '^' -> {
'_' -> KeyEvent.VK_UNDERSCORE KeyEvent.VK_CIRCUMFLEX
else -> }
']' -> {
KeyEvent.VK_CLOSE_BRACKET
}
':' -> {
KeyEvent.VK_COLON
}
',' -> {
KeyEvent.VK_COMMA
}
'$' -> {
KeyEvent.VK_DOLLAR
}
'=' -> {
KeyEvent.VK_EQUALS
}
'€' -> {
KeyEvent.VK_EURO_SIGN
}
'!' -> {
KeyEvent.VK_EXCLAMATION_MARK
}
'>' -> {
KeyEvent.VK_GREATER
}
'(' -> {
KeyEvent.VK_LEFT_PARENTHESIS
}
'<' -> {
KeyEvent.VK_LESS
}
'-' -> {
KeyEvent.VK_MINUS
}
'#' -> {
KeyEvent.VK_NUMBER_SIGN
}
'[' -> {
KeyEvent.VK_OPEN_BRACKET
}
'.' -> {
KeyEvent.VK_PERIOD
}
'+' -> {
KeyEvent.VK_PLUS
}
'\'' -> {
KeyEvent.VK_QUOTE
}
'"' -> {
KeyEvent.VK_QUOTEDBL
}
')' -> {
KeyEvent.VK_RIGHT_PARENTHESIS
}
';' -> {
KeyEvent.VK_SEMICOLON
}
'/' -> {
KeyEvent.VK_SLASH
}
' ' -> {
KeyEvent.VK_SPACE
}
'_' -> {
KeyEvent.VK_UNDERSCORE
}
else -> {
when (strKey) { when (strKey) {
"Alt" -> KeyEvent.VK_ALT "Alt" -> KeyEvent.VK_ALT
"Backspace" -> KeyEvent.VK_BACK_SPACE "Backspace" -> KeyEvent.VK_BACK_SPACE
@@ -435,6 +525,7 @@ class KcefWebView {
"ArrowUp" -> KeyEvent.VK_UP "ArrowUp" -> KeyEvent.VK_UP
else -> KeyEvent.VK_UNDEFINED else -> KeyEvent.VK_UNDEFINED
} }
}
} }
if (id == KeyEvent.KEY_TYPED) { if (id == KeyEvent.KEY_TYPED) {
if (char == KeyEvent.CHAR_UNDEFINED && code != KeyEvent.VK_ENTER) return null if (char == KeyEvent.CHAR_UNDEFINED && code != KeyEvent.VK_ENTER) return null

View File

@@ -104,18 +104,23 @@ object WebView : Websocket<String>() {
dr.resize(event.width, event.height) dr.resize(event.width, event.height)
logger.debug { "Loading URL $url" } logger.debug { "Loading URL $url" }
} }
is ResizeMessage -> { is ResizeMessage -> {
dr.resize(event.width, event.height) dr.resize(event.width, event.height)
} }
is JsEventMessage -> { is JsEventMessage -> {
dr.event(event) dr.event(event)
} }
is JsPasteMessage -> { is JsPasteMessage -> {
dr.paste(event.data) dr.paste(event.data)
} }
is JsCopyMessage -> { is JsCopyMessage -> {
dr.copy() dr.copy()
} }
is JsPingMessage -> { is JsPingMessage -> {
notifyAllClients("{\"type\":\"pong\"}") notifyAllClients("{\"type\":\"pong\"}")
} }

View File

@@ -135,9 +135,11 @@ class SourceMutation {
filters = updateFilterList(source, filters), filters = updateFilterList(source, filters),
) )
} }
FetchSourceMangaType.POPULAR -> { FetchSourceMangaType.POPULAR -> {
source.getPopularManga(page) source.getPopularManga(page)
} }
FetchSourceMangaType.LATEST -> { FetchSourceMangaType.LATEST -> {
if (!source.supportsLatest) throw Exception("Source does not support latest") if (!source.supportsLatest) throw Exception("Source does not support latest")
source.getLatestUpdates(page) source.getLatestUpdates(page)

View File

@@ -61,8 +61,14 @@ class TrackQuery {
cursor: Cursor, cursor: Cursor,
): Boolean = ): Boolean =
when (this) { when (this) {
ID -> tracker.id > cursor.value.toInt() ID -> {
NAME -> tracker.name > cursor.value tracker.id > cursor.value.toInt()
}
NAME -> {
tracker.name > cursor.value
}
IS_LOGGED_IN -> { IS_LOGGED_IN -> {
val value = cursor.value.substringAfter('-').toBooleanStrict() val value = cursor.value.substringAfter('-').toBooleanStrict()
!value || tracker.isLoggedIn !value || tracker.isLoggedIn
@@ -74,8 +80,14 @@ class TrackQuery {
cursor: Cursor, cursor: Cursor,
): Boolean = ): Boolean =
when (this) { when (this) {
ID -> tracker.id < cursor.value.toInt() ID -> {
NAME -> tracker.name < cursor.value tracker.id < cursor.value.toInt()
}
NAME -> {
tracker.name < cursor.value
}
IS_LOGGED_IN -> { IS_LOGGED_IN -> {
val value = cursor.value.substringAfter('-').toBooleanStrict() val value = cursor.value.substringAfter('-').toBooleanStrict()
value || !tracker.isLoggedIn value || !tracker.isLoggedIn
@@ -159,18 +171,21 @@ class TrackQuery {
res = res =
when (orderType) { when (orderType) {
SortOrder.DESC, SortOrder.DESC_NULLS_FIRST, SortOrder.DESC_NULLS_LAST -> SortOrder.DESC, SortOrder.DESC_NULLS_FIRST, SortOrder.DESC_NULLS_LAST -> {
when (orderBy) { when (orderBy) {
TrackerOrderBy.ID -> res.sortedByDescending { it.id } TrackerOrderBy.ID -> res.sortedByDescending { it.id }
TrackerOrderBy.NAME -> res.sortedByDescending { it.name } TrackerOrderBy.NAME -> res.sortedByDescending { it.name }
TrackerOrderBy.IS_LOGGED_IN -> res.sortedByDescending { it.isLoggedIn } TrackerOrderBy.IS_LOGGED_IN -> res.sortedByDescending { it.isLoggedIn }
} }
SortOrder.ASC, SortOrder.ASC_NULLS_FIRST, SortOrder.ASC_NULLS_LAST -> }
SortOrder.ASC, SortOrder.ASC_NULLS_FIRST, SortOrder.ASC_NULLS_LAST -> {
when (orderBy) { when (orderBy) {
TrackerOrderBy.ID -> res.sortedBy { it.id } TrackerOrderBy.ID -> res.sortedBy { it.id }
TrackerOrderBy.NAME -> res.sortedBy { it.name } TrackerOrderBy.NAME -> res.sortedBy { it.name }
TrackerOrderBy.IS_LOGGED_IN -> res.sortedBy { it.isLoggedIn } TrackerOrderBy.IS_LOGGED_IN -> res.sortedBy { it.isLoggedIn }
} }
}
} }
} }
} }

View File

@@ -61,6 +61,7 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
is GraphQLRequest -> { is GraphQLRequest -> {
request.copy(variables = request.variables?.modifyFiles(mapItems)) request.copy(variables = request.variables?.modifyFiles(mapItems))
} }
is GraphQLBatchRequest -> { is GraphQLBatchRequest -> {
request.copy( request.copy(
requests = requests =

View File

@@ -63,10 +63,16 @@ class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
override fun willGenerateGraphQLType(type: KType): GraphQLType? = override fun willGenerateGraphQLType(type: KType): GraphQLType? =
when (type.classifier as? KClass<*>) { when (type.classifier as? KClass<*>) {
Long::class -> GraphQLLongAsString // encode to string for JS // encode to string for JS
Duration::class -> GraphQLDurationAsString // encode Duration as ISO-8601 string Long::class -> GraphQLLongAsString
// encode Duration as ISO-8601 string
Duration::class -> GraphQLDurationAsString
Cursor::class -> GraphQLCursor Cursor::class -> GraphQLCursor
UploadedFile::class -> GraphQLUpload UploadedFile::class -> GraphQLUpload
else -> super.willGenerateGraphQLType(type) else -> super.willGenerateGraphQLType(type)
} }
} }

View File

@@ -25,7 +25,9 @@ private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
private fun toStringImpl(input: Any): String = private fun toStringImpl(input: Any): String =
when (input) { when (input) {
is Duration -> input.toIsoString() is Duration -> input.toIsoString()
is String -> Duration.parse(input).toIsoString() is String -> Duration.parse(input).toIsoString()
else -> throw CoercingSerializeException( else -> throw CoercingSerializeException(
"Expected a Duration or String but was ${CoercingUtil.typeName(input)}", "Expected a Duration or String but was ${CoercingUtil.typeName(input)}",
) )

View File

@@ -31,46 +31,59 @@ data class BackupRestoreStatus(
fun ProtoBackupImport.BackupRestoreState.toStatus(): BackupRestoreStatus = fun ProtoBackupImport.BackupRestoreState.toStatus(): BackupRestoreStatus =
when (this) { when (this) {
ProtoBackupImport.BackupRestoreState.Idle -> ProtoBackupImport.BackupRestoreState.Idle -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.IDLE, state = BackupRestoreState.IDLE,
totalManga = 0, totalManga = 0,
mangaProgress = 0, mangaProgress = 0,
) )
is ProtoBackupImport.BackupRestoreState.Success -> }
is ProtoBackupImport.BackupRestoreState.Success -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.SUCCESS, state = BackupRestoreState.SUCCESS,
totalManga = 0, totalManga = 0,
mangaProgress = 0, mangaProgress = 0,
) )
is ProtoBackupImport.BackupRestoreState.Failure -> }
is ProtoBackupImport.BackupRestoreState.Failure -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.FAILURE, state = BackupRestoreState.FAILURE,
totalManga = 0, totalManga = 0,
mangaProgress = 0, mangaProgress = 0,
) )
is ProtoBackupImport.BackupRestoreState.RestoringCategories -> }
is ProtoBackupImport.BackupRestoreState.RestoringCategories -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.RESTORING_CATEGORIES, state = BackupRestoreState.RESTORING_CATEGORIES,
totalManga = totalManga, totalManga = totalManga,
mangaProgress = current, mangaProgress = current,
) )
is ProtoBackupImport.BackupRestoreState.RestoringMeta -> }
is ProtoBackupImport.BackupRestoreState.RestoringMeta -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.RESTORING_META, state = BackupRestoreState.RESTORING_META,
totalManga = totalManga, totalManga = totalManga,
mangaProgress = current, mangaProgress = current,
) )
is ProtoBackupImport.BackupRestoreState.RestoringSettings -> }
is ProtoBackupImport.BackupRestoreState.RestoringSettings -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.RESTORING_SETTINGS, state = BackupRestoreState.RESTORING_SETTINGS,
totalManga = totalManga, totalManga = totalManga,
mangaProgress = current, mangaProgress = current,
) )
is ProtoBackupImport.BackupRestoreState.RestoringManga -> }
is ProtoBackupImport.BackupRestoreState.RestoringManga -> {
BackupRestoreStatus( BackupRestoreStatus(
state = BackupRestoreState.RESTORING_MANGA, state = BackupRestoreState.RESTORING_MANGA,
totalManga = totalManga, totalManga = totalManga,
mangaProgress = current, mangaProgress = current,
) )
}
} }

View File

@@ -204,12 +204,27 @@ data class GroupFilter(
fun filterOf(filter: SourceFilter<*>): Filter = fun filterOf(filter: SourceFilter<*>): Filter =
when (filter) { when (filter) {
is SourceFilter.Header -> HeaderFilter(filter.name) is SourceFilter.Header -> {
is SourceFilter.Separator -> SeparatorFilter(filter.name) HeaderFilter(filter.name)
is SourceFilter.Select<*> -> SelectFilter(filter.name, filter.displayValues, filter.state) }
is SourceFilter.Text -> TextFilter(filter.name, filter.state)
is SourceFilter.CheckBox -> CheckBoxFilter(filter.name, filter.state) is SourceFilter.Separator -> {
is SourceFilter.TriState -> SeparatorFilter(filter.name)
}
is SourceFilter.Select<*> -> {
SelectFilter(filter.name, filter.displayValues, filter.state)
}
is SourceFilter.Text -> {
TextFilter(filter.name, filter.state)
}
is SourceFilter.CheckBox -> {
CheckBoxFilter(filter.name, filter.state)
}
is SourceFilter.TriState -> {
TriStateFilter( TriStateFilter(
filter.name, filter.name,
when (filter.state) { when (filter.state) {
@@ -218,13 +233,22 @@ fun filterOf(filter: SourceFilter<*>): Filter =
else -> TriState.IGNORE else -> TriState.IGNORE
}, },
) )
is SourceFilter.Group<*> -> }
is SourceFilter.Group<*> -> {
GroupFilter( GroupFilter(
filter.name, filter.name,
filter.state.map { filterOf(it as SourceFilter<*>) }, filter.state.map { filterOf(it as SourceFilter<*>) },
) )
is SourceFilter.Sort -> SortFilter(filter.name, filter.values.asList(), filter.state?.let(SortFilter::SortSelection)) }
else -> throw RuntimeException("sealed class cannot have more subtypes!")
is SourceFilter.Sort -> {
SortFilter(filter.name, filter.values.asList(), filter.state?.let(SortFilter::SortSelection))
}
else -> {
throw RuntimeException("sealed class cannot have more subtypes!")
}
} }
/*sealed interface FilterChange { /*sealed interface FilterChange {
@@ -286,25 +310,31 @@ fun updateFilterList(
is SourceFilter.Header -> { is SourceFilter.Header -> {
// NOOP // NOOP
} }
is SourceFilter.Separator -> { is SourceFilter.Separator -> {
// NOOP // NOOP
} }
is SourceFilter.Select<*> -> { is SourceFilter.Select<*> -> {
filter.state = change.selectState filter.state = change.selectState
?: throw Exception("Expected select state change at position ${change.position}") ?: throw Exception("Expected select state change at position ${change.position}")
} }
is SourceFilter.Text -> { is SourceFilter.Text -> {
filter.state = change.textState filter.state = change.textState
?: throw Exception("Expected text state change at position ${change.position}") ?: throw Exception("Expected text state change at position ${change.position}")
} }
is SourceFilter.CheckBox -> { is SourceFilter.CheckBox -> {
filter.state = change.checkBoxState filter.state = change.checkBoxState
?: throw Exception("Expected checkbox state change at position ${change.position}") ?: throw Exception("Expected checkbox state change at position ${change.position}")
} }
is SourceFilter.TriState -> { is SourceFilter.TriState -> {
filter.state = change.triState?.ordinal filter.state = change.triState?.ordinal
?: throw Exception("Expected tri state change at position ${change.position}") ?: throw Exception("Expected tri state change at position ${change.position}")
} }
is SourceFilter.Group<*> -> { is SourceFilter.Group<*> -> {
val groupChange = val groupChange =
change.groupChange change.groupChange
@@ -315,20 +345,24 @@ fun updateFilterList(
groupFilter.state = groupChange.checkBoxState groupFilter.state = groupChange.checkBoxState
?: throw Exception("Expected checkbox state change at position ${change.position}") ?: throw Exception("Expected checkbox state change at position ${change.position}")
} }
is SourceFilter.TriState -> { is SourceFilter.TriState -> {
groupFilter.state = groupChange.triState?.ordinal groupFilter.state = groupChange.triState?.ordinal
?: throw Exception("Expected tri state change at position ${change.position}") ?: throw Exception("Expected tri state change at position ${change.position}")
} }
is SourceFilter.Text -> { is SourceFilter.Text -> {
groupFilter.state = groupChange.textState groupFilter.state = groupChange.textState
?: throw Exception("Expected text state change at position ${change.position}") ?: throw Exception("Expected text state change at position ${change.position}")
} }
is SourceFilter.Select<*> -> { is SourceFilter.Select<*> -> {
groupFilter.state = groupChange.selectState groupFilter.state = groupChange.selectState
?: throw Exception("Expected select state change at position ${change.position}") ?: throw Exception("Expected select state change at position ${change.position}")
} }
} }
} }
is SourceFilter.Sort -> { is SourceFilter.Sort -> {
filter.state = change.sortState?.run { filter.state = change.sortState?.run {
SourceFilter.Sort.Selection(index, ascending) SourceFilter.Sort.Selection(index, ascending)
@@ -402,7 +436,7 @@ data class MultiSelectListPreference(
fun preferenceOf(preference: SourcePreference): Preference = fun preferenceOf(preference: SourcePreference): Preference =
when (preference) { when (preference) {
is SourceSwitchPreference -> is SourceSwitchPreference -> {
SwitchPreference( SwitchPreference(
preference.key, preference.key,
preference.title?.toString(), preference.title?.toString(),
@@ -412,7 +446,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
preference.currentValue as Boolean, preference.currentValue as Boolean,
preference.defaultValue as Boolean, preference.defaultValue as Boolean,
) )
is SourceCheckBoxPreference -> }
is SourceCheckBoxPreference -> {
CheckBoxPreference( CheckBoxPreference(
preference.key, preference.key,
preference.title?.toString(), preference.title?.toString(),
@@ -422,7 +458,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
preference.currentValue as Boolean, preference.currentValue as Boolean,
preference.defaultValue as Boolean, preference.defaultValue as Boolean,
) )
is SourceEditTextPreference -> }
is SourceEditTextPreference -> {
EditTextPreference( EditTextPreference(
preference.key, preference.key,
preference.title?.toString(), preference.title?.toString(),
@@ -435,7 +473,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
preference.dialogMessage?.toString(), preference.dialogMessage?.toString(),
preference.text, preference.text,
) )
is SourceListPreference -> }
is SourceListPreference -> {
ListPreference( ListPreference(
preference.key, preference.key,
preference.title?.toString(), preference.title?.toString(),
@@ -447,7 +487,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
preference.entries.map { it.toString() }, preference.entries.map { it.toString() },
preference.entryValues.map { it.toString() }, preference.entryValues.map { it.toString() },
) )
is SourceMultiSelectListPreference -> }
is SourceMultiSelectListPreference -> {
MultiSelectListPreference( MultiSelectListPreference(
preference.key, preference.key,
preference.title?.toString(), preference.title?.toString(),
@@ -461,5 +503,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
preference.entries.map { it.toString() }, preference.entries.map { it.toString() },
preference.entryValues.map { it.toString() }, preference.entryValues.map { it.toString() },
) )
else -> throw RuntimeException("sealed class cannot have more subtypes!") }
else -> {
throw RuntimeException("sealed class cannot have more subtypes!")
}
} }

View File

@@ -513,25 +513,33 @@ object Chapter {
// Make sure some filter is defined // Make sure some filter is defined
val condition = val condition =
when { when {
mangaId != null -> mangaId != null -> {
// mangaId is not null, scope query under manga // mangaId is not null, scope query under manga
when { when {
input.chapterIds != null -> input.chapterIds != null -> {
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) } Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) }
}
input.chapterIndexes != null -> input.chapterIndexes != null -> {
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) } Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) }
}
else -> null else -> {
null
}
} }
}
else -> { else -> {
// mangaId is null, only chapterIndexes is valid for this case // mangaId is null, only chapterIndexes is valid for this case
when { when {
input.chapterIds != null -> input.chapterIds != null -> {
Op.build { (ChapterTable.id inList input.chapterIds) } Op.build { (ChapterTable.id inList input.chapterIds) }
}
else -> null else -> {
null
}
} }
} }
} ?: return } ?: return

View File

@@ -372,10 +372,11 @@ object Manga {
val sourceId = mangaEntry[MangaTable.sourceReference] val sourceId = mangaEntry[MangaTable.sourceReference]
return when (val source = getCatalogueSourceOrStub(sourceId)) { return when (val source = getCatalogueSourceOrStub(sourceId)) {
is HttpSource -> is HttpSource -> {
getImageResponse(cacheSaveDir, fileName) { getImageResponse(cacheSaveDir, fileName) {
fetchHttpSourceMangaThumbnail(source, mangaEntry) fetchHttpSourceMangaThumbnail(source, mangaEntry)
} }
}
is LocalSource -> { is LocalSource -> {
val imageFile = val imageFile =
@@ -393,7 +394,7 @@ object Manga {
imageFile.inputStream() to contentType imageFile.inputStream() to contentType
} }
is StubSource -> is StubSource -> {
getImageResponse(cacheSaveDir, fileName) { getImageResponse(cacheSaveDir, fileName) {
val thumbnailUrl = val thumbnailUrl =
mangaEntry[MangaTable.thumbnail_url] mangaEntry[MangaTable.thumbnail_url]
@@ -403,8 +404,11 @@ object Manga {
GET(thumbnailUrl, cache = CacheControl.FORCE_NETWORK), GET(thumbnailUrl, cache = CacheControl.FORCE_NETWORK),
).await() ).await()
} }
}
else -> throw IllegalArgumentException("Unknown source") else -> {
throw IllegalArgumentException("Unknown source")
}
} }
} }

View File

@@ -86,7 +86,10 @@ object Search {
}, },
) )
} }
else -> it
else -> {
it
}
}, },
) )
} }
@@ -122,13 +125,27 @@ object Search {
is Filter.Header -> { is Filter.Header -> {
// NOOP // NOOP
} }
is Filter.Separator -> { is Filter.Separator -> {
// NOOP // NOOP
} }
is Filter.Select<*> -> filter.state = change.state.toInt()
is Filter.Text -> filter.state = change.state is Filter.Select<*> -> {
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict() filter.state = change.state.toInt()
is Filter.TriState -> 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<*> -> { is Filter.Group<*> -> {
val groupChange = jsonMapper.fromJsonString<FilterChange>(change.state) val groupChange = jsonMapper.fromJsonString<FilterChange>(change.state)
@@ -139,6 +156,7 @@ object Search {
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt() is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
} }
} }
is Filter.Sort -> { is Filter.Sort -> {
filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java) filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
} }

View File

@@ -120,8 +120,11 @@ object DownloadManager {
fun handleRequest(ctx: WsMessageContext) { fun handleRequest(ctx: WsMessageContext) {
when (ctx.message()) { when (ctx.message()) {
"STATUS" -> notifyClient(ctx) "STATUS" -> {
else -> notifyClient(ctx)
}
else -> {
ctx.send( ctx.send(
""" """
|Invalid command. |Invalid command.
@@ -131,6 +134,7 @@ object DownloadManager {
| |
""".trimMargin(), """.trimMargin(),
) )
}
} }
} }

View File

@@ -49,6 +49,7 @@ sealed class FileType {
is RegularFile -> { is RegularFile -> {
this.file.name this.file.name
} }
is ZipFile -> { is ZipFile -> {
this.entry.name this.entry.name
} }
@@ -59,6 +60,7 @@ sealed class FileType {
is RegularFile -> { is RegularFile -> {
this.file.extension this.file.extension
} }
is ZipFile -> { is ZipFile -> {
this.entry.name.substringAfterLast(".") this.entry.name.substringAfterLast(".")
} }

View File

@@ -149,6 +149,7 @@ object ExtensionsList {
this[ExtensionTable.hasUpdate] = true this[ExtensionTable.hasUpdate] = true
updateMap.putIfAbsent(foundExtension.pkgName, foundExtension) updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
} }
foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode] -> { foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode] -> {
// somehow the user installed an invalid version // somehow the user installed an invalid version
this[ExtensionTable.isObsolete] = true this[ExtensionTable.isObsolete] = true

View File

@@ -150,6 +150,7 @@ object KoreaderSyncService {
null null
} }
} }
KoreaderSyncChecksumMethod.FILENAME -> { KoreaderSyncChecksumMethod.FILENAME -> {
logger.debug { "[KOSYNC HASH] No hash for chapterId=$chapterId. Generating from filename." } logger.debug { "[KOSYNC HASH] No hash for chapterId=$chapterId. Generating from filename." }
(ChapterTable innerJoin MangaTable) (ChapterTable innerJoin MangaTable)

View File

@@ -69,56 +69,82 @@ class Anilist(
when (trackPreferences.getScoreType(this)) { when (trackPreferences.getScoreType(this)) {
// 10 point // 10 point
POINT_10 -> IntRange(0, 10).map(Int::toString) POINT_10 -> IntRange(0, 10).map(Int::toString)
// 100 point // 100 point
POINT_100 -> IntRange(0, 100).map(Int::toString) POINT_100 -> IntRange(0, 100).map(Int::toString)
// 5 stars // 5 stars
POINT_5 -> IntRange(0, 5).map { "$it" } POINT_5 -> IntRange(0, 5).map { "$it" }
// Smiley // Smiley
POINT_3 -> listOf("-", "😦", "😐", "😊") POINT_3 -> listOf("-", "😦", "😐", "😊")
// 10 point decimal // 10 point decimal
POINT_10_DECIMAL -> IntRange(0, 100).map { (it / 10f).toString() } POINT_10_DECIMAL -> IntRange(0, 100).map { (it / 10f).toString() }
else -> throw Exception("Unknown score type") else -> throw Exception("Unknown score type")
} }
override fun indexToScore(index: Int): Double = override fun indexToScore(index: Int): Double =
when (trackPreferences.getScoreType(this)) { when (trackPreferences.getScoreType(this)) {
// 10 point // 10 point
POINT_10 -> index * 10.0 POINT_10 -> {
index * 10.0
}
// 100 point // 100 point
POINT_100 -> index.toDouble() POINT_100 -> {
index.toDouble()
}
// 5 stars // 5 stars
POINT_5 -> POINT_5 -> {
when (index) { when (index) {
0 -> 0.0 0 -> 0.0
else -> index * 20.0 - 10.0 else -> index * 20.0 - 10.0
} }
}
// Smiley // Smiley
POINT_3 -> POINT_3 -> {
when (index) { when (index) {
0 -> 0.0 0 -> 0.0
else -> index * 25.0 + 10.0 else -> index * 25.0 + 10.0
} }
}
// 10 point decimal // 10 point decimal
POINT_10_DECIMAL -> index.toDouble() POINT_10_DECIMAL -> {
else -> throw Exception("Unknown score type") index.toDouble()
}
else -> {
throw Exception("Unknown score type")
}
} }
override fun displayScore(track: Track): String { override fun displayScore(track: Track): String {
val score = track.score val score = track.score
return when (val type = trackPreferences.getScoreType(this)) { return when (val type = trackPreferences.getScoreType(this)) {
POINT_5 -> POINT_5 -> {
when (score) { when (score) {
0.0 -> "0 ★" 0.0 -> "0 ★"
else -> "${((score + 10) / 20).toInt()}" else -> "${((score + 10) / 20).toInt()}"
} }
POINT_3 -> }
POINT_3 -> {
when { when {
score == 0.0 -> "0" score == 0.0 -> "0"
score <= 35 -> "😦" score <= 35 -> "😦"
score <= 60 -> "😐" score <= 60 -> "😐"
else -> "😊" else -> "😊"
} }
else -> track.toApiScore(type) }
else -> {
track.toApiScore(type)
}
} }
} }

View File

@@ -16,11 +16,17 @@ fun Track.toApiStatus() =
fun Track.toApiScore(scoreType: String?): String = fun Track.toApiScore(scoreType: String?): String =
when (scoreType) { when (scoreType) {
// 10 point // 10 point
"POINT_10" -> (score.toInt() / 10).toString() "POINT_10" -> {
(score.toInt() / 10).toString()
}
// 100 point // 100 point
"POINT_100" -> score.toInt().toString() "POINT_100" -> {
score.toInt().toString()
}
// 5 stars // 5 stars
"POINT_5" -> "POINT_5" -> {
when { when {
score == 0.0 -> "0" score == 0.0 -> "0"
score < 30 -> "1" score < 30 -> "1"
@@ -29,15 +35,24 @@ fun Track.toApiScore(scoreType: String?): String =
score < 90 -> "4" score < 90 -> "4"
else -> "5" else -> "5"
} }
}
// Smiley // Smiley
"POINT_3" -> "POINT_3" -> {
when { when {
score == 0.0 -> "0" score == 0.0 -> "0"
score <= 35 -> ":(" score <= 35 -> ":("
score <= 60 -> ":|" score <= 60 -> ":|"
else -> ":)" else -> ":)"
} }
}
// 10 point decimal // 10 point decimal
"POINT_10_DECIMAL" -> (score / 10).toString() "POINT_10_DECIMAL" -> {
else -> throw NotImplementedError("Unknown score type") (score / 10).toString()
}
else -> {
throw NotImplementedError("Unknown score type")
}
} }

View File

@@ -24,12 +24,19 @@ class MangaUpdates(
(0..10) (0..10)
.flatMap { decimal -> .flatMap { decimal ->
when (decimal) { when (decimal) {
0 -> listOf("-") 0 -> {
10 -> listOf("10.0") listOf("-")
else -> }
10 -> {
listOf("10.0")
}
else -> {
(0..9).map { fraction -> (0..9).map { fraction ->
"$decimal.$fraction" "$decimal.$fraction"
} }
}
} }
} }
} }

View File

@@ -26,8 +26,11 @@ object UpdaterSocket : Websocket<UpdateStatus>() {
override fun handleRequest(ctx: WsMessageContext) { override fun handleRequest(ctx: WsMessageContext) {
when (ctx.message()) { when (ctx.message()) {
"STATUS" -> notifyClient(ctx, updater.statusDeprecated.value) "STATUS" -> {
else -> notifyClient(ctx, updater.statusDeprecated.value)
}
else -> {
ctx.send( ctx.send(
""" """
|Invalid command. |Invalid command.
@@ -37,6 +40,7 @@ object UpdaterSocket : Websocket<UpdateStatus>() {
| |
""".trimMargin(), """.trimMargin(),
) )
}
} }
} }

View File

@@ -48,7 +48,10 @@ private fun getChapterDir(
chapterEntry[ChapterTable.scanlator] != null -> { chapterEntry[ChapterTable.scanlator] != null -> {
"${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
} }
else -> chapterEntry[ChapterTable.name]
else -> {
chapterEntry[ChapterTable.name]
}
}, },
) )

View File

@@ -319,6 +319,7 @@ object OpdsEntryBuilder {
} }
titlePrefix = statusKey.localized(locale) titlePrefix = statusKey.localized(locale)
} }
is ProgressSource.Remote -> { is ProgressSource.Remote -> {
idSuffix = ":remote" idSuffix = ":remote"
titlePrefix = MR.strings.opds_chapter_status_synced.localized(locale, progressSource.device) titlePrefix = MR.strings.opds_chapter_status_synced.localized(locale, progressSource.device)
@@ -378,6 +379,7 @@ object OpdsEntryBuilder {
} }
titleRes.localized(locale) titleRes.localized(locale)
} }
progressSource is ProgressSource.Local -> { progressSource is ProgressSource.Local -> {
val titleRes = val titleRes =
if (progressSource.lastPageRead > 0) { if (progressSource.lastPageRead > 0) {
@@ -387,6 +389,7 @@ object OpdsEntryBuilder {
} }
titleRes.localized(locale) titleRes.localized(locale)
} }
progressSource is ProgressSource.Remote -> { progressSource is ProgressSource.Remote -> {
val titleRes = val titleRes =
if (progressSource.lastPageRead > 0) { if (progressSource.lastPageRead > 0) {
@@ -396,7 +399,11 @@ object OpdsEntryBuilder {
} }
titleRes.localized(locale, progressSource.device) titleRes.localized(locale, progressSource.device)
} }
else -> "" // Should not happen
else -> {
// Should not happen
""
}
} }
links.add( links.add(

View File

@@ -155,30 +155,40 @@ object OpdsFeedBuilder {
val feedTitle = val feedTitle =
when (criteria.primaryFilter) { when (criteria.primaryFilter) {
PrimaryFilterType.SOURCE -> PrimaryFilterType.SOURCE -> {
MR.strings.opds_feeds_library_source_specific_title.localized( MR.strings.opds_feeds_library_source_specific_title.localized(
locale, locale,
result.feedTitleComponent ?: criteria.sourceId.toString(), result.feedTitleComponent ?: criteria.sourceId.toString(),
) )
PrimaryFilterType.CATEGORY -> }
PrimaryFilterType.CATEGORY -> {
MR.strings.opds_feeds_category_specific_title.localized( MR.strings.opds_feeds_category_specific_title.localized(
locale, locale,
result.feedTitleComponent ?: criteria.categoryId.toString(), result.feedTitleComponent ?: criteria.categoryId.toString(),
) )
PrimaryFilterType.GENRE -> }
PrimaryFilterType.GENRE -> {
MR.strings.opds_feeds_genre_specific_title.localized( MR.strings.opds_feeds_genre_specific_title.localized(
locale, locale,
result.feedTitleComponent ?: "Unknown", result.feedTitleComponent ?: "Unknown",
) )
}
PrimaryFilterType.STATUS -> { PrimaryFilterType.STATUS -> {
val statusName = NavigationRepository.getStatuses(locale).find { it.id == criteria.statusId }?.title val statusName = NavigationRepository.getStatuses(locale).find { it.id == criteria.statusId }?.title
MR.strings.opds_feeds_status_specific_title.localized(locale, statusName ?: criteria.statusId.toString()) MR.strings.opds_feeds_status_specific_title.localized(locale, statusName ?: criteria.statusId.toString())
} }
PrimaryFilterType.LANGUAGE -> { PrimaryFilterType.LANGUAGE -> {
val langName = Locale.forLanguageTag(criteria.langCode ?: "").getDisplayName(locale) val langName = Locale.forLanguageTag(criteria.langCode ?: "").getDisplayName(locale)
MR.strings.opds_feeds_language_specific_title.localized(locale, langName) MR.strings.opds_feeds_language_specific_title.localized(locale, langName)
} }
else -> MR.strings.opds_feeds_all_series_in_library_title.localized(locale)
else -> {
MR.strings.opds_feeds_all_series_in_library_title.localized(locale)
}
} }
val feedUrl = val feedUrl =

View File

@@ -108,7 +108,7 @@ object MangaRepository {
// Efficiently get the name of the primary filter item // Efficiently get the name of the primary filter item
val specificFilterName = val specificFilterName =
when (criteria.primaryFilter) { when (criteria.primaryFilter) {
PrimaryFilterType.SOURCE -> PrimaryFilterType.SOURCE -> {
criteria.sourceId?.let { criteria.sourceId?.let {
SourceTable SourceTable
.select(SourceTable.name) .select(SourceTable.name)
@@ -116,7 +116,9 @@ object MangaRepository {
.firstOrNull() .firstOrNull()
?.get(SourceTable.name) ?.get(SourceTable.name)
} }
PrimaryFilterType.CATEGORY -> }
PrimaryFilterType.CATEGORY -> {
criteria.categoryId?.let { criteria.categoryId?.let {
CategoryTable CategoryTable
.select(CategoryTable.name) .select(CategoryTable.name)
@@ -124,10 +126,25 @@ object MangaRepository {
.firstOrNull() .firstOrNull()
?.get(CategoryTable.name) ?.get(CategoryTable.name)
} }
PrimaryFilterType.GENRE -> criteria.genre }
PrimaryFilterType.STATUS -> criteria.statusId.toString() // Controller will map this to a localized string
PrimaryFilterType.LANGUAGE -> criteria.langCode // Controller will map this to a display name PrimaryFilterType.GENRE -> {
else -> null criteria.genre
}
// Controller will map this to a localized string
PrimaryFilterType.STATUS -> {
criteria.statusId.toString()
}
// Controller will map this to a display name
PrimaryFilterType.LANGUAGE -> {
criteria.langCode
}
else -> {
null
}
} }
applyMangaLibrarySortAndFilter(query, sort, filter) applyMangaLibrarySortAndFilter(query, sort, filter)

View File

@@ -44,10 +44,18 @@ object OpdsStringUtil {
if (serverConfig.opdsUseBinaryFileSizes.value) { if (serverConfig.opdsUseBinaryFileSizes.value) {
// Binary notation (base 1024) // Binary notation (base 1024)
when { when {
size >= 1_125_899_906_842_624 -> "%.2f TiB".format(size / 1_125_899_906_842_624.0) // 1024^4 // 1024^4
size >= 1_073_741_824 -> "%.2f GiB".format(size / 1_073_741_824.0) // 1024^3 size >= 1_125_899_906_842_624 -> "%.2f TiB".format(size / 1_125_899_906_842_624.0)
size >= 1_048_576 -> "%.2f MiB".format(size / 1_048_576.0) // 1024^2
size >= 1024 -> "%.2f KiB".format(size / 1024.0) // 1024 // 1024^3
size >= 1_073_741_824 -> "%.2f GiB".format(size / 1_073_741_824.0)
// 1024^2
size >= 1_048_576 -> "%.2f MiB".format(size / 1_048_576.0)
// 1024
size >= 1024 -> "%.2f KiB".format(size / 1024.0)
else -> "$size bytes" else -> "$size bytes"
} }
} else { } else {

View File

@@ -52,6 +52,7 @@ object DBManager {
addDataSourceProperty("cachePrepStmts", "true") addDataSourceProperty("cachePrepStmts", "true")
addDataSourceProperty("useServerPrepStmts", "true") addDataSourceProperty("useServerPrepStmts", "true")
} }
DatabaseType.H2 -> { DatabaseType.H2 -> {
jdbcUrl = "jdbc:h2:${applicationDirs.dataRoot}/database" jdbcUrl = "jdbc:h2:${applicationDirs.dataRoot}/database"
driverClassName = "org.h2.Driver" driverClassName = "org.h2.Driver"
@@ -103,7 +104,7 @@ object DBManager {
.connect(hikariDataSource!!, databaseConfig = dbConfig) .connect(hikariDataSource!!, databaseConfig = dbConfig)
} else { } else {
when (serverConfig.databaseType.value) { when (serverConfig.databaseType.value) {
DatabaseType.POSTGRESQL -> DatabaseType.POSTGRESQL -> {
Database.connect( Database.connect(
"jdbc:${serverConfig.databaseUrl.value}", "jdbc:${serverConfig.databaseUrl.value}",
"org.postgresql.Driver", "org.postgresql.Driver",
@@ -111,12 +112,15 @@ object DBManager {
password = serverConfig.databasePassword.value, password = serverConfig.databasePassword.value,
databaseConfig = dbConfig, databaseConfig = dbConfig,
) )
DatabaseType.H2 -> }
DatabaseType.H2 -> {
Database.connect( Database.connect(
"jdbc:h2:${Injekt.get<ApplicationDirs>().dataRoot}/database", "jdbc:h2:${Injekt.get<ApplicationDirs>().dataRoot}/database",
"org.h2.Driver", "org.h2.Driver",
databaseConfig = dbConfig, databaseConfig = dbConfig,
) )
}
} }
}.also { db = it } }.also { db = it }
} }

View File

@@ -6,7 +6,9 @@ import suwayomi.tachidesk.server.serverConfig
val UNLIMITED_TEXT val UNLIMITED_TEXT
get() = get() =
when (serverConfig.databaseType.value) { when (serverConfig.databaseType.value) {
DatabaseType.H2 -> "VARCHAR" // the default length is `Integer.MAX_VALUE` // the default length is `Integer.MAX_VALUE`
DatabaseType.H2 -> "VARCHAR"
DatabaseType.POSTGRESQL -> "TEXT" DatabaseType.POSTGRESQL -> "TEXT"
} }

View File

@@ -25,8 +25,14 @@ fun UserType.requireUser(): Int =
fun UserType.requireUserWithBasicFallback(ctx: Context): Int = fun UserType.requireUserWithBasicFallback(ctx: Context): Int =
when (this) { when (this) {
is UserType.Admin -> id is UserType.Admin -> {
UserType.Visitor if ctx.getAttribute(Attribute.TachideskBasic) -> 1 id
}
UserType.Visitor if ctx.getAttribute(Attribute.TachideskBasic) -> {
1
}
UserType.Visitor -> { UserType.Visitor -> {
ctx.header("WWW-Authenticate", "Basic") ctx.header("WWW-Authenticate", "Basic")
throw UnauthorizedException() throw UnauthorizedException()
@@ -53,8 +59,14 @@ fun getUserFromContext(ctx: Context): UserType {
return when (serverConfig.authMode.value) { return when (serverConfig.authMode.value) {
// NOTE: Basic Auth is expected to have been validated by JavalinSetup // NOTE: Basic Auth is expected to have been validated by JavalinSetup
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1) AuthMode.NONE, AuthMode.BASIC_AUTH -> {
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor UserType.Admin(1)
}
AuthMode.SIMPLE_LOGIN -> {
if (cookieValid()) UserType.Admin(1) else UserType.Visitor
}
AuthMode.UI_LOGIN -> { AuthMode.UI_LOGIN -> {
val authentication = ctx.header(Header.AUTHORIZATION) ?: ctx.cookie("suwayomi-server-token") val authentication = ctx.header(Header.AUTHORIZATION) ?: ctx.cookie("suwayomi-server-token")
val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token") val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token")
@@ -72,8 +84,14 @@ fun getUserFromWsContext(ctx: WsConnectContext): UserType {
return when (serverConfig.authMode.value) { return when (serverConfig.authMode.value) {
// NOTE: Basic Auth is expected to have been validated by JavalinSetup // NOTE: Basic Auth is expected to have been validated by JavalinSetup
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1) AuthMode.NONE, AuthMode.BASIC_AUTH -> {
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor UserType.Admin(1)
}
AuthMode.SIMPLE_LOGIN -> {
if (cookieValid()) UserType.Admin(1) else UserType.Visitor
}
AuthMode.UI_LOGIN -> { AuthMode.UI_LOGIN -> {
val authentication = val authentication =
ctx.header(Header.AUTHORIZATION) ?: ctx.header("Sec-WebSocket-Protocol") ?: ctx.cookie("suwayomi-server-token") ctx.header(Header.AUTHORIZATION) ?: ctx.header("Sec-WebSocket-Protocol") ?: ctx.cookie("suwayomi-server-token")

View File

@@ -72,6 +72,7 @@ object AppMutex {
AppMutexState.Clear -> { AppMutexState.Clear -> {
logger.info { "Mutex status is clear, Resuming startup." } logger.info { "Mutex status is clear, Resuming startup." }
} }
AppMutexState.TachideskInstanceRunning -> { AppMutexState.TachideskInstanceRunning -> {
logger.info { "Another instance of Suwayomi-Server is running on $appIP:${serverConfig.port.value}" } logger.info { "Another instance of Suwayomi-Server is running on $appIP:${serverConfig.port.value}" }
@@ -82,6 +83,7 @@ object AppMutex {
shutdownApp(MutexCheckFailedTachideskRunning) shutdownApp(MutexCheckFailedTachideskRunning)
} }
AppMutexState.OtherApplicationRunning -> { AppMutexState.OtherApplicationRunning -> {
logger.error { "A non Suwayomi-Server application is running on $appIP:${serverConfig.port.value}, aborting startup." } logger.error { "A non Suwayomi-Server application is running on $appIP:${serverConfig.port.value}, aborting startup." }
shutdownApp(MutexCheckFailedAnotherAppRunning) shutdownApp(MutexCheckFailedAnotherAppRunning)

View File

@@ -42,12 +42,30 @@ fun <T> getParam(
} }
val typedItem: Any? = val typedItem: Any? =
when (val clazz = param.clazz as Class<T>) { when (val clazz = param.clazz as Class<T>) {
String::class.java, java.lang.String::class.java -> getSimpleParamItem(ctx, param) ?: param.defaultValue String::class.java, java.lang.String::class.java -> {
Int::class.java, java.lang.Integer::class.java -> getSimpleParamItem(ctx, param)?.toIntOrNull() ?: param.defaultValue getSimpleParamItem(ctx, param) ?: param.defaultValue
Long::class.java, java.lang.Long::class.java -> getSimpleParamItem(ctx, param)?.toLongOrNull() ?: param.defaultValue }
Boolean::class.java, java.lang.Boolean::class.java -> getSimpleParamItem(ctx, param)?.toBoolean() ?: param.defaultValue
Float::class.java, java.lang.Float::class.java -> getSimpleParamItem(ctx, param)?.toFloatOrNull() ?: param.defaultValue Int::class.java, java.lang.Integer::class.java -> {
Double::class.java, java.lang.Double::class.java -> getSimpleParamItem(ctx, param)?.toDoubleOrNull() ?: param.defaultValue getSimpleParamItem(ctx, param)?.toIntOrNull() ?: param.defaultValue
}
Long::class.java, java.lang.Long::class.java -> {
getSimpleParamItem(ctx, param)?.toLongOrNull() ?: param.defaultValue
}
Boolean::class.java, java.lang.Boolean::class.java -> {
getSimpleParamItem(ctx, param)?.toBoolean() ?: param.defaultValue
}
Float::class.java, java.lang.Float::class.java -> {
getSimpleParamItem(ctx, param)?.toFloatOrNull() ?: param.defaultValue
}
Double::class.java, java.lang.Double::class.java -> {
getSimpleParamItem(ctx, param)?.toDoubleOrNull() ?: param.defaultValue
}
else -> { else -> {
when (param) { when (param) {
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz) is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)

View File

@@ -12,6 +12,7 @@ import android.content.Context
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.lang.launchIO
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.reactivecircus.cache4k.Cache import io.github.reactivecircus.cache4k.Cache
@@ -193,7 +194,7 @@ object WebInterfaceManager {
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launchIO {
setupWebUI() setupWebUI()
isSetupComplete = true isSetupComplete = true
} }
@@ -400,7 +401,7 @@ object WebInterfaceManager {
try { try {
downloadVersion(flavor, getVersion()) downloadVersion(flavor, getVersion())
true true
} catch (e: Exception) { } catch (_: Exception) {
false false
} || } ||
isLocalWebUIValid isLocalWebUIValid
@@ -427,7 +428,7 @@ object WebInterfaceManager {
try { try {
setupBundledWebUI() setupBundledWebUI()
} catch (e: Exception) { } catch (_: Exception) {
throw Exception("Unable to setup a webUI") throw Exception("Unable to setup a webUI")
} }
} }
@@ -498,7 +499,7 @@ object WebInterfaceManager {
private fun getLocalVersion(path: String = applicationDirs.webUIRoot): String = private fun getLocalVersion(path: String = applicationDirs.webUIRoot): String =
try { try {
File("$path/revision").readText().trim() File("$path/revision").readText().trim()
} catch (e: Exception) { } catch (_: Exception) {
"r-1" "r-1"
} }
@@ -585,7 +586,7 @@ object WebInterfaceManager {
.string() .string()
.trim() .trim()
}) })
} catch (e: Exception) { } catch (_: Exception) {
"" ""
} }
@@ -712,7 +713,7 @@ object WebInterfaceManager {
version: String, version: String,
) { ) {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launchIO {
downloadVersion(flavor, version) downloadVersion(flavor, version)
serveWebUI() serveWebUI()
} }

View File

@@ -61,9 +61,11 @@ class TestExtensionCompatibility {
it.obsolete -> { it.obsolete -> {
uninstallExtension(it.pkgName) uninstallExtension(it.pkgName)
} }
it.hasUpdate -> { it.hasUpdate -> {
updateExtension(it.pkgName) updateExtension(it.pkgName)
} }
else -> { else -> {
uninstallExtension(it.pkgName) uninstallExtension(it.pkgName)
installExtension(it.pkgName) installExtension(it.pkgName)