Compare commits

..

37 Commits

Author SHA1 Message Date
Aria Moradi 6a11d2e357 bump version
CI Publish / Validate Gradle Wrapper (push) Successful in 11s
CI Publish / Build artifacts and release (push) Failing after 16s
2021-09-13 19:32:05 +04:30
Aria Moradi 0a9e0bc9e4 update WebUI 2021-09-13 19:31:07 +04:30
Aria Moradi 5914e367d1 better CHANGELOG 2021-09-13 19:29:54 +04:30
Mitchell Syer aeaed888d4 Update Proto models to match Tachi preview (#192) 2021-09-12 04:04:48 +04:30
Aria Moradi a0f054b005 CHANGELOG.md 2021-09-11 19:47:06 +04:30
Aria Moradi 4498e9d444 beter handling of uninstalling Extensions 2021-09-11 16:26:04 +04:30
Aria Moradi 43e0763fef fix a bug where if two sources return the same URL, a false duplicate might be detected 2021-09-11 14:47:03 +04:30
Aria Moradi a519c8a482 better PR links 2021-09-11 03:18:36 +04:30
Aria Moradi 19bc595a2a update CHANGELOG.md 2021-09-11 03:13:26 +04:30
Aria Moradi db07825c58 remove println instance 2021-09-11 03:09:26 +04:30
Aria Moradi b199e3bf0e fix installing apk with weird name 2021-09-11 01:21:22 +04:30
Aria Moradi 1f9ea0891e add some logging 2021-09-11 00:48:17 +04:30
Aria Moradi 0a7aa48f1e fix apk log and apk name 2021-09-11 00:14:52 +04:30
Aria Moradi 4b65b7da6c rm dummy 2021-09-10 21:42:52 +04:30
Mosei 183a7dac4b Create docker_build_stable (#184)
* Create docker_build_stable

Add docker build workflow

* Rename docker_build_stable to docker_build_stable.yml

* Update docker_build_stable.yml

* Update docker_build_stable.yml
2021-09-10 21:42:31 +04:30
Aria Moradi 6726f008c1 add dummy file to trigger workflow 2021-09-10 21:22:28 +04:30
Mosei 89cf0c140f Update publish.yml (#190) 2021-09-10 20:49:57 +04:30
Mosei 504025ce80 Update build_push.yml (#191) 2021-09-10 20:49:33 +04:30
Aria Moradi fee9e914f1 fix exposed crash 2021-09-10 00:45:00 +04:30
Aria Moradi 76efa71c68 update WebUI 2021-09-09 23:05:58 +04:30
Aria Moradi 26e61959ae update WebUI 2021-09-09 16:46:19 +04:30
Aria Moradi 9a8956ef9d update dependencies 2021-09-09 16:41:41 +04:30
Aria Moradi 10d3ffc2f6 better UX 2021-09-09 06:05:09 +04:30
Aria Moradi 090399f61d add support for installing external APK 2021-09-09 05:22:45 +04:30
Aria Moradi ae7d975a92 run won't get stuck now 2021-09-09 05:15:51 +04:30
Aria Moradi 55ec6bcafe add TODO for changing later 2021-09-09 05:15:08 +04:30
Aria Moradi f0566d15af fix compiler warning 2021-09-08 20:27:29 +04:30
Aria Moradi a730b692bc update WebUI 2021-09-08 20:24:22 +04:30
Aria Moradi 826d767423 [SKIP CI] update CHANGELOG.md 2021-09-08 20:23:45 +04:30
Aria Moradi 6d227c7fcd Fix typo 2021-09-06 22:23:26 +04:30
Aria Moradi 7d9d97840e Update README.md 2021-09-06 14:41:00 +04:30
Mosei 110ded45a0 Update README.md (#189)
Remove depreciated arbuilder’s docker link
2021-09-05 21:16:25 +04:30
Aria Moradi 7872b593c7 Credit to @Syer10 2021-09-05 17:50:45 +04:30
Aria Moradi 90be30bddb add support for the new gnere type in WebUI 2021-09-05 01:40:34 +04:30
Mitchell Syer a298c61dab Change type of Manga.genre to a List<String> (#188) 2021-09-05 01:36:52 +04:30
Aria Moradi eb416c45bd remove not-code changes from CHANGELOG 2021-09-05 00:54:01 +04:30
Aria Moradi b05b817aeb Update README.md 2021-09-03 18:52:06 +04:30
28 changed files with 237 additions and 103 deletions
-4
View File
@@ -123,7 +123,3 @@ jobs:
owner: "Suwayomi"
repo: "Tachidesk-Server-preview"
tag: ${{ steps.GenTagName.outputs.value }}
- name: Run Docker build workflow
run: |
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.DEPLOY_PREVIEW_TOKEN }}" -d '{"ref":"main", "inputs":{"tachidesk_release_type": "preview"}}' https://api.github.com/repos/suwayomi/docker-tachidesk/actions/workflows/build_container_images.yml/dispatches
+15
View File
@@ -0,0 +1,15 @@
name: Docker Build Stable
on:
release:
types: [published]
jobs:
build_publish_docker_container:
runs-on: ubuntu-latest
steps:
- name: run docker build and publish script
run: |
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.DEPLOY_PREVIEW_TOKEN }}" -d '{"ref":"main", "inputs":{"tachidesk_release_type": "stable"}}' https://api.github.com/repos/suwayomi/docker-tachidesk/actions/workflows/build_container_images.yml/dispatches
-6
View File
@@ -81,9 +81,3 @@ jobs:
tags: true
draft: true
verbose: true
- name: Run Docker build workflow
run: |
sleep 10 # sleep a bit to make sure the release is actually inside github db
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.DEPLOY_PREVIEW_TOKEN }}" -d '{"ref":"main", "inputs":{"tachidesk_release_type": "stable"}}' https://api.github.com/repos/suwayomi/docker-tachidesk/actions/workflows/build_container_images.yml/dispatches
+4 -7
View File
@@ -1,4 +1,7 @@
# Server: v0.X.Y-next + WebUI: rXXX
# Server: v0.X.Y-rXXX + WebUI: rXXX
## TL;DR
<!-- TODO: fill before release -->
## Tachidesk-Server
### Public API
#### Non-breaking changes
@@ -13,9 +16,6 @@
### Private API
- N/A
#### Non-code changes
- N/A
## Tachidesk-WebUI
#### Visible changes
@@ -26,6 +26,3 @@
#### Internal changes
- N/A
#### Non-code changes
- N/A
+29 -15
View File
@@ -1,34 +1,48 @@
# Server: v0.4.9-next + WebUI: r769
# Server: v0.5.0 + WebUI: r789
## TL;DR
- You can now install APK extensions from the extensions page
- WebUI now comes with an updated Material Design looks and is faster a little bit.
- WebUI now shows Nsfw content by default, disable it in settings if you prefer to not see Nsfw stuff
- Added support for configuration of sources, this enables MangaDex, Komga, Cubari and many other sources
- Chapters in the Manga page and Sources in the source page now look nicer and will glow with mouse hover
## Tachidesk-Server
### Public API
#### Non-breaking changes
- N/A
- (r888) add installing APK from external sources endpoint
#### Breaking changes
- N/A
- (r877 [#188](https://github.com/Suwayomi/Tachidesk-Server/pull/188) by @Syer10) `MangaDataClass.genre` changed type to `List<String>`
#### Bug fixes
- N/A
- (r899-r901) fix when an external apk is installed and it doesn't have the default tachiyomi-extensions name
- (r905) fix a bug where if two sources return the same URL, a false duplicate might be detected
### Private API
- N/A
#### Non-code changes
- N/A
- (r887) the `run` task won't call `downloadWebUI` now
- (r902) cleanup print/ln instances
- (r906) better handling of uninstalling Extensions
## Tachidesk-WebUI
#### Visible changes
- N/A
- (r770) add support for the new genre type
- (r771) set the default value of `showNsfw` to `true` so we won't have visual artifacts with a clean install
- (r774 [#21](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @voltrare) `ReaderNavbar.jsx`: Swap close and retract Navbar buttons
- (r775 [#23](https://github.com/Suwayomi/Tachidesk-WebUI/pull/23) by @voltrare) `yarn.lock`: Fixes version inconsistency after commit 9b866811b
- (r776 [#23](https://github.com/Suwayomi/Tachidesk-WebUI/pull/23) by @voltrare) add margin between Source and Extension cards, make the Search button look nicer
- (r777) add support for installing external APK files
- (r778) fix the makeToaster?
- (r779) Action button for installing external extension
- (r780 Suwayomi/Tachidesk-WebUI#25) add on hover, active effect to Chapter/Episode card
- (r782-r785) updating material-ui to v5 changed the theme
- (r785-r788) better `SourceCard` looks on mobile, move `SourceDataClass.isConfigurable` gear button to `SourceMangas`
- (r789) implement source configuration
#### Bug fixes
- N/A
#### Internal changes
- N/A
#### Non-code changes
- N/A
- (r782-r785) update dependencies, migrate material-ui from v4 to v5
@@ -109,4 +123,4 @@
- Re-ordering categories now works
#### Internal changes
- N/A
- N/A
+36 -14
View File
@@ -4,11 +4,11 @@
| ![CI](https://github.com/Suwayomi/Tachidesk/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Tachidesk.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Tachidesk/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Tachidesk-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) |
# Tachidesk-Server is a server app! You may not want to Download Tachidesk-Server directly.
Yes, you need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachodesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.
Yes, you need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.
Here's a list of known clients/user interfaces for Tachidesk-Server:
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The "official" front-end for Tachidesk-Server, A native desktop Application.
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/electrion front-end that Tachidesk is traditionally shipped with.
- [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.
@@ -41,23 +41,45 @@ Here is a list of current features:
**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
### All Operating Systems
You should have The Java Runtime Environment(JRE) 8 or newer and a modern browser installed(Google is your friend for seeking assitance). Also an internet connection is required as almost everything this app does is downloading stuff.
# 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).
Download the latest "Stable" jar release from [the releases section](https://github.com/Suwayomi/Tachidesk/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.
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
To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
#### Launcher Scripts
- `Tachidesk Electron Launcher`: Launches Tachidesk inside Electron as a desktop applicaton
- `Tachidesk Browser Launcher`: Launches Tachidesk in a browser window
- `Tachidesk Debug Launcher`: Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why.
**Node:** Linux launcher scripts are named a bit differently but work the same.
### Windows
Download the latest "Stable" win32 or win64 (depending on your system, usually you want win64) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
The Windows specific build has java bundled inside, so you don't have to install java to use it. Unzip `Tachidesk-vX.Y.Z-rxxx-win64.zip` and run one of the Launcher files depending on what you want(see bellow). The rest works like the previous section.
#### Windows Launchers
- `Tachidesk Electron Launcher.bat`: Launches Tachidesk inside Electron as a desktop applicaton
- `Tachidesk Browser Launcher.bat`: Launches Tachidesk in a browser window
- `Tachidesk Debug Launcher.bat`: Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why.
Unzip the downloaded file and double click on one of the launcher scripts.
### macOS
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
Unzip the downloaded file and double click on one of the launcher scripts.
### GNU/Linux
Download the latest `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
`tar xvf` the downloaded file and double click on one of the launcher scripts or run them using the terminal.
## Other methods of getting Tachidesk
### Arch Linux
You can install Tachidesk from the AUR
```
@@ -65,7 +87,7 @@ yay -S tachidesk
```
### Docker
Check our Offical Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) or use [arbuilder's](https://github.com/arbuilder/Tachidesk-docker) tachidesk docker repo for installation. 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.
Install from the command line:
```
+8 -7
View File
@@ -46,18 +46,18 @@ configure(projects) {
testImplementation(kotlin("test-junit5"))
// coroutines
val coroutinesVersion = "1.5.0"
val coroutinesVersion = "1.5.1"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
val kotlinSerializationVersion = "1.2.1"
val kotlinSerializationVersion = "1.3.0-RC"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
// Dependency Injection
implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0")
implementation("org.kodein.di:kodein-di-conf-jvm:7.7.0")
// Logging
implementation("org.slf4j:slf4j-api:1.7.30")
@@ -69,8 +69,8 @@ configure(projects) {
implementation("io.reactivex:rxkotlin:1.0.0")
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
// JSoup
implementation("org.jsoup:jsoup:1.13.1")
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
implementation("org.jsoup:jsoup:1.14.1")
// dependency of :AndroidCompat:Config
implementation("com.typesafe:config:1.4.1")
@@ -80,14 +80,15 @@ configure(projects) {
implementation("net.harawata:appdirs:1.2.1")
// dex2jar
val dex2jarVersion = "v21"
val dex2jarVersion = "v26"
implementation("com.github.ThexXTURBOXx.dex2jar:dex-translator:$dex2jarVersion")
implementation("com.github.ThexXTURBOXx.dex2jar:dex-tools:$dex2jarVersion")
// APK parser
implementation("net.dongliu:apk-parser:2.6.10")
// Jackson
// dependency both in AndroidCompat and server, version locked by javalin
implementation("com.fasterxml.jackson.core:jackson-annotations:2.10.3")
}
}
+4 -4
View File
@@ -7,16 +7,16 @@ import java.io.BufferedReader
* 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/. */
const val kotlinVersion = "1.5.21"
const val kotlinVersion = "1.5.30"
const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.9"
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.0"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r769"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r789"
// counts commits on the the master branch
// counts commits on the master branch
val tachideskRevision = runCatching {
System.getenv("ProductRevision") ?: Runtime
.getRuntime()
+7 -7
View File
@@ -9,7 +9,7 @@ plugins {
application
kotlin("plugin.serialization")
id("com.github.johnrengelman.shadow") version "7.0.0"
id("org.jmailen.kotlinter") version "3.5.0"
id("org.jmailen.kotlinter") version "3.6.0"
id("com.github.gmazzo.buildconfig") version "3.0.2"
}
@@ -31,13 +31,13 @@ dependencies {
implementation("com.squareup.okio:okio:2.10.0")
// Javalin api
implementation("io.javalin:javalin:3.13.6")
// jackson version is tied to javalin, ref: `io.javalin.core.util.OptionalDependency`
implementation("io.javalin:javalin:3.13.11")
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
// Exposed ORM
val exposedVersion = "0.31.1"
val exposedVersion = "0.34.1"
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
@@ -46,12 +46,12 @@ dependencies {
implementation("com.h2database:h2:1.4.200")
// Exposed Migrations
val exposedMigrationsVersion = "3.1.1"
val exposedMigrationsVersion = "3.1.2"
implementation("com.github.Suwayomi:exposed-migrations:$exposedMigrationsVersion")
// tray icon
implementation("com.dorkbox:SystemTray:4.1")
implementation("com.dorkbox:Utilities:1.9")
implementation("com.dorkbox:Utilities:1.9") // version locked by SystemTray
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
@@ -162,7 +162,7 @@ tasks {
}
named("run") {
dependsOn("formatKotlin", "lintKotlin", "downloadWebUI")
dependsOn("formatKotlin", "lintKotlin")
}
named<Copy>("processResources") {
@@ -12,6 +12,7 @@ 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() {
@@ -48,7 +49,7 @@ fun AnimeTable.toDataClass(mangaEntry: ResultRow) =
mangaEntry[artist],
mangaEntry[author],
mangaEntry[description],
mangaEntry[genre],
mangaEntry[genre].toGenreList(),
Companion.valueOf(mangaEntry[status]).name,
mangaEntry[inLibrary]
)
@@ -26,6 +26,7 @@ object MangaAPI {
get("list", ExtensionController::list)
get("install/:pkgName", ExtensionController::install)
post("install", ExtensionController::installFile)
get("update/:pkgName", ExtensionController::update)
get("uninstall/:pkgName", ExtensionController::uninstall)
@@ -29,6 +29,7 @@ object BackupController {
/** expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */
fun protobufImportFile(ctx: Context) {
// TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz"
ctx.json(
JavalinSetup.future {
ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content)
@@ -8,11 +8,14 @@ package suwayomi.tachidesk.manga.controller
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import io.javalin.http.Context
import mu.KotlinLogging
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
import suwayomi.tachidesk.server.JavalinSetup.future
object ExtensionController {
private val logger = KotlinLogging.logger {}
/** list all extensions */
fun list(ctx: Context) {
ctx.json(
@@ -33,6 +36,19 @@ object ExtensionController {
)
}
/** install the uploaded apk file */
fun installFile(ctx: Context) {
val uploadedFile = ctx.uploadedFile("file")!!
logger.debug { "Uploaded extension file name: " + uploadedFile.filename }
ctx.json(
future {
Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename)
}
)
}
/** update extension identified with "pkgName" */
fun update(ctx: Context) {
val pkgName = ctx.pathParam("pkgName")
@@ -25,6 +25,7 @@ import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.storage.CachedImageResponse.clearCachedImage
import suwayomi.tachidesk.manga.impl.util.storage.CachedImageResponse.getCachedImageResponse
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
import suwayomi.tachidesk.manga.model.table.MangaStatus
import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -56,7 +57,7 @@ object Manga {
mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre],
mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference]),
@@ -106,7 +107,7 @@ object Manga {
fetchedManga.artist,
fetchedManga.author,
fetchedManga.description,
fetchedManga.genre,
fetchedManga.genre.toGenreList(),
MangaStatus.valueOf(fetchedManga.status).name,
mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference]),
@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.impl
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.MangasPage
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
@@ -16,6 +17,7 @@ import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
import suwayomi.tachidesk.manga.model.table.MangaStatus
import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -41,7 +43,9 @@ object MangaList {
val mangasPage = this
val mangaList = transaction {
return@transaction mangasPage.mangas.map { manga ->
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
var mangaEntry = MangaTable.select {
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
}.firstOrNull()
if (mangaEntry == null) { // create manga entry
val mangaId = MangaTable.insertAndGetId {
it[url] = manga.url
@@ -57,7 +61,9 @@ object MangaList {
it[sourceReference] = sourceId
}.value
mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.first()
mangaEntry = MangaTable.select {
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
}.first()
MangaDataClass(
mangaId,
@@ -72,7 +78,7 @@ object MangaList {
manga.artist,
manga.author,
manga.description,
manga.genre,
manga.genre.toGenreList(),
MangaStatus.valueOf(manga.status).name,
false, // It's a new manga entry
meta = getMangaMetaMap(mangaId),
@@ -94,7 +100,7 @@ object MangaList {
mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre],
mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary],
meta = getMangaMetaMap(mangaId),
@@ -43,6 +43,7 @@ object ProtoBackupExport : ProtoBackupBase() {
Backup(
backupManga(databaseManga, flags),
backupCategories(),
emptyList(),
backupExtensionInfo(databaseManga)
)
}
@@ -62,7 +62,7 @@ object ProtoBackupImport : ProtoBackupBase() {
}
// Store source mapping for error messages
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
sourceMapping = backup.getSourceMap()
// Restore individual manga
backup.backupManga.forEach {
@@ -103,7 +103,7 @@ object ProtoBackupImport : ProtoBackupBase() {
val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories
val history = backupManga.history
val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
val tracks = backupManga.getTrackingImpl()
try {
@@ -24,7 +24,7 @@ object ProtoBackupValidator : AbstractBackupValidator() {
throw Exception("Backup does not contain any manga.")
}
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
val sources = backup.getSourceMap()
val missingSources = transaction {
sources
@@ -8,5 +8,11 @@ data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>,
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
// Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var backupSources: List<BackupSource> = emptyList(),
)
@ProtoNumber(100) var brokenBackupSources: List<BrokenBackupSource> = emptyList(),
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
) {
fun getSourceMap(): Map<Long, String> {
return (brokenBackupSources.map { BackupSource(it.name, it.sourceId) } + backupSources)
.associate { it.sourceId to it.name }
}
}
@@ -4,7 +4,13 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupHistory(
data class BrokenBackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long
)
@Serializable
data class BackupHistory(
@ProtoNumber(1) var url: String,
@ProtoNumber(2) var lastRead: Long
)
@@ -33,8 +33,9 @@ data class BackupManga(
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
@ProtoNumber(100) var favorite: Boolean = true,
@ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
) {
fun getMangaImpl(): MangaImpl {
return MangaImpl().apply {
@@ -5,9 +5,15 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSource(
data class BrokenBackupSource(
@ProtoNumber(0) var name: String = "",
@ProtoNumber(1) var sourceId: Long
)
@Serializable
data class BackupSource(
@ProtoNumber(1) var name: String = "",
@ProtoNumber(2) var sourceId: Long
) {
companion object {
fun copyFrom(source: Source): BackupSource {
@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.impl.download
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
@@ -21,6 +22,8 @@ import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Queued
import suwayomi.tachidesk.manga.model.table.ChapterTable
import java.util.concurrent.CopyOnWriteArrayList
private val logger = KotlinLogging.logger {}
class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter>, val notifier: () -> Unit) : Thread() {
var shouldStop: Boolean = false
@@ -67,10 +70,10 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex }
step()
} catch (e: DownloadShouldStopException) {
println("Downloader was stopped")
logger.debug("Downloader was stopped")
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Queued }
} catch (e: Exception) {
println("Downloader faced an exception")
logger.debug("Downloader faced an exception")
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Error; it.tries++ }
e.printStackTrace()
} finally {
@@ -17,6 +17,7 @@ import mu.KotlinLogging
import okhttp3.Request
import okio.buffer
import okio.sink
import okio.source
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
@@ -27,6 +28,8 @@ import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
import suwayomi.tachidesk.manga.impl.util.GetHttpSource
import suwayomi.tachidesk.manga.impl.util.PackageTools
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN
@@ -36,7 +39,6 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
import suwayomi.tachidesk.manga.impl.util.PackageTools.getSignatureHash
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
import suwayomi.tachidesk.manga.impl.util.PackageTools.trustedSignatures
import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.storage.CachedImageResponse.getCachedImageResponse
import suwayomi.tachidesk.manga.model.table.ExtensionTable
@@ -68,7 +70,23 @@ object Extension {
}
}
suspend fun installAPK(fetcher: suspend () -> String): Int {
suspend fun installExternalExtension(inputStream: InputStream, apkName: String): Int {
return installAPK(true) {
val savePath = "${applicationDirs.extensionsRoot}/$apkName"
logger.debug { "Saving apk at $apkName" }
// download apk file
val downloadedFile = File(savePath)
downloadedFile.sink().buffer().use { sink ->
inputStream.source().use { source ->
sink.writeAll(source)
sink.flush()
}
}
savePath
}
}
suspend fun installAPK(forceReinstall: Boolean = false, fetcher: suspend () -> String): Int {
val apkFilePath = fetcher()
val apkName = File(apkFilePath).name
@@ -78,16 +96,19 @@ object Extension {
ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()
}?.get(ExtensionTable.isInstalled) ?: false
if (!isInstalled) {
val fileNameWithoutType = apkName.substringBefore(".apk")
val fileNameWithoutType = apkName.substringBefore(".apk")
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
val jarFilePath = "$dirPathWithoutType.jar"
val dexFilePath = "$dirPathWithoutType.dex"
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
val jarFilePath = "$dirPathWithoutType.jar"
val dexFilePath = "$dirPathWithoutType.dex"
val packageInfo = getPackageInfo(apkFilePath)
val pkgName = packageInfo.packageName
val packageInfo = getPackageInfo(apkFilePath)
val pkgName = packageInfo.packageName
if (isInstalled && forceReinstall) {
uninstallExtension(pkgName)
}
if (!isInstalled || forceReinstall) {
if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) {
throw Exception("This apk is not a Tachiyomi extension")
}
@@ -103,12 +124,12 @@ object Extension {
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")
}
// 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"
@@ -120,7 +141,7 @@ object Extension {
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
// clean up
// File(apkFilePath).delete()
File(apkFilePath).delete()
File(dexFilePath).delete()
// collect sources from the extension
@@ -155,6 +176,7 @@ object Extension {
}
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[this.apkName] = apkName
it[this.isInstalled] = true
it[this.classFQName] = className
}
@@ -200,19 +222,30 @@ object Extension {
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() }
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction {
val sources = transaction {
val extensionId = extensionRecord[ExtensionTable.id].value
val sources = SourceTable.select { SourceTable.extension eq extensionId }.map { it[SourceTable.id].value }
SourceTable.deleteWhere { SourceTable.extension eq extensionId }
if (extensionRecord[ExtensionTable.isObsolete])
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
else
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[isInstalled] = false
}
sources
}
if (File(jarPath).exists()) {
// free up the file descriptor if exists
PackageTools.jarLoaderMap.remove(jarPath)?.close()
// clear all loaded sources
sources.forEach { GetHttpSource.invalidateSourceCache(it) }
File(jarPath).delete()
}
}
@@ -45,7 +45,7 @@ object PackageTools {
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key
var trustedSignatures = mutableSetOf<String>() + officialSignature + unofficialSignature
val trustedSignatures = mutableSetOf<String>() + officialSignature + unofficialSignature
/**
* Convert dex to jar, a wrapper for the dex2jar library
@@ -136,13 +136,19 @@ object PackageTools {
}
}
val jarLoaderMap = mutableMapOf<String, URLClassLoader>()
/**
* 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")))
logger.debug { "loading jar with path: $jarPath" }
val classLoader = jarLoaderMap[jarPath] ?: URLClassLoader(arrayOf<URL>(URL("file:$jarPath")))
val classToLoad = Class.forName(className, false, classLoader)
jarLoaderMap[jarPath] = classLoader
return classToLoad.getDeclaredConstructor().newInstance()
}
}
@@ -0,0 +1,3 @@
package suwayomi.tachidesk.manga.impl.util.lang
fun List<String>.trimAll() = map { it.trim() }
@@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.model.dataclass
* 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.manga.impl.util.lang.trimAll
import suwayomi.tachidesk.manga.model.table.MangaStatus
data class MangaDataClass(
@@ -22,7 +23,7 @@ data class MangaDataClass(
val artist: String? = null,
val author: String? = null,
val description: String? = null,
val genre: String? = null,
val genre: List<String> = emptyList(),
val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false,
val source: SourceDataClass? = null,
@@ -39,3 +40,5 @@ data class PagedMangaListDataClass(
val mangaList: List<MangaDataClass>,
val hasNextPage: Boolean
)
internal fun String?.toGenreList() = this?.split(",")?.trimAll().orEmpty()
@@ -13,6 +13,7 @@ import org.jetbrains.exposed.sql.ResultRow
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
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 MangaTable : IntIdTable() {
@@ -52,7 +53,7 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) =
mangaEntry[artist],
mangaEntry[author],
mangaEntry[description],
mangaEntry[genre],
mangaEntry[genre].toGenreList(),
Companion.valueOf(mangaEntry[status]).name,
mangaEntry[inLibrary],
meta = getMangaMetaMap(mangaEntry[id].value),