Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d35e31e02d | |||
| 6c4ca36c09 | |||
| 70355dc505 | |||
| 20aeaf2a05 | |||
| 1f13e1d08b | |||
| eb9d35c123 | |||
| 45808cd530 | |||
| fd715a3f92 | |||
| e3b32367a7 | |||
| bf9554a746 | |||
| b9f8ca1488 | |||
| e8c4159678 | |||
| e57e71629e | |||
| 2bfd9d24a4 | |||
| b18b8fe22f | |||
| b154ff2f9d | |||
| e9b764b63c | |||
| 7216b97d92 | |||
| 0e9d93b194 | |||
| 2cbee62f0a | |||
| 379e9da5fe | |||
| ae7caa4901 | |||
| cd8b4c9dd7 | |||
| 60cd61dfd2 | |||
| 5a6637d9fc | |||
| dca7ed23f5 | |||
| 8cb5791f3b | |||
| 9b67f2c58f | |||
| 3815810d4f | |||
| 819ceba17d | |||
| 0aa0d62e03 | |||
| b3e2a35880 | |||
| 15ec20c65d | |||
| d4d6d7e12f | |||
| 2e7a4f1421 | |||
| ab8a52faf3 | |||
| bd465559fb | |||
| 13ec45a95c | |||
| 13b034875b | |||
| bb701fb088 | |||
| b367414865 | |||
| 4b00eec608 | |||
| 5e11b51152 | |||
| 9fb43b996e |
@@ -45,10 +45,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
|
||||
@@ -47,11 +47,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
env:
|
||||
@@ -64,12 +59,6 @@ jobs:
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
|
||||
# - name: Mock Build and copy webUI, Build Jar
|
||||
# run: |
|
||||
# mkdir -p master/server/build
|
||||
# cd master/server/build
|
||||
# echo "test" > Tachidesk-v0.3.8-r583.jar
|
||||
|
||||
- name: Generate Tag Name
|
||||
id: GenTagName
|
||||
run: |
|
||||
@@ -87,11 +76,6 @@ jobs:
|
||||
./unix-bundler.sh macOS-x64
|
||||
./unix-bundler.sh macOS-arm64
|
||||
|
||||
# - name: Mock make windows packages
|
||||
# run: |
|
||||
# cd master/server/build
|
||||
# echo test > Tachidesk-v0.3.8-r580-win32.zip
|
||||
|
||||
- name: Checkout preview branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
@@ -46,11 +46,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build and copy webUI, Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
env:
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
// Android stub library
|
||||
implementation(fileTree("lib/"))
|
||||
implementation("com.github.Suwayomi:android-jar:1.0.0")
|
||||
|
||||
// XML
|
||||
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
android.jar
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-20
@@ -1,28 +1,11 @@
|
||||
# Server: v0.X.Y-rXXX + WebUI: rXXX
|
||||
# Server: v0.X.Y-next + WebUI: rXXX
|
||||
## TL;DR
|
||||
<!-- TODO: fill before release -->
|
||||
|
||||
## Tachidesk-Server
|
||||
### Public API
|
||||
#### Non-breaking changes
|
||||
- N/A
|
||||
|
||||
#### Breaking changes
|
||||
## Tachidesk-Server Changelog
|
||||
- N/A
|
||||
|
||||
#### Bug fixes
|
||||
- N/A
|
||||
|
||||
### Private API
|
||||
## Tachidesk-WebUI Changelog
|
||||
- N/A
|
||||
|
||||
|
||||
## Tachidesk-WebUI
|
||||
#### Visible changes
|
||||
- N/A
|
||||
|
||||
#### Bug fixes
|
||||
- N/A
|
||||
|
||||
#### Internal changes
|
||||
- N/A
|
||||
|
||||
+51
-3
@@ -1,3 +1,50 @@
|
||||
# Server: v0.5.4 + WebUI: r820
|
||||
## TL;DR
|
||||
- Fixed ReadComicOnline, Toonily and possibly other sources not working
|
||||
- Backup and Restore now includes Updates tab data
|
||||
- Removed Anime support from WebUI, Anime support will also be removed from Tachidesk-Server in a future update
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- (r973) convert android.jar lib to a maven repo
|
||||
- (r978) mimic Tachiyomi's behaviour more closely, fixes ReadComicOnline (EN)
|
||||
- (r980) fix export chapter ordering, include new props in backup
|
||||
- (r982) remove isNsfw annotation detection
|
||||
- (r984) use correct time conversion units when doing backups
|
||||
- (r989) Support using a CatalogueSource instead of only HttpSources ([#219](https://github.com/Suwayomi/Tachidesk-Server/pull/219) by @Syer10)
|
||||
- (r991) Use a custom task to run electron ([#220](https://github.com/Suwayomi/Tachidesk-Server/pull/220) by @Syer10)
|
||||
|
||||
## Tachidesk-WebUI Changelog
|
||||
- (r810) fix wrong strings in set Server Address dialog, fixes [#39](https://github.com/Suwayomi/Tachidesk-WebUI/issues/39)
|
||||
- (r811) fix chapterFetch loop
|
||||
- (r812) fix overlapping requests
|
||||
- (r813) fix typo
|
||||
- (r814) Better portrait support ([#41](https://github.com/Suwayomi/Tachidesk-WebUI/issues/41) by @minhe7735)
|
||||
- (r815) fixes Reader navbar colors when in light mode ([#43](https://github.com/Suwayomi/Tachidesk-WebUI/issues/43) by @abhijeetChawla)
|
||||
- (r816) default languages cleanup, force Local source enabled
|
||||
- (r817) force Local source at LangSelect
|
||||
- (r818) rename ExtensionLangSelect: generic name for generic use
|
||||
- (r819) don't show anime anymore
|
||||
- (r820) Remove Anime support
|
||||
|
||||
|
||||
|
||||
# Server: v0.5.3 + WebUI: r809
|
||||
## TL;DR
|
||||
- added support for a equivalent page to Tachiyomi's Updates tab
|
||||
- fix launchers not working on macOS M1/arm64
|
||||
|
||||
## 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 +59,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
|
||||
|
||||
+6
-3
@@ -22,9 +22,6 @@ This structure is chosen to
|
||||
You need these software packages installed in order to build the project
|
||||
|
||||
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||
- Android stubs jar
|
||||
- **Manual download:** Download [android.jar](https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
||||
- **Automated download:** Run `AndroidCompat/getAndroid.sh`(macOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows) from project's root directory to download and rebuild the jar file from Google's repository.
|
||||
|
||||
### building the full-blown jar (Tachidesk-Server + Tachidesk-WebUI bundle)
|
||||
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
|
||||
@@ -37,3 +34,9 @@ First Build the jar, then cd into the `scripts` directory and run `./windows-bun
|
||||
|
||||
## Running in development mode
|
||||
run `./gradlew :server:run --stacktrace` to run the server
|
||||
|
||||
## Building the android-jar maven repository
|
||||
Run `AndroidCompat/getAndroid.sh`(macOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows)
|
||||
from project's root directory to download and rebuild the jar file from Google's repository,
|
||||
then use `AndroidCompat/lib/android.jar` to manually create a maven repository inside the `android-jar` git branch.
|
||||
Update the dependency declaration afterwards.
|
||||
@@ -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" native desktop front-end for Tachidesk-Server. Currently the most advanced.
|
||||
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server is traditionally shipped with. Usually gets new features faster.
|
||||
- [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:
|
||||
|
||||
@@ -32,6 +32,7 @@ Here is a list of current features:
|
||||
- Searching and browsing installed sources
|
||||
- Ability to download Manga for offline read
|
||||
- Backup and restore support powered by Tachiyomi Backups
|
||||
- Viewing latest updated chapters.
|
||||
- From Aniyomi
|
||||
- Installing and executing Aniyomi's Extensions
|
||||
- Searching and browsing installed sources.
|
||||
@@ -39,8 +40,6 @@ Here is a list of current features:
|
||||
|
||||
**Note:** These are capabilities of Tachidesk-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info.
|
||||
|
||||
**Note:** Tachidesk-Server is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk-Server/wiki/Troubleshooting) if it happens.
|
||||
|
||||
# Downloading and Running the app
|
||||
## General Requirements
|
||||
In order to use the app effectively you need the following:
|
||||
|
||||
@@ -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 {
|
||||
@@ -17,6 +18,7 @@ allprojects {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven("https://jitpack.io")
|
||||
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +31,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 +40,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.4"
|
||||
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r807"
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r820"
|
||||
|
||||
// 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"
|
||||
|
||||
+15
-16
@@ -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,16 +69,11 @@ dependencies {
|
||||
|
||||
// uncomment to test extensions directly
|
||||
// implementation(fileTree("lib/"))
|
||||
implementation(kotlin("script-runtime"))
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set(MainClass)
|
||||
|
||||
// uncomment for testing electron
|
||||
// applicationDefaultJvmArgs = listOf(
|
||||
// "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
||||
// "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
||||
// )
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -127,20 +121,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")
|
||||
@@ -171,4 +158,16 @@ tasks {
|
||||
|
||||
overwrite(shouldOverwrite())
|
||||
}
|
||||
|
||||
register("runElectron") {
|
||||
group = "application"
|
||||
finalizedBy(run)
|
||||
doFirst {
|
||||
application.applicationDefaultJvmArgs = listOf(
|
||||
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
||||
// Change this to the installed electron application
|
||||
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package eu.kanade.tachiyomi.annoations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Nsfw
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.source.local
|
||||
|
||||
import com.github.junrar.Archive
|
||||
import eu.kanade.tachiyomi.source.local.FileSystemInterceptor.fakeUrlFrom
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Directory
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Epub
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Rar
|
||||
@@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
@@ -27,15 +26,6 @@ import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.select
|
||||
@@ -51,14 +41,12 @@ import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class LocalSource : HttpSource() {
|
||||
class LocalSource : CatalogueSource {
|
||||
companion object {
|
||||
const val ID = 0L
|
||||
const val LANG = "localsourcelang"
|
||||
@@ -133,13 +121,8 @@ class LocalSource : HttpSource() {
|
||||
override val id = ID
|
||||
override val name = NAME
|
||||
override val lang = LANG
|
||||
override val baseUrl: String = ""
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.addInterceptor(FileSystemInterceptor)
|
||||
.build()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override fun toString() = name
|
||||
@@ -181,7 +164,7 @@ class LocalSource : HttpSource() {
|
||||
// Try to find the cover
|
||||
val cover = getCoverFile(File("${applicationDirs.localMangaRoot}/$url"))
|
||||
if (cover != null && cover.exists()) {
|
||||
thumbnail_url = fakeUrlFrom(cover.absolutePath)
|
||||
thumbnail_url = cover.absolutePath
|
||||
}
|
||||
|
||||
val chapters = fetchChapterList(this).toBlocking().first()
|
||||
@@ -197,8 +180,7 @@ class LocalSource : HttpSource() {
|
||||
// Copy the cover from the first chapter found.
|
||||
if (thumbnail_url == null) {
|
||||
try {
|
||||
val dest = updateCover(chapter, this)
|
||||
thumbnail_url = dest?.absolutePath?.let { fakeUrlFrom(it) }
|
||||
thumbnail_url = updateCover(chapter, this)?.absolutePath
|
||||
} catch (e: Exception) {
|
||||
logger.error { e }
|
||||
}
|
||||
@@ -311,7 +293,7 @@ class LocalSource : HttpSource() {
|
||||
chapterFile.listFiles().orEmpty().sortedBy { it.name }.mapIndexed { index, page ->
|
||||
Page(
|
||||
index,
|
||||
imageUrl = fakeUrlFrom(applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name)
|
||||
imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -412,67 +394,4 @@ class LocalSource : HttpSource() {
|
||||
data class Rar(val file: File) : Format()
|
||||
data class Epub(val file: File) : Format()
|
||||
}
|
||||
|
||||
// ///////////////////// Not used ///////////////////// //
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used")
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not used")
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> = throw Exception("Not used")
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not used")
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||
throw Exception("Not used")
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used")
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// Windows
|
||||
if (System.getProperty("os.name").lowercase().startsWith("win")) {
|
||||
// convert paths like "c/Users/..." to "c:/Users/..."
|
||||
return StringBuilder(path).insert(1, ":").toString()
|
||||
}
|
||||
|
||||
return "/$path"
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val url = request.url
|
||||
val filePath = restoreFilePath(url.toString())
|
||||
return try {
|
||||
Response.Builder()
|
||||
.body(File(filePath).source().buffer().asResponseBody())
|
||||
.code(200)
|
||||
.message("Some file")
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
.request(request)
|
||||
.build()
|
||||
} catch (e: FileNotFoundException) {
|
||||
Response.Builder()
|
||||
.body("".toResponseBody())
|
||||
.code(404)
|
||||
.message(e.message ?: "File not found ($filePath)")
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
.request(request)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,10 @@ package suwayomi.tachidesk.manga.impl
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
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
|
||||
@@ -20,11 +21,12 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.manga.impl.Manga.getManga
|
||||
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterDir
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
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 +42,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)
|
||||
}
|
||||
@@ -52,7 +54,7 @@ object Chapter {
|
||||
|
||||
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
|
||||
val manga = getManga(mangaId)
|
||||
val source = getHttpSource(manga.sourceId.toLong())
|
||||
val source = getCatalogueSourceOrStub(manga.sourceId.toLong())
|
||||
|
||||
val sManga = SManga.create().apply {
|
||||
title = manga.title
|
||||
@@ -63,11 +65,12 @@ object Chapter {
|
||||
|
||||
// Recognize number for new chapters.
|
||||
chapterList.forEach {
|
||||
source.prepareNewChapter(it, sManga)
|
||||
(source as? HttpSource)?.prepareNewChapter(it, sManga)
|
||||
ChapterRecognition.parseChapterNumber(it, sManga)
|
||||
}
|
||||
|
||||
val chapterCount = chapterList.count()
|
||||
var now = Instant.now().epochSecond
|
||||
|
||||
transaction {
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
@@ -80,7 +83,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 +94,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 +107,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 +141,7 @@ object Chapter {
|
||||
dbChapter[ChapterTable.lastReadAt],
|
||||
|
||||
chapterCount - index,
|
||||
dbChapter[ChapterTable.fetchedAt],
|
||||
dbChapter[ChapterTable.isDownloaded],
|
||||
|
||||
dbChapter[ChapterTable.pageCount],
|
||||
@@ -151,7 +156,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,13 +164,13 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val pageList = source.fetchPageList(
|
||||
SChapter.create().apply {
|
||||
@@ -203,7 +208,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 +224,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 +255,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 +270,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 +287,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 +308,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 ->
|
||||
|
||||
@@ -8,7 +8,9 @@ package suwayomi.tachidesk.manga.impl
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.select
|
||||
@@ -19,11 +21,12 @@ import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||
import suwayomi.tachidesk.manga.impl.Source.getSource
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||
@@ -31,6 +34,8 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
object Manga {
|
||||
@@ -61,43 +66,43 @@ 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],
|
||||
false
|
||||
)
|
||||
} else { // initialize manga
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
val sManga = SManga.create().apply {
|
||||
url = mangaEntry[MangaTable.url]
|
||||
title = mangaEntry[MangaTable.title]
|
||||
}
|
||||
val fetchedManga = source.fetchMangaDetails(sManga).awaitSingle()
|
||||
val networkManga = source.fetchMangaDetails(sManga).awaitSingle()
|
||||
sManga.copyFrom(networkManga)
|
||||
|
||||
transaction {
|
||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||
|
||||
if (fetchedManga.title != mangaEntry[MangaTable.title]) {
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, fetchedManga.title)
|
||||
if (sManga.title != mangaEntry[MangaTable.title]) {
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, sManga.title)
|
||||
|
||||
if (canUpdateTitle)
|
||||
it[MangaTable.title] = fetchedManga.title
|
||||
it[MangaTable.title] = sManga.title
|
||||
}
|
||||
it[MangaTable.initialized] = true
|
||||
|
||||
it[MangaTable.artist] = fetchedManga.artist
|
||||
it[MangaTable.author] = fetchedManga.author
|
||||
it[MangaTable.description] = truncate(fetchedManga.description, 4096)
|
||||
it[MangaTable.genre] = fetchedManga.genre
|
||||
it[MangaTable.status] = fetchedManga.status
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||
it[MangaTable.artist] = sManga.artist
|
||||
it[MangaTable.author] = sManga.author
|
||||
it[MangaTable.description] = truncate(sManga.description, 4096)
|
||||
it[MangaTable.genre] = sManga.genre
|
||||
it[MangaTable.status] = sManga.status
|
||||
if (sManga.thumbnail_url != null && sManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
|
||||
|
||||
it[MangaTable.realUrl] = try {
|
||||
source.mangaDetailsRequest(sManga).url.toString()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
it[MangaTable.realUrl] = runCatching {
|
||||
(source as? HttpSource)?.mangaDetailsRequest(sManga)?.url?.toString()
|
||||
}.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +120,13 @@ object Manga {
|
||||
|
||||
true,
|
||||
|
||||
fetchedManga.artist,
|
||||
fetchedManga.author,
|
||||
fetchedManga.description,
|
||||
fetchedManga.genre.toGenreList(),
|
||||
MangaStatus.valueOf(fetchedManga.status).name,
|
||||
sManga.artist,
|
||||
sManga.author,
|
||||
sManga.description,
|
||||
sManga.genre.toGenreList(),
|
||||
MangaStatus.valueOf(sManga.status).name,
|
||||
mangaEntry[MangaTable.inLibrary],
|
||||
mangaEntry[MangaTable.inLibraryAt],
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
getMangaMetaMap(mangaId),
|
||||
mangaEntry[MangaTable.realUrl],
|
||||
@@ -161,25 +167,39 @@ object Manga {
|
||||
val saveDir = applicationDirs.mangaThumbnailsRoot
|
||||
val fileName = mangaId.toString()
|
||||
|
||||
return getImageResponse(saveDir, fileName, useCache) {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
val source = getHttpSource(sourceId)
|
||||
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||
is HttpSource -> getImageResponse(saveDir, fileName, useCache) {
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException()
|
||||
}
|
||||
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException()
|
||||
}
|
||||
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers)
|
||||
).await()
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers)
|
||||
).await()
|
||||
}
|
||||
is LocalSource -> {
|
||||
val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let {
|
||||
val file = File(it)
|
||||
if (file.exists()) {
|
||||
file
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: throw IOException("Thumbnail does not exist")
|
||||
val contentType = ImageUtil.findImageType { imageFile.inputStream() }?.mime
|
||||
?: "image/jpeg"
|
||||
imageFile.inputStream() to contentType
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown source")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||
@@ -27,14 +27,15 @@ object MangaList {
|
||||
}
|
||||
|
||||
suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
||||
val source = getHttpSource(sourceId)
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val mangasPage = if (popular) {
|
||||
source.fetchPopularManga(pageNum).awaitSingle()
|
||||
} else {
|
||||
if (source.supportsLatest)
|
||||
if (source.supportsLatest) {
|
||||
source.fetchLatestUpdates(pageNum).awaitSingle()
|
||||
else
|
||||
} else {
|
||||
throw Exception("Source $source doesn't support latest")
|
||||
}
|
||||
}
|
||||
return mangasPage.processEntries(sourceId)
|
||||
}
|
||||
@@ -81,6 +82,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 +105,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
|
||||
|
||||
@@ -17,11 +17,12 @@ import org.jetbrains.exposed.sql.update
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterDir
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.PageTable
|
||||
@@ -43,10 +44,10 @@ object Page {
|
||||
|
||||
suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, useCache: Boolean = true): Pair<InputStream, String> {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = getCatalogueSourceOrStub(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
|
||||
@@ -61,19 +62,20 @@ object Page {
|
||||
)
|
||||
|
||||
// we treat Local source differently
|
||||
if (mangaEntry[MangaTable.sourceReference] == LocalSource.ID) {
|
||||
if (source.id == LocalSource.ID) {
|
||||
// is of archive format
|
||||
if (LocalSource.pageCache.containsKey(chapterEntry[ChapterTable.url])) {
|
||||
val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index]()
|
||||
return pageStream to "image/jpeg"
|
||||
val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index]
|
||||
return pageStream() to (ImageUtil.findImageType { pageStream() }?.mime ?: "image/jpeg")
|
||||
}
|
||||
|
||||
// is of directory format
|
||||
return ImageResponse.getNoCacheImageResponse {
|
||||
source.fetchImage(tachiyomiPage).awaitSingle()
|
||||
}
|
||||
val imageFile = File(tachiyomiPage.imageUrl!!)
|
||||
return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg")
|
||||
}
|
||||
|
||||
source as HttpSource
|
||||
|
||||
if (pageEntry[PageTable.imageUrl] == null) {
|
||||
val trueImageUrl = getTrueImageUrl(tachiyomiPage, source)
|
||||
transaction {
|
||||
|
||||
@@ -10,13 +10,13 @@ package suwayomi.tachidesk.manga.impl
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||
|
||||
object Search {
|
||||
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
||||
val source = getHttpSource(sourceId)
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle()
|
||||
return searchManga.processEntries(sourceId)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ object Search {
|
||||
|
||||
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList {
|
||||
if (reset || !filterListCache.containsKey(sourceId)) {
|
||||
filterListCache[sourceId] = getHttpSource(sourceId).getFilterList()
|
||||
filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList()
|
||||
}
|
||||
return filterListCache[sourceId]!!
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.content.Context
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.getPreferenceKey
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import mu.KotlinLogging
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
@@ -21,8 +20,9 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.invalidateSourceCache
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.invalidateSourceCache
|
||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
@@ -36,7 +36,7 @@ object Source {
|
||||
fun getSourceList(): List<SourceDataClass> {
|
||||
return transaction {
|
||||
SourceTable.selectAll().map {
|
||||
val httpSource = getHttpSource(it[SourceTable.id].value)
|
||||
val catalogueSource = getCatalogueSourceOrStub(it[SourceTable.id].value)
|
||||
val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()
|
||||
|
||||
SourceDataClass(
|
||||
@@ -44,10 +44,10 @@ object Source {
|
||||
it[SourceTable.name],
|
||||
it[SourceTable.lang],
|
||||
getExtensionIconUrl(sourceExtension[ExtensionTable.apkName]),
|
||||
httpSource.supportsLatest,
|
||||
httpSource is ConfigurableSource,
|
||||
catalogueSource.supportsLatest,
|
||||
catalogueSource is ConfigurableSource,
|
||||
it[SourceTable.isNsfw],
|
||||
httpSource.toString(),
|
||||
catalogueSource.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -55,13 +55,8 @@ object Source {
|
||||
|
||||
fun getSource(sourceId: Long): SourceDataClass { // all the data extracted fresh form the source instance
|
||||
return transaction {
|
||||
if (sourceId == LocalSource.ID) {
|
||||
// initialize local source
|
||||
getHttpSource(sourceId)
|
||||
}
|
||||
|
||||
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||
val httpSource = source?.let { getHttpSource(sourceId) }
|
||||
val catalogueSource = source?.let { getCatalogueSource(sourceId) }
|
||||
val extension = source?.let {
|
||||
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
|
||||
}
|
||||
@@ -75,10 +70,10 @@ object Source {
|
||||
extension!![ExtensionTable.apkName]
|
||||
)
|
||||
},
|
||||
httpSource?.supportsLatest,
|
||||
httpSource?.let { it is ConfigurableSource },
|
||||
catalogueSource?.supportsLatest,
|
||||
catalogueSource?.let { it is ConfigurableSource },
|
||||
source?.get(SourceTable.isNsfw),
|
||||
httpSource?.toString()
|
||||
catalogueSource?.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +98,7 @@ object Source {
|
||||
* Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap]
|
||||
*/
|
||||
fun getSourcePreferences(sourceId: Long): List<PreferenceObject> {
|
||||
val source = getHttpSource(sourceId)
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
|
||||
if (source is ConfigurableSource) {
|
||||
val sourceShardPreferences =
|
||||
|
||||
+4
-3
@@ -32,6 +32,7 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ProtoBackupExport : ProtoBackupBase() {
|
||||
suspend fun createBackup(flags: BackupFlags): InputStream {
|
||||
@@ -68,7 +69,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
||||
MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
||||
mangaRow[MangaTable.thumbnail_url],
|
||||
0, // not supported in Tachidesk
|
||||
TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
|
||||
0, // not supported in Tachidesk
|
||||
)
|
||||
|
||||
@@ -84,10 +85,10 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
it.read,
|
||||
it.bookmarked,
|
||||
it.lastPageRead,
|
||||
0, // not supported in Tachidesk
|
||||
TimeUnit.SECONDS.toMillis(it.fetchedAt),
|
||||
it.uploadDate,
|
||||
it.chapterNumber,
|
||||
it.index,
|
||||
chapters.size - it.index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -34,6 +34,7 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.io.InputStream
|
||||
import java.lang.Integer.max
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ProtoBackupImport : ProtoBackupBase() {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@@ -148,6 +149,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
it[initialized] = manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
|
||||
// insert chapter data
|
||||
@@ -160,12 +163,14 @@ 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
|
||||
it[lastPageRead] = chapter.last_page_read
|
||||
it[isBookmarked] = chapter.bookmark
|
||||
|
||||
it[fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +195,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}
|
||||
|
||||
// merge chapter data
|
||||
@@ -207,7 +214,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
|
||||
import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
|
||||
@@ -39,6 +38,7 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
@@ -51,9 +51,6 @@ object Extension {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
private fun Any.isNsfw(): Boolean =
|
||||
this::class.annotations.any { it.toString() == "@eu.kanade.tachiyomi.annotations.Nsfw()" }
|
||||
|
||||
suspend fun installExtension(pkgName: String): Int {
|
||||
logger.debug("Installing $pkgName")
|
||||
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
||||
@@ -189,7 +186,7 @@ object Extension {
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw || extensionMainClassInstance.isNsfw()
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
}
|
||||
@@ -243,7 +240,7 @@ object Extension {
|
||||
PackageTools.jarLoaderMap.remove(jarPath)?.close()
|
||||
|
||||
// clear all loaded sources
|
||||
sources.forEach { GetHttpSource.invalidateSourceCache(it) }
|
||||
sources.forEach { GetCatalogueSource.invalidateSourceCache(it) }
|
||||
|
||||
File(jarPath).delete()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.SafePath
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
@@ -22,7 +23,7 @@ private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
fun getMangaDir(mangaId: Int): String {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = GetHttpSource.getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val sourceDir = source.toString()
|
||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||
@@ -46,7 +47,7 @@ fun getChapterDir(mangaId: Int, chapterId: Int): String {
|
||||
/** return value says if rename/move was successful */
|
||||
fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = GetHttpSource.getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val sourceDir = source.toString()
|
||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||
|
||||
+14
-11
@@ -1,4 +1,4 @@
|
||||
package suwayomi.tachidesk.manga.impl.util
|
||||
package suwayomi.tachidesk.manga.impl.util.source
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
@@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.impl.util
|
||||
* 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 eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
@@ -22,23 +23,21 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object GetHttpSource {
|
||||
private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
|
||||
object GetCatalogueSource {
|
||||
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>(
|
||||
mapOf(LocalSource.ID to LocalSource())
|
||||
)
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
fun getHttpSource(sourceId: Long): HttpSource {
|
||||
val cachedResult: HttpSource? = sourceCache[sourceId]
|
||||
fun getCatalogueSource(sourceId: Long): CatalogueSource? {
|
||||
val cachedResult: CatalogueSource? = sourceCache[sourceId]
|
||||
if (cachedResult != null) {
|
||||
return cachedResult
|
||||
}
|
||||
|
||||
val sourceRecord = transaction {
|
||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
|
||||
}
|
||||
|
||||
if (sourceId == LocalSource.ID) {
|
||||
return LocalSource()
|
||||
}
|
||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||
} ?: return null
|
||||
|
||||
val extensionId = sourceRecord[SourceTable.extension]
|
||||
val extensionRecord = transaction {
|
||||
@@ -60,6 +59,10 @@ object GetHttpSource {
|
||||
return sourceCache[sourceId]!!
|
||||
}
|
||||
|
||||
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource {
|
||||
return getCatalogueSource(sourceId) ?: StubSource(sourceId)
|
||||
}
|
||||
|
||||
fun invalidateSourceCache(sourceId: Long) {
|
||||
sourceCache.remove(sourceId)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package suwayomi.tachidesk.manga.impl.util.source
|
||||
|
||||
/*
|
||||
* 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 eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import rx.Observable
|
||||
|
||||
class StubSource(override val id: Long) : CatalogueSource {
|
||||
override val lang: String = "other"
|
||||
override val supportsLatest: Boolean = false
|
||||
override val name: String
|
||||
get() = id.toString()
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList {
|
||||
return FilterList()
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
private fun getSourceNotInstalledException(): SourceNotInstalledException {
|
||||
return SourceNotInstalledException(id)
|
||||
}
|
||||
|
||||
inner class SourceNotInstalledException(val id: Long) :
|
||||
Exception("Source not installed: $id")
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -26,8 +26,8 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.installExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||
import suwayomi.tachidesk.server.applicationSetup
|
||||
import java.io.File
|
||||
@@ -69,7 +69,7 @@ class TestExtensions {
|
||||
}
|
||||
}
|
||||
}
|
||||
sources = getSourceList().map { getHttpSource(it.id.toLong()) }
|
||||
sources = getSourceList().map { getCatalogueSource(it.id.toLong())!! as HttpSource }
|
||||
}
|
||||
setLoggingEnabled(true)
|
||||
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
||||
|
||||
Reference in New Issue
Block a user