Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 454de23844 | |||
| 1176092fc6 | |||
| 0fc2c57395 | |||
| 0b292f8d74 | |||
| 4bbe51331f | |||
| c289786dfa | |||
| 9a9752a6a1 | |||
| c43b688f8e | |||
| a6272d5221 | |||
| b440a58d95 | |||
| ec1589aa2d | |||
| 8d48e56fa0 | |||
| df7938037e | |||
| c908ee2d49 | |||
| b714abddae | |||
| 63ca189907 | |||
| 6130fb9ba2 | |||
| f0edc676fc | |||
| b68fdb0772 | |||
| 76c7bdd604 | |||
| 205101568e | |||
| efff68c49b | |||
| f74f60bb1d | |||
| 63ea28a620 | |||
| 2c6d043277 | |||
| 9a226f7b64 | |||
| 3b73a0fd72 | |||
| 2478aa77cd | |||
| a41068dbc9 | |||
| 5e47b7ae6b | |||
| debf45a7d5 | |||
| e7041e8c8c | |||
| bd960992bc | |||
| 0c5f6b432c | |||
| 49232edbd5 | |||
| b02884f58d | |||
| 845b588426 | |||
| 3a97d7c8be | |||
| 2cb2ded2d9 | |||
| 14e02bee6c | |||
| 30f7cdc1ba | |||
| 420d14fc37 | |||
| 3d7953d977 | |||
| 35238b3da1 | |||
| 446f4283e0 | |||
| 8062dd3709 | |||
| 41c8fde8c5 | |||
| d90b986d19 | |||
| 64ea8416b2 | |||
| 100a4c9d35 | |||
| 4ef6dec89a | |||
| a14cdc48bd | |||
| 8a4ddbc6df | |||
| ee33acc561 | |||
| 5fe69becf3 | |||
| d460d3ccdf | |||
| 0ee74943e8 | |||
| 82837e38d2 | |||
| 91df90d760 | |||
| 1ee37da720 | |||
| 6f8fc5b69d | |||
| be1918c769 | |||
| 0057b35a0a | |||
| d12974702a | |||
| 921c41689d | |||
| 6389899507 | |||
| 92ede2a2b3 | |||
| 826a63ed71 | |||
| d1576a2a72 | |||
| 95f218d704 | |||
| 315d3a0ac0 | |||
| 954818cef2 | |||
| 5a95ca9b1b | |||
| c1e6f4c26e | |||
| 7c603258fb | |||
| fcbc582686 | |||
| 9c4906b90b | |||
| 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: |
|
||||||
@@ -84,14 +73,10 @@ jobs:
|
|||||||
./windows-bundler.sh win32
|
./windows-bundler.sh win32
|
||||||
./windows-bundler.sh win64
|
./windows-bundler.sh win64
|
||||||
./unix-bundler.sh linux-x64
|
./unix-bundler.sh linux-x64
|
||||||
|
./debian-packager.sh
|
||||||
./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:
|
||||||
@@ -119,7 +104,7 @@ jobs:
|
|||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
artifacts: "master/server/build/*.jar,master/server/build/*.zip,master/server/build/*.tar.gz"
|
artifacts: "master/server/build/*.jar,master/server/build/*.msi,master/server/build/*.zip,master/server/build/*.tar.gz,master/server/build/*.deb"
|
||||||
owner: "Suwayomi"
|
owner: "Suwayomi"
|
||||||
repo: "Tachidesk-Server-preview"
|
repo: "Tachidesk-Server-preview"
|
||||||
tag: ${{ steps.GenTagName.outputs.value }}
|
tag: ${{ steps.GenTagName.outputs.value }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: CI Publish
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_wrapper:
|
check_wrapper:
|
||||||
@@ -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:
|
||||||
@@ -63,12 +58,13 @@ jobs:
|
|||||||
dependencies-cache-enabled: true
|
dependencies-cache-enabled: true
|
||||||
configuration-cache-enabled: true
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
- name: make bundle packages
|
- name: Make bundle packages
|
||||||
run: |
|
run: |
|
||||||
cd master/scripts
|
cd master/scripts
|
||||||
./windows-bundler.sh win32
|
./windows-bundler.sh win32
|
||||||
./windows-bundler.sh win64
|
./windows-bundler.sh win64
|
||||||
./unix-bundler.sh linux-x64
|
./unix-bundler.sh linux-x64
|
||||||
|
./debian-packager.sh
|
||||||
./unix-bundler.sh macOS-x64
|
./unix-bundler.sh macOS-x64
|
||||||
./unix-bundler.sh macOS-arm64
|
./unix-bundler.sh macOS-arm64
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
file: "master/server/build/*.jar;master/server/build/*.zip;master/server/build/*.tar.gz"
|
file: "master/server/build/*.jar;master/server/build/*.msi;master/server/build/*.zip;master/server/build/*.tar.gz;master/server/build/*.deb"
|
||||||
tags: true
|
tags: true
|
||||||
draft: true
|
draft: true
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|||||||
+12
-8
@@ -1,17 +1,21 @@
|
|||||||
# Ignore Gradle project-specific cache directory
|
# Ignore project-specific local files and dirs
|
||||||
.gradle
|
.gradle
|
||||||
.idea
|
.idea
|
||||||
gradle.properties
|
gradle.properties
|
||||||
|
|
||||||
|
# But we need these
|
||||||
|
!.idea/runConfigurations
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Ignore Gradle build output directory
|
||||||
build
|
build
|
||||||
|
server/out
|
||||||
|
AndroidCompat/out
|
||||||
|
|
||||||
|
# WebUI is either to be downloaded on-demand or is a dynamic build asset
|
||||||
server/src/main/resources/WebUI.zip
|
server/src/main/resources/WebUI.zip
|
||||||
server/tmp/
|
|
||||||
server/tachiserver-data/
|
|
||||||
|
|
||||||
# bundle asset downlaods
|
# bundling stage downlaoded assets
|
||||||
OpenJDK*.*
|
scripts/OpenJDK*
|
||||||
zulu*jre*
|
scripts/zulu*
|
||||||
electron-*.*
|
scripts/electron-*
|
||||||
rcedit-*
|
scripts/rcedit-*
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -9,20 +9,20 @@ dependencies {
|
|||||||
implementation(project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
// APK sig verifier
|
// APK sig verifier
|
||||||
compileOnly("com.android.tools.build:apksig:7.1.0-alpha12")
|
compileOnly("com.android.tools.build:apksig:7.1.0-beta05")
|
||||||
|
|
||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||||
|
|
||||||
// substitute for duktape-android
|
// substitute for duktape-android
|
||||||
implementation("org.mozilla:rhino-runtime:1.7.13") // slimmer version of 'org.mozilla:rhino'
|
implementation("org.mozilla:rhino-runtime:1.7.14") // slimmer version of 'org.mozilla:rhino'
|
||||||
implementation("org.mozilla:rhino-engine:1.7.13") // provides the same interface as 'javax.script' a.k.a Nashorn
|
implementation("org.mozilla:rhino-engine:1.7.14") // provides the same interface as 'javax.script' a.k.a Nashorn
|
||||||
|
|
||||||
// Kotlin wrapper around Java Preferences, makes certain things easier
|
// Kotlin wrapper around Java Preferences, makes certain things easier
|
||||||
val multiplatformSettingsVersion = "0.8"
|
val multiplatformSettingsVersion = "0.8.1"
|
||||||
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
||||||
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
||||||
|
|
||||||
// Android version of SimpleDateFormat
|
// Android version of SimpleDateFormat
|
||||||
implementation("com.ibm.icu:icu4j:69.1")
|
implementation("com.ibm.icu:icu4j:70.1")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,33 +13,49 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MultiSelectListPreference extends DialogPreference {
|
public class MultiSelectListPreference extends DialogPreference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/MultiSelectListPreference.java
|
||||||
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
public MultiSelectListPreference(Context context) { super(context); }
|
private CharSequence[] entries;
|
||||||
|
private CharSequence[] entryValues;
|
||||||
|
|
||||||
|
public MultiSelectListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntries(CharSequence[] entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntryValues(CharSequence[] entryValues) {
|
||||||
|
this.entryValues = entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntryValues() {
|
||||||
|
return entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public void setEntries(CharSequence[] entries) { throw new RuntimeException("Stub!"); }
|
public void setValues(Set<String> values) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public CharSequence[] getEntries() { throw new RuntimeException("Stub!"); }
|
public Set<String> getValues() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
public int findIndexOfValue(String value) {
|
||||||
public void setEntryValues(CharSequence[] entryValues) { throw new RuntimeException("Stub!"); }
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
@JsonIgnore
|
|
||||||
public CharSequence[] getEntryValues() { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public void setValues(Set<String> values) { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Set<String> getValues() { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
public int findIndexOfValue(String value) { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultValueType() {
|
public String getDefaultValueType() {
|
||||||
return "Set";
|
return "Set<String>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ package androidx.preference;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A minimal implementation of androidx.preference.Preference
|
* A minimal implementation of androidx.preference.Preference
|
||||||
@@ -113,18 +114,22 @@ public class Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Object getCurrentValue() {
|
public Object getCurrentValue() {
|
||||||
switch (getDefaultValueType()) {
|
switch (getDefaultValueType()) {
|
||||||
case "String":
|
case "String":
|
||||||
return sharedPreferences.getString(key, (String)defaultValue);
|
return sharedPreferences.getString(key, (String)defaultValue);
|
||||||
case "Boolean":
|
case "Boolean":
|
||||||
return sharedPreferences.getBoolean(key, (Boolean)defaultValue);
|
return sharedPreferences.getBoolean(key, (Boolean)defaultValue);
|
||||||
|
case "Set<String>":
|
||||||
|
return sharedPreferences.getStringSet(key, (Set<String>)defaultValue);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unsupported type");
|
throw new RuntimeException("Unsupported type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void saveNewValue(Object value) {
|
public void saveNewValue(Object value) {
|
||||||
switch (getDefaultValueType()) {
|
switch (getDefaultValueType()) {
|
||||||
case "String":
|
case "String":
|
||||||
@@ -133,6 +138,9 @@ public class Preference {
|
|||||||
case "Boolean":
|
case "Boolean":
|
||||||
sharedPreferences.edit().putBoolean(key, (Boolean)value).apply();
|
sharedPreferences.edit().putBoolean(key, (Boolean)value).apply();
|
||||||
break;
|
break;
|
||||||
|
case "Set<String>":
|
||||||
|
sharedPreferences.edit().putStringSet(key, (Set<String>)value).apply();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unsupported type");
|
throw new RuntimeException("Unsupported type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Bitmap.CompressFormat;
|
import android.graphics.Bitmap.CompressFormat;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
@@ -25,19 +26,14 @@ import android.util.Xml;
|
|||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
/** {@hide} */
|
/** {@hide} */
|
||||||
public class XmlUtils {
|
public class XmlUtils {
|
||||||
private static final String STRING_ARRAY_SEPARATOR = ":";
|
private static final String STRING_ARRAY_SEPARATOR = ":";
|
||||||
@@ -1396,9 +1392,9 @@ public class XmlUtils {
|
|||||||
} else if (tagName.equals("long")) {
|
} else if (tagName.equals("long")) {
|
||||||
return Long.valueOf(parser.getAttributeValue(null, "value"));
|
return Long.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("float")) {
|
} else if (tagName.equals("float")) {
|
||||||
return new Float(parser.getAttributeValue(null, "value"));
|
return Float.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("double")) {
|
} else if (tagName.equals("double")) {
|
||||||
return new Double(parser.getAttributeValue(null, "value"));
|
return Double.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("boolean")) {
|
} else if (tagName.equals("boolean")) {
|
||||||
return Boolean.valueOf(parser.getAttributeValue(null, "value"));
|
return Boolean.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+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?>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -12,11 +12,11 @@ import com.russhwolf.settings.ExperimentalSettingsApi
|
|||||||
import com.russhwolf.settings.ExperimentalSettingsImplementation
|
import com.russhwolf.settings.ExperimentalSettingsImplementation
|
||||||
import com.russhwolf.settings.JvmPreferencesSettings
|
import com.russhwolf.settings.JvmPreferencesSettings
|
||||||
import com.russhwolf.settings.serialization.decodeValue
|
import com.russhwolf.settings.serialization.decodeValue
|
||||||
|
import com.russhwolf.settings.serialization.decodeValueOrNull
|
||||||
import com.russhwolf.settings.serialization.encodeValue
|
import com.russhwolf.settings.serialization.encodeValue
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.builtins.SetSerializer
|
import kotlinx.serialization.builtins.SetSerializer
|
||||||
import kotlinx.serialization.builtins.nullable
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import java.util.prefs.PreferenceChangeListener
|
import java.util.prefs.PreferenceChangeListener
|
||||||
import java.util.prefs.Preferences
|
import java.util.prefs.Preferences
|
||||||
@@ -40,13 +40,13 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
|
override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
|
||||||
try {
|
try {
|
||||||
return if (defValues != null) {
|
return if (defValues != null) {
|
||||||
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, defValues)
|
preferences.decodeValue(SetSerializer(String.serializer()), key, defValues)
|
||||||
} else {
|
} else {
|
||||||
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, null)
|
preferences.decodeValueOrNull(SetSerializer(String.serializer()), key)
|
||||||
}?.toMutableSet()
|
}
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
throw ClassCastException("$key was not a StringSet")
|
throw ClassCastException("$key was not a StringSet")
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
|
|||||||
+231
-3
@@ -1,3 +1,230 @@
|
|||||||
|
# Server: v0.6.1 + WebUI: r911
|
||||||
|
## TL;DR
|
||||||
|
- msi and deb packages thanks to @mahor1221
|
||||||
|
- [Tachidesk-Flutter](https://github.com/Suwayomi/Tachidesk-Flutter) exists now!
|
||||||
|
|
||||||
|
## Tachidesk-Server Changelog
|
||||||
|
- (r1047) update (by @AriaMoradi)
|
||||||
|
- (r1048) bump version (by @AriaMoradi)
|
||||||
|
- (r1049) Update README.md (by @AriaMoradi)
|
||||||
|
- (r1050) Update README.md (by @AriaMoradi)
|
||||||
|
- (r1051) refactor getChapter ([#268](https://github.com/Suwayomi/Tachidesk-Server/pull/268) by @AriaMoradi)
|
||||||
|
- (r1052) Improve documentation with Http codes ([#261](https://github.com/Suwayomi/Tachidesk-Server/pull/261) by @Syer10)
|
||||||
|
- (r1053) Add Route to stop and reset the updater ([#260](https://github.com/Suwayomi/Tachidesk-Server/pull/260) by @ntbm)
|
||||||
|
- (r1054) ignore non image files ([#269](https://github.com/Suwayomi/Tachidesk-Server/pull/269) by @AriaMoradi)
|
||||||
|
- (r1055) fix compile erorr (by @AriaMoradi)
|
||||||
|
- (r1056) update dex2jar (by @AriaMoradi)
|
||||||
|
- (r1057) Update Gradle and Dependencies ([#281](https://github.com/Suwayomi/Tachidesk-Server/pull/281) by @Syer10)
|
||||||
|
- (r1058) Handlers must return a result ([#282](https://github.com/Suwayomi/Tachidesk-Server/pull/282) by @Syer10)
|
||||||
|
- (r1059) Allow app compilation on Java 18+ ([#286](https://github.com/Suwayomi/Tachidesk-Server/pull/286) by @Syer10)
|
||||||
|
- (r1060) Automated MSI package building ([#277](https://github.com/Suwayomi/Tachidesk-Server/pull/277) by @mahor1221)
|
||||||
|
- (r1061) Automated debian package building ([#287](https://github.com/Suwayomi/Tachidesk-Server/pull/287) by @mahor1221)
|
||||||
|
- (r1062) fix Debian package errors ([#288](https://github.com/Suwayomi/Tachidesk-Server/pull/288) by @mahor1221)
|
||||||
|
- (r1063) Fix build_push.yml Hopefully ([#289](https://github.com/Suwayomi/Tachidesk-Server/pull/289) by @mahor1221)
|
||||||
|
- (r1064) Improve windows-bundler.sh ([#290](https://github.com/Suwayomi/Tachidesk-Server/pull/290) by @mahor1221)
|
||||||
|
- (r1065) add Tachidesk-Flutter to readme ([#292](https://github.com/Suwayomi/Tachidesk-Server/pull/292)) @DattatreyaReddy)
|
||||||
|
- (r1066) no online fetch on backup ([#293](https://github.com/Suwayomi/Tachidesk-Server/pull/293) by @AriaMoradi)
|
||||||
|
- (r1067) auto-remove duplicate chapters ([#294](https://github.com/Suwayomi/Tachidesk-Server/pull/294) by @AriaMoradi)
|
||||||
|
- (r1068) remove gson ([#295](https://github.com/Suwayomi/Tachidesk-Server/pull/295) by @AriaMoradi)
|
||||||
|
|
||||||
|
## Tachidesk-WebUI Changelog
|
||||||
|
- (r894) migrate ReaderNavbar to Mui 5 ([#84](https://github.com/Suwayomi/Tachidesk-WebUI/pull/84) by @AriaMoradi)
|
||||||
|
- (r895) migrate SpinnerImage to Mui 5 ([#97](https://github.com/Suwayomi/Tachidesk-WebUI/pull/97) by @AriaMoradi)
|
||||||
|
- (r896) migrate VerticalPager to Mui 5 ([#94](https://github.com/Suwayomi/Tachidesk-WebUI/pull/94) by @AriaMoradi)
|
||||||
|
- (r897) migrate PagedPager to Mui 5 ([#93](https://github.com/Suwayomi/Tachidesk-WebUI/pull/93) by @AriaMoradi)
|
||||||
|
- (r898) MangaCard imges don't stretch now ([#110](https://github.com/Suwayomi/Tachidesk-WebUI/pull/110) by @abhijeetChawla)
|
||||||
|
- (r899) show correct title ([#111](https://github.com/Suwayomi/Tachidesk-WebUI/pull/111) by @AriaMoradi)
|
||||||
|
- (r900) migrate DoublePage to Mui 5 ([#88](https://github.com/Suwayomi/Tachidesk-WebUI/pull/88) by @AriaMoradi)
|
||||||
|
- (r901) migrate DoublePagedPager to Mui 5 ([#91](https://github.com/Suwayomi/Tachidesk-WebUI/pull/91) by @AriaMoradi)
|
||||||
|
- (r902) migrate Reader to Mui 5 ([#100](https://github.com/Suwayomi/Tachidesk-WebUI/pull/100) by @AriaMoradi)
|
||||||
|
- (r903) migrate HorizantalPager to Mui 5 ([#92](https://github.com/Suwayomi/Tachidesk-WebUI/pull/92) by @AriaMoradi)
|
||||||
|
- (r904) migrate PageNumber to Mui 5 ([#90](https://github.com/Suwayomi/Tachidesk-WebUI/pull/90) by @AriaMoradi)
|
||||||
|
- (r905) Chapter filter is woking ([#114](https://github.com/Suwayomi/Tachidesk-WebUI/pull/114) by @abhijeetChawla)
|
||||||
|
- (r906) added extension search ([#115](https://github.com/Suwayomi/Tachidesk-WebUI/pull/115) by @abhijeetChawla)
|
||||||
|
- (r907) cleanup ([#117](https://github.com/Suwayomi/Tachidesk-WebUI/pull/117) by @AriaMoradi)
|
||||||
|
- (r908) handle search shortcuts ([#116](https://github.com/Suwayomi/Tachidesk-WebUI/pull/116) by @AriaMoradi)
|
||||||
|
- (r909) Refactor for Removing unnecesary UseEffect ([#118](https://github.com/Suwayomi/Tachidesk-WebUI/pull/118) by @abhijeetChawla)
|
||||||
|
- (r910) refactor ChapterList ([#125](https://github.com/Suwayomi/Tachidesk-WebUI/pull/125) by @abhijeetChawla)
|
||||||
|
- (r911) refactor ChapterOptions ([#126](https://github.com/Suwayomi/Tachidesk-WebUI/pull/126) by @abhijeetChawla)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Server: v0.6.0 + WebUI: r893
|
||||||
|
## TL;DR
|
||||||
|
- WebUI design went through a whole lot of changes, including
|
||||||
|
- Got rid of hamburger menu, now we have a custom mobile navbar
|
||||||
|
- Unread and Download count badges
|
||||||
|
- Back button so better electron experience
|
||||||
|
- There's a whole lot more that I'm too lazy to explore.
|
||||||
|
- Completely removed anime support
|
||||||
|
- Fixed category reordering
|
||||||
|
- Added support for search filters(Server side only)
|
||||||
|
- Added support for updating library(Server side only)
|
||||||
|
- A bunch of API breaking changes(hence why bumping to v0.6.0)!
|
||||||
|
|
||||||
|
## Tachidesk-Server Changelog
|
||||||
|
- (r996) cleanup (by @AriaMoradi)
|
||||||
|
- (r999) better cleaning algorithm (by @AriaMoradi)
|
||||||
|
- (r1007) remove anime support (by @AriaMoradi)
|
||||||
|
- (r1009) Fix tests ([#226](https://github.com/Suwayomi/Tachidesk-Server/pull/226) by @ntbm)
|
||||||
|
- (r1010) Expose unread and download count of Manga in category api ([#227](https://github.com/Suwayomi/Tachidesk-Server/pull/227) by @ntbm)
|
||||||
|
- (r1011) add Cache Header to Thumbnail Response for improved library performance ([#228](https://github.com/Suwayomi/Tachidesk-Server/pull/228) by @ntbm)
|
||||||
|
- (r1013) Fix unread and download counts casing ([#230](https://github.com/Suwayomi/Tachidesk-Server/pull/230) by @Syer10)
|
||||||
|
- (r1014) Fix broken test ([#231](https://github.com/Suwayomi/Tachidesk-Server/pull/231) by @ntbm)
|
||||||
|
- (r1016) Fix category reorder Endpoint. Added Test for Category Reorder ([#232](https://github.com/Suwayomi/Tachidesk-Server/pull/232) by @ntbm)
|
||||||
|
- (r1017) change windows bundle names (by @AriaMoradi)
|
||||||
|
- (r1018) improve tests (by @AriaMoradi)
|
||||||
|
- (r1019) allow injecting Sources (by @AriaMoradi)
|
||||||
|
- (r1020) update (by @AriaMoradi)
|
||||||
|
- (r1021) fix credit (by @AriaMoradi)
|
||||||
|
- (r1022) cleanup (by @AriaMoradi)
|
||||||
|
- (r1023) refactor (by @AriaMoradi)
|
||||||
|
- (r1024) refactor (by @AriaMoradi)
|
||||||
|
- (r1025) implement Source Filters (by @AriaMoradi)
|
||||||
|
- (r1026) ignore build artifacts generated by teting (by @AriaMoradi)
|
||||||
|
- (r1027) convert request type (by @AriaMoradi)
|
||||||
|
- (r1028) Update CONTRIBUTING.md (by @AriaMoradi)
|
||||||
|
- (r1029) stop supporting zero based image storage ([#242](https://github.com/Suwayomi/src/pull/242) by @AriaMoradi)
|
||||||
|
- (r1030) add manga data to download queue object ([#244](https://github.com/Suwayomi/src/pull/244) by @AriaMoradi)
|
||||||
|
- (r1031) Fix Manga Meta, add Manga Meta test ([#245](https://github.com/Suwayomi/src/pull/245) by @Syer10)
|
||||||
|
- (r1032) add pagination to recentChapters ([#246](https://github.com/Suwayomi/src/pull/246) by @AriaMoradi)
|
||||||
|
- (r1033) update (by @AriaMoradi)
|
||||||
|
- (r1034) Implement Update of Library/Category ([#235](https://github.com/Suwayomi/src/pull/235) by @ntbm)
|
||||||
|
- (r1035) update (by @AriaMoradi)
|
||||||
|
- (r1036) Mention the existence of Mahor's Tachidesk-GTK (by @AriaMoradi)
|
||||||
|
- (r1037) Add a Kotlin DSL for endpoint documentation ([#249](https://github.com/Suwayomi/Tachidesk-Server/pull/249) by @Syer10)
|
||||||
|
- (r1038) update (by @AriaMoradi)
|
||||||
|
- (r1039) update (by @AriaMoradi)
|
||||||
|
- (r1040) cleanup directory names ([#251](https://github.com/Suwayomi/Tachidesk-Server/pull/251) by @AriaMoradi)
|
||||||
|
- (r1041) Fix first page not being detected correctly ([#253](https://github.com/Suwayomi/Tachidesk-Server/pull/253) by @AriaMoradi)
|
||||||
|
- (r1042) Update README.md (by @AriaMoradi)
|
||||||
|
- (r1043) Update README.md (by @AriaMoradi)
|
||||||
|
- (r1044) migrate application directories ([#255](https://github.com/Suwayomi/Tachidesk-Server/pull/255) by @AriaMoradi)
|
||||||
|
- (r1045) add support for MultiSelectListPreference ([#258](https://github.com/Suwayomi/Tachidesk-Server/pull/258) by @AriaMoradi)
|
||||||
|
- (r1046) empty searchTerm support ([#259](https://github.com/Suwayomi/Tachidesk-Server/pull/259) by @AriaMoradi)
|
||||||
|
|
||||||
|
|
||||||
|
## Tachidesk-WebUI
|
||||||
|
- (r821) add Permanent sidebar for desktop widths([#46](https://github.com/Suwayomi/Tachidesk-WebUI/pull/46) by @abhijeetChawla)
|
||||||
|
- (r822) Fix Local Source being missing (by @AriaMoradi)
|
||||||
|
- (r823) fix the ugliness of bare messages (by @AriaMoradi)
|
||||||
|
- (r824) add pull request template (by @AriaMoradi)
|
||||||
|
- (r825) add Unread badges ([#48](https://github.com/Suwayomi/Tachidesk-WebUI/pull/48) by @ntbm)
|
||||||
|
- (r826) Back button implementation ([#47](https://github.com/Suwayomi/Tachidesk-WebUI/pull/47) by @abhijeetChawla)
|
||||||
|
- (r827) remove redundant '/manga' prefix from paths (by @AriaMoradi)
|
||||||
|
- (r828) refactor (by @AriaMoradi)
|
||||||
|
- (r829) put Sources and Extensions in the same screen (by @AriaMoradi)
|
||||||
|
- (r830) Set Fallback Image for broken Thumbnails ([#50](https://github.com/Suwayomi/Tachidesk-WebUI/pull/50) by @ntbm)
|
||||||
|
- (r833) Apply Api changes for unread badges ([#52](https://github.com/Suwayomi/Tachidesk-WebUI/pull/52) by @ntbm)
|
||||||
|
- (r834) add EmptyView to DownloadQueue, refactro strings ([#53](https://github.com/Suwayomi/Tachidesk-WebUI/pull/53) by @abhijeetChawla)
|
||||||
|
- (r835) Bottom navbar for mobile ([#51](https://github.com/Suwayomi/Tachidesk-WebUI/pull/51) by @abhijeetChawla)
|
||||||
|
- (r836) Implement Unread Filter for Library ([#54](https://github.com/Suwayomi/Tachidesk-WebUI/pull/54) by @ntbm)
|
||||||
|
- (r837) fix navbar broken logic (by @AriaMoradi)
|
||||||
|
- (r838) fix navbar (by @AriaMoradi)
|
||||||
|
- (r839) refactor (by @AriaMoradi)
|
||||||
|
- (r840) refactor (by @AriaMoradi)
|
||||||
|
- (r841) refactor (by @AriaMoradi)
|
||||||
|
- (r842) show different NavbarItems depending on device width (by @AriaMoradi)
|
||||||
|
- (r843) remove text decoration (by @AriaMoradi)
|
||||||
|
- (r844) fancy icon based on if path selected (by @AriaMoradi)
|
||||||
|
- (r845) custom Extension icon, google's version is shit (by @AriaMoradi)
|
||||||
|
- (r846) refactor (by @AriaMoradi)
|
||||||
|
- (r848) move info (by @AriaMoradi)
|
||||||
|
- (r849) add Search to Library ([#55](https://github.com/Suwayomi/Tachidesk-WebUI/pull/55) by @ntbm)
|
||||||
|
- (r850) add aspect ratio to the manga card. ([#56](https://github.com/Suwayomi/Tachidesk-WebUI/pull/56) by @abhijeetChawla)
|
||||||
|
- (r851) better wording (by @AriaMoradi)
|
||||||
|
- (r852) reorder nav buttons (by @AriaMoradi)
|
||||||
|
- (r853) nicer gradient (by @AriaMoradi)
|
||||||
|
- (r854) refactor MangaCard (by @AriaMoradi)
|
||||||
|
- (r855) closes #58 (by @AriaMoradi
|
||||||
|
- (r856) Add Resume Reading FAB Manga screen ([#59](https://github.com/Suwayomi/Tachidesk-WebUI/pull/59) by @abhijeetChawla)
|
||||||
|
- (r857) add filter and badge for `downloadCount` ([#62](https://github.com/Suwayomi/Tachidesk-WebUI/pull/62) by @abhijeetChawla)
|
||||||
|
- (r858) add issue template (by @AriaMoradi)
|
||||||
|
- (r859) Change color of navbar in light mode ([#65](https://github.com/Suwayomi/Tachidesk-WebUI/pull/65) by @abhijeetChawla)
|
||||||
|
- (r860) fix manga FAB margins ([#66](https://github.com/Suwayomi/Tachidesk-WebUI/pull/66) by @AriaMoradi)
|
||||||
|
- (r861) remove extra scrollbar on mobile ([#67](https://github.com/Suwayomi/Tachidesk-WebUI/pull/67) by @AriaMoradi)
|
||||||
|
- (r862) Fix Bad messages in Library Appbar search ([#70](https://github.com/Suwayomi/Tachidesk-WebUI/pull/70) by @ntbm)
|
||||||
|
- (r863) ban the style prop (by @AriaMoradi)
|
||||||
|
- (r864) Updates pagination update ([#68](https://github.com/Suwayomi/Tachidesk-WebUI/pull/68) by @AriaMoradi)
|
||||||
|
- (r865) make the whole chapter card into a button ([#73](https://github.com/Suwayomi/Tachidesk-WebUI/pull/73) by @AriaMoradi)
|
||||||
|
- (r866) fix chapter actions not working if manga is not fetched online ([#74](https://github.com/Suwayomi/Tachidesk-WebUI/pull/74) by @AriaMoradi)
|
||||||
|
- (r867) migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Tachidesk-WebUI/pull/72) by @abhijeetChawla)
|
||||||
|
- (r868) load first page on read manga ([#76](https://github.com/Suwayomi/Tachidesk-WebUI/pull/76) by @AriaMoradi)
|
||||||
|
- (r869) Revert "migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Tachidesk-WebUI/pull/72))" (by @AriaMoradi)
|
||||||
|
- (r870) migrate Backup to Mui 5 ([#106](https://github.com/Suwayomi/Tachidesk-WebUI/pull/106) by @AriaMoradi)
|
||||||
|
- (r871) migrate EmptyView to Mui 5 ([#95](https://github.com/Suwayomi/Tachidesk-WebUI/pull/95) by @AriaMoradi)
|
||||||
|
- (r872) migrate CategorySelect to Mui 5 ([#85](https://github.com/Suwayomi/Tachidesk-WebUI/pull/85) by @AriaMoradi)
|
||||||
|
- (r873) migrate LibraryOptions to Mui 5 ([#83](https://github.com/Suwayomi/Tachidesk-WebUI/pull/83) by @AriaMoradi)
|
||||||
|
- (r874) migrate ChapterCard.tsx to Mui 5 ([#80](https://github.com/Suwayomi/Tachidesk-WebUI/pull/80) by @AriaMoradi)
|
||||||
|
- (r875) migrate App.tsx to Mui 5 ([#79](https://github.com/Suwayomi/Tachidesk-WebUI/pull/79) by @AriaMoradi)
|
||||||
|
- (r876) migrate SourceConfigure to Mui 5 ([#103](https://github.com/Suwayomi/Tachidesk-WebUI/pull/103) by @AriaMoradi)
|
||||||
|
- (r877) migrate Settings to Mui 5 ([#102](https://github.com/Suwayomi/Tachidesk-WebUI/pull/102) by @AriaMoradi)
|
||||||
|
- (r878) migrate Updates to Mui 5 ([#104](https://github.com/Suwayomi/Tachidesk-WebUI/pull/104) by @AriaMoradi)
|
||||||
|
- (r879) Save tabs number in Url to persist tab when go to other paths ([#78](https://github.com/Suwayomi/Tachidesk-WebUI/pull/78) by @abhijeetChawla)
|
||||||
|
- (r880) migrate LangSelect to Mui 5 ([#86](https://github.com/Suwayomi/Tachidesk-WebUI/pull/86) by @AriaMoradi)
|
||||||
|
- (r881) migrate ExtensionCard.tsx to Mui 5 ([#81](https://github.com/Suwayomi/Tachidesk-WebUI/pull/81) by @AriaMoradi)
|
||||||
|
- (r882) migrate SingleSearch to Mui 5 ([#101](https://github.com/Suwayomi/Tachidesk-WebUI/pull/101) by @AriaMoradi)
|
||||||
|
- (r883) migrate LoadingPlaceholder to Mui 5 ([#96](https://github.com/Suwayomi/Tachidesk-WebUI/pull/96) by @AriaMoradi)
|
||||||
|
- (r884) migrate About to Mui 5 ([#105](https://github.com/Suwayomi/Tachidesk-WebUI/pull/105) by @AriaMoradi)
|
||||||
|
- (r885) migrate SourceCard to Mui 5 ([#82](https://github.com/Suwayomi/Tachidesk-WebUI/pull/82) by @AriaMoradi)
|
||||||
|
- (r886) migrate Manga to Mui 5 ([#99](https://github.com/Suwayomi/Tachidesk-WebUI/pull/99) by @AriaMoradi)
|
||||||
|
- (r887) migrate Browse to Mui 5 ([#98](https://github.com/Suwayomi/Tachidesk-WebUI/pull/98) by @AriaMoradi)
|
||||||
|
- (r888) migrate DesktopSideBar to Mui 5 ([#87](https://github.com/Suwayomi/Tachidesk-WebUI/pull/87) by @AriaMoradi)
|
||||||
|
- (r889) cleanup library ([#107](https://github.com/Suwayomi/Tachidesk-WebUI/pull/107) by @AriaMoradi)
|
||||||
|
- (r890) support for new searchTerm (by @AriaMoradi)
|
||||||
|
- (r891) Revert "support for new searchTerm" (by @AriaMoradi)
|
||||||
|
- (r892) add support for emptySearch ([#109](https://github.com/Suwayomi/Tachidesk-WebUI/pull/109) by @AriaMoradi)
|
||||||
|
- (r893) add support for MultiSelectListPreference ([#108](https://github.com/Suwayomi/Tachidesk-WebUI/pull/108) by @AriaMoradi)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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 +239,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
|
||||||
|
|||||||
+22
-5
@@ -2,10 +2,20 @@
|
|||||||
## Where should I start?
|
## Where should I start?
|
||||||
Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
|
Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
|
||||||
|
|
||||||
**Note 1:** Notify the developers on [Suwayomi discord](https://discord.gg/DDZdqZWaHA) (#programming channel) or open a WIP pull request before starting if you decide to take on working on anything from/not from the roadmap in order to avoid parallel efforts on the same issue/feature.
|
**Note 1:** Notify the developers on [Suwayomi discord](https://discord.gg/DDZdqZWaHA) (#tachidesk-server and #tachidesk-webui channels) or open a WIP pull request before starting if you decide to take on working on anything from/not from the roadmap in order to avoid parallel efforts on the same issue/feature.
|
||||||
|
|
||||||
**Note 2:** Store all changes with each direct commit/PR in [CHANGELOG.md](./CHANGELOG.md).
|
**Note 2:** Your pull request will be squashed into a single commit.
|
||||||
|
|
||||||
|
### Project goals and vision
|
||||||
|
- Porting Tachiyomi and covering it's features
|
||||||
|
- Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159)
|
||||||
|
- Generally rejecting features that Tachiyomi(main app) doesn't have,
|
||||||
|
- Unless it's something that makes sense for desktop sizes or desktop form factor (keyboard + mouse)
|
||||||
|
- Additional/crazy features can go in forks and alternative clients
|
||||||
|
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) should
|
||||||
|
- be responsive
|
||||||
|
- support both desktop and mobile form factors well
|
||||||
|
|
||||||
## How does Tachidesk-Server work?
|
## How does Tachidesk-Server work?
|
||||||
This project has two components:
|
This project has two components:
|
||||||
1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a REST API.
|
1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a REST API.
|
||||||
@@ -22,9 +32,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 +44,13 @@ 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
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
run `./gradlew :server:test` to execute all tests
|
||||||
|
to test a specific class run `./gradlew :server:test --tests <package.with.classname>`
|
||||||
|
|
||||||
|
## 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,60 +3,75 @@
|
|||||||
|-------|----------|---------|---------|
|
|-------|----------|---------|---------|
|
||||||
|  | [](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.
|
## Table of Content
|
||||||
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.
|
- [What is Tachidesk?](#what-is-tachidesk)
|
||||||
|
- [Tachidesk client projects](#tachidesk-client-projects)
|
||||||
|
* [Is this application usable? Should I test it?](#is-this-application-usable-should-i-test-it)
|
||||||
|
- [Downloading and Running the app](#downloading-and-running-the-app)
|
||||||
|
* [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||||
|
- [Launcher Scripts](#launcher-scripts)
|
||||||
|
+ [Windows](#windows)
|
||||||
|
+ [macOS](#macos)
|
||||||
|
+ [GNU/Linux](#gnulinux)
|
||||||
|
* [Other methods of getting Tachidesk](#other-methods-of-getting-tachidesk)
|
||||||
|
+ [Arch Linux](#arch-linux)
|
||||||
|
+ [Ubuntu-based distributions](#ubuntu-based-distributions)
|
||||||
|
+ [Docker](#docker)
|
||||||
|
* [Advanced Methods](#advanced-methods)
|
||||||
|
+ [Running the jar release directly](#running-the-jar-release-directly)
|
||||||
|
+ [Using Tachidesk Remotely](#using-tachidesk-remotely)
|
||||||
|
- [Syncing With Tachiyomi](#syncing-with-tachiyomi)
|
||||||
|
- [Troubleshooting and Support](#troubleshooting-and-support)
|
||||||
|
- [Contributing and Technical info](#contributing-and-technical-info)
|
||||||
|
- [Credit](#credit)
|
||||||
|
- [License](#license)
|
||||||
|
<!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ -->
|
||||||
|
|
||||||
Here's a list of known clients/user interfaces for Tachidesk-Server:
|
# What is Tachidesk?
|
||||||
- [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/).
|
||||||
|
|
||||||
Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
|
Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
|
||||||
|
|
||||||
|
`Tachidesk` is a general term used to describe the combination of Tachidesk-Server(this project) and one of our clients.
|
||||||
|
Think of it roughly like the concept of "distribution" in GNU/Linux distributions, in which Linux(Tachidesk-Server) is the kernel and the difference is which desktop environment(Tachidesk client) you get with it.
|
||||||
|
|
||||||
Tachidesk-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
|
Tachidesk-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
|
||||||
|
|
||||||
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
|
Ability to sync with Tachiyomi is a planned feature.
|
||||||
|
|
||||||
|
# Tachidesk client projects
|
||||||
|
**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:
|
||||||
|
##### Actively Developed Cients
|
||||||
|
- [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-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Tachidesk-Server. Currently the most advanced.
|
||||||
|
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
|
||||||
|
- [Tachidesk-Flutter](https://github.com/Suwayomi/Tachidesk-Flutter): A Flutter front-end for Desktop(Linux, windows, etc.), in early stage of development.
|
||||||
|
##### Inctive/Abandoned Cients
|
||||||
|
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
|
||||||
|
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client, 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:
|
||||||
|
|
||||||
- From Tachiyomi
|
- Installing and executing Tachiyomi's Extensions, So you'll get the same sources
|
||||||
- Installing and executing Tachiyomi's Extensions, So you'll get the same sources
|
- A library to save your mangas and categories to put them into
|
||||||
- A library to save your mangas and categories to put them into
|
- 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-compatible Backups
|
||||||
- Backup and restore support powered by Tachiyomi Backups
|
- Viewing latest updated chapters.
|
||||||
- From Aniyomi
|
|
||||||
- Installing and executing Aniyomi's Extensions
|
|
||||||
- Searching and browsing installed sources.
|
|
||||||
- Viewing an anime and it's episodes
|
|
||||||
|
|
||||||
**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
|
|
||||||
In order to use the app effectively you need the following:
|
|
||||||
- The jar release of Tachideesk-Server
|
|
||||||
- The Java Runtime Environment(JRE) 8 or newer (included in bundle releases)
|
|
||||||
- A Modern Browser like Google Chrome, Firefox, etc.
|
|
||||||
- ElectronJS (optional) (included in bundle releases)
|
|
||||||
- An internet connection (when you want to use online features)
|
|
||||||
## Using the jar release directly
|
|
||||||
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
|
||||||
|
|
||||||
Make sure you have The Java Runtime Environment installed on your system, Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxx.jar` (or `java -jar Tachidesk-latest.jar` if you have the latest preview) from a Terminal/Command Prompt window to run the app which will open a new browser window automatically. Also the System Tray Icon is your friend if you need to open the browser window again or close Tachidesk.
|
|
||||||
|
|
||||||
## Using Operating System Specific Bundles
|
## Using Operating System Specific Bundles
|
||||||
To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
|
To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
|
||||||
|
|
||||||
|
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
|
||||||
|
|
||||||
#### Launcher Scripts
|
#### Launcher Scripts
|
||||||
- `Tachidesk Electron Launcher`: Launches Tachidesk inside Electron as a desktop applicaton
|
- `Tachidesk Electron Launcher`: Launches Tachidesk inside Electron as a desktop applicaton
|
||||||
- `Tachidesk Browser Launcher`: Launches Tachidesk in a browser window
|
- `Tachidesk Browser Launcher`: Launches Tachidesk in a browser window
|
||||||
@@ -86,6 +101,14 @@ You can install Tachidesk from the AUR
|
|||||||
yay -S tachidesk
|
yay -S tachidesk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ubuntu-based distributions
|
||||||
|
More information can be found on the [PPA's page](https://launchpad.net/~suwayomi/+archive/ubuntu/tachidesk).
|
||||||
|
```
|
||||||
|
sudo add-apt-repository ppa:suwayomi/tachidesk
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install tachidesk
|
||||||
|
```
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
Check our Official Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Tachidesk Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default the server will be running on http://localhost:4567 open this url in your browser.
|
Check our Official Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Tachidesk Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default the server will be running on http://localhost:4567 open this url in your browser.
|
||||||
|
|
||||||
@@ -98,8 +121,33 @@ Run Container from the command line:
|
|||||||
$ docker run -p 4567:4567 ghcr.io/suwayomi/tachidesk
|
$ docker run -p 4567:4567 ghcr.io/suwayomi/tachidesk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Advanced Methods
|
||||||
|
### Running the jar release directly
|
||||||
|
In order to run the app you need the following:
|
||||||
|
- The jar release of Tachidesk-Server
|
||||||
|
- The Java Runtime Environment(JRE) 8 or newer
|
||||||
|
- A Browser like Google Chrome, Firefox, Edge, etc.
|
||||||
|
- ElectronJS (optional)
|
||||||
|
|
||||||
|
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||||
|
|
||||||
|
Make sure you have The Java Runtime Environment installed on your system, Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically.
|
||||||
|
|
||||||
### Using Tachidesk Remotely
|
### Using Tachidesk Remotely
|
||||||
You can run Tachidesk on your computer or a server and connect to it remotely through the web interface with a web browser on any device including a mobile or tablet or even your smart TV!, this method of using Tachidesk is only recommended if you are a power user and know what you are doing.
|
You can run Tachidesk on your computer or a server and connect to it remotely through one of our clients or the bundled web interface with a web browser. This method of using Tachidesk is requires a bit of networking/firewall/port forwarding/server configuration/etc. knowledge on your side, if you can run a Minecraft server and configure it, then you are good to go.
|
||||||
|
|
||||||
|
Check out [this wiki page](https://github.com/Suwayomi/Tachidesk-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Tachidesk-Server.
|
||||||
|
|
||||||
|
If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click clack away!).
|
||||||
|
|
||||||
|
## Syncing With Tachiyomi
|
||||||
|
### The Tachidesk extension
|
||||||
|
- You can install the `Tachidesk` extension inside tachiyomi.
|
||||||
|
- The extension will load Tachidesk library.
|
||||||
|
- By manipulating filters you can browse your categories.
|
||||||
|
|
||||||
|
### Other methods
|
||||||
|
Checkout [this issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159) for tracking progress.
|
||||||
|
|
||||||
## Troubleshooting and Support
|
## Troubleshooting and Support
|
||||||
See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
|
See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
|
||||||
|
|||||||
+11
-7
@@ -5,7 +5,8 @@ import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|||||||
plugins {
|
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.8.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(
|
||||||
@@ -65,22 +69,22 @@ configure(projects) {
|
|||||||
testImplementation(kotlin("test-junit5"))
|
testImplementation(kotlin("test-junit5"))
|
||||||
|
|
||||||
// coroutines
|
// coroutines
|
||||||
val coroutinesVersion = "1.5.2"
|
val coroutinesVersion = "1.6.0"
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||||
|
|
||||||
val kotlinSerializationVersion = "1.3.0-RC"
|
val kotlinSerializationVersion = "1.3.2"
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
||||||
|
|
||||||
// Dependency Injection
|
// Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di-conf-jvm:7.8.0")
|
implementation("org.kodein.di:kodein-di-conf-jvm:7.10.0")
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:1.7.32")
|
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||||
implementation("ch.qos.logback:logback-classic:1.2.6")
|
implementation("ch.qos.logback:logback-classic:1.2.6")
|
||||||
implementation("io.github.microutils:kotlin-logging:2.0.11")
|
implementation("io.github.microutils:kotlin-logging:2.1.21")
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
@@ -88,7 +92,7 @@ configure(projects) {
|
|||||||
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||||
|
|
||||||
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
|
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
|
||||||
implementation("org.jsoup:jsoup:1.14.2")
|
implementation("org.jsoup:jsoup:1.14.3")
|
||||||
|
|
||||||
// dependency of :AndroidCompat:Config
|
// dependency of :AndroidCompat:Config
|
||||||
implementation("com.typesafe:config:1.4.1")
|
implementation("com.typesafe:config:1.4.1")
|
||||||
@@ -98,7 +102,7 @@ configure(projects) {
|
|||||||
implementation("net.harawata:appdirs:1.2.1")
|
implementation("net.harawata:appdirs:1.2.1")
|
||||||
|
|
||||||
// dex2jar
|
// dex2jar
|
||||||
val dex2jarVersion = "v26"
|
val dex2jarVersion = "v35"
|
||||||
implementation("com.github.ThexXTURBOXx.dex2jar:dex-translator:$dex2jarVersion")
|
implementation("com.github.ThexXTURBOXx.dex2jar:dex-translator:$dex2jarVersion")
|
||||||
implementation("com.github.ThexXTURBOXx.dex2jar:dex-tools:$dex2jarVersion")
|
implementation("com.github.ThexXTURBOXx.dex2jar:dex-tools:$dex2jarVersion")
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import java.io.BufferedReader
|
|||||||
* 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/. */
|
||||||
|
|
||||||
const val kotlinVersion = "1.5.30"
|
const val kotlinVersion = "1.6.10"
|
||||||
|
|
||||||
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.6.1"
|
||||||
|
|
||||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r807"
|
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r911"
|
||||||
|
|
||||||
// counts commits on the master branch
|
// counts commits on the master branch
|
||||||
val tachideskRevision = runCatching {
|
val tachideskRevision = runCatching {
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,67 +17,101 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@@ -106,80 +140,95 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
warn "Could not query maximum file descriptor limit"
|
||||||
fi
|
esac
|
||||||
ulimit -n $MAX_FD
|
case $MAX_FD in #(
|
||||||
if [ $? -ne 0 ] ; then
|
'' | soft) :;; #(
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
*)
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
else
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
echo "creating debian package"
|
||||||
|
jar=$(ls ../server/build/*.jar | tail -n1)
|
||||||
|
release_ver=$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v)
|
||||||
|
orig_dir="tachidesk-$release_ver" # dir uses hyphen "-"
|
||||||
|
orig_tar_gz="tachidesk_$release_ver.orig.tar.gz" # orig file uses underscore "_"
|
||||||
|
package_name="tachidesk_$release_ver-1_all.deb"
|
||||||
|
|
||||||
|
# copy artifacts
|
||||||
|
mkdir "$orig_dir"
|
||||||
|
cp "$jar" "$orig_dir/Tachidesk.jar"
|
||||||
|
cp -r "resources/debian" "$orig_dir"
|
||||||
|
cp "resources/tachidesk-browser-launcher-standalone.sh" "$orig_dir/debian"
|
||||||
|
cp "resources/tachidesk-debug-launcher-standalone.sh" "$orig_dir/debian"
|
||||||
|
cp "resources/tachidesk-electron-launcher-standalone.sh" "$orig_dir/debian"
|
||||||
|
cp "resources/tachidesk.desktop" "$orig_dir/debian"
|
||||||
|
cp "../server/src/main/resources/icon/faviconlogo.png" "$orig_dir/debian/tachidesk.png"
|
||||||
|
|
||||||
|
# prepare
|
||||||
|
tar cvzf "$orig_tar_gz" "$orig_dir/Tachidesk.jar"
|
||||||
|
sed -i "s/\${version}/$release_ver/" "$orig_dir/debian/changelog"
|
||||||
|
|
||||||
|
# build
|
||||||
|
mkdir "build"
|
||||||
|
mv $orig_dir $orig_tar_gz "build/"
|
||||||
|
cd "build/$orig_dir/debian"
|
||||||
|
sudo apt install devscripts build-essential dh-exec
|
||||||
|
# --lintian-opts --profile debian: build Debian packages on Ubuntu
|
||||||
|
debuild -uc -us --lintian-opts --profile debian
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# clean build directory
|
||||||
|
mv "build/$package_name" "./"
|
||||||
|
rm -rf "build"
|
||||||
|
|
||||||
|
# clean up from possible previous runs
|
||||||
|
if [ -f "../server/build/$package_name" ]; then
|
||||||
|
rm "../server/build/$package_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$package_name" "../server/build/"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
tachidesk (${version}-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server
|
||||||
|
|
||||||
|
-- Mahor Foruzesh <mahorforuzesh@pm.me> Fri, 14 Jan 2022 00:00:00 +0000
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
Source: tachidesk
|
||||||
|
Section: web
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Mahor Foruzesh <mahorforuzesh@pm.me>
|
||||||
|
Build-Depends: debhelper-compat (= 12), dh-exec
|
||||||
|
Standards-Version: 4.5.1
|
||||||
|
Homepage: https://github.com/Suwayomi/Tachidesk-Server
|
||||||
|
|
||||||
|
Package: tachidesk
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, default-jre-headless (>= 8)
|
||||||
|
Description: Manga Reader
|
||||||
|
A free and open source manga reader server that runs extensions built for Tachiyomi.
|
||||||
|
Tachidesk is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.
|
||||||
Executable
+381
@@ -0,0 +1,381 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: tachidesk
|
||||||
|
Upstream-Contact: https://discord.gg/DDZdqZWaHA
|
||||||
|
Source: https://github.com/Suwayomi/Tachidesk-Server
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: Contributors to the Suwayomi project
|
||||||
|
License: MPL-2.0
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
.
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
.
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
.
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
.
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
.
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
.
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
.
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
.
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
.
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
.
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
.
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
.
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
.
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
.
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
.
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
.
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
.
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
.
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
.
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
.
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
.
|
||||||
|
2.1. Grants
|
||||||
|
.
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
.
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
.
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
.
|
||||||
|
2.2. Effective Date
|
||||||
|
.
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
.
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
.
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
.
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
.
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
.
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
.
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
.
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
.
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
.
|
||||||
|
2.5. Representation
|
||||||
|
.
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
.
|
||||||
|
2.6. Fair Use
|
||||||
|
.
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
.
|
||||||
|
2.7. Conditions
|
||||||
|
.
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
.
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
.
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
.
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
.
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
.
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
.
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
.
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
.
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
.
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
.
|
||||||
|
3.4. Notices
|
||||||
|
.
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
.
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
.
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
.
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
.
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
.
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
.
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
.
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
.
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
.
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
.
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
.
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
.
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
.
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
.
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
.
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
.
|
||||||
|
10.1. New Versions
|
||||||
|
.
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
.
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
.
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
.
|
||||||
|
10.3. Modified Versions
|
||||||
|
.
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
.
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
.
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
.
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
.
|
||||||
|
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/.
|
||||||
|
.
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
.
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
.
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
.
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/dh-exec
|
||||||
|
|
||||||
|
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk
|
||||||
|
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk-browser
|
||||||
|
debian/tachidesk-debug-launcher-standalone.sh => usr/bin/tachidesk-debug
|
||||||
|
debian/tachidesk-electron-launcher-standalone.sh => usr/bin/tachidesk-electron
|
||||||
|
Tachidesk.jar => usr/share/java/tachidesk/tachidesk.jar
|
||||||
|
debian/tachidesk.png usr/share/pixmaps/
|
||||||
|
debian/tachidesk.desktop usr/share/applications/
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# See debhelper(7) (uncomment to enable)
|
||||||
|
# output every command that modifies files on the build system.
|
||||||
|
#export DH_VERBOSE = 1
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_strip_nondeterminism:
|
||||||
|
true
|
||||||
|
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
3.0 (quilt)
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
debian/tachidesk.png
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<Product Id="*" UpgradeCode="*"
|
||||||
|
Version="$(var.ProductVersion)" Language="1033" Name="Tachidesk Server" Manufacturer="Suwayomi">
|
||||||
|
<Package InstallerVersion="300" Compressed="yes" />
|
||||||
|
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
|
||||||
|
|
||||||
|
<Condition Message="This version of Windows does not support deploying 64-bit packages.">
|
||||||
|
VersionNT64
|
||||||
|
</Condition>
|
||||||
|
|
||||||
|
<!-- Directory -->
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
<Directory Id="ProgramFiles64Folder">
|
||||||
|
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
|
||||||
|
<Directory Id="jre"/>
|
||||||
|
<Directory Id="electron"/>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ProgramMenuDir" Name="Tachidesk-Server">
|
||||||
|
<Component Id="ProgramMenuDir" Guid="*">
|
||||||
|
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="DesktopFolder" />
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<!-- Component -->
|
||||||
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
|
<Component Id="TachideskJAR" Guid="*" Win64="yes">
|
||||||
|
<File Id="Tachidesk.jar" Source="$(var.SourceDir)/Tachidesk.jar" KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskBrowserBAT" Guid="*" Win64="yes">
|
||||||
|
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
|
||||||
|
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskDebugBAT" Guid="*" Win64="yes">
|
||||||
|
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
|
||||||
|
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskElectronBAT" Guid="*" Win64="yes">
|
||||||
|
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
|
||||||
|
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<!-- Feature -->
|
||||||
|
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
|
||||||
|
<ComponentGroupRef Id="jre" />
|
||||||
|
<ComponentRef Id="TachideskJAR" />
|
||||||
|
<ComponentRef Id="TachideskBrowserBAT" />
|
||||||
|
<ComponentRef Id="TachideskDebugBAT" />
|
||||||
|
<ComponentRef Id="ProgramMenuDir" />
|
||||||
|
<ComponentGroupRef Id="electron" />
|
||||||
|
<ComponentRef Id="TachideskElectronBAT" />
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<Product Id="*" UpgradeCode="*"
|
||||||
|
Version="$(var.ProductVersion)" Language="1033" Name="Tachidesk Server" Manufacturer="Suwayomi">
|
||||||
|
<Package InstallerVersion="300" Compressed="yes" />
|
||||||
|
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
|
||||||
|
|
||||||
|
<!-- Directory -->
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
<Directory Id="ProgramFilesFolder">
|
||||||
|
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
|
||||||
|
<Directory Id="jre"/>
|
||||||
|
<Directory Id="electron"/>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ProgramMenuDir" Name="Tachidesk-Server">
|
||||||
|
<Component Id="ProgramMenuDir" Guid="*">
|
||||||
|
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="DesktopFolder" />
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<!-- Component -->
|
||||||
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
|
<Component Id="TachideskJAR" Guid="*">
|
||||||
|
<File Id="Tachidesk.jar" Source="$(var.SourceDir)/Tachidesk.jar" KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskBrowserBAT" Guid="*">
|
||||||
|
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
|
||||||
|
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskDebugBAT" Guid="*">
|
||||||
|
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
|
||||||
|
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="TachideskElectronBAT" Guid="*">
|
||||||
|
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
|
||||||
|
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
|
||||||
|
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
|
||||||
|
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
|
||||||
|
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
|
||||||
|
</File>
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<!-- Feature -->
|
||||||
|
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
|
||||||
|
<ComponentGroupRef Id="jre" />
|
||||||
|
<ComponentRef Id="TachideskJAR" />
|
||||||
|
<ComponentRef Id="TachideskBrowserBAT" />
|
||||||
|
<ComponentRef Id="TachideskDebugBAT" />
|
||||||
|
<ComponentRef Id="ProgramMenuDir" />
|
||||||
|
<ComponentGroupRef Id="electron" />
|
||||||
|
<ComponentRef Id="TachideskElectronBAT" />
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec /usr/bin/java -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ ! -f /usr/bin/electron ]; then
|
||||||
|
echo "Electron executable was not found!
|
||||||
|
In order to run this launcher, you need Electron installed.
|
||||||
|
|
||||||
|
You can install it with these commands:
|
||||||
|
sudo apt install npm
|
||||||
|
sudo npm install electron -g
|
||||||
|
"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Tachidesk
|
||||||
|
Comment=Manga Reader
|
||||||
|
Exec=/usr/bin/java -jar /usr/share/java/tachidesk/tachidesk.jar "\\$@"
|
||||||
|
Icon=tachidesk
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;
|
||||||
@@ -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"
|
||||||
@@ -40,11 +40,9 @@ jar=$(ls ../server/build/*.jar | tail -n1)
|
|||||||
jar_name=$(echo $jar | cut -d'/' -f4)
|
jar_name=$(echo $jar | cut -d'/' -f4)
|
||||||
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
|
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
|
||||||
|
|
||||||
|
|
||||||
# make release dir
|
# make release dir
|
||||||
mkdir $release_name
|
mkdir $release_name
|
||||||
|
|
||||||
|
|
||||||
echo "Dealing with jre..."
|
echo "Dealing with jre..."
|
||||||
if [ ! -f $jre ]; then
|
if [ ! -f $jre ]; then
|
||||||
curl -L $jre_url -o $jre
|
curl -L $jre_url -o $jre
|
||||||
|
|||||||
@@ -7,18 +7,18 @@
|
|||||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
electron_version="v14.0.0"
|
electron_version="v14.0.0"
|
||||||
|
arch=$1
|
||||||
if [ $1 = "win32" ]; then
|
if [ $arch = "win32" ]; then
|
||||||
jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
|
jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
|
||||||
jre_release="jdk8u292-b10"
|
jre_release="jdk8u292-b10"
|
||||||
jre_url="https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/$jre_release/$jre"
|
jre_url="https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/$jre_release/$jre"
|
||||||
arch="win32"
|
arch="windows-x86"
|
||||||
electron="electron-$electron_version-win32-ia32.zip"
|
electron="electron-$electron_version-win32-ia32.zip"
|
||||||
else
|
else
|
||||||
jre="OpenJDK8U-jre_x64_windows_hotspot_8u302b08.zip"
|
jre="OpenJDK8U-jre_x64_windows_hotspot_8u302b08.zip"
|
||||||
jre_release="jdk8u302-b08"
|
jre_release="jdk8u302-b08"
|
||||||
jre_url="https://github.com/adoptium/temurin8-binaries/releases/download/$jre_release/$jre"
|
jre_url="https://github.com/adoptium/temurin8-binaries/releases/download/$jre_release/$jre"
|
||||||
arch="win64"
|
arch="windows-x64"
|
||||||
electron="electron-$electron_version-win32-x64.zip"
|
electron="electron-$electron_version-win32-x64.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -30,11 +30,9 @@ jar=$(ls ../server/build/*.jar | tail -n1)
|
|||||||
jar_name=$(echo $jar | cut -d'/' -f4)
|
jar_name=$(echo $jar | cut -d'/' -f4)
|
||||||
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
|
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
|
||||||
|
|
||||||
|
|
||||||
# make release dir
|
# make release dir
|
||||||
mkdir $release_name
|
mkdir $release_name
|
||||||
|
|
||||||
|
|
||||||
echo "Dealing with jre..."
|
echo "Dealing with jre..."
|
||||||
if [ ! -f $jre ]; then
|
if [ ! -f $jre ]; then
|
||||||
curl -L $jre_url -o $jre
|
curl -L $jre_url -o $jre
|
||||||
@@ -62,6 +60,7 @@ if [ $CI = true ]; then
|
|||||||
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
|
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
|
||||||
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
|
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
|
||||||
sudo apt install --install-recommends winehq-stable
|
sudo apt install --install-recommends winehq-stable
|
||||||
|
sudo apt install -y wixl
|
||||||
fi
|
fi
|
||||||
# this script assumes that wine is installed here on out
|
# this script assumes that wine is installed here on out
|
||||||
|
|
||||||
@@ -76,11 +75,30 @@ cp "resources/Tachidesk Electron Launcher.bat" $release_name
|
|||||||
zip_name=$release_name.zip
|
zip_name=$release_name.zip
|
||||||
zip -9 -r $zip_name $release_name
|
zip -9 -r $zip_name $release_name
|
||||||
|
|
||||||
|
# create msi package
|
||||||
|
msi_name=$release_name.msi
|
||||||
|
release_ver=$(tmp=${jar%-*} && echo ${tmp##*-} | tr -d v)
|
||||||
|
icon="../server/src/main/resources/icon/faviconlogo.ico"
|
||||||
|
|
||||||
|
find $release_name/jre | wixl-heat --var var.SourceDir -p $release_name/ --directory-ref jre --component-group jre >jre.wxs
|
||||||
|
find $release_name/electron | wixl-heat --var var.SourceDir -p $release_name/ --directory-ref electron --component-group electron >electron.wxs
|
||||||
|
if [ $arch = "win32" ]; then
|
||||||
|
wixl -D ProductVersion=$release_ver -D SourceDir=$release_name -D Icon=$icon \
|
||||||
|
--arch x86 resources/msi/tachidesk-server-x86.wxs jre.wxs electron.wxs -o $msi_name
|
||||||
|
else
|
||||||
|
wixl -D ProductVersion=$release_ver -D SourceDir=$release_name -D Icon=$icon \
|
||||||
|
--arch x64 resources/msi/tachidesk-server-x64.wxs jre.wxs electron.wxs -o $msi_name
|
||||||
|
fi
|
||||||
|
|
||||||
rm -rf $release_name
|
rm -rf $release_name
|
||||||
|
|
||||||
# clean up from possible previous runs
|
# clean up from possible previous runs
|
||||||
if [ -f ../server/build/$zip_name ]; then
|
if [ -f ../server/build/$zip_name ]; then
|
||||||
rm ../server/build/$zip_name
|
rm ../server/build/$zip_name
|
||||||
fi
|
fi
|
||||||
|
if [ -f ../server/build/$msi_name ]; then
|
||||||
|
rm ../server/build/$msi_name
|
||||||
|
fi
|
||||||
|
|
||||||
mv $zip_name ../server/build/
|
mv $zip_name ../server/build/
|
||||||
|
mv $msi_name ../server/build/
|
||||||
|
|||||||
+33
-29
@@ -1,23 +1,23 @@
|
|||||||
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.1.2"
|
||||||
id("com.github.gmazzo.buildconfig") version "3.0.3"
|
id("com.github.gmazzo.buildconfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// okhttp
|
// okhttp
|
||||||
val okhttpVersion = "4.9.1" // version is locked by Tachiyomi extensions
|
val okhttpVersion = "4.9.3" // Major version is locked by Tachiyomi extensions
|
||||||
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||||
implementation("com.squareup.okio:okio:2.10.0")
|
implementation("com.squareup.okio:okio:3.0.0")
|
||||||
|
|
||||||
// Javalin api
|
// Javalin api
|
||||||
implementation("io.javalin:javalin:4.0.0")
|
implementation("io.javalin:javalin:4.2.0")
|
||||||
|
implementation("io.javalin:javalin-openapi:4.2.0")
|
||||||
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
val jacksonVersion = "2.12.4"
|
val jacksonVersion = "2.12.4"
|
||||||
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||||
@@ -33,7 +33,7 @@ dependencies {
|
|||||||
implementation("com.h2database:h2:1.4.200")
|
implementation("com.h2database:h2:1.4.200")
|
||||||
|
|
||||||
// Exposed Migrations
|
// Exposed Migrations
|
||||||
implementation("com.github.Suwayomi:exposed-migrations:3.1.2")
|
implementation("com.github.Suwayomi:exposed-migrations:3.1.4")
|
||||||
|
|
||||||
// tray icon
|
// tray icon
|
||||||
implementation("com.dorkbox:SystemTray:4.1")
|
implementation("com.dorkbox:SystemTray:4.1")
|
||||||
@@ -41,11 +41,9 @@ dependencies {
|
|||||||
|
|
||||||
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
|
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
|
||||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.9.1")
|
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
implementation("org.jsoup:jsoup:1.14.2")
|
implementation("org.jsoup:jsoup:1.14.3")
|
||||||
implementation("com.google.code.gson:gson:2.8.8")
|
|
||||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
|
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
|
||||||
@@ -54,11 +52,11 @@ dependencies {
|
|||||||
implementation("org.ow2.asm:asm:9.2")
|
implementation("org.ow2.asm:asm:9.2")
|
||||||
|
|
||||||
// Disk & File
|
// Disk & File
|
||||||
implementation("net.lingala.zip4j:zip4j:2.9.0")
|
implementation("net.lingala.zip4j:zip4j:2.9.1")
|
||||||
implementation("com.github.junrar:junrar:7.4.0")
|
implementation("com.github.junrar:junrar:7.4.0")
|
||||||
|
|
||||||
// CloudflareInterceptor
|
// CloudflareInterceptor
|
||||||
implementation("net.sourceforge.htmlunit:htmlunit:2.52.0")
|
implementation("net.sourceforge.htmlunit:htmlunit:2.56.0")
|
||||||
|
|
||||||
// Source models and interfaces from Tachiyomi 1.x
|
// Source models and interfaces from Tachiyomi 1.x
|
||||||
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
|
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
|
||||||
@@ -70,16 +68,13 @@ dependencies {
|
|||||||
|
|
||||||
// uncomment to test extensions directly
|
// uncomment to test extensions directly
|
||||||
// implementation(fileTree("lib/"))
|
// implementation(fileTree("lib/"))
|
||||||
|
implementation(kotlin("script-runtime"))
|
||||||
|
|
||||||
|
testImplementation("io.mockk:mockk:1.12.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -109,7 +104,7 @@ buildConfig {
|
|||||||
buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag))
|
buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag))
|
||||||
|
|
||||||
|
|
||||||
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Tachidesk"))
|
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Tachidesk-Server"))
|
||||||
buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA"))
|
buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,18 +122,15 @@ tasks {
|
|||||||
archiveBaseName.set(rootProject.name)
|
archiveBaseName.set(rootProject.name)
|
||||||
archiveVersion.set(tachideskVersion)
|
archiveVersion.set(tachideskVersion)
|
||||||
archiveClassifier.set(tachideskRevision)
|
archiveClassifier.set(tachideskRevision)
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnit()
|
|
||||||
}
|
|
||||||
|
|
||||||
withType<ShadowJar> {
|
|
||||||
destinationDirectory.set(File("$rootDir/server/build"))
|
destinationDirectory.set(File("$rootDir/server/build"))
|
||||||
}
|
}
|
||||||
|
|
||||||
named("run") {
|
test {
|
||||||
dependsOn(":formatKotlin", ":lintKotlin")
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
showStandardStreams = true
|
||||||
|
events("passed", "skipped", "failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
named<Copy>("processResources") {
|
named<Copy>("processResources") {
|
||||||
@@ -171,4 +163,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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ package eu.kanade.tachiyomi
|
|||||||
* 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 android.app.Application
|
|
||||||
import com.google.gson.Gson
|
|
||||||
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
// import eu.kanade.tachiyomi.data.cache.CoverCache
|
// import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
// import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
// import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@@ -17,6 +15,7 @@ import com.google.gson.Gson
|
|||||||
// import eu.kanade.tachiyomi.data.sync.LibrarySyncManager
|
// import eu.kanade.tachiyomi.data.sync.LibrarySyncManager
|
||||||
// import eu.kanade.tachiyomi.data.track.TrackManager
|
// import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import android.app.Application
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@@ -53,8 +52,6 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
//
|
//
|
||||||
// addSingletonFactory { LibrarySyncManager(app) }
|
// addSingletonFactory { LibrarySyncManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { Gson() }
|
|
||||||
|
|
||||||
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||||
|
|
||||||
// Asynchronously init expensive components for a faster cold start
|
// Asynchronously init expensive components for a faster cold start
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package eu.kanade.tachiyomi;
|
||||||
|
|
||||||
|
public class BuildConfig {
|
||||||
|
public static final int VERSION_CODE = -1;
|
||||||
|
public static final String VERSION_NAME = "stub";
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
interface AnimeCatalogueSource : AnimeSource {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
|
||||||
*/
|
|
||||||
override val lang: String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the source has support for latest updates.
|
|
||||||
*/
|
|
||||||
val supportsLatest: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of anime.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
fun fetchPopularAnime(page: Int): Observable<AnimesPage>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of anime.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
* @param query the search query.
|
|
||||||
* @param filters the list of filters to apply.
|
|
||||||
*/
|
|
||||||
fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of latest anime updates.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
fun fetchLatestUpdates(page: Int): Observable<AnimesPage>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of filters for the source.
|
|
||||||
*/
|
|
||||||
fun getFilterList(): AnimeFilterList
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A basic interface for creating a source. It could be an online source, a local source, etc...
|
|
||||||
*/
|
|
||||||
interface AnimeSource {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id for the source. Must be unique.
|
|
||||||
*/
|
|
||||||
val id: Long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the source.
|
|
||||||
*/
|
|
||||||
val name: String
|
|
||||||
|
|
||||||
val lang: String
|
|
||||||
get() = ""
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the updated details for a anime.
|
|
||||||
*
|
|
||||||
* @param anime the anime to update.
|
|
||||||
*/
|
|
||||||
// @Deprecated("Use getAnimeDetails instead")
|
|
||||||
fun fetchAnimeDetails(anime: SAnime): Observable<SAnime>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with all the available episodes for an anime.
|
|
||||||
*
|
|
||||||
* @param anime the anime to update.
|
|
||||||
*/
|
|
||||||
// @Deprecated("Use getEpisodeList instead")
|
|
||||||
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with a list of video for the episode of an anime.
|
|
||||||
*
|
|
||||||
* @param episode the episode to get the link for.
|
|
||||||
*/
|
|
||||||
// @Deprecated("Use getEpisodeList instead")
|
|
||||||
fun fetchVideoList(episode: SEpisode): Observable<List<Video>>
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * [1.x API] Get the updated details for a anime.
|
|
||||||
// */
|
|
||||||
// @Suppress("DEPRECATION")
|
|
||||||
// override suspend fun getAnimeDetails(anime: AnimeInfo): AnimeInfo {
|
|
||||||
// val sAnime = anime.toSAnime()
|
|
||||||
// val networkAnime = fetchAnimeDetails(sAnime).awaitSingle()
|
|
||||||
// sAnime.copyFrom(networkAnime)
|
|
||||||
// return sAnime.toAnimeInfo()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * [1.x API] Get all the available episodes for a anime.
|
|
||||||
// */
|
|
||||||
// @Suppress("DEPRECATION")
|
|
||||||
// override suspend fun getEpisodeList(anime: AnimeInfo): List<EpisodeInfo> {
|
|
||||||
// return fetchEpisodeList(anime.toSAnime()).awaitSingle()
|
|
||||||
// .map { it.toEpisodeInfo() }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * [1.x API] Get a link for the episode of an anime.
|
|
||||||
// */
|
|
||||||
// @Suppress("DEPRECATION")
|
|
||||||
// override suspend fun getEpisodeLink(episode: EpisodeInfo): String {
|
|
||||||
// return fetchEpisodeLink(episode.toSEpisode()).awaitSingle()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this)
|
|
||||||
|
|
||||||
fun AnimeSource.getPreferenceKey(): String = "source_$id"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory for creating sources at runtime.
|
|
||||||
*/
|
|
||||||
interface AnimeSourceFactory {
|
|
||||||
/**
|
|
||||||
* Create a new copy of the sources
|
|
||||||
* @return The created sources
|
|
||||||
*/
|
|
||||||
fun createSources(): List<AnimeSource>
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
|
||||||
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
|
|
||||||
interface ConfigurableAnimeSource : AnimeSource {
|
|
||||||
|
|
||||||
fun setupPreferenceScreen(screen: PreferenceScreen)
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
sealed class AnimeFilter<T>(val name: String, var state: T) {
|
|
||||||
open class Header(name: String) : AnimeFilter<Any>(name, 0)
|
|
||||||
open class Separator(name: String = "") : AnimeFilter<Any>(name, 0)
|
|
||||||
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : AnimeFilter<Int>(name, state)
|
|
||||||
abstract class Text(name: String, state: String = "") : AnimeFilter<String>(name, state)
|
|
||||||
abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter<Boolean>(name, state)
|
|
||||||
abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter<Int>(name, state) {
|
|
||||||
fun isIgnored() = state == STATE_IGNORE
|
|
||||||
fun isIncluded() = state == STATE_INCLUDE
|
|
||||||
fun isExcluded() = state == STATE_EXCLUDE
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val STATE_IGNORE = 0
|
|
||||||
const val STATE_INCLUDE = 1
|
|
||||||
const val STATE_EXCLUDE = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Group<V>(name: String, state: List<V>) : AnimeFilter<List<V>>(name, state)
|
|
||||||
|
|
||||||
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) :
|
|
||||||
AnimeFilter<Sort.Selection?>(name, state) {
|
|
||||||
data class Selection(val index: Int, val ascending: Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other !is AnimeFilter<*>) return false
|
|
||||||
|
|
||||||
return name == other.name && state == other.state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = name.hashCode()
|
|
||||||
result = 31 * result + (state?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
data class AnimeFilterList(val list: List<AnimeFilter<*>>) : List<AnimeFilter<*>> by list {
|
|
||||||
|
|
||||||
constructor(vararg fs: AnimeFilter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
data class AnimesPage(val animes: List<SAnime>, val hasNextPage: Boolean)
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
// import tachiyomi.animesource.model.AnimeInfo
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface SAnime : Serializable {
|
|
||||||
|
|
||||||
var url: String
|
|
||||||
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
var artist: String?
|
|
||||||
|
|
||||||
var author: String?
|
|
||||||
|
|
||||||
var description: String?
|
|
||||||
|
|
||||||
var genre: String?
|
|
||||||
|
|
||||||
var status: Int
|
|
||||||
|
|
||||||
var thumbnail_url: String?
|
|
||||||
|
|
||||||
var initialized: Boolean
|
|
||||||
|
|
||||||
fun copyFrom(other: SAnime) {
|
|
||||||
title = other.title
|
|
||||||
|
|
||||||
if (other.author != null) {
|
|
||||||
author = other.author
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other.artist != null) {
|
|
||||||
artist = other.artist
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other.description != null) {
|
|
||||||
description = other.description
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other.genre != null) {
|
|
||||||
genre = other.genre
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other.thumbnail_url != null) {
|
|
||||||
thumbnail_url = other.thumbnail_url
|
|
||||||
}
|
|
||||||
|
|
||||||
status = other.status
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
initialized = other.initialized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val UNKNOWN = 0
|
|
||||||
const val ONGOING = 1
|
|
||||||
const val COMPLETED = 2
|
|
||||||
const val LICENSED = 3
|
|
||||||
|
|
||||||
fun create(): SAnime {
|
|
||||||
return SAnimeImpl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun SAnime.toAnimeInfo(): AnimeInfo {
|
|
||||||
// return AnimeInfo(
|
|
||||||
// key = this.url,
|
|
||||||
// title = this.title,
|
|
||||||
// artist = this.artist ?: "",
|
|
||||||
// author = this.author ?: "",
|
|
||||||
// description = this.description ?: "",
|
|
||||||
// genres = this.genre?.split(", ") ?: emptyList(),
|
|
||||||
// status = this.status,
|
|
||||||
// cover = this.thumbnail_url ?: ""
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fun AnimeInfo.toSAnime(): SAnime {
|
|
||||||
// val animeInfo = this
|
|
||||||
// return SAnime.create().apply {
|
|
||||||
// url = animeInfo.key
|
|
||||||
// title = animeInfo.title
|
|
||||||
// artist = animeInfo.artist
|
|
||||||
// author = animeInfo.author
|
|
||||||
// description = animeInfo.description
|
|
||||||
// genre = animeInfo.genres.joinToString(", ")
|
|
||||||
// status = animeInfo.status
|
|
||||||
// thumbnail_url = animeInfo.cover
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
class SAnimeImpl : SAnime {
|
|
||||||
|
|
||||||
override lateinit var url: String
|
|
||||||
|
|
||||||
override lateinit var title: String
|
|
||||||
|
|
||||||
override var artist: String? = null
|
|
||||||
|
|
||||||
override var author: String? = null
|
|
||||||
|
|
||||||
override var description: String? = null
|
|
||||||
|
|
||||||
override var genre: String? = null
|
|
||||||
|
|
||||||
override var status: Int = 0
|
|
||||||
|
|
||||||
override var thumbnail_url: String? = null
|
|
||||||
|
|
||||||
override var initialized: Boolean = false
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
// import tachiyomi.animesource.model.EpisodeInfo
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface SEpisode : Serializable {
|
|
||||||
|
|
||||||
var url: String
|
|
||||||
|
|
||||||
var name: String
|
|
||||||
|
|
||||||
var date_upload: Long
|
|
||||||
|
|
||||||
var episode_number: Float
|
|
||||||
|
|
||||||
var scanlator: String?
|
|
||||||
|
|
||||||
fun copyFrom(other: SEpisode) {
|
|
||||||
name = other.name
|
|
||||||
url = other.url
|
|
||||||
date_upload = other.date_upload
|
|
||||||
episode_number = other.episode_number
|
|
||||||
scanlator = other.scanlator
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun create(): SEpisode {
|
|
||||||
return SEpisodeImpl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun SEpisode.toEpisodeInfo(): EpisodeInfo {
|
|
||||||
// return EpisodeInfo(
|
|
||||||
// dateUpload = this.date_upload,
|
|
||||||
// key = this.url,
|
|
||||||
// name = this.name,
|
|
||||||
// number = this.episode_number,
|
|
||||||
// scanlator = this.scanlator ?: ""
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fun EpisodeInfo.toSEpisode(): SEpisode {
|
|
||||||
// val episode = this
|
|
||||||
// return SEpisode.create().apply {
|
|
||||||
// url = episode.key
|
|
||||||
// name = episode.name
|
|
||||||
// date_upload = episode.dateUpload
|
|
||||||
// episode_number = episode.number
|
|
||||||
// scanlator = episode.scanlator
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
class SEpisodeImpl : SEpisode {
|
|
||||||
|
|
||||||
override lateinit var url: String
|
|
||||||
|
|
||||||
override lateinit var name: String
|
|
||||||
|
|
||||||
override var date_upload: Long = 0
|
|
||||||
|
|
||||||
override var episode_number: Float = -1f
|
|
||||||
|
|
||||||
override var scanlator: String? = null
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.network.ProgressListener
|
|
||||||
import rx.subjects.Subject
|
|
||||||
// import tachiyomi.animesource.model.VideoUrl
|
|
||||||
|
|
||||||
open class Video(
|
|
||||||
val url: String = "",
|
|
||||||
val quality: String = "",
|
|
||||||
var videoUrl: String? = null,
|
|
||||||
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
|
|
||||||
) : ProgressListener {
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
@Volatile
|
|
||||||
var status: Int = 0
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
statusSubject?.onNext(value)
|
|
||||||
statusCallback?.invoke(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
@Volatile
|
|
||||||
var progress: Int = 0
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
statusCallback?.invoke(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private var statusSubject: Subject<Int, Int>? = null
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private var statusCallback: ((Video) -> Unit)? = null
|
|
||||||
|
|
||||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
|
||||||
progress = if (contentLength > 0) {
|
|
||||||
(100 * bytesRead / contentLength).toInt()
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setStatusSubject(subject: Subject<Int, Int>?) {
|
|
||||||
this.statusSubject = subject
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setStatusCallback(f: ((Video) -> Unit)?) {
|
|
||||||
statusCallback = f
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val QUEUE = 0
|
|
||||||
const val LOAD_VIDEO = 1
|
|
||||||
const val DOWNLOAD_IMAGE = 2
|
|
||||||
const val READY = 3
|
|
||||||
const val ERROR = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun Video.toVideoUrl(): VideoUrl {
|
|
||||||
// return VideoUrl(
|
|
||||||
// url = this.videoUrl ?: this.url
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fun VideoUrl.toVideo(index: Int): Video {
|
|
||||||
// return Video(
|
|
||||||
// videoUrl = this.url
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.online
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
import eu.kanade.tachiyomi.network.newCallWithProgress
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.net.URI
|
|
||||||
import java.net.URISyntaxException
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple implementation for sources from a website.
|
|
||||||
*/
|
|
||||||
abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network service.
|
|
||||||
*/
|
|
||||||
protected val network: NetworkHelper by injectLazy()
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Preferences that a source may need.
|
|
||||||
// */
|
|
||||||
// val preferences: SharedPreferences by lazy {
|
|
||||||
// Injekt.get<Application>().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
|
||||||
*/
|
|
||||||
abstract val baseUrl: String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version id used to generate the source id. If the site completely changes and urls are
|
|
||||||
* incompatible, you may increase this value and it'll be considered as a new source.
|
|
||||||
*/
|
|
||||||
open val versionId = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
|
||||||
* of the MD5 of the string: sourcename/language/versionId
|
|
||||||
* Note the generated id sets the sign bit to 0.
|
|
||||||
*/
|
|
||||||
override val id by lazy {
|
|
||||||
val key = "${name.lowercase()}/$lang/$versionId"
|
|
||||||
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
|
||||||
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Headers used for requests.
|
|
||||||
*/
|
|
||||||
val headers: Headers by lazy { headersBuilder().build() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default network client for doing requests.
|
|
||||||
*/
|
|
||||||
open val client: OkHttpClient
|
|
||||||
get() = network.client
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
|
||||||
*/
|
|
||||||
protected open fun headersBuilder() = Headers.Builder().apply {
|
|
||||||
add("User-Agent", DEFAULT_USER_AGENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visible name of the source.
|
|
||||||
*/
|
|
||||||
override fun toString() = "$name (${lang.uppercase()})"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of anime. Normally it's not needed to
|
|
||||||
* override this method.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
|
|
||||||
return client.newCall(popularAnimeRequest(page))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
popularAnimeParse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for the popular anime given the page.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
protected abstract fun popularAnimeRequest(page: Int): Request
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun popularAnimeParse(response: Response): AnimesPage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of anime. Normally it's not needed to
|
|
||||||
* override this method.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
* @param query the search query.
|
|
||||||
* @param filters the list of filters to apply.
|
|
||||||
*/
|
|
||||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
|
||||||
return client.newCall(searchAnimeRequest(page, query, filters))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
searchAnimeParse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for the search anime given the page.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
* @param query the search query.
|
|
||||||
* @param filters the list of filters to apply.
|
|
||||||
*/
|
|
||||||
protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun searchAnimeParse(response: Response): AnimesPage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable containing a page with a list of latest anime updates.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
|
|
||||||
return client.newCall(latestUpdatesRequest(page))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
latestUpdatesParse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for latest anime given the page.
|
|
||||||
*
|
|
||||||
* @param page the page number to retrieve.
|
|
||||||
*/
|
|
||||||
protected abstract fun latestUpdatesRequest(page: Int): Request
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun latestUpdatesParse(response: Response): AnimesPage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the updated details for a anime. Normally it's not needed to
|
|
||||||
* override this method.
|
|
||||||
*
|
|
||||||
* @param anime the anime to be updated.
|
|
||||||
*/
|
|
||||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
|
||||||
return client.newCall(animeDetailsRequest(anime))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
animeDetailsParse(response).apply { initialized = true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for the details of a anime. Override only if it's needed to change the
|
|
||||||
* url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param anime the anime to be updated.
|
|
||||||
*/
|
|
||||||
open fun animeDetailsRequest(anime: SAnime): Request {
|
|
||||||
return GET(baseUrl + anime.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns the details of a anime.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun animeDetailsParse(response: Response): SAnime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the updated episode list for a anime. Normally it's not needed to
|
|
||||||
* override this method. If a anime is licensed an empty episode list observable is returned
|
|
||||||
*
|
|
||||||
* @param anime the anime to look for episodes.
|
|
||||||
*/
|
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
|
||||||
return if (anime.status != SAnime.LICENSED) {
|
|
||||||
client.newCall(episodeListRequest(anime))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
episodeListParse(response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Observable.error(Exception("Licensed - No episodes to show"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for updating the episode list. Override only if it's needed to override
|
|
||||||
* the url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param anime the anime to look for episodes.
|
|
||||||
*/
|
|
||||||
protected open fun episodeListRequest(anime: SAnime): Request {
|
|
||||||
return GET(baseUrl + anime.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a list of episodes.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the page list for a chapter.
|
|
||||||
*
|
|
||||||
* @param chapter the chapter whose page list has to be fetched.
|
|
||||||
*/
|
|
||||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
|
||||||
return client.newCall(videoListRequest(episode))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
videoListParse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for getting the episode link. Override only if it's needed to override
|
|
||||||
* the url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param episode the episode to look for links.
|
|
||||||
*/
|
|
||||||
protected open fun videoListRequest(episode: SEpisode): Request {
|
|
||||||
return GET(baseUrl + episode.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a list of pages.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun videoListParse(response: Response): List<Video>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the page containing the source url of the image. If there's any
|
|
||||||
* error, it will return null instead of throwing an exception.
|
|
||||||
*
|
|
||||||
* @param page the page whose source image has to be fetched.
|
|
||||||
*/
|
|
||||||
open fun fetchVideoUrl(video: Video): Observable<String> {
|
|
||||||
return client.newCall(videoUrlRequest(video))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { videoUrlParse(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for getting the url to the source image. Override only if it's needed to
|
|
||||||
* override the url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param page the chapter whose page list has to be fetched
|
|
||||||
*/
|
|
||||||
protected open fun videoUrlRequest(video: Video): Request {
|
|
||||||
return GET(video.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns the absolute url to the source image.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
protected abstract fun videoUrlParse(response: Response): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable with the response of the source image.
|
|
||||||
*
|
|
||||||
* @param page the page whose source image has to be downloaded.
|
|
||||||
*/
|
|
||||||
fun fetchVideo(video: Video): Observable<Response> {
|
|
||||||
return client.newCallWithProgress(videoRequest(video), video)
|
|
||||||
.asObservableSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for getting the source image. Override only if it's needed to override
|
|
||||||
* the url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param video the video whose link has to be fetched
|
|
||||||
*/
|
|
||||||
protected open fun videoRequest(video: Video): Request {
|
|
||||||
return GET(video.videoUrl!!, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns the url of the episode without the scheme and domain. It saves some redundancy from
|
|
||||||
* database and the urls could still work after a domain change.
|
|
||||||
*
|
|
||||||
* @param url the full url to the episode.
|
|
||||||
*/
|
|
||||||
fun SEpisode.setUrlWithoutDomain(url: String) {
|
|
||||||
this.url = getUrlWithoutDomain(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns the url of the anime without the scheme and domain. It saves some redundancy from
|
|
||||||
* database and the urls could still work after a domain change.
|
|
||||||
*
|
|
||||||
* @param url the full url to the anime.
|
|
||||||
*/
|
|
||||||
fun SAnime.setUrlWithoutDomain(url: String) {
|
|
||||||
this.url = getUrlWithoutDomain(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the url of the given string without the scheme and domain.
|
|
||||||
*
|
|
||||||
* @param orig the full url.
|
|
||||||
*/
|
|
||||||
private fun getUrlWithoutDomain(orig: String): String {
|
|
||||||
return try {
|
|
||||||
val uri = URI(orig)
|
|
||||||
var out = uri.path
|
|
||||||
if (uri.query != null) {
|
|
||||||
out += "?" + uri.query
|
|
||||||
}
|
|
||||||
if (uri.fragment != null) {
|
|
||||||
out += "#" + uri.fragment
|
|
||||||
}
|
|
||||||
out
|
|
||||||
} catch (e: URISyntaxException) {
|
|
||||||
orig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before inserting a new episode into database. Use it if you need to override episode
|
|
||||||
* fields, like the title or the episode number. Do not change anything to [anime].
|
|
||||||
*
|
|
||||||
* @param episode the episode to be added.
|
|
||||||
* @param anime the anime of the episode.
|
|
||||||
*/
|
|
||||||
open fun prepareNewEpisode(episode: SEpisode, anime: SAnime) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of filters for the source.
|
|
||||||
*/
|
|
||||||
override fun getFilterList() = AnimeFilterList()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.online
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
fun AnimeHttpSource.getVideoUrl(video: Video): Observable<Video> {
|
|
||||||
video.status = Video.LOAD_VIDEO
|
|
||||||
return fetchVideoUrl(video)
|
|
||||||
.doOnError { video.status = Video.ERROR }
|
|
||||||
.onErrorReturn { null }
|
|
||||||
.doOnNext { video.videoUrl = it }
|
|
||||||
.map { video }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AnimeHttpSource.fetchUrlFromVideo(video: Video): Observable<Video> {
|
|
||||||
return Observable.just(video)
|
|
||||||
.filter { !it.videoUrl.isNullOrEmpty() }
|
|
||||||
.mergeWith(fetchRemainingVideoUrlsFromVideoList(video))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AnimeHttpSource.fetchRemainingVideoUrlsFromVideoList(video: Video): Observable<Video> {
|
|
||||||
return Observable.just(video)
|
|
||||||
.filter { it.videoUrl.isNullOrEmpty() }
|
|
||||||
.concatMap { getVideoUrl(it) }
|
|
||||||
}
|
|
||||||
-206
@@ -1,206 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.online
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple implementation for sources from a website using Jsoup, an HTML parser.
|
|
||||||
*/
|
|
||||||
abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
val animes = document.select(popularAnimeSelector()).map { element ->
|
|
||||||
popularAnimeFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasNextPage = popularAnimeNextPageSelector()?.let { selector ->
|
|
||||||
document.select(selector).first()
|
|
||||||
} != null
|
|
||||||
|
|
||||||
return AnimesPage(animes, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
|
||||||
*/
|
|
||||||
protected abstract fun popularAnimeSelector(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
|
||||||
* totally fine to fill only those two values.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [popularAnimeSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun popularAnimeFromElement(element: Element): SAnime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
|
||||||
* there's no next page.
|
|
||||||
*/
|
|
||||||
protected abstract fun popularAnimeNextPageSelector(): String?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
val animes = document.select(searchAnimeSelector()).map { element ->
|
|
||||||
searchAnimeFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasNextPage = searchAnimeNextPageSelector()?.let { selector ->
|
|
||||||
document.select(selector).first()
|
|
||||||
} != null
|
|
||||||
|
|
||||||
return AnimesPage(animes, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
|
||||||
*/
|
|
||||||
protected abstract fun searchAnimeSelector(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
|
||||||
* totally fine to fill only those two values.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [searchAnimeSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun searchAnimeFromElement(element: Element): SAnime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
|
||||||
* there's no next page.
|
|
||||||
*/
|
|
||||||
protected abstract fun searchAnimeNextPageSelector(): String?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
val animes = document.select(latestUpdatesSelector()).map { element ->
|
|
||||||
latestUpdatesFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
|
|
||||||
document.select(selector).first()
|
|
||||||
} != null
|
|
||||||
|
|
||||||
return AnimesPage(animes, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
|
||||||
*/
|
|
||||||
protected abstract fun latestUpdatesSelector(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
|
||||||
* totally fine to fill only those two values.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [latestUpdatesSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun latestUpdatesFromElement(element: Element): SAnime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
|
||||||
* there's no next page.
|
|
||||||
*/
|
|
||||||
protected abstract fun latestUpdatesNextPageSelector(): String?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns the details of a anime.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun animeDetailsParse(response: Response): SAnime {
|
|
||||||
return animeDetailsParse(response.asJsoup())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the details of the anime from the given [document].
|
|
||||||
*
|
|
||||||
* @param document the parsed document.
|
|
||||||
*/
|
|
||||||
protected abstract fun animeDetailsParse(document: Document): SAnime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a list of episodes.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
return document.select(episodeListSelector()).map { episodeFromElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each episode.
|
|
||||||
*/
|
|
||||||
protected abstract fun episodeListSelector(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a episode from the given element.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [episodeListSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun episodeFromElement(element: Element): SEpisode
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns the page list.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
return document.select(videoListSelector()).map { videoFromElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each video.
|
|
||||||
*/
|
|
||||||
protected abstract fun videoListSelector(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a video from the given element.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [videoListSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun videoFromElement(element: Element): Video
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the response from the site and returns the absolute url to the source video.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun videoUrlParse(response: Response): String {
|
|
||||||
return videoUrlParse(response.asJsoup())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute url to the source image from the document.
|
|
||||||
*
|
|
||||||
* @param document the parsed document.
|
|
||||||
*/
|
|
||||||
protected abstract fun videoUrlParse(document: Document): String
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.annoations
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Nsfw
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
// import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -11,6 +12,8 @@ import okhttp3.internal.closeQuietly
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.fullType
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
@@ -124,3 +127,12 @@ fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListene
|
|||||||
|
|
||||||
return progressClient.newCall(request)
|
return progressClient.newCall(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Response.parseAs(): T {
|
||||||
|
// Avoiding Injekt.get<Json>() due to compiler issues
|
||||||
|
val json = Injekt.getInstance<Json>(fullType<Json>().type)
|
||||||
|
this.use {
|
||||||
|
val responseBody = it.body?.string().orEmpty()
|
||||||
|
return json.decodeFromString(responseBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -44,6 +34,7 @@ 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 rx.Observable
|
import rx.Observable
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||||
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,14 +42,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"
|
||||||
@@ -101,7 +90,7 @@ class LocalSource : HttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addDbRecords() {
|
fun register() {
|
||||||
transaction {
|
transaction {
|
||||||
val sourceRecord = SourceTable.select { SourceTable.id eq ID }.firstOrNull()
|
val sourceRecord = SourceTable.select { SourceTable.id eq ID }.firstOrNull()
|
||||||
|
|
||||||
@@ -127,19 +116,16 @@ class LocalSource : HttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerCatalogueSource(ID to LocalSource())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +167,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 +183,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 }
|
||||||
}
|
}
|
||||||
@@ -308,12 +293,15 @@ class LocalSource : HttpSource() {
|
|||||||
return when (getFormat(chapterFile)) {
|
return when (getFormat(chapterFile)) {
|
||||||
is Directory -> {
|
is Directory -> {
|
||||||
Observable.just(
|
Observable.just(
|
||||||
chapterFile.listFiles().orEmpty().sortedBy { it.name }.mapIndexed { index, page ->
|
chapterFile.listFiles().orEmpty()
|
||||||
Page(
|
.sortedBy { it.name }
|
||||||
index,
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name, it::inputStream) }
|
||||||
imageUrl = fakeUrlFrom(applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name)
|
.mapIndexed { index, page ->
|
||||||
)
|
Page(
|
||||||
}
|
index,
|
||||||
|
imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Zip -> {
|
is Zip -> {
|
||||||
@@ -412,67 +400,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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.source.model
|
package eu.kanade.tachiyomi.source.model
|
||||||
|
|
||||||
sealed class Filter<T>(val name: String, var state: T) {
|
// The class is originally sealed, Tachidesk adds new subclasses for serialization
|
||||||
|
// sealed class Filter<T>(val name: String, var state: T) {
|
||||||
|
open class Filter<T>(val name: String, var state: T) {
|
||||||
open class Header(name: String) : Filter<Any>(name, 0)
|
open class Header(name: String) : Filter<Any>(name, 0)
|
||||||
open class Separator(name: String = "") : Filter<Any>(name, 0)
|
open class Separator(name: String = "") : Filter<Any>(name, 0)
|
||||||
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
|
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package suwayomi.tachidesk
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/. */
|
||||||
|
|||||||
@@ -1,380 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 io.javalin.Javalin
|
|
||||||
import suwayomi.tachidesk.anime.impl.Anime.getAnime
|
|
||||||
import suwayomi.tachidesk.anime.impl.Anime.getAnimeThumbnail
|
|
||||||
import suwayomi.tachidesk.anime.impl.AnimeList.getAnimeList
|
|
||||||
import suwayomi.tachidesk.anime.impl.Episode.getEpisode
|
|
||||||
import suwayomi.tachidesk.anime.impl.Episode.getEpisodeList
|
|
||||||
import suwayomi.tachidesk.anime.impl.Episode.modifyEpisode
|
|
||||||
import suwayomi.tachidesk.anime.impl.Search.sourceSearch
|
|
||||||
import suwayomi.tachidesk.anime.impl.Source.getAnimeSource
|
|
||||||
import suwayomi.tachidesk.anime.impl.Source.getSourceList
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.getExtensionIcon
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.installExtension
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.uninstallExtension
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.updateExtension
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.ExtensionsList.getExtensionList
|
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
|
||||||
|
|
||||||
object AnimeAPI {
|
|
||||||
fun defineEndpoints(app: Javalin) {
|
|
||||||
// list all extensions
|
|
||||||
app.get("/api/v1/anime/extension/list") { ctx ->
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
getExtensionList()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// install extension identified with "pkgName"
|
|
||||||
app.get("/api/v1/anime/extension/install/{pkgName}") { ctx ->
|
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
|
||||||
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
installExtension(pkgName)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update extension identified with "pkgName"
|
|
||||||
app.get("/api/v1/anime/extension/update/{pkgName}") { ctx ->
|
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
|
||||||
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
updateExtension(pkgName)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// uninstall extension identified with "pkgName"
|
|
||||||
app.get("/api/v1/anime/extension/uninstall/{pkgName}") { ctx ->
|
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
|
||||||
|
|
||||||
uninstallExtension(pkgName)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// icon for extension named `apkName`
|
|
||||||
app.get("/api/v1/anime/extension/icon/{apkName}") { ctx -> // TODO: move to pkgName
|
|
||||||
val apkName = ctx.pathParam("apkName")
|
|
||||||
|
|
||||||
ctx.future(
|
|
||||||
future { getExtensionIcon(apkName) }
|
|
||||||
.thenApply {
|
|
||||||
ctx.header("content-type", it.second)
|
|
||||||
it.first
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// list of sources
|
|
||||||
app.get("/api/v1/anime/source/list") { ctx ->
|
|
||||||
ctx.json(getSourceList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch source with id `sourceId`
|
|
||||||
app.get("/api/v1/anime/source/{sourceId}") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
ctx.json(getAnimeSource(sourceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// popular animes from source with id `sourceId`
|
|
||||||
app.get("/api/v1/anime/source/{sourceId}/popular/{pageNum}") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
getAnimeList(sourceId, pageNum, popular = true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// latest animes from source with id `sourceId`
|
|
||||||
app.get("/api/v1/anime/source/{sourceId}/latest/{pageNum}") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
getAnimeList(sourceId, pageNum, popular = false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get anime info
|
|
||||||
app.get("/api/v1/anime/anime/{animeId}/") { ctx ->
|
|
||||||
val animeId = ctx.pathParam("animeId").toInt()
|
|
||||||
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean() ?: false
|
|
||||||
|
|
||||||
ctx.future(
|
|
||||||
future {
|
|
||||||
getAnime(animeId, onlineFetch)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// anime thumbnail
|
|
||||||
app.get("api/v1/anime/anime/{animeId}/thumbnail") { ctx ->
|
|
||||||
val animeId = ctx.pathParam("animeId").toInt()
|
|
||||||
|
|
||||||
ctx.future(
|
|
||||||
future { getAnimeThumbnail(animeId) }
|
|
||||||
.thenApply {
|
|
||||||
ctx.header("content-type", it.second)
|
|
||||||
it.first
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// // list manga's categories
|
|
||||||
// app.get("api/v1/manga/{mangaId}/category/") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
// ctx.json(getMangaCategories(mangaId))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // adds the manga to category
|
|
||||||
// app.get("api/v1/manga/{mangaId}/category/{categoryId}") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// addMangaToCategory(mangaId, categoryId)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // removes the manga from the category
|
|
||||||
// app.delete("api/v1/manga/{mangaId}/category/{categoryId}") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// removeMangaFromCategory(mangaId, categoryId)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// get episode list when showing a anime
|
|
||||||
app.get("/api/v1/anime/anime/{animeId}/episodes") { ctx ->
|
|
||||||
val animeId = ctx.pathParam("animeId").toInt()
|
|
||||||
|
|
||||||
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
|
|
||||||
|
|
||||||
ctx.future(future { getEpisodeList(animeId, onlineFetch) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// used to display a episode, get a episode in order to show it's <Quality pending>
|
|
||||||
app.get("/api/v1/anime/anime/{animeId}/episode/{episodeIndex}") { ctx ->
|
|
||||||
val episodeIndex = ctx.pathParam("episodeIndex").toInt()
|
|
||||||
val animeId = ctx.pathParam("animeId").toInt()
|
|
||||||
ctx.future(future { getEpisode(episodeIndex, animeId) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// used to modify a episode's parameters
|
|
||||||
app.patch("/api/v1/anime/anime/{animeId}/episode/{episodeIndex}") { ctx ->
|
|
||||||
val episodeIndex = ctx.pathParam("episodeIndex").toInt()
|
|
||||||
val animeId = ctx.pathParam("animeId").toInt()
|
|
||||||
|
|
||||||
val read = ctx.formParam("read")?.toBoolean()
|
|
||||||
val bookmarked = ctx.formParam("bookmarked")?.toBoolean()
|
|
||||||
val markPrevRead = ctx.formParam("markPrevRead")?.toBoolean()
|
|
||||||
val lastPageRead = ctx.formParam("lastPageRead")?.toInt()
|
|
||||||
|
|
||||||
modifyEpisode(animeId, episodeIndex, read, bookmarked, markPrevRead, lastPageRead)
|
|
||||||
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// // get page at index "index"
|
|
||||||
// app.get("/api/v1/manga/{mangaId}/chapter/{chapterIndex}/page/{index}") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
// val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
|
||||||
// val index = ctx.pathParam("index").toInt()
|
|
||||||
//
|
|
||||||
// ctx.result(
|
|
||||||
// JavalinSetup.future { getPageImage(mangaId, chapterIndex, index) }
|
|
||||||
// .thenApply {
|
|
||||||
// ctx.header("content-type", it.second)
|
|
||||||
// it.first
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // submit a chapter for download
|
|
||||||
// app.put("/api/v1/manga/{mangaId}/chapter/{chapterIndex}/download") { ctx ->
|
|
||||||
// // TODO
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // cancel a chapter download
|
|
||||||
// app.delete("/api/v1/manga/{mangaId}/chapter/{chapterIndex}/download") { ctx ->
|
|
||||||
// // TODO
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // global search, Not implemented yet
|
|
||||||
// app.get("/api/v1/search/{searchTerm}") { ctx ->
|
|
||||||
// val searchTerm = ctx.pathParam("searchTerm")
|
|
||||||
// ctx.json(sourceGlobalSearch(searchTerm))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// single source search
|
|
||||||
app.get("/api/v1/anime/source/{sourceId}/search/{searchTerm}/{pageNum}") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.future(future { sourceSearch(sourceId, searchTerm, pageNum) })
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// // source filter list
|
|
||||||
// app.get("/api/v1/source/{sourceId}/filters/") { ctx ->
|
|
||||||
// val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
// ctx.json(sourceFilters(sourceId))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // adds the manga to library
|
|
||||||
// app.get("api/v1/manga/{mangaId}/library") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
//
|
|
||||||
// ctx.future(
|
|
||||||
// JavalinSetup.future { addMangaToLibrary(mangaId) }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // removes the manga from the library
|
|
||||||
// app.delete("api/v1/manga/{mangaId}/library") { ctx ->
|
|
||||||
// val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
//
|
|
||||||
// ctx.future(
|
|
||||||
// JavalinSetup.future { removeMangaFromLibrary(mangaId) }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // lists mangas that have no category assigned
|
|
||||||
// app.get("/api/v1/library/") { ctx ->
|
|
||||||
// ctx.json(getLibraryMangas())
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // category list
|
|
||||||
// app.get("/api/v1/category/") { ctx ->
|
|
||||||
// ctx.json(Category.getCategoryList())
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // category create
|
|
||||||
// app.post("/api/v1/category/") { ctx ->
|
|
||||||
// val name = ctx.formParam("name")!!
|
|
||||||
// Category.createCategory(name)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // returns some static info of the current app build
|
|
||||||
// app.get("/api/v1/about/") { ctx ->
|
|
||||||
// ctx.json(About.getAbout())
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // category modification
|
|
||||||
// app.patch("/api/v1/category/{categoryId}") { ctx ->
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// val name = ctx.formParam("name")
|
|
||||||
// val isDefault = ctx.formParam("default")?.toBoolean()
|
|
||||||
// Category.updateCategory(categoryId, name, isDefault)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // category re-ordering
|
|
||||||
// app.patch("/api/v1/category/{categoryId}/reorder") { ctx ->
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// val from = ctx.formParam("from")!!.toInt()
|
|
||||||
// val to = ctx.formParam("to")!!.toInt()
|
|
||||||
// Category.reorderCategory(categoryId, from, to)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // category delete
|
|
||||||
// app.delete("/api/v1/category/{categoryId}") { ctx ->
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// Category.removeCategory(categoryId)
|
|
||||||
// ctx.status(200)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // returns the manga list associated with a category
|
|
||||||
// app.get("/api/v1/category/{categoryId}") { ctx ->
|
|
||||||
// val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
// ctx.json(getCategoryMangaList(categoryId))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // expects a Tachiyomi legacy backup json in the body
|
|
||||||
// app.post("/api/v1/backup/legacy/import") { ctx ->
|
|
||||||
// ctx.future(
|
|
||||||
// future {
|
|
||||||
// restoreLegacyBackup(ctx.bodyAsInputStream())
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json"
|
|
||||||
// app.post("/api/v1/backup/legacy/import/file") { ctx ->
|
|
||||||
// ctx.future(
|
|
||||||
// JavalinSetup.future {
|
|
||||||
// restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // returns a Tachiyomi legacy backup json created from the current database as a json body
|
|
||||||
// app.get("/api/v1/backup/legacy/export") { ctx ->
|
|
||||||
// ctx.contentType("application/json")
|
|
||||||
// ctx.future(
|
|
||||||
// JavalinSetup.future {
|
|
||||||
// createLegacyBackup(
|
|
||||||
// BackupFlags(
|
|
||||||
// includeManga = true,
|
|
||||||
// includeCategories = true,
|
|
||||||
// includeChapters = true,
|
|
||||||
// includeTracking = true,
|
|
||||||
// includeHistory = true,
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // returns a Tachiyomi legacy backup json created from the current database as a file
|
|
||||||
// app.get("/api/v1/backup/legacy/export/file") { ctx ->
|
|
||||||
// ctx.contentType("application/json")
|
|
||||||
// val sdf = SimpleDateFormat("yyyy-MM-dd_HH-mm")
|
|
||||||
// val currentDate = sdf.format(Date())
|
|
||||||
//
|
|
||||||
// ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"")
|
|
||||||
// ctx.future(
|
|
||||||
// JavalinSetup.future {
|
|
||||||
// createLegacyBackup(
|
|
||||||
// BackupFlags(
|
|
||||||
// includeManga = true,
|
|
||||||
// includeCategories = true,
|
|
||||||
// includeChapters = true,
|
|
||||||
// includeTracking = true,
|
|
||||||
// includeHistory = true,
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Download queue stats
|
|
||||||
// app.ws("/api/v1/downloads") { ws ->
|
|
||||||
// ws.onConnect { ctx ->
|
|
||||||
// // TODO: send current stat
|
|
||||||
// // TODO: add to downlad subscribers
|
|
||||||
// }
|
|
||||||
// ws.onMessage {
|
|
||||||
// // TODO: send current stat
|
|
||||||
// }
|
|
||||||
// ws.onClose { ctx ->
|
|
||||||
// // TODO: remove from subscribers
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import suwayomi.tachidesk.anime.impl.AnimeList.proxyThumbnailUrl
|
|
||||||
import suwayomi.tachidesk.anime.impl.Source.getAnimeSource
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.AnimeDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeStatus
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeTable
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
object Anime {
|
|
||||||
private fun truncate(text: String?, maxLength: Int): String? {
|
|
||||||
return if (text?.length ?: 0 > maxLength)
|
|
||||||
text?.take(maxLength - 3) + "..."
|
|
||||||
else
|
|
||||||
text
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getAnime(animeId: Int, onlineFetch: Boolean = false): AnimeDataClass {
|
|
||||||
var animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
|
||||||
|
|
||||||
return if (animeEntry[AnimeTable.initialized] && !onlineFetch) {
|
|
||||||
AnimeDataClass(
|
|
||||||
animeId,
|
|
||||||
animeEntry[AnimeTable.sourceReference].toString(),
|
|
||||||
|
|
||||||
animeEntry[AnimeTable.url],
|
|
||||||
animeEntry[AnimeTable.title],
|
|
||||||
proxyThumbnailUrl(animeId),
|
|
||||||
|
|
||||||
true,
|
|
||||||
|
|
||||||
animeEntry[AnimeTable.artist],
|
|
||||||
animeEntry[AnimeTable.author],
|
|
||||||
animeEntry[AnimeTable.description],
|
|
||||||
animeEntry[AnimeTable.genre],
|
|
||||||
AnimeStatus.valueOf(animeEntry[AnimeTable.status]).name,
|
|
||||||
animeEntry[AnimeTable.inLibrary],
|
|
||||||
getAnimeSource(animeEntry[AnimeTable.sourceReference]),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
} else { // initialize anime
|
|
||||||
val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference])
|
|
||||||
val fetchedAnime = source.fetchAnimeDetails(
|
|
||||||
SAnime.create().apply {
|
|
||||||
url = animeEntry[AnimeTable.url]
|
|
||||||
title = animeEntry[AnimeTable.title]
|
|
||||||
}
|
|
||||||
).awaitSingle()
|
|
||||||
|
|
||||||
transaction {
|
|
||||||
AnimeTable.update({ AnimeTable.id eq animeId }) {
|
|
||||||
|
|
||||||
it[AnimeTable.initialized] = true
|
|
||||||
|
|
||||||
it[AnimeTable.artist] = fetchedAnime.artist
|
|
||||||
it[AnimeTable.author] = fetchedAnime.author
|
|
||||||
it[AnimeTable.description] = truncate(fetchedAnime.description, 4096)
|
|
||||||
it[AnimeTable.genre] = fetchedAnime.genre
|
|
||||||
it[AnimeTable.status] = fetchedAnime.status
|
|
||||||
if (fetchedAnime.thumbnail_url != null && fetchedAnime.thumbnail_url.orEmpty().isNotEmpty())
|
|
||||||
it[AnimeTable.thumbnail_url] = fetchedAnime.thumbnail_url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAnimeThumbnail(animeId)
|
|
||||||
|
|
||||||
animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
|
||||||
|
|
||||||
AnimeDataClass(
|
|
||||||
animeId,
|
|
||||||
animeEntry[AnimeTable.sourceReference].toString(),
|
|
||||||
|
|
||||||
animeEntry[AnimeTable.url],
|
|
||||||
animeEntry[AnimeTable.title],
|
|
||||||
proxyThumbnailUrl(animeId),
|
|
||||||
|
|
||||||
true,
|
|
||||||
|
|
||||||
fetchedAnime.artist,
|
|
||||||
fetchedAnime.author,
|
|
||||||
fetchedAnime.description,
|
|
||||||
fetchedAnime.genre,
|
|
||||||
AnimeStatus.valueOf(fetchedAnime.status).name,
|
|
||||||
animeEntry[AnimeTable.inLibrary],
|
|
||||||
getAnimeSource(animeEntry[AnimeTable.sourceReference]),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
|
||||||
suspend fun getAnimeThumbnail(animeId: Int): Pair<InputStream, String> {
|
|
||||||
val saveDir = applicationDirs.animeThumbnailsRoot
|
|
||||||
val fileName = animeId.toString()
|
|
||||||
|
|
||||||
return getCachedImageResponse(saveDir, fileName) {
|
|
||||||
getAnime(animeId) // make sure is initialized
|
|
||||||
|
|
||||||
val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
|
||||||
|
|
||||||
val sourceId = animeEntry[AnimeTable.sourceReference]
|
|
||||||
val source = getAnimeHttpSource(sourceId)
|
|
||||||
|
|
||||||
val thumbnailUrl = animeEntry[AnimeTable.thumbnail_url]!!
|
|
||||||
|
|
||||||
source.client.newCall(
|
|
||||||
GET(thumbnailUrl, source.headers)
|
|
||||||
).await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearAnimeThumbnail(animeId: Int) {
|
|
||||||
val saveDir = applicationDirs.animeThumbnailsRoot
|
|
||||||
val fileName = animeId.toString()
|
|
||||||
|
|
||||||
clearCachedImage(saveDir, fileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.animesource.model.AnimesPage
|
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.AnimeDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.PagedAnimeListDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeStatus
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeTable
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
|
||||||
|
|
||||||
object AnimeList {
|
|
||||||
fun proxyThumbnailUrl(animeId: Int): String {
|
|
||||||
return "/api/v1/anime/anime/$animeId/thumbnail"
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getAnimeList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedAnimeListDataClass {
|
|
||||||
val source = getAnimeHttpSource(sourceId)
|
|
||||||
val animesPage = if (popular) {
|
|
||||||
source.fetchPopularAnime(pageNum).awaitSingle()
|
|
||||||
} else {
|
|
||||||
if (source.supportsLatest)
|
|
||||||
source.fetchLatestUpdates(pageNum).awaitSingle()
|
|
||||||
else
|
|
||||||
throw Exception("Source $source doesn't support latest")
|
|
||||||
}
|
|
||||||
return animesPage.processEntries(sourceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AnimesPage.processEntries(sourceId: Long): PagedAnimeListDataClass {
|
|
||||||
val animesPage = this
|
|
||||||
val animeList = transaction {
|
|
||||||
return@transaction animesPage.animes.map { anime ->
|
|
||||||
val animeEntry = AnimeTable.select { AnimeTable.url eq anime.url }.firstOrNull()
|
|
||||||
if (animeEntry == null) { // create anime entry
|
|
||||||
val animeId = AnimeTable.insertAndGetId {
|
|
||||||
it[url] = anime.url
|
|
||||||
it[title] = anime.title
|
|
||||||
|
|
||||||
it[artist] = anime.artist
|
|
||||||
it[author] = anime.author
|
|
||||||
it[description] = anime.description
|
|
||||||
it[genre] = anime.genre
|
|
||||||
it[status] = anime.status
|
|
||||||
it[thumbnail_url] = anime.thumbnail_url
|
|
||||||
|
|
||||||
it[sourceReference] = sourceId
|
|
||||||
}.value
|
|
||||||
|
|
||||||
AnimeDataClass(
|
|
||||||
animeId,
|
|
||||||
sourceId.toString(),
|
|
||||||
|
|
||||||
anime.url,
|
|
||||||
anime.title,
|
|
||||||
proxyThumbnailUrl(animeId),
|
|
||||||
|
|
||||||
anime.initialized,
|
|
||||||
|
|
||||||
anime.artist,
|
|
||||||
anime.author,
|
|
||||||
anime.description,
|
|
||||||
anime.genre,
|
|
||||||
AnimeStatus.valueOf(anime.status).name
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val animeId = animeEntry[AnimeTable.id].value
|
|
||||||
AnimeDataClass(
|
|
||||||
animeId,
|
|
||||||
sourceId.toString(),
|
|
||||||
|
|
||||||
anime.url,
|
|
||||||
anime.title,
|
|
||||||
proxyThumbnailUrl(animeId),
|
|
||||||
|
|
||||||
true,
|
|
||||||
|
|
||||||
animeEntry[AnimeTable.artist],
|
|
||||||
animeEntry[AnimeTable.author],
|
|
||||||
animeEntry[AnimeTable.description],
|
|
||||||
animeEntry[AnimeTable.genre],
|
|
||||||
AnimeStatus.valueOf(animeEntry[AnimeTable.status]).name,
|
|
||||||
animeEntry[AnimeTable.inLibrary]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return PagedAnimeListDataClass(
|
|
||||||
animeList,
|
|
||||||
animesPage.hasNextPage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import org.jetbrains.exposed.sql.SortOrder.DESC
|
|
||||||
import org.jetbrains.exposed.sql.and
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
import suwayomi.tachidesk.anime.impl.Anime.getAnime
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.EpisodeDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.VideoDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeTable
|
|
||||||
import suwayomi.tachidesk.anime.model.table.EpisodeTable
|
|
||||||
import suwayomi.tachidesk.anime.model.table.toDataClass
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
|
||||||
|
|
||||||
object Episode {
|
|
||||||
/** get episode list when showing an anime */
|
|
||||||
suspend fun getEpisodeList(animeId: Int, onlineFetch: Boolean?): List<EpisodeDataClass> {
|
|
||||||
return if (onlineFetch == true) {
|
|
||||||
getSourceEpisodes(animeId)
|
|
||||||
} else {
|
|
||||||
transaction {
|
|
||||||
EpisodeTable.select { EpisodeTable.anime eq animeId }.orderBy(EpisodeTable.episodeIndex to DESC)
|
|
||||||
.map {
|
|
||||||
EpisodeTable.toDataClass(it)
|
|
||||||
}
|
|
||||||
}.ifEmpty {
|
|
||||||
// If it was explicitly set to offline dont grab episodes
|
|
||||||
if (onlineFetch == null) {
|
|
||||||
getSourceEpisodes(animeId)
|
|
||||||
} else emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getSourceEpisodes(animeId: Int): List<EpisodeDataClass> {
|
|
||||||
val animeDetails = getAnime(animeId)
|
|
||||||
val source = getAnimeHttpSource(animeDetails.sourceId.toLong())
|
|
||||||
val episodeList = source.fetchEpisodeList(
|
|
||||||
SAnime.create().apply {
|
|
||||||
title = animeDetails.title
|
|
||||||
url = animeDetails.url
|
|
||||||
}
|
|
||||||
).awaitSingle()
|
|
||||||
|
|
||||||
val episodeCount = episodeList.count()
|
|
||||||
|
|
||||||
transaction {
|
|
||||||
episodeList.reversed().forEachIndexed { index, fetchedEpisode ->
|
|
||||||
val episodeEntry = EpisodeTable.select { EpisodeTable.url eq fetchedEpisode.url }.firstOrNull()
|
|
||||||
if (episodeEntry == null) {
|
|
||||||
EpisodeTable.insert {
|
|
||||||
it[url] = fetchedEpisode.url
|
|
||||||
it[name] = fetchedEpisode.name
|
|
||||||
it[date_upload] = fetchedEpisode.date_upload
|
|
||||||
it[episode_number] = fetchedEpisode.episode_number
|
|
||||||
it[scanlator] = fetchedEpisode.scanlator
|
|
||||||
|
|
||||||
it[episodeIndex] = index + 1
|
|
||||||
it[anime] = animeId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EpisodeTable.update({ EpisodeTable.url eq fetchedEpisode.url }) {
|
|
||||||
it[name] = fetchedEpisode.name
|
|
||||||
it[date_upload] = fetchedEpisode.date_upload
|
|
||||||
it[episode_number] = fetchedEpisode.episode_number
|
|
||||||
it[scanlator] = fetchedEpisode.scanlator
|
|
||||||
|
|
||||||
it[episodeIndex] = index + 1
|
|
||||||
it[anime] = animeId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear any orphaned episodes that are in the db but not in `episodeList`
|
|
||||||
val dbEpisodeCount = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId }.count() }
|
|
||||||
if (dbEpisodeCount > episodeCount) { // we got some clean up due
|
|
||||||
val dbEpisodeList = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId } }
|
|
||||||
|
|
||||||
dbEpisodeList.forEach {
|
|
||||||
if (it[EpisodeTable.episodeIndex] >= episodeList.size ||
|
|
||||||
episodeList[it[EpisodeTable.episodeIndex] - 1].url != it[EpisodeTable.url]
|
|
||||||
) {
|
|
||||||
transaction {
|
|
||||||
// PageTable.deleteWhere { PageTable.episode eq it[EpisodeTable.id] }
|
|
||||||
EpisodeTable.deleteWhere { EpisodeTable.id eq it[EpisodeTable.id] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val dbEpisodeMap = transaction {
|
|
||||||
EpisodeTable.select { EpisodeTable.anime eq animeId }
|
|
||||||
.associateBy({ it[EpisodeTable.url] }, { it })
|
|
||||||
}
|
|
||||||
|
|
||||||
return episodeList.mapIndexed { index, it ->
|
|
||||||
|
|
||||||
val dbEpisode = dbEpisodeMap.getValue(it.url)
|
|
||||||
|
|
||||||
EpisodeDataClass(
|
|
||||||
it.url,
|
|
||||||
it.name,
|
|
||||||
it.date_upload,
|
|
||||||
it.episode_number,
|
|
||||||
it.scanlator,
|
|
||||||
animeId,
|
|
||||||
|
|
||||||
dbEpisode[EpisodeTable.isRead],
|
|
||||||
dbEpisode[EpisodeTable.isBookmarked],
|
|
||||||
dbEpisode[EpisodeTable.lastPageRead],
|
|
||||||
|
|
||||||
episodeCount - index,
|
|
||||||
episodeList.size
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to display a episode, get a episode in order to show it's video */
|
|
||||||
suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass {
|
|
||||||
val episode = getEpisodeList(animeId, false)
|
|
||||||
.first { it.index == episodeIndex }
|
|
||||||
|
|
||||||
val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
|
||||||
val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference])
|
|
||||||
val fetchedVideos = source.fetchVideoList(
|
|
||||||
SEpisode.create().also {
|
|
||||||
it.url = episode.url
|
|
||||||
it.name = episode.name
|
|
||||||
}
|
|
||||||
).awaitSingle()
|
|
||||||
|
|
||||||
return EpisodeDataClass(
|
|
||||||
episode.url,
|
|
||||||
episode.name,
|
|
||||||
episode.uploadDate,
|
|
||||||
episode.episodeNumber,
|
|
||||||
episode.scanlator,
|
|
||||||
animeId,
|
|
||||||
episode.read,
|
|
||||||
episode.bookmarked,
|
|
||||||
episode.lastPageRead,
|
|
||||||
episode.index,
|
|
||||||
episode.episodeCount,
|
|
||||||
fetchedVideos.map {
|
|
||||||
VideoDataClass(
|
|
||||||
it.url,
|
|
||||||
it.quality,
|
|
||||||
it.videoUrl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// /** used to display a episode, get a episode in order to show it's pages */
|
|
||||||
// suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass {
|
|
||||||
// val episodeEntry = transaction {
|
|
||||||
// EpisodeTable.select {
|
|
||||||
// (EpisodeTable.episodeIndex eq episodeIndex) and (EpisodeTable.anime eq animeId)
|
|
||||||
// }.first()
|
|
||||||
// }
|
|
||||||
// val animeEntry = transaction { MangaTable.select { MangaTable.id eq animeId }.first() }
|
|
||||||
// val source = getAnimeHttpSource(animeEntry[MangaTable.sourceReference])
|
|
||||||
//
|
|
||||||
// val pageList = source.fetchPageList(
|
|
||||||
// SEpisode.create().apply {
|
|
||||||
// url = episodeEntry[EpisodeTable.url]
|
|
||||||
// name = episodeEntry[EpisodeTable.name]
|
|
||||||
// }
|
|
||||||
// ).awaitSingle()
|
|
||||||
//
|
|
||||||
// val episodeId = episodeEntry[EpisodeTable.id].value
|
|
||||||
// val episodeCount = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId }.count() }
|
|
||||||
//
|
|
||||||
// // update page list for this episode
|
|
||||||
// transaction {
|
|
||||||
// pageList.forEach { page ->
|
|
||||||
// val pageEntry = transaction { PageTable.select { (PageTable.episode eq episodeId) and (PageTable.index eq page.index) }.firstOrNull() }
|
|
||||||
// if (pageEntry == null) {
|
|
||||||
// PageTable.insert {
|
|
||||||
// it[index] = page.index
|
|
||||||
// it[url] = page.url
|
|
||||||
// it[imageUrl] = page.imageUrl
|
|
||||||
// it[episode] = episodeId
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// PageTable.update({ (PageTable.episode eq episodeId) and (PageTable.index eq page.index) }) {
|
|
||||||
// it[url] = page.url
|
|
||||||
// it[imageUrl] = page.imageUrl
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return EpisodeDataClass(
|
|
||||||
// episodeEntry[EpisodeTable.url],
|
|
||||||
// episodeEntry[EpisodeTable.name],
|
|
||||||
// episodeEntry[EpisodeTable.date_upload],
|
|
||||||
// episodeEntry[EpisodeTable.episode_number],
|
|
||||||
// episodeEntry[EpisodeTable.scanlator],
|
|
||||||
// animeId,
|
|
||||||
// episodeEntry[EpisodeTable.isRead],
|
|
||||||
// episodeEntry[EpisodeTable.isBookmarked],
|
|
||||||
// episodeEntry[EpisodeTable.lastPageRead],
|
|
||||||
//
|
|
||||||
// episodeEntry[EpisodeTable.episodeIndex],
|
|
||||||
// episodeCount.toInt(),
|
|
||||||
// pageList.count()
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun modifyEpisode(animeId: Int, episodeIndex: Int, isRead: Boolean?, isBookmarked: Boolean?, markPrevRead: Boolean?, lastPageRead: Int?) {
|
|
||||||
transaction {
|
|
||||||
if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) {
|
|
||||||
EpisodeTable.update({ (EpisodeTable.anime eq animeId) and (EpisodeTable.episodeIndex eq episodeIndex) }) { update ->
|
|
||||||
isRead?.also {
|
|
||||||
update[EpisodeTable.isRead] = it
|
|
||||||
}
|
|
||||||
isBookmarked?.also {
|
|
||||||
update[EpisodeTable.isBookmarked] = it
|
|
||||||
}
|
|
||||||
lastPageRead?.also {
|
|
||||||
update[EpisodeTable.lastPageRead] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markPrevRead?.let {
|
|
||||||
EpisodeTable.update({ (EpisodeTable.anime eq animeId) and (EpisodeTable.episodeIndex less episodeIndex) }) {
|
|
||||||
it[EpisodeTable.isRead] = markPrevRead
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 suwayomi.tachidesk.anime.impl.AnimeList.processEntries
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.PagedAnimeListDataClass
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
|
||||||
|
|
||||||
object Search {
|
|
||||||
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedAnimeListDataClass {
|
|
||||||
val source = getAnimeHttpSource(sourceId)
|
|
||||||
val searchManga = source.fetchSearchAnime(pageNum, searchTerm, source.getFilterList()).awaitSingle()
|
|
||||||
return searchManga.processEntries(sourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 mu.KotlinLogging
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.getExtensionIconUrl
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.AnimeSourceDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeExtensionTable
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeSourceTable
|
|
||||||
|
|
||||||
object Source {
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
fun getSourceList(): List<AnimeSourceDataClass> {
|
|
||||||
return transaction {
|
|
||||||
AnimeSourceTable.selectAll().map {
|
|
||||||
AnimeSourceDataClass(
|
|
||||||
it[AnimeSourceTable.id].value.toString(),
|
|
||||||
it[AnimeSourceTable.name],
|
|
||||||
it[AnimeSourceTable.lang],
|
|
||||||
getExtensionIconUrl(AnimeExtensionTable.select { AnimeExtensionTable.id eq it[AnimeSourceTable.extension] }.first()[AnimeExtensionTable.apkName]),
|
|
||||||
getAnimeHttpSource(it[AnimeSourceTable.id].value).supportsLatest
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAnimeSource(sourceId: Long): AnimeSourceDataClass {
|
|
||||||
return transaction {
|
|
||||||
val source = AnimeSourceTable.select { AnimeSourceTable.id eq sourceId }.firstOrNull()
|
|
||||||
|
|
||||||
AnimeSourceDataClass(
|
|
||||||
sourceId.toString(),
|
|
||||||
source?.get(AnimeSourceTable.name),
|
|
||||||
source?.get(AnimeSourceTable.lang),
|
|
||||||
source?.let { AnimeExtensionTable.select { AnimeExtensionTable.id eq source[AnimeSourceTable.extension] }.first()[AnimeExtensionTable.iconUrl] },
|
|
||||||
source?.let { getAnimeHttpSource(sourceId).supportsLatest }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.extension
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import mu.KotlinLogging
|
|
||||||
import okhttp3.Request
|
|
||||||
import okio.buffer
|
|
||||||
import okio.sink
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.ExtensionsList.extensionTableAsDataClass
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.github.ExtensionGithubApi
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.EXTENSION_FEATURE
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.LIB_VERSION_MAX
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.LIB_VERSION_MIN
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.METADATA_NSFW
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.METADATA_SOURCE_CLASS
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.dex2jar
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.getPackageInfo
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.getSignatureHash
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.loadExtensionSources
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.trustedSignatures
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeExtensionTable
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeSourceTable
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
object Extension {
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
|
||||||
|
|
||||||
data class InstallableAPK(
|
|
||||||
val apkFilePath: String,
|
|
||||||
val pkgName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun installExtension(pkgName: String): Int {
|
|
||||||
logger.debug("Installing $pkgName")
|
|
||||||
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
|
||||||
|
|
||||||
return installAPK {
|
|
||||||
val apkURL = ExtensionGithubApi.getApkUrl(extensionRecord)
|
|
||||||
val apkName = Uri.parse(apkURL).lastPathSegment!!
|
|
||||||
val apkSavePath = "${applicationDirs.extensionsRoot}/$apkName"
|
|
||||||
// download apk file
|
|
||||||
downloadAPKFile(apkURL, apkSavePath)
|
|
||||||
|
|
||||||
apkSavePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun installAPK(fetcher: suspend () -> String): Int {
|
|
||||||
val apkFilePath = fetcher()
|
|
||||||
val apkName = File(apkFilePath).name
|
|
||||||
|
|
||||||
// check if we don't have the extension already installed
|
|
||||||
// if it's installed and we want to update, it first has to be uninstalled
|
|
||||||
val isInstalled = transaction {
|
|
||||||
AnimeExtensionTable.select { AnimeExtensionTable.apkName eq apkName }.firstOrNull()
|
|
||||||
}?.get(AnimeExtensionTable.isInstalled) ?: false
|
|
||||||
|
|
||||||
if (!isInstalled) {
|
|
||||||
val fileNameWithoutType = apkName.substringBefore(".apk")
|
|
||||||
|
|
||||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
|
||||||
val jarFilePath = "$dirPathWithoutType.jar"
|
|
||||||
val dexFilePath = "$dirPathWithoutType.dex"
|
|
||||||
|
|
||||||
val packageInfo = getPackageInfo(apkFilePath)
|
|
||||||
val pkgName = packageInfo.packageName
|
|
||||||
|
|
||||||
if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) {
|
|
||||||
throw Exception("This apk is not a Tachiyomi extension")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate lib version
|
|
||||||
val libVersion = packageInfo.versionName.substringBeforeLast('.').toDouble()
|
|
||||||
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
|
||||||
throw Exception(
|
|
||||||
"Lib version is $libVersion, while only versions " +
|
|
||||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(packageInfo)
|
|
||||||
|
|
||||||
if (signatureHash == null) {
|
|
||||||
throw Exception("Package $pkgName isn't signed")
|
|
||||||
} else if (signatureHash !in trustedSignatures) {
|
|
||||||
// TODO: allow trusting keys
|
|
||||||
throw Exception("This apk is not a signed with the official tachiyomi signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
val isNsfw = packageInfo.applicationInfo.metaData.getString(METADATA_NSFW) == "1"
|
|
||||||
|
|
||||||
val className = packageInfo.packageName + packageInfo.applicationInfo.metaData.getString(METADATA_SOURCE_CLASS)
|
|
||||||
|
|
||||||
logger.debug("Main class for extension is $className")
|
|
||||||
|
|
||||||
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
// File(apkFilePath).delete()
|
|
||||||
File(dexFilePath).delete()
|
|
||||||
|
|
||||||
// collect sources from the extension
|
|
||||||
val sources: List<AnimeCatalogueSource> = when (val instance = loadExtensionSources(jarFilePath, className)) {
|
|
||||||
is AnimeSource -> listOf(instance)
|
|
||||||
is AnimeSourceFactory -> instance.createSources()
|
|
||||||
else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}")
|
|
||||||
}.map { it as AnimeCatalogueSource }
|
|
||||||
|
|
||||||
val langs = sources.map { it.lang }.toSet()
|
|
||||||
val extensionLang = when (langs.size) {
|
|
||||||
0 -> ""
|
|
||||||
1 -> langs.first()
|
|
||||||
else -> "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
val extensionName = packageInfo.applicationInfo.nonLocalizedLabel.toString().substringAfter("Aniyomi: ")
|
|
||||||
|
|
||||||
// update extension info
|
|
||||||
transaction {
|
|
||||||
if (AnimeExtensionTable.select { AnimeExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
|
|
||||||
AnimeExtensionTable.insert {
|
|
||||||
it[this.apkName] = apkName
|
|
||||||
it[name] = extensionName
|
|
||||||
it[this.pkgName] = packageInfo.packageName
|
|
||||||
it[versionName] = packageInfo.versionName
|
|
||||||
it[versionCode] = packageInfo.versionCode
|
|
||||||
it[lang] = extensionLang
|
|
||||||
it[this.isNsfw] = isNsfw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq pkgName }) {
|
|
||||||
it[this.isInstalled] = true
|
|
||||||
it[this.classFQName] = className
|
|
||||||
}
|
|
||||||
|
|
||||||
val extensionId = AnimeExtensionTable.select { AnimeExtensionTable.pkgName eq pkgName }.first()[AnimeExtensionTable.id].value
|
|
||||||
|
|
||||||
sources.forEach { httpSource ->
|
|
||||||
AnimeSourceTable.insert {
|
|
||||||
it[id] = httpSource.id
|
|
||||||
it[name] = httpSource.name
|
|
||||||
it[lang] = httpSource.lang
|
|
||||||
it[extension] = extensionId
|
|
||||||
}
|
|
||||||
logger.debug("Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 201 // we installed successfully
|
|
||||||
} else {
|
|
||||||
return 302 // extension was already installed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val network: NetworkHelper by injectLazy()
|
|
||||||
|
|
||||||
private suspend fun downloadAPKFile(url: String, savePath: String) {
|
|
||||||
val request = Request.Builder().url(url).build()
|
|
||||||
val response = network.client.newCall(request).await()
|
|
||||||
|
|
||||||
val downloadedFile = File(savePath)
|
|
||||||
downloadedFile.sink().buffer().use { sink ->
|
|
||||||
response.body!!.source().use { source ->
|
|
||||||
sink.writeAll(source)
|
|
||||||
sink.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun uninstallExtension(pkgName: String) {
|
|
||||||
logger.debug("Uninstalling $pkgName")
|
|
||||||
|
|
||||||
val extensionRecord = transaction { AnimeExtensionTable.select { AnimeExtensionTable.pkgName eq pkgName }.first() }
|
|
||||||
val fileNameWithoutType = extensionRecord[AnimeExtensionTable.apkName].substringBefore(".apk")
|
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
|
||||||
transaction {
|
|
||||||
val extensionId = extensionRecord[AnimeExtensionTable.id].value
|
|
||||||
|
|
||||||
AnimeSourceTable.deleteWhere { AnimeSourceTable.extension eq extensionId }
|
|
||||||
if (extensionRecord[AnimeExtensionTable.isObsolete])
|
|
||||||
AnimeExtensionTable.deleteWhere { AnimeExtensionTable.pkgName eq pkgName }
|
|
||||||
else
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq pkgName }) {
|
|
||||||
it[isInstalled] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File(jarPath).exists()) {
|
|
||||||
File(jarPath).delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateExtension(pkgName: String): Int {
|
|
||||||
val targetExtension = ExtensionsList.updateMap.remove(pkgName)!!
|
|
||||||
uninstallExtension(pkgName)
|
|
||||||
transaction {
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq pkgName }) {
|
|
||||||
it[name] = targetExtension.name
|
|
||||||
it[versionName] = targetExtension.versionName
|
|
||||||
it[versionCode] = targetExtension.versionCode
|
|
||||||
it[lang] = targetExtension.lang
|
|
||||||
it[isNsfw] = targetExtension.isNsfw
|
|
||||||
it[apkName] = targetExtension.apkName
|
|
||||||
it[iconUrl] = targetExtension.iconUrl
|
|
||||||
it[hasUpdate] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return installExtension(pkgName)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
|
|
||||||
val iconUrl = transaction { AnimeExtensionTable.select { AnimeExtensionTable.apkName eq apkName }.first() }[AnimeExtensionTable.iconUrl]
|
|
||||||
|
|
||||||
val saveDir = "${applicationDirs.extensionsRoot}/icon"
|
|
||||||
|
|
||||||
return getCachedImageResponse(saveDir, apkName) {
|
|
||||||
network.client.newCall(
|
|
||||||
GET(iconUrl)
|
|
||||||
).await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExtensionIconUrl(apkName: String): String {
|
|
||||||
return "/api/v1/anime/extension/icon/$apkName"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.extension
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 mu.KotlinLogging
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.Extension.getExtensionIconUrl
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.github.ExtensionGithubApi
|
|
||||||
import suwayomi.tachidesk.anime.impl.extension.github.OnlineExtension
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.AnimeExtensionDataClass
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeExtensionTable
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
object ExtensionsList {
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
var lastUpdateCheck: Long = 0
|
|
||||||
var updateMap = ConcurrentHashMap<String, OnlineExtension>()
|
|
||||||
|
|
||||||
/** 60,000 milliseconds = 60 seconds */
|
|
||||||
private const val ExtensionUpdateDelayTime = 60 * 1000
|
|
||||||
|
|
||||||
suspend fun getExtensionList(): List<AnimeExtensionDataClass> {
|
|
||||||
// update if {ExtensionUpdateDelayTime} seconds has passed or requested offline and database is empty
|
|
||||||
if (lastUpdateCheck + ExtensionUpdateDelayTime < System.currentTimeMillis()) {
|
|
||||||
logger.debug("Getting extensions list from the internet")
|
|
||||||
lastUpdateCheck = System.currentTimeMillis()
|
|
||||||
|
|
||||||
val foundExtensions = ExtensionGithubApi.findExtensions()
|
|
||||||
updateExtensionDatabase(foundExtensions)
|
|
||||||
} else {
|
|
||||||
logger.debug("used cached extension list")
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensionTableAsDataClass()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun extensionTableAsDataClass() = transaction {
|
|
||||||
AnimeExtensionTable.selectAll().map {
|
|
||||||
AnimeExtensionDataClass(
|
|
||||||
it[AnimeExtensionTable.apkName],
|
|
||||||
getExtensionIconUrl(it[AnimeExtensionTable.apkName]),
|
|
||||||
it[AnimeExtensionTable.name],
|
|
||||||
it[AnimeExtensionTable.pkgName],
|
|
||||||
it[AnimeExtensionTable.versionName],
|
|
||||||
it[AnimeExtensionTable.versionCode],
|
|
||||||
it[AnimeExtensionTable.lang],
|
|
||||||
it[AnimeExtensionTable.isNsfw],
|
|
||||||
it[AnimeExtensionTable.isInstalled],
|
|
||||||
it[AnimeExtensionTable.hasUpdate],
|
|
||||||
it[AnimeExtensionTable.isObsolete],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateExtensionDatabase(foundExtensions: List<OnlineExtension>) {
|
|
||||||
transaction {
|
|
||||||
foundExtensions.forEach { foundExtension ->
|
|
||||||
val extensionRecord = AnimeExtensionTable.select { AnimeExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull()
|
|
||||||
if (extensionRecord != null) {
|
|
||||||
if (extensionRecord[AnimeExtensionTable.isInstalled]) {
|
|
||||||
when {
|
|
||||||
foundExtension.versionCode > extensionRecord[AnimeExtensionTable.versionCode] -> {
|
|
||||||
// there is an update
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq foundExtension.pkgName }) {
|
|
||||||
it[hasUpdate] = true
|
|
||||||
}
|
|
||||||
updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
|
|
||||||
}
|
|
||||||
foundExtension.versionCode < extensionRecord[AnimeExtensionTable.versionCode] -> {
|
|
||||||
// some how the user installed an invalid version
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq foundExtension.pkgName }) {
|
|
||||||
it[isObsolete] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// extension is not installed so we can overwrite the data without a care
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq foundExtension.pkgName }) {
|
|
||||||
it[name] = foundExtension.name
|
|
||||||
it[versionName] = foundExtension.versionName
|
|
||||||
it[versionCode] = foundExtension.versionCode
|
|
||||||
it[lang] = foundExtension.lang
|
|
||||||
it[isNsfw] = foundExtension.isNsfw
|
|
||||||
it[apkName] = foundExtension.apkName
|
|
||||||
it[iconUrl] = foundExtension.iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// insert new record
|
|
||||||
AnimeExtensionTable.insert {
|
|
||||||
it[name] = foundExtension.name
|
|
||||||
it[pkgName] = foundExtension.pkgName
|
|
||||||
it[versionName] = foundExtension.versionName
|
|
||||||
it[versionCode] = foundExtension.versionCode
|
|
||||||
it[lang] = foundExtension.lang
|
|
||||||
it[isNsfw] = foundExtension.isNsfw
|
|
||||||
it[apkName] = foundExtension.apkName
|
|
||||||
it[iconUrl] = foundExtension.iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deal with obsolete extensions
|
|
||||||
AnimeExtensionTable.selectAll().forEach { extensionRecord ->
|
|
||||||
val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[AnimeExtensionTable.pkgName] }
|
|
||||||
if (foundExtension == null) {
|
|
||||||
// not in the repo, so this extensions is obsolete
|
|
||||||
if (extensionRecord[AnimeExtensionTable.isInstalled]) {
|
|
||||||
// is installed so we should mark it as obsolete
|
|
||||||
AnimeExtensionTable.update({ AnimeExtensionTable.pkgName eq extensionRecord[AnimeExtensionTable.pkgName] }) {
|
|
||||||
it[isObsolete] = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// is not installed so we can remove the record without a care
|
|
||||||
AnimeExtensionTable.deleteWhere { AnimeExtensionTable.pkgName eq extensionRecord[AnimeExtensionTable.pkgName] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.extension.github
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 com.github.salomonbrys.kotson.int
|
|
||||||
import com.github.salomonbrys.kotson.string
|
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonParser
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import okhttp3.Request
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.LIB_VERSION_MAX
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.LIB_VERSION_MIN
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.AnimeExtensionDataClass
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.UnzippingInterceptor
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
object ExtensionGithubApi {
|
|
||||||
private const val BASE_URL = "https://raw.githubusercontent.com"
|
|
||||||
private const val REPO_URL_PREFIX = "$BASE_URL/jmir1/tachiyomi-extensions/repo"
|
|
||||||
|
|
||||||
private fun parseResponse(json: JsonArray): List<OnlineExtension> {
|
|
||||||
return json
|
|
||||||
.map { it.asJsonObject }
|
|
||||||
.filter { element ->
|
|
||||||
val versionName = element["version"].string
|
|
||||||
val libVersion = versionName.substringBeforeLast('.').toInt()
|
|
||||||
libVersion in LIB_VERSION_MIN..LIB_VERSION_MAX
|
|
||||||
}
|
|
||||||
.map { element ->
|
|
||||||
val name = element["name"].string.substringAfter("Aniyomi: ")
|
|
||||||
val pkgName = element["pkg"].string
|
|
||||||
val apkName = element["apk"].string
|
|
||||||
val versionName = element["version"].string
|
|
||||||
val versionCode = element["code"].int
|
|
||||||
val lang = element["lang"].string
|
|
||||||
val nsfw = element["nsfw"].int == 1
|
|
||||||
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
|
|
||||||
|
|
||||||
OnlineExtension(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun findExtensions(): List<OnlineExtension> {
|
|
||||||
val response = getRepo()
|
|
||||||
return parseResponse(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getApkUrl(extension: AnimeExtensionDataClass): String {
|
|
||||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val client by lazy {
|
|
||||||
val network: NetworkHelper by injectLazy()
|
|
||||||
network.client.newBuilder()
|
|
||||||
.addNetworkInterceptor { chain ->
|
|
||||||
val originalResponse = chain.proceed(chain.request())
|
|
||||||
originalResponse.newBuilder()
|
|
||||||
.header("Content-Encoding", "gzip")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
.addInterceptor(UnzippingInterceptor())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRepo(): JsonArray {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("$REPO_URL_PREFIX/index.json.gz")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = client.newCall(request).execute().use { response -> response.body!!.string() }
|
|
||||||
return JsonParser.parseString(response).asJsonArray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.extension.github
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 OnlineExtension(
|
|
||||||
val name: String,
|
|
||||||
val pkgName: String,
|
|
||||||
val versionName: String,
|
|
||||||
val versionCode: Int,
|
|
||||||
val lang: String,
|
|
||||||
val isNsfw: Boolean,
|
|
||||||
val apkName: String,
|
|
||||||
val iconUrl: String
|
|
||||||
)
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.util
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.animesource.AnimeSource
|
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
|
|
||||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import suwayomi.tachidesk.anime.impl.util.PackageTools.loadExtensionSources
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeExtensionTable
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeSourceTable
|
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
object GetAnimeHttpSource {
|
|
||||||
private val sourceCache = ConcurrentHashMap<Long, AnimeHttpSource>()
|
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
|
||||||
|
|
||||||
fun getAnimeHttpSource(sourceId: Long): AnimeHttpSource {
|
|
||||||
val cachedResult: AnimeHttpSource? = sourceCache[sourceId]
|
|
||||||
if (cachedResult != null) {
|
|
||||||
return cachedResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val sourceRecord = transaction {
|
|
||||||
AnimeSourceTable.select { AnimeSourceTable.id eq sourceId }.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
val extensionId = sourceRecord[AnimeSourceTable.extension]
|
|
||||||
val extensionRecord = transaction {
|
|
||||||
AnimeExtensionTable.select { AnimeExtensionTable.id eq extensionId }.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
val apkName = extensionRecord[AnimeExtensionTable.apkName]
|
|
||||||
val className = extensionRecord[AnimeExtensionTable.classFQName]
|
|
||||||
val jarName = apkName.substringBefore(".apk") + ".jar"
|
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
|
|
||||||
|
|
||||||
when (val instance = loadExtensionSources(jarPath, className)) {
|
|
||||||
is AnimeSource -> listOf(instance)
|
|
||||||
is AnimeSourceFactory -> instance.createSources()
|
|
||||||
else -> throw Exception("Unknown source class type! ${instance.javaClass}")
|
|
||||||
}.forEach {
|
|
||||||
sourceCache[it.id] = it as AnimeHttpSource
|
|
||||||
}
|
|
||||||
return sourceCache[sourceId]!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.impl.util
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.Signature
|
|
||||||
import android.os.Bundle
|
|
||||||
import com.googlecode.d2j.dex.Dex2jar
|
|
||||||
import com.googlecode.d2j.reader.MultiDexFileReader
|
|
||||||
import com.googlecode.dex2jar.tools.BaksmaliBaseDexExceptionHandler
|
|
||||||
import eu.kanade.tachiyomi.util.lang.Hash
|
|
||||||
import mu.KotlinLogging
|
|
||||||
import net.dongliu.apk.parser.ApkFile
|
|
||||||
import net.dongliu.apk.parser.ApkParsers
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import suwayomi.tachidesk.manga.impl.util.BytecodeEditor
|
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
|
||||||
import xyz.nulldev.androidcompat.pm.InstalledPackage.Companion.toList
|
|
||||||
import xyz.nulldev.androidcompat.pm.toPackageInfo
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URL
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
|
|
||||||
object PackageTools {
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
|
||||||
|
|
||||||
const val EXTENSION_FEATURE = "tachiyomi.animeextension"
|
|
||||||
const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class"
|
|
||||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
|
|
||||||
const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
|
|
||||||
const val LIB_VERSION_MIN = 12
|
|
||||||
const val LIB_VERSION_MAX = 12
|
|
||||||
|
|
||||||
private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" // jmir1's key
|
|
||||||
var trustedSignatures = mutableSetOf<String>() + officialSignature
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert dex to jar, a wrapper for the dex2jar library
|
|
||||||
*/
|
|
||||||
fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) {
|
|
||||||
// adopted from com.googlecode.dex2jar.tools.Dex2jarCmd.doCommandLine
|
|
||||||
// source at: https://github.com/DexPatcher/dex2jar/tree/v2.1-20190905-lanchon/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java
|
|
||||||
|
|
||||||
val jarFilePath = File(jarFile).toPath()
|
|
||||||
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
|
||||||
val handler = BaksmaliBaseDexExceptionHandler()
|
|
||||||
Dex2jar
|
|
||||||
.from(reader)
|
|
||||||
.withExceptionHandler(handler)
|
|
||||||
.reUseReg(false)
|
|
||||||
.topoLogicalSort()
|
|
||||||
.skipDebug(true)
|
|
||||||
.optimizeSynchronized(false)
|
|
||||||
.printIR(false)
|
|
||||||
.noCode(false)
|
|
||||||
.skipExceptions(false)
|
|
||||||
.to(jarFilePath)
|
|
||||||
if (handler.hasException()) {
|
|
||||||
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
|
||||||
logger.error(
|
|
||||||
"""
|
|
||||||
Detail Error Information in File $errorFile
|
|
||||||
Please report this file to one of following link if possible (any one).
|
|
||||||
https://sourceforge.net/p/dex2jar/tickets/
|
|
||||||
https://bitbucket.org/pxb1988/dex2jar/issues
|
|
||||||
https://github.com/pxb1988/dex2jar/issues
|
|
||||||
dex2jar@googlegroups.com
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
|
||||||
} else {
|
|
||||||
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A modified version of `xyz.nulldev.androidcompat.pm.InstalledPackage.info` */
|
|
||||||
fun getPackageInfo(apkFilePath: String): PackageInfo {
|
|
||||||
val apk = File(apkFilePath)
|
|
||||||
return ApkParsers.getMetaInfo(apk).toPackageInfo(apk).apply {
|
|
||||||
val parsed = ApkFile(apk)
|
|
||||||
val dbFactory = DocumentBuilderFactory.newInstance()
|
|
||||||
val dBuilder = dbFactory.newDocumentBuilder()
|
|
||||||
val doc = parsed.manifestXml.byteInputStream().use {
|
|
||||||
dBuilder.parse(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(parsed.manifestXml)
|
|
||||||
|
|
||||||
applicationInfo.metaData = Bundle().apply {
|
|
||||||
val appTag = doc.getElementsByTagName("application").item(0)
|
|
||||||
|
|
||||||
appTag?.childNodes?.toList()
|
|
||||||
.orEmpty()
|
|
||||||
.asSequence()
|
|
||||||
.filter {
|
|
||||||
it.nodeType == Node.ELEMENT_NODE
|
|
||||||
}.map {
|
|
||||||
it as Element
|
|
||||||
}.filter {
|
|
||||||
it.tagName == "meta-data"
|
|
||||||
}.forEach {
|
|
||||||
putString(
|
|
||||||
it.attributes.getNamedItem("android:name").nodeValue,
|
|
||||||
it.attributes.getNamedItem("android:value").nodeValue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signatures = (
|
|
||||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
|
||||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
|
||||||
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
|
||||||
.map { Signature(it.data) }.toTypedArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
|
||||||
val signatures = pkgInfo.signatures
|
|
||||||
return if (signatures != null && signatures.isNotEmpty()) {
|
|
||||||
Hash.sha256(signatures.first().toByteArray())
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* loads the extension main class called $className from the jar located at $jarPath
|
|
||||||
* It may return an instance of HttpSource or SourceFactory depending on the extension.
|
|
||||||
*/
|
|
||||||
fun loadExtensionSources(jarPath: String, className: String): Any {
|
|
||||||
val classLoader = URLClassLoader(arrayOf<URL>(URL("file:$jarPath")))
|
|
||||||
val classToLoad = Class.forName(className, false, classLoader)
|
|
||||||
return classToLoad.getDeclaredConstructor().newInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.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/. */
|
|
||||||
|
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeStatus
|
|
||||||
|
|
||||||
data class AnimeDataClass(
|
|
||||||
val id: Int,
|
|
||||||
val sourceId: String,
|
|
||||||
|
|
||||||
val url: String,
|
|
||||||
val title: String,
|
|
||||||
val thumbnailUrl: String? = null,
|
|
||||||
|
|
||||||
val initialized: Boolean = false,
|
|
||||||
|
|
||||||
val artist: String? = null,
|
|
||||||
val author: String? = null,
|
|
||||||
val description: String? = null,
|
|
||||||
val genre: String? = null,
|
|
||||||
val status: String = AnimeStatus.UNKNOWN.name,
|
|
||||||
val inLibrary: Boolean = false,
|
|
||||||
val source: AnimeSourceDataClass? = null,
|
|
||||||
|
|
||||||
val freshData: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PagedAnimeListDataClass(
|
|
||||||
val mangaList: List<AnimeDataClass>,
|
|
||||||
val hasNextPage: Boolean
|
|
||||||
)
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.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 AnimeExtensionDataClass(
|
|
||||||
val apkName: String,
|
|
||||||
val iconUrl: String,
|
|
||||||
|
|
||||||
val name: String,
|
|
||||||
val pkgName: String,
|
|
||||||
val versionName: String,
|
|
||||||
val versionCode: Int,
|
|
||||||
val lang: String,
|
|
||||||
val isNsfw: Boolean,
|
|
||||||
|
|
||||||
val installed: Boolean,
|
|
||||||
val hasUpdate: Boolean,
|
|
||||||
val obsolete: Boolean,
|
|
||||||
)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.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 EpisodeDataClass(
|
|
||||||
val url: String,
|
|
||||||
val name: String,
|
|
||||||
val uploadDate: Long,
|
|
||||||
val episodeNumber: Float,
|
|
||||||
val scanlator: String?,
|
|
||||||
val animeId: Int,
|
|
||||||
|
|
||||||
/** chapter is read */
|
|
||||||
val read: Boolean,
|
|
||||||
|
|
||||||
/** chapter is bookmarked */
|
|
||||||
val bookmarked: Boolean,
|
|
||||||
|
|
||||||
/** last read page, zero means not read/no data */
|
|
||||||
val lastPageRead: Int,
|
|
||||||
|
|
||||||
/** this chapter's index, starts with 1 */
|
|
||||||
val index: Int,
|
|
||||||
|
|
||||||
/** total episode count, used to calculate if there's a next and prev episode */
|
|
||||||
val episodeCount: Int? = null,
|
|
||||||
|
|
||||||
/** used to construct pages in the front-end */
|
|
||||||
val videos: List<VideoDataClass>? = null,
|
|
||||||
)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.model.table
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 org.jetbrains.exposed.dao.id.IntIdTable
|
|
||||||
|
|
||||||
object AnimeExtensionTable : IntIdTable() {
|
|
||||||
val apkName = varchar("apk_name", 1024)
|
|
||||||
|
|
||||||
// default is the local source icon from tachiyomi
|
|
||||||
val iconUrl = varchar("icon_url", 2048)
|
|
||||||
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
|
|
||||||
|
|
||||||
val name = varchar("name", 128)
|
|
||||||
val pkgName = varchar("pkg_name", 128)
|
|
||||||
val versionName = varchar("version_name", 16)
|
|
||||||
val versionCode = integer("version_code")
|
|
||||||
val lang = varchar("lang", 10)
|
|
||||||
val isNsfw = bool("is_nsfw")
|
|
||||||
|
|
||||||
val isInstalled = bool("is_installed").default(false)
|
|
||||||
val hasUpdate = bool("has_update").default(false)
|
|
||||||
val isObsolete = bool("is_obsolete").default(false)
|
|
||||||
|
|
||||||
val classFQName = varchar("class_name", 1024).default("") // fully qualified name
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.model.table
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 org.jetbrains.exposed.dao.id.IdTable
|
|
||||||
|
|
||||||
object AnimeSourceTable : IdTable<Long>() {
|
|
||||||
override val id = long("id").entityId()
|
|
||||||
val name = varchar("name", 128)
|
|
||||||
val lang = varchar("lang", 10)
|
|
||||||
val extension = reference("extension", AnimeExtensionTable)
|
|
||||||
val partOfFactorySource = bool("part_of_factory_source").default(false)
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.model.table
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.animesource.model.SAnime
|
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaStatus.Companion
|
|
||||||
|
|
||||||
object AnimeTable : IntIdTable() {
|
|
||||||
val url = varchar("url", 2048)
|
|
||||||
val title = varchar("title", 512)
|
|
||||||
val initialized = bool("initialized").default(false)
|
|
||||||
|
|
||||||
val artist = varchar("artist", 64).nullable()
|
|
||||||
val author = varchar("author", 64).nullable()
|
|
||||||
val description = varchar("description", 4096).nullable()
|
|
||||||
val genre = varchar("genre", 1024).nullable()
|
|
||||||
|
|
||||||
val status = integer("status").default(SAnime.UNKNOWN)
|
|
||||||
val thumbnail_url = varchar("thumbnail_url", 2048).nullable()
|
|
||||||
|
|
||||||
val inLibrary = bool("in_library").default(false)
|
|
||||||
val defaultCategory = bool("default_category").default(true)
|
|
||||||
|
|
||||||
// source is used by some ancestor of IntIdTable
|
|
||||||
val sourceReference = long("source")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AnimeTable.toDataClass(mangaEntry: ResultRow) =
|
|
||||||
MangaDataClass(
|
|
||||||
mangaEntry[this.id].value,
|
|
||||||
mangaEntry[sourceReference].toString(),
|
|
||||||
|
|
||||||
mangaEntry[url],
|
|
||||||
mangaEntry[title],
|
|
||||||
proxyThumbnailUrl(mangaEntry[this.id].value),
|
|
||||||
|
|
||||||
mangaEntry[initialized],
|
|
||||||
|
|
||||||
mangaEntry[artist],
|
|
||||||
mangaEntry[author],
|
|
||||||
mangaEntry[description],
|
|
||||||
mangaEntry[genre].toGenreList(),
|
|
||||||
Companion.valueOf(mangaEntry[status]).name,
|
|
||||||
mangaEntry[inLibrary]
|
|
||||||
)
|
|
||||||
|
|
||||||
enum class AnimeStatus(val status: Int) {
|
|
||||||
UNKNOWN(0),
|
|
||||||
ONGOING(1),
|
|
||||||
COMPLETED(2),
|
|
||||||
LICENSED(3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun valueOf(value: Int): AnimeStatus = values().find { it.status == value } ?: UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package suwayomi.tachidesk.anime.model.table
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 org.jetbrains.exposed.dao.id.IntIdTable
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.EpisodeDataClass
|
|
||||||
|
|
||||||
object EpisodeTable : IntIdTable() {
|
|
||||||
val url = varchar("url", 2048)
|
|
||||||
val name = varchar("name", 512)
|
|
||||||
val date_upload = long("date_upload").default(0)
|
|
||||||
val episode_number = float("episode_number").default(-1f)
|
|
||||||
val scanlator = varchar("scanlator", 128).nullable()
|
|
||||||
|
|
||||||
val isRead = bool("read").default(false)
|
|
||||||
val isBookmarked = bool("bookmark").default(false)
|
|
||||||
val lastPageRead = integer("last_page_read").default(0)
|
|
||||||
|
|
||||||
// index is reserved by a function
|
|
||||||
val episodeIndex = integer("index")
|
|
||||||
|
|
||||||
val anime = reference("anime", AnimeTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun EpisodeTable.toDataClass(episodeEntry: ResultRow) =
|
|
||||||
EpisodeDataClass(
|
|
||||||
episodeEntry[url],
|
|
||||||
episodeEntry[name],
|
|
||||||
episodeEntry[date_upload],
|
|
||||||
episodeEntry[episode_number],
|
|
||||||
episodeEntry[scanlator],
|
|
||||||
episodeEntry[anime].value,
|
|
||||||
episodeEntry[isRead],
|
|
||||||
episodeEntry[isBookmarked],
|
|
||||||
episodeEntry[lastPageRead],
|
|
||||||
episodeEntry[episodeIndex],
|
|
||||||
transaction { EpisodeTable.select { anime eq episodeEntry[anime] }.count().toInt() }
|
|
||||||
)
|
|
||||||
@@ -10,7 +10,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/. */
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -43,14 +44,15 @@ object MangaAPI {
|
|||||||
get("{sourceId}/preferences", SourceController::getPreferences)
|
get("{sourceId}/preferences", SourceController::getPreferences)
|
||||||
post("{sourceId}/preferences", SourceController::setPreference)
|
post("{sourceId}/preferences", SourceController::setPreference)
|
||||||
|
|
||||||
get("{sourceId}/filters", SourceController::filters)
|
get("{sourceId}/filters", SourceController::getFilters)
|
||||||
|
post("{sourceId}/filters", SourceController::setFilter)
|
||||||
|
|
||||||
get("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle)
|
get("{sourceId}/search", SourceController::searchSingle)
|
||||||
// get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal)
|
// get("all/search", SourceController::searchGlobal) // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
path("manga") {
|
path("manga") {
|
||||||
get("{mangaId}", MangaController::retrieve)
|
get("{mangaId}", MangaController.retrieve)
|
||||||
get("{mangaId}/thumbnail", MangaController::thumbnail)
|
get("{mangaId}/thumbnail", MangaController::thumbnail)
|
||||||
|
|
||||||
get("{mangaId}/category", MangaController::categoryList)
|
get("{mangaId}/category", MangaController::categoryList)
|
||||||
@@ -76,11 +78,13 @@ object MangaAPI {
|
|||||||
get("", CategoryController::categoryList)
|
get("", CategoryController::categoryList)
|
||||||
post("", CategoryController::categoryCreate)
|
post("", CategoryController::categoryCreate)
|
||||||
|
|
||||||
|
// The order here is important {categoryId} needs to be applied last
|
||||||
|
// or throws a NumberFormatException
|
||||||
|
patch("reorder", CategoryController::categoryReorder)
|
||||||
|
|
||||||
get("{categoryId}", CategoryController::categoryMangas)
|
get("{categoryId}", CategoryController::categoryMangas)
|
||||||
patch("{categoryId}", CategoryController::categoryModify)
|
patch("{categoryId}", CategoryController::categoryModify)
|
||||||
delete("{categoryId}", CategoryController::categoryDelete)
|
delete("{categoryId}", CategoryController::categoryDelete)
|
||||||
|
|
||||||
patch("reorder", CategoryController::categoryReorder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path("backup") {
|
path("backup") {
|
||||||
@@ -106,5 +110,13 @@ 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/{pageNum}", UpdateController::recentChapters)
|
||||||
|
post("fetch", UpdateController::categoryUpdate)
|
||||||
|
post("reset", UpdateController.reset)
|
||||||
|
get("summary", UpdateController::updateSummary)
|
||||||
|
ws("", UpdateController::categoryUpdateWS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user