Compare commits

...

15 Commits

Author SHA1 Message Date
Aria Moradi 819ceba17d bump version
CI Publish / Validate Gradle Wrapper (push) Successful in 12s
CI Publish / Build artifacts and release (push) Failing after 16s
2021-09-28 00:52:49 +03:30
Aria Moradi 0aa0d62e03 update changelog file and it's template 2021-09-28 00:51:05 +03:30
Aria Moradi b3e2a35880 update WebUI 2021-09-28 00:50:33 +03:30
Aria Moradi 15ec20c65d fix sorting 2021-09-27 20:27:40 +03:30
Aria Moradi d4d6d7e12f add recentChapters endpoint 2021-09-27 18:27:05 +03:30
Aria Moradi 2e7a4f1421 remove no longer relevant comment 2021-09-27 14:44:48 +03:30
Aria Moradi ab8a52faf3 rename ChapterTable.chapterIndex to ChapterTable.sourceOrder 2021-09-27 14:36:06 +03:30
Aria Moradi bd465559fb Update README.md 2021-09-26 23:48:29 +03:30
Aria Moradi 13ec45a95c aftermath of adding kotlinter to all modules 2021-09-25 04:34:02 +03:30
Mitchell Syer 13b034875b Workaround StdLib issue and add KtLint to all modules (#206)
* Workaround buildconfig kotlin stdlib issue

* Add KtLint to all modules
2021-09-25 04:31:03 +03:30
Aria Moradi bb701fb088 fix macOS-arm64 java path 2021-09-24 14:06:19 +03:30
Aria Moradi b367414865 changes 2021-09-24 13:56:26 +03:30
Aria Moradi 4b00eec608 update CHANGELOG 2021-09-19 18:01:13 +04:30
Aria Moradi 5e11b51152 update CHANGELOG 2021-09-19 17:59:37 +04:30
Aria Moradi 9fb43b996e CHANGELOG update 2021-09-19 17:39:28 +04:30
48 changed files with 319 additions and 196 deletions
@@ -58,7 +58,6 @@ open class ConfigManager {
ConfigFactory.parseFile(it)
}
val config = ConfigFactory.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
@@ -16,5 +16,5 @@ fun setLogLevel(level: Level) {
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
}
fun debugLogsEnabled(config: Config)
= System.getProperty("suwayomi.tachidesk.config.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
fun debugLogsEnabled(config: Config) =
System.getProperty("suwayomi.tachidesk.config.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
@@ -9,8 +9,10 @@ import android.content.Context
class PreferenceManager {
companion object {
@JvmStatic
fun getDefaultSharedPreferences(context: Context)
= context.getSharedPreferences(context.applicationInfo.packageName,
Context.MODE_PRIVATE)!!
fun getDefaultSharedPreferences(context: Context) =
context.getSharedPreferences(
context.applicationInfo.packageName,
Context.MODE_PRIVATE
)!!
}
}
@@ -13,7 +13,7 @@ class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
val debug: Boolean by config
companion object {
fun register(config: Config)
= ApplicationInfoConfigModule(config.getConfig("android.app"))
fun register(config: Config) =
ApplicationInfoConfigModule(config.getConfig("android.app"))
}
}
@@ -28,7 +28,7 @@ class FilesConfigModule(config: Config) : ConfigModule(config) {
val packageDir: String by config
companion object {
fun register(config: Config)
= FilesConfigModule(config.getConfig("android.files"))
fun register(config: Config) =
FilesConfigModule(config.getConfig("android.files"))
}
}
@@ -1,8 +1,8 @@
package xyz.nulldev.androidcompat.config
import com.typesafe.config.Config
import xyz.nulldev.ts.config.ConfigModule
import io.github.config4k.getValue
import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(val config: Config) : ConfigModule(config) {
val isDebuggable: Boolean by config
@@ -16,7 +16,7 @@ class SystemConfigModule(val config: Config) : ConfigModule(config) {
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
companion object {
fun register(config: Config)
= SystemConfigModule(config.getConfig("android.system"))
fun register(config: Config) =
SystemConfigModule(config.getConfig("android.system"))
}
}
@@ -75,8 +75,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(cachedFindColumn(column))
}
private fun cachedFindColumn(column: String?)
= columnCache.getOrPut(column!!, {
private fun cachedFindColumn(column: String?) =
columnCache.getOrPut(column!!, {
findColumn(column)
})
@@ -14,8 +14,6 @@ import java.io.File
import javax.imageio.ImageIO
import javax.xml.parsers.DocumentBuilderFactory
data class InstalledPackage(val root: File) {
val apk = File(root, "package.apk")
val jar = File(root, "translated.jar")
@@ -40,13 +38,17 @@ data class InstalledPackage(val root: File) {
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue)
putString(
it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue
)
}
}
it.signatures = (parsed.apkSingers.flatMap { it.certificateMetas }
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
it.signatures = (
parsed.apkSingers.flatMap { it.certificateMetas }
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
.map { Signature(it.data) }.toTypedArray()
}
@@ -64,5 +64,4 @@ object KodeinGlobalHelper {
fun <T : Any> instance(type: Class<T>): T {
return instance(type, null)
}
}
+2 -20
View File
@@ -2,27 +2,9 @@
## TL;DR
<!-- TODO: fill before release -->
## Tachidesk-Server
### Public API
#### Non-breaking changes
- N/A
#### Breaking changes
- N/A
#### Bug fixes
- N/A
### Private API
## Tachidesk-Server Changelog
- N/A
## Tachidesk-WebUI
#### Visible changes
- N/A
#### Bug fixes
- N/A
#### Internal changes
## Tachidesk-WebUI Changelog
- N/A
+20 -3
View File
@@ -1,3 +1,19 @@
# Server: v0.5.3 + WebUI: r809
## TL;DR
<!-- TODO: fill before release -->
## Tachidesk-Server Changelog
- (r956) fix macOS-arm64 bundle launchers not working
- (r957) Workaround StdLib issue and add KtLint to all modules ([#206](https://github.com/Suwayomi/Tachidesk-Server/pull/206) by @Syer10)
- (r960-r963) Add recently updated chapters(Updates) endpoint
## Tachidesk-WebUI Changelog
- (r808) fix chapter list not calling onlineFetch=true
- (r809) add support for Updates
# Server: v0.5.2 + WebUI: r807
## TL;DR
- Fixed Local source not working on Windows
@@ -12,11 +28,12 @@
- N/A
#### Bug fixes
- N/A
- (r948) Fix ManaToki (KO) and NewToki (KO) (issue [#202](https://github.com/Suwayomi/Tachidesk-Server/issue/202))
- (r949) Local source: fix windows paths
### Private API
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Tachidesk-WebUI/pull/199) by @Syer10)
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Tachidesk-WebUI/pull/200) by @Syer10)
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Tachidesk-Server/pull/200) by @Syer10)
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Tachidesk-Server/pull/199) by @Syer10)
## Tachidesk-WebUI
+10 -10
View File
@@ -3,16 +3,7 @@
|-------|----------|---------|---------|
| ![CI](https://github.com/Suwayomi/Tachidesk/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Tachidesk.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Tachidesk/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Tachidesk-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) |
# Tachidesk-Server is a server app! You may not want to Download Tachidesk-Server directly.
Yes, you need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.
Here's a list of known clients/user interfaces for Tachidesk-Server:
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The "official" front-end for Tachidesk-Server, A native desktop Application.
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/electrion front-end that Tachidesk-Server is traditionally shipped with.
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
# What is Tachidesk then?
# What is Tachidesk?
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
A free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
@@ -23,6 +14,15 @@ Tachidesk-Server is as multi-platform as you can get. Any platform that runs jav
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
# Tachidesk-Server is a server app! You may not want to Download Tachidesk-Server directly.
Yes, you need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.
Here's a list of known clients/user interfaces for Tachidesk-Server:
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The "official" front-end for Tachidesk-Server, A native desktop Application.
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/electrion front-end that Tachidesk-Server is traditionally shipped with.
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
## Is this application usable? Should I test it?
Here is a list of current features:
+3
View File
@@ -6,6 +6,7 @@ plugins {
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
id("org.jmailen.kotlinter") version "3.6.0"
id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
}
allprojects {
@@ -29,6 +30,7 @@ val projects = listOf(
configure(projects) {
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
apply(plugin = "org.jmailen.kotlinter")
java {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -37,6 +39,7 @@ configure(projects) {
tasks {
withType<KotlinCompile> {
dependsOn(formatKotlin)
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs = listOf(
+2 -2
View File
@@ -12,9 +12,9 @@ const val kotlinVersion = "1.5.30"
const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.2"
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.3"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r807"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r809"
// counts commits on the master branch
val tachideskRevision = runCatching {
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
@@ -24,7 +24,7 @@ elif [ $1 = "macOS-arm64" ]; then
jre="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64.tar.gz"
jre_release="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64"
jre_url="https://cdn.azul.com/zulu/bin/$jre"
jre_dir="$jre_release"
jre_dir="$jre_release/zulu-8.jre"
electron="electron-$electron_version-darwin-arm64.zip"
else
echo "Unsupported arch value: $1"
+3 -10
View File
@@ -1,11 +1,10 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import de.undercouch.gradle.tasks.download.Download
import java.time.Instant
plugins {
application
id("com.github.johnrengelman.shadow") version "7.0.0"
id("com.github.gmazzo.buildconfig") version "3.0.3"
id("com.github.gmazzo.buildconfig")
}
dependencies {
@@ -70,6 +69,7 @@ dependencies {
// uncomment to test extensions directly
// implementation(fileTree("lib/"))
implementation(kotlin("script-runtime"))
}
application {
@@ -127,20 +127,13 @@ tasks {
archiveBaseName.set(rootProject.name)
archiveVersion.set(tachideskVersion)
archiveClassifier.set(tachideskRevision)
destinationDirectory.set(File("$rootDir/server/build"))
}
test {
useJUnit()
}
withType<ShadowJar> {
destinationDirectory.set(File("$rootDir/server/build"))
}
named("run") {
dependsOn(":formatKotlin", ":lintKotlin")
}
named<Copy>("processResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mustRunAfter("downloadWebUI")
@@ -440,7 +440,6 @@ class LocalSource : HttpSource() {
private object FileSystemInterceptor : Interceptor {
fun fakeUrlFrom(path: String): String = "http://$path"
private fun restoreFilePath(url: String): String {
val path = URLDecoder.decode(url.replaceFirst("http://", ""), "UTF-8")
@@ -19,6 +19,7 @@ import suwayomi.tachidesk.manga.controller.DownloadController
import suwayomi.tachidesk.manga.controller.ExtensionController
import suwayomi.tachidesk.manga.controller.MangaController
import suwayomi.tachidesk.manga.controller.SourceController
import suwayomi.tachidesk.manga.controller.UpdateController
object MangaAPI {
fun defineEndpoints() {
@@ -106,5 +107,9 @@ object MangaAPI {
get("{mangaId}/chapter/{chapterIndex}", DownloadController::queueChapter)
delete("{mangaId}/chapter/{chapterIndex}", DownloadController::unqueueChapter)
}
path("update") {
get("recentChapters", UpdateController::recentChapters)
}
}
}
@@ -0,0 +1,23 @@
package suwayomi.tachidesk.manga.controller
import io.javalin.http.Context
import suwayomi.tachidesk.manga.impl.Chapter
import suwayomi.tachidesk.server.JavalinSetup.future
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
object UpdateController {
/** get recently updated manga chapters */
fun recentChapters(ctx: Context) {
ctx.future(
future {
Chapter.getRecentChapters()
}
)
}
}
@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.SortOrder.DESC
import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
@@ -25,6 +25,7 @@ import suwayomi.tachidesk.manga.impl.util.getChapterDir
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -40,7 +41,7 @@ object Chapter {
getSourceChapters(mangaId)
} else {
transaction {
ChapterTable.select { ChapterTable.manga eq mangaId }.orderBy(ChapterTable.chapterIndex to DESC)
ChapterTable.select { ChapterTable.manga eq mangaId }.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
.map {
ChapterTable.toDataClass(it)
}
@@ -68,6 +69,7 @@ object Chapter {
}
val chapterCount = chapterList.count()
var now = Instant.now().epochSecond
transaction {
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
@@ -80,7 +82,8 @@ object Chapter {
it[chapter_number] = fetchedChapter.chapter_number
it[scanlator] = fetchedChapter.scanlator
it[chapterIndex] = index + 1
it[sourceOrder] = index + 1
it[fetchedAt] = now++
it[ChapterTable.manga] = mangaId
}
} else {
@@ -90,7 +93,7 @@ object Chapter {
it[chapter_number] = fetchedChapter.chapter_number
it[scanlator] = fetchedChapter.scanlator
it[chapterIndex] = index + 1
it[sourceOrder] = index + 1
it[ChapterTable.manga] = mangaId
}
}
@@ -103,8 +106,8 @@ object Chapter {
val dbChapterList = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.toList() }
dbChapterList.forEach {
if (it[ChapterTable.chapterIndex] >= chapterList.size ||
chapterList[it[ChapterTable.chapterIndex] - 1].url != it[ChapterTable.url]
if (it[ChapterTable.sourceOrder] >= chapterList.size ||
chapterList[it[ChapterTable.sourceOrder] - 1].url != it[ChapterTable.url]
) {
transaction {
PageTable.deleteWhere { PageTable.chapter eq it[ChapterTable.id] }
@@ -137,6 +140,7 @@ object Chapter {
dbChapter[ChapterTable.lastReadAt],
chapterCount - index,
dbChapter[ChapterTable.fetchedAt],
dbChapter[ChapterTable.isDownloaded],
dbChapter[ChapterTable.pageCount],
@@ -151,7 +155,7 @@ object Chapter {
suspend fun getChapter(chapterIndex: Int, mangaId: Int): ChapterDataClass {
val chapterEntry = transaction {
ChapterTable.select {
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
}.first()
}
@@ -159,7 +163,7 @@ object Chapter {
chapterEntry[ChapterTable.isDownloaded] && firstPageExists(mangaId, chapterEntry[ChapterTable.id].value)
return if (!isReallyDownloaded) {
transaction {
ChapterTable.update({ (ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId) }) {
ChapterTable.update({ (ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId) }) {
it[isDownloaded] = false
}
}
@@ -203,7 +207,7 @@ object Chapter {
val pageCount = pageList.count()
transaction {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }) {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }) {
it[ChapterTable.pageCount] = pageCount
}
}
@@ -219,7 +223,8 @@ object Chapter {
chapterEntry[ChapterTable.lastPageRead],
chapterEntry[ChapterTable.lastReadAt],
chapterEntry[ChapterTable.chapterIndex],
chapterEntry[ChapterTable.sourceOrder],
chapterEntry[ChapterTable.fetchedAt],
chapterEntry[ChapterTable.isDownloaded],
pageCount,
chapterCount.toInt(),
@@ -249,7 +254,7 @@ object Chapter {
) {
transaction {
if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }) { update ->
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }) { update ->
isRead?.also {
update[ChapterTable.isRead] = it
}
@@ -264,7 +269,7 @@ object Chapter {
}
markPrevRead?.let {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex less chapterIndex) }) {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder less chapterIndex) }) {
it[ChapterTable.isRead] = markPrevRead
}
}
@@ -281,7 +286,7 @@ object Chapter {
fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) {
transaction {
val chapterId =
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
.first()[ChapterTable.id].value
val meta =
transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } }.firstOrNull()
@@ -302,16 +307,30 @@ object Chapter {
fun deleteChapter(mangaId: Int, chapterIndex: Int) {
transaction {
val chapterId =
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
.first()[ChapterTable.id].value
val chapterDir = getChapterDir(mangaId, chapterId)
File(chapterDir).deleteRecursively()
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }) {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }) {
it[isDownloaded] = false
}
}
}
fun getRecentChapters(): List<MangaChapterDataClass> {
return transaction {
(ChapterTable innerJoin MangaTable)
.select { (MangaTable.inLibrary eq true) and (ChapterTable.fetchedAt greater MangaTable.inLibraryAt) }
.orderBy(ChapterTable.fetchedAt to SortOrder.DESC)
.map {
MangaChapterDataClass(
MangaTable.toDataClass(it),
ChapterTable.toDataClass(it)
)
}
}
}
}
@@ -16,6 +16,7 @@ import suwayomi.tachidesk.manga.impl.Manga.getManga
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
import suwayomi.tachidesk.manga.model.table.CategoryTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import java.time.Instant
object Library {
suspend fun addMangaToLibrary(mangaId: Int) {
@@ -25,8 +26,9 @@ object Library {
val defaultCategories = CategoryTable.select { CategoryTable.isDefault eq true }.toList()
MangaTable.update({ MangaTable.id eq manga.id }) {
it[MangaTable.inLibrary] = true
it[MangaTable.defaultCategory] = defaultCategories.isEmpty()
it[inLibrary] = true
it[inLibraryAt] = Instant.now().epochSecond
it[defaultCategory] = defaultCategories.isEmpty()
}
defaultCategories.forEach { category ->
@@ -61,6 +61,7 @@ object Manga {
mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt],
getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaId),
mangaEntry[MangaTable.realUrl],
@@ -121,6 +122,7 @@ object Manga {
fetchedManga.genre.toGenreList(),
MangaStatus.valueOf(fetchedManga.status).name,
mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt],
getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaId),
mangaEntry[MangaTable.realUrl],
@@ -81,6 +81,7 @@ object MangaList {
manga.genre.toGenreList(),
MangaStatus.valueOf(manga.status).name,
false, // It's a new manga entry
0,
meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl],
freshData = true
@@ -103,6 +104,7 @@ object MangaList {
mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt],
meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl],
freshData = false
@@ -46,7 +46,7 @@ object Page {
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
val chapterEntry = transaction {
ChapterTable.select {
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
}.first()
}
val chapterId = chapterEntry[ChapterTable.id].value
@@ -160,7 +160,7 @@ object ProtoBackupImport : ProtoBackupBase() {
it[chapter_number] = chapter.chapter_number
it[scanlator] = chapter.scanlator
it[chapterIndex] = chaptersLength - chapter.source_order
it[sourceOrder] = chaptersLength - chapter.source_order
it[ChapterTable.manga] = mangaId
it[isRead] = chapter.read
@@ -207,7 +207,7 @@ object ProtoBackupImport : ProtoBackupBase() {
it[chapter_number] = chapter.chapter_number
it[scanlator] = chapter.scanlator
it[chapterIndex] = chaptersLength - chapter.source_order
it[sourceOrder] = chaptersLength - chapter.source_order
it[ChapterTable.manga] = mangaId
it[isRead] = chapter.read
@@ -77,7 +77,7 @@ object DownloadManager {
mangaId,
chapter = ChapterTable.toDataClass(
transaction {
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
.first()
}
)
@@ -61,7 +61,7 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
}
download.state = Finished
transaction {
ChapterTable.update({ (ChapterTable.manga eq download.mangaId) and (ChapterTable.chapterIndex eq download.chapterIndex) }) {
ChapterTable.update({ (ChapterTable.manga eq download.mangaId) and (ChapterTable.sourceOrder eq download.chapterIndex) }) {
it[isDownloaded] = true
}
}
@@ -27,9 +27,13 @@ data class ChapterDataClass(
/** last read page, zero means not read/no data */
val lastReadAt: Long,
// TODO(v0.6.0): rename to sourceOrder
/** this chapter's index, starts with 1 */
val index: Int,
/** the date we fist saw this chapter*/
val fetchedAt: Long,
/** is chapter downloaded */
val downloaded: Boolean,
@@ -0,0 +1,13 @@
package suwayomi.tachidesk.manga.model.dataclass
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
data class MangaChapterDataClass(
val manga: MangaDataClass,
val chapter: ChapterDataClass,
)
@@ -26,6 +26,7 @@ data class MangaDataClass(
val genre: List<String> = emptyList(),
val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false,
val inLibraryAt: Long = 0,
val source: SourceDataClass? = null,
/** meta data for clients */
@@ -25,9 +25,9 @@ object ChapterTable : IntIdTable() {
val isBookmarked = bool("bookmark").default(false)
val lastPageRead = integer("last_page_read").default(0)
val lastReadAt = long("last_read_at").default(0)
val fetchedAt = long("fetched_at").default(0)
// index is reserved by a function
val chapterIndex = integer("index")
val sourceOrder = integer("source_order")
val isDownloaded = bool("is_downloaded").default(false)
@@ -48,7 +48,8 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
chapterEntry[isBookmarked],
chapterEntry[lastPageRead],
chapterEntry[lastReadAt],
chapterEntry[chapterIndex],
chapterEntry[sourceOrder],
chapterEntry[fetchedAt],
chapterEntry[isDownloaded],
chapterEntry[pageCount],
transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() },
@@ -31,6 +31,7 @@ object MangaTable : IntIdTable() {
val inLibrary = bool("in_library").default(false)
val defaultCategory = bool("default_category").default(true)
val inLibraryAt = long("in_library_at").default(0)
// the [source] field name is used by some ancestor of IntIdTable
val sourceReference = long("source")
@@ -56,6 +57,7 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) =
mangaEntry[genre].toGenreList(),
Companion.valueOf(mangaEntry[status]).name,
mangaEntry[inLibrary],
mangaEntry[inLibraryAt],
meta = getMangaMetaMap(mangaEntry[id].value),
realUrl = mangaEntry[realUrl],
)
@@ -0,0 +1,17 @@
package suwayomi.tachidesk.server.database.migration
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration
@Suppress("ClassName", "unused")
class M0016_ChapterIndexRenameToSourceOrder : SQLMigration() {
override val sql = """
ALTER TABLE CHAPTER ALTER COLUMN INDEX RENAME TO SOURCE_ORDER;
""".trimIndent()
}
@@ -0,0 +1,18 @@
package suwayomi.tachidesk.server.database.migration
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.AddColumnMigration
@Suppress("ClassName", "unused")
class M0017_ChapterFetchedAt : AddColumnMigration(
"Chapter",
"fetched_at",
"BIGINT",
"0"
)
@@ -0,0 +1,18 @@
package suwayomi.tachidesk.server.database.migration
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.AddColumnMigration
@Suppress("ClassName", "unused")
class M0018_MangaInLibraryAt : AddColumnMigration(
"Manga",
"in_library_at",
"BIGINT",
"0"
)