mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01:00
Format
This commit is contained in:
@@ -242,13 +242,14 @@ class JavaSharedPreferences(
|
||||
}
|
||||
notify(it.key)
|
||||
}
|
||||
|
||||
is Action.Remove -> {
|
||||
preferences.remove(it.key)
|
||||
/**
|
||||
* Set<String> are stored like
|
||||
* key.0 = value1
|
||||
* key.1 = value2
|
||||
* key.size = 2
|
||||
/*
|
||||
Set<String> are stored like
|
||||
key.0 = value1
|
||||
key.1 = value2
|
||||
key.size = 2
|
||||
*/
|
||||
preferences.keys.forEach { key ->
|
||||
if (key.startsWith(it.key + ".")) {
|
||||
@@ -258,7 +259,10 @@ class JavaSharedPreferences(
|
||||
|
||||
notify(it.key)
|
||||
}
|
||||
Action.Clear -> preferences.clear()
|
||||
|
||||
Action.Clear -> {
|
||||
preferences.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,12 @@ internal class RateLimitInterceptor(
|
||||
|
||||
val request = chain.request()
|
||||
when (host) {
|
||||
null, request.url.host -> {} // need rate limit
|
||||
else -> return chain.proceed(request)
|
||||
// need rate limit
|
||||
null, request.url.host -> {}
|
||||
|
||||
else -> {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -110,6 +110,7 @@ class LocalSource(
|
||||
mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
}
|
||||
}
|
||||
|
||||
is OrderBy.Latest -> {
|
||||
mangaDirs =
|
||||
if (filter.state!!.ascending) {
|
||||
@@ -246,6 +247,7 @@ class LocalSource(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Format.Rar -> {
|
||||
JunrarArchive(chapter).use { rar ->
|
||||
rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
|
||||
@@ -255,6 +257,7 @@ class LocalSource(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -337,6 +340,7 @@ class LocalSource(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is Format.Zip -> {
|
||||
val loader = ZipPageLoader(format.file)
|
||||
val pages = loader.getPages()
|
||||
@@ -344,6 +348,7 @@ class LocalSource(
|
||||
|
||||
pages
|
||||
}
|
||||
|
||||
is Format.Rar -> {
|
||||
val loader = RarPageLoader(format.file)
|
||||
val pages = loader.getPages()
|
||||
@@ -351,6 +356,7 @@ class LocalSource(
|
||||
|
||||
pages
|
||||
}
|
||||
|
||||
is Format.Epub -> {
|
||||
val loader = EpubPageLoader(format.file)
|
||||
val pages = loader.getPages()
|
||||
@@ -390,6 +396,7 @@ class LocalSource(
|
||||
|
||||
entry?.let { coverManager.update(manga, it.inputStream()) }
|
||||
}
|
||||
|
||||
is Format.Zip -> {
|
||||
ZipFile.builder().setFile(format.file).get().use { zip ->
|
||||
val entry =
|
||||
@@ -401,6 +408,7 @@ class LocalSource(
|
||||
entry?.let { coverManager.update(manga, zip.getInputStream(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
is Format.Rar -> {
|
||||
JunrarArchive(format.file).use { archive ->
|
||||
val entry =
|
||||
@@ -411,6 +419,7 @@ class LocalSource(
|
||||
entry?.let { coverManager.update(manga, archive.getInputStream(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
is Format.Epub -> {
|
||||
EpubFile(format.file).use { epub ->
|
||||
val entry =
|
||||
|
||||
@@ -58,6 +58,7 @@ object ChapterRecognition {
|
||||
numberMatch.none() -> {
|
||||
return chapterNumber ?: -1.0
|
||||
}
|
||||
|
||||
numberMatch.count() > 1 -> {
|
||||
// Remove unwanted tags.
|
||||
unwanted.replace(cleanChapterName, "").let { name ->
|
||||
|
||||
@@ -367,37 +367,127 @@ class KcefWebView {
|
||||
): KeyEvent? {
|
||||
val code =
|
||||
when (char.uppercaseChar()) {
|
||||
in 'A'..'Z', in '0'..'9' -> char.uppercaseChar().code
|
||||
'&' -> KeyEvent.VK_AMPERSAND
|
||||
'*' -> KeyEvent.VK_ASTERISK
|
||||
'@' -> KeyEvent.VK_AT
|
||||
'\\' -> KeyEvent.VK_BACK_SLASH
|
||||
'{' -> KeyEvent.VK_BRACELEFT
|
||||
'}' -> KeyEvent.VK_BRACERIGHT
|
||||
'^' -> KeyEvent.VK_CIRCUMFLEX
|
||||
']' -> 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 ->
|
||||
in 'A'..'Z', in '0'..'9' -> {
|
||||
char.uppercaseChar().code
|
||||
}
|
||||
|
||||
'&' -> {
|
||||
KeyEvent.VK_AMPERSAND
|
||||
}
|
||||
|
||||
'*' -> {
|
||||
KeyEvent.VK_ASTERISK
|
||||
}
|
||||
|
||||
'@' -> {
|
||||
KeyEvent.VK_AT
|
||||
}
|
||||
|
||||
'\\' -> {
|
||||
KeyEvent.VK_BACK_SLASH
|
||||
}
|
||||
|
||||
'{' -> {
|
||||
KeyEvent.VK_BRACELEFT
|
||||
}
|
||||
|
||||
'}' -> {
|
||||
KeyEvent.VK_BRACERIGHT
|
||||
}
|
||||
|
||||
'^' -> {
|
||||
KeyEvent.VK_CIRCUMFLEX
|
||||
}
|
||||
|
||||
']' -> {
|
||||
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) {
|
||||
"Alt" -> KeyEvent.VK_ALT
|
||||
"Backspace" -> KeyEvent.VK_BACK_SPACE
|
||||
@@ -435,6 +525,7 @@ class KcefWebView {
|
||||
"ArrowUp" -> KeyEvent.VK_UP
|
||||
else -> KeyEvent.VK_UNDEFINED
|
||||
}
|
||||
}
|
||||
}
|
||||
if (id == KeyEvent.KEY_TYPED) {
|
||||
if (char == KeyEvent.CHAR_UNDEFINED && code != KeyEvent.VK_ENTER) return null
|
||||
|
||||
@@ -104,18 +104,23 @@ object WebView : Websocket<String>() {
|
||||
dr.resize(event.width, event.height)
|
||||
logger.debug { "Loading URL $url" }
|
||||
}
|
||||
|
||||
is ResizeMessage -> {
|
||||
dr.resize(event.width, event.height)
|
||||
}
|
||||
|
||||
is JsEventMessage -> {
|
||||
dr.event(event)
|
||||
}
|
||||
|
||||
is JsPasteMessage -> {
|
||||
dr.paste(event.data)
|
||||
}
|
||||
|
||||
is JsCopyMessage -> {
|
||||
dr.copy()
|
||||
}
|
||||
|
||||
is JsPingMessage -> {
|
||||
notifyAllClients("{\"type\":\"pong\"}")
|
||||
}
|
||||
|
||||
@@ -135,9 +135,11 @@ class SourceMutation {
|
||||
filters = updateFilterList(source, filters),
|
||||
)
|
||||
}
|
||||
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
source.getPopularManga(page)
|
||||
}
|
||||
|
||||
FetchSourceMangaType.LATEST -> {
|
||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||
source.getLatestUpdates(page)
|
||||
|
||||
@@ -61,8 +61,14 @@ class TrackQuery {
|
||||
cursor: Cursor,
|
||||
): Boolean =
|
||||
when (this) {
|
||||
ID -> tracker.id > cursor.value.toInt()
|
||||
NAME -> tracker.name > cursor.value
|
||||
ID -> {
|
||||
tracker.id > cursor.value.toInt()
|
||||
}
|
||||
|
||||
NAME -> {
|
||||
tracker.name > cursor.value
|
||||
}
|
||||
|
||||
IS_LOGGED_IN -> {
|
||||
val value = cursor.value.substringAfter('-').toBooleanStrict()
|
||||
!value || tracker.isLoggedIn
|
||||
@@ -74,8 +80,14 @@ class TrackQuery {
|
||||
cursor: Cursor,
|
||||
): Boolean =
|
||||
when (this) {
|
||||
ID -> tracker.id < cursor.value.toInt()
|
||||
NAME -> tracker.name < cursor.value
|
||||
ID -> {
|
||||
tracker.id < cursor.value.toInt()
|
||||
}
|
||||
|
||||
NAME -> {
|
||||
tracker.name < cursor.value
|
||||
}
|
||||
|
||||
IS_LOGGED_IN -> {
|
||||
val value = cursor.value.substringAfter('-').toBooleanStrict()
|
||||
value || !tracker.isLoggedIn
|
||||
@@ -159,18 +171,21 @@ class TrackQuery {
|
||||
|
||||
res =
|
||||
when (orderType) {
|
||||
SortOrder.DESC, SortOrder.DESC_NULLS_FIRST, SortOrder.DESC_NULLS_LAST ->
|
||||
SortOrder.DESC, SortOrder.DESC_NULLS_FIRST, SortOrder.DESC_NULLS_LAST -> {
|
||||
when (orderBy) {
|
||||
TrackerOrderBy.ID -> res.sortedByDescending { it.id }
|
||||
TrackerOrderBy.NAME -> res.sortedByDescending { it.name }
|
||||
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) {
|
||||
TrackerOrderBy.ID -> res.sortedBy { it.id }
|
||||
TrackerOrderBy.NAME -> res.sortedBy { it.name }
|
||||
TrackerOrderBy.IS_LOGGED_IN -> res.sortedBy { it.isLoggedIn }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
is GraphQLRequest -> {
|
||||
request.copy(variables = request.variables?.modifyFiles(mapItems))
|
||||
}
|
||||
|
||||
is GraphQLBatchRequest -> {
|
||||
request.copy(
|
||||
requests =
|
||||
|
||||
@@ -63,10 +63,16 @@ class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
|
||||
|
||||
override fun willGenerateGraphQLType(type: KType): GraphQLType? =
|
||||
when (type.classifier as? KClass<*>) {
|
||||
Long::class -> GraphQLLongAsString // encode to string for JS
|
||||
Duration::class -> GraphQLDurationAsString // encode Duration as ISO-8601 string
|
||||
// encode to string for JS
|
||||
Long::class -> GraphQLLongAsString
|
||||
|
||||
// encode Duration as ISO-8601 string
|
||||
Duration::class -> GraphQLDurationAsString
|
||||
|
||||
Cursor::class -> GraphQLCursor
|
||||
|
||||
UploadedFile::class -> GraphQLUpload
|
||||
|
||||
else -> super.willGenerateGraphQLType(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
|
||||
private fun toStringImpl(input: Any): String =
|
||||
when (input) {
|
||||
is Duration -> input.toIsoString()
|
||||
|
||||
is String -> Duration.parse(input).toIsoString()
|
||||
|
||||
else -> throw CoercingSerializeException(
|
||||
"Expected a Duration or String but was ${CoercingUtil.typeName(input)}",
|
||||
)
|
||||
|
||||
@@ -31,46 +31,59 @@ data class BackupRestoreStatus(
|
||||
|
||||
fun ProtoBackupImport.BackupRestoreState.toStatus(): BackupRestoreStatus =
|
||||
when (this) {
|
||||
ProtoBackupImport.BackupRestoreState.Idle ->
|
||||
ProtoBackupImport.BackupRestoreState.Idle -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.IDLE,
|
||||
totalManga = 0,
|
||||
mangaProgress = 0,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.Success ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.Success -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.SUCCESS,
|
||||
totalManga = 0,
|
||||
mangaProgress = 0,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.Failure ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.Failure -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.FAILURE,
|
||||
totalManga = 0,
|
||||
mangaProgress = 0,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringCategories ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringCategories -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_CATEGORIES,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringMeta ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringMeta -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_META,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringSettings ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringSettings -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_SETTINGS,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringManga ->
|
||||
}
|
||||
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringManga -> {
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_MANGA,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,12 +204,27 @@ data class GroupFilter(
|
||||
|
||||
fun filterOf(filter: SourceFilter<*>): Filter =
|
||||
when (filter) {
|
||||
is SourceFilter.Header -> HeaderFilter(filter.name)
|
||||
is SourceFilter.Separator -> 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 ->
|
||||
is SourceFilter.Header -> {
|
||||
HeaderFilter(filter.name)
|
||||
}
|
||||
|
||||
is SourceFilter.Separator -> {
|
||||
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(
|
||||
filter.name,
|
||||
when (filter.state) {
|
||||
@@ -218,13 +233,22 @@ fun filterOf(filter: SourceFilter<*>): Filter =
|
||||
else -> TriState.IGNORE
|
||||
},
|
||||
)
|
||||
is SourceFilter.Group<*> ->
|
||||
}
|
||||
|
||||
is SourceFilter.Group<*> -> {
|
||||
GroupFilter(
|
||||
filter.name,
|
||||
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 {
|
||||
@@ -286,25 +310,31 @@ fun updateFilterList(
|
||||
is SourceFilter.Header -> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
is SourceFilter.Separator -> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
is SourceFilter.Select<*> -> {
|
||||
filter.state = change.selectState
|
||||
?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.Text -> {
|
||||
filter.state = change.textState
|
||||
?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.CheckBox -> {
|
||||
filter.state = change.checkBoxState
|
||||
?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.TriState -> {
|
||||
filter.state = change.triState?.ordinal
|
||||
?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.Group<*> -> {
|
||||
val groupChange =
|
||||
change.groupChange
|
||||
@@ -315,20 +345,24 @@ fun updateFilterList(
|
||||
groupFilter.state = groupChange.checkBoxState
|
||||
?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.TriState -> {
|
||||
groupFilter.state = groupChange.triState?.ordinal
|
||||
?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.Text -> {
|
||||
groupFilter.state = groupChange.textState
|
||||
?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
|
||||
is SourceFilter.Select<*> -> {
|
||||
groupFilter.state = groupChange.selectState
|
||||
?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SourceFilter.Sort -> {
|
||||
filter.state = change.sortState?.run {
|
||||
SourceFilter.Sort.Selection(index, ascending)
|
||||
@@ -402,7 +436,7 @@ data class MultiSelectListPreference(
|
||||
|
||||
fun preferenceOf(preference: SourcePreference): Preference =
|
||||
when (preference) {
|
||||
is SourceSwitchPreference ->
|
||||
is SourceSwitchPreference -> {
|
||||
SwitchPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
@@ -412,7 +446,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean,
|
||||
)
|
||||
is SourceCheckBoxPreference ->
|
||||
}
|
||||
|
||||
is SourceCheckBoxPreference -> {
|
||||
CheckBoxPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
@@ -422,7 +458,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean,
|
||||
)
|
||||
is SourceEditTextPreference ->
|
||||
}
|
||||
|
||||
is SourceEditTextPreference -> {
|
||||
EditTextPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
@@ -435,7 +473,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.text,
|
||||
)
|
||||
is SourceListPreference ->
|
||||
}
|
||||
|
||||
is SourceListPreference -> {
|
||||
ListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
@@ -447,7 +487,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() },
|
||||
)
|
||||
is SourceMultiSelectListPreference ->
|
||||
}
|
||||
|
||||
is SourceMultiSelectListPreference -> {
|
||||
MultiSelectListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
@@ -461,5 +503,9 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
preference.entries.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!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,25 +513,33 @@ object Chapter {
|
||||
// Make sure some filter is defined
|
||||
val condition =
|
||||
when {
|
||||
mangaId != null ->
|
||||
mangaId != null -> {
|
||||
// mangaId is not null, scope query under manga
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
input.chapterIds != null -> {
|
||||
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) }
|
||||
}
|
||||
|
||||
else -> null
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// mangaId is null, only chapterIndexes is valid for this case
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
input.chapterIds != null -> {
|
||||
Op.build { (ChapterTable.id inList input.chapterIds) }
|
||||
}
|
||||
|
||||
else -> null
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: return
|
||||
|
||||
@@ -372,10 +372,11 @@ object Manga {
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
|
||||
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||
is HttpSource ->
|
||||
is HttpSource -> {
|
||||
getImageResponse(cacheSaveDir, fileName) {
|
||||
fetchHttpSourceMangaThumbnail(source, mangaEntry)
|
||||
}
|
||||
}
|
||||
|
||||
is LocalSource -> {
|
||||
val imageFile =
|
||||
@@ -393,7 +394,7 @@ object Manga {
|
||||
imageFile.inputStream() to contentType
|
||||
}
|
||||
|
||||
is StubSource ->
|
||||
is StubSource -> {
|
||||
getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl =
|
||||
mangaEntry[MangaTable.thumbnail_url]
|
||||
@@ -403,8 +404,11 @@ object Manga {
|
||||
GET(thumbnailUrl, cache = CacheControl.FORCE_NETWORK),
|
||||
).await()
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown source")
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unknown source")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,10 @@ object Search {
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> it
|
||||
|
||||
else -> {
|
||||
it
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -122,13 +125,27 @@ object Search {
|
||||
is Filter.Header -> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
is Filter.Separator -> {
|
||||
// NOOP
|
||||
}
|
||||
is Filter.Select<*> -> filter.state = change.state.toInt()
|
||||
is Filter.Text -> filter.state = change.state
|
||||
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict()
|
||||
is Filter.TriState -> filter.state = change.state.toInt()
|
||||
|
||||
is Filter.Select<*> -> {
|
||||
filter.state = change.state.toInt()
|
||||
}
|
||||
|
||||
is Filter.Text -> {
|
||||
filter.state = change.state
|
||||
}
|
||||
|
||||
is Filter.CheckBox -> {
|
||||
filter.state = change.state.toBooleanStrict()
|
||||
}
|
||||
|
||||
is Filter.TriState -> {
|
||||
filter.state = change.state.toInt()
|
||||
}
|
||||
|
||||
is Filter.Group<*> -> {
|
||||
val groupChange = jsonMapper.fromJsonString<FilterChange>(change.state)
|
||||
|
||||
@@ -139,6 +156,7 @@ object Search {
|
||||
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
is Filter.Sort -> {
|
||||
filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
|
||||
}
|
||||
|
||||
@@ -120,8 +120,11 @@ object DownloadManager {
|
||||
|
||||
fun handleRequest(ctx: WsMessageContext) {
|
||||
when (ctx.message()) {
|
||||
"STATUS" -> notifyClient(ctx)
|
||||
else ->
|
||||
"STATUS" -> {
|
||||
notifyClient(ctx)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ctx.send(
|
||||
"""
|
||||
|Invalid command.
|
||||
@@ -131,6 +134,7 @@ object DownloadManager {
|
||||
|
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ sealed class FileType {
|
||||
is RegularFile -> {
|
||||
this.file.name
|
||||
}
|
||||
|
||||
is ZipFile -> {
|
||||
this.entry.name
|
||||
}
|
||||
@@ -59,6 +60,7 @@ sealed class FileType {
|
||||
is RegularFile -> {
|
||||
this.file.extension
|
||||
}
|
||||
|
||||
is ZipFile -> {
|
||||
this.entry.name.substringAfterLast(".")
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ object ExtensionsList {
|
||||
this[ExtensionTable.hasUpdate] = true
|
||||
updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
|
||||
}
|
||||
|
||||
foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode] -> {
|
||||
// somehow the user installed an invalid version
|
||||
this[ExtensionTable.isObsolete] = true
|
||||
|
||||
@@ -150,6 +150,7 @@ object KoreaderSyncService {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
KoreaderSyncChecksumMethod.FILENAME -> {
|
||||
logger.debug { "[KOSYNC HASH] No hash for chapterId=$chapterId. Generating from filename." }
|
||||
(ChapterTable innerJoin MangaTable)
|
||||
|
||||
@@ -69,56 +69,82 @@ class Anilist(
|
||||
when (trackPreferences.getScoreType(this)) {
|
||||
// 10 point
|
||||
POINT_10 -> IntRange(0, 10).map(Int::toString)
|
||||
|
||||
// 100 point
|
||||
POINT_100 -> IntRange(0, 100).map(Int::toString)
|
||||
|
||||
// 5 stars
|
||||
POINT_5 -> IntRange(0, 5).map { "$it ★" }
|
||||
|
||||
// Smiley
|
||||
POINT_3 -> listOf("-", "😦", "😐", "😊")
|
||||
|
||||
// 10 point decimal
|
||||
POINT_10_DECIMAL -> IntRange(0, 100).map { (it / 10f).toString() }
|
||||
|
||||
else -> throw Exception("Unknown score type")
|
||||
}
|
||||
|
||||
override fun indexToScore(index: Int): Double =
|
||||
when (trackPreferences.getScoreType(this)) {
|
||||
// 10 point
|
||||
POINT_10 -> index * 10.0
|
||||
POINT_10 -> {
|
||||
index * 10.0
|
||||
}
|
||||
|
||||
// 100 point
|
||||
POINT_100 -> index.toDouble()
|
||||
POINT_100 -> {
|
||||
index.toDouble()
|
||||
}
|
||||
|
||||
// 5 stars
|
||||
POINT_5 ->
|
||||
POINT_5 -> {
|
||||
when (index) {
|
||||
0 -> 0.0
|
||||
else -> index * 20.0 - 10.0
|
||||
}
|
||||
}
|
||||
|
||||
// Smiley
|
||||
POINT_3 ->
|
||||
POINT_3 -> {
|
||||
when (index) {
|
||||
0 -> 0.0
|
||||
else -> index * 25.0 + 10.0
|
||||
}
|
||||
}
|
||||
|
||||
// 10 point decimal
|
||||
POINT_10_DECIMAL -> index.toDouble()
|
||||
else -> throw Exception("Unknown score type")
|
||||
POINT_10_DECIMAL -> {
|
||||
index.toDouble()
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw Exception("Unknown score type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun displayScore(track: Track): String {
|
||||
val score = track.score
|
||||
return when (val type = trackPreferences.getScoreType(this)) {
|
||||
POINT_5 ->
|
||||
POINT_5 -> {
|
||||
when (score) {
|
||||
0.0 -> "0 ★"
|
||||
else -> "${((score + 10) / 20).toInt()} ★"
|
||||
}
|
||||
POINT_3 ->
|
||||
}
|
||||
|
||||
POINT_3 -> {
|
||||
when {
|
||||
score == 0.0 -> "0"
|
||||
score <= 35 -> "😦"
|
||||
score <= 60 -> "😐"
|
||||
else -> "😊"
|
||||
}
|
||||
else -> track.toApiScore(type)
|
||||
}
|
||||
|
||||
else -> {
|
||||
track.toApiScore(type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,17 @@ fun Track.toApiStatus() =
|
||||
fun Track.toApiScore(scoreType: String?): String =
|
||||
when (scoreType) {
|
||||
// 10 point
|
||||
"POINT_10" -> (score.toInt() / 10).toString()
|
||||
"POINT_10" -> {
|
||||
(score.toInt() / 10).toString()
|
||||
}
|
||||
|
||||
// 100 point
|
||||
"POINT_100" -> score.toInt().toString()
|
||||
"POINT_100" -> {
|
||||
score.toInt().toString()
|
||||
}
|
||||
|
||||
// 5 stars
|
||||
"POINT_5" ->
|
||||
"POINT_5" -> {
|
||||
when {
|
||||
score == 0.0 -> "0"
|
||||
score < 30 -> "1"
|
||||
@@ -29,15 +35,24 @@ fun Track.toApiScore(scoreType: String?): String =
|
||||
score < 90 -> "4"
|
||||
else -> "5"
|
||||
}
|
||||
}
|
||||
|
||||
// Smiley
|
||||
"POINT_3" ->
|
||||
"POINT_3" -> {
|
||||
when {
|
||||
score == 0.0 -> "0"
|
||||
score <= 35 -> ":("
|
||||
score <= 60 -> ":|"
|
||||
else -> ":)"
|
||||
}
|
||||
}
|
||||
|
||||
// 10 point decimal
|
||||
"POINT_10_DECIMAL" -> (score / 10).toString()
|
||||
else -> throw NotImplementedError("Unknown score type")
|
||||
"POINT_10_DECIMAL" -> {
|
||||
(score / 10).toString()
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw NotImplementedError("Unknown score type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,19 @@ class MangaUpdates(
|
||||
(0..10)
|
||||
.flatMap { decimal ->
|
||||
when (decimal) {
|
||||
0 -> listOf("-")
|
||||
10 -> listOf("10.0")
|
||||
else ->
|
||||
0 -> {
|
||||
listOf("-")
|
||||
}
|
||||
|
||||
10 -> {
|
||||
listOf("10.0")
|
||||
}
|
||||
|
||||
else -> {
|
||||
(0..9).map { fraction ->
|
||||
"$decimal.$fraction"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@ object UpdaterSocket : Websocket<UpdateStatus>() {
|
||||
|
||||
override fun handleRequest(ctx: WsMessageContext) {
|
||||
when (ctx.message()) {
|
||||
"STATUS" -> notifyClient(ctx, updater.statusDeprecated.value)
|
||||
else ->
|
||||
"STATUS" -> {
|
||||
notifyClient(ctx, updater.statusDeprecated.value)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ctx.send(
|
||||
"""
|
||||
|Invalid command.
|
||||
@@ -37,6 +40,7 @@ object UpdaterSocket : Websocket<UpdateStatus>() {
|
||||
|
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,10 @@ private fun getChapterDir(
|
||||
chapterEntry[ChapterTable.scanlator] != null -> {
|
||||
"${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
||||
}
|
||||
else -> chapterEntry[ChapterTable.name]
|
||||
|
||||
else -> {
|
||||
chapterEntry[ChapterTable.name]
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -319,6 +319,7 @@ object OpdsEntryBuilder {
|
||||
}
|
||||
titlePrefix = statusKey.localized(locale)
|
||||
}
|
||||
|
||||
is ProgressSource.Remote -> {
|
||||
idSuffix = ":remote"
|
||||
titlePrefix = MR.strings.opds_chapter_status_synced.localized(locale, progressSource.device)
|
||||
@@ -378,6 +379,7 @@ object OpdsEntryBuilder {
|
||||
}
|
||||
titleRes.localized(locale)
|
||||
}
|
||||
|
||||
progressSource is ProgressSource.Local -> {
|
||||
val titleRes =
|
||||
if (progressSource.lastPageRead > 0) {
|
||||
@@ -387,6 +389,7 @@ object OpdsEntryBuilder {
|
||||
}
|
||||
titleRes.localized(locale)
|
||||
}
|
||||
|
||||
progressSource is ProgressSource.Remote -> {
|
||||
val titleRes =
|
||||
if (progressSource.lastPageRead > 0) {
|
||||
@@ -396,7 +399,11 @@ object OpdsEntryBuilder {
|
||||
}
|
||||
titleRes.localized(locale, progressSource.device)
|
||||
}
|
||||
else -> "" // Should not happen
|
||||
|
||||
else -> {
|
||||
// Should not happen
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
links.add(
|
||||
|
||||
@@ -155,30 +155,40 @@ object OpdsFeedBuilder {
|
||||
|
||||
val feedTitle =
|
||||
when (criteria.primaryFilter) {
|
||||
PrimaryFilterType.SOURCE ->
|
||||
PrimaryFilterType.SOURCE -> {
|
||||
MR.strings.opds_feeds_library_source_specific_title.localized(
|
||||
locale,
|
||||
result.feedTitleComponent ?: criteria.sourceId.toString(),
|
||||
)
|
||||
PrimaryFilterType.CATEGORY ->
|
||||
}
|
||||
|
||||
PrimaryFilterType.CATEGORY -> {
|
||||
MR.strings.opds_feeds_category_specific_title.localized(
|
||||
locale,
|
||||
result.feedTitleComponent ?: criteria.categoryId.toString(),
|
||||
)
|
||||
PrimaryFilterType.GENRE ->
|
||||
}
|
||||
|
||||
PrimaryFilterType.GENRE -> {
|
||||
MR.strings.opds_feeds_genre_specific_title.localized(
|
||||
locale,
|
||||
result.feedTitleComponent ?: "Unknown",
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryFilterType.STATUS -> {
|
||||
val statusName = NavigationRepository.getStatuses(locale).find { it.id == criteria.statusId }?.title
|
||||
MR.strings.opds_feeds_status_specific_title.localized(locale, statusName ?: criteria.statusId.toString())
|
||||
}
|
||||
|
||||
PrimaryFilterType.LANGUAGE -> {
|
||||
val langName = Locale.forLanguageTag(criteria.langCode ?: "").getDisplayName(locale)
|
||||
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 =
|
||||
|
||||
@@ -108,7 +108,7 @@ object MangaRepository {
|
||||
// Efficiently get the name of the primary filter item
|
||||
val specificFilterName =
|
||||
when (criteria.primaryFilter) {
|
||||
PrimaryFilterType.SOURCE ->
|
||||
PrimaryFilterType.SOURCE -> {
|
||||
criteria.sourceId?.let {
|
||||
SourceTable
|
||||
.select(SourceTable.name)
|
||||
@@ -116,7 +116,9 @@ object MangaRepository {
|
||||
.firstOrNull()
|
||||
?.get(SourceTable.name)
|
||||
}
|
||||
PrimaryFilterType.CATEGORY ->
|
||||
}
|
||||
|
||||
PrimaryFilterType.CATEGORY -> {
|
||||
criteria.categoryId?.let {
|
||||
CategoryTable
|
||||
.select(CategoryTable.name)
|
||||
@@ -124,10 +126,25 @@ object MangaRepository {
|
||||
.firstOrNull()
|
||||
?.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
|
||||
else -> null
|
||||
}
|
||||
|
||||
PrimaryFilterType.GENRE -> {
|
||||
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)
|
||||
|
||||
@@ -44,10 +44,18 @@ object OpdsStringUtil {
|
||||
if (serverConfig.opdsUseBinaryFileSizes.value) {
|
||||
// Binary notation (base 1024)
|
||||
when {
|
||||
size >= 1_125_899_906_842_624 -> "%.2f TiB".format(size / 1_125_899_906_842_624.0) // 1024^4
|
||||
size >= 1_073_741_824 -> "%.2f GiB".format(size / 1_073_741_824.0) // 1024^3
|
||||
size >= 1_048_576 -> "%.2f MiB".format(size / 1_048_576.0) // 1024^2
|
||||
size >= 1024 -> "%.2f KiB".format(size / 1024.0) // 1024
|
||||
// 1024^4
|
||||
size >= 1_125_899_906_842_624 -> "%.2f TiB".format(size / 1_125_899_906_842_624.0)
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -52,6 +52,7 @@ object DBManager {
|
||||
addDataSourceProperty("cachePrepStmts", "true")
|
||||
addDataSourceProperty("useServerPrepStmts", "true")
|
||||
}
|
||||
|
||||
DatabaseType.H2 -> {
|
||||
jdbcUrl = "jdbc:h2:${applicationDirs.dataRoot}/database"
|
||||
driverClassName = "org.h2.Driver"
|
||||
@@ -103,7 +104,7 @@ object DBManager {
|
||||
.connect(hikariDataSource!!, databaseConfig = dbConfig)
|
||||
} else {
|
||||
when (serverConfig.databaseType.value) {
|
||||
DatabaseType.POSTGRESQL ->
|
||||
DatabaseType.POSTGRESQL -> {
|
||||
Database.connect(
|
||||
"jdbc:${serverConfig.databaseUrl.value}",
|
||||
"org.postgresql.Driver",
|
||||
@@ -111,12 +112,15 @@ object DBManager {
|
||||
password = serverConfig.databasePassword.value,
|
||||
databaseConfig = dbConfig,
|
||||
)
|
||||
DatabaseType.H2 ->
|
||||
}
|
||||
|
||||
DatabaseType.H2 -> {
|
||||
Database.connect(
|
||||
"jdbc:h2:${Injekt.get<ApplicationDirs>().dataRoot}/database",
|
||||
"org.h2.Driver",
|
||||
databaseConfig = dbConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}.also { db = it }
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import suwayomi.tachidesk.server.serverConfig
|
||||
val UNLIMITED_TEXT
|
||||
get() =
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,14 @@ fun UserType.requireUser(): Int =
|
||||
|
||||
fun UserType.requireUserWithBasicFallback(ctx: Context): Int =
|
||||
when (this) {
|
||||
is UserType.Admin -> id
|
||||
UserType.Visitor if ctx.getAttribute(Attribute.TachideskBasic) -> 1
|
||||
is UserType.Admin -> {
|
||||
id
|
||||
}
|
||||
|
||||
UserType.Visitor if ctx.getAttribute(Attribute.TachideskBasic) -> {
|
||||
1
|
||||
}
|
||||
|
||||
UserType.Visitor -> {
|
||||
ctx.header("WWW-Authenticate", "Basic")
|
||||
throw UnauthorizedException()
|
||||
@@ -53,8 +59,14 @@ fun getUserFromContext(ctx: Context): UserType {
|
||||
|
||||
return when (serverConfig.authMode.value) {
|
||||
// NOTE: Basic Auth is expected to have been validated by JavalinSetup
|
||||
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1)
|
||||
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor
|
||||
AuthMode.NONE, AuthMode.BASIC_AUTH -> {
|
||||
UserType.Admin(1)
|
||||
}
|
||||
|
||||
AuthMode.SIMPLE_LOGIN -> {
|
||||
if (cookieValid()) UserType.Admin(1) else UserType.Visitor
|
||||
}
|
||||
|
||||
AuthMode.UI_LOGIN -> {
|
||||
val authentication = ctx.header(Header.AUTHORIZATION) ?: ctx.cookie("suwayomi-server-token")
|
||||
val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token")
|
||||
@@ -72,8 +84,14 @@ fun getUserFromWsContext(ctx: WsConnectContext): UserType {
|
||||
|
||||
return when (serverConfig.authMode.value) {
|
||||
// NOTE: Basic Auth is expected to have been validated by JavalinSetup
|
||||
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1)
|
||||
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor
|
||||
AuthMode.NONE, AuthMode.BASIC_AUTH -> {
|
||||
UserType.Admin(1)
|
||||
}
|
||||
|
||||
AuthMode.SIMPLE_LOGIN -> {
|
||||
if (cookieValid()) UserType.Admin(1) else UserType.Visitor
|
||||
}
|
||||
|
||||
AuthMode.UI_LOGIN -> {
|
||||
val authentication =
|
||||
ctx.header(Header.AUTHORIZATION) ?: ctx.header("Sec-WebSocket-Protocol") ?: ctx.cookie("suwayomi-server-token")
|
||||
|
||||
@@ -72,6 +72,7 @@ object AppMutex {
|
||||
AppMutexState.Clear -> {
|
||||
logger.info { "Mutex status is clear, Resuming startup." }
|
||||
}
|
||||
|
||||
AppMutexState.TachideskInstanceRunning -> {
|
||||
logger.info { "Another instance of Suwayomi-Server is running on $appIP:${serverConfig.port.value}" }
|
||||
|
||||
@@ -82,6 +83,7 @@ object AppMutex {
|
||||
|
||||
shutdownApp(MutexCheckFailedTachideskRunning)
|
||||
}
|
||||
|
||||
AppMutexState.OtherApplicationRunning -> {
|
||||
logger.error { "A non Suwayomi-Server application is running on $appIP:${serverConfig.port.value}, aborting startup." }
|
||||
shutdownApp(MutexCheckFailedAnotherAppRunning)
|
||||
|
||||
@@ -42,12 +42,30 @@ fun <T> getParam(
|
||||
}
|
||||
val typedItem: Any? =
|
||||
when (val clazz = param.clazz as Class<T>) {
|
||||
String::class.java, java.lang.String::class.java -> getSimpleParamItem(ctx, param) ?: param.defaultValue
|
||||
Int::class.java, java.lang.Integer::class.java -> 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
|
||||
String::class.java, java.lang.String::class.java -> {
|
||||
getSimpleParamItem(ctx, param) ?: param.defaultValue
|
||||
}
|
||||
|
||||
Int::class.java, java.lang.Integer::class.java -> {
|
||||
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 -> {
|
||||
when (param) {
|
||||
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.content.Context
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.github.reactivecircus.cache4k.Cache
|
||||
@@ -193,7 +194,7 @@ object WebInterfaceManager {
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
GlobalScope.launchIO {
|
||||
setupWebUI()
|
||||
isSetupComplete = true
|
||||
}
|
||||
@@ -400,7 +401,7 @@ object WebInterfaceManager {
|
||||
try {
|
||||
downloadVersion(flavor, getVersion())
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
} ||
|
||||
isLocalWebUIValid
|
||||
@@ -427,7 +428,7 @@ object WebInterfaceManager {
|
||||
|
||||
try {
|
||||
setupBundledWebUI()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
throw Exception("Unable to setup a webUI")
|
||||
}
|
||||
}
|
||||
@@ -498,7 +499,7 @@ object WebInterfaceManager {
|
||||
private fun getLocalVersion(path: String = applicationDirs.webUIRoot): String =
|
||||
try {
|
||||
File("$path/revision").readText().trim()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
"r-1"
|
||||
}
|
||||
|
||||
@@ -585,7 +586,7 @@ object WebInterfaceManager {
|
||||
.string()
|
||||
.trim()
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
@@ -712,7 +713,7 @@ object WebInterfaceManager {
|
||||
version: String,
|
||||
) {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
GlobalScope.launchIO {
|
||||
downloadVersion(flavor, version)
|
||||
serveWebUI()
|
||||
}
|
||||
|
||||
@@ -61,9 +61,11 @@ class TestExtensionCompatibility {
|
||||
it.obsolete -> {
|
||||
uninstallExtension(it.pkgName)
|
||||
}
|
||||
|
||||
it.hasUpdate -> {
|
||||
updateExtension(it.pkgName)
|
||||
}
|
||||
|
||||
else -> {
|
||||
uninstallExtension(it.pkgName)
|
||||
installExtension(it.pkgName)
|
||||
|
||||
Reference in New Issue
Block a user