Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84206a7074 | |||
| d1500baae1 | |||
| 045801dd1a | |||
| 14a2cbc793 | |||
| fd385017df | |||
| 9b05954cf2 | |||
| 6aaf636069 | |||
| d30e89e5ec | |||
| 7acc745478 | |||
| 5a9a2d816e | |||
| 105f11ed02 | |||
| 3021437a05 | |||
| 439602fc03 | |||
| 34e13b9589 | |||
| 2aab4ae918 | |||
| 7ef67671a4 | |||
| e8df84416c | |||
| be930bb68b | |||
| db52948865 | |||
| d2a72526f6 | |||
| 0a9f57b32b | |||
| 180f210536 | |||
| c1baa31eed | |||
| cacc97cec7 | |||
| d5691fd81c | |||
| 49dc9fe5f6 | |||
| c0b49c7428 | |||
| fa345af42d | |||
| db3cc786a1 | |||
| fe879ae51d | |||
| 2f55460ffb | |||
| fbc5bd4642 | |||
| 5e0c7d3c9d | |||
| 083996a48d | |||
| 9d38f478e3 | |||
| 57274a0a01 | |||
| b3b56b7fc8 | |||
| 0b690577da | |||
| e9683a3a37 | |||
| f8f67b3eba | |||
| 7b16b082d8 | |||
| 2a783f0d8e | |||
| 42ae32de33 | |||
| cec7ddc486 | |||
| 9c55fc3868 | |||
| 104c5a8d83 | |||
| 7450b16742 | |||
| 3ecd0931a1 | |||
| 2f2a52ae2f | |||
| f464087c30 | |||
| 2364960388 | |||
| 76be4d64cd | |||
| 7d98e8ce47 | |||
| 40831fc681 | |||
| e38e7ccf26 | |||
| 98b9e2f2cf | |||
| 4bf3c12f76 |
+2
-1
@@ -24,4 +24,5 @@
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.swp binary
|
||||
*.pdf binary
|
||||
*.pdf binary
|
||||
*.exe binary
|
||||
@@ -1,17 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf preview/*.jar preview/*.zip
|
||||
|
||||
cp master/server/build/Tachidesk-*.jar preview
|
||||
cp master/server/build/Tachidesk-*.zip preview
|
||||
|
||||
cd preview
|
||||
|
||||
new_jar_build=$(ls *.jar| tail -1)
|
||||
new_jar_build=$(ls Tachidesk-*.jar)
|
||||
echo "last jar build file name: $new_jar_build"
|
||||
|
||||
cp -f $new_jar_build Tachidesk-latest.jar
|
||||
|
||||
rm -rf latest_pointer/*
|
||||
cp $new_jar_build latest_pointer
|
||||
|
||||
latest=$(ls *.jar | tail -n1 | sed -e's/Tachidesk-\|.jar//g')
|
||||
latest=$(echo $new_jar_build | sed -e's/Tachidesk-\|.jar//g')
|
||||
echo "{ \"latest\": \"$latest\" }" > index.json
|
||||
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
**/react/node_modules
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }}
|
||||
|
||||
- name: Build and copy webUI, Build Jar and launch4j
|
||||
- name: Build and copy webUI, Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
build-root-directory: master
|
||||
wrapper-directory: master
|
||||
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace
|
||||
arguments: :webUI:copyBuild :server:shadowJar --stacktrace
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
build:
|
||||
name: Build FatJar
|
||||
name: Build artifacts and deploy preview
|
||||
needs: check_wrapper
|
||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -59,22 +59,30 @@ jobs:
|
||||
**/react/node_modules
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }}
|
||||
|
||||
- name: Build and copy webUI, Build Jar and launch4j
|
||||
- name: Build and copy webUI, Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
build-root-directory: master
|
||||
wrapper-directory: master
|
||||
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace
|
||||
arguments: :webUI:copyBuild :server:shadowJar --stacktrace
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
|
||||
- name: make windows packages
|
||||
run: |
|
||||
cd master/scripts
|
||||
./windows32-bundler.sh
|
||||
./windows64-bundler.sh
|
||||
|
||||
- name: Checkout preview branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: preview
|
||||
repository: 'Suwayomi/Tachidesk-preview'
|
||||
ref: main
|
||||
path: preview
|
||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||
|
||||
- name: Deploy preview
|
||||
run: |
|
||||
./master/.github/scripts/commit-preview.sh
|
||||
./master/.github/scripts/commit-preview.sh
|
||||
@@ -56,54 +56,30 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/react/node_modules
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }}
|
||||
|
||||
- name: Build and copy webUI, Build Jar and launch4j
|
||||
- name: Build and copy webUI, Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
build-root-directory: master
|
||||
wrapper-directory: master
|
||||
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace
|
||||
arguments: :webUI:copyBuild :server:shadowJar --stacktrace
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
|
||||
- name: make windows packages
|
||||
run: |
|
||||
cd master/scripts
|
||||
./windows32-bundler.sh
|
||||
./windows64-bundler.sh
|
||||
|
||||
- name: Upload Release
|
||||
uses: xresloader/upload-to-github-release@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
file: "master/server/build/*.jar;master/server/build/*-win32.zip"
|
||||
file: "master/server/build/*.jar;master/server/build/*.zip"
|
||||
tags: true
|
||||
draft: true
|
||||
verbose: true
|
||||
|
||||
# - name: Create Release
|
||||
# id: create_release
|
||||
# uses: actions/create-release@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# tag_name: ${{ github.ref }}
|
||||
# release_name: Release ${{ github.ref }}
|
||||
# body: |
|
||||
# Release body
|
||||
# draft: false
|
||||
# prerelease: true
|
||||
#
|
||||
# - name: Get the Ref
|
||||
# id: get-ref
|
||||
# uses: ankitvgupta/ref-to-tag-action@master
|
||||
# with:
|
||||
# ref: ${{ github.ref }}
|
||||
# head_ref: ${{ github.head_ref }}
|
||||
#
|
||||
# - name: Get the tag
|
||||
# run: echo "The tag was ${{ steps.get-ref.outputs.tag }}"
|
||||
#
|
||||
# - name: Upload Release
|
||||
# uses: AButler/upload-release-assets@v2.0
|
||||
# with:
|
||||
# files: 'master/repo/*'
|
||||
# repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# release-tag: ${{ steps.get-ref.outputs.tag }}
|
||||
|
||||
+4
-1
@@ -8,4 +8,7 @@ build
|
||||
|
||||
server/src/main/resources/react
|
||||
server/tmp/
|
||||
server/tachiserver-data/
|
||||
server/tachiserver-data/
|
||||
|
||||
# OpenJDK downlaods
|
||||
OpenJDK*
|
||||
@@ -87,7 +87,6 @@ function Dedupe($path)
|
||||
}
|
||||
|
||||
Dedupe "AndroidCompat/src/main/java"
|
||||
Dedupe "server/src/main/java"
|
||||
Dedupe "server/src/main/kotlin"
|
||||
|
||||
Write-Output "Copying Android.jar to library folder..."
|
||||
|
||||
@@ -20,7 +20,7 @@ fi
|
||||
|
||||
|
||||
# foolproof against running from AndroidCompat dir instead of running from project root
|
||||
if [ "$(basename $(pwd))" = "AndroidCompat" ]; then
|
||||
if [ "$(basename "$(pwd)")" = "AndroidCompat" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
@@ -59,7 +59,7 @@ zip --delete android.jar javax/*
|
||||
echo "Removing java..."
|
||||
zip --delete android.jar java/*
|
||||
|
||||
echo "Removing overriden classes..."
|
||||
echo "Removing overridden classes..."
|
||||
zip --delete android.jar android/app/Application.class
|
||||
zip --delete android.jar android/app/Service.class
|
||||
zip --delete android.jar android/net/Uri.class
|
||||
@@ -68,12 +68,12 @@ zip --delete android.jar android/os/Environment.class
|
||||
zip --delete android.jar android/text/format/Formatter.class
|
||||
zip --delete android.jar android/text/Html.class
|
||||
|
||||
# Dedup overriden Android classes
|
||||
# Dedup overridden Android classes
|
||||
ABS_JAR="$(realpath android.jar)"
|
||||
function dedup() {
|
||||
pushd "$1"
|
||||
CLASSES="$(find * -type f)"
|
||||
echo "$CLASSES" | while read class
|
||||
CLASSES="$(find ./* -type f)"
|
||||
echo "$CLASSES" | while read -r class
|
||||
do
|
||||
NAME="${class%.*}"
|
||||
echo "Processing class: $NAME"
|
||||
@@ -82,13 +82,10 @@ function dedup() {
|
||||
popd
|
||||
}
|
||||
|
||||
pushd ..
|
||||
popd
|
||||
dedup AndroidCompat/src/main/java
|
||||
dedup server/src/main/java
|
||||
dedup server/src/main/kotlin
|
||||
popd
|
||||
|
||||
popd
|
||||
echo "Copying Android.jar to library folder..."
|
||||
mv tmp/android.jar AndroidCompat/lib
|
||||
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@ This structure is chosen to
|
||||
- Eaise development of alternative user intefaces for Tachidesk
|
||||
|
||||
## User Interfaces for Tachidesk server
|
||||
Currently there are three known interfaces for Tachidesk:
|
||||
Currently, there are three known interfaces for Tachidesk:
|
||||
1. [webUI](https://github.com/Suwayomi/Tachidesk/tree/master/webUI/react): The react SPA that Tachidesk is traditionally shipped with.
|
||||
2. [TachideskJUI](https://github.com/Suwayomi/TachideskJUI): A Jetbrains Compose Native app, re-uses components made for the upcoming Tachiyomi 1.x
|
||||
3. [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stages of development.
|
||||
@@ -28,7 +28,7 @@ You need these software packages installed in order to build the project
|
||||
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||
- Android stubs jar
|
||||
- Manual download: Download [android.jar](https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
||||
- Automated download: Run `AndroidCompat/getAndroid.sh`(MacOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows) from project's root directory to download and rebuild the jar file from Google's repository.
|
||||
- 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.
|
||||
### webUI
|
||||
- Nodejs LTS or latest
|
||||
- Yarn
|
||||
@@ -38,15 +38,15 @@ Run `./gradlew :webUI:copyBuild server:shadowJar`, the resulting built jar file
|
||||
### building without `webUI` bundled(server only)
|
||||
Delete the `server/src/main/resources/react` directory if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
||||
### building the Windows package
|
||||
Run `./gradlew :server:windowsPackage` to build a server only bundle and `./gradlew :webUI:copyBuild :server:windowsPackage` to get a full bundle , the resulting built zip package file will be `server/build/Tachidesk-vX.Y.Z-rxxx-win32.zip`.
|
||||
First Build the jar, then cd into the `scripts` directory and run `./windows<bits>-bundler.sh` (or `./windows<bits>-bundler.ps1` if you are on windows), the resulting built zip package file will be `server/build/Tachidesk-vX.Y.Z-rxxx-win64.zip`.
|
||||
## Running in development mode
|
||||
First satistify [the prerequisites](#prerequisites)
|
||||
First satisfy [the prerequisites](#prerequisites)
|
||||
### server
|
||||
run `./gradlew :server:run --stacktrace` to run the server
|
||||
### webUI
|
||||
How to do it is described in `webUI/react/README.md` but for short,
|
||||
first cd into `webUI/react` then run `yarn` to install the node modules(do this only once)
|
||||
then `yarn start` to start the development server, if a new browser window doesn't get opned automatically,
|
||||
then `yarn start` to start the development server, if a new browser window doesn't get opened automatically,
|
||||
then open `http://127.0.0.1:3000` in a modern browser. This is a `create-react-app` project
|
||||
and supports HMR and all the other goodies you'll need.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
| Build | Stable | Preview | Support Server |
|
||||
|-------|----------|---------|---------|
|
||||
|  | [](https://github.com/Suwayomi/Tachidesk/releases) | [](https://github.com/Suwayomi/Tachidesk/tree/preview/latest_pointer) | [](https://discord.gg/DDZdqZWaHA) |
|
||||
|  | [](https://github.com/Suwayomi/Tachidesk/releases) | [](https://github.com/Suwayomi/Tachidesk/tree/preview/) | [](https://discord.gg/DDZdqZWaHA) |
|
||||
|
||||
# Tachidesk
|
||||
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
|
||||
@@ -14,6 +14,8 @@ Tachidesk is as multi-platform as you can get. Any platform that runs java and/o
|
||||
|
||||
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
|
||||
|
||||
**Tachidesk needs serious front-end dev help for it's reader and other parts, if you like the app and want to see it become better please don't hesitate to contribute some code!**
|
||||
|
||||
## Is this application usable? Should I test it?
|
||||
Here is a list of current features:
|
||||
|
||||
@@ -24,7 +26,7 @@ Here is a list of current features:
|
||||
- Ability to download Mangas for offline read(This partially works)
|
||||
- Backup and restore support powered by Tachiyomi Legacy Backups
|
||||
|
||||
**Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update, so you may have to delete your data to fix it. See [General troubleshooting](#general-troubleshooting) and [Support and help](#support-and-help) if it happens.
|
||||
**Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting) if it happens.
|
||||
|
||||
## Downloading and Running the app
|
||||
### All Operating Systems
|
||||
@@ -35,19 +37,15 @@ Download the latest "Stable" jar release from [the releases section](https://git
|
||||
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.
|
||||
|
||||
### Windows
|
||||
Download the latest win32 release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases).
|
||||
Download the latest win32 or win64 (depending on your system, usually you want win64) release from [the releases section](https://github.com/Suwayomi/Tachidesk/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-win32.zip` and run `server.exe`. The rest works like the previous section.
|
||||
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 `Tachidesk Launcher.exe` or `Tachidesk Launcher.bat`. The rest works like the previous section.
|
||||
|
||||
### Arch Linux
|
||||
You can install Tachidesk from the AUR
|
||||
```
|
||||
yay -S tachidesk
|
||||
```
|
||||
Or the latest preview version
|
||||
```
|
||||
yay -S tachidesk-preview
|
||||
```
|
||||
|
||||
### Docker
|
||||
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
jre\bin\java -Dir.armor.tachidesk.debugLogsEnabled=true -jar Tachidesk.jar
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
start "" jre/bin/javaw -jar Tachidesk.jar
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
system("start jre\\bin\\javaw -jar Tachidesk.jar");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Building `Tachidesk Launcher.exe`
|
||||
1. compile `Tachidesk Launcher.c` statically using GCC MinGW: `gcc -o "Tachidesk Launcher.exe" "Tachidesk Launcher.c"`
|
||||
2. Add `server/src/main/resources/icon/faviconlogo.ico` into the exe with `rcedit` from the electron project: `rcedit "Tachidesk Launcher.exe" --set-icon faviconlogo.ico`
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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/.
|
||||
|
||||
Write-Output "Downloading jre..."
|
||||
|
||||
$jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
|
||||
if (!(Test-Path $jre)) {
|
||||
Invoke-WebRequest -Uri "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip" -OutFile $jre -UseBasicParsing
|
||||
}
|
||||
|
||||
Write-Output "creating windows bundle"
|
||||
|
||||
$jar=$(Get-ChildItem ../server/build/Tachidesk-*.jar)
|
||||
$release_name=$jar.BaseName + "-win32"
|
||||
|
||||
# make release dir
|
||||
New-Item -ItemType Directory $release_name
|
||||
|
||||
Expand-Archive $jre -DestinationPath "./" -ErrorAction SilentlyContinue
|
||||
|
||||
# move jre
|
||||
Move-Item "jdk8u292-b10-jre" "$release_name/jre"
|
||||
|
||||
Copy-Item $jar.FullName "$release_name/Tachidesk.jar"
|
||||
|
||||
Copy-Item "resources/Tachidesk Launcher-win32.exe" $release_name
|
||||
Copy-Item "resources/Tachidesk Launcher.bat" $release_name
|
||||
Copy-Item "resources/Tachidesk Debug Launcher.bat" $release_name
|
||||
|
||||
$zip_name="$release_name.zip"
|
||||
Compress-Archive -CompressionLevel Optimal -DestinationPath $zip_name -Path $release_name -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Remove-Item -Force -Recurse $release_name
|
||||
|
||||
Move-Item $zip_name "../server/build/" -ErrorAction SilentlyContinue
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/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 "Downloading jre..."
|
||||
|
||||
jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
|
||||
if [ ! -f $jre ]; then
|
||||
curl -L "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip" -o $jre
|
||||
fi
|
||||
|
||||
echo "creating windows bundle"
|
||||
|
||||
jar=$(ls ../server/build/Tachidesk-*.jar)
|
||||
jar_name=$(echo $jar | cut -d'/' -f4)
|
||||
release_name=$(echo $jar_name | cut -d'.' -f4 --complement)-win32
|
||||
|
||||
# make release dir
|
||||
mkdir $release_name
|
||||
|
||||
unzip $jre
|
||||
|
||||
# move jre
|
||||
mv jdk8u292-b10-jre $release_name/jre
|
||||
|
||||
cp $jar $release_name/Tachidesk.jar
|
||||
|
||||
cp "resources/Tachidesk Launcher-win32.exe" "$release_name/Tachidesk Launcher.exe"
|
||||
cp "resources/Tachidesk Launcher.bat" $release_name
|
||||
cp "resources/Tachidesk Debug Launcher.bat" $release_name
|
||||
|
||||
zip_name=$release_name.zip
|
||||
zip -9 -r $zip_name $release_name
|
||||
|
||||
rm -rf $release_name
|
||||
|
||||
mv $zip_name ../server/build/
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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/.
|
||||
|
||||
Write-Output "Downloading jre..."
|
||||
|
||||
$jre="OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip"
|
||||
if (!(Test-Path $jre)) {
|
||||
Invoke-WebRequest -Uri "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip" -OutFile $jre -UseBasicParsing
|
||||
}
|
||||
|
||||
Write-Output "creating windows bundle"
|
||||
|
||||
$jar=$(Get-ChildItem ../server/build/Tachidesk-*.jar)
|
||||
$release_name=$jar.BaseName + "-win64"
|
||||
|
||||
# make release dir
|
||||
New-Item -ItemType Directory $release_name
|
||||
|
||||
Expand-Archive $jre -DestinationPath "./" -ErrorAction SilentlyContinue
|
||||
|
||||
# move jre
|
||||
Move-Item "jdk8u292-b10-jre" "$release_name/jre"
|
||||
|
||||
Copy-Item $jar.FullName "$release_name/Tachidesk.jar"
|
||||
|
||||
Copy-Item "resources/Tachidesk Launcher-win64.exe" $release_name
|
||||
Copy-Item "resources/Tachidesk Launcher.bat" $release_name
|
||||
Copy-Item "resources/Tachidesk Debug Launcher.bat" $release_name
|
||||
|
||||
$zip_name="$release_name.zip"
|
||||
Compress-Archive -CompressionLevel Optimal -DestinationPath $zip_name -Path $release_name -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Remove-Item -Force -Recurse $release_name
|
||||
|
||||
Move-Item $zip_name "../server/build/" -ErrorAction SilentlyContinue
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/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 "Downloading jre..."
|
||||
|
||||
jre="OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip"
|
||||
if [ ! -f $jre ]; then
|
||||
curl -L "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip" -o $jre
|
||||
fi
|
||||
|
||||
echo "creating windows bundle"
|
||||
|
||||
jar=$(ls ../server/build/Tachidesk-*.jar)
|
||||
jar_name=$(echo $jar | cut -d'/' -f4)
|
||||
release_name=$(echo $jar_name | cut -d'.' -f4 --complement)-win64
|
||||
|
||||
# make release dir
|
||||
mkdir $release_name
|
||||
|
||||
unzip $jre
|
||||
|
||||
# move jre
|
||||
mv jdk8u292-b10-jre $release_name/jre
|
||||
|
||||
cp $jar $release_name/Tachidesk.jar
|
||||
|
||||
cp "resources/Tachidesk Launcher-win64.exe" "$release_name/Tachidesk Launcher.exe"
|
||||
cp "resources/Tachidesk Launcher.bat" $release_name
|
||||
cp "resources/Tachidesk Debug Launcher.bat" $release_name
|
||||
|
||||
zip_name=$release_name.zip
|
||||
zip -9 -r $zip_name $release_name
|
||||
|
||||
rm -rf $release_name
|
||||
|
||||
mv $zip_name ../server/build/
|
||||
+4
-69
@@ -8,7 +8,6 @@ plugins {
|
||||
application
|
||||
id("com.github.johnrengelman.shadow") version "7.0.0"
|
||||
id("org.jmailen.kotlinter") version "3.4.3"
|
||||
id("edu.sc.seis.launch4j") version "2.5.0"
|
||||
id("de.fuerstenau.buildconfig") version "1.1.8"
|
||||
}
|
||||
|
||||
@@ -83,7 +82,7 @@ dependencies {
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
}
|
||||
|
||||
val MainClass = "ir.armor.tachidesk.Main"
|
||||
val MainClass = "ir.armor.tachidesk.MainKt"
|
||||
application {
|
||||
mainClass.set(MainClass)
|
||||
}
|
||||
@@ -97,7 +96,7 @@ sourceSets {
|
||||
}
|
||||
|
||||
// should be bumped with each stable release
|
||||
val tachideskVersion = "v0.3.2"
|
||||
val tachideskVersion = "v0.3.7"
|
||||
|
||||
// counts commit count on master
|
||||
val tachideskRevision = Runtime
|
||||
@@ -126,18 +125,8 @@ buildConfig {
|
||||
buildConfigField("boolean", "debug", project.hasProperty("debugApp").toString())
|
||||
}
|
||||
|
||||
launch4j { //used for windows
|
||||
mainClassName = MainClass
|
||||
bundledJrePath = "jre"
|
||||
bundledJre64Bit = true
|
||||
jreMinVersion = "8"
|
||||
outputDir = "${rootProject.name}-$tachideskVersion-$tachideskRevision-win32"
|
||||
icon = "${projectDir}/src/main/resources/icon/faviconlogo.ico"
|
||||
jar = "${projectDir}/build/${rootProject.name}-$tachideskVersion-$tachideskRevision.jar"
|
||||
}
|
||||
|
||||
tasks {
|
||||
jar {
|
||||
shadowJar {
|
||||
manifest {
|
||||
attributes(
|
||||
mapOf(
|
||||
@@ -149,9 +138,6 @@ tasks {
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
shadowJar {
|
||||
manifest.inheritFrom(jar.get().manifest) //will make your shadowJar (produced by jar task) runnable
|
||||
archiveBaseName.set(rootProject.name)
|
||||
archiveVersion.set(tachideskVersion)
|
||||
archiveClassifier.set(tachideskRevision)
|
||||
@@ -165,61 +151,11 @@ tasks {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnit()
|
||||
}
|
||||
|
||||
register<Zip>("windowsPackage") {
|
||||
from(fileTree("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32"))
|
||||
destinationDirectory.set(File("$buildDir"))
|
||||
archiveFileName.set("${rootProject.name}-$tachideskVersion-$tachideskRevision-win32.zip")
|
||||
dependsOn("windowsPackageWorkaround2")
|
||||
}
|
||||
|
||||
register<Delete>("windowsPackageWorkaround2") {
|
||||
delete(
|
||||
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/jre",
|
||||
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/lib",
|
||||
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/server.exe",
|
||||
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/Tachidesk-$tachideskVersion-$tachideskRevision-win32/Tachidesk-$tachideskVersion-$tachideskRevision-win32"
|
||||
)
|
||||
dependsOn("windowsPackageWorkaround")
|
||||
}
|
||||
|
||||
register<Copy>("windowsPackageWorkaround") {
|
||||
from("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
|
||||
into("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
|
||||
dependsOn("deleteUnwantedJreDir")
|
||||
}
|
||||
|
||||
register<Delete>("deleteUnwantedJreDir") {
|
||||
delete(
|
||||
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/jdk8u282-b08-jre"
|
||||
)
|
||||
dependsOn("addJreToDistributable")
|
||||
}
|
||||
|
||||
register<Copy>("addJreToDistributable") {
|
||||
from(zipTree("$buildDir/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip"))
|
||||
into("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
|
||||
eachFile {
|
||||
path = path.replace(".*-jre".toRegex(), "jre")
|
||||
}
|
||||
dependsOn("downloadJre")
|
||||
dependsOn("createExe")
|
||||
}
|
||||
|
||||
named("createExe") {
|
||||
dependsOn("shadowJar")
|
||||
}
|
||||
|
||||
register<de.undercouch.gradle.tasks.download.Download>("downloadJre") {
|
||||
src("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip")
|
||||
dest("$buildDir/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip")
|
||||
overwrite(false)
|
||||
onlyIfModified(true)
|
||||
}
|
||||
|
||||
withType<ShadowJar> {
|
||||
destinationDirectory.set(File("$rootDir/server/build"))
|
||||
dependsOn("formatKotlin", "lintKotlin")
|
||||
@@ -242,4 +178,3 @@ tasks {
|
||||
source(files("src"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,7 @@ package ir.armor.tachidesk
|
||||
import ir.armor.tachidesk.server.JavalinSetup.javalinSetup
|
||||
import ir.armor.tachidesk.server.applicationSetup
|
||||
|
||||
class Main {
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
applicationSetup()
|
||||
javalinSetup()
|
||||
}
|
||||
}
|
||||
fun main() {
|
||||
applicationSetup()
|
||||
javalinSetup()
|
||||
}
|
||||
|
||||
@@ -22,102 +22,110 @@ 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.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
|
||||
object Chapter {
|
||||
/** get chapter list when showing a manga */
|
||||
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean): List<ChapterDataClass> {
|
||||
return if (!onlineFetch) {
|
||||
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean?): List<ChapterDataClass> {
|
||||
return if (onlineFetch == true) {
|
||||
getSourceChapters(mangaId)
|
||||
} else {
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }.orderBy(ChapterTable.chapterIndex to DESC)
|
||||
.map {
|
||||
ChapterTable.toDataClass(it)
|
||||
}
|
||||
}.ifEmpty {
|
||||
// If it was explicitly set to offline dont grab chapters
|
||||
if (onlineFetch == null) {
|
||||
getSourceChapters(mangaId)
|
||||
} else emptyList()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
val mangaDetails = getManga(mangaId)
|
||||
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
||||
val chapterList = source.fetchChapterList(
|
||||
SManga.create().apply {
|
||||
title = mangaDetails.title
|
||||
url = mangaDetails.url
|
||||
}
|
||||
).awaitSingle()
|
||||
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
|
||||
val mangaDetails = getManga(mangaId)
|
||||
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
||||
val chapterList = source.fetchChapterList(
|
||||
SManga.create().apply {
|
||||
title = mangaDetails.title
|
||||
url = mangaDetails.url
|
||||
}
|
||||
).awaitSingle()
|
||||
|
||||
val chapterCount = chapterList.count()
|
||||
val chapterCount = chapterList.count()
|
||||
|
||||
transaction {
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
||||
if (chapterEntry == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = fetchedChapter.url
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
transaction {
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
||||
if (chapterEntry == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = fetchedChapter.url
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear any orphaned chapters that are in the db but not in `chapterList`
|
||||
val dbChapterCount = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count() }
|
||||
if (dbChapterCount > chapterCount) { // we got some clean up due
|
||||
val dbChapterList = transaction { ChapterTable.select { ChapterTable.manga eq mangaId } }
|
||||
// clear any orphaned chapters that are in the db but not in `chapterList`
|
||||
val dbChapterCount = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count() }
|
||||
if (dbChapterCount > chapterCount) { // we got some clean up due
|
||||
val dbChapterList = transaction { ChapterTable.select { ChapterTable.manga eq mangaId } }
|
||||
|
||||
dbChapterList.forEach {
|
||||
if (it[ChapterTable.chapterIndex] >= chapterList.size ||
|
||||
chapterList[it[ChapterTable.chapterIndex] - 1].url != it[ChapterTable.url]
|
||||
) {
|
||||
transaction {
|
||||
PageTable.deleteWhere { PageTable.chapter eq it[ChapterTable.id] }
|
||||
ChapterTable.deleteWhere { ChapterTable.id eq it[ChapterTable.id] }
|
||||
}
|
||||
dbChapterList.forEach {
|
||||
if (it[ChapterTable.chapterIndex] >= chapterList.size ||
|
||||
chapterList[it[ChapterTable.chapterIndex] - 1].url != it[ChapterTable.url]
|
||||
) {
|
||||
transaction {
|
||||
PageTable.deleteWhere { PageTable.chapter eq it[ChapterTable.id] }
|
||||
ChapterTable.deleteWhere { ChapterTable.id eq it[ChapterTable.id] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dbChapterMap = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.associateBy({ it[ChapterTable.url] }, { it })
|
||||
}
|
||||
val dbChapterMap = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.associateBy({ it[ChapterTable.url] }, { it })
|
||||
}
|
||||
|
||||
return chapterList.mapIndexed { index, it ->
|
||||
return chapterList.mapIndexed { index, it ->
|
||||
|
||||
val dbChapter = dbChapterMap.getValue(it.url)
|
||||
val dbChapter = dbChapterMap.getValue(it.url)
|
||||
|
||||
ChapterDataClass(
|
||||
it.url,
|
||||
it.name,
|
||||
it.date_upload,
|
||||
it.chapter_number,
|
||||
it.scanlator,
|
||||
mangaId,
|
||||
ChapterDataClass(
|
||||
it.url,
|
||||
it.name,
|
||||
it.date_upload,
|
||||
it.chapter_number,
|
||||
it.scanlator,
|
||||
mangaId,
|
||||
|
||||
dbChapter[ChapterTable.isRead],
|
||||
dbChapter[ChapterTable.isBookmarked],
|
||||
dbChapter[ChapterTable.lastPageRead],
|
||||
dbChapter[ChapterTable.isRead],
|
||||
dbChapter[ChapterTable.isBookmarked],
|
||||
dbChapter[ChapterTable.lastPageRead],
|
||||
|
||||
chapterCount - index,
|
||||
)
|
||||
}
|
||||
chapterCount - index,
|
||||
chapterList.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +134,9 @@ object Chapter {
|
||||
val chapterEntry = transaction {
|
||||
ChapterTable.select {
|
||||
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||
}.firstOrNull()!!
|
||||
}.first()
|
||||
}
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val pageList = source.fetchPageList(
|
||||
@@ -139,7 +147,7 @@ object Chapter {
|
||||
).awaitSingle()
|
||||
|
||||
val chapterId = chapterEntry[ChapterTable.id].value
|
||||
val chapterCount = transaction { ChapterTable.selectAll().count() }
|
||||
val chapterCount = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count() }
|
||||
|
||||
// update page list for this chapter
|
||||
transaction {
|
||||
|
||||
@@ -159,7 +159,7 @@ object Extension {
|
||||
it[this.classFQName] = className
|
||||
}
|
||||
|
||||
val extensionId = ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!![ExtensionTable.id].value
|
||||
val extensionId = ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first()[ExtensionTable.id].value
|
||||
|
||||
sources.forEach { httpSource ->
|
||||
SourceTable.insert {
|
||||
@@ -195,7 +195,7 @@ object Extension {
|
||||
fun uninstallExtension(pkgName: String) {
|
||||
logger.debug("Uninstalling $pkgName")
|
||||
|
||||
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! }
|
||||
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() }
|
||||
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
|
||||
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
||||
transaction {
|
||||
@@ -234,7 +234,7 @@ object Extension {
|
||||
}
|
||||
|
||||
suspend fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
|
||||
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl]
|
||||
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.first() }[ExtensionTable.iconUrl]
|
||||
|
||||
val saveDir = "${applicationDirs.extensionsRoot}/icon"
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ object Manga {
|
||||
}
|
||||
|
||||
suspend fun getManga(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
|
||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
return if (mangaEntry[MangaTable.initialized] && !onlineFetch) {
|
||||
MangaDataClass(
|
||||
@@ -78,14 +78,14 @@ object Manga {
|
||||
it[MangaTable.description] = truncate(fetchedManga.description, 4096)
|
||||
it[MangaTable.genre] = fetchedManga.genre
|
||||
it[MangaTable.status] = fetchedManga.status
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||
}
|
||||
}
|
||||
|
||||
clearMangaThumbnail(mangaId)
|
||||
|
||||
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
MangaDataClass(
|
||||
mangaId,
|
||||
@@ -117,7 +117,7 @@ object Manga {
|
||||
return getCachedImageResponse(saveDir, fileName) {
|
||||
getManga(mangaId) // make sure is initialized
|
||||
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
val source = getHttpSource(sourceId)
|
||||
@@ -130,7 +130,7 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearMangaThumbnail(mangaId: Int) {
|
||||
private fun clearMangaThumbnail(mangaId: Int) {
|
||||
val saveDir = applicationDirs.thumbnailsRoot
|
||||
val fileName = mangaId.toString()
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ object MangaList {
|
||||
val mangasPage = this
|
||||
val mangaList = transaction {
|
||||
return@transaction mangasPage.mangas.map { manga ->
|
||||
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
||||
val mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
||||
if (mangaEntry == null) { // create manga entry
|
||||
val mangaId = MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
|
||||
@@ -40,16 +40,16 @@ object Page {
|
||||
}
|
||||
|
||||
suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int): Pair<InputStream, String> {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val chapterEntry = transaction {
|
||||
ChapterTable.select {
|
||||
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||
}.firstOrNull()!!
|
||||
}.first()
|
||||
}
|
||||
val chapterId = chapterEntry[ChapterTable.id].value
|
||||
|
||||
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.firstOrNull()!! }
|
||||
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.first() }
|
||||
|
||||
val tachiPage = Page(
|
||||
pageEntry[PageTable.index],
|
||||
@@ -78,11 +78,11 @@ object Page {
|
||||
// TODO: rewrite this to match tachiyomi
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
fun getChapterDir(mangaId: Int, chapterId: Int): String {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
val source = getHttpSource(sourceId)
|
||||
val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! }
|
||||
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
|
||||
val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.first() }
|
||||
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() }
|
||||
|
||||
val chapterDir = when {
|
||||
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
||||
|
||||
@@ -198,7 +198,7 @@ object LegacyBackupImport : LegacyBackupBase() {
|
||||
it[description] = fetchedManga.description
|
||||
it[genre] = fetchedManga.genre
|
||||
it[status] = fetchedManga.status
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ package ir.armor.tachidesk.impl.util
|
||||
import okhttp3.Response
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
@@ -19,12 +18,13 @@ import java.nio.file.Paths
|
||||
|
||||
object CachedImageResponse {
|
||||
private fun pathToInputStream(path: String): InputStream {
|
||||
return BufferedInputStream(FileInputStream(path))
|
||||
return FileInputStream(path).buffered()
|
||||
}
|
||||
|
||||
private fun findFileNameStartingWith(directoryPath: String, fileName: String): String? {
|
||||
File(directoryPath).listFiles().forEach { file ->
|
||||
if (file.name.startsWith("$fileName."))
|
||||
val target = "$fileName."
|
||||
File(directoryPath).listFiles().orEmpty().forEach { file ->
|
||||
if (file.name.startsWith(target))
|
||||
return "$directoryPath/${file.name}"
|
||||
}
|
||||
return null
|
||||
@@ -56,16 +56,13 @@ object CachedImageResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(
|
||||
pathToInputStream(fullPath),
|
||||
contentType
|
||||
)
|
||||
return pathToInputStream(fullPath) to contentType
|
||||
} else {
|
||||
throw Exception("request error! ${response.code}")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearCachedImage(saveDir: String, fileName: String) {
|
||||
fun clearCachedImage(saveDir: String, fileName: String) {
|
||||
val cachedFile = findFileNameStartingWith(saveDir, fileName)
|
||||
cachedFile?.also {
|
||||
File(it).delete()
|
||||
|
||||
@@ -32,12 +32,12 @@ object GetHttpSource {
|
||||
}
|
||||
|
||||
val sourceRecord = transaction {
|
||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
|
||||
SourceTable.select { SourceTable.id eq sourceId }.first()
|
||||
}
|
||||
|
||||
val extensionId = sourceRecord[SourceTable.extension]
|
||||
val extensionRecord = transaction {
|
||||
ExtensionTable.select { ExtensionTable.id eq extensionId }.firstOrNull()!!
|
||||
ExtensionTable.select { ExtensionTable.id eq extensionId }.first()
|
||||
}
|
||||
|
||||
val apkName = extensionRecord[ExtensionTable.apkName]
|
||||
|
||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
@@ -26,7 +27,9 @@ suspend fun Call.await(): Response {
|
||||
return
|
||||
}
|
||||
|
||||
continuation.resume(response)
|
||||
continuation.resume(response) {
|
||||
response.body?.closeQuietly()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
|
||||
@@ -71,12 +71,14 @@ object PackageTools {
|
||||
if (handler.hasException()) {
|
||||
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
||||
logger.error(
|
||||
"Detail Error Information in File $errorFile\n" +
|
||||
"Please report this file to one of following link if possible (any one).\n" +
|
||||
" https://sourceforge.net/p/dex2jar/tickets/\n" +
|
||||
" https://bitbucket.org/pxb1988/dex2jar/issues\n" +
|
||||
" https://github.com/pxb1988/dex2jar/issues\n" +
|
||||
" dex2jar@googlegroups.com"
|
||||
"""
|
||||
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>())
|
||||
}
|
||||
@@ -98,18 +100,21 @@ object PackageTools {
|
||||
applicationInfo.metaData = Bundle().apply {
|
||||
val appTag = doc.getElementsByTagName("application").item(0)
|
||||
|
||||
appTag?.childNodes?.toList()?.filter {
|
||||
it.nodeType == Node.ELEMENT_NODE
|
||||
}?.map {
|
||||
it as Element
|
||||
}?.filter {
|
||||
it.tagName == "meta-data"
|
||||
}?.map {
|
||||
putString(
|
||||
it.attributes.getNamedItem("android:name").nodeValue,
|
||||
it.attributes.getNamedItem("android:value").nodeValue
|
||||
)
|
||||
}
|
||||
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 = (
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
|
||||
object DBMangaer {
|
||||
object DBManager {
|
||||
val db by lazy {
|
||||
val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
|
||||
@@ -24,7 +24,7 @@ object DBMangaer {
|
||||
|
||||
fun databaseUp() {
|
||||
// must mention db object so the lazy block executes
|
||||
val db = DBMangaer.db
|
||||
val db = DBManager.db
|
||||
db.useNestedTransactions = true
|
||||
|
||||
val migrations = loadMigrationsFrom("ir.armor.tachidesk.model.database.migration")
|
||||
+87
-70
@@ -14,104 +14,121 @@ import org.jetbrains.exposed.dao.id.IntIdTable
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
@Suppress("ClassName", "unused")
|
||||
class M0001_Initial : Migration() {
|
||||
private object ExtensionTable : IntIdTable() {
|
||||
val apkName = varchar("apk_name", 1024)
|
||||
private class ExtensionTable : IntIdTable() {
|
||||
init {
|
||||
varchar("apk_name", 1024)
|
||||
// default is the local source icon from tachiyomi
|
||||
varchar("icon_url", 2048)
|
||||
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
|
||||
varchar("name", 128)
|
||||
varchar("pkg_name", 128)
|
||||
varchar("version_name", 16)
|
||||
integer("version_code")
|
||||
varchar("lang", 10)
|
||||
bool("is_nsfw")
|
||||
|
||||
// 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")
|
||||
bool("is_installed").default(false)
|
||||
bool("has_update").default(false)
|
||||
bool("is_obsolete").default(false)
|
||||
|
||||
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
|
||||
varchar("class_name", 1024).default("") // fully qualified name
|
||||
}
|
||||
}
|
||||
|
||||
private object SourceTable : IdTable<Long>() {
|
||||
private class SourceTable(extensionTable: ExtensionTable) : IdTable<Long>() {
|
||||
override val id = long("id").entityId()
|
||||
val name = varchar("name", 128)
|
||||
val lang = varchar("lang", 10)
|
||||
val extension = reference("extension", ExtensionTable)
|
||||
val partOfFactorySource = bool("part_of_factory_source").default(false)
|
||||
init {
|
||||
varchar("name", 128)
|
||||
varchar("lang", 10)
|
||||
reference("extension", extensionTable)
|
||||
bool("part_of_factory_source").default(false)
|
||||
}
|
||||
}
|
||||
|
||||
private object MangaTable : IntIdTable() {
|
||||
val url = varchar("url", 2048)
|
||||
val title = varchar("title", 512)
|
||||
val initialized = bool("initialized").default(false)
|
||||
private class MangaTable : IntIdTable() {
|
||||
init {
|
||||
varchar("url", 2048)
|
||||
varchar("title", 512)
|
||||
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()
|
||||
varchar("artist", 64).nullable()
|
||||
varchar("author", 64).nullable()
|
||||
varchar("description", 4096).nullable()
|
||||
varchar("genre", 1024).nullable()
|
||||
|
||||
// val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN)
|
||||
val status = integer("status").default(SManga.UNKNOWN)
|
||||
val thumbnail_url = varchar("thumbnail_url", 2048).nullable()
|
||||
// val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN)
|
||||
integer("status").default(SManga.UNKNOWN)
|
||||
varchar("thumbnail_url", 2048).nullable()
|
||||
|
||||
val inLibrary = bool("in_library").default(false)
|
||||
val defaultCategory = bool("default_category").default(true)
|
||||
bool("in_library").default(false)
|
||||
bool("default_category").default(true)
|
||||
|
||||
// source is used by some ancestor of IntIdTable
|
||||
val sourceReference = long("source")
|
||||
// source is used by some ancestor of IntIdTable
|
||||
long("source")
|
||||
}
|
||||
}
|
||||
|
||||
private object ChapterTable : IntIdTable() {
|
||||
val url = varchar("url", 2048)
|
||||
val name = varchar("name", 512)
|
||||
val date_upload = long("date_upload").default(0)
|
||||
val chapter_number = float("chapter_number").default(-1f)
|
||||
val scanlator = varchar("scanlator", 128).nullable()
|
||||
private class ChapterTable(mangaTable: MangaTable) : IntIdTable() {
|
||||
init {
|
||||
varchar("url", 2048)
|
||||
varchar("name", 512)
|
||||
long("date_upload").default(0)
|
||||
float("chapter_number").default(-1f)
|
||||
varchar("scanlator", 128).nullable()
|
||||
|
||||
val isRead = bool("read").default(false)
|
||||
val isBookmarked = bool("bookmark").default(false)
|
||||
val lastPageRead = integer("last_page_read").default(0)
|
||||
bool("read").default(false)
|
||||
bool("bookmark").default(false)
|
||||
integer("last_page_read").default(0)
|
||||
|
||||
val chapterIndex = integer("number_in_list")
|
||||
|
||||
val manga = reference("manga", MangaTable)
|
||||
integer("number_in_list")
|
||||
reference("manga", mangaTable)
|
||||
}
|
||||
}
|
||||
|
||||
private object PageTable : IntIdTable() {
|
||||
val index = integer("index")
|
||||
val url = varchar("url", 2048)
|
||||
val imageUrl = varchar("imageUrl", 2048).nullable()
|
||||
|
||||
val chapter = reference("chapter", ChapterTable)
|
||||
private class PageTable(chapterTable: ChapterTable) : IntIdTable() {
|
||||
init {
|
||||
integer("index")
|
||||
varchar("url", 2048)
|
||||
varchar("imageUrl", 2048).nullable()
|
||||
reference("chapter", chapterTable)
|
||||
}
|
||||
}
|
||||
|
||||
private object CategoryTable : IntIdTable() {
|
||||
val name = varchar("name", 64)
|
||||
val isLanding = bool("is_landing").default(false)
|
||||
val order = integer("order").default(0)
|
||||
private class CategoryTable : IntIdTable() {
|
||||
init {
|
||||
varchar("name", 64)
|
||||
bool("is_landing").default(false)
|
||||
integer("order").default(0)
|
||||
}
|
||||
}
|
||||
|
||||
private object CategoryMangaTable : IntIdTable() {
|
||||
val category = reference("category", ir.armor.tachidesk.model.database.table.CategoryTable)
|
||||
val manga = reference("manga", ir.armor.tachidesk.model.database.table.MangaTable)
|
||||
private class CategoryMangaTable : IntIdTable() {
|
||||
init {
|
||||
reference("category", ir.armor.tachidesk.model.database.table.CategoryTable)
|
||||
reference("manga", ir.armor.tachidesk.model.database.table.MangaTable)
|
||||
}
|
||||
}
|
||||
|
||||
/** initial migration, create all tables */
|
||||
override fun run() {
|
||||
transaction {
|
||||
val extensionTable = ExtensionTable()
|
||||
val sourceTable = SourceTable(extensionTable)
|
||||
val mangaTable = MangaTable()
|
||||
val chapterTable = ChapterTable(mangaTable)
|
||||
val pageTable = PageTable(chapterTable)
|
||||
val categoryTable = CategoryTable()
|
||||
val categoryMangaTable = CategoryMangaTable()
|
||||
SchemaUtils.create(
|
||||
ExtensionTable,
|
||||
ExtensionTable,
|
||||
SourceTable,
|
||||
MangaTable,
|
||||
ChapterTable,
|
||||
PageTable,
|
||||
CategoryTable,
|
||||
CategoryMangaTable,
|
||||
extensionTable,
|
||||
sourceTable,
|
||||
mangaTable,
|
||||
chapterTable,
|
||||
pageTable,
|
||||
categoryTable,
|
||||
categoryMangaTable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ import ir.armor.tachidesk.model.database.migration.lib.Migration
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import org.jetbrains.exposed.sql.vendors.currentDialect
|
||||
|
||||
@Suppress("ClassName", "unused")
|
||||
class M0002_ChapterTableIndexRename : Migration() {
|
||||
/** this migration renamed ChapterTable.NUMBER_IN_LIST to ChapterTable.INDEX */
|
||||
override fun run() {
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ fun runMigrations(migrations: List<Migration>, database: Database = TransactionM
|
||||
logger.info { "Migrations finished successfully" }
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
fun loadMigrationsFrom(classPath: String): List<Migration> {
|
||||
return ClassPath.from(Thread.currentThread().contextClassLoader)
|
||||
.getTopLevelClasses(classPath)
|
||||
|
||||
@@ -18,8 +18,8 @@ object CategoryTable : IntIdTable() {
|
||||
}
|
||||
|
||||
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
|
||||
categoryEntry[CategoryTable.id].value,
|
||||
categoryEntry[CategoryTable.order],
|
||||
categoryEntry[CategoryTable.name],
|
||||
categoryEntry[CategoryTable.isLanding],
|
||||
categoryEntry[this.id].value,
|
||||
categoryEntry[this.order],
|
||||
categoryEntry[this.name],
|
||||
categoryEntry[this.isLanding],
|
||||
)
|
||||
|
||||
@@ -30,14 +30,14 @@ object ChapterTable : IntIdTable() {
|
||||
|
||||
fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
|
||||
ChapterDataClass(
|
||||
chapterEntry[ChapterTable.url],
|
||||
chapterEntry[ChapterTable.name],
|
||||
chapterEntry[ChapterTable.date_upload],
|
||||
chapterEntry[ChapterTable.chapter_number],
|
||||
chapterEntry[ChapterTable.scanlator],
|
||||
chapterEntry[ChapterTable.manga].value,
|
||||
chapterEntry[ChapterTable.isRead],
|
||||
chapterEntry[ChapterTable.isBookmarked],
|
||||
chapterEntry[ChapterTable.lastPageRead],
|
||||
chapterEntry[ChapterTable.chapterIndex],
|
||||
chapterEntry[this.url],
|
||||
chapterEntry[this.name],
|
||||
chapterEntry[this.date_upload],
|
||||
chapterEntry[this.chapter_number],
|
||||
chapterEntry[this.scanlator],
|
||||
chapterEntry[this.manga].value,
|
||||
chapterEntry[this.isRead],
|
||||
chapterEntry[this.isBookmarked],
|
||||
chapterEntry[this.lastPageRead],
|
||||
chapterEntry[this.chapterIndex],
|
||||
)
|
||||
|
||||
@@ -36,21 +36,21 @@ object MangaTable : IntIdTable() {
|
||||
|
||||
fun MangaTable.toDataClass(mangaEntry: ResultRow) =
|
||||
MangaDataClass(
|
||||
mangaEntry[MangaTable.id].value,
|
||||
mangaEntry[MangaTable.sourceReference].toString(),
|
||||
mangaEntry[this.id].value,
|
||||
mangaEntry[this.sourceReference].toString(),
|
||||
|
||||
mangaEntry[MangaTable.url],
|
||||
mangaEntry[MangaTable.title],
|
||||
proxyThumbnailUrl(mangaEntry[MangaTable.id].value),
|
||||
mangaEntry[this.url],
|
||||
mangaEntry[this.title],
|
||||
proxyThumbnailUrl(mangaEntry[this.id].value),
|
||||
|
||||
mangaEntry[MangaTable.initialized],
|
||||
mangaEntry[this.initialized],
|
||||
|
||||
mangaEntry[MangaTable.artist],
|
||||
mangaEntry[MangaTable.author],
|
||||
mangaEntry[MangaTable.description],
|
||||
mangaEntry[MangaTable.genre],
|
||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
mangaEntry[MangaTable.inLibrary]
|
||||
mangaEntry[this.artist],
|
||||
mangaEntry[this.author],
|
||||
mangaEntry[this.description],
|
||||
mangaEntry[this.genre],
|
||||
MangaStatus.valueOf(mangaEntry[this.status]).name,
|
||||
mangaEntry[this.inLibrary]
|
||||
)
|
||||
|
||||
enum class MangaStatus(val status: Int) {
|
||||
|
||||
@@ -25,7 +25,7 @@ data class ChapterDataClass(
|
||||
val lastPageRead: Int,
|
||||
|
||||
/** this chapter's index, starts with 1 */
|
||||
val index: Int? = null,
|
||||
val index: Int,
|
||||
|
||||
/** total chapter count, used to calculate if there's a next and prev chapter */
|
||||
val chapterCount: Int? = null,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ir.armor.tachidesk.server
|
||||
|
||||
import io.javalin.Javalin
|
||||
import ir.armor.tachidesk.Main
|
||||
import ir.armor.tachidesk.impl.Category.createCategory
|
||||
import ir.armor.tachidesk.impl.Category.getCategoryList
|
||||
import ir.armor.tachidesk.impl.Category.removeCategory
|
||||
@@ -56,7 +55,8 @@ import kotlin.concurrent.thread
|
||||
|
||||
object JavalinSetup {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
private fun <T> future(block: suspend CoroutineScope.() -> T): CompletableFuture<T> {
|
||||
return scope.future(block = block)
|
||||
@@ -68,7 +68,7 @@ object JavalinSetup {
|
||||
val app = Javalin.create { config ->
|
||||
try {
|
||||
// if the bellow line throws an exception then webUI is not bundled
|
||||
Main::class.java.getResource("/react/index.html")
|
||||
this::class.java.getResource("/react/index.html")
|
||||
|
||||
// no exception so we can tell javalin to serve webUI
|
||||
hasWebUiBundled = true
|
||||
@@ -96,7 +96,10 @@ object JavalinSetup {
|
||||
logger.error("NullPointerException while handling the request", e)
|
||||
ctx.status(404)
|
||||
}
|
||||
|
||||
app.exception(NoSuchElementException::class.java) { e, ctx ->
|
||||
logger.error("NoSuchElementException while handling the request", e)
|
||||
ctx.status(404)
|
||||
}
|
||||
app.exception(IOException::class.java) { e, ctx ->
|
||||
logger.error("IOException while handling the request", e)
|
||||
ctx.status(500)
|
||||
@@ -257,7 +260,7 @@ object JavalinSetup {
|
||||
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
|
||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
|
||||
|
||||
ctx.json(future { getChapterList(mangaId, onlineFetch) })
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class ServerConfig(config: Config) : ConfigModule(config) {
|
||||
val socksProxyPort: String by config
|
||||
|
||||
// misc
|
||||
val debugLogsEnabled: Boolean by config
|
||||
val debugLogsEnabled: Boolean = System.getProperty("ir.armor.tachidesk.debugLogsEnabled", config.getString("debugLogsEnabled")).toBoolean()
|
||||
val systemTrayEnabled: Boolean by config
|
||||
val initialOpenInBrowserEnabled: Boolean by config
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ package ir.armor.tachidesk.server
|
||||
|
||||
import ch.qos.logback.classic.Level
|
||||
import eu.kanade.tachiyomi.App
|
||||
import ir.armor.tachidesk.Main
|
||||
import ir.armor.tachidesk.model.database.databaseUp
|
||||
import ir.armor.tachidesk.server.util.systemTray
|
||||
import mu.KotlinLogging
|
||||
@@ -42,6 +41,8 @@ val systemTray by lazy { systemTray() }
|
||||
val androidCompat by lazy { AndroidCompat() }
|
||||
|
||||
fun applicationSetup() {
|
||||
logger.info("Running Tachidesk ${BuildConfig.version} revision ${BuildConfig.revision}")
|
||||
|
||||
// Application dirs
|
||||
val applicationDirs = ApplicationDirs()
|
||||
DI.global.addImport(
|
||||
@@ -81,7 +82,7 @@ fun applicationSetup() {
|
||||
try {
|
||||
val dataConfFile = File("${applicationDirs.dataRoot}/server.conf")
|
||||
if (!dataConfFile.exists()) {
|
||||
Main::class.java.getResourceAsStream("/server-reference.conf").use { input ->
|
||||
JavalinSetup::class.java.getResourceAsStream("/server-reference.conf").use { input ->
|
||||
dataConfFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
@@ -97,7 +98,7 @@ fun applicationSetup() {
|
||||
if (serverConfig.systemTrayEnabled) {
|
||||
try {
|
||||
systemTray
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ import dorkbox.systemTray.SystemTray
|
||||
import dorkbox.systemTray.SystemTray.TrayType
|
||||
import dorkbox.util.CacheUtil
|
||||
import dorkbox.util.Desktop
|
||||
import ir.armor.tachidesk.Main
|
||||
import ir.armor.tachidesk.server.BuildConfig
|
||||
import ir.armor.tachidesk.server.ServerConfig
|
||||
import ir.armor.tachidesk.server.serverConfig
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun openInBrowser() {
|
||||
try {
|
||||
Desktop.browseURL("http://127.0.0.1:4567")
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -45,11 +45,11 @@ fun systemTray(): SystemTray? {
|
||||
}
|
||||
)
|
||||
|
||||
val icon = Main::class.java.getResource("/icon/faviconlogo.png")
|
||||
val icon = ServerConfig::class.java.getResource("/icon/faviconlogo.png")
|
||||
|
||||
// systemTray.setTooltip("Tachidesk")
|
||||
// systemTray.setTooltip("Tachidesk")
|
||||
systemTray.setImage(icon)
|
||||
// systemTray.status = "No Mail"
|
||||
// systemTray.status = "No Mail"
|
||||
|
||||
mainMenu.add(
|
||||
MenuItem("Quit") {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Container } from '@material-ui/core';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
|
||||
|
||||
import NavBar from './components/NavBar';
|
||||
import NavBar from './components/navbar/NavBar';
|
||||
import Sources from './screens/Sources';
|
||||
import Extensions from './screens/Extensions';
|
||||
import SourceMangas from './screens/SourceMangas';
|
||||
|
||||
@@ -18,6 +18,7 @@ import FilterListIcon from '@material-ui/icons/FilterList';
|
||||
import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import { langCodeToName } from '../util/language';
|
||||
import cloneObject from '../util/cloneObject';
|
||||
|
||||
const useStyles = makeStyles(() => createStyles({
|
||||
paper: {
|
||||
@@ -54,7 +55,7 @@ export default function ExtensionLangSelect(props: IProps) {
|
||||
if (checked) {
|
||||
setMShownLangs([...mShownLangs, lang]);
|
||||
} else {
|
||||
const clone = JSON.parse(JSON.stringify(mShownLangs));
|
||||
const clone = cloneObject(mShownLangs);
|
||||
clone.splice(clone.indexOf(lang), 1);
|
||||
setMShownLangs(clone);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/*
|
||||
* 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 ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import Slide, { SlideProps } from '@material-ui/core/Slide';
|
||||
|
||||
+3
-3
@@ -12,9 +12,9 @@ import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import NavBarContext from '../context/NavbarContext';
|
||||
import DarkTheme from '../context/DarkTheme';
|
||||
import TemporaryDrawer from './TemporaryDrawer';
|
||||
import NavBarContext from '../../context/NavbarContext';
|
||||
import DarkTheme from '../../context/DarkTheme';
|
||||
import TemporaryDrawer from '../TemporaryDrawer';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
+14
-3
@@ -30,8 +30,8 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
|
||||
import DarkTheme from '../context/DarkTheme';
|
||||
import NavBarContext from '../context/NavbarContext';
|
||||
import DarkTheme from '../../context/DarkTheme';
|
||||
import NavBarContext from '../../context/NavbarContext';
|
||||
|
||||
const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
|
||||
// main container and root div need to change classes...
|
||||
@@ -46,7 +46,7 @@ const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
|
||||
position: settings.staticNav ? 'sticky' : 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
minWidth: '300px',
|
||||
width: '300px',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: '#0a0b0b',
|
||||
@@ -143,6 +143,7 @@ export const defaultReaderSettings = () => ({
|
||||
staticNav: false,
|
||||
showPageNumber: true,
|
||||
continuesPageGap: false,
|
||||
loadNextonEnding: false,
|
||||
readerType: 'ContinuesVertical',
|
||||
} as IReaderSettings);
|
||||
|
||||
@@ -277,6 +278,16 @@ export default function ReaderNavBar(props: IProps) {
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Load next chapter at ending" />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={settings.loadNextonEnding}
|
||||
onChange={(e) => setSettingValue('loadNextonEnding', e.target.checked)}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Reader Type" />
|
||||
<Select
|
||||
@@ -60,11 +60,13 @@ function LazyImage(props: IProps) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
if (settings.readerType === 'Webtoon' || settings.readerType === 'ContinuesVertical') {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
} return () => {};
|
||||
}, [handleScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -92,14 +94,14 @@ function LazyImage(props: IProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function Page(props: IProps) {
|
||||
const Page = React.forwardRef((props: IProps, ref: any) => {
|
||||
const {
|
||||
src, index, setCurPage, settings,
|
||||
} = props;
|
||||
const classes = useStyles(settings)();
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto' }}>
|
||||
<div ref={ref} style={{ margin: '0 auto' }}>
|
||||
<LazyImage
|
||||
src={src}
|
||||
index={index}
|
||||
@@ -108,4 +110,6 @@ export default function Page(props: IProps) {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import Page from './Page';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
reader: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
export default function VerticalReader(props: IReaderProps) {
|
||||
const { pages, settings, setCurPage } = props;
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.reader}>
|
||||
{
|
||||
pages.map((page) => (
|
||||
<Page
|
||||
key={page.index}
|
||||
index={page.index}
|
||||
src={page.src}
|
||||
setCurPage={setCurPage}
|
||||
settings={settings}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+9
-2
@@ -1,7 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import Page from './Page';
|
||||
import Page from '../Page';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
reader: {
|
||||
@@ -21,7 +28,7 @@ interface IProps {
|
||||
settings: IReaderSettings
|
||||
}
|
||||
|
||||
export default function HorizontalReader(props: IProps) {
|
||||
export default function HorizontalPager(props: IProps) {
|
||||
const { pages, settings, setCurPage } = props;
|
||||
|
||||
const classes = useStyles();
|
||||
+23
-8
@@ -1,7 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useEffect } from 'react';
|
||||
import Page from './Page';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Page from '../Page';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
reader: {
|
||||
@@ -16,13 +24,20 @@ const useStyles = makeStyles({
|
||||
|
||||
export default function PagedReader(props: IReaderProps) {
|
||||
const {
|
||||
pages, settings, setCurPage, curPage,
|
||||
pages, settings, setCurPage, curPage, manga, chapter, nextChapter,
|
||||
} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function nextPage() {
|
||||
if (curPage < pages.length - 1) { setCurPage(curPage + 1); }
|
||||
if (curPage < pages.length - 1) {
|
||||
setCurPage(curPage + 1);
|
||||
} else if (settings.loadNextonEnding) {
|
||||
nextChapter();
|
||||
}
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
@@ -52,16 +67,16 @@ export default function PagedReader(props: IReaderProps) {
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keyup', keyboardControl, false);
|
||||
document.addEventListener('click', clickControl);
|
||||
pageRef.current?.addEventListener('click', clickControl);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keyup', keyboardControl);
|
||||
document.removeEventListener('click', clickControl);
|
||||
pageRef.current?.removeEventListener('click', clickControl);
|
||||
};
|
||||
}, [curPage]);
|
||||
}, [curPage, pageRef]);
|
||||
|
||||
return (
|
||||
<div className={classes.reader}>
|
||||
<div ref={pageRef} className={classes.reader}>
|
||||
<Page
|
||||
key={curPage}
|
||||
index={curPage}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Page from '../Page';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
reader: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
export default function VerticalReader(props: IReaderProps) {
|
||||
const {
|
||||
pages, settings, setCurPage, curPage, manga, chapter, nextChapter,
|
||||
} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const [initialScroll, setInitialScroll] = useState(-1);
|
||||
const initialPageRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleLoadNextonEnding = () => {
|
||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
|
||||
nextChapter();
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (settings.loadNextonEnding) { window.addEventListener('scroll', handleLoadNextonEnding); }
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleLoadNextonEnding);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if ((chapter as IChapter).lastPageRead > -1) {
|
||||
setInitialScroll((chapter as IChapter).lastPageRead);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialScroll > -1) {
|
||||
initialPageRef.current?.scrollIntoView();
|
||||
}
|
||||
}, [initialScroll, initialPageRef.current]);
|
||||
|
||||
return (
|
||||
<div className={classes.reader}>
|
||||
{
|
||||
pages.map((page) => (
|
||||
<Page
|
||||
key={page.index}
|
||||
index={page.index}
|
||||
src={page.src}
|
||||
setCurPage={setCurPage}
|
||||
settings={settings}
|
||||
ref={page.index === initialScroll ? initialPageRef : null}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||
import MangaGrid from '../components/MangaGrid';
|
||||
import NavbarContext from '../context/NavbarContext';
|
||||
import client from '../util/client';
|
||||
import cloneObject from '../util/cloneObject';
|
||||
|
||||
interface IMangaCategory {
|
||||
category: ICategory
|
||||
@@ -98,7 +99,7 @@ export default function Library() {
|
||||
client.get(`/api/v1/category/${tab.category.id}`)
|
||||
.then((response) => response.data)
|
||||
.then((data: IManga[]) => {
|
||||
const tabsClone = JSON.parse(JSON.stringify(tabs));
|
||||
const tabsClone = cloneObject(tabs);
|
||||
tabsClone[index].mangas = data;
|
||||
tabsClone[index].isFetched = true;
|
||||
|
||||
|
||||
@@ -44,10 +44,6 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// const InnerItem = React.memo(({ chapters, index }: any) => (
|
||||
// <ChapterCard chapter={chapters[index]} />
|
||||
// ));
|
||||
|
||||
export default function Manga() {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
@@ -60,6 +56,7 @@ export default function Manga() {
|
||||
const [manga, setManga] = useState<IManga>();
|
||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||
const [fetchedChapters, setFetchedChapters] = useState(false);
|
||||
const [noChaptersFound, setNoChaptersFound] = useState(false);
|
||||
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
|
||||
|
||||
function triggerChaptersUpdate() {
|
||||
@@ -84,20 +81,13 @@ export default function Manga() {
|
||||
.then((data) => {
|
||||
if (data.length === 0 && fetchedChapters) {
|
||||
makeToast('No chapters found', 'warning');
|
||||
setNoChaptersFound(true);
|
||||
}
|
||||
setChapters(data);
|
||||
})
|
||||
.then(() => setFetchedChapters(true));
|
||||
}, [chapters.length, fetchedChapters, chapterUpdateTriggerer]);
|
||||
|
||||
// const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
|
||||
const itemContent = (index:any) => (
|
||||
<ChapterCard
|
||||
chapter={chapters[index]}
|
||||
triggerChaptersUpdate={triggerChaptersUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<LoadingPlaceholder
|
||||
@@ -107,7 +97,7 @@ export default function Manga() {
|
||||
/>
|
||||
|
||||
<LoadingPlaceholder
|
||||
shouldRender={chapters.length > 0 || fetchedChapters}
|
||||
shouldRender={chapters.length > 0 || noChaptersFound}
|
||||
>
|
||||
<Virtuoso
|
||||
style={{ // override Virtuoso default values and set them with class
|
||||
@@ -115,7 +105,12 @@ export default function Manga() {
|
||||
}}
|
||||
className={classes.chapters}
|
||||
totalCount={chapters.length}
|
||||
itemContent={itemContent}
|
||||
itemContent={(index:number) => (
|
||||
<ChapterCard
|
||||
chapter={chapters[index]}
|
||||
triggerChaptersUpdate={triggerChaptersUpdate}
|
||||
/>
|
||||
)}
|
||||
useWindowScroll={window.innerWidth < 960}
|
||||
overscan={window.innerHeight * 0.5}
|
||||
/>
|
||||
|
||||
@@ -9,16 +9,17 @@
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import HorizontalReader from '../components/reader/HorizontalReader';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import HorizontalPager from '../components/reader/pager/HorizontalPager';
|
||||
import Page from '../components/reader/Page';
|
||||
import PageNumber from '../components/reader/PageNumber';
|
||||
import PagedReader from '../components/reader/PagedReader';
|
||||
import VerticalReader from '../components/reader/VerticalReader';
|
||||
import ReaderNavBar, { defaultReaderSettings } from '../components/ReaderNavBar';
|
||||
import WebtoonPager from '../components/reader/pager/PagedPager';
|
||||
import VerticalPager from '../components/reader/pager/VerticalPager';
|
||||
import ReaderNavBar, { defaultReaderSettings } from '../components/navbar/ReaderNavBar';
|
||||
import NavbarContext from '../context/NavbarContext';
|
||||
import client from '../util/client';
|
||||
import useLocalStorage from '../util/useLocalStorage';
|
||||
import cloneObject from '../util/cloneObject';
|
||||
|
||||
const useStyles = (settings: IReaderSettings) => makeStyles({
|
||||
root: {
|
||||
@@ -33,24 +34,24 @@ const useStyles = (settings: IReaderSettings) => makeStyles({
|
||||
const getReaderComponent = (readerType: ReaderType) => {
|
||||
switch (readerType) {
|
||||
case 'ContinuesVertical':
|
||||
return VerticalReader;
|
||||
return VerticalPager;
|
||||
break;
|
||||
case 'Webtoon':
|
||||
return VerticalReader;
|
||||
return VerticalPager;
|
||||
break;
|
||||
case 'SingleVertical':
|
||||
return PagedReader;
|
||||
return WebtoonPager;
|
||||
break;
|
||||
case 'SingleRTL':
|
||||
return PagedReader;
|
||||
return WebtoonPager;
|
||||
break;
|
||||
case 'SingleLTR':
|
||||
return PagedReader;
|
||||
return WebtoonPager;
|
||||
break;
|
||||
case 'ContinuesHorizontal':
|
||||
return HorizontalReader;
|
||||
return HorizontalPager;
|
||||
default:
|
||||
return VerticalReader;
|
||||
return VerticalPager;
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -62,6 +63,7 @@ export default function Reader() {
|
||||
const [settings, setSettings] = useLocalStorage<IReaderSettings>('readerSettings', defaultReaderSettings);
|
||||
|
||||
const classes = useStyles(settings)();
|
||||
const history = useHistory();
|
||||
|
||||
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||
|
||||
@@ -69,9 +71,22 @@ export default function Reader() {
|
||||
const [manga, setManga] = useState<IMangaCard | IManga>({ id: +mangaId, title: '', thumbnailUrl: '' });
|
||||
const [chapter, setChapter] = useState<IChapter | IPartialChpter>(initialChapter());
|
||||
const [curPage, setCurPage] = useState<number>(0);
|
||||
|
||||
const { setOverride, setTitle } = useContext(NavbarContext);
|
||||
useEffect(() => {
|
||||
// make sure settings has all the keys
|
||||
const settingsClone = cloneObject(settings) as any;
|
||||
const defualtSettings = defaultReaderSettings();
|
||||
let shouldUpdateSettings = false;
|
||||
Object.keys(defualtSettings).forEach((key) => {
|
||||
const keyOf = key as keyof IReaderSettings;
|
||||
if (settings[keyOf] === undefined) {
|
||||
settingsClone[keyOf] = defualtSettings[keyOf];
|
||||
shouldUpdateSettings = true;
|
||||
}
|
||||
});
|
||||
if (shouldUpdateSettings) { setSettings(settingsClone); }
|
||||
|
||||
// set the custom navbar
|
||||
setOverride(
|
||||
{
|
||||
status: true,
|
||||
@@ -107,9 +122,24 @@ export default function Reader() {
|
||||
.then((response) => response.data)
|
||||
.then((data:IChapter) => {
|
||||
setChapter(data);
|
||||
setCurPage(data.lastPageRead);
|
||||
});
|
||||
}, [chapterIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (curPage !== -1) {
|
||||
const formData = new FormData();
|
||||
formData.append('lastPageRead', curPage.toString());
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
|
||||
}
|
||||
|
||||
if (curPage === chapter.pageCount - 1) {
|
||||
const formDataRead = new FormData();
|
||||
formDataRead.append('read', 'true');
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formDataRead);
|
||||
}
|
||||
}, [curPage]);
|
||||
|
||||
if (chapter.pageCount === -1) {
|
||||
return (
|
||||
<div className={classes.loading}>
|
||||
@@ -118,6 +148,17 @@ export default function Reader() {
|
||||
);
|
||||
}
|
||||
|
||||
const nextChapter = () => {
|
||||
if (chapter.index < chapter.chapterCount) {
|
||||
const formData = new FormData();
|
||||
formData.append('lastPageRead', `${chapter.pageCount - 1}`);
|
||||
formData.append('read', 'true');
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
|
||||
|
||||
history.push(`/manga/${manga.id}/chapter/${chapter.index + 1}`);
|
||||
}
|
||||
};
|
||||
|
||||
const pages = range(chapter.pageCount).map((index) => ({
|
||||
index,
|
||||
src: `${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterIndex}/page/${index}`,
|
||||
@@ -138,6 +179,9 @@ export default function Reader() {
|
||||
setCurPage={setCurPage}
|
||||
curPage={curPage}
|
||||
settings={settings}
|
||||
manga={manga}
|
||||
chapter={chapter}
|
||||
nextChapter={nextChapter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Vendored
+4
@@ -99,6 +99,7 @@ type ReaderType =
|
||||
interface IReaderSettings{
|
||||
staticNav: boolean
|
||||
showPageNumber: boolean
|
||||
loadNextonEnding: boolean
|
||||
readerType: ReaderType
|
||||
}
|
||||
|
||||
@@ -113,4 +114,7 @@ interface IReaderProps {
|
||||
setCurPage: React.Dispatch<React.SetStateAction<number>>
|
||||
curPage: number
|
||||
settings: IReaderSettings
|
||||
manga: IMangaCard | IManga
|
||||
chapter: IChapter | IPartialChpter
|
||||
nextChapter: () => void
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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/. */
|
||||
|
||||
export default function cloneObject<T extends object>(obj: T) {
|
||||
return JSON.parse(JSON.stringify(obj)) as T;
|
||||
}
|
||||
Reference in New Issue
Block a user