Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 819ceba17d | |||
| 0aa0d62e03 | |||
| b3e2a35880 | |||
| 15ec20c65d | |||
| d4d6d7e12f | |||
| 2e7a4f1421 | |||
| ab8a52faf3 | |||
| bd465559fb | |||
| 13ec45a95c | |||
| 13b034875b | |||
| bb701fb088 | |||
| b367414865 | |||
| 4b00eec608 | |||
| 5e11b51152 | |||
| 9fb43b996e |
@@ -14,7 +14,7 @@ const val CONFIG_PREFIX = "suwayomi.tachidesk.config"
|
||||
val ApplicationRootDir: String
|
||||
get(): String {
|
||||
return System.getProperty(
|
||||
"$CONFIG_PREFIX.server.rootDir",
|
||||
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
||||
"$CONFIG_PREFIX.server.rootDir",
|
||||
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.kodein.di.singleton
|
||||
|
||||
class ConfigKodeinModule {
|
||||
fun create() = DI.Module("ConfigManager") {
|
||||
//Config module
|
||||
// Config module
|
||||
bind<ConfigManager>() with singleton { GlobalConfigManager }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ open class ConfigManager {
|
||||
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
||||
val config by lazy { loadConfigs() }
|
||||
|
||||
//Public read-only view of modules
|
||||
// Public read-only view of modules
|
||||
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
||||
get() = generatedModules
|
||||
|
||||
@@ -42,29 +42,28 @@ open class ConfigManager {
|
||||
* Load configs
|
||||
*/
|
||||
fun loadConfigs(): Config {
|
||||
//Load reference configs
|
||||
// Load reference configs
|
||||
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||
val baseConfig =
|
||||
ConfigFactory.parseMap(
|
||||
mapOf(
|
||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
||||
)
|
||||
ConfigFactory.parseMap(
|
||||
mapOf(
|
||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
||||
)
|
||||
)
|
||||
|
||||
//Load user config
|
||||
// Load user config
|
||||
val userConfig =
|
||||
File(ApplicationRootDir, "server.conf").let {
|
||||
ConfigFactory.parseFile(it)
|
||||
}
|
||||
|
||||
File(ApplicationRootDir, "server.conf").let {
|
||||
ConfigFactory.parseFile(it)
|
||||
}
|
||||
|
||||
val config = ConfigFactory.empty()
|
||||
.withFallback(baseConfig)
|
||||
.withFallback(userConfig)
|
||||
.withFallback(compatConfig)
|
||||
.withFallback(serverConfig)
|
||||
.resolve()
|
||||
.withFallback(baseConfig)
|
||||
.withFallback(userConfig)
|
||||
.withFallback(compatConfig)
|
||||
.withFallback(serverConfig)
|
||||
.resolve()
|
||||
|
||||
// set log level early
|
||||
if (debugLogsEnabled(config)) {
|
||||
|
||||
@@ -20,7 +20,7 @@ abstract class ConfigModule(config: Config)
|
||||
/**
|
||||
* Abstract jvm-commandline-argument-overridable config module.
|
||||
*/
|
||||
abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String): ConfigModule(config) {
|
||||
abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String) : ConfigModule(config) {
|
||||
val overridableConfig = SystemPropertyOverrideDelegate(config, moduleName)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String)
|
||||
configValue.toString()
|
||||
)
|
||||
|
||||
return when(T::class.simpleName) {
|
||||
return when (T::class.simpleName) {
|
||||
"Int" -> combined.toInt()
|
||||
"Boolean" -> combined.toBoolean()
|
||||
// add more types as needed
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -3,4 +3,4 @@ package xyz.nulldev.ts.config.util
|
||||
import com.typesafe.config.Config
|
||||
|
||||
operator fun Config.get(key: String) = getString(key)
|
||||
?: throw IllegalStateException("Could not find value for config entry: $key!")
|
||||
?: throw IllegalStateException("Could not find value for config entry: $key!")
|
||||
|
||||
@@ -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
|
||||
)!!
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@ import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory
|
||||
|
||||
class RequerySQLiteOpenHelperFactory {
|
||||
fun create(configuration: SupportSQLiteOpenHelper.Configuration) = FrameworkSQLiteOpenHelperFactory().create(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ class AndroidCompat {
|
||||
application.attach(context)
|
||||
application.onCreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class AndroidCompatInitializer {
|
||||
fun init() {
|
||||
DI.global.addImport(AndroidCompatModule().create())
|
||||
|
||||
//Register config modules
|
||||
// Register config modules
|
||||
GlobalConfigManager.registerModules(
|
||||
FilesConfigModule.register(GlobalConfigManager.config),
|
||||
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
|
||||
|
||||
@@ -29,7 +29,7 @@ class AndroidCompatModule {
|
||||
|
||||
bind<PackageController>() with singleton { PackageController() }
|
||||
|
||||
//Context
|
||||
// Context
|
||||
bind<CustomContext>() with singleton { CustomContext() }
|
||||
bind<Context>() with singleton {
|
||||
val context: Context by DI.global.instance<CustomContext>()
|
||||
|
||||
+2
-2
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,26 @@ import xyz.nulldev.ts.config.ConfigModule
|
||||
*/
|
||||
|
||||
class FilesConfigModule(config: Config) : ConfigModule(config) {
|
||||
val dataDir:String by config
|
||||
val filesDir:String by config
|
||||
val noBackupFilesDir:String by config
|
||||
val dataDir: String by config
|
||||
val filesDir: String by config
|
||||
val noBackupFilesDir: String by config
|
||||
val externalFilesDirs: MutableList<String> by config
|
||||
val obbDirs: MutableList<String> by config
|
||||
val cacheDir:String by config
|
||||
val codeCacheDir:String by config
|
||||
val cacheDir: String by config
|
||||
val codeCacheDir: String by config
|
||||
val externalCacheDirs: MutableList<String> by config
|
||||
val externalMediaDirs: MutableList<String> by config
|
||||
val rootDir:String by config
|
||||
val externalStorageDir:String by config
|
||||
val downloadCacheDir:String by config
|
||||
val databasesDir:String by config
|
||||
val rootDir: String by config
|
||||
val externalStorageDir: String by config
|
||||
val downloadCacheDir: String by config
|
||||
val databasesDir: String by config
|
||||
|
||||
val prefsDir:String by config
|
||||
val prefsDir: String by config
|
||||
|
||||
val packageDir:String by 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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
|
||||
val parentMetadata = parent.metaData
|
||||
val columnCount = parentMetadata.columnCount
|
||||
val columnLabels = (1 .. columnCount).map {
|
||||
val columnLabels = (1..columnCount).map {
|
||||
parentMetadata.getColumnLabel(it)
|
||||
}.toTypedArray()
|
||||
|
||||
@@ -41,10 +41,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
// How can we optimize this?
|
||||
// We need to fill the cache as the set is loaded
|
||||
|
||||
//Fill cache
|
||||
while(parent.next()) {
|
||||
// Fill cache
|
||||
while (parent.next()) {
|
||||
cachedContent += ResultSetEntry().apply {
|
||||
for(i in 1 .. columnCount)
|
||||
for (i in 1..columnCount)
|
||||
data += parent.getObject(i)
|
||||
}
|
||||
resultSetLength++
|
||||
@@ -60,8 +60,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
private fun internalMove(row: Int) {
|
||||
if(cursor < 0) cursor = 0
|
||||
else if(cursor > resultSetLength + 1) cursor = resultSetLength + 1
|
||||
if (cursor < 0) cursor = 0
|
||||
else if (cursor > resultSetLength + 1) cursor = resultSetLength + 1
|
||||
else cursor = row
|
||||
}
|
||||
|
||||
@@ -75,10 +75,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
return obj(cachedFindColumn(column))
|
||||
}
|
||||
|
||||
private fun cachedFindColumn(column: String?)
|
||||
= columnCache.getOrPut(column!!, {
|
||||
findColumn(column)
|
||||
})
|
||||
private fun cachedFindColumn(column: String?) =
|
||||
columnCache.getOrPut(column!!, {
|
||||
findColumn(column)
|
||||
})
|
||||
|
||||
override fun getNClob(columnIndex: Int): NClob {
|
||||
return obj(columnIndex) as NClob
|
||||
@@ -157,27 +157,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getDate(columnIndex: Int): Date {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getDate(columnLabel: String?): Date {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getDate(columnIndex: Int, cal: Calendar?): Date {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getDate(columnLabel: String?, cal: Calendar?): Date {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun beforeFirst() {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -202,12 +202,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -236,22 +236,22 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getTime(columnIndex: Int): Time {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTime(columnLabel: String?): Time {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTime(columnIndex: Int, cal: Calendar?): Time {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTime(columnLabel: String?, cal: Calendar?): Time {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -272,28 +272,28 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun absolute(row: Int): Boolean {
|
||||
if(row > 0) {
|
||||
if (row > 0) {
|
||||
internalMove(row)
|
||||
} else {
|
||||
last()
|
||||
for(i in 1 .. row)
|
||||
for (i in 1..row)
|
||||
previous()
|
||||
}
|
||||
return cursorValid()
|
||||
}
|
||||
|
||||
override fun getSQLXML(columnIndex: Int): SQLXML? {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getSQLXML(columnLabel: String?): SQLXML? {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun <T : Any?> unwrap(iface: Class<T>?): T {
|
||||
if(thisIsWrapperFor(iface))
|
||||
if (thisIsWrapperFor(iface))
|
||||
return this as T
|
||||
else
|
||||
return parent.unwrap(iface)
|
||||
@@ -426,12 +426,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getBlob(columnIndex: Int): Blob {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getBlob(columnLabel: String?): Blob {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -500,12 +500,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -531,9 +531,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
private fun castToLong(obj: Any?): Long {
|
||||
if(obj == null) return 0
|
||||
else if(obj is Long) return obj
|
||||
else if(obj is Number) return obj.toLong()
|
||||
if (obj == null) return 0
|
||||
else if (obj is Long) return obj
|
||||
else if (obj is Number) return obj.toLong()
|
||||
else throw IllegalStateException("Object is not a long!")
|
||||
}
|
||||
|
||||
@@ -546,12 +546,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getClob(columnIndex: Int): Clob {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getClob(columnLabel: String?): Clob {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -604,12 +604,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getArray(columnIndex: Int): Array {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getArray(columnLabel: String?): Array {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -688,32 +688,32 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getTimestamp(columnIndex: Int): Timestamp {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTimestamp(columnLabel: String?): Timestamp {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getRef(columnIndex: Int): Ref {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getRef(columnLabel: String?): Ref {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -792,12 +792,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
}
|
||||
|
||||
override fun getRowId(columnIndex: Int): RowId {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override fun getRowId(columnLabel: String?): RowId {
|
||||
//TODO Maybe?
|
||||
// TODO Maybe?
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
@@ -848,4 +848,4 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
class ResultSetEntry {
|
||||
val data = mutableListOf<Any?>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -174,4 +174,4 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
javaPreferences.removeNode()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,20 +38,24 @@ 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
|
||||
.map { Signature(it.data) }.toTypedArray()
|
||||
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()
|
||||
}
|
||||
|
||||
fun verify(): Boolean {
|
||||
val res = ApkVerifier.Builder(apk)
|
||||
.build()
|
||||
.verify()
|
||||
.build()
|
||||
.verify()
|
||||
|
||||
return res.isVerified
|
||||
}
|
||||
@@ -69,7 +71,7 @@ data class InstalledPackage(val root: File) {
|
||||
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
|
||||
|
||||
ImageIO.write(read, "png", icon)
|
||||
} catch(e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
icon.delete()
|
||||
}
|
||||
}
|
||||
@@ -77,7 +79,7 @@ data class InstalledPackage(val root: File) {
|
||||
fun writeJar() {
|
||||
try {
|
||||
Dex2jar.from(apk).to(jar.toPath())
|
||||
} catch(e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
jar.delete()
|
||||
}
|
||||
}
|
||||
@@ -92,4 +94,4 @@ data class InstalledPackage(val root: File) {
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class PackageController {
|
||||
if (!installed.jar.exists()) {
|
||||
throw IllegalStateException("Failed to translate APK dex!")
|
||||
}
|
||||
} catch(t: Throwable) {
|
||||
} catch (t: Throwable) {
|
||||
root.deleteRecursively()
|
||||
throw t
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class PackageController {
|
||||
}
|
||||
|
||||
fun deletePackage(pack: InstalledPackage) {
|
||||
if(!pack.root.exists()) error("Package was never installed!")
|
||||
if (!pack.root.exists()) error("Package was never installed!")
|
||||
|
||||
val packageName = pack.info.packageName
|
||||
pack.root.deleteRecursively()
|
||||
@@ -74,7 +74,7 @@ class PackageController {
|
||||
|
||||
fun findPackage(packageName: String): InstalledPackage? {
|
||||
val file = File(androidFiles.packagesDir, packageName)
|
||||
return if(file.exists())
|
||||
return if (file.exists())
|
||||
InstalledPackage(file)
|
||||
else
|
||||
null
|
||||
@@ -84,4 +84,4 @@ class PackageController {
|
||||
val pkgName = ApkParsers.getMetaInfo(apkFile).packageName
|
||||
return findPackage(pkgName)?.jar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
||||
sourceDir = apk.absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ interface Resource {
|
||||
fun getType(): Class<out Resource>
|
||||
|
||||
fun getValue(): Any?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ class ServiceSupport {
|
||||
|
||||
runningServices[name] = service
|
||||
|
||||
//Setup service
|
||||
// Setup service
|
||||
thread {
|
||||
callOnCreate(service)
|
||||
//TODO Handle more complex cases
|
||||
// TODO Handle more complex cases
|
||||
service.onStartCommand(intent, 0, 0)
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ class ServiceSupport {
|
||||
fun stopService(name: String) {
|
||||
logger.debug { "Stopping service: $name" }
|
||||
val service = runningServices.remove(name)
|
||||
if(service == null) {
|
||||
if (service == null) {
|
||||
logger.warn { "An attempt was made to stop a service that is not running: $name" }
|
||||
} else {
|
||||
thread {
|
||||
@@ -63,6 +63,6 @@ class ServiceSupport {
|
||||
fun serviceInstanceFromClass(className: String): Service {
|
||||
val clazzObj = Class.forName(className)
|
||||
return clazzObj.getDeclaredConstructor().newInstance() as? Service
|
||||
?: throw IllegalArgumentException("$className is not a Service!")
|
||||
?: throw IllegalArgumentException("$className is not a Service!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ object KodeinGlobalHelper {
|
||||
@JvmStatic
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
|
||||
return when(type) {
|
||||
return when (type) {
|
||||
AndroidFiles::class.java -> {
|
||||
val instance: AndroidFiles by (kodein ?: kodein()).instance()
|
||||
instance as T
|
||||
@@ -64,5 +64,4 @@ object KodeinGlobalHelper {
|
||||
fun <T : Any> instance(type: Class<T>): T {
|
||||
return instance(type, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-20
@@ -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
@@ -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
|
||||
|
||||
@@ -3,16 +3,7 @@
|
||||
|-------|----------|---------|---------|
|
||||
|  | [](https://github.com/Suwayomi/Tachidesk/releases) | [](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [](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:
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
|
||||
+13
@@ -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],
|
||||
)
|
||||
|
||||
+17
@@ -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()
|
||||
}
|
||||
+18
@@ -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"
|
||||
)
|
||||
+18
@@ -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"
|
||||
)
|
||||
Reference in New Issue
Block a user