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
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
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
|
- name: Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
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
|
- name: Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
uses: eskatos/gradle-command-action@v1
|
||||||
env:
|
env:
|
||||||
@@ -64,12 +59,6 @@ jobs:
|
|||||||
dependencies-cache-enabled: true
|
dependencies-cache-enabled: true
|
||||||
configuration-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
|
- name: Generate Tag Name
|
||||||
id: GenTagName
|
id: GenTagName
|
||||||
run: |
|
run: |
|
||||||
@@ -87,11 +76,6 @@ jobs:
|
|||||||
./unix-bundler.sh macOS-x64
|
./unix-bundler.sh macOS-x64
|
||||||
./unix-bundler.sh macOS-arm64
|
./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
|
- name: Checkout preview branch
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -46,11 +46,6 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
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
|
- name: Build and copy webUI, Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
uses: eskatos/gradle-command-action@v1
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const val CONFIG_PREFIX = "suwayomi.tachidesk.config"
|
|||||||
val ApplicationRootDir: String
|
val ApplicationRootDir: String
|
||||||
get(): String {
|
get(): String {
|
||||||
return System.getProperty(
|
return System.getProperty(
|
||||||
"$CONFIG_PREFIX.server.rootDir",
|
"$CONFIG_PREFIX.server.rootDir",
|
||||||
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import org.kodein.di.singleton
|
|||||||
|
|
||||||
class ConfigKodeinModule {
|
class ConfigKodeinModule {
|
||||||
fun create() = DI.Module("ConfigManager") {
|
fun create() = DI.Module("ConfigManager") {
|
||||||
//Config module
|
// Config module
|
||||||
bind<ConfigManager>() with singleton { GlobalConfigManager }
|
bind<ConfigManager>() with singleton { GlobalConfigManager }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ open class ConfigManager {
|
|||||||
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
||||||
val config by lazy { loadConfigs() }
|
val config by lazy { loadConfigs() }
|
||||||
|
|
||||||
//Public read-only view of modules
|
// Public read-only view of modules
|
||||||
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
||||||
get() = generatedModules
|
get() = generatedModules
|
||||||
|
|
||||||
@@ -42,29 +42,28 @@ open class ConfigManager {
|
|||||||
* Load configs
|
* Load configs
|
||||||
*/
|
*/
|
||||||
fun loadConfigs(): Config {
|
fun loadConfigs(): Config {
|
||||||
//Load reference configs
|
// Load reference configs
|
||||||
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
||||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
val baseConfig =
|
val baseConfig =
|
||||||
ConfigFactory.parseMap(
|
ConfigFactory.parseMap(
|
||||||
mapOf(
|
mapOf(
|
||||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
//Load user config
|
// Load user config
|
||||||
val userConfig =
|
val userConfig =
|
||||||
File(ApplicationRootDir, "server.conf").let {
|
File(ApplicationRootDir, "server.conf").let {
|
||||||
ConfigFactory.parseFile(it)
|
ConfigFactory.parseFile(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val config = ConfigFactory.empty()
|
val config = ConfigFactory.empty()
|
||||||
.withFallback(baseConfig)
|
.withFallback(baseConfig)
|
||||||
.withFallback(userConfig)
|
.withFallback(userConfig)
|
||||||
.withFallback(compatConfig)
|
.withFallback(compatConfig)
|
||||||
.withFallback(serverConfig)
|
.withFallback(serverConfig)
|
||||||
.resolve()
|
.resolve()
|
||||||
|
|
||||||
// set log level early
|
// set log level early
|
||||||
if (debugLogsEnabled(config)) {
|
if (debugLogsEnabled(config)) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ abstract class ConfigModule(config: Config)
|
|||||||
/**
|
/**
|
||||||
* Abstract jvm-commandline-argument-overridable config module.
|
* 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)
|
val overridableConfig = SystemPropertyOverrideDelegate(config, moduleName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String)
|
|||||||
configValue.toString()
|
configValue.toString()
|
||||||
)
|
)
|
||||||
|
|
||||||
return when(T::class.simpleName) {
|
return when (T::class.simpleName) {
|
||||||
"Int" -> combined.toInt()
|
"Int" -> combined.toInt()
|
||||||
"Boolean" -> combined.toBoolean()
|
"Boolean" -> combined.toBoolean()
|
||||||
// add more types as needed
|
// 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
|
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
|
||||||
}
|
}
|
||||||
|
|
||||||
fun debugLogsEnabled(config: Config)
|
fun debugLogsEnabled(config: Config) =
|
||||||
= System.getProperty("suwayomi.tachidesk.config.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
|
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
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
operator fun Config.get(key: String) = getString(key)
|
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 {
|
dependencies {
|
||||||
// Android stub library
|
// Android stub library
|
||||||
implementation(fileTree("lib/"))
|
implementation("com.github.Suwayomi:android-jar:1.0.0")
|
||||||
|
|
||||||
// XML
|
// XML
|
||||||
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
android.jar
|
|
||||||
@@ -9,8 +9,10 @@ import android.content.Context
|
|||||||
class PreferenceManager {
|
class PreferenceManager {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDefaultSharedPreferences(context: Context)
|
fun getDefaultSharedPreferences(context: Context) =
|
||||||
= context.getSharedPreferences(context.applicationInfo.packageName,
|
context.getSharedPreferences(
|
||||||
Context.MODE_PRIVATE)!!
|
context.applicationInfo.packageName,
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -5,4 +5,4 @@ import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory
|
|||||||
|
|
||||||
class RequerySQLiteOpenHelperFactory {
|
class RequerySQLiteOpenHelperFactory {
|
||||||
fun create(configuration: SupportSQLiteOpenHelper.Configuration) = FrameworkSQLiteOpenHelperFactory().create(configuration)
|
fun create(configuration: SupportSQLiteOpenHelper.Configuration) = FrameworkSQLiteOpenHelperFactory().create(configuration)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ class AndroidCompat {
|
|||||||
application.attach(context)
|
application.attach(context)
|
||||||
application.onCreate()
|
application.onCreate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class AndroidCompatInitializer {
|
|||||||
fun init() {
|
fun init() {
|
||||||
DI.global.addImport(AndroidCompatModule().create())
|
DI.global.addImport(AndroidCompatModule().create())
|
||||||
|
|
||||||
//Register config modules
|
// Register config modules
|
||||||
GlobalConfigManager.registerModules(
|
GlobalConfigManager.registerModules(
|
||||||
FilesConfigModule.register(GlobalConfigManager.config),
|
FilesConfigModule.register(GlobalConfigManager.config),
|
||||||
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
|
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class AndroidCompatModule {
|
|||||||
|
|
||||||
bind<PackageController>() with singleton { PackageController() }
|
bind<PackageController>() with singleton { PackageController() }
|
||||||
|
|
||||||
//Context
|
// Context
|
||||||
bind<CustomContext>() with singleton { CustomContext() }
|
bind<CustomContext>() with singleton { CustomContext() }
|
||||||
bind<Context>() with singleton {
|
bind<Context>() with singleton {
|
||||||
val context: Context by DI.global.instance<CustomContext>()
|
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
|
val debug: Boolean by config
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) =
|
||||||
= ApplicationInfoConfigModule(config.getConfig("android.app"))
|
ApplicationInfoConfigModule(config.getConfig("android.app"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,26 +9,26 @@ import xyz.nulldev.ts.config.ConfigModule
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class FilesConfigModule(config: Config) : ConfigModule(config) {
|
class FilesConfigModule(config: Config) : ConfigModule(config) {
|
||||||
val dataDir:String by config
|
val dataDir: String by config
|
||||||
val filesDir:String by config
|
val filesDir: String by config
|
||||||
val noBackupFilesDir:String by config
|
val noBackupFilesDir: String by config
|
||||||
val externalFilesDirs: MutableList<String> by config
|
val externalFilesDirs: MutableList<String> by config
|
||||||
val obbDirs: MutableList<String> by config
|
val obbDirs: MutableList<String> by config
|
||||||
val cacheDir:String by config
|
val cacheDir: String by config
|
||||||
val codeCacheDir:String by config
|
val codeCacheDir: String by config
|
||||||
val externalCacheDirs: MutableList<String> by config
|
val externalCacheDirs: MutableList<String> by config
|
||||||
val externalMediaDirs: MutableList<String> by config
|
val externalMediaDirs: MutableList<String> by config
|
||||||
val rootDir:String by config
|
val rootDir: String by config
|
||||||
val externalStorageDir:String by config
|
val externalStorageDir: String by config
|
||||||
val downloadCacheDir:String by config
|
val downloadCacheDir: String by config
|
||||||
val databasesDir: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 {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) =
|
||||||
= FilesConfigModule(config.getConfig("android.files"))
|
FilesConfigModule(config.getConfig("android.files"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package xyz.nulldev.androidcompat.config
|
package xyz.nulldev.androidcompat.config
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import xyz.nulldev.ts.config.ConfigModule
|
|
||||||
import io.github.config4k.getValue
|
import io.github.config4k.getValue
|
||||||
|
import xyz.nulldev.ts.config.ConfigModule
|
||||||
|
|
||||||
class SystemConfigModule(val config: Config) : ConfigModule(config) {
|
class SystemConfigModule(val config: Config) : ConfigModule(config) {
|
||||||
val isDebuggable: Boolean by 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")
|
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) =
|
||||||
= SystemConfigModule(config.getConfig("android.system"))
|
SystemConfigModule(config.getConfig("android.system"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
|
|
||||||
val parentMetadata = parent.metaData
|
val parentMetadata = parent.metaData
|
||||||
val columnCount = parentMetadata.columnCount
|
val columnCount = parentMetadata.columnCount
|
||||||
val columnLabels = (1 .. columnCount).map {
|
val columnLabels = (1..columnCount).map {
|
||||||
parentMetadata.getColumnLabel(it)
|
parentMetadata.getColumnLabel(it)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
@@ -41,10 +41,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
// How can we optimize this?
|
// How can we optimize this?
|
||||||
// We need to fill the cache as the set is loaded
|
// We need to fill the cache as the set is loaded
|
||||||
|
|
||||||
//Fill cache
|
// Fill cache
|
||||||
while(parent.next()) {
|
while (parent.next()) {
|
||||||
cachedContent += ResultSetEntry().apply {
|
cachedContent += ResultSetEntry().apply {
|
||||||
for(i in 1 .. columnCount)
|
for (i in 1..columnCount)
|
||||||
data += parent.getObject(i)
|
data += parent.getObject(i)
|
||||||
}
|
}
|
||||||
resultSetLength++
|
resultSetLength++
|
||||||
@@ -60,8 +60,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun internalMove(row: Int) {
|
private fun internalMove(row: Int) {
|
||||||
if(cursor < 0) cursor = 0
|
if (cursor < 0) cursor = 0
|
||||||
else if(cursor > resultSetLength + 1) cursor = resultSetLength + 1
|
else if (cursor > resultSetLength + 1) cursor = resultSetLength + 1
|
||||||
else cursor = row
|
else cursor = row
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +75,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
return obj(cachedFindColumn(column))
|
return obj(cachedFindColumn(column))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cachedFindColumn(column: String?)
|
private fun cachedFindColumn(column: String?) =
|
||||||
= columnCache.getOrPut(column!!, {
|
columnCache.getOrPut(column!!, {
|
||||||
findColumn(column)
|
findColumn(column)
|
||||||
})
|
})
|
||||||
|
|
||||||
override fun getNClob(columnIndex: Int): NClob {
|
override fun getNClob(columnIndex: Int): NClob {
|
||||||
return obj(columnIndex) as NClob
|
return obj(columnIndex) as NClob
|
||||||
@@ -157,27 +157,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getDate(columnIndex: Int): Date {
|
override fun getDate(columnIndex: Int): Date {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDate(columnLabel: String?): Date {
|
override fun getDate(columnLabel: String?): Date {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDate(columnIndex: Int, cal: Calendar?): Date {
|
override fun getDate(columnIndex: Int, cal: Calendar?): Date {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDate(columnLabel: String?, cal: Calendar?): Date {
|
override fun getDate(columnLabel: String?, cal: Calendar?): Date {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeFirst() {
|
override fun beforeFirst() {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,12 +202,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
|
override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
|
override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,22 +236,22 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getTime(columnIndex: Int): Time {
|
override fun getTime(columnIndex: Int): Time {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTime(columnLabel: String?): Time {
|
override fun getTime(columnLabel: String?): Time {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTime(columnIndex: Int, cal: Calendar?): Time {
|
override fun getTime(columnIndex: Int, cal: Calendar?): Time {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTime(columnLabel: String?, cal: Calendar?): Time {
|
override fun getTime(columnLabel: String?, cal: Calendar?): Time {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,28 +272,28 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun absolute(row: Int): Boolean {
|
override fun absolute(row: Int): Boolean {
|
||||||
if(row > 0) {
|
if (row > 0) {
|
||||||
internalMove(row)
|
internalMove(row)
|
||||||
} else {
|
} else {
|
||||||
last()
|
last()
|
||||||
for(i in 1 .. row)
|
for (i in 1..row)
|
||||||
previous()
|
previous()
|
||||||
}
|
}
|
||||||
return cursorValid()
|
return cursorValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSQLXML(columnIndex: Int): SQLXML? {
|
override fun getSQLXML(columnIndex: Int): SQLXML? {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSQLXML(columnLabel: String?): SQLXML? {
|
override fun getSQLXML(columnLabel: String?): SQLXML? {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any?> unwrap(iface: Class<T>?): T {
|
override fun <T : Any?> unwrap(iface: Class<T>?): T {
|
||||||
if(thisIsWrapperFor(iface))
|
if (thisIsWrapperFor(iface))
|
||||||
return this as T
|
return this as T
|
||||||
else
|
else
|
||||||
return parent.unwrap(iface)
|
return parent.unwrap(iface)
|
||||||
@@ -426,12 +426,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getBlob(columnIndex: Int): Blob {
|
override fun getBlob(columnIndex: Int): Blob {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBlob(columnLabel: String?): Blob {
|
override fun getBlob(columnLabel: String?): Blob {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,12 +500,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
|
override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
|
override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,9 +531,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun castToLong(obj: Any?): Long {
|
private fun castToLong(obj: Any?): Long {
|
||||||
if(obj == null) return 0
|
if (obj == null) return 0
|
||||||
else if(obj is Long) return obj
|
else if (obj is Long) return obj
|
||||||
else if(obj is Number) return obj.toLong()
|
else if (obj is Number) return obj.toLong()
|
||||||
else throw IllegalStateException("Object is not a long!")
|
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 {
|
override fun getClob(columnIndex: Int): Clob {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getClob(columnLabel: String?): Clob {
|
override fun getClob(columnLabel: String?): Clob {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,12 +604,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getArray(columnIndex: Int): Array {
|
override fun getArray(columnIndex: Int): Array {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getArray(columnLabel: String?): Array {
|
override fun getArray(columnLabel: String?): Array {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,32 +688,32 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimestamp(columnIndex: Int): Timestamp {
|
override fun getTimestamp(columnIndex: Int): Timestamp {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimestamp(columnLabel: String?): Timestamp {
|
override fun getTimestamp(columnLabel: String?): Timestamp {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
|
override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
|
override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRef(columnIndex: Int): Ref {
|
override fun getRef(columnIndex: Int): Ref {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRef(columnLabel: String?): Ref {
|
override fun getRef(columnLabel: String?): Ref {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,12 +792,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getRowId(columnIndex: Int): RowId {
|
override fun getRowId(columnIndex: Int): RowId {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRowId(columnLabel: String?): RowId {
|
override fun getRowId(columnLabel: String?): RowId {
|
||||||
//TODO Maybe?
|
// TODO Maybe?
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,4 +848,4 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
class ResultSetEntry {
|
class ResultSetEntry {
|
||||||
val data = mutableListOf<Any?>()
|
val data = mutableListOf<Any?>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -174,4 +174,4 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
javaPreferences.removeNode()
|
javaPreferences.removeNode()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import java.io.File
|
|||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data class InstalledPackage(val root: File) {
|
data class InstalledPackage(val root: File) {
|
||||||
val apk = File(root, "package.apk")
|
val apk = File(root, "package.apk")
|
||||||
val jar = File(root, "translated.jar")
|
val jar = File(root, "translated.jar")
|
||||||
@@ -40,20 +38,24 @@ data class InstalledPackage(val root: File) {
|
|||||||
}?.filter {
|
}?.filter {
|
||||||
it.tagName == "meta-data"
|
it.tagName == "meta-data"
|
||||||
}?.map {
|
}?.map {
|
||||||
putString(it.attributes.getNamedItem("android:name").nodeValue,
|
putString(
|
||||||
it.attributes.getNamedItem("android:value").nodeValue)
|
it.attributes.getNamedItem("android:name").nodeValue,
|
||||||
|
it.attributes.getNamedItem("android:value").nodeValue
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it.signatures = (parsed.apkSingers.flatMap { it.certificateMetas }
|
it.signatures = (
|
||||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||||
.map { Signature(it.data) }.toTypedArray()
|
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
||||||
|
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||||
|
.map { Signature(it.data) }.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verify(): Boolean {
|
fun verify(): Boolean {
|
||||||
val res = ApkVerifier.Builder(apk)
|
val res = ApkVerifier.Builder(apk)
|
||||||
.build()
|
.build()
|
||||||
.verify()
|
.verify()
|
||||||
|
|
||||||
return res.isVerified
|
return res.isVerified
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ data class InstalledPackage(val root: File) {
|
|||||||
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
|
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
|
||||||
|
|
||||||
ImageIO.write(read, "png", icon)
|
ImageIO.write(read, "png", icon)
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
icon.delete()
|
icon.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ data class InstalledPackage(val root: File) {
|
|||||||
fun writeJar() {
|
fun writeJar() {
|
||||||
try {
|
try {
|
||||||
Dex2jar.from(apk).to(jar.toPath())
|
Dex2jar.from(apk).to(jar.toPath())
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
jar.delete()
|
jar.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,4 +94,4 @@ data class InstalledPackage(val root: File) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class PackageController {
|
|||||||
if (!installed.jar.exists()) {
|
if (!installed.jar.exists()) {
|
||||||
throw IllegalStateException("Failed to translate APK dex!")
|
throw IllegalStateException("Failed to translate APK dex!")
|
||||||
}
|
}
|
||||||
} catch(t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
root.deleteRecursively()
|
root.deleteRecursively()
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ class PackageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deletePackage(pack: InstalledPackage) {
|
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
|
val packageName = pack.info.packageName
|
||||||
pack.root.deleteRecursively()
|
pack.root.deleteRecursively()
|
||||||
@@ -74,7 +74,7 @@ class PackageController {
|
|||||||
|
|
||||||
fun findPackage(packageName: String): InstalledPackage? {
|
fun findPackage(packageName: String): InstalledPackage? {
|
||||||
val file = File(androidFiles.packagesDir, packageName)
|
val file = File(androidFiles.packagesDir, packageName)
|
||||||
return if(file.exists())
|
return if (file.exists())
|
||||||
InstalledPackage(file)
|
InstalledPackage(file)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
@@ -84,4 +84,4 @@ class PackageController {
|
|||||||
val pkgName = ApkParsers.getMetaInfo(apkFile).packageName
|
val pkgName = ApkParsers.getMetaInfo(apkFile).packageName
|
||||||
return findPackage(pkgName)?.jar
|
return findPackage(pkgName)?.jar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
|||||||
sourceDir = apk.absolutePath
|
sourceDir = apk.absolutePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ interface Resource {
|
|||||||
fun getType(): Class<out Resource>
|
fun getType(): Class<out Resource>
|
||||||
|
|
||||||
fun getValue(): Any?
|
fun getValue(): Any?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ class ServiceSupport {
|
|||||||
|
|
||||||
runningServices[name] = service
|
runningServices[name] = service
|
||||||
|
|
||||||
//Setup service
|
// Setup service
|
||||||
thread {
|
thread {
|
||||||
callOnCreate(service)
|
callOnCreate(service)
|
||||||
//TODO Handle more complex cases
|
// TODO Handle more complex cases
|
||||||
service.onStartCommand(intent, 0, 0)
|
service.onStartCommand(intent, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ class ServiceSupport {
|
|||||||
fun stopService(name: String) {
|
fun stopService(name: String) {
|
||||||
logger.debug { "Stopping service: $name" }
|
logger.debug { "Stopping service: $name" }
|
||||||
val service = runningServices.remove(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" }
|
logger.warn { "An attempt was made to stop a service that is not running: $name" }
|
||||||
} else {
|
} else {
|
||||||
thread {
|
thread {
|
||||||
@@ -63,6 +63,6 @@ class ServiceSupport {
|
|||||||
fun serviceInstanceFromClass(className: String): Service {
|
fun serviceInstanceFromClass(className: String): Service {
|
||||||
val clazzObj = Class.forName(className)
|
val clazzObj = Class.forName(className)
|
||||||
return clazzObj.getDeclaredConstructor().newInstance() as? Service
|
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
|
@JvmStatic
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
|
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
AndroidFiles::class.java -> {
|
AndroidFiles::class.java -> {
|
||||||
val instance: AndroidFiles by (kodein ?: kodein()).instance()
|
val instance: AndroidFiles by (kodein ?: kodein()).instance()
|
||||||
instance as T
|
instance as T
|
||||||
@@ -64,5 +64,4 @@ object KodeinGlobalHelper {
|
|||||||
fun <T : Any> instance(type: Class<T>): T {
|
fun <T : Any> instance(type: Class<T>): T {
|
||||||
return instance(type, null)
|
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
|
## TL;DR
|
||||||
<!-- TODO: fill before release -->
|
|
||||||
|
|
||||||
## Tachidesk-Server
|
|
||||||
### Public API
|
|
||||||
#### Non-breaking changes
|
|
||||||
- N/A
|
- N/A
|
||||||
|
|
||||||
#### Breaking changes
|
## Tachidesk-Server Changelog
|
||||||
- N/A
|
- N/A
|
||||||
|
|
||||||
#### Bug fixes
|
## Tachidesk-WebUI Changelog
|
||||||
- N/A
|
|
||||||
|
|
||||||
### Private API
|
|
||||||
- N/A
|
- 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
|
# Server: v0.5.2 + WebUI: r807
|
||||||
## TL;DR
|
## TL;DR
|
||||||
- Fixed Local source not working on Windows
|
- Fixed Local source not working on Windows
|
||||||
@@ -12,11 +59,12 @@
|
|||||||
- N/A
|
- N/A
|
||||||
|
|
||||||
#### Bug fixes
|
#### 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
|
### 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-Server/pull/200) by @Syer10)
|
||||||
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Tachidesk-WebUI/pull/200) by @Syer10)
|
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Tachidesk-Server/pull/199) by @Syer10)
|
||||||
|
|
||||||
|
|
||||||
## Tachidesk-WebUI
|
## 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
|
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)
|
- 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)
|
### 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`.
|
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
|
## Running in development mode
|
||||||
run `./gradlew :server:run --stacktrace` to run the server
|
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) |
|
|  | [](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.
|
# What is Tachidesk?
|
||||||
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?
|
|
||||||
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
|
<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/).
|
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.
|
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?
|
## Is this application usable? Should I test it?
|
||||||
Here is a list of current features:
|
Here is a list of current features:
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ Here is a list of current features:
|
|||||||
- Searching and browsing installed sources
|
- Searching and browsing installed sources
|
||||||
- Ability to download Manga for offline read
|
- Ability to download Manga for offline read
|
||||||
- Backup and restore support powered by Tachiyomi Backups
|
- Backup and restore support powered by Tachiyomi Backups
|
||||||
|
- Viewing latest updated chapters.
|
||||||
- From Aniyomi
|
- From Aniyomi
|
||||||
- Installing and executing Aniyomi's Extensions
|
- Installing and executing Aniyomi's Extensions
|
||||||
- Searching and browsing installed sources.
|
- 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:** 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
|
# Downloading and Running the app
|
||||||
## General Requirements
|
## General Requirements
|
||||||
In order to use the app effectively you need the following:
|
In order to use the app effectively you need the following:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ plugins {
|
|||||||
kotlin("jvm") version kotlinVersion
|
kotlin("jvm") version kotlinVersion
|
||||||
kotlin("plugin.serialization") version kotlinVersion
|
kotlin("plugin.serialization") version kotlinVersion
|
||||||
id("org.jmailen.kotlinter") version "3.6.0"
|
id("org.jmailen.kotlinter") version "3.6.0"
|
||||||
|
id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@@ -17,6 +18,7 @@ allprojects {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
|
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ val projects = listOf(
|
|||||||
configure(projects) {
|
configure(projects) {
|
||||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||||
|
apply(plugin = "org.jmailen.kotlinter")
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
@@ -37,6 +40,7 @@ configure(projects) {
|
|||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
|
dependsOn(formatKotlin)
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
freeCompilerArgs = listOf(
|
freeCompilerArgs = listOf(
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const val kotlinVersion = "1.5.30"
|
|||||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||||
|
|
||||||
// should be bumped with each stable release
|
// 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
|
// counts commits on the master branch
|
||||||
val tachideskRevision = runCatching {
|
val tachideskRevision = runCatching {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#/bin/bash
|
||||||
|
|
||||||
# Copyright (C) Contributors to the Suwayomi project
|
# 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="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_release="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64"
|
||||||
jre_url="https://cdn.azul.com/zulu/bin/$jre"
|
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"
|
electron="electron-$electron_version-darwin-arm64.zip"
|
||||||
else
|
else
|
||||||
echo "Unsupported arch value: $1"
|
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 de.undercouch.gradle.tasks.download.Download
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
id("com.github.johnrengelman.shadow") version "7.0.0"
|
id("com.github.johnrengelman.shadow") version "7.0.0"
|
||||||
id("com.github.gmazzo.buildconfig") version "3.0.3"
|
id("com.github.gmazzo.buildconfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -70,16 +69,11 @@ dependencies {
|
|||||||
|
|
||||||
// uncomment to test extensions directly
|
// uncomment to test extensions directly
|
||||||
// implementation(fileTree("lib/"))
|
// implementation(fileTree("lib/"))
|
||||||
|
implementation(kotlin("script-runtime"))
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set(MainClass)
|
mainClass.set(MainClass)
|
||||||
|
|
||||||
// uncomment for testing electron
|
|
||||||
// applicationDefaultJvmArgs = listOf(
|
|
||||||
// "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
|
||||||
// "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -127,20 +121,13 @@ tasks {
|
|||||||
archiveBaseName.set(rootProject.name)
|
archiveBaseName.set(rootProject.name)
|
||||||
archiveVersion.set(tachideskVersion)
|
archiveVersion.set(tachideskVersion)
|
||||||
archiveClassifier.set(tachideskRevision)
|
archiveClassifier.set(tachideskRevision)
|
||||||
|
destinationDirectory.set(File("$rootDir/server/build"))
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnit()
|
useJUnit()
|
||||||
}
|
}
|
||||||
|
|
||||||
withType<ShadowJar> {
|
|
||||||
destinationDirectory.set(File("$rootDir/server/build"))
|
|
||||||
}
|
|
||||||
|
|
||||||
named("run") {
|
|
||||||
dependsOn(":formatKotlin", ":lintKotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
named<Copy>("processResources") {
|
named<Copy>("processResources") {
|
||||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
mustRunAfter("downloadWebUI")
|
mustRunAfter("downloadWebUI")
|
||||||
@@ -171,4 +158,16 @@ tasks {
|
|||||||
|
|
||||||
overwrite(shouldOverwrite())
|
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
|
package eu.kanade.tachiyomi.source.local
|
||||||
|
|
||||||
import com.github.junrar.Archive
|
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.Directory
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Epub
|
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Epub
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource.Format.Rar
|
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.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
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.chapter.ChapterRecognition
|
||||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
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.jsonArray
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import mu.KotlinLogging
|
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.insert
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@@ -51,14 +41,12 @@ import suwayomi.tachidesk.server.ApplicationDirs
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
class LocalSource : HttpSource() {
|
class LocalSource : CatalogueSource {
|
||||||
companion object {
|
companion object {
|
||||||
const val ID = 0L
|
const val ID = 0L
|
||||||
const val LANG = "localsourcelang"
|
const val LANG = "localsourcelang"
|
||||||
@@ -133,13 +121,8 @@ class LocalSource : HttpSource() {
|
|||||||
override val id = ID
|
override val id = ID
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val lang = LANG
|
override val lang = LANG
|
||||||
override val baseUrl: String = ""
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.addInterceptor(FileSystemInterceptor)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
override fun toString() = name
|
override fun toString() = name
|
||||||
@@ -181,7 +164,7 @@ class LocalSource : HttpSource() {
|
|||||||
// Try to find the cover
|
// Try to find the cover
|
||||||
val cover = getCoverFile(File("${applicationDirs.localMangaRoot}/$url"))
|
val cover = getCoverFile(File("${applicationDirs.localMangaRoot}/$url"))
|
||||||
if (cover != null && cover.exists()) {
|
if (cover != null && cover.exists()) {
|
||||||
thumbnail_url = fakeUrlFrom(cover.absolutePath)
|
thumbnail_url = cover.absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapters = fetchChapterList(this).toBlocking().first()
|
val chapters = fetchChapterList(this).toBlocking().first()
|
||||||
@@ -197,8 +180,7 @@ class LocalSource : HttpSource() {
|
|||||||
// Copy the cover from the first chapter found.
|
// Copy the cover from the first chapter found.
|
||||||
if (thumbnail_url == null) {
|
if (thumbnail_url == null) {
|
||||||
try {
|
try {
|
||||||
val dest = updateCover(chapter, this)
|
thumbnail_url = updateCover(chapter, this)?.absolutePath
|
||||||
thumbnail_url = dest?.absolutePath?.let { fakeUrlFrom(it) }
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error { e }
|
logger.error { e }
|
||||||
}
|
}
|
||||||
@@ -311,7 +293,7 @@ class LocalSource : HttpSource() {
|
|||||||
chapterFile.listFiles().orEmpty().sortedBy { it.name }.mapIndexed { index, page ->
|
chapterFile.listFiles().orEmpty().sortedBy { it.name }.mapIndexed { index, page ->
|
||||||
Page(
|
Page(
|
||||||
index,
|
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 Rar(val file: File) : Format()
|
||||||
data class Epub(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.ExtensionController
|
||||||
import suwayomi.tachidesk.manga.controller.MangaController
|
import suwayomi.tachidesk.manga.controller.MangaController
|
||||||
import suwayomi.tachidesk.manga.controller.SourceController
|
import suwayomi.tachidesk.manga.controller.SourceController
|
||||||
|
import suwayomi.tachidesk.manga.controller.UpdateController
|
||||||
|
|
||||||
object MangaAPI {
|
object MangaAPI {
|
||||||
fun defineEndpoints() {
|
fun defineEndpoints() {
|
||||||
@@ -106,5 +107,9 @@ object MangaAPI {
|
|||||||
get("{mangaId}/chapter/{chapterIndex}", DownloadController::queueChapter)
|
get("{mangaId}/chapter/{chapterIndex}", DownloadController::queueChapter)
|
||||||
delete("{mangaId}/chapter/{chapterIndex}", DownloadController::unqueueChapter)
|
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.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
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.chapter.ChapterRecognition
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
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.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
@@ -20,11 +21,12 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.manga.impl.Manga.getManga
|
import suwayomi.tachidesk.manga.impl.Manga.getManga
|
||||||
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
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.getChapterDir
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
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
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
|
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.ChapterMetaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -40,7 +42,7 @@ object Chapter {
|
|||||||
getSourceChapters(mangaId)
|
getSourceChapters(mangaId)
|
||||||
} else {
|
} else {
|
||||||
transaction {
|
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 {
|
.map {
|
||||||
ChapterTable.toDataClass(it)
|
ChapterTable.toDataClass(it)
|
||||||
}
|
}
|
||||||
@@ -52,7 +54,7 @@ object Chapter {
|
|||||||
|
|
||||||
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
|
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
|
||||||
val manga = getManga(mangaId)
|
val manga = getManga(mangaId)
|
||||||
val source = getHttpSource(manga.sourceId.toLong())
|
val source = getCatalogueSourceOrStub(manga.sourceId.toLong())
|
||||||
|
|
||||||
val sManga = SManga.create().apply {
|
val sManga = SManga.create().apply {
|
||||||
title = manga.title
|
title = manga.title
|
||||||
@@ -63,11 +65,12 @@ object Chapter {
|
|||||||
|
|
||||||
// Recognize number for new chapters.
|
// Recognize number for new chapters.
|
||||||
chapterList.forEach {
|
chapterList.forEach {
|
||||||
source.prepareNewChapter(it, sManga)
|
(source as? HttpSource)?.prepareNewChapter(it, sManga)
|
||||||
ChapterRecognition.parseChapterNumber(it, sManga)
|
ChapterRecognition.parseChapterNumber(it, sManga)
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapterCount = chapterList.count()
|
val chapterCount = chapterList.count()
|
||||||
|
var now = Instant.now().epochSecond
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||||
@@ -80,7 +83,8 @@ object Chapter {
|
|||||||
it[chapter_number] = fetchedChapter.chapter_number
|
it[chapter_number] = fetchedChapter.chapter_number
|
||||||
it[scanlator] = fetchedChapter.scanlator
|
it[scanlator] = fetchedChapter.scanlator
|
||||||
|
|
||||||
it[chapterIndex] = index + 1
|
it[sourceOrder] = index + 1
|
||||||
|
it[fetchedAt] = now++
|
||||||
it[ChapterTable.manga] = mangaId
|
it[ChapterTable.manga] = mangaId
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -90,7 +94,7 @@ object Chapter {
|
|||||||
it[chapter_number] = fetchedChapter.chapter_number
|
it[chapter_number] = fetchedChapter.chapter_number
|
||||||
it[scanlator] = fetchedChapter.scanlator
|
it[scanlator] = fetchedChapter.scanlator
|
||||||
|
|
||||||
it[chapterIndex] = index + 1
|
it[sourceOrder] = index + 1
|
||||||
it[ChapterTable.manga] = mangaId
|
it[ChapterTable.manga] = mangaId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +107,8 @@ object Chapter {
|
|||||||
val dbChapterList = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.toList() }
|
val dbChapterList = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.toList() }
|
||||||
|
|
||||||
dbChapterList.forEach {
|
dbChapterList.forEach {
|
||||||
if (it[ChapterTable.chapterIndex] >= chapterList.size ||
|
if (it[ChapterTable.sourceOrder] >= chapterList.size ||
|
||||||
chapterList[it[ChapterTable.chapterIndex] - 1].url != it[ChapterTable.url]
|
chapterList[it[ChapterTable.sourceOrder] - 1].url != it[ChapterTable.url]
|
||||||
) {
|
) {
|
||||||
transaction {
|
transaction {
|
||||||
PageTable.deleteWhere { PageTable.chapter eq it[ChapterTable.id] }
|
PageTable.deleteWhere { PageTable.chapter eq it[ChapterTable.id] }
|
||||||
@@ -137,6 +141,7 @@ object Chapter {
|
|||||||
dbChapter[ChapterTable.lastReadAt],
|
dbChapter[ChapterTable.lastReadAt],
|
||||||
|
|
||||||
chapterCount - index,
|
chapterCount - index,
|
||||||
|
dbChapter[ChapterTable.fetchedAt],
|
||||||
dbChapter[ChapterTable.isDownloaded],
|
dbChapter[ChapterTable.isDownloaded],
|
||||||
|
|
||||||
dbChapter[ChapterTable.pageCount],
|
dbChapter[ChapterTable.pageCount],
|
||||||
@@ -151,7 +156,7 @@ object Chapter {
|
|||||||
suspend fun getChapter(chapterIndex: Int, mangaId: Int): ChapterDataClass {
|
suspend fun getChapter(chapterIndex: Int, mangaId: Int): ChapterDataClass {
|
||||||
val chapterEntry = transaction {
|
val chapterEntry = transaction {
|
||||||
ChapterTable.select {
|
ChapterTable.select {
|
||||||
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||||
}.first()
|
}.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,13 +164,13 @@ object Chapter {
|
|||||||
chapterEntry[ChapterTable.isDownloaded] && firstPageExists(mangaId, chapterEntry[ChapterTable.id].value)
|
chapterEntry[ChapterTable.isDownloaded] && firstPageExists(mangaId, chapterEntry[ChapterTable.id].value)
|
||||||
return if (!isReallyDownloaded) {
|
return if (!isReallyDownloaded) {
|
||||||
transaction {
|
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
|
it[isDownloaded] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
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(
|
val pageList = source.fetchPageList(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
@@ -203,7 +208,7 @@ object Chapter {
|
|||||||
val pageCount = pageList.count()
|
val pageCount = pageList.count()
|
||||||
|
|
||||||
transaction {
|
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
|
it[ChapterTable.pageCount] = pageCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +224,8 @@ object Chapter {
|
|||||||
chapterEntry[ChapterTable.lastPageRead],
|
chapterEntry[ChapterTable.lastPageRead],
|
||||||
chapterEntry[ChapterTable.lastReadAt],
|
chapterEntry[ChapterTable.lastReadAt],
|
||||||
|
|
||||||
chapterEntry[ChapterTable.chapterIndex],
|
chapterEntry[ChapterTable.sourceOrder],
|
||||||
|
chapterEntry[ChapterTable.fetchedAt],
|
||||||
chapterEntry[ChapterTable.isDownloaded],
|
chapterEntry[ChapterTable.isDownloaded],
|
||||||
pageCount,
|
pageCount,
|
||||||
chapterCount.toInt(),
|
chapterCount.toInt(),
|
||||||
@@ -249,7 +255,7 @@ object Chapter {
|
|||||||
) {
|
) {
|
||||||
transaction {
|
transaction {
|
||||||
if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) {
|
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 {
|
isRead?.also {
|
||||||
update[ChapterTable.isRead] = it
|
update[ChapterTable.isRead] = it
|
||||||
}
|
}
|
||||||
@@ -264,7 +270,7 @@ object Chapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markPrevRead?.let {
|
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
|
it[ChapterTable.isRead] = markPrevRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +287,7 @@ object Chapter {
|
|||||||
fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) {
|
fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) {
|
||||||
transaction {
|
transaction {
|
||||||
val chapterId =
|
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
|
.first()[ChapterTable.id].value
|
||||||
val meta =
|
val meta =
|
||||||
transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } }.firstOrNull()
|
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) {
|
fun deleteChapter(mangaId: Int, chapterIndex: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
val chapterId =
|
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
|
.first()[ChapterTable.id].value
|
||||||
|
|
||||||
val chapterDir = getChapterDir(mangaId, chapterId)
|
val chapterDir = getChapterDir(mangaId, chapterId)
|
||||||
|
|
||||||
File(chapterDir).deleteRecursively()
|
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
|
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.CategoryMangaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
object Library {
|
object Library {
|
||||||
suspend fun addMangaToLibrary(mangaId: Int) {
|
suspend fun addMangaToLibrary(mangaId: Int) {
|
||||||
@@ -25,8 +26,9 @@ object Library {
|
|||||||
val defaultCategories = CategoryTable.select { CategoryTable.isDefault eq true }.toList()
|
val defaultCategories = CategoryTable.select { CategoryTable.isDefault eq true }.toList()
|
||||||
|
|
||||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
MangaTable.update({ MangaTable.id eq manga.id }) {
|
||||||
it[MangaTable.inLibrary] = true
|
it[inLibrary] = true
|
||||||
it[MangaTable.defaultCategory] = defaultCategories.isEmpty()
|
it[inLibraryAt] = Instant.now().epochSecond
|
||||||
|
it[defaultCategory] = defaultCategories.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultCategories.forEach { category ->
|
defaultCategories.forEach { category ->
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ package suwayomi.tachidesk.manga.impl
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
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.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@@ -19,11 +21,12 @@ import org.kodein.di.conf.global
|
|||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||||
import suwayomi.tachidesk.manga.impl.Source.getSource
|
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.lang.awaitSingle
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
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.clearCachedImage
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
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.impl.util.updateMangaDownloadDir
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
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.MangaStatus
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
object Manga {
|
object Manga {
|
||||||
@@ -61,43 +66,43 @@ object Manga {
|
|||||||
mangaEntry[MangaTable.genre].toGenreList(),
|
mangaEntry[MangaTable.genre].toGenreList(),
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
mangaEntry[MangaTable.inLibrary],
|
mangaEntry[MangaTable.inLibrary],
|
||||||
|
mangaEntry[MangaTable.inLibraryAt],
|
||||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||||
getMangaMetaMap(mangaId),
|
getMangaMetaMap(mangaId),
|
||||||
mangaEntry[MangaTable.realUrl],
|
mangaEntry[MangaTable.realUrl],
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
} else { // initialize manga
|
} else { // initialize manga
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||||
val sManga = SManga.create().apply {
|
val sManga = SManga.create().apply {
|
||||||
url = mangaEntry[MangaTable.url]
|
url = mangaEntry[MangaTable.url]
|
||||||
title = mangaEntry[MangaTable.title]
|
title = mangaEntry[MangaTable.title]
|
||||||
}
|
}
|
||||||
val fetchedManga = source.fetchMangaDetails(sManga).awaitSingle()
|
val networkManga = source.fetchMangaDetails(sManga).awaitSingle()
|
||||||
|
sManga.copyFrom(networkManga)
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
|
||||||
if (fetchedManga.title != mangaEntry[MangaTable.title]) {
|
if (sManga.title != mangaEntry[MangaTable.title]) {
|
||||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, fetchedManga.title)
|
val canUpdateTitle = updateMangaDownloadDir(mangaId, sManga.title)
|
||||||
|
|
||||||
if (canUpdateTitle)
|
if (canUpdateTitle)
|
||||||
it[MangaTable.title] = fetchedManga.title
|
it[MangaTable.title] = sManga.title
|
||||||
}
|
}
|
||||||
it[MangaTable.initialized] = true
|
it[MangaTable.initialized] = true
|
||||||
|
|
||||||
it[MangaTable.artist] = fetchedManga.artist
|
it[MangaTable.artist] = sManga.artist
|
||||||
it[MangaTable.author] = fetchedManga.author
|
it[MangaTable.author] = sManga.author
|
||||||
it[MangaTable.description] = truncate(fetchedManga.description, 4096)
|
it[MangaTable.description] = truncate(sManga.description, 4096)
|
||||||
it[MangaTable.genre] = fetchedManga.genre
|
it[MangaTable.genre] = sManga.genre
|
||||||
it[MangaTable.status] = fetchedManga.status
|
it[MangaTable.status] = sManga.status
|
||||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url.orEmpty().isNotEmpty())
|
if (sManga.thumbnail_url != null && sManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
|
||||||
|
|
||||||
it[MangaTable.realUrl] = try {
|
it[MangaTable.realUrl] = runCatching {
|
||||||
source.mangaDetailsRequest(sManga).url.toString()
|
(source as? HttpSource)?.mangaDetailsRequest(sManga)?.url?.toString()
|
||||||
} catch (e: Exception) {
|
}.getOrNull()
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +120,13 @@ object Manga {
|
|||||||
|
|
||||||
true,
|
true,
|
||||||
|
|
||||||
fetchedManga.artist,
|
sManga.artist,
|
||||||
fetchedManga.author,
|
sManga.author,
|
||||||
fetchedManga.description,
|
sManga.description,
|
||||||
fetchedManga.genre.toGenreList(),
|
sManga.genre.toGenreList(),
|
||||||
MangaStatus.valueOf(fetchedManga.status).name,
|
MangaStatus.valueOf(sManga.status).name,
|
||||||
mangaEntry[MangaTable.inLibrary],
|
mangaEntry[MangaTable.inLibrary],
|
||||||
|
mangaEntry[MangaTable.inLibraryAt],
|
||||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||||
getMangaMetaMap(mangaId),
|
getMangaMetaMap(mangaId),
|
||||||
mangaEntry[MangaTable.realUrl],
|
mangaEntry[MangaTable.realUrl],
|
||||||
@@ -161,25 +167,39 @@ object Manga {
|
|||||||
val saveDir = applicationDirs.mangaThumbnailsRoot
|
val saveDir = applicationDirs.mangaThumbnailsRoot
|
||||||
val fileName = mangaId.toString()
|
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]
|
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||||
val source = getHttpSource(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]
|
source.client.newCall(
|
||||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
GET(thumbnailUrl, source.headers)
|
||||||
// initialize then try again
|
).await()
|
||||||
getManga(mangaId)
|
}
|
||||||
transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!!
|
is LocalSource -> {
|
||||||
} else {
|
val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let {
|
||||||
// source provides no thumbnail url for this manga
|
val file = File(it)
|
||||||
throw NullPointerException()
|
if (file.exists()) {
|
||||||
}
|
file
|
||||||
|
} else {
|
||||||
source.client.newCall(
|
null
|
||||||
GET(thumbnailUrl, source.headers)
|
}
|
||||||
).await()
|
} ?: 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.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
|
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.lang.awaitSingle
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||||
@@ -27,14 +27,15 @@ object MangaList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getCatalogueSourceOrStub(sourceId)
|
||||||
val mangasPage = if (popular) {
|
val mangasPage = if (popular) {
|
||||||
source.fetchPopularManga(pageNum).awaitSingle()
|
source.fetchPopularManga(pageNum).awaitSingle()
|
||||||
} else {
|
} else {
|
||||||
if (source.supportsLatest)
|
if (source.supportsLatest) {
|
||||||
source.fetchLatestUpdates(pageNum).awaitSingle()
|
source.fetchLatestUpdates(pageNum).awaitSingle()
|
||||||
else
|
} else {
|
||||||
throw Exception("Source $source doesn't support latest")
|
throw Exception("Source $source doesn't support latest")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return mangasPage.processEntries(sourceId)
|
return mangasPage.processEntries(sourceId)
|
||||||
}
|
}
|
||||||
@@ -81,6 +82,7 @@ object MangaList {
|
|||||||
manga.genre.toGenreList(),
|
manga.genre.toGenreList(),
|
||||||
MangaStatus.valueOf(manga.status).name,
|
MangaStatus.valueOf(manga.status).name,
|
||||||
false, // It's a new manga entry
|
false, // It's a new manga entry
|
||||||
|
0,
|
||||||
meta = getMangaMetaMap(mangaId),
|
meta = getMangaMetaMap(mangaId),
|
||||||
realUrl = mangaEntry[MangaTable.realUrl],
|
realUrl = mangaEntry[MangaTable.realUrl],
|
||||||
freshData = true
|
freshData = true
|
||||||
@@ -103,6 +105,7 @@ object MangaList {
|
|||||||
mangaEntry[MangaTable.genre].toGenreList(),
|
mangaEntry[MangaTable.genre].toGenreList(),
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
mangaEntry[MangaTable.inLibrary],
|
mangaEntry[MangaTable.inLibrary],
|
||||||
|
mangaEntry[MangaTable.inLibraryAt],
|
||||||
meta = getMangaMetaMap(mangaId),
|
meta = getMangaMetaMap(mangaId),
|
||||||
realUrl = mangaEntry[MangaTable.realUrl],
|
realUrl = mangaEntry[MangaTable.realUrl],
|
||||||
freshData = false
|
freshData = false
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ import org.jetbrains.exposed.sql.update
|
|||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
import org.kodein.di.instance
|
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.getChapterDir
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
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
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
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.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.PageTable
|
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> {
|
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 mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||||
val chapterEntry = transaction {
|
val chapterEntry = transaction {
|
||||||
ChapterTable.select {
|
ChapterTable.select {
|
||||||
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||||
}.first()
|
}.first()
|
||||||
}
|
}
|
||||||
val chapterId = chapterEntry[ChapterTable.id].value
|
val chapterId = chapterEntry[ChapterTable.id].value
|
||||||
@@ -61,19 +62,20 @@ object Page {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// we treat Local source differently
|
// we treat Local source differently
|
||||||
if (mangaEntry[MangaTable.sourceReference] == LocalSource.ID) {
|
if (source.id == LocalSource.ID) {
|
||||||
// is of archive format
|
// is of archive format
|
||||||
if (LocalSource.pageCache.containsKey(chapterEntry[ChapterTable.url])) {
|
if (LocalSource.pageCache.containsKey(chapterEntry[ChapterTable.url])) {
|
||||||
val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index]()
|
val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index]
|
||||||
return pageStream to "image/jpeg"
|
return pageStream() to (ImageUtil.findImageType { pageStream() }?.mime ?: "image/jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
// is of directory format
|
// is of directory format
|
||||||
return ImageResponse.getNoCacheImageResponse {
|
val imageFile = File(tachiyomiPage.imageUrl!!)
|
||||||
source.fetchImage(tachiyomiPage).awaitSingle()
|
return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source as HttpSource
|
||||||
|
|
||||||
if (pageEntry[PageTable.imageUrl] == null) {
|
if (pageEntry[PageTable.imageUrl] == null) {
|
||||||
val trueImageUrl = getTrueImageUrl(tachiyomiPage, source)
|
val trueImageUrl = getTrueImageUrl(tachiyomiPage, source)
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ package suwayomi.tachidesk.manga.impl
|
|||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
|
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.lang.awaitSingle
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||||
|
|
||||||
object Search {
|
object Search {
|
||||||
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
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()
|
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle()
|
||||||
return searchManga.processEntries(sourceId)
|
return searchManga.processEntries(sourceId)
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ object Search {
|
|||||||
|
|
||||||
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList {
|
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList {
|
||||||
if (reset || !filterListCache.containsKey(sourceId)) {
|
if (reset || !filterListCache.containsKey(sourceId)) {
|
||||||
filterListCache[sourceId] = getHttpSource(sourceId).getFilterList()
|
filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList()
|
||||||
}
|
}
|
||||||
return filterListCache[sourceId]!!
|
return filterListCache[sourceId]!!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import android.content.Context
|
|||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.getPreferenceKey
|
import eu.kanade.tachiyomi.source.getPreferenceKey
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
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.conf.global
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl
|
import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl
|
||||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.invalidateSourceCache
|
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.dataclass.SourceDataClass
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
@@ -36,7 +36,7 @@ object Source {
|
|||||||
fun getSourceList(): List<SourceDataClass> {
|
fun getSourceList(): List<SourceDataClass> {
|
||||||
return transaction {
|
return transaction {
|
||||||
SourceTable.selectAll().map {
|
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()
|
val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()
|
||||||
|
|
||||||
SourceDataClass(
|
SourceDataClass(
|
||||||
@@ -44,10 +44,10 @@ object Source {
|
|||||||
it[SourceTable.name],
|
it[SourceTable.name],
|
||||||
it[SourceTable.lang],
|
it[SourceTable.lang],
|
||||||
getExtensionIconUrl(sourceExtension[ExtensionTable.apkName]),
|
getExtensionIconUrl(sourceExtension[ExtensionTable.apkName]),
|
||||||
httpSource.supportsLatest,
|
catalogueSource.supportsLatest,
|
||||||
httpSource is ConfigurableSource,
|
catalogueSource is ConfigurableSource,
|
||||||
it[SourceTable.isNsfw],
|
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
|
fun getSource(sourceId: Long): SourceDataClass { // all the data extracted fresh form the source instance
|
||||||
return transaction {
|
return transaction {
|
||||||
if (sourceId == LocalSource.ID) {
|
|
||||||
// initialize local source
|
|
||||||
getHttpSource(sourceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
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 {
|
val extension = source?.let {
|
||||||
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
|
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
|
||||||
}
|
}
|
||||||
@@ -75,10 +70,10 @@ object Source {
|
|||||||
extension!![ExtensionTable.apkName]
|
extension!![ExtensionTable.apkName]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
httpSource?.supportsLatest,
|
catalogueSource?.supportsLatest,
|
||||||
httpSource?.let { it is ConfigurableSource },
|
catalogueSource?.let { it is ConfigurableSource },
|
||||||
source?.get(SourceTable.isNsfw),
|
source?.get(SourceTable.isNsfw),
|
||||||
httpSource?.toString()
|
catalogueSource?.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +98,7 @@ object Source {
|
|||||||
* Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap]
|
* Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap]
|
||||||
*/
|
*/
|
||||||
fun getSourcePreferences(sourceId: Long): List<PreferenceObject> {
|
fun getSourcePreferences(sourceId: Long): List<PreferenceObject> {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getCatalogueSourceOrStub(sourceId)
|
||||||
|
|
||||||
if (source is ConfigurableSource) {
|
if (source is ConfigurableSource) {
|
||||||
val sourceShardPreferences =
|
val sourceShardPreferences =
|
||||||
|
|||||||
+4
-3
@@ -32,6 +32,7 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object ProtoBackupExport : ProtoBackupBase() {
|
object ProtoBackupExport : ProtoBackupBase() {
|
||||||
suspend fun createBackup(flags: BackupFlags): InputStream {
|
suspend fun createBackup(flags: BackupFlags): InputStream {
|
||||||
@@ -68,7 +69,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
|||||||
mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
||||||
MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
||||||
mangaRow[MangaTable.thumbnail_url],
|
mangaRow[MangaTable.thumbnail_url],
|
||||||
0, // not supported in Tachidesk
|
TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
|
||||||
0, // not supported in Tachidesk
|
0, // not supported in Tachidesk
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,10 +85,10 @@ object ProtoBackupExport : ProtoBackupBase() {
|
|||||||
it.read,
|
it.read,
|
||||||
it.bookmarked,
|
it.bookmarked,
|
||||||
it.lastPageRead,
|
it.lastPageRead,
|
||||||
0, // not supported in Tachidesk
|
TimeUnit.SECONDS.toMillis(it.fetchedAt),
|
||||||
it.uploadDate,
|
it.uploadDate,
|
||||||
it.chapterNumber,
|
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.io.InputStream
|
||||||
import java.lang.Integer.max
|
import java.lang.Integer.max
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object ProtoBackupImport : ProtoBackupBase() {
|
object ProtoBackupImport : ProtoBackupBase() {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
@@ -148,6 +149,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
|||||||
it[initialized] = manga.description != null
|
it[initialized] = manga.description != null
|
||||||
|
|
||||||
it[inLibrary] = manga.favorite
|
it[inLibrary] = manga.favorite
|
||||||
|
|
||||||
|
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||||
}.value
|
}.value
|
||||||
|
|
||||||
// insert chapter data
|
// insert chapter data
|
||||||
@@ -160,12 +163,14 @@ object ProtoBackupImport : ProtoBackupBase() {
|
|||||||
it[chapter_number] = chapter.chapter_number
|
it[chapter_number] = chapter.chapter_number
|
||||||
it[scanlator] = chapter.scanlator
|
it[scanlator] = chapter.scanlator
|
||||||
|
|
||||||
it[chapterIndex] = chaptersLength - chapter.source_order
|
it[sourceOrder] = chaptersLength - chapter.source_order
|
||||||
it[ChapterTable.manga] = mangaId
|
it[ChapterTable.manga] = mangaId
|
||||||
|
|
||||||
it[isRead] = chapter.read
|
it[isRead] = chapter.read
|
||||||
it[lastPageRead] = chapter.last_page_read
|
it[lastPageRead] = chapter.last_page_read
|
||||||
it[isBookmarked] = chapter.bookmark
|
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[initialized] = dbManga[initialized] || manga.description != null
|
||||||
|
|
||||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||||
|
|
||||||
|
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge chapter data
|
// merge chapter data
|
||||||
@@ -207,7 +214,7 @@ object ProtoBackupImport : ProtoBackupBase() {
|
|||||||
it[chapter_number] = chapter.chapter_number
|
it[chapter_number] = chapter.chapter_number
|
||||||
it[scanlator] = chapter.scanlator
|
it[scanlator] = chapter.scanlator
|
||||||
|
|
||||||
it[chapterIndex] = chaptersLength - chapter.source_order
|
it[sourceOrder] = chaptersLength - chapter.source_order
|
||||||
it[ChapterTable.manga] = mangaId
|
it[ChapterTable.manga] = mangaId
|
||||||
|
|
||||||
it[isRead] = chapter.read
|
it[isRead] = chapter.read
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ object DownloadManager {
|
|||||||
mangaId,
|
mangaId,
|
||||||
chapter = ChapterTable.toDataClass(
|
chapter = ChapterTable.toDataClass(
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
|
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
|
||||||
.first()
|
.first()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
|
|||||||
}
|
}
|
||||||
download.state = Finished
|
download.state = Finished
|
||||||
transaction {
|
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
|
it[isDownloaded] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import org.kodein.di.conf.global
|
|||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
|
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
|
||||||
import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
|
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
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
|
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.getPackageInfo
|
||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
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.impl.util.storage.ImageResponse.getImageResponse
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
@@ -51,9 +51,6 @@ object Extension {
|
|||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
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 {
|
suspend fun installExtension(pkgName: String): Int {
|
||||||
logger.debug("Installing $pkgName")
|
logger.debug("Installing $pkgName")
|
||||||
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
||||||
@@ -189,7 +186,7 @@ object Extension {
|
|||||||
it[name] = httpSource.name
|
it[name] = httpSource.name
|
||||||
it[lang] = httpSource.lang
|
it[lang] = httpSource.lang
|
||||||
it[extension] = extensionId
|
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}" }
|
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||||
}
|
}
|
||||||
@@ -243,7 +240,7 @@ object Extension {
|
|||||||
PackageTools.jarLoaderMap.remove(jarPath)?.close()
|
PackageTools.jarLoaderMap.remove(jarPath)?.close()
|
||||||
|
|
||||||
// clear all loaded sources
|
// clear all loaded sources
|
||||||
sources.forEach { GetHttpSource.invalidateSourceCache(it) }
|
sources.forEach { GetCatalogueSource.invalidateSourceCache(it) }
|
||||||
|
|
||||||
File(jarPath).delete()
|
File(jarPath).delete()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
import org.kodein.di.instance
|
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.impl.util.storage.SafePath
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -22,7 +23,7 @@ private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
|||||||
|
|
||||||
fun getMangaDir(mangaId: Int): String {
|
fun getMangaDir(mangaId: Int): String {
|
||||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
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 sourceDir = source.toString()
|
||||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
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 */
|
/** return value says if rename/move was successful */
|
||||||
fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
|
fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
|
||||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
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 sourceDir = source.toString()
|
||||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
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
|
* 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
|
* 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/. */
|
* 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.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||||
@@ -22,23 +23,21 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
|||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object GetHttpSource {
|
object GetCatalogueSource {
|
||||||
private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
|
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>(
|
||||||
|
mapOf(LocalSource.ID to LocalSource())
|
||||||
|
)
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||||
|
|
||||||
fun getHttpSource(sourceId: Long): HttpSource {
|
fun getCatalogueSource(sourceId: Long): CatalogueSource? {
|
||||||
val cachedResult: HttpSource? = sourceCache[sourceId]
|
val cachedResult: CatalogueSource? = sourceCache[sourceId]
|
||||||
if (cachedResult != null) {
|
if (cachedResult != null) {
|
||||||
return cachedResult
|
return cachedResult
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceRecord = transaction {
|
val sourceRecord = transaction {
|
||||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
|
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||||
}
|
} ?: return null
|
||||||
|
|
||||||
if (sourceId == LocalSource.ID) {
|
|
||||||
return LocalSource()
|
|
||||||
}
|
|
||||||
|
|
||||||
val extensionId = sourceRecord[SourceTable.extension]
|
val extensionId = sourceRecord[SourceTable.extension]
|
||||||
val extensionRecord = transaction {
|
val extensionRecord = transaction {
|
||||||
@@ -60,6 +59,10 @@ object GetHttpSource {
|
|||||||
return sourceCache[sourceId]!!
|
return sourceCache[sourceId]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource {
|
||||||
|
return getCatalogueSource(sourceId) ?: StubSource(sourceId)
|
||||||
|
}
|
||||||
|
|
||||||
fun invalidateSourceCache(sourceId: Long) {
|
fun invalidateSourceCache(sourceId: Long) {
|
||||||
sourceCache.remove(sourceId)
|
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 */
|
/** last read page, zero means not read/no data */
|
||||||
val lastReadAt: Long,
|
val lastReadAt: Long,
|
||||||
|
|
||||||
|
// TODO(v0.6.0): rename to sourceOrder
|
||||||
/** this chapter's index, starts with 1 */
|
/** this chapter's index, starts with 1 */
|
||||||
val index: Int,
|
val index: Int,
|
||||||
|
|
||||||
|
/** the date we fist saw this chapter*/
|
||||||
|
val fetchedAt: Long,
|
||||||
|
|
||||||
/** is chapter downloaded */
|
/** is chapter downloaded */
|
||||||
val downloaded: Boolean,
|
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 genre: List<String> = emptyList(),
|
||||||
val status: String = MangaStatus.UNKNOWN.name,
|
val status: String = MangaStatus.UNKNOWN.name,
|
||||||
val inLibrary: Boolean = false,
|
val inLibrary: Boolean = false,
|
||||||
|
val inLibraryAt: Long = 0,
|
||||||
val source: SourceDataClass? = null,
|
val source: SourceDataClass? = null,
|
||||||
|
|
||||||
/** meta data for clients */
|
/** meta data for clients */
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ object ChapterTable : IntIdTable() {
|
|||||||
val isBookmarked = bool("bookmark").default(false)
|
val isBookmarked = bool("bookmark").default(false)
|
||||||
val lastPageRead = integer("last_page_read").default(0)
|
val lastPageRead = integer("last_page_read").default(0)
|
||||||
val lastReadAt = long("last_read_at").default(0)
|
val lastReadAt = long("last_read_at").default(0)
|
||||||
|
val fetchedAt = long("fetched_at").default(0)
|
||||||
|
|
||||||
// index is reserved by a function
|
val sourceOrder = integer("source_order")
|
||||||
val chapterIndex = integer("index")
|
|
||||||
|
|
||||||
val isDownloaded = bool("is_downloaded").default(false)
|
val isDownloaded = bool("is_downloaded").default(false)
|
||||||
|
|
||||||
@@ -48,7 +48,8 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
|
|||||||
chapterEntry[isBookmarked],
|
chapterEntry[isBookmarked],
|
||||||
chapterEntry[lastPageRead],
|
chapterEntry[lastPageRead],
|
||||||
chapterEntry[lastReadAt],
|
chapterEntry[lastReadAt],
|
||||||
chapterEntry[chapterIndex],
|
chapterEntry[sourceOrder],
|
||||||
|
chapterEntry[fetchedAt],
|
||||||
chapterEntry[isDownloaded],
|
chapterEntry[isDownloaded],
|
||||||
chapterEntry[pageCount],
|
chapterEntry[pageCount],
|
||||||
transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() },
|
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 inLibrary = bool("in_library").default(false)
|
||||||
val defaultCategory = bool("default_category").default(true)
|
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
|
// the [source] field name is used by some ancestor of IntIdTable
|
||||||
val sourceReference = long("source")
|
val sourceReference = long("source")
|
||||||
@@ -56,6 +57,7 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) =
|
|||||||
mangaEntry[genre].toGenreList(),
|
mangaEntry[genre].toGenreList(),
|
||||||
Companion.valueOf(mangaEntry[status]).name,
|
Companion.valueOf(mangaEntry[status]).name,
|
||||||
mangaEntry[inLibrary],
|
mangaEntry[inLibrary],
|
||||||
|
mangaEntry[inLibraryAt],
|
||||||
meta = getMangaMetaMap(mangaEntry[id].value),
|
meta = getMangaMetaMap(mangaEntry[id].value),
|
||||||
realUrl = mangaEntry[realUrl],
|
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.uninstallExtension
|
||||||
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
|
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
|
||||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
|
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.lang.awaitSingle
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||||
import suwayomi.tachidesk.server.applicationSetup
|
import suwayomi.tachidesk.server.applicationSetup
|
||||||
import java.io.File
|
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)
|
setLoggingEnabled(true)
|
||||||
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
||||||
|
|||||||
Reference in New Issue
Block a user