Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a1e1e01dc | |||
| a567701639 | |||
| 1802271358 | |||
| 9e649eef79 | |||
| 1eb4a9c216 | |||
| e3f65d2192 | |||
| bb09cfddb3 | |||
| d383939c9f | |||
| 32dd543562 | |||
| 5a75f26791 | |||
| 95c437efd5 | |||
| ec877f632f | |||
| 8666cbf8bc | |||
| 84b0c26450 | |||
| 64e5bbabb3 | |||
| cc1a15e5ba | |||
| d29e942a72 | |||
| 8d86c88c38 | |||
| c7dc7421aa | |||
| 34ed3e5c68 | |||
| 1a4a8af384 | |||
| 62b1e99bbf | |||
| 1aa3b76934 | |||
| 3e53c50f64 | |||
| 430386bc84 | |||
| 30049e8152 | |||
| 34d9a7a233 | |||
| 183972475b | |||
| fd46727f8e | |||
| f6ce010aa2 | |||
| d0ff30df9f | |||
| 8e449abd67 | |||
| 2986130268 | |||
| 1c0c09f2f2 | |||
| 44100cb5b6 | |||
| cfc6e5cd2a | |||
| c067d14c2c | |||
| aded854a2b | |||
| e79d0b9dd2 | |||
| a0115d88b0 | |||
| 85ec2ed367 | |||
| bf908c4d17 | |||
| f41c5c9428 | |||
| 04837983fa | |||
| 5d484b012c | |||
| 436a8d0585 | |||
| 28cc0a6f84 | |||
| 26cc2f2c96 | |||
| 149107e749 | |||
| a74936c5f5 | |||
| ff8c8913d4 | |||
| 83426e1302 |
+26
-5
@@ -1,6 +1,27 @@
|
|||||||
#
|
* text=auto
|
||||||
# https://help.github.com/articles/dealing-with-line-endings/
|
* text eol=lf
|
||||||
#
|
|
||||||
# These are explicitly windows files and should use crlf
|
|
||||||
*.bat text eol=crlf
|
|
||||||
|
|
||||||
|
# Windows forced line-endings
|
||||||
|
/.idea/* text eol=crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.ps1 text eol=crlf
|
||||||
|
|
||||||
|
# Gradle wrapper
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
|
# Images
|
||||||
|
*.webp binary
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.swp binary
|
||||||
|
*.pdf binary
|
||||||
@@ -25,6 +25,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
|
|||||||
## Device information
|
## Device information
|
||||||
- Tachidesk version: (Example: v0.2.3-r255-win32)
|
- Tachidesk version: (Example: v0.2.3-r255-win32)
|
||||||
- Server Operating System: (Example: Ubuntu 20.04)
|
- Server Operating System: (Example: Ubuntu 20.04)
|
||||||
|
- Server Desktop Environment: N/A or (Example: Gnome 40)
|
||||||
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
|
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
|
||||||
- Client Operating System: <usually the same as above Server Operating System>
|
- Client Operating System: <usually the same as above Server Operating System>
|
||||||
- Client Web Browser: (Example: Google Chrome 89.0.4389.82)
|
- Client Web Browser: (Example: Google Chrome 89.0.4389.82)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "body",
|
"type": "body",
|
||||||
"regex": "(Tachidesk version|Server Operating System|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
|
"regex": "(Tachidesk version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
|
||||||
"message": "The requested information was not filled out"
|
"message": "The requested information was not filled out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package xyz.nulldev.ts.config
|
package xyz.nulldev.ts.config
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Android stub library
|
// Android stub library
|
||||||
// compileOnly( fileTree(File(rootProject.rootDir, "libs/android"), include: "*.jar")
|
|
||||||
implementation(fileTree("lib/"))
|
implementation(fileTree("lib/"))
|
||||||
implementation(fileTree("${rootProject.rootDir}/server/lib/dex2jar/"))
|
|
||||||
|
|
||||||
|
|
||||||
// Android JAR libs
|
// Android JAR libs
|
||||||
@@ -41,10 +39,10 @@ dependencies {
|
|||||||
compileOnly( group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
compileOnly( group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
implementation( project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
// dex2jar
|
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
|
||||||
// compileOnly( "dex2jar:dex-translator")
|
compileOnly("com.github.DexPatcher.dex2jar:dex-tools:v2.1-20190905-lanchon")
|
||||||
|
|
||||||
// APK parser
|
// APK parser
|
||||||
compileOnly("net.dongliu:apk-parser:2.6.10")
|
compileOnly("net.dongliu:apk-parser:2.6.10")
|
||||||
@@ -55,7 +53,11 @@ dependencies {
|
|||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly( "androidx.annotation:annotation:1.2.0-alpha01")
|
compileOnly( "androidx.annotation:annotation:1.2.0-alpha01")
|
||||||
|
|
||||||
// compileOnly("io.reactivex:rxjava:1.3.8")
|
// substitute for duktape-android
|
||||||
|
// 'org.mozilla:rhino' includes some code that we don't need so use 'org.mozilla:rhino-runtime' instead
|
||||||
|
implementation("org.mozilla:rhino-runtime:1.7.13")
|
||||||
|
// 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn
|
||||||
|
implementation("org.mozilla:rhino-engine:1.7.13")
|
||||||
}
|
}
|
||||||
|
|
||||||
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
# This is a windows only PowerShell script to create android.jar stubs
|
||||||
|
|
||||||
|
# foolproof against running from AndroidCompat dir instead of running from project root
|
||||||
|
if ($(Split-Path -Path (Get-Location) -Leaf) -eq "AndroidCompat" ) {
|
||||||
|
Set-Location ..
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Getting required Android.jar..."
|
||||||
|
Remove-Item -Recurse -Force "tmp" -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
New-Item -ItemType Directory -Force -Path "tmp" | Out-Null
|
||||||
|
|
||||||
|
$androidEncoded = (Invoke-WebRequest -Uri "https://android.googlesource.com/platform/prebuilts/sdk/+/3b8a524d25fa6c3d795afb1eece3f24870c60988/27/public/android.jar?format=TEXT").content
|
||||||
|
|
||||||
|
$android_jar = (Get-Location).Path + "\tmp\android.jar"
|
||||||
|
|
||||||
|
[IO.File]::WriteAllBytes($android_jar, [Convert]::FromBase64String($androidEncoded))
|
||||||
|
|
||||||
|
# We need to remove any stub classes that we have implementations for
|
||||||
|
Write-Output "Patching JAR..."
|
||||||
|
|
||||||
|
function Remove-Files-Zip($zipfile, $path)
|
||||||
|
{
|
||||||
|
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null
|
||||||
|
|
||||||
|
$stream = New-Object IO.FileStream($zipfile, [IO.FileMode]::Open)
|
||||||
|
$mode = [IO.Compression.ZipArchiveMode]::Update
|
||||||
|
$zip = New-Object IO.Compression.ZipArchive($stream, $mode)
|
||||||
|
|
||||||
|
($zip.Entries | Where-Object { $_.FullName -like $path }) | ForEach-Object { Write-Output "Deleting: $($_.FullName)"; $_.Delete() }
|
||||||
|
|
||||||
|
$zip.Dispose()
|
||||||
|
$stream.Close()
|
||||||
|
$stream.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Removing org.json..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/json/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.apache..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/apache/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.w3c..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/w3c/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.xml..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/xml/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.xmlpull..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/xmlpull/*'
|
||||||
|
|
||||||
|
Write-Output "Removing junit..."
|
||||||
|
Remove-Files-Zip $android_jar 'junit/*'
|
||||||
|
|
||||||
|
Write-Output "Removing javax..."
|
||||||
|
Remove-Files-Zip $android_jar 'javax/*'
|
||||||
|
|
||||||
|
Write-Output "Removing java..."
|
||||||
|
Remove-Files-Zip $android_jar 'java/*'
|
||||||
|
|
||||||
|
Write-Output "Removing overriden classes..."
|
||||||
|
Remove-Files-Zip $android_jar 'android/app/Application.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/app/Service.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/net/Uri.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/net/Uri$Builder.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/os/Environment.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/text/format/Formatter.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/text/Html.class'
|
||||||
|
|
||||||
|
function Dedupe($path)
|
||||||
|
{
|
||||||
|
Push-Location $path
|
||||||
|
$classes = Get-ChildItem . *.* -Recurse | Where-Object { !$_.PSIsContainer }
|
||||||
|
$classes | ForEach-Object {
|
||||||
|
"Processing class: $($_.FullName)"
|
||||||
|
Remove-Files-Zip $android_jar "$($_.Name).class" | Out-Null
|
||||||
|
Remove-Files-Zip $android_jar "$($_.Name)$*.class" | Out-Null
|
||||||
|
Remove-Files-Zip $android_jar "$($_.Name)Kt.class" | Out-Null
|
||||||
|
Remove-Files-Zip $android_jar "$($_.Name)Kt$*.class" | Out-Null
|
||||||
|
}
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Dedupe "AndroidCompat/src/main/java"
|
||||||
|
Dedupe "server/src/main/java"
|
||||||
|
Dedupe "server/src/main/kotlin"
|
||||||
|
|
||||||
|
Write-Output "Copying Android.jar to library folder..."
|
||||||
|
Move-Item -Force $android_jar "AndroidCompat/lib/android.jar"
|
||||||
|
|
||||||
|
Write-Output "Cleaning up..."
|
||||||
|
Remove-Item -Recurse -Force "tmp"
|
||||||
|
|
||||||
|
Write-Output "Done!"
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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/.
|
||||||
|
|
||||||
|
# This is a bash script to create android.jar stubs
|
||||||
|
|
||||||
# foolproof against running from AndroidCompat dir instead of running from project root
|
# foolproof against running from AndroidCompat dir instead of running from project root
|
||||||
if [ "$(basename $(pwd))" = "AndroidCompat" ]; then
|
if [ "$(basename $(pwd))" = "AndroidCompat" ]; then
|
||||||
cd ..
|
cd ..
|
||||||
@@ -13,7 +21,7 @@ pushd "tmp"
|
|||||||
|
|
||||||
curl "https://android.googlesource.com/platform/prebuilts/sdk/+/3b8a524d25fa6c3d795afb1eece3f24870c60988/27/public/android.jar?format=TEXT" | base64 --decode > android.jar
|
curl "https://android.googlesource.com/platform/prebuilts/sdk/+/3b8a524d25fa6c3d795afb1eece3f24870c60988/27/public/android.jar?format=TEXT" | base64 --decode > android.jar
|
||||||
|
|
||||||
# We need to remove any stub classes that we might use
|
# We need to remove any stub classes that we have implementations for
|
||||||
echo "Patching JAR..."
|
echo "Patching JAR..."
|
||||||
|
|
||||||
echo "Removing org.json..."
|
echo "Removing org.json..."
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Square, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.squareup.duktape;
|
package com.squareup.duktape;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 kotlin.NotImplementedError;
|
import kotlin.NotImplementedError;
|
||||||
|
|
||||||
import javax.script.ScriptEngine;
|
import javax.script.ScriptEngine;
|
||||||
@@ -22,11 +14,18 @@ import javax.script.ScriptEngineManager;
|
|||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
|
||||||
/** A simple EMCAScript (Javascript) interpreter. */
|
/* Note (March 2021):
|
||||||
|
* The old implementation for duktape-android used the nashorn engine which is deprecated.
|
||||||
|
* This new implementation uses Mozilla's Rhino: https://github.com/mozilla/rhino
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple EMCAScript (Javascript) interpreter.
|
||||||
|
*/
|
||||||
public final class Duktape implements Closeable, AutoCloseable {
|
public final class Duktape implements Closeable, AutoCloseable {
|
||||||
|
|
||||||
private ScriptEngineManager factory = new ScriptEngineManager();
|
private ScriptEngineManager factory = new ScriptEngineManager();
|
||||||
private ScriptEngine engine = factory.getEngineByName("JavaScript");
|
private ScriptEngine engine = factory.getEngineByName("rhino");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new interpreter instance. Calls to this method <strong>must</strong> matched with
|
* Create a new interpreter instance. Calls to this method <strong>must</strong> matched with
|
||||||
@@ -38,17 +37,6 @@ public final class Duktape implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private Duktape() {}
|
private Duktape() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluate {@code script} and return a result. {@code fileName} will be used in error
|
|
||||||
* reporting. Note that the result must be one of the supported Java types or the call will
|
|
||||||
* return null.
|
|
||||||
*
|
|
||||||
* @throws DuktapeException if there is an error evaluating the script.
|
|
||||||
*/
|
|
||||||
public synchronized Object evaluate(String script, String fileName) {
|
|
||||||
throw new NotImplementedError("Not implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate {@code script} and return a result. Note that the result must be one of the
|
* Evaluate {@code script} and return a result. Note that the result must be one of the
|
||||||
* supported Java types or the call will return null.
|
* supported Java types or the call will return null.
|
||||||
@@ -76,18 +64,18 @@ public final class Duktape implements Closeable, AutoCloseable {
|
|||||||
throw new NotImplementedError("Not implemented!");
|
throw new NotImplementedError("Not implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Attaches to a global JavaScript object called {@code name} that implements {@code type}.
|
// * Attaches to a global JavaScript object called {@code name} that implements {@code type}.
|
||||||
* {@code type} defines the interface implemented in JavaScript that will be accessible to Java.
|
// * {@code type} defines the interface implemented in JavaScript that will be accessible to Java.
|
||||||
* {@code type} must be an interface that does not extend any other interfaces, and cannot define
|
// * {@code type} must be an interface that does not extend any other interfaces, and cannot define
|
||||||
* any overloaded methods.
|
// * any overloaded methods.
|
||||||
* <p>Methods of the interface may return {@code void} or any of the following supported argument
|
// * <p>Methods of the interface may return {@code void} or any of the following supported argument
|
||||||
* types: {@code boolean}, {@link Boolean}, {@code int}, {@link Integer}, {@code double},
|
// * types: {@code boolean}, {@link Boolean}, {@code int}, {@link Integer}, {@code double},
|
||||||
* {@link Double}, {@link String}.
|
// * {@link Double}, {@link String}.
|
||||||
*/
|
// */
|
||||||
public synchronized <T> T get(final String name, final Class<T> type) {
|
// public synchronized <T> T get(final String name, final Class<T> type) {
|
||||||
throw new NotImplementedError("Not implemented!");
|
// throw new NotImplementedError("Not implemented!");
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the native resources associated with this object. You <strong>must</strong> call this
|
* Release the native resources associated with this object. You <strong>must</strong> call this
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.squareup.duktape;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/. */
|
||||||
|
// part of tachiyomi-extensions which was originally licensed under Apache License Version 2.0
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** This is the reference Duktape stub that tachiyomi's extensions depend on.
|
||||||
|
* Intended to be used as a reference.
|
||||||
|
*/
|
||||||
|
public class DuktapeStub implements Closeable {
|
||||||
|
|
||||||
|
public static Duktape create() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Object evaluate(String script) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized <T> void set(String name, Class<T> type, T object) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||

|

|
||||||
# Tachidesk
|
# Tachidesk
|
||||||
A free and open source manga reader that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
|
A free and open source manga reader that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
|
||||||
|
|
||||||
@@ -18,21 +18,28 @@ Here is a list of current features:
|
|||||||
|
|
||||||
**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, 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.
|
||||||
|
|
||||||
Anyways, for more info checkout [finished milestone #1](https://github.com/AriaMoradi/Tachidesk/issues/2) and [milestone #2](https://github.com/AriaMoradi/Tachidesk/projects/1) to see what's implemented in more detail.
|
Anyways, for more info checkout [finished milestone #1](https://github.com/Suwayomi/Tachidesk/issues/2) and [milestone #2](https://github.com/Suwayomi/Tachidesk/projects/1) to see what's implemented in more detail.
|
||||||
|
|
||||||
## Downloading and Running the app
|
## Downloading and Running the app
|
||||||
### Downloading the app
|
|
||||||
Download the latest jar or windows(win32) release from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
|
|
||||||
|
|
||||||
### All Operating Systems
|
### All Operating Systems
|
||||||
You should have The Java Runtime Environment(JRE) 8 or newer and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
|
You should have The Java Runtime Environment(JRE) 8 or newer and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
|
||||||
|
|
||||||
|
Download the latest jar release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases).
|
||||||
|
|
||||||
Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxx.jar` 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` 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 only
|
### Windows
|
||||||
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`.
|
Download the latest win32 release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases).
|
||||||
|
|
||||||
### Running on Docker
|
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.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
You can install Tachidesk from the AUR
|
||||||
|
```
|
||||||
|
yay -S tachidesk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
|
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
|
||||||
|
|
||||||
## General troubleshooting
|
## General troubleshooting
|
||||||
@@ -55,18 +62,25 @@ This project has two components:
|
|||||||
2. **webUI:** A react SPA project that works with the server to do the presentation.
|
2. **webUI:** A react SPA project that works with the server to do the presentation.
|
||||||
|
|
||||||
## Building from source
|
## Building from source
|
||||||
### Get Android stubs jar
|
### Prerequisite: Get Android stubs jar
|
||||||
#### Manual download
|
#### Manual download
|
||||||
Download [android.jar](https://raw.githubusercontent.com/AriaMoradi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
Download [android.jar](https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
||||||
#### Automated download(needs `bash`, `curl`, `base64`, `zip` to work)
|
#### Automated download(needs `bash`, `curl`, `base64`, `zip` to work)
|
||||||
Run `AndroidCompat/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
|
Run `AndroidCompat/getAndroid.sh`(MacOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows) from project's root directory to download and rebuild the jar file from Google's repository.
|
||||||
### building the jar
|
### Prerequisite: Software dependencies
|
||||||
Run `./gradlew shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
You need this software packages installed in order to build this project:
|
||||||
|
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||||
|
- Nodejs LTS or latest
|
||||||
|
- Yarn
|
||||||
|
### building the full-blown jar
|
||||||
|
Run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
||||||
|
### building without `webUI` bundled
|
||||||
|
Delete the `server/src/main/resources/react` directory if exists from previous runs, then run `./gradlew server:shadowJar -x :webUI:copyBuild`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
||||||
### building the Windows package
|
### building the Windows package
|
||||||
Run `./gradlew windowsPackage`, the resulting built zip package file will be `server/build/Tachidesk-vX.Y.Z-rxxx-win32.zip`.
|
Run `./gradlew windowsPackage`, the resulting built zip package file will be `server/build/Tachidesk-vX.Y.Z-rxxx-win32.zip`.
|
||||||
## Running for development purposes
|
## Running for development purposes
|
||||||
### `server` module
|
### `server` module
|
||||||
Run `./gradlew :server:run -x :webUI:copyBuild --stacktrace` to run the server
|
Follow [Get Android stubs jar](#prerequisite-get-android-stubs-jar) then run `./gradlew :server:run -x :webUI:copyBuild --stacktrace` to run the server
|
||||||
### `webUI` module
|
### `webUI` module
|
||||||
How to do it is described in `webUI/react/README.md` but for short,
|
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)
|
first cd into `webUI/react` then run `yarn` to install the node modules(do this only once)
|
||||||
@@ -75,6 +89,8 @@ How to do it is described in `webUI/react/README.md` but for short,
|
|||||||
and supports HMR and all the other goodies you'll need.
|
and supports HMR and all the other goodies you'll need.
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
|
This project is a spiritual successor of [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server), Many of the ideas and the groundwork adopted in this project comes from TachiWeb.
|
||||||
|
|
||||||
The `AndroidCompat` module was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
|
The `AndroidCompat` module was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
|
||||||
|
|
||||||
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
||||||
@@ -85,7 +101,7 @@ Changes to both codebases is licensed under `MPL v. 2.0` as the rest of this pro
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2020-2021 Aria Moradi and contributors
|
Copyright (C) Contributors to the Suwayomi project
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
|||||||
+3
-1
@@ -61,7 +61,7 @@ configure(listOf(
|
|||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||||
implementation("org.slf4j:slf4j-simple:1.7.30")
|
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
implementation("io.github.microutils:kotlin-logging:2.0.3")
|
implementation("io.github.microutils:kotlin-logging:2.0.3")
|
||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
@@ -76,6 +76,8 @@ configure(listOf(
|
|||||||
|
|
||||||
// dependency of :AndroidCompat:Config
|
// dependency of :AndroidCompat:Config
|
||||||
implementation("com.typesafe:config:1.4.0")
|
implementation("com.typesafe:config:1.4.0")
|
||||||
|
implementation("io.github.config4k:config4k:0.4.2")
|
||||||
|
|
||||||
|
|
||||||
// to get application content root
|
// to get application content root
|
||||||
implementation("net.harawata:appdirs:1.2.0")
|
implementation("net.harawata:appdirs:1.2.0")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ plugins {
|
|||||||
id("edu.sc.seis.launch4j") version "2.4.9"
|
id("edu.sc.seis.launch4j") version "2.4.9"
|
||||||
}
|
}
|
||||||
|
|
||||||
val TachideskVersion = "v0.2.5"
|
val TachideskVersion = "v0.2.6"
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -57,19 +57,17 @@ dependencies {
|
|||||||
|
|
||||||
implementation("org.jsoup:jsoup:1.13.1")
|
implementation("org.jsoup:jsoup:1.13.1")
|
||||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||||
implementation("com.squareup.duktape:duktape-android:1.3.0")
|
|
||||||
|
|
||||||
|
|
||||||
val coroutinesVersion = "1.3.9"
|
val coroutinesVersion = "1.3.9"
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
|
|
||||||
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
|
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
|
||||||
implementation(fileTree("lib/dex2jar/"))
|
implementation("com.github.DexPatcher.dex2jar:dex-tools:v2.1-20190905-lanchon")
|
||||||
|
|
||||||
|
|
||||||
// api
|
// api
|
||||||
implementation("io.javalin:javalin:3.12.0")
|
implementation("io.javalin:javalin:3.12.0")
|
||||||
implementation("org.slf4j:slf4j-simple:1.8.0-beta4")
|
|
||||||
implementation("org.slf4j:slf4j-api:1.8.0-beta4")
|
|
||||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
|
||||||
|
|
||||||
// Exposed ORM
|
// Exposed ORM
|
||||||
@@ -87,9 +85,8 @@ dependencies {
|
|||||||
implementation(project(":AndroidCompat"))
|
implementation(project(":AndroidCompat"))
|
||||||
implementation(project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
|
// uncomment to test extensions directly
|
||||||
// testImplementation("org.jetbrains.kotlin:kotlin-test")
|
// implementation(fileTree("lib/"))
|
||||||
// testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val name = "ir.armor.tachidesk.Main"
|
val name = "ir.armor.tachidesk.Main"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,67 +0,0 @@
|
|||||||
==== dx-*.jar
|
|
||||||
Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
|
|
||||||
|
|
||||||
==== antlr-*.jar
|
|
||||||
[The BSD License]
|
|
||||||
Copyright (c) 2003-2007, Terence Parr
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in
|
|
||||||
the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of the author nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this
|
|
||||||
software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
||||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
||||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
==== asm-*.jar
|
|
||||||
|
|
||||||
ASM: a very small and fast Java bytecode manipulation framework
|
|
||||||
Copyright (c) 2000-2005 INRIA, France Telecom
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of the copyright holders nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
||||||
THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
// import android.content.res.Configuration
|
// import android.content.res.Configuration
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.extension.api
|
package eu.kanade.tachiyomi.extension.api
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.extension.util
|
package eu.kanade.tachiyomi.extension.util
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.annotation.SuppressLint
|
// import android.annotation.SuppressLint
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import android.content.pm.PackageInfo
|
// import android.content.pm.PackageInfo
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.annotation.SuppressLint
|
// import android.annotation.SuppressLint
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import android.os.Build
|
// import android.os.Build
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.CookieJar
|
import okhttp3.CookieJar
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import eu.kanade.tachiyomi.BuildConfig
|
// import eu.kanade.tachiyomi.BuildConfig
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
|||||||
@@ -1,260 +1,22 @@
|
|||||||
package ir.armor.tachidesk
|
package ir.armor.tachidesk
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import io.javalin.Javalin
|
import ir.armor.tachidesk.server.applicationSetup
|
||||||
import ir.armor.tachidesk.util.addMangaToCategory
|
import ir.armor.tachidesk.server.javalinSetup
|
||||||
import ir.armor.tachidesk.util.addMangaToLibrary
|
|
||||||
import ir.armor.tachidesk.util.createCategory
|
|
||||||
import ir.armor.tachidesk.util.getCategoryList
|
|
||||||
import ir.armor.tachidesk.util.getCategoryMangaList
|
|
||||||
import ir.armor.tachidesk.util.getChapter
|
|
||||||
import ir.armor.tachidesk.util.getChapterList
|
|
||||||
import ir.armor.tachidesk.util.getExtensionIcon
|
|
||||||
import ir.armor.tachidesk.util.getExtensionList
|
|
||||||
import ir.armor.tachidesk.util.getLibraryMangas
|
|
||||||
import ir.armor.tachidesk.util.getManga
|
|
||||||
import ir.armor.tachidesk.util.getMangaCategories
|
|
||||||
import ir.armor.tachidesk.util.getMangaList
|
|
||||||
import ir.armor.tachidesk.util.getPageImage
|
|
||||||
import ir.armor.tachidesk.util.getSource
|
|
||||||
import ir.armor.tachidesk.util.getSourceList
|
|
||||||
import ir.armor.tachidesk.util.getThumbnail
|
|
||||||
import ir.armor.tachidesk.util.installAPK
|
|
||||||
import ir.armor.tachidesk.util.openInBrowser
|
|
||||||
import ir.armor.tachidesk.util.removeCategory
|
|
||||||
import ir.armor.tachidesk.util.removeExtension
|
|
||||||
import ir.armor.tachidesk.util.removeMangaFromCategory
|
|
||||||
import ir.armor.tachidesk.util.removeMangaFromLibrary
|
|
||||||
import ir.armor.tachidesk.util.reorderCategory
|
|
||||||
import ir.armor.tachidesk.util.sourceFilters
|
|
||||||
import ir.armor.tachidesk.util.sourceGlobalSearch
|
|
||||||
import ir.armor.tachidesk.util.sourceSearch
|
|
||||||
import ir.armor.tachidesk.util.updateCategory
|
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
serverSetup()
|
applicationSetup()
|
||||||
|
javalinSetup()
|
||||||
var hasWebUiBundled: Boolean = false
|
|
||||||
|
|
||||||
val app = Javalin.create { config ->
|
|
||||||
try {
|
|
||||||
this::class.java.classLoader.getResource("/react/index.html")
|
|
||||||
hasWebUiBundled = true
|
|
||||||
config.addStaticFiles("/react")
|
|
||||||
config.addSinglePageRoot("/", "/react/index.html")
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
println("Warning: react build files are missing.")
|
|
||||||
hasWebUiBundled = false
|
|
||||||
}
|
|
||||||
config.enableCorsForAllOrigins()
|
|
||||||
}.start(serverConfig.ip, serverConfig.port)
|
|
||||||
if (hasWebUiBundled) {
|
|
||||||
openInBrowser()
|
|
||||||
}
|
|
||||||
|
|
||||||
app.exception(NullPointerException::class.java) { _, ctx ->
|
|
||||||
ctx.status(404)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/extension/list") { ctx ->
|
|
||||||
ctx.json(getExtensionList())
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/extension/install/:apkName") { ctx ->
|
|
||||||
val apkName = ctx.pathParam("apkName")
|
|
||||||
println("installing $apkName")
|
|
||||||
|
|
||||||
ctx.status(
|
|
||||||
installAPK(apkName)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/extension/uninstall/:apkName") { ctx ->
|
|
||||||
val apkName = ctx.pathParam("apkName")
|
|
||||||
println("uninstalling $apkName")
|
|
||||||
removeExtension(apkName)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// icon for extension named `apkName`
|
|
||||||
app.get("/api/v1/extension/icon/:apkName") { ctx ->
|
|
||||||
val apkName = ctx.pathParam("apkName")
|
|
||||||
val result = getExtensionIcon(apkName)
|
|
||||||
|
|
||||||
ctx.result(result.first)
|
|
||||||
ctx.header("content-type", result.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// list of sources
|
|
||||||
app.get("/api/v1/source/list") { ctx ->
|
|
||||||
ctx.json(getSourceList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch source with id `sourceId`
|
|
||||||
app.get("/api/v1/source/:sourceId") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
ctx.json(getSource(sourceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// popular mangas from source with id `sourceId`
|
|
||||||
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.json(getMangaList(sourceId, pageNum, popular = true))
|
|
||||||
}
|
|
||||||
|
|
||||||
// latest mangas from source with id `sourceId`
|
|
||||||
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.json(getMangaList(sourceId, pageNum, popular = false))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get manga info
|
|
||||||
app.get("/api/v1/manga/:mangaId/") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
ctx.json(getManga(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// manga thumbnail
|
|
||||||
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
val result = getThumbnail(mangaId)
|
|
||||||
|
|
||||||
ctx.result(result.first)
|
|
||||||
ctx.header("content-type", result.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adds the manga to library
|
|
||||||
app.get("api/v1/manga/:mangaId/library") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
addMangaToLibrary(mangaId)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the manga from the library
|
|
||||||
app.delete("api/v1/manga/:mangaId/library") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
removeMangaFromLibrary(mangaId)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// list manga's categories
|
|
||||||
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
ctx.json(getMangaCategories(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// adds the manga to category
|
|
||||||
app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
addMangaToCategory(mangaId, categoryId)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the manga from the category
|
|
||||||
app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
removeMangaFromCategory(mangaId, categoryId)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
ctx.json(getChapterList(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterId") { ctx ->
|
|
||||||
val chapterId = ctx.pathParam("chapterId").toInt()
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
ctx.json(getChapter(chapterId, mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterId/page/:index") { ctx ->
|
|
||||||
val chapterId = ctx.pathParam("chapterId").toInt()
|
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
|
||||||
val index = ctx.pathParam("index").toInt()
|
|
||||||
val result = getPageImage(mangaId, chapterId, index)
|
|
||||||
|
|
||||||
ctx.result(result.first)
|
|
||||||
ctx.header("content-type", result.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// global search
|
|
||||||
app.get("/api/v1/search/:searchTerm") { ctx ->
|
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
|
||||||
ctx.json(sourceGlobalSearch(searchTerm))
|
|
||||||
}
|
|
||||||
|
|
||||||
// single source search
|
|
||||||
app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
|
||||||
ctx.json(sourceSearch(sourceId, searchTerm, pageNum))
|
|
||||||
}
|
|
||||||
|
|
||||||
// source filter list
|
|
||||||
app.get("/api/v1/source/:sourceId/filters/") { ctx ->
|
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
|
||||||
ctx.json(sourceFilters(sourceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// lists mangas that have no category assigned
|
|
||||||
app.get("/api/v1/library/") { ctx ->
|
|
||||||
ctx.json(getLibraryMangas())
|
|
||||||
}
|
|
||||||
|
|
||||||
// category list
|
|
||||||
app.get("/api/v1/category/") { ctx ->
|
|
||||||
ctx.json(getCategoryList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// category create
|
|
||||||
app.post("/api/v1/category/") { ctx ->
|
|
||||||
val name = ctx.formParam("name")!!
|
|
||||||
createCategory(name)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// category modification
|
|
||||||
app.patch("/api/v1/category/:categoryId") { ctx ->
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
val name = ctx.formParam("name")
|
|
||||||
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
|
||||||
updateCategory(categoryId, name, isLanding)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// category re-ordering
|
|
||||||
app.patch("/api/v1/category/:categoryId/reorder") { ctx ->
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
val from = ctx.formParam("from")!!.toInt()
|
|
||||||
val to = ctx.formParam("to")!!.toInt()
|
|
||||||
reorderCategory(categoryId, from, to)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// category delete
|
|
||||||
app.delete("/api/v1/category/:categoryId") { ctx ->
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
removeCategory(categoryId)
|
|
||||||
ctx.status(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the manga list associated with a category
|
|
||||||
app.get("/api/v1/category/:categoryId") { ctx ->
|
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
|
||||||
ctx.json(getCategoryMangaList(categoryId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package ir.armor.tachidesk
|
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import xyz.nulldev.ts.config.ConfigModule
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class ServerConfig(config: Config) : ConfigModule(config) {
|
|
||||||
val ip = config.getString("ip")
|
|
||||||
val port = config.getInt("port")
|
|
||||||
|
|
||||||
// proxy
|
|
||||||
val socksProxy = config.getBoolean("socksProxy")
|
|
||||||
val socksProxyHost = config.getString("socksProxyHost")
|
|
||||||
val socksProxyPort = config.getString("socksProxyPort")
|
|
||||||
|
|
||||||
fun registerFile(file: String): File {
|
|
||||||
return File(file).apply {
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun register(config: Config) = ServerConfig(config.getConfig("server"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package ir.armor.tachidesk.database
|
package ir.armor.tachidesk.database
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ir.armor.tachidesk.applicationDirs
|
|
||||||
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
||||||
import ir.armor.tachidesk.database.table.CategoryTable
|
import ir.armor.tachidesk.database.table.CategoryTable
|
||||||
import ir.armor.tachidesk.database.table.ChapterTable
|
import ir.armor.tachidesk.database.table.ChapterTable
|
||||||
@@ -12,6 +14,7 @@ import ir.armor.tachidesk.database.table.ExtensionTable
|
|||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.PageTable
|
import ir.armor.tachidesk.database.table.PageTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
|
import ir.armor.tachidesk.server.applicationDirs
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -12,5 +15,7 @@ data class ChapterDataClass(
|
|||||||
val chapter_number: Float,
|
val chapter_number: Float,
|
||||||
val scanlator: String?,
|
val scanlator: String?,
|
||||||
val mangaId: Int,
|
val mangaId: Int,
|
||||||
|
val chapterIndex: Int,
|
||||||
|
val chapterCount: Int,
|
||||||
val pageCount: Int? = null,
|
val pageCount: Int? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
object CategoryMangaTable : IntIdTable() {
|
object CategoryMangaTable : IntIdTable() {
|
||||||
val category = reference("category", CategoryTable)
|
val category = reference("category", CategoryTable)
|
||||||
val manga = reference("manga", MangaTable)
|
val manga = reference("manga", MangaTable)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -13,5 +16,7 @@ object ChapterTable : IntIdTable() {
|
|||||||
val chapter_number = float("chapter_number").default(-1f)
|
val chapter_number = float("chapter_number").default(-1f)
|
||||||
val scanlator = varchar("scanlator", 128).nullable()
|
val scanlator = varchar("scanlator", 128).nullable()
|
||||||
|
|
||||||
|
val chapterIndex = integer("number_in_list")
|
||||||
|
|
||||||
val manga = reference("manga", MangaTable)
|
val manga = reference("manga", MangaTable)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.util.proxyThumbnailUrl
|
import ir.armor.tachidesk.impl.proxyThumbnailUrl
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -1,4 +1,11 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
||||||
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
||||||
@@ -12,10 +19,6 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
fun createCategory(name: String) {
|
fun createCategory(name: String) {
|
||||||
transaction {
|
transaction {
|
||||||
val count = CategoryTable.selectAll().count()
|
val count = CategoryTable.selectAll().count()
|
||||||
+8
-5
@@ -1,4 +1,11 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
@@ -14,10 +21,6 @@ import org.jetbrains.exposed.sql.select
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) {
|
if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) {
|
||||||
+49
-9
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -14,7 +17,9 @@ import org.jetbrains.exposed.sql.and
|
|||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
||||||
val mangaDetails = getManga(mangaId)
|
val mangaDetails = getManga(mangaId)
|
||||||
@@ -27,8 +32,10 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
|||||||
}
|
}
|
||||||
).toBlocking().first()
|
).toBlocking().first()
|
||||||
|
|
||||||
|
val chapterCount = chapterList.count()
|
||||||
|
|
||||||
return transaction {
|
return transaction {
|
||||||
chapterList.forEach { fetchedChapter ->
|
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||||
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
||||||
if (chapterEntry == null) {
|
if (chapterEntry == null) {
|
||||||
ChapterTable.insertAndGetId {
|
ChapterTable.insertAndGetId {
|
||||||
@@ -38,12 +45,29 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
|||||||
it[chapter_number] = fetchedChapter.chapter_number
|
it[chapter_number] = fetchedChapter.chapter_number
|
||||||
it[scanlator] = fetchedChapter.scanlator
|
it[scanlator] = fetchedChapter.scanlator
|
||||||
|
|
||||||
|
it[chapterIndex] = index + 1
|
||||||
|
it[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[manga] = mangaId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return@transaction chapterList.map {
|
// clear any orphaned chapters
|
||||||
|
val dbChapterCount = transaction { ChapterTable.selectAll().count() }
|
||||||
|
if (dbChapterCount > chapterCount) { // we got some clean up due
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return@transaction chapterList.mapIndexed { index, it ->
|
||||||
ChapterDataClass(
|
ChapterDataClass(
|
||||||
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
||||||
it.url,
|
it.url,
|
||||||
@@ -51,16 +75,19 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
|||||||
it.date_upload,
|
it.date_upload,
|
||||||
it.chapter_number,
|
it.chapter_number,
|
||||||
it.scanlator,
|
it.scanlator,
|
||||||
mangaId
|
mangaId,
|
||||||
|
chapterCount - index,
|
||||||
|
chapterCount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
|
fun getChapter(chapterIndex: Int, mangaId: Int): ChapterDataClass {
|
||||||
return transaction {
|
return transaction {
|
||||||
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
|
val chapterEntry = ChapterTable.select {
|
||||||
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
|
ChapterTable.chapterIndex eq chapterIndex and (ChapterTable.manga eq mangaId)
|
||||||
|
}.firstOrNull()!!
|
||||||
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||||
|
|
||||||
@@ -71,14 +98,20 @@ fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
|
|||||||
}
|
}
|
||||||
).toBlocking().first()
|
).toBlocking().first()
|
||||||
|
|
||||||
|
val chapterId = chapterEntry[ChapterTable.id].value
|
||||||
|
val chapterCount = transaction { ChapterTable.selectAll().count() }
|
||||||
|
|
||||||
val chapter = ChapterDataClass(
|
val chapter = ChapterDataClass(
|
||||||
chapterEntry[ChapterTable.id].value,
|
chapterId,
|
||||||
chapterEntry[ChapterTable.url],
|
chapterEntry[ChapterTable.url],
|
||||||
chapterEntry[ChapterTable.name],
|
chapterEntry[ChapterTable.name],
|
||||||
chapterEntry[ChapterTable.date_upload],
|
chapterEntry[ChapterTable.date_upload],
|
||||||
chapterEntry[ChapterTable.chapter_number],
|
chapterEntry[ChapterTable.chapter_number],
|
||||||
chapterEntry[ChapterTable.scanlator],
|
chapterEntry[ChapterTable.scanlator],
|
||||||
mangaId,
|
mangaId,
|
||||||
|
chapterEntry[ChapterTable.chapterIndex],
|
||||||
|
chapterCount.toInt(),
|
||||||
|
|
||||||
pageList.count()
|
pageList.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,6 +126,13 @@ fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
|
|||||||
it[this.chapter] = chapterId
|
it[this.chapter] = chapterId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
transaction {
|
||||||
|
PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) }) {
|
||||||
|
it[url] = page.url
|
||||||
|
it[imageUrl] = page.imageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+58
-23
@@ -1,20 +1,26 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import com.googlecode.dex2jar.tools.Dex2jarCmd
|
import com.googlecode.d2j.dex.Dex2jar
|
||||||
|
import com.googlecode.d2j.reader.MultiDexFileReader
|
||||||
|
import com.googlecode.dex2jar.tools.BaksmaliBaseDexExceptionHandler
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.APKExtractor
|
|
||||||
import ir.armor.tachidesk.applicationDirs
|
|
||||||
import ir.armor.tachidesk.database.table.ExtensionTable
|
import ir.armor.tachidesk.database.table.ExtensionTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
|
import ir.armor.tachidesk.impl.util.APKExtractor
|
||||||
|
import ir.armor.tachidesk.server.applicationDirs
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import mu.KotlinLogging
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
@@ -28,8 +34,45 @@ import java.io.File
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) {
|
||||||
|
// adopted from com.googlecode.dex2jar.tools.Dex2jarCmd.doCommandLine
|
||||||
|
// source at: https://github.com/DexPatcher/dex2jar/tree/v2.1-20190905-lanchon/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java
|
||||||
|
|
||||||
|
val jarFilePath = File(jarFile).toPath()
|
||||||
|
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
||||||
|
val handler = BaksmaliBaseDexExceptionHandler()
|
||||||
|
Dex2jar
|
||||||
|
.from(reader)
|
||||||
|
.withExceptionHandler(handler)
|
||||||
|
.reUseReg(false)
|
||||||
|
.topoLogicalSort()
|
||||||
|
.skipDebug(true)
|
||||||
|
.optimizeSynchronized(false)
|
||||||
|
.printIR(false)
|
||||||
|
.noCode(false)
|
||||||
|
.skipExceptions(false)
|
||||||
|
.to(jarFilePath)
|
||||||
|
if (handler.hasException()) {
|
||||||
|
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
||||||
|
logger.error(
|
||||||
|
"Detail Error Information in File $errorFile\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"
|
||||||
|
)
|
||||||
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun installAPK(apkName: String): Int {
|
fun installAPK(apkName: String): Int {
|
||||||
|
logger.debug("Installing $apkName")
|
||||||
val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
|
val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
|
||||||
val fileNameWithoutType = apkName.substringBefore(".apk")
|
val fileNameWithoutType = apkName.substringBefore(".apk")
|
||||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
||||||
@@ -49,9 +92,9 @@ fun installAPK(apkName: String): Int {
|
|||||||
downloadAPKFile(apkToDownload, apkFilePath)
|
downloadAPKFile(apkToDownload, apkFilePath)
|
||||||
|
|
||||||
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
||||||
println(className)
|
logger.debug(className)
|
||||||
// dex -> jar
|
// dex -> jar
|
||||||
Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force")
|
dex2jar(dexFilePath, jarFilePath, fileNameWithoutType)
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
File(apkFilePath).delete()
|
File(apkFilePath).delete()
|
||||||
@@ -69,11 +112,6 @@ fun installAPK(apkName: String): Int {
|
|||||||
if (instance is HttpSource) { // single source
|
if (instance is HttpSource) { // single source
|
||||||
val httpSource = instance as HttpSource
|
val httpSource = instance as HttpSource
|
||||||
transaction {
|
transaction {
|
||||||
// SourceEntity.new {
|
|
||||||
// sourceId = httpSource.id
|
|
||||||
// name = httpSource.name
|
|
||||||
// this.extension = ExtensionEntity.find { ExtensionsTable.name eq extension.name }.first().id
|
|
||||||
// }
|
|
||||||
if (SourceTable.select { SourceTable.id eq httpSource.id }.count() == 0L) {
|
if (SourceTable.select { SourceTable.id eq httpSource.id }.count() == 0L) {
|
||||||
SourceTable.insert {
|
SourceTable.insert {
|
||||||
it[this.id] = httpSource.id
|
it[this.id] = httpSource.id
|
||||||
@@ -82,9 +120,7 @@ fun installAPK(apkName: String): Int {
|
|||||||
it[extension] = extensionId
|
it[extension] = extensionId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// println(httpSource.id)
|
logger.debug("Installed source ${httpSource.name} with id {httpSource.id}")
|
||||||
// println(httpSource.name)
|
|
||||||
// println()
|
|
||||||
}
|
}
|
||||||
} else { // multi source
|
} else { // multi source
|
||||||
val sourceFactory = instance as SourceFactory
|
val sourceFactory = instance as SourceFactory
|
||||||
@@ -101,9 +137,7 @@ fun installAPK(apkName: String): Int {
|
|||||||
it[positionInFactorySource] = index
|
it[positionInFactorySource] = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// println(httpSource.id)
|
logger.debug("Installed source ${httpSource.name} with id:${httpSource.id}")
|
||||||
// println(httpSource.name)
|
|
||||||
// println()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,9 +168,11 @@ private fun downloadAPKFile(url: String, apkPath: String) {
|
|||||||
sink.close()
|
sink.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeExtension(pkgName: String) {
|
fun removeExtension(apkName: String) {
|
||||||
val extensionRecord = getExtensionList(true).first { it.apkName == pkgName }
|
logger.debug("Uninstalling $apkName")
|
||||||
val fileNameWithoutType = pkgName.substringBefore(".apk")
|
|
||||||
|
val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
|
||||||
|
val fileNameWithoutType = apkName.substringBefore(".apk")
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
transaction {
|
transaction {
|
||||||
val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id]
|
val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id]
|
||||||
@@ -158,9 +194,8 @@ 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 }.firstOrNull()!! }[ExtensionTable.iconUrl]
|
||||||
|
|
||||||
val saveDir = "${applicationDirs.extensionsRoot}/icon"
|
val saveDir = "${applicationDirs.extensionsRoot}/icon"
|
||||||
val fileName = apkName
|
|
||||||
|
|
||||||
return getCachedResponse(saveDir, fileName) {
|
return getCachedImageResponse(saveDir, apkName) {
|
||||||
network.client.newCall(
|
network.client.newCall(
|
||||||
GET(iconUrl)
|
GET(iconUrl)
|
||||||
).execute()
|
).execute()
|
||||||
+10
-4
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -9,12 +12,15 @@ import eu.kanade.tachiyomi.extension.model.Extension
|
|||||||
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass
|
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass
|
||||||
import ir.armor.tachidesk.database.table.ExtensionTable
|
import ir.armor.tachidesk.database.table.ExtensionTable
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private object Data {
|
private object Data {
|
||||||
var lastExtensionCheck: Long = 0
|
var lastExtensionCheck: Long = 0
|
||||||
}
|
}
|
||||||
@@ -28,7 +34,7 @@ private fun extensionDatabaseIsEmtpy(): Boolean {
|
|||||||
fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
|
fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
|
||||||
// update if 60 seconds has passed or requested offline and database is empty
|
// update if 60 seconds has passed or requested offline and database is empty
|
||||||
if (Data.lastExtensionCheck + 60 * 1000 < System.currentTimeMillis() || (offline && extensionDatabaseIsEmtpy())) {
|
if (Data.lastExtensionCheck + 60 * 1000 < System.currentTimeMillis() || (offline && extensionDatabaseIsEmtpy())) {
|
||||||
println("Getting extensions list from the internet")
|
logger.debug("Getting extensions list from the internet")
|
||||||
Data.lastExtensionCheck = System.currentTimeMillis()
|
Data.lastExtensionCheck = System.currentTimeMillis()
|
||||||
var foundExtensions: List<Extension.Available>
|
var foundExtensions: List<Extension.Available>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@@ -66,7 +72,7 @@ fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println("used cached extension list")
|
logger.debug("used cached extension list")
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction {
|
return transaction {
|
||||||
+8
-6
@@ -1,20 +1,22 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
import ir.armor.tachidesk.database.table.CategoryMangaTable
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.toDataClass
|
import ir.armor.tachidesk.database.table.toDataClass
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
fun addMangaToLibrary(mangaId: Int) {
|
fun addMangaToLibrary(mangaId: Int) {
|
||||||
val manga = getManga(mangaId)
|
val manga = getManga(mangaId)
|
||||||
if (!manga.inLibrary) {
|
if (!manga.inLibrary) {
|
||||||
+7
-4
@@ -1,15 +1,18 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import ir.armor.tachidesk.applicationDirs
|
|
||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.database.table.MangaStatus
|
import ir.armor.tachidesk.database.table.MangaStatus
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
|
import ir.armor.tachidesk.server.applicationDirs
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
@@ -90,7 +93,7 @@ fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
|
|||||||
val saveDir = applicationDirs.thumbnailsRoot
|
val saveDir = applicationDirs.thumbnailsRoot
|
||||||
val fileName = mangaId.toString()
|
val fileName = mangaId.toString()
|
||||||
|
|
||||||
return getCachedResponse(saveDir, fileName) {
|
return getCachedImageResponse(saveDir, fileName) {
|
||||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
+15
-6
@@ -1,16 +1,19 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.applicationDirs
|
|
||||||
import ir.armor.tachidesk.database.table.ChapterTable
|
import ir.armor.tachidesk.database.table.ChapterTable
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.PageTable
|
import ir.armor.tachidesk.database.table.PageTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
|
import ir.armor.tachidesk.server.applicationDirs
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@@ -25,10 +28,16 @@ fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
|||||||
return page.imageUrl!!
|
return page.imageUrl!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
|
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 }.firstOrNull()!! }
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||||
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
|
val chapterEntry = transaction {
|
||||||
|
ChapterTable.select {
|
||||||
|
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||||
|
}.firstOrNull()!!
|
||||||
|
}
|
||||||
|
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) }.firstOrNull()!! }
|
||||||
|
|
||||||
val tachiPage = Page(
|
val tachiPage = Page(
|
||||||
@@ -49,7 +58,7 @@ fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, St
|
|||||||
File(saveDir).mkdirs()
|
File(saveDir).mkdirs()
|
||||||
val fileName = index.toString()
|
val fileName = index.toString()
|
||||||
|
|
||||||
return getCachedResponse(saveDir, fileName) {
|
return getCachedImageResponse(saveDir, fileName) {
|
||||||
source.fetchImage(tachiPage).toBlocking().first()
|
source.fetchImage(tachiPage).toBlocking().first()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
+12
-8
@@ -1,17 +1,21 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.applicationDirs
|
|
||||||
import ir.armor.tachidesk.database.dataclass.SourceDataClass
|
import ir.armor.tachidesk.database.dataclass.SourceDataClass
|
||||||
import ir.armor.tachidesk.database.entity.ExtensionEntity
|
import ir.armor.tachidesk.database.entity.ExtensionEntity
|
||||||
import ir.armor.tachidesk.database.entity.SourceEntity
|
import ir.armor.tachidesk.database.entity.SourceEntity
|
||||||
import ir.armor.tachidesk.database.table.ExtensionTable
|
import ir.armor.tachidesk.database.table.ExtensionTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
|
import ir.armor.tachidesk.server.applicationDirs
|
||||||
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@@ -19,6 +23,8 @@ import java.lang.NullPointerException
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
|
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
|
||||||
private val extensionCache = mutableListOf<Pair<String, Any>>()
|
private val extensionCache = mutableListOf<Pair<String, Any>>()
|
||||||
|
|
||||||
@@ -29,7 +35,7 @@ fun getHttpSource(sourceId: Long): HttpSource {
|
|||||||
|
|
||||||
val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId }
|
val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId }
|
||||||
if (cachedResult != null) {
|
if (cachedResult != null) {
|
||||||
println("used cached HttpSource: ${cachedResult.second.name}")
|
logger.debug("used cached HttpSource: ${cachedResult.second.name}")
|
||||||
return cachedResult.second
|
return cachedResult.second
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +47,15 @@ fun getHttpSource(sourceId: Long): HttpSource {
|
|||||||
val jarName = apkName.substringBefore(".apk") + ".jar"
|
val jarName = apkName.substringBefore(".apk") + ".jar"
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
|
val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
|
||||||
|
|
||||||
println(jarName)
|
|
||||||
|
|
||||||
val cachedExtensionPair = extensionCache.firstOrNull { it.first == jarPath }
|
val cachedExtensionPair = extensionCache.firstOrNull { it.first == jarPath }
|
||||||
var usedCached = false
|
var usedCached = false
|
||||||
val instance =
|
val instance =
|
||||||
if (cachedExtensionPair != null) {
|
if (cachedExtensionPair != null) {
|
||||||
usedCached = true
|
usedCached = true
|
||||||
println("Used cached Extension")
|
logger.debug("Used cached Extension")
|
||||||
cachedExtensionPair.second
|
cachedExtensionPair.second
|
||||||
} else {
|
} else {
|
||||||
println("No Extension cache")
|
logger.debug("No Extension cache")
|
||||||
val child = URLClassLoader(arrayOf<URL>(URL("file:$jarPath")), this::class.java.classLoader)
|
val child = URLClassLoader(arrayOf<URL>(URL("file:$jarPath")), this::class.java.classLoader)
|
||||||
val classToLoad = Class.forName(className, true, child)
|
val classToLoad = Class.forName(className, true, child)
|
||||||
classToLoad.newInstance()
|
classToLoad.newInstance()
|
||||||
+5
-3
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk;
|
package ir.armor.tachidesk.impl.util;
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -16,7 +19,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
public class APKExtractor {
|
public class APKExtractor {
|
||||||
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
|
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
|
||||||
+6
-3
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.impl
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ private fun BufferedSource.saveTo(stream: OutputStream) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCachedResponse(saveDir: String, fileName: String, fetcher: () -> Response): Pair<InputStream, String> {
|
fun getCachedImageResponse(saveDir: String, fileName: String, fetcher: () -> Response): Pair<InputStream, String> {
|
||||||
val cachedFile = findFileNameStartingWith(saveDir, fileName)
|
val cachedFile = findFileNameStartingWith(saveDir, fileName)
|
||||||
val filePath = "$saveDir/$fileName"
|
val filePath = "$saveDir/$fileName"
|
||||||
if (cachedFile != null) {
|
if (cachedFile != null) {
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package ir.armor.tachidesk.server
|
||||||
|
|
||||||
|
import io.javalin.Javalin
|
||||||
|
import ir.armor.tachidesk.Main
|
||||||
|
import ir.armor.tachidesk.impl.addMangaToCategory
|
||||||
|
import ir.armor.tachidesk.impl.addMangaToLibrary
|
||||||
|
import ir.armor.tachidesk.impl.createCategory
|
||||||
|
import ir.armor.tachidesk.impl.getCategoryList
|
||||||
|
import ir.armor.tachidesk.impl.getCategoryMangaList
|
||||||
|
import ir.armor.tachidesk.impl.getChapter
|
||||||
|
import ir.armor.tachidesk.impl.getChapterList
|
||||||
|
import ir.armor.tachidesk.impl.getExtensionIcon
|
||||||
|
import ir.armor.tachidesk.impl.getExtensionList
|
||||||
|
import ir.armor.tachidesk.impl.getLibraryMangas
|
||||||
|
import ir.armor.tachidesk.impl.getManga
|
||||||
|
import ir.armor.tachidesk.impl.getMangaCategories
|
||||||
|
import ir.armor.tachidesk.impl.getMangaList
|
||||||
|
import ir.armor.tachidesk.impl.getPageImage
|
||||||
|
import ir.armor.tachidesk.impl.getSource
|
||||||
|
import ir.armor.tachidesk.impl.getSourceList
|
||||||
|
import ir.armor.tachidesk.impl.getThumbnail
|
||||||
|
import ir.armor.tachidesk.impl.installAPK
|
||||||
|
import ir.armor.tachidesk.impl.removeCategory
|
||||||
|
import ir.armor.tachidesk.impl.removeExtension
|
||||||
|
import ir.armor.tachidesk.impl.removeMangaFromCategory
|
||||||
|
import ir.armor.tachidesk.impl.removeMangaFromLibrary
|
||||||
|
import ir.armor.tachidesk.impl.reorderCategory
|
||||||
|
import ir.armor.tachidesk.impl.sourceFilters
|
||||||
|
import ir.armor.tachidesk.impl.sourceGlobalSearch
|
||||||
|
import ir.armor.tachidesk.impl.sourceSearch
|
||||||
|
import ir.armor.tachidesk.impl.updateCategory
|
||||||
|
import ir.armor.tachidesk.server.util.openInBrowser
|
||||||
|
import mu.KotlinLogging
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
fun javalinSetup() {
|
||||||
|
var hasWebUiBundled = false
|
||||||
|
|
||||||
|
val app = Javalin.create { config ->
|
||||||
|
try {
|
||||||
|
Main::class.java.getResource("/react/index.html")
|
||||||
|
hasWebUiBundled = true
|
||||||
|
config.addStaticFiles("/react")
|
||||||
|
config.addSinglePageRoot("/", "/react/index.html")
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
logger.warn("react build files are missing.")
|
||||||
|
hasWebUiBundled = false
|
||||||
|
}
|
||||||
|
config.enableCorsForAllOrigins()
|
||||||
|
}.start(serverConfig.ip, serverConfig.port)
|
||||||
|
if (hasWebUiBundled && serverConfig.initialOpenInBrowserEnabled) {
|
||||||
|
openInBrowser()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.exception(NullPointerException::class.java) { e, ctx ->
|
||||||
|
logger.error("NullPointerException while handling the request", e)
|
||||||
|
ctx.status(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/extension/list") { ctx ->
|
||||||
|
ctx.json(getExtensionList())
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/extension/install/:apkName") { ctx ->
|
||||||
|
val apkName = ctx.pathParam("apkName")
|
||||||
|
|
||||||
|
ctx.status(
|
||||||
|
installAPK(apkName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/extension/uninstall/:apkName") { ctx ->
|
||||||
|
val apkName = ctx.pathParam("apkName")
|
||||||
|
|
||||||
|
removeExtension(apkName)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// icon for extension named `apkName`
|
||||||
|
app.get("/api/v1/extension/icon/:apkName") { ctx ->
|
||||||
|
val apkName = ctx.pathParam("apkName")
|
||||||
|
val result = getExtensionIcon(apkName)
|
||||||
|
|
||||||
|
ctx.result(result.first)
|
||||||
|
ctx.header("content-type", result.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// list of sources
|
||||||
|
app.get("/api/v1/source/list") { ctx ->
|
||||||
|
ctx.json(getSourceList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch source with id `sourceId`
|
||||||
|
app.get("/api/v1/source/:sourceId") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
ctx.json(getSource(sourceId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// popular mangas from source with id `sourceId`
|
||||||
|
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
|
ctx.json(getMangaList(sourceId, pageNum, popular = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// latest mangas from source with id `sourceId`
|
||||||
|
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
|
ctx.json(getMangaList(sourceId, pageNum, popular = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get manga info
|
||||||
|
app.get("/api/v1/manga/:mangaId/") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
ctx.json(getManga(mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// manga thumbnail
|
||||||
|
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
val result = getThumbnail(mangaId)
|
||||||
|
|
||||||
|
ctx.result(result.first)
|
||||||
|
ctx.header("content-type", result.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds the manga to library
|
||||||
|
app.get("api/v1/manga/:mangaId/library") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
addMangaToLibrary(mangaId)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes the manga from the library
|
||||||
|
app.delete("api/v1/manga/:mangaId/library") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
removeMangaFromLibrary(mangaId)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// list manga's categories
|
||||||
|
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
ctx.json(getMangaCategories(mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds the manga to category
|
||||||
|
app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
addMangaToCategory(mangaId, categoryId)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes the manga from the category
|
||||||
|
app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
removeMangaFromCategory(mangaId, categoryId)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
ctx.json(getChapterList(mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||||
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
ctx.json(getChapter(chapterIndex, mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
|
val index = ctx.pathParam("index").toInt()
|
||||||
|
val result = getPageImage(mangaId, chapterIndex, index)
|
||||||
|
|
||||||
|
ctx.result(result.first)
|
||||||
|
ctx.header("content-type", result.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// global search
|
||||||
|
app.get("/api/v1/search/:searchTerm") { ctx ->
|
||||||
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
|
ctx.json(sourceGlobalSearch(searchTerm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// single source search
|
||||||
|
app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
|
ctx.json(sourceSearch(sourceId, searchTerm, pageNum))
|
||||||
|
}
|
||||||
|
|
||||||
|
// source filter list
|
||||||
|
app.get("/api/v1/source/:sourceId/filters/") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
ctx.json(sourceFilters(sourceId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// lists mangas that have no category assigned
|
||||||
|
app.get("/api/v1/library/") { ctx ->
|
||||||
|
ctx.json(getLibraryMangas())
|
||||||
|
}
|
||||||
|
|
||||||
|
// category list
|
||||||
|
app.get("/api/v1/category/") { ctx ->
|
||||||
|
ctx.json(getCategoryList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// category create
|
||||||
|
app.post("/api/v1/category/") { ctx ->
|
||||||
|
val name = ctx.formParam("name")!!
|
||||||
|
createCategory(name)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// category modification
|
||||||
|
app.patch("/api/v1/category/:categoryId") { ctx ->
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
val name = ctx.formParam("name")
|
||||||
|
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
||||||
|
updateCategory(categoryId, name, isLanding)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// category re-ordering
|
||||||
|
app.patch("/api/v1/category/:categoryId/reorder") { ctx ->
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
val from = ctx.formParam("from")!!.toInt()
|
||||||
|
val to = ctx.formParam("to")!!.toInt()
|
||||||
|
reorderCategory(categoryId, from, to)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// category delete
|
||||||
|
app.delete("/api/v1/category/:categoryId") { ctx ->
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
removeCategory(categoryId)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the manga list associated with a category
|
||||||
|
app.get("/api/v1/category/:categoryId") { ctx ->
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
ctx.json(getCategoryMangaList(categoryId))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package ir.armor.tachidesk.server
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import io.github.config4k.getValue
|
||||||
|
import xyz.nulldev.ts.config.ConfigModule
|
||||||
|
|
||||||
|
class ServerConfig(config: Config) : ConfigModule(config) {
|
||||||
|
val ip: String by config
|
||||||
|
val port: Int by config
|
||||||
|
|
||||||
|
// proxy
|
||||||
|
val socksProxy: Boolean by config
|
||||||
|
val socksProxyHost: String by config
|
||||||
|
val socksProxyPort: String by config
|
||||||
|
|
||||||
|
// misc
|
||||||
|
val debugLogsEnabled: Boolean by config
|
||||||
|
val systemTrayEnabled: Boolean by config
|
||||||
|
val initialOpenInBrowserEnabled: Boolean by config
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun register(config: Config) = ServerConfig(config.getConfig("server"))
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
-4
@@ -1,8 +1,18 @@
|
|||||||
package ir.armor.tachidesk
|
package ir.armor.tachidesk.server
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
import eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
|
import ir.armor.tachidesk.Main
|
||||||
import ir.armor.tachidesk.database.makeDataBaseTables
|
import ir.armor.tachidesk.database.makeDataBaseTables
|
||||||
import ir.armor.tachidesk.util.systemTray
|
import ir.armor.tachidesk.server.util.systemTray
|
||||||
|
import mu.KotlinLogging
|
||||||
import net.harawata.appdirs.AppDirsFactory
|
import net.harawata.appdirs.AppDirsFactory
|
||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
@@ -12,6 +22,8 @@ import xyz.nulldev.ts.config.ConfigKodeinModule
|
|||||||
import xyz.nulldev.ts.config.GlobalConfigManager
|
import xyz.nulldev.ts.config.GlobalConfigManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
object applicationDirs {
|
object applicationDirs {
|
||||||
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!!
|
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!!
|
||||||
val extensionsRoot = "$dataRoot/extensions"
|
val extensionsRoot = "$dataRoot/extensions"
|
||||||
@@ -25,12 +37,17 @@ val systemTray by lazy { systemTray() }
|
|||||||
|
|
||||||
val androidCompat by lazy { AndroidCompat() }
|
val androidCompat by lazy { AndroidCompat() }
|
||||||
|
|
||||||
fun serverSetup() {
|
fun applicationSetup() {
|
||||||
// register server config
|
// register server config
|
||||||
GlobalConfigManager.registerModule(
|
GlobalConfigManager.registerModule(
|
||||||
ServerConfig.register(GlobalConfigManager.config)
|
ServerConfig.register(GlobalConfigManager.config)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// set application wide logging level
|
||||||
|
if (serverConfig.debugLogsEnabled) {
|
||||||
|
(mu.KotlinLogging.logger(org.slf4j.Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = Level.DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
// make dirs we need
|
// make dirs we need
|
||||||
listOf(
|
listOf(
|
||||||
applicationDirs.dataRoot,
|
applicationDirs.dataRoot,
|
||||||
@@ -41,10 +58,29 @@ fun serverSetup() {
|
|||||||
File(it).mkdirs()
|
File(it).mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create conf file if doesn't exist
|
||||||
|
try {
|
||||||
|
val dataConfFile = File("${applicationDirs.dataRoot}/server.conf")
|
||||||
|
if (!dataConfFile.exists()) {
|
||||||
|
Main::class.java.getResourceAsStream("/server-reference.conf").use { input ->
|
||||||
|
dataConfFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("Exception while creating initial server.conf:\n", e)
|
||||||
|
}
|
||||||
|
|
||||||
makeDataBaseTables()
|
makeDataBaseTables()
|
||||||
|
|
||||||
// create system tray
|
// create system tray
|
||||||
systemTray
|
if (serverConfig.systemTrayEnabled)
|
||||||
|
try {
|
||||||
|
systemTray
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
// Load config API
|
// Load config API
|
||||||
DI.global.addImport(ConfigKodeinModule().create())
|
DI.global.addImport(ConfigKodeinModule().create())
|
||||||
@@ -53,6 +89,11 @@ fun serverSetup() {
|
|||||||
// start app
|
// start app
|
||||||
androidCompat.startApp(App())
|
androidCompat.startApp(App())
|
||||||
|
|
||||||
|
// Disable jetty's logging
|
||||||
|
System.setProperty("org.eclipse.jetty.util.log.announce", "false")
|
||||||
|
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog")
|
||||||
|
System.setProperty("org.eclipse.jetty.LEVEL", "OFF")
|
||||||
|
|
||||||
// socks proxy settings
|
// socks proxy settings
|
||||||
System.getProperties()["proxySet"] = serverConfig.socksProxy.toString()
|
System.getProperties()["proxySet"] = serverConfig.socksProxy.toString()
|
||||||
System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost
|
System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost
|
||||||
+7
-3
@@ -1,6 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.server.util
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -10,6 +13,7 @@ import dorkbox.systemTray.SystemTray.TrayType
|
|||||||
import dorkbox.util.CacheUtil
|
import dorkbox.util.CacheUtil
|
||||||
import dorkbox.util.Desktop
|
import dorkbox.util.Desktop
|
||||||
import ir.armor.tachidesk.Main
|
import ir.armor.tachidesk.Main
|
||||||
|
import ir.armor.tachidesk.server.serverConfig
|
||||||
import java.awt.event.ActionListener
|
import java.awt.event.ActionListener
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@@ -24,7 +28,7 @@ fun openInBrowser() {
|
|||||||
fun systemTray(): SystemTray? {
|
fun systemTray(): SystemTray? {
|
||||||
try {
|
try {
|
||||||
// ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java
|
// ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java
|
||||||
SystemTray.DEBUG = true; // for test apps, we always want to run in debug mode
|
SystemTray.DEBUG = serverConfig.debugLogsEnabled
|
||||||
if (System.getProperty("os.name").startsWith("Windows"))
|
if (System.getProperty("os.name").startsWith("Windows"))
|
||||||
SystemTray.FORCE_TRAY_TYPE = TrayType.Swing
|
SystemTray.FORCE_TRAY_TYPE = TrayType.Swing
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<!-- encoders are assigned the type
|
||||||
|
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="Exposed" level="ERROR"/>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
# Server ip and port bindings
|
# Server ip and port bindings
|
||||||
server.ip = 0.0.0.0
|
server.ip = "0.0.0.0"
|
||||||
server.port = 4567
|
server.port = 4567
|
||||||
|
|
||||||
# Socks5 proxy
|
# Socks5 proxy
|
||||||
server.socksProxy = false
|
server.socksProxy = false
|
||||||
server.socksProxyHost = ""
|
server.socksProxyHost = ""
|
||||||
server.socksProxyPort = ""
|
server.socksProxyPort = ""
|
||||||
|
|
||||||
|
# misc
|
||||||
|
server.debugLogsEnabled = false
|
||||||
|
server.systemTrayEnabled = true
|
||||||
|
server.initialOpenInBrowserEnabled = true
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"@types/react-lazyload": "^3.1.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"fontsource-roboto": "^4.0.0",
|
"fontsource-roboto": "^4.0.0",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-beautiful-dnd": "^13.0.0",
|
"react-beautiful-dnd": "^13.0.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
|
"react-lazyload": "^3.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.1",
|
"react-scripts": "4.0.1",
|
||||||
"web-vitals": "^0.2.4"
|
"web-vitals": "^0.2.4"
|
||||||
|
|||||||
+21
-5
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -27,9 +30,12 @@ import useLocalStorage from './util/useLocalStorage';
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const [title, setTitle] = useState<string>('Tachidesk');
|
const [title, setTitle] = useState<string>('Tachidesk');
|
||||||
const [action, setAction] = useState<any>(<div />);
|
const [action, setAction] = useState<any>(<div />);
|
||||||
|
const [override, setOverride] = useState<INavbarOverride>({ status: false, value: <div /> });
|
||||||
|
|
||||||
const [darkTheme, setDarkTheme] = useLocalStorage<boolean>('darkTheme', true);
|
const [darkTheme, setDarkTheme] = useLocalStorage<boolean>('darkTheme', true);
|
||||||
|
|
||||||
const navBarContext = {
|
const navBarContext = {
|
||||||
title, setTitle, action, setAction,
|
title, setTitle, action, setAction, override, setOverride,
|
||||||
};
|
};
|
||||||
const darkThemeContext = { darkTheme, setDarkTheme };
|
const darkThemeContext = { darkTheme, setDarkTheme };
|
||||||
|
|
||||||
@@ -63,7 +69,12 @@ export default function App() {
|
|||||||
<NavbarContext.Provider value={navBarContext}>
|
<NavbarContext.Provider value={navBarContext}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<Container maxWidth={false} disableGutters>
|
<Container
|
||||||
|
id="appMainContainer"
|
||||||
|
maxWidth={false}
|
||||||
|
disableGutters
|
||||||
|
style={{ paddingTop: '64px' }}
|
||||||
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/sources/:sourceId/search/">
|
<Route path="/sources/:sourceId/search/">
|
||||||
<Search />
|
<Search />
|
||||||
@@ -80,8 +91,8 @@ export default function App() {
|
|||||||
<Route path="/sources">
|
<Route path="/sources">
|
||||||
<Sources />
|
<Sources />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/manga/:mangaId/chapter/:chapterId">
|
<Route path="/manga/:mangaId/chapter/:chapterNum">
|
||||||
<Reader />
|
<></>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/manga/:id">
|
<Route path="/manga/:id">
|
||||||
<Manga />
|
<Manga />
|
||||||
@@ -106,6 +117,11 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/manga/:mangaId/chapter/:chapterIndex">
|
||||||
|
<Reader />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
</NavbarContext.Provider>
|
</NavbarContext.Provider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -85,6 +88,14 @@ export default function CategorySelect(props: IProps) {
|
|||||||
<DialogTitle>Set categories</DialogTitle>
|
<DialogTitle>Set categories</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
|
{categoryInfos.length === 0
|
||||||
|
&& (
|
||||||
|
<span>
|
||||||
|
No categories found!
|
||||||
|
<br />
|
||||||
|
You should make some from settings.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{categoryInfos.map((categoryInfo) => (
|
{categoryInfos.map((categoryInfo) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={(
|
control={(
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -8,6 +12,7 @@ import Card from '@material-ui/core/Card';
|
|||||||
import CardContent from '@material-ui/core/CardContent';
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -41,6 +46,7 @@ interface IProps{
|
|||||||
|
|
||||||
export default function ChapterCard(props: IProps) {
|
export default function ChapterCard(props: IProps) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const history = useHistory();
|
||||||
const { chapter } = props;
|
const { chapter } = props;
|
||||||
|
|
||||||
const dateStr = chapter.date_upload && new Date(chapter.date_upload).toISOString().slice(0, 10);
|
const dateStr = chapter.date_upload && new Date(chapter.date_upload).toISOString().slice(0, 10);
|
||||||
@@ -63,9 +69,19 @@ export default function ChapterCard(props: IProps) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex' }}>
|
<Link
|
||||||
<Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `/manga/${chapter.mangaId}/chapter/${chapter.id}`; }}>open</Button>
|
to={`/manga/${chapter.mangaId}/chapter/${chapter.chapterIndex}`}
|
||||||
</div>
|
style={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
>
|
||||||
|
open
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
|
/* eslint-disable react/require-default-props */
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
loading: {
|
||||||
|
margin: '10px auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
shouldRender: boolean | (() => boolean)
|
||||||
|
children?: React.ReactNode
|
||||||
|
component?: string | React.FunctionComponent<any> | React.ComponentClass<any, any>
|
||||||
|
componentProps?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoadingPlaceholder(props: IProps) {
|
||||||
|
const {
|
||||||
|
children, shouldRender, component, componentProps,
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const condition = shouldRender instanceof Function ? shouldRender() : shouldRender;
|
||||||
|
|
||||||
|
if (condition) {
|
||||||
|
if (component) {
|
||||||
|
return React.createElement(component, componentProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.loading}>
|
||||||
|
<CircularProgress thickness={5} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ const useStyles = makeStyles({
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: IManga
|
manga: IMangaCard
|
||||||
}
|
}
|
||||||
const MangaCard = React.forwardRef((props: IProps, ref) => {
|
const MangaCard = React.forwardRef((props: IProps, ref) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,56 +1,189 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import { Button, createStyles, makeStyles } from '@material-ui/core';
|
import { makeStyles } from '@material-ui/core';
|
||||||
import React, { useState } from 'react';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
import { Theme } from '@material-ui/core/styles';
|
||||||
|
import FavoriteIcon from '@material-ui/icons/Favorite';
|
||||||
|
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
|
||||||
|
import FilterListIcon from '@material-ui/icons/FilterList';
|
||||||
|
import PublicIcon from '@material-ui/icons/Public';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import NavbarContext from '../context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from '../util/client';
|
||||||
|
import useLocalStorage from '../util/useLocalStorage';
|
||||||
import CategorySelect from './CategorySelect';
|
import CategorySelect from './CategorySelect';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => createStyles({
|
const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({
|
||||||
root: {
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
position: 'sticky',
|
||||||
|
top: '64px',
|
||||||
|
left: '0px',
|
||||||
|
width: '50vw',
|
||||||
|
height: 'calc(100vh - 64px)',
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
padding: '10px',
|
||||||
|
// [theme.breakpoints.up('md')]: {
|
||||||
|
// minWidth: '50%',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
leftRight: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row-reverse',
|
},
|
||||||
|
leftSide: {
|
||||||
|
'& img': {
|
||||||
|
borderRadius: 4,
|
||||||
|
maxWidth: '100%',
|
||||||
|
minWidth: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
},
|
||||||
|
maxWidth: '50%',
|
||||||
|
// [theme.breakpoints.up('md')]: {
|
||||||
|
// minWidth: '100px',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
rightSide: {
|
||||||
|
marginLeft: 15,
|
||||||
|
maxWidth: '100%',
|
||||||
|
'& span': {
|
||||||
|
fontWeight: '400',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: '1.3em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-around',
|
||||||
'& button': {
|
'& button': {
|
||||||
marginLeft: 10,
|
color: inLibrary === 'In Library' ? '#2196f3' : 'inherit',
|
||||||
|
},
|
||||||
|
'& span': {
|
||||||
|
display: 'block',
|
||||||
|
fontSize: '0.85em',
|
||||||
|
},
|
||||||
|
'& a': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: '#858585',
|
||||||
|
'& button': {
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
paddingLeft: '10px',
|
||||||
|
paddingRight: '10px',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: '1.2em',
|
||||||
|
// maxWidth: '50%',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: '1.3em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
'& h4': {
|
||||||
|
marginTop: '1em',
|
||||||
|
marginBottom: 0,
|
||||||
|
},
|
||||||
|
'& p': {
|
||||||
|
textAlign: 'justify',
|
||||||
|
textJustify: 'inter-word',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
genre: {
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
'& h5': {
|
||||||
|
border: '2px solid #2196f3',
|
||||||
|
borderRadius: '1.13em',
|
||||||
|
marginRight: '1em',
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: '10px',
|
||||||
|
padding: '0.3em',
|
||||||
|
color: '#2196f3',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface IProps{
|
interface IProps{
|
||||||
manga: IManga
|
manga: IManga
|
||||||
source: ISource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceName(source: ISource) {
|
function getSourceName(source: ISource) {
|
||||||
if (source.name !== null) { return source.name; }
|
if (source.name !== null) {
|
||||||
|
return `${source.name} (${source.lang.toLocaleUpperCase()})`;
|
||||||
|
}
|
||||||
return source.id;
|
return source.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getValueOrUnknown(val: string) {
|
||||||
|
return val || 'UNKNOWN';
|
||||||
|
}
|
||||||
|
|
||||||
export default function MangaDetails(props: IProps) {
|
export default function MangaDetails(props: IProps) {
|
||||||
const classes = useStyles();
|
const { setAction } = useContext(NavbarContext);
|
||||||
const { manga, source } = props;
|
|
||||||
|
const { manga } = props;
|
||||||
const [inLibrary, setInLibrary] = useState<string>(
|
const [inLibrary, setInLibrary] = useState<string>(
|
||||||
manga.inLibrary ? 'In Library' : 'Not In Library',
|
manga.inLibrary ? 'In Library' : 'Add To Library',
|
||||||
);
|
);
|
||||||
|
|
||||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState<boolean>(false);
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inLibrary === 'In Library') {
|
||||||
|
setAction(
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setCategoryDialogOpen(true)}
|
||||||
|
aria-label="display more actions"
|
||||||
|
edge="end"
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<FilterListIcon />
|
||||||
|
</IconButton>
|
||||||
|
<CategorySelect
|
||||||
|
open={categoryDialogOpen}
|
||||||
|
setOpen={setCategoryDialogOpen}
|
||||||
|
mangaId={manga.id}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
|
||||||
|
);
|
||||||
|
} else { setAction(<></>); }
|
||||||
|
}, [inLibrary, categoryDialogOpen]);
|
||||||
|
|
||||||
|
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||||
|
|
||||||
|
const classes = useStyles(inLibrary)();
|
||||||
|
|
||||||
function addToLibrary() {
|
function addToLibrary() {
|
||||||
setInLibrary('adding');
|
// setInLibrary('adding');
|
||||||
client.get(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
client.get(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
||||||
setInLibrary('In Library');
|
setInLibrary('In Library');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFromLibrary() {
|
function removeFromLibrary() {
|
||||||
setInLibrary('removing');
|
// setInLibrary('removing');
|
||||||
client.delete(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
client.delete(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
||||||
setInLibrary('Not In Library');
|
setInLibrary('Add To Library');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleButtonClick() {
|
function handleButtonClick() {
|
||||||
if (inLibrary === 'Not In Library') {
|
if (inLibrary === 'Add To Library') {
|
||||||
addToLibrary();
|
addToLibrary();
|
||||||
} else {
|
} else {
|
||||||
removeFromLibrary();
|
removeFromLibrary();
|
||||||
@@ -58,26 +191,64 @@ export default function MangaDetails(props: IProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={classes.root}>
|
||||||
<h1>
|
<div className={classes.top}>
|
||||||
{manga.title}
|
<div className={classes.leftRight}>
|
||||||
</h1>
|
<div className={classes.leftSide}>
|
||||||
<h3>
|
<img src={serverAddress + manga.thumbnailUrl} alt="Manga Thumbnail" />
|
||||||
Source:
|
</div>
|
||||||
{' '}
|
<div className={classes.rightSide}>
|
||||||
{getSourceName(source)}
|
<h1>
|
||||||
</h3>
|
{manga.title}
|
||||||
<div className={classes.root}>
|
</h1>
|
||||||
<Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button>
|
<h3>
|
||||||
{inLibrary === 'In Library'
|
Author:
|
||||||
&& <Button variant="outlined" onClick={() => setCategoryDialogOpen(true)}>Edit Categories</Button>}
|
{' '}
|
||||||
|
<span>{getValueOrUnknown(manga.author)}</span>
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Artist:
|
||||||
|
{' '}
|
||||||
|
<span>{getValueOrUnknown(manga.artist)}</span>
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Status:
|
||||||
|
{' '}
|
||||||
|
{manga.status}
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Source:
|
||||||
|
{' '}
|
||||||
|
{getSourceName(manga.source)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.buttons}>
|
||||||
|
<div>
|
||||||
|
<IconButton onClick={() => handleButtonClick()}>
|
||||||
|
{inLibrary === 'In Library' && <FavoriteIcon />}
|
||||||
|
{inLibrary !== 'In Library' && <FavoriteBorderIcon />}
|
||||||
|
<span>{inLibrary}</span>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
{ /* eslint-disable-next-line react/jsx-no-target-blank */ }
|
||||||
|
<a href={manga.url} target="_blank">
|
||||||
|
<IconButton>
|
||||||
|
<PublicIcon />
|
||||||
|
<span>Open Site</span>
|
||||||
|
</IconButton>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.bottom}>
|
||||||
|
<div className={classes.description}>
|
||||||
|
<h4>About</h4>
|
||||||
|
<p>{manga.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className={classes.genre}>
|
||||||
|
{manga.genre.split(', ').map((g) => <h5 key={g}>{g}</h5>)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CategorySelect
|
|
||||||
open={categoryDialogOpen}
|
|
||||||
setOpen={setCategoryDialogOpen}
|
|
||||||
mangaId={manga.id}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -7,7 +10,7 @@ import Grid from '@material-ui/core/Grid';
|
|||||||
import MangaCard from './MangaCard';
|
import MangaCard from './MangaCard';
|
||||||
|
|
||||||
interface IProps{
|
interface IProps{
|
||||||
mangas: IManga[]
|
mangas: IMangaCard[]
|
||||||
message?: string
|
message?: string
|
||||||
hasNextPage: boolean
|
hasNextPage: boolean
|
||||||
lastPageNum: number
|
lastPageNum: number
|
||||||
@@ -48,7 +51,7 @@ export default function MangaGrid(props: IProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} xs={12} style={{ margin: 0, padding: '5px' }}>
|
<Grid container spacing={1} style={{ margin: 0, width: '100%', padding: '5px' }}>
|
||||||
{mapped}
|
{mapped}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/*
|
||||||
// TODO: remove above!
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import MoreIcon from '@material-ui/icons/MoreVert';
|
|
||||||
import AppBar from '@material-ui/core/AppBar';
|
import AppBar from '@material-ui/core/AppBar';
|
||||||
import Toolbar from '@material-ui/core/Toolbar';
|
import Toolbar from '@material-ui/core/Toolbar';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import MenuIcon from '@material-ui/icons/Menu';
|
import MenuIcon from '@material-ui/icons/Menu';
|
||||||
import MenuItem from '@material-ui/core/MenuItem';
|
|
||||||
import Menu from '@material-ui/core/Menu';
|
|
||||||
|
|
||||||
import TemporaryDrawer from './TemporaryDrawer';
|
|
||||||
import NavBarContext from '../context/NavbarContext';
|
import NavBarContext from '../context/NavbarContext';
|
||||||
import DarkTheme from '../context/DarkTheme';
|
import DarkTheme from '../context/DarkTheme';
|
||||||
|
import TemporaryDrawer from './TemporaryDrawer';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -31,89 +28,40 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// const theme = createMuiTheme({
|
|
||||||
// overrides: {
|
|
||||||
// MuiAppBar: {
|
|
||||||
// colorPrimary: { backgroundColor: '#FFC0CB' },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// palette: { type: 'dark' },
|
|
||||||
// });
|
|
||||||
|
|
||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const { title, action, override } = useContext(NavBarContext);
|
||||||
const { title, action } = useContext(NavBarContext);
|
|
||||||
const open = Boolean(anchorEl);
|
|
||||||
|
|
||||||
const { darkTheme } = useContext(DarkTheme);
|
const { darkTheme } = useContext(DarkTheme);
|
||||||
|
|
||||||
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<>
|
||||||
<AppBar position="static" color={darkTheme ? 'default' : 'primary'}>
|
{override.status && override.value}
|
||||||
<Toolbar>
|
{!override.status
|
||||||
<IconButton
|
&& (
|
||||||
edge="start"
|
<div className={classes.root}>
|
||||||
className={classes.menuButton}
|
<AppBar position="fixed" color={darkTheme ? 'default' : 'primary'}>
|
||||||
color="inherit"
|
<Toolbar>
|
||||||
aria-label="menu"
|
<IconButton
|
||||||
disableRipple
|
edge="start"
|
||||||
onClick={() => setDrawerOpen(true)}
|
className={classes.menuButton}
|
||||||
>
|
color="inherit"
|
||||||
<MenuIcon />
|
aria-label="menu"
|
||||||
</IconButton>
|
disableRipple
|
||||||
<Typography variant="h6" className={classes.title}>
|
onClick={() => setDrawerOpen(true)}
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
{action}
|
|
||||||
{/* <IconButton
|
|
||||||
onClick={handleMenu}
|
|
||||||
aria-label="display more actions"
|
|
||||||
edge="end"
|
|
||||||
color="inherit"
|
|
||||||
>
|
|
||||||
<FilterListIcon />
|
|
||||||
</IconButton> */}
|
|
||||||
{/* <Menu
|
|
||||||
id="menu-appbar"
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: 'top',
|
|
||||||
horizontal: 'right',
|
|
||||||
}}
|
|
||||||
keepMounted
|
|
||||||
transformOrigin={{
|
|
||||||
vertical: 'top',
|
|
||||||
horizontal: 'right',
|
|
||||||
}}
|
|
||||||
open={open}
|
|
||||||
onClose={handleClose}
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => { setDarkTheme(true); handleClose(); }}
|
|
||||||
>
|
>
|
||||||
Dark Theme
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
</MenuItem>
|
<Typography variant="h6" className={classes.title}>
|
||||||
<MenuItem
|
{title}
|
||||||
onClick={() => { setDarkTheme(false); handleClose(); }}
|
</Typography>
|
||||||
>
|
{action}
|
||||||
Light Theme
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
</MenuItem>
|
<TemporaryDrawer drawerOpen={drawerOpen} setDrawerOpen={setDrawerOpen} />
|
||||||
</Menu> */}
|
</div>
|
||||||
</Toolbar>
|
)}
|
||||||
</AppBar>
|
</>
|
||||||
<TemporaryDrawer drawerOpen={drawerOpen} setDrawerOpen={setDrawerOpen} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/* eslint-disable react/no-unused-prop-types */
|
||||||
|
/* 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 CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import LazyLoad from 'react-lazyload';
|
||||||
|
import { IReaderSettings } from './ReaderNavBar';
|
||||||
|
|
||||||
|
const useStyles = (settings: IReaderSettings) => makeStyles({
|
||||||
|
loading: {
|
||||||
|
margin: '100px auto',
|
||||||
|
height: '100vh',
|
||||||
|
},
|
||||||
|
loadingImage: {
|
||||||
|
padding: settings.staticNav ? 'calc(50vh - 40px) calc(50vw - 340px)' : 'calc(50vh - 40px) calc(50vw - 40px)',
|
||||||
|
height: '100vh',
|
||||||
|
width: '200px',
|
||||||
|
backgroundColor: '#525252',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: settings.continuesPageGap ? '15px' : 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
src: string
|
||||||
|
index: number
|
||||||
|
setCurPage: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
settings: IReaderSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
function LazyImage(props: IProps) {
|
||||||
|
const {
|
||||||
|
src, index, setCurPage, settings,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const classes = useStyles(settings)();
|
||||||
|
const [imageSrc, setImagsrc] = useState<string>('');
|
||||||
|
const ref = useRef<HTMLImageElement>(null);
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (ref.current) {
|
||||||
|
const rect = ref.current.getBoundingClientRect();
|
||||||
|
if (rect.y < 0 && rect.y + rect.height > 0) {
|
||||||
|
setCurPage(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = src;
|
||||||
|
|
||||||
|
img.onload = () => setImagsrc(src);
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
if (imageSrc.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className={classes.loadingImage}>
|
||||||
|
<CircularProgress thickness={5} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className={classes.image}
|
||||||
|
ref={ref}
|
||||||
|
src={imageSrc}
|
||||||
|
alt={`Page #${index}`}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page(props: IProps) {
|
||||||
|
const {
|
||||||
|
src, index, setCurPage, settings,
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles(settings)();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: '0 auto' }}>
|
||||||
|
<LazyLoad
|
||||||
|
offset={window.innerHeight}
|
||||||
|
placeholder={(
|
||||||
|
<div className={classes.loading}>
|
||||||
|
<CircularProgress thickness={5} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
once
|
||||||
|
>
|
||||||
|
<LazyImage
|
||||||
|
src={src}
|
||||||
|
index={index}
|
||||||
|
setCurPage={setCurPage}
|
||||||
|
settings={settings}
|
||||||
|
/>
|
||||||
|
</LazyLoad>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
/* 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 IconButton from '@material-ui/core/IconButton';
|
||||||
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
|
import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
|
||||||
|
import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
|
||||||
|
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||||
|
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
||||||
|
import { makeStyles, Theme, useTheme } from '@material-ui/core/styles';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useHistory, Link } from 'react-router-dom';
|
||||||
|
import Slide from '@material-ui/core/Slide';
|
||||||
|
import Fade from '@material-ui/core/Fade';
|
||||||
|
import Zoom from '@material-ui/core/Zoom';
|
||||||
|
import { Switch } from '@material-ui/core';
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
|
||||||
|
// main container and root div need to change classes...
|
||||||
|
AppMainContainer: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
AppRootElment: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
|
||||||
|
root: {
|
||||||
|
position: settings.staticNav ? 'sticky' : 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
minWidth: '300px',
|
||||||
|
height: '100vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
backgroundColor: '#0a0b0b',
|
||||||
|
|
||||||
|
'& header': {
|
||||||
|
backgroundColor: '#363b3d',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '64px',
|
||||||
|
paddingLeft: '24px',
|
||||||
|
paddingRight: '24px',
|
||||||
|
|
||||||
|
transition: 'left 2s ease',
|
||||||
|
|
||||||
|
'& button': {
|
||||||
|
flexGrow: 0,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'& button:nth-child(1)': {
|
||||||
|
marginRight: '16px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& button:nth-child(3)': {
|
||||||
|
marginRight: '-12px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& h1': {
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& hr': {
|
||||||
|
margin: '0 16px',
|
||||||
|
height: '1px',
|
||||||
|
border: '0',
|
||||||
|
backgroundColor: 'rgb(38, 41, 43)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
navigation: {
|
||||||
|
margin: '0 16px',
|
||||||
|
|
||||||
|
'& > span:nth-child(1)': {
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'block',
|
||||||
|
marginTop: '16px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& $navigationChapters': {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr',
|
||||||
|
gridTemplateAreas: '"prev next"',
|
||||||
|
gridColumnGap: '5px',
|
||||||
|
margin: '10px 0',
|
||||||
|
|
||||||
|
'& a': {
|
||||||
|
flexGrow: 1,
|
||||||
|
textDecoration: 'none',
|
||||||
|
|
||||||
|
'& button': {
|
||||||
|
width: '100%',
|
||||||
|
padding: '5px 8px',
|
||||||
|
textTransform: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
navigationChapters: {}, // dummy rule
|
||||||
|
|
||||||
|
settingsCollapsseHeader: {
|
||||||
|
'& span': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
openDrawerButton: {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0 + 20,
|
||||||
|
left: 10 + 20,
|
||||||
|
height: '40px',
|
||||||
|
width: '40px',
|
||||||
|
borderRadius: 5,
|
||||||
|
backgroundColor: 'black',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface IReaderSettings{
|
||||||
|
staticNav: boolean
|
||||||
|
showPageNumber: boolean
|
||||||
|
continuesPageGap: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultReaderSettings = () => ({
|
||||||
|
staticNav: false,
|
||||||
|
showPageNumber: true,
|
||||||
|
continuesPageGap: false,
|
||||||
|
} as IReaderSettings);
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
settings: IReaderSettings
|
||||||
|
setSettings: React.Dispatch<React.SetStateAction<IReaderSettings>>
|
||||||
|
manga: IManga | IMangaCard
|
||||||
|
chapter: IChapter | IPartialChpter
|
||||||
|
curPage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReaderNavBar(props: IProps) {
|
||||||
|
const { title } = useContext(NavBarContext);
|
||||||
|
const { darkTheme } = useContext(DarkTheme);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings, setSettings, manga, chapter, curPage,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false || settings.staticNav);
|
||||||
|
const [drawerVisible, setDrawerVisible] = useState(false || settings.staticNav);
|
||||||
|
const [hideOpenButton, setHideOpenButton] = useState(false);
|
||||||
|
const [prevScrollPos, setPrevScrollPos] = useState(0);
|
||||||
|
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(false);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const classes = useStyles(settings)();
|
||||||
|
|
||||||
|
const setSettingValue = (key: string, value: any) => setSettings({ ...settings, [key]: value });
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const currentScrollPos = window.pageYOffset;
|
||||||
|
|
||||||
|
if (Math.abs(currentScrollPos - prevScrollPos) > 20) {
|
||||||
|
setHideOpenButton(currentScrollPos > prevScrollPos);
|
||||||
|
setPrevScrollPos(currentScrollPos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
const rootEl = document.querySelector('#root')!;
|
||||||
|
const mainContainer = document.querySelector('#appMainContainer')!;
|
||||||
|
|
||||||
|
rootEl.classList.add(classes.AppRootElment);
|
||||||
|
mainContainer.classList.add(classes.AppMainContainer);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
rootEl.classList.remove(classes.AppRootElment);
|
||||||
|
mainContainer.classList.remove(classes.AppMainContainer);
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, [handleScroll]);// handleScroll changes on every render
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ClickAwayListener onClickAway={() => (drawerVisible && setDrawerOpen(false))}>
|
||||||
|
<Slide
|
||||||
|
direction="right"
|
||||||
|
in={drawerOpen}
|
||||||
|
timeout={200}
|
||||||
|
appear={false}
|
||||||
|
mountOnEnter
|
||||||
|
unmountOnExit
|
||||||
|
onEntered={() => setDrawerVisible(true)}
|
||||||
|
onExited={() => setDrawerVisible(false)}
|
||||||
|
>
|
||||||
|
<div className={classes.root}>
|
||||||
|
<header>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
disableRipple
|
||||||
|
onClick={() => history.push(`/manga/${manga.id}`)}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h1">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
{!settings.staticNav
|
||||||
|
&& (
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
disableRipple
|
||||||
|
onClick={() => setDrawerOpen(false)}
|
||||||
|
>
|
||||||
|
<KeyboardArrowLeftIcon />
|
||||||
|
</IconButton>
|
||||||
|
) }
|
||||||
|
</header>
|
||||||
|
<ListItem ContainerComponent="div" className={classes.settingsCollapsseHeader}>
|
||||||
|
<ListItemText primary="Reader Settings" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
disableRipple
|
||||||
|
disableFocusRipple
|
||||||
|
onClick={() => setSettingsCollapseOpen(!settingsCollapseOpen)}
|
||||||
|
>
|
||||||
|
{settingsCollapseOpen && <KeyboardArrowUpIcon />}
|
||||||
|
{!settingsCollapseOpen && <KeyboardArrowDownIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={settingsCollapseOpen} timeout="auto" unmountOnExit>
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary="Static Navigation" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.staticNav}
|
||||||
|
onChange={(e) => setSettingValue('staticNav', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary="Show page number" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.showPageNumber}
|
||||||
|
onChange={(e) => setSettingValue('showPageNumber', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary="Continues Page gap" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.continuesPageGap}
|
||||||
|
onChange={(e) => setSettingValue('continuesPageGap', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
<hr />
|
||||||
|
<div className={classes.navigation}>
|
||||||
|
<span>
|
||||||
|
Currently on page
|
||||||
|
{' '}
|
||||||
|
{curPage + 1}
|
||||||
|
{' '}
|
||||||
|
of
|
||||||
|
{' '}
|
||||||
|
{chapter.pageCount}
|
||||||
|
</span>
|
||||||
|
<div className={classes.navigationChapters}>
|
||||||
|
{chapter.chapterIndex > 1
|
||||||
|
&& (
|
||||||
|
<Link
|
||||||
|
style={{ gridArea: 'prev' }}
|
||||||
|
to={`/manga/${manga.id}/chapter/${chapter.chapterIndex - 1}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<KeyboardArrowLeftIcon />}
|
||||||
|
>
|
||||||
|
Chapter
|
||||||
|
{' '}
|
||||||
|
{chapter.chapterIndex - 1}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{chapter.chapterIndex < chapter.chapterCount
|
||||||
|
&& (
|
||||||
|
<Link
|
||||||
|
style={{ gridArea: 'next' }}
|
||||||
|
to={`/manga/${manga.id}/chapter/${chapter.chapterIndex + 1}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
endIcon={<KeyboardArrowRightIcon />}
|
||||||
|
>
|
||||||
|
Chapter
|
||||||
|
{' '}
|
||||||
|
{chapter.chapterIndex + 1}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
</ClickAwayListener>
|
||||||
|
<Zoom in={!drawerOpen}>
|
||||||
|
<Fade in={!hideOpenButton}>
|
||||||
|
<IconButton
|
||||||
|
className={classes.openDrawerButton}
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
disableRipple
|
||||||
|
disableFocusRipple
|
||||||
|
onClick={() => setDrawerOpen(true)}
|
||||||
|
>
|
||||||
|
<KeyboardArrowRightIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Fade>
|
||||||
|
</Zoom>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -27,68 +30,54 @@ interface IProps {
|
|||||||
export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const sideList = (side: 'left') => (
|
|
||||||
<div
|
|
||||||
className={classes.list}
|
|
||||||
role="presentation"
|
|
||||||
onClick={() => setDrawerOpen(false)}
|
|
||||||
onKeyDown={() => setDrawerOpen(false)}
|
|
||||||
>
|
|
||||||
<List>
|
|
||||||
<Link to="/library" style={{ color: 'inherit', textDecoration: 'none' }}>
|
|
||||||
<ListItem button key="Library">
|
|
||||||
<ListItemIcon>
|
|
||||||
<InboxIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Library" />
|
|
||||||
</ListItem>
|
|
||||||
</Link>
|
|
||||||
<Link to="/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
|
|
||||||
<ListItem button key="Extensions">
|
|
||||||
<ListItemIcon>
|
|
||||||
<InboxIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Extensions" />
|
|
||||||
</ListItem>
|
|
||||||
</Link>
|
|
||||||
<Link to="/sources" style={{ color: 'inherit', textDecoration: 'none' }}>
|
|
||||||
<ListItem button key="Sources">
|
|
||||||
<ListItemIcon>
|
|
||||||
<InboxIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Sources" />
|
|
||||||
</ListItem>
|
|
||||||
</Link>
|
|
||||||
<Link to="/settings" style={{ color: 'inherit', textDecoration: 'none' }}>
|
|
||||||
<ListItem button key="settings">
|
|
||||||
<ListItemIcon>
|
|
||||||
<InboxIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Settings" />
|
|
||||||
</ListItem>
|
|
||||||
</Link>
|
|
||||||
{/* <Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
|
|
||||||
<ListItem button key="Search">
|
|
||||||
<ListItemIcon>
|
|
||||||
<InboxIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Global Search" />
|
|
||||||
</ListItem>
|
|
||||||
</Link> */}
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Drawer
|
<Drawer
|
||||||
BackdropProps={{ invisible: true }}
|
|
||||||
open={drawerOpen}
|
open={drawerOpen}
|
||||||
anchor="left"
|
anchor="left"
|
||||||
onClose={() => setDrawerOpen(false)}
|
onClose={() => setDrawerOpen(false)}
|
||||||
>
|
>
|
||||||
{sideList('left')}
|
<div
|
||||||
|
className={classes.list}
|
||||||
|
role="presentation"
|
||||||
|
onClick={() => setDrawerOpen(false)}
|
||||||
|
onKeyDown={() => setDrawerOpen(false)}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
<Link to="/library" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="Library">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Library" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="Extensions">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Extensions" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/sources" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="Sources">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Sources" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/settings" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="settings">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Settings" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
@@ -9,6 +12,8 @@ type ContextType = {
|
|||||||
setTitle: React.Dispatch<React.SetStateAction<string>>
|
setTitle: React.Dispatch<React.SetStateAction<string>>
|
||||||
action: any
|
action: any
|
||||||
setAction: React.Dispatch<React.SetStateAction<any>>
|
setAction: React.Dispatch<React.SetStateAction<any>>
|
||||||
|
override: INavbarOverride
|
||||||
|
setOverride: React.Dispatch<React.SetStateAction<INavbarOverride>>
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavBarContext = React.createContext<ContextType>({
|
const NavBarContext = React.createContext<ContextType>({
|
||||||
@@ -16,6 +21,8 @@ const NavBarContext = React.createContext<ContextType>({
|
|||||||
setTitle: ():void => {},
|
setTitle: ():void => {},
|
||||||
action: <div />,
|
action: <div />,
|
||||||
setAction: ():void => {},
|
setAction: ():void => {},
|
||||||
|
override: { status: false, value: <div /> },
|
||||||
|
setOverride: ():void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default NavBarContext;
|
export default NavBarContext;
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
Vendored
+4
-1
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,55 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React, { useEffect, useState, useContext } from 'react';
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
|
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
import ChapterCard from '../components/ChapterCard';
|
import ChapterCard from '../components/ChapterCard';
|
||||||
import MangaDetails from '../components/MangaDetails';
|
import MangaDetails from '../components/MangaDetails';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from '../context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from '../util/client';
|
||||||
|
import LoadingPlaceholder from '../components/LoadingPlaceholder';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
chapters: {
|
||||||
|
listStyle: 'none',
|
||||||
|
padding: 0,
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '50vw',
|
||||||
|
height: 'calc(100vh - 64px)',
|
||||||
|
overflowY: 'auto',
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
margin: '10px 0',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export default function Manga() {
|
export default function Manga() {
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const classes = useStyles();
|
||||||
useEffect(() => { setTitle('Manga'); setAction(<></>); }, []);
|
|
||||||
|
const { setTitle } = useContext(NavbarContext);
|
||||||
|
useEffect(() => { setTitle('Manga'); }, []); // delegate setting topbar action to MangaDetails
|
||||||
|
|
||||||
const { id } = useParams<{id: string}>();
|
const { id } = useParams<{id: string}>();
|
||||||
|
|
||||||
const [manga, setManga] = useState<IManga>();
|
const [manga, setManga] = useState<IManga>();
|
||||||
const [source, setSource] = useState<ISource>();
|
|
||||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,32 +61,31 @@ export default function Manga() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (manga !== undefined) {
|
|
||||||
client.get(`/api/v1/source/${manga.sourceId}`)
|
|
||||||
.then((response) => response.data)
|
|
||||||
.then((data: ISource) => {
|
|
||||||
setSource(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [manga]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
client.get(`/api/v1/manga/${id}/chapters`)
|
client.get(`/api/v1/manga/${id}/chapters`)
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
.then((data) => setChapters(data));
|
.then((data) => setChapters(data));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const chapterCards = chapters.map((chapter) => (
|
const chapterCards = (
|
||||||
<ol style={{ listStyle: 'none', padding: 0 }}>
|
<LoadingPlaceholder
|
||||||
<ChapterCard chapter={chapter} />
|
shouldRender={chapters.length > 0}
|
||||||
</ol>
|
>
|
||||||
));
|
<ol className={classes.chapters}>
|
||||||
|
{chapters.map((chapter) => (<ChapterCard chapter={chapter} />))}
|
||||||
|
</ol>
|
||||||
|
</LoadingPlaceholder>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={classes.root}>
|
||||||
{(manga && source) && <MangaDetails manga={manga} source={source} />}
|
<LoadingPlaceholder
|
||||||
|
shouldRender={manga !== undefined}
|
||||||
|
component={MangaDetails}
|
||||||
|
componentProps={{ manga }}
|
||||||
|
/>
|
||||||
{chapterCards}
|
{chapterCards}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,121 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import Page from '../components/Page';
|
||||||
|
import ReaderNavBar, { defaultReaderSettings, IReaderSettings } from '../components/ReaderNavBar';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from '../context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from '../util/client';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from '../util/useLocalStorage';
|
||||||
|
|
||||||
const style = {
|
const useStyles = (settings: IReaderSettings) => makeStyles({
|
||||||
display: 'flex',
|
reader: {
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
flexDirection: 'column',
|
||||||
margin: '0 auto',
|
justifyContent: 'center',
|
||||||
backgroundColor: '#343a40',
|
margin: '0 auto',
|
||||||
} as React.CSSProperties;
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
margin: '50px auto',
|
||||||
|
},
|
||||||
|
|
||||||
|
pageNumber: {
|
||||||
|
display: settings.showPageNumber ? 'block' : 'none',
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '50px',
|
||||||
|
right: settings.staticNav ? 'calc((100vw - 325px)/2)' : 'calc((100vw - 25px)/2)',
|
||||||
|
width: '50px',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
borderRadius: '10px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
|
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
|
||||||
|
const initialChapter = () => ({ pageCount: -1, chapterIndex: -1, chapterCount: 0 });
|
||||||
|
|
||||||
export default function Reader() {
|
export default function Reader() {
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const [settings, setSettings] = useLocalStorage<IReaderSettings>('readerSettings', defaultReaderSettings);
|
||||||
useEffect(() => { setTitle('Reader'); setAction(<></>); }, []);
|
|
||||||
|
const classes = useStyles(settings)();
|
||||||
|
|
||||||
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||||
|
|
||||||
const [pageCount, setPageCount] = useState<number>(-1);
|
const { chapterIndex, mangaId } = useParams<{chapterIndex: string, mangaId: string}>();
|
||||||
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
|
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(() => {
|
||||||
|
setOverride(
|
||||||
|
{
|
||||||
|
status: true,
|
||||||
|
value: (
|
||||||
|
<ReaderNavBar
|
||||||
|
settings={settings}
|
||||||
|
setSettings={setSettings}
|
||||||
|
manga={manga}
|
||||||
|
chapter={chapter}
|
||||||
|
curPage={curPage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// clean up for when we leave the reader
|
||||||
|
return () => setOverride({ status: false, value: <div /> });
|
||||||
|
}, [manga, chapter, settings, curPage, chapterIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterId}`)
|
setTitle('Reader');
|
||||||
|
client.get(`/api/v1/manga/${mangaId}/`)
|
||||||
|
.then((response) => response.data)
|
||||||
|
.then((data: IManga) => {
|
||||||
|
setManga(data);
|
||||||
|
setTitle(data.title);
|
||||||
|
});
|
||||||
|
}, [chapterIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChapter(initialChapter);
|
||||||
|
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
.then((data:IChapter) => {
|
.then((data:IChapter) => {
|
||||||
setTitle(data.name);
|
setChapter(data);
|
||||||
setPageCount(data.pageCount);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, [chapterIndex]);
|
||||||
|
|
||||||
if (pageCount === -1) {
|
if (chapter.pageCount === -1) {
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div className={classes.loading}>
|
||||||
<h3>wait</h3>
|
<CircularProgress thickness={5} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = range(pageCount).map((index) => (
|
|
||||||
<div style={{ margin: '0 auto' }}>
|
|
||||||
<img src={`${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterId}/page/${index}`} alt="F" style={{ maxWidth: '100%' }} />
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div className={classes.reader}>
|
||||||
{mapped}
|
<div className={classes.pageNumber}>
|
||||||
|
{`${curPage + 1} / ${chapter.pageCount}`}
|
||||||
|
</div>
|
||||||
|
{range(chapter.pageCount).map((index) => (
|
||||||
|
<Page
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
src={`${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterIndex}/page/${index}`}
|
||||||
|
setCurPage={setCurPage}
|
||||||
|
settings={settings}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user