Compare commits

...

2 Commits
v1.4.3 ... main

Author SHA1 Message Date
arkon
29e358953e Misc cleanup 2023-09-14 22:24:21 -04:00
arkon
1aa6775836 Revert RxJava API changes for 1.4
These will return for 1.5
2023-09-14 22:05:47 -04:00
73 changed files with 204 additions and 390 deletions

View File

@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout pull request
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
@ -34,4 +34,4 @@ jobs:
- name: Build project
uses: gradle/gradle-command-action@v2
with:
arguments: :server:shadowJar
arguments: :inspector:shadowJar

View File

@ -16,7 +16,7 @@ jobs:
steps:
- name: Clone repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
@ -34,4 +34,4 @@ jobs:
- name: Build project
uses: gradle/gradle-command-action@v2
with:
arguments: :server:shadowJar
arguments: :inspector:shadowJar

View File

@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout ${{ github.ref }}
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
@ -36,7 +36,7 @@ jobs:
- name: Build project
uses: gradle/gradle-command-action@v2
with:
arguments: :server:shadowJar
arguments: :inspector:shadowJar
- name: Upload Release
uses: xresloader/upload-to-github-release@v1

8
.gitignore vendored
View File

@ -7,11 +7,3 @@ local.properties
# Ignore Gradle build output directory
build
server/src/main/resources/webUI
server/tmp/
server/tachiserver-data/
# bundle asset downlaods
OpenJDK*.zip
electron-*.zip
rcedit-*

View File

@ -5,7 +5,8 @@ package xyz.nulldev.ts.config
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import ch.qos.logback.classic.Level
import com.typesafe.config.Config

View File

@ -5,7 +5,8 @@ package xyz.nulldev.ts.config
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.typesafe.config.Config

View File

@ -5,7 +5,8 @@ package xyz.nulldev.ts.config
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import ch.qos.logback.classic.Level
import mu.KotlinLogging

View File

@ -5,7 +5,8 @@ package android.widget;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
public class EditText {
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }

View File

@ -5,7 +5,8 @@ package android.widget;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
public class Toast {
public static final int LENGTH_LONG = 1;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.annotation.NonNull;
import android.annotation.Nullable;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;
import android.text.TextUtils;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;
import android.content.SharedPreferences;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package androidx.preference;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context;

View File

@ -5,7 +5,8 @@ package xyz.nulldev.androidcompat.io.sharedprefs
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.SharedPreferences
import com.russhwolf.settings.ExperimentalSettingsApi
@ -23,7 +24,7 @@ import java.util.prefs.Preferences
@OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(key: String) : SharedPreferences {
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
private val javaPreferences = Preferences.userRoot().node("inspector/$key")
private val preferences = JvmPreferencesSettings(javaPreferences)
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()

View File

@ -5,7 +5,8 @@ package xyz.nulldev.androidcompat.replace.java.text;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.util.Currency;

View File

@ -5,7 +5,8 @@ package xyz.nulldev.androidcompat.replace.java.text;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.ibm.icu.text.DateFormatSymbols;
import com.ibm.icu.text.DisplayContext;

View File

@ -5,7 +5,8 @@ package xyz.nulldev.androidcompat.replace.java.util;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.util.ULocale;

View File

@ -5,7 +5,8 @@ package xyz.nulldev.androidcompat.replace.java.util;
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.ibm.icu.util.ULocale;

View File

@ -9,8 +9,7 @@ plugins {
}
allprojects {
group = "suwayomi"
group = "tachiyomi"
version = "1.0"
repositories {
@ -24,9 +23,9 @@ allprojects {
}
val projects = listOf(
project(":AndroidCompat"),
project(":AndroidCompat:Config"),
project(":server")
project(":AndroidCompat"),
project(":AndroidCompat:Config"),
project(":inspector")
)
configure(projects) {
@ -47,7 +46,6 @@ configure(projects) {
// Kotlin
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
testImplementation(kotlin("test"))
// coroutines
val coroutinesVersion = "1.6.4"

View File

@ -21,7 +21,6 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
implementation("com.squareup.okio:okio:3.3.0")
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
@ -35,12 +34,9 @@ dependencies {
// uncomment to test extensions directly
// implementation(fileTree("lib/"))
// Testing
testImplementation(kotlin("test-junit5"))
}
val MainClass = "suwayomi.tachidesk.MainKt"
val MainClass = "inspector.MainKt"
application {
mainClass.set(MainClass)
}
@ -75,7 +71,7 @@ val String.wrapped get() = """"$this""""
buildConfig {
className("BuildConfig")
packageName("suwayomi.server")
packageName("inspector")
useKotlinOutput()
@ -88,13 +84,13 @@ tasks {
shadowJar {
manifest {
attributes(
mapOf(
"Main-Class" to MainClass,
"Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Tachiyomi Open Source Project",
"Specification-Version" to inspectorVersion,
"Implementation-Version" to inspectorRevision
)
mapOf(
"Main-Class" to MainClass,
"Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Tachiyomi Open Source Project",
"Specification-Version" to inspectorVersion,
"Implementation-Version" to inspectorRevision
)
)
}
archiveBaseName.set(rootProject.name)

View File

@ -5,7 +5,8 @@ package eu.kanade.tachiyomi
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.app.Application
import android.content.Context

View File

@ -5,36 +5,26 @@ package eu.kanade.tachiyomi
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.app.Application
import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.serialization.json.Json
import rx.Observable
import rx.schedulers.Schedulers
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addSingleton
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)
addSingletonFactory { NetworkHelper(app) }
addSingletonFactory { JavaScriptEngine(app) }
addSingletonFactory { Json { ignoreUnknownKeys = true } }
// Asynchronously init expensive components for a faster cold start
rxAsync { get<NetworkHelper>() }
}
private fun rxAsync(block: () -> Unit) {
Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe()
}
}

View File

@ -0,0 +1,8 @@
package eu.kanade.tachiyomi
class BuildConfig {
companion object {
const val VERSION_NAME = inspector.BuildConfig.NAME
val VERSION_CODE = inspector.BuildConfig.REVISION.trimStart('r').toInt()
}
}

View File

@ -5,7 +5,8 @@ package eu.kanade.tachiyomi.network
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import okhttp3.Cookie
import okhttp3.CookieJar

View File

@ -5,7 +5,8 @@ package eu.kanade.tachiyomi.network
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.Context
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor

View File

@ -38,6 +38,7 @@ fun GET(
.cacheControl(cache)
.build()
}
fun POST(
url: String,
headers: Headers = DEFAULT_HEADERS,

View File

@ -5,7 +5,8 @@ package eu.kanade.tachiyomi.network.interceptor
*
* 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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import okhttp3.Interceptor
import okhttp3.Response

View File

@ -29,11 +29,16 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
TODO("Not yet implemented")
}
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
TODO("Not yet implemented")
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList
): Observable<MangasPage> {
TODO("Not yet implemented")
}

View File

@ -0,0 +1,46 @@
package eu.kanade.tachiyomi.source
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import rx.Observable
/**
* A basic interface for creating a source. It could be an online source, a local source, etc...
*/
interface Source {
/**
* 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 manga.
*
* @param manga the manga to update.
*/
fun fetchMangaDetails(manga: SManga): Observable<SManga>
/**
* Returns an observable with all the available chapters for a manga.
*
* @param manga the manga to update.
*/
fun fetchChapterList(manga: SManga): Observable<List<SChapter>>
/**
* Returns an observable with the list of pages a chapter has.
*
* @param chapter the chapter.
*/
fun fetchPageList(chapter: SChapter): Observable<List<Page>>
}

View File

@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.source.model
sealed class Filter<T>(val name: String, var state: T) {
open class Header(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)
abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {

View File

@ -56,7 +56,8 @@ abstract class HttpSource : CatalogueSource {
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
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
.reduce(Long::or) and Long.MAX_VALUE
}
/**
@ -118,7 +119,11 @@ abstract class HttpSource : CatalogueSource {
* @param query the search query.
* @param filters the list of filters to apply.
*/
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList
): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
@ -133,7 +138,11 @@ abstract class HttpSource : CatalogueSource {
* @param query the search query.
* @param filters the list of filters to apply.
*/
protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
protected abstract fun searchMangaRequest(
page: Int,
query: String,
filters: FilterList
): Request
/**
* Parses the response from the site and returns a [MangasPage] object.

View File

@ -4,4 +4,5 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) =
withContext(Dispatchers.IO, block)

View File

@ -1,4 +1,4 @@
package suwayomi.tachidesk
package inspector
/*
* Copyright (C) Contributors to the Suwayomi project
@ -8,14 +8,18 @@ package suwayomi.tachidesk
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.source.online.HttpSource
import inspector.util.Extension
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mu.KotlinLogging
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.server.applicationSetup
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.AndroidCompat
import xyz.nulldev.androidcompat.AndroidCompatInitializer
import xyz.nulldev.ts.config.ConfigKodeinModule
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
@ -23,41 +27,53 @@ import kotlin.io.path.extension
import kotlin.streams.asSequence
private val logger = KotlinLogging.logger {}
private val androidCompat by lazy { AndroidCompat() }
suspend fun main(args: Array<String>) {
if (args.size < 3) {
throw RuntimeException("Inspector must be given the path of apks directory, output json, and a tmp dir")
}
applicationSetup()
val (apksPath, outputPath, tmpDirPath) = args
initApplication()
val tmpDir = File(tmpDirPath, "tmp").also { it.mkdir() }
val extensions = Files.find(Paths.get(apksPath), 2, { _, fileAttributes -> fileAttributes.isRegularFile })
.asSequence()
.filter { it.extension == "apk" }
.toList()
val extensions =
Files.find(Paths.get(apksPath), 2, { _, fileAttributes -> fileAttributes.isRegularFile })
.asSequence()
.filter { it.extension == "apk" }
.toList()
logger.info("Found ${extensions.size} extensions")
val extensionsInfo = extensions.associate {
logger.debug("Installing $it")
val (pkgName, sources) = Extension.installAPK(tmpDir) { it.toFile() }
val (pkgName, sources) = Extension.installApk(tmpDir) { it.toFile() }
pkgName to sources.map { source -> SourceJson(source) }
}
File(outputPath).writeText(Json.encodeToString(extensionsInfo))
}
private fun initApplication() {
logger.info("Running Inspector ${BuildConfig.VERSION} revision ${BuildConfig.REVISION}")
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
}
@Serializable
data class SourceJson(
private data class SourceJson(
val name: String,
val lang: String,
val id: String,
val baseUrl: String,
val versionId: Int,
val hasCloudflare: Short
) {
constructor(source: HttpSource) :
this(
@ -66,10 +82,5 @@ data class SourceJson(
source.id.toString(),
source.baseUrl,
source.versionId,
source.client.interceptors
.any { it is CloudflareInterceptor }
.toShort()
)
}
private fun Boolean.toShort(): Short = if (this) 1 else 0

View File

@ -1,29 +1,33 @@
package suwayomi.tachidesk.manga.impl.extension
package inspector.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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource
import inspector.util.PackageTools.EXTENSION_FEATURE
import inspector.util.PackageTools.LIB_VERSION_MAX
import inspector.util.PackageTools.LIB_VERSION_MIN
import inspector.util.PackageTools.METADATA_SOURCE_CLASS
import inspector.util.PackageTools.dex2jar
import inspector.util.PackageTools.getPackageInfo
import inspector.util.PackageTools.loadExtensionSources
import mu.KotlinLogging
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_SOURCE_CLASS
import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
import java.io.File
object Extension {
private val logger = KotlinLogging.logger {}
suspend fun installAPK(tmpDir: File, fetcher: suspend () -> File): Pair<String, List<HttpSource>> {
suspend fun installApk(
tmpDir: File,
fetcher: suspend () -> File
): Pair<String, List<HttpSource>> {
val apkFile = fetcher()
val jarFile = File(tmpDir, "${apkFile.nameWithoutExtension}.jar")
@ -43,14 +47,19 @@ object Extension {
)
}
val className = packageInfo.packageName + packageInfo.applicationInfo.metaData.getString(METADATA_SOURCE_CLASS)
val className = packageInfo.packageName + packageInfo.applicationInfo.metaData.getString(
METADATA_SOURCE_CLASS
)
logger.trace("Main class for extension is $className")
dex2jar(apkFile, jarFile)
// collect sources from the extension
return packageInfo.packageName to when (val instance = loadExtensionSources(jarFile.absolutePath, className)) {
return packageInfo.packageName to when (
val instance =
loadExtensionSources(jarFile.absolutePath, className)
) {
is Source -> listOf(instance).filterIsInstance<HttpSource>()
is SourceFactory -> instance.createSources().filterIsInstance<HttpSource>()
else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}")

View File

@ -1,11 +1,12 @@
package suwayomi.tachidesk.manga.impl.util
package inspector.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/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import android.content.pm.PackageInfo
import android.content.pm.Signature
@ -57,7 +58,8 @@ object PackageTools {
.skipExceptions(false)
.to(jarFilePath)
if (handler.hasException()) {
val errorFile: Path = jarFilePath.parent.resolve("${dexFile.nameWithoutExtension}-error.txt")
val errorFile: Path =
jarFilePath.parent.resolve("${dexFile.nameWithoutExtension}-error.txt")
logger.error(
"""
Detail Error Information in File $errorFile

View File

@ -8,7 +8,7 @@
</encoder>
</appender>
<logger name="Exposed" level="ERROR"/>
<logger name="Exposed" level="ERROR" />
<root level="INFO">
<appender-ref ref="STDOUT" />

View File

@ -1,8 +0,0 @@
package eu.kanade.tachiyomi
class BuildConfig {
companion object {
const val VERSION_NAME = suwayomi.server.BuildConfig.NAME
val VERSION_CODE = suwayomi.server.BuildConfig.REVISION.trimStart('r').toInt()
}
}

View File

@ -1,101 +0,0 @@
package eu.kanade.tachiyomi.source
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.awaitSingle
import rx.Observable
/**
* A basic interface for creating a source. It could be an online source, a local source, etc.
*/
interface Source {
/**
* ID for the source. Must be unique.
*/
val id: Long
/**
* Name of the source.
*/
val name: String
val lang: String
get() = ""
/**
* Get the updated details for a manga.
*
* @since extensions-lib 1.4
* @param manga the manga to update.
* @return the updated manga.
*/
@Suppress("DEPRECATION")
suspend fun getMangaDetails(manga: SManga): SManga {
return fetchMangaDetails(manga).awaitSingle()
}
/**
* Get all the available chapters for a manga.
*
* @since extensions-lib 1.4
* @param manga the manga to update.
* @return the chapters for the manga.
*/
@Suppress("DEPRECATION")
suspend fun getChapterList(manga: SManga): List<SChapter> {
return fetchChapterList(manga).awaitSingle()
}
/**
* Get the list of pages a chapter has. Pages should be returned
* in the expected order; the index is ignored.
*
* @since extensions-lib 1.4
* @param chapter the chapter.
* @return the pages for the chapter.
*/
@Suppress("DEPRECATION")
suspend fun getPageList(chapter: SChapter): List<Page> {
return fetchPageList(chapter).awaitSingle()
}
/**
* Returns an observable with the updated details for a manga.
*
* @param manga the manga to update.
*/
@Deprecated(
"Use the non-RxJava API instead",
ReplaceWith("getMangaDetails"),
)
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException(
"Not used",
)
/**
* Returns an observable with all the available chapters for a manga.
*
* @param manga the manga to update.
*/
@Deprecated(
"Use the non-RxJava API instead",
ReplaceWith("getChapterList"),
)
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException(
"Not used",
)
/**
* Returns an observable with the list of pages a chapter has. Pages should be returned
* in the expected order; the index is ignored.
*
* @param chapter the chapter.
*/
@Deprecated(
"Use the non-RxJava API instead",
ReplaceWith("getPageList"),
)
fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty()
}

View File

@ -1,26 +0,0 @@
package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga
/**
* A source that may handle opening an SManga for a given URI.
*
* @since extensions-lib 1.5
*/
interface ResolvableSource : Source {
/**
* Whether this source may potentially handle the given URI.
*
* @since extensions-lib 1.5
*/
fun canResolveUri(uri: String): Boolean
/**
* Called if canHandleUri is true. Returns the corresponding SManga, if possible.
*
* @since extensions-lib 1.5
*/
suspend fun getManga(uri: String): SManga?
}

View File

@ -1,54 +0,0 @@
package eu.kanade.tachiyomi.util
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import rx.Observable
import rx.Subscriber
import rx.Subscription
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun <T> Observable<T>.awaitSingle(): T = single().awaitOne()
@OptIn(InternalCoroutinesApi::class)
private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutine { cont ->
cont.unsubscribeOnCancellation(
subscribe(
object : Subscriber<T>() {
override fun onStart() {
request(1)
}
override fun onNext(t: T) {
cont.resume(t)
}
override fun onCompleted() {
if (cont.isActive) {
cont.resumeWithException(
IllegalStateException(
"Should have invoked onNext",
),
)
}
}
override fun onError(e: Throwable) {
/*
* Rx1 observable throws NoSuchElementException if cancellation happened before
* element emission. To mitigate this we try to atomically resume continuation with exception:
* if resume failed, then we know that continuation successfully cancelled itself
*/
val token = cont.tryResumeWithException(e)
if (token != null) {
cont.completeResume(token)
}
}
},
),
)
}
private fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
invokeOnCancellation { sub.unsubscribe() }

View File

@ -1,44 +0,0 @@
package eu.kanade.tachiyomi.util.lang
import java.security.MessageDigest
object Hash {
private val chars = charArrayOf(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'
)
private val MD5 get() = MessageDigest.getInstance("MD5")
private val SHA256 get() = MessageDigest.getInstance("SHA-256")
fun sha256(bytes: ByteArray): String {
return encodeHex(SHA256.digest(bytes))
}
fun sha256(string: String): String {
return sha256(string.toByteArray())
}
fun md5(bytes: ByteArray): String {
return encodeHex(MD5.digest(bytes))
}
fun md5(string: String): String {
return md5(string.toByteArray())
}
private fun encodeHex(data: ByteArray): String {
val l = data.size
val out = CharArray(l shl 1)
var i = 0
var j = 0
while (i < l) {
out[j++] = chars[(240 and data[i].toInt()).ushr(4)]
out[j++] = chars[15 and data[i].toInt()]
i++
}
return String(out)
}
}

View File

@ -1,33 +0,0 @@
package suwayomi.tachidesk.server
/*
* 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.App
import mu.KotlinLogging
import org.kodein.di.DI
import org.kodein.di.conf.global
import suwayomi.server.BuildConfig
import xyz.nulldev.androidcompat.AndroidCompat
import xyz.nulldev.androidcompat.AndroidCompatInitializer
import xyz.nulldev.ts.config.ConfigKodeinModule
private val logger = KotlinLogging.logger {}
val androidCompat by lazy { AndroidCompat() }
fun applicationSetup() {
logger.info("Running Inspector ${BuildConfig.VERSION} revision ${BuildConfig.REVISION}")
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
}

View File

@ -1,13 +0,0 @@
# Server ip and port bindings
server.ip = "0.0.0.0"
server.port = 4567
# Socks5 proxy
server.socksProxy = false
server.socksProxyHost = ""
server.socksProxyPort = ""
# misc
server.debugLogsEnabled = true
server.systemTrayEnabled = false
server.initialOpenInBrowserEnabled = true

View File

@ -1,5 +1,5 @@
rootProject.name = "Tachiyomi Extensions Inspector"
include("server")
include("AndroidCompat")
include("AndroidCompat:Config")
include("AndroidCompat:Config")
include("inspector")