Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06e93fd7bd | |||
| 02cac5df10 | |||
| 6ee3348f50 | |||
| 8780597091 | |||
| c98899d501 | |||
| 7654653a25 | |||
| 0c1a0ef408 | |||
| e7f2192579 | |||
| 5c3b1e0b07 | |||
| 6ad59f2e2b | |||
| 03dd778fac | |||
| 6b833a38d1 | |||
| a83885353c | |||
| 10d7c7c06d | |||
| 3b575271cb | |||
| 5ad92413f3 | |||
| 7fbdb39319 | |||
| 92abdf7fb7 | |||
| ea0c666cfe | |||
| b5e395a039 | |||
| e530072a07 | |||
| f85cbe1ca5 | |||
| 5f9126eb2f | |||
| d77c57ede0 | |||
| 02f9a0d1d7 | |||
| 53192f56ca | |||
| 01d89cbb48 | |||
| be55cb974b | |||
| 34c394ed19 | |||
| 1433a21abd | |||
| ec28794655 | |||
| 72122b7cbf | |||
| 392a7990d2 | |||
| 0bdcf8b4ba | |||
| 8295440bfd | |||
| e52aa6daf4 | |||
| ef067ef5b9 | |||
| 76686db6a1 | |||
| 6bc5046773 | |||
| 1a5cfd8f58 | |||
| bf76962d23 | |||
| a8acca6a38 | |||
| 7891c627c1 | |||
| ee55145e45 | |||
| 5cda584568 | |||
| 031890deb6 | |||
| 0f149c9b33 | |||
| 41f22df16f | |||
| a11e5e623d | |||
| 489ffa1679 | |||
| f977d181a8 | |||
| 2249d237dd | |||
| c52457c80e | |||
| 3904cbf789 | |||
| 759ae9fca0 | |||
| 06954591c7 | |||
| bbdae74567 | |||
| 154e54d833 | |||
| f18e0f4a62 | |||
| 2b19bc850d | |||
| a0fb30a3ad | |||
| e5387ff5f7 | |||
| 123d8a2637 | |||
| 6c72659bd8 | |||
| 44d89506d4 | |||
| d8a5cdfb78 | |||
| 5b5e2b26f9 | |||
| 9fa51c8a1d | |||
| 7633d2156a | |||
| 400e059455 | |||
| f3d6bb4f22 | |||
| c50b5e7448 | |||
| 5b75b361f6 | |||
| 146290283f | |||
| 35bcbc69a2 | |||
| a58dcc6f19 | |||
| b4595b70d6 | |||
| 347b6faa82 | |||
| 162a6b70af | |||
| 3b0a05126b | |||
| 02da884f17 | |||
| c4d8bba5ca | |||
| b979db9acb | |||
| b35c120bd1 | |||
| 9e438566e4 | |||
| 3c518707eb | |||
| f8bee14808 | |||
| 9f60bb8f3e | |||
| 7eb752654b | |||
| a9d27acce3 | |||
| 3234c41333 | |||
| 339b28e7fb | |||
| c624c6d860 | |||
| 0cac39c19f | |||
| 37bd5c32ab | |||
| fc02f96b18 | |||
| 27a83c4915 | |||
| 68006c4e68 | |||
| b97a808e7b | |||
| 484f75374d | |||
| f45f6faf96 | |||
| 57fef90bc3 | |||
| e51ccdf2f0 | |||
| f39cf0f0f5 | |||
| 817589f710 | |||
| b6e79532a9 | |||
| 61ca48ccdd | |||
| 3b36ec550d | |||
| 39cae6cc2d | |||
| 4478042f40 | |||
| a1ee1458e3 | |||
| 78b50b0881 | |||
| 2af88056f2 | |||
| b20215731f | |||
| a76959c295 | |||
| 83c94c044d | |||
| 949829ae6b | |||
| 9d7f54be82 | |||
| aa8d27f679 | |||
| b58a716daa | |||
| ce9d080469 | |||
| 34d0ce69fa | |||
| b8842054a5 | |||
| 966b39e039 | |||
| 2fcbe44953 | |||
| f2795c972a | |||
| 1a67d4db11 | |||
| 6c4c2a175b | |||
| 0a7e6cce87 | |||
| 3e47859d88 | |||
| 8e405e3996 | |||
| 3b21442e25 | |||
| dd895157c6 | |||
| 436daeb87c | |||
| 00f5652db9 | |||
| c452a3548f | |||
| 1dc1932a84 | |||
| 3941986ab2 | |||
| 0f95e19c76 | |||
| bb09c558b6 | |||
| ed7e9f4a2e | |||
| 6b0fddd421 | |||
| 4dbd9d70d2 | |||
| 53c4659044 | |||
| fb41ad38f6 | |||
| ebcb9a9562 | |||
| 678a12490b | |||
| 4978461ac7 | |||
| d63965cc02 | |||
| 2a27491f9f | |||
| 748f0494b4 | |||
| c9575449da | |||
| 3e7c5e2948 | |||
| 21e64eb54a | |||
| c4b2f8582e | |||
| c1f2aae90d | |||
| 9d09a1fe5d | |||
| 7d006a19c3 | |||
| d23d10601e | |||
| b4db9ebdb0 | |||
| d8050dc483 | |||
| 4011a4c3ee | |||
| c35dd1a2c7 | |||
| bc6e28cabe | |||
| 68492bf591 | |||
| 29e7bab4e4 | |||
| 5be4d2a104 | |||
| 0585000cf3 | |||
| 045e4d23fb | |||
| 7b5d96189e | |||
| 5b46359057 | |||
| 44de584be3 | |||
| 52a9be3a7e | |||
| 8d119fd710 | |||
| b802c6977a | |||
| 1af1562473 | |||
| 0d79ac68f8 | |||
| 3ce9f72e3f | |||
| 9437e4243a | |||
| b92f9a2e4d | |||
| 6181467abb | |||
| 5b55858184 | |||
| 6dd789dedc | |||
| 8d3a144afd | |||
| 5577dff087 | |||
| f4e32bac1a | |||
| 02aada7f08 | |||
| fb05371ac2 | |||
| 9a9c0532de | |||
| 2e0f72f182 | |||
| f210bbc22a | |||
| 08c4f8bcc2 | |||
| 9062252939 | |||
| 5c79672d84 | |||
| 5e48b05270 | |||
| 28ab0af6d4 | |||
| cdb98d2175 | |||
| d95f4fe1e1 | |||
| 6e2be271c3 | |||
| bfccbaf731 | |||
| c7b4f226b3 | |||
| d142d3a25a | |||
| f8b00a4541 | |||
| 808e0ecae7 | |||
| a09cac1063 | |||
| 513af98872 | |||
| fafcaa222c | |||
| 1dd79a0b1e | |||
| bbd7e30298 | |||
| 2818fbe575 | |||
| a11db6662d | |||
| 904157a91a | |||
| 904d3980d6 | |||
| 89e2ba9f75 | |||
| 3f4dd2861e | |||
| c1a9b158e2 | |||
| 7db947eba2 | |||
| 450861d47a | |||
| 3df0106325 | |||
| 2b767eb488 | |||
| f8c77b3673 | |||
| aaaae4e719 | |||
| 679e2c0da9 | |||
| 055c1c47f6 | |||
| 275727ed90 | |||
| 257e1dd03d | |||
| 5bf2a4aed4 | |||
| dc79b4c90a | |||
| 4cdc7b348d | |||
| ddedceeded | |||
| 3179169913 | |||
| 8ef2877040 | |||
| 11b2a6b616 | |||
| 04ad0033d7 | |||
| 46e2ef125a | |||
| 9a33e3808a | |||
| 8ae451ece5 | |||
| a5d64be197 | |||
| 4482b325d7 | |||
| f46745d70c | |||
| b213de19cc | |||
| 75355f0784 | |||
| 8547159eec | |||
| d90bfb6e3e | |||
| 82ad2fbe80 | |||
| a414860626 | |||
| 392f66e938 | |||
| a40021a823 | |||
| c86f7c5356 | |||
| 3075888d26 | |||
| 7a0d3a1efe | |||
| 7b22397a82 | |||
| b2cfb5a1e9 | |||
| 283e38c30a | |||
| 02717b317c | |||
| 7fa1250a67 | |||
| 590e43c827 | |||
| 16e9c0b19a | |||
| 8661438f69 | |||
| 9049b4a090 | |||
| a2622fe3e1 | |||
| 664d5fe637 | |||
| 97f9180063 | |||
| eed8012521 | |||
| ab1e3e4302 | |||
| c12b6f39d8 | |||
| b280c03afa | |||
| 1d9991e562 | |||
| 87aae46a1f |
@@ -9,3 +9,6 @@ ij_kotlin_name_count_to_use_star_import_for_members=2147483647
|
|||||||
ktlint_standard_discouraged-comment-location=disabled
|
ktlint_standard_discouraged-comment-location=disabled
|
||||||
ktlint_standard_if-else-wrapping=disabled
|
ktlint_standard_if-else-wrapping=disabled
|
||||||
ktlint_standard_no-consecutive-comments=disabled
|
ktlint_standard_no-consecutive-comments=disabled
|
||||||
|
|
||||||
|
[**/generated/**]
|
||||||
|
ktlint=disabled
|
||||||
@@ -42,7 +42,18 @@ body:
|
|||||||
label: Suwayomi-Server version
|
label: Suwayomi-Server version
|
||||||
description: You can find your Suwayomi-Server version in **More → About**.
|
description: You can find your Suwayomi-Server version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "v2.1.1867"
|
Example: "v2.2.2100"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: database
|
||||||
|
attributes:
|
||||||
|
label: Used database
|
||||||
|
description: H2 is the default database
|
||||||
|
options:
|
||||||
|
- H2
|
||||||
|
- PostgreSQL
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!--
|
||||||
|
Pull Request Checklist:
|
||||||
|
- Mention what the pull request does and the reasons behind the changes
|
||||||
|
- Mention all issues the pull request is closing
|
||||||
|
- Make sure to update the CHANGELOG accordingly if necessary based on the LAST stable release
|
||||||
|
-->
|
||||||
@@ -14,32 +14,44 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build pull request
|
name: Build pull request
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout pull request
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -47,7 +59,62 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build Jar
|
- name: Build And Run Jar
|
||||||
working-directory: master
|
working-directory: master
|
||||||
run: ./gradlew ktlintCheck :server:shadowJar --stacktrace
|
run: |
|
||||||
|
./gradlew ktlintCheck :server:shadowJar --stacktrace
|
||||||
|
gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so
|
||||||
|
export LD_PRELOAD="$(pwd)/scripts/resources/catch_abort.so"
|
||||||
|
JAR=$(ls ./server/build/*.jar| head -1)
|
||||||
|
set +e
|
||||||
|
timeout 30s java -DcrashOnFailedMigration=true \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseType=POSTGRESQL \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseUrl=postgresql://localhost:5432/postgres \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseUsername=postgres \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databasePassword=postgres \
|
||||||
|
-jar "$JAR"
|
||||||
|
|
||||||
|
ecode="$?"
|
||||||
|
# if exit code is not 124 or 125, then error
|
||||||
|
if ! [ "$ecode" -eq 124 -o "$ecode" -eq 125 ]; then
|
||||||
|
printf "exit code '%s' - exiting.\n" "$ecode"
|
||||||
|
exit "$ecode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
timeout 30s java -DcrashOnFailedMigration=true \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
|
||||||
|
-jar "$JAR"
|
||||||
|
|
||||||
|
ecode="$?"
|
||||||
|
# if exit code is not 124 or 125, then error
|
||||||
|
if ! [ "$ecode" -eq 124 -o "$ecode" -eq 125 ]; then
|
||||||
|
printf "exit code '%s' - exiting.\n" "$ecode"
|
||||||
|
exit "$ecode"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
check_docs:
|
||||||
|
name: Validate that all options are documented
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Validate all options are documented
|
||||||
|
run: |
|
||||||
|
f="`cat ./server/server-config/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt |
|
||||||
|
awk -F' ' 'BEGIN{prev=""}{if ($3 ~ "Mutable" && !(prev ~ "@Deprecated")) print "server." substr($2, 1, length($2)-1); prev=$0}' |
|
||||||
|
while read -r setting; do
|
||||||
|
if ! grep "$setting" ./docs/Configuring-Suwayomi‐Server.md >/dev/null; then
|
||||||
|
echo "Setting $setting not documented" >&2
|
||||||
|
echo ":"
|
||||||
|
fi
|
||||||
|
done`"
|
||||||
|
if [ -n "$f" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build Jar
|
name: Build Jar
|
||||||
@@ -26,20 +26,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout master branch
|
- name: Checkout master branch
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -54,14 +54,14 @@ jobs:
|
|||||||
run: ./gradlew :server:shadowJar --stacktrace
|
run: ./gradlew :server:shadowJar --stacktrace
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
@@ -86,24 +86,24 @@ jobs:
|
|||||||
name: linux-x64
|
name: linux-x64
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
- os: macos-14
|
- os: macos-15
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
- os: macos-13
|
- os: macos-15-intel
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
|
os: [ubuntu-latest, windows-latest, macos-15, macos-15-intel]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
- name: Package JDK
|
- name: Package JDK
|
||||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
- name: Upload JRE package
|
- name: Upload JRE package
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}-jre
|
name: ${{ matrix.name }}-jre
|
||||||
path: suwa
|
path: suwa
|
||||||
@@ -134,26 +134,26 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
path: server/build
|
||||||
|
|
||||||
- name: Download JRE
|
- name: Download JRE
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.jre }}-jre
|
name: ${{ matrix.jre }}-jre
|
||||||
path: jre
|
path: jre
|
||||||
|
|
||||||
- name: Download icons
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ jobs:
|
|||||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.name }} release
|
- name: Upload ${{ matrix.name }} release
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
@@ -174,41 +174,41 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
|
|
||||||
- name: Checkout Preview branch
|
- name: Checkout Preview branch
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
ref: main
|
ref: main
|
||||||
@@ -240,7 +240,7 @@ jobs:
|
|||||||
git push origin $TAG
|
git push origin $TAG
|
||||||
|
|
||||||
- name: Upload Preview Release
|
- name: Upload Preview Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build Jar
|
name: Build Jar
|
||||||
@@ -27,20 +27,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ${{ github.ref }}
|
- name: Checkout ${{ github.ref }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -56,14 +56,14 @@ jobs:
|
|||||||
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
@@ -88,24 +88,24 @@ jobs:
|
|||||||
name: linux-x64
|
name: linux-x64
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
- os: macos-14
|
- os: macos-15
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
- os: macos-13
|
- os: macos-15-intel
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
|
os: [ubuntu-latest, windows-latest, macos-15, macos-15-intel]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
- name: Package JDK
|
- name: Package JDK
|
||||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
- name: Upload JDK package
|
- name: Upload JDK package
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}-jre
|
name: ${{ matrix.name }}-jre
|
||||||
path: suwa
|
path: suwa
|
||||||
@@ -136,26 +136,26 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
path: server/build
|
||||||
|
|
||||||
- name: Download JRE
|
- name: Download JRE
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.jre }}-jre
|
name: ${{ matrix.jre }}-jre
|
||||||
path: jre
|
path: jre
|
||||||
|
|
||||||
- name: Download icons
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.name }} files
|
- name: Upload ${{ matrix.name }} files
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
@@ -177,35 +177,35 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
run: cd release && sha256sum * > Checksums.sha256
|
run: cd release && sha256sum * > Checksums.sha256
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: GitHub Wiki upload
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths: [docs/**, .github/workflows/wiki.yml]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
wiki:
|
||||||
|
name: Publish to GitHub Wiki
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: ${{github.repository}}
|
||||||
|
path: ${{github.repository}}
|
||||||
|
|
||||||
|
- name: Checkout Wiki
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: ${{github.repository}}.wiki
|
||||||
|
path: ${{github.repository}}.wiki
|
||||||
|
|
||||||
|
- name: Push to wiki
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
cd $GITHUB_WORKSPACE/${{github.repository}}.wiki
|
||||||
|
cp -r $GITHUB_WORKSPACE/${{github.repository}}/docs/* .
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
git add .
|
||||||
|
git diff-index --quiet HEAD || git commit -m "action: wiki sync" && git push
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -113,22 +113,23 @@ open class ConfigManager {
|
|||||||
value: Any,
|
value: Any,
|
||||||
) {
|
) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val actualValue = if (value is Enum<*>) value.name else value
|
val configValue = value.toConfig("internal").getValue("internal")
|
||||||
val configValue = actualValue.toConfig("internal").getValue("internal")
|
|
||||||
|
|
||||||
updateUserConfigFile(path, configValue)
|
updateUserConfigFile(path, configValue)
|
||||||
internalConfig = internalConfig.withValue(path, configValue)
|
internalConfig = internalConfig.withValue(path, configValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetUserConfig(updateInternalConfig: Boolean = true): ConfigDocument {
|
private fun createConfigDocumentFromReference(): ConfigDocument {
|
||||||
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
|
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
|
||||||
val serverConfigDoc = ConfigDocumentFactory.parseString(serverConfigFileContent)
|
return ConfigDocumentFactory.parseString(serverConfigFileContent)
|
||||||
userConfigFile.writeText(serverConfigDoc.render())
|
}
|
||||||
|
|
||||||
if (updateInternalConfig) {
|
fun resetUserConfig(): ConfigDocument {
|
||||||
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
val serverConfigDoc = createConfigDocumentFromReference()
|
||||||
}
|
|
||||||
|
userConfigFile.writeText(serverConfigDoc.render())
|
||||||
|
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||||
|
|
||||||
return serverConfigDoc
|
return serverConfigDoc
|
||||||
}
|
}
|
||||||
@@ -136,8 +137,9 @@ open class ConfigManager {
|
|||||||
/**
|
/**
|
||||||
* Makes sure the "UserConfig" is up-to-date.
|
* Makes sure the "UserConfig" is up-to-date.
|
||||||
*
|
*
|
||||||
* - adds missing settings
|
* - Adds missing settings
|
||||||
* - removes outdated settings
|
* - Migrates deprecated settings
|
||||||
|
* - Removes outdated settings
|
||||||
*/
|
*/
|
||||||
fun updateUserConfig(migrate: ConfigDocument.(Config) -> ConfigDocument) {
|
fun updateUserConfig(migrate: ConfigDocument.(Config) -> ConfigDocument) {
|
||||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
@@ -150,16 +152,17 @@ open class ConfigManager {
|
|||||||
}
|
}
|
||||||
val hasMissingSettings = refKeys.any { !userConfig.hasPath(it) }
|
val hasMissingSettings = refKeys.any { !userConfig.hasPath(it) }
|
||||||
val hasOutdatedSettings = userConfig.entrySet().any { !refKeys.contains(it.key) && it.key.count { c -> c == '.' } <= 1 }
|
val hasOutdatedSettings = userConfig.entrySet().any { !refKeys.contains(it.key) && it.key.count { c -> c == '.' } <= 1 }
|
||||||
|
|
||||||
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
|
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
|
||||||
if (!isUserConfigOutdated) {
|
if (!isUserConfigOutdated) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug {
|
logger.debug {
|
||||||
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings"
|
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var newUserConfigDoc: ConfigDocument = resetUserConfig(false)
|
var newUserConfigDoc: ConfigDocument = createConfigDocumentFromReference()
|
||||||
userConfig
|
userConfig
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.filter {
|
.filter {
|
||||||
@@ -175,6 +178,23 @@ open class ConfigManager {
|
|||||||
userConfigFile.writeText(newUserConfigDoc.render())
|
userConfigFile.writeText(newUserConfigDoc.render())
|
||||||
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getRedactedConfig(nonPrivacySafeKeys: List<String>): Config {
|
||||||
|
val entries =
|
||||||
|
config.entrySet().associate { entry ->
|
||||||
|
val key = entry.key
|
||||||
|
val value =
|
||||||
|
if (nonPrivacySafeKeys.any { key.split(".").getOrNull(1) == it }) {
|
||||||
|
"[REDACTED]"
|
||||||
|
} else {
|
||||||
|
entry.value.unwrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
key to value
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigFactory.parseMap(entries)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalConfigManager : ConfigManager()
|
object GlobalConfigManager : ConfigManager()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package android.graphics;
|
|||||||
|
|
||||||
import android.annotation.ColorInt;
|
import android.annotation.ColorInt;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -58,7 +60,23 @@ public final class Bitmap {
|
|||||||
ARGB_8888(5),
|
ARGB_8888(5),
|
||||||
RGBA_F16(6),
|
RGBA_F16(6),
|
||||||
HARDWARE(7),
|
HARDWARE(7),
|
||||||
RGBA_1010102(8);
|
RGBA_1010102(8),
|
||||||
|
|
||||||
|
_TYPE_3BYTE_BGR(BufferedImage.TYPE_3BYTE_BGR),
|
||||||
|
_TYPE_4BYTE_ABGR(BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
|
_TYPE_4BYTE_ABGR_PRE(BufferedImage.TYPE_4BYTE_ABGR_PRE),
|
||||||
|
_TYPE_BYTE_BINARY(BufferedImage.TYPE_BYTE_BINARY),
|
||||||
|
_TYPE_BYTE_GRAY(BufferedImage.TYPE_BYTE_GRAY),
|
||||||
|
_TYPE_BYTE_INDEXED(BufferedImage.TYPE_BYTE_INDEXED),
|
||||||
|
_TYPE_CUSTOM(BufferedImage.TYPE_CUSTOM),
|
||||||
|
_TYPE_INT_ARGB(BufferedImage.TYPE_INT_ARGB),
|
||||||
|
_TYPE_INT_ARGB_PRE(BufferedImage.TYPE_INT_ARGB_PRE),
|
||||||
|
_TYPE_INT_BGR(BufferedImage.TYPE_INT_BGR),
|
||||||
|
_TYPE_INT_RGB(BufferedImage.TYPE_INT_RGB),
|
||||||
|
_TYPE_USHORT_555_RGB(BufferedImage.TYPE_USHORT_555_RGB),
|
||||||
|
_TYPE_USHORT_565_RGB(BufferedImage.TYPE_USHORT_565_RGB),
|
||||||
|
_TYPE_USHORT_GRAY(BufferedImage.TYPE_USHORT_GRAY),
|
||||||
|
;
|
||||||
|
|
||||||
final int nativeInt;
|
final int nativeInt;
|
||||||
|
|
||||||
@@ -83,11 +101,62 @@ public final class Bitmap {
|
|||||||
return BufferedImage.TYPE_USHORT_565_RGB;
|
return BufferedImage.TYPE_USHORT_565_RGB;
|
||||||
case ARGB_8888:
|
case ARGB_8888:
|
||||||
return BufferedImage.TYPE_INT_ARGB;
|
return BufferedImage.TYPE_INT_ARGB;
|
||||||
|
case _TYPE_3BYTE_BGR:
|
||||||
|
case _TYPE_4BYTE_ABGR:
|
||||||
|
case _TYPE_4BYTE_ABGR_PRE:
|
||||||
|
case _TYPE_BYTE_BINARY:
|
||||||
|
case _TYPE_BYTE_GRAY:
|
||||||
|
case _TYPE_BYTE_INDEXED:
|
||||||
|
case _TYPE_CUSTOM:
|
||||||
|
case _TYPE_INT_ARGB:
|
||||||
|
case _TYPE_INT_ARGB_PRE:
|
||||||
|
case _TYPE_INT_BGR:
|
||||||
|
case _TYPE_INT_RGB:
|
||||||
|
case _TYPE_USHORT_555_RGB:
|
||||||
|
case _TYPE_USHORT_565_RGB:
|
||||||
|
case _TYPE_USHORT_GRAY:
|
||||||
|
return config.ordinal();
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Config bufferedImageTypeToConfig(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case BufferedImage.TYPE_BYTE_GRAY:
|
||||||
|
return Config.ALPHA_8;
|
||||||
|
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||||
|
return Config.RGB_565;
|
||||||
|
case BufferedImage.TYPE_INT_ARGB:
|
||||||
|
return Config.ARGB_8888;
|
||||||
|
case BufferedImage.TYPE_3BYTE_BGR:
|
||||||
|
return Config._TYPE_3BYTE_BGR;
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||||
|
return Config._TYPE_4BYTE_ABGR;
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||||
|
return Config._TYPE_4BYTE_ABGR_PRE;
|
||||||
|
case BufferedImage.TYPE_BYTE_BINARY:
|
||||||
|
return Config._TYPE_BYTE_BINARY;
|
||||||
|
case BufferedImage.TYPE_BYTE_INDEXED:
|
||||||
|
return Config._TYPE_BYTE_INDEXED;
|
||||||
|
case BufferedImage.TYPE_CUSTOM:
|
||||||
|
return Config._TYPE_CUSTOM;
|
||||||
|
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||||
|
return Config._TYPE_INT_ARGB_PRE;
|
||||||
|
case BufferedImage.TYPE_INT_BGR:
|
||||||
|
return Config._TYPE_INT_BGR;
|
||||||
|
case BufferedImage.TYPE_INT_RGB:
|
||||||
|
return Config._TYPE_INT_RGB;
|
||||||
|
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||||
|
return Config._TYPE_USHORT_555_RGB;
|
||||||
|
case BufferedImage.TYPE_USHORT_GRAY:
|
||||||
|
return Config._TYPE_USHORT_GRAY;
|
||||||
|
default:
|
||||||
|
Log.w("Bitmap", "Encountered unsupported image type " + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common code for checking that x and y are >= 0
|
* Common code for checking that x and y are >= 0
|
||||||
*
|
*
|
||||||
@@ -246,6 +315,23 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared code to check for illegal arguments passed to getPixel()
|
||||||
|
* or setPixel()
|
||||||
|
*
|
||||||
|
* @param x x coordinate of the pixel
|
||||||
|
* @param y y coordinate of the pixel
|
||||||
|
*/
|
||||||
|
private void checkPixelAccess(int x, int y) {
|
||||||
|
checkXYSign(x, y);
|
||||||
|
if (x >= getWidth()) {
|
||||||
|
throw new IllegalArgumentException("x must be < bitmap.width()");
|
||||||
|
}
|
||||||
|
if (y >= getHeight()) {
|
||||||
|
throw new IllegalArgumentException("y must be < bitmap.height()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
|
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
|
||||||
int x, int y, int width, int height) {
|
int x, int y, int width, int height) {
|
||||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
@@ -253,6 +339,63 @@ public final class Bitmap {
|
|||||||
image.getRGB(x, y, width, height, pixels, offset, stride);
|
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public int getPixel(int x, int y) {
|
||||||
|
checkPixelAccess(x, y);
|
||||||
|
return image.getRGB(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Write the specified {@link Color} into the bitmap (assuming it is
|
||||||
|
* mutable) at the x,y coordinate. The color must be a
|
||||||
|
* non-premultiplied ARGB value in the {@link ColorSpace.Named#SRGB sRGB}
|
||||||
|
* color space.</p>
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the pixel to replace (0...width-1)
|
||||||
|
* @param y The y coordinate of the pixel to replace (0...height-1)
|
||||||
|
* @param color The ARGB color to write into the bitmap
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the bitmap is not mutable
|
||||||
|
* @throws IllegalArgumentException if x, y are outside of the bitmap's
|
||||||
|
* bounds.
|
||||||
|
*/
|
||||||
|
public void setPixel(int x, int y, @ColorInt int color) {
|
||||||
|
checkPixelAccess(x, y);
|
||||||
|
image.setRGB(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Replace pixels in the bitmap with the colors in the array. Each element
|
||||||
|
* in the array is a packed int representing a non-premultiplied ARGB
|
||||||
|
* {@link Color} in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
|
||||||
|
*
|
||||||
|
* @param pixels The colors to write to the bitmap
|
||||||
|
* @param offset The index of the first color to read from pixels[]
|
||||||
|
* @param stride The number of colors in pixels[] to skip between rows.
|
||||||
|
* Normally this value will be the same as the width of
|
||||||
|
* the bitmap, but it can be larger (or negative).
|
||||||
|
* @param x The x coordinate of the first pixel to write to in
|
||||||
|
* the bitmap.
|
||||||
|
* @param y The y coordinate of the first pixel to write to in
|
||||||
|
* the bitmap.
|
||||||
|
* @param width The number of colors to copy from pixels[] per row
|
||||||
|
* @param height The number of rows to write to the bitmap
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the bitmap is not mutable
|
||||||
|
* @throws IllegalArgumentException if x, y, width, height are outside of
|
||||||
|
* the bitmap's bounds.
|
||||||
|
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
|
||||||
|
* to receive the specified number of pixels.
|
||||||
|
*/
|
||||||
|
public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
|
||||||
|
int x, int y, int width, int height) {
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
|
image.setRGB(x, y, width, height, pixels, offset, stride);
|
||||||
|
}
|
||||||
|
|
||||||
public void eraseColor(int c) {
|
public void eraseColor(int c) {
|
||||||
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||||
Graphics2D graphics = image.createGraphics();
|
Graphics2D graphics = image.createGraphics();
|
||||||
@@ -264,4 +407,10 @@ public final class Bitmap {
|
|||||||
public void recycle() {
|
public void recycle() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Config getConfig() {
|
||||||
|
int type = image.getType();
|
||||||
|
return bufferedImageTypeToConfig(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,462 @@ import java.util.Iterator;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class BitmapFactory {
|
public class BitmapFactory {
|
||||||
|
public static class Options {
|
||||||
|
/**
|
||||||
|
* Create a default Options object, which if left unchanged will give
|
||||||
|
* the same result from the decoder as if null were passed.
|
||||||
|
*/
|
||||||
|
public Options() {
|
||||||
|
inScaled = true;
|
||||||
|
inPremultiplied = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, decode methods that take the Options object will attempt to
|
||||||
|
* reuse this bitmap when loading content. If the decode operation
|
||||||
|
* cannot use this bitmap, the decode method will throw an
|
||||||
|
* {@link java.lang.IllegalArgumentException}. The
|
||||||
|
* current implementation necessitates that the reused bitmap be
|
||||||
|
* mutable, and the resulting reused bitmap will continue to remain
|
||||||
|
* mutable even when decoding a resource which would normally result in
|
||||||
|
* an immutable bitmap.</p>
|
||||||
|
*
|
||||||
|
* <p>You should still always use the returned Bitmap of the decode
|
||||||
|
* method and not assume that reusing the bitmap worked, due to the
|
||||||
|
* constraints outlined above and failure situations that can occur.
|
||||||
|
* Checking whether the return value matches the value of the inBitmap
|
||||||
|
* set in the Options structure will indicate if the bitmap was reused,
|
||||||
|
* but in all cases you should use the Bitmap returned by the decoding
|
||||||
|
* function to ensure that you are using the bitmap that was used as the
|
||||||
|
* decode destination.</p>
|
||||||
|
*
|
||||||
|
* <h3>Usage with BitmapFactory</h3>
|
||||||
|
*
|
||||||
|
* <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any
|
||||||
|
* mutable bitmap can be reused by {@link BitmapFactory} to decode any
|
||||||
|
* other bitmaps as long as the resulting {@link Bitmap#getByteCount()
|
||||||
|
* byte count} of the decoded bitmap is less than or equal to the {@link
|
||||||
|
* Bitmap#getAllocationByteCount() allocated byte count} of the reused
|
||||||
|
* bitmap. This can be because the intrinsic size is smaller, or its
|
||||||
|
* size post scaling (for density / sample size) is smaller.</p>
|
||||||
|
*
|
||||||
|
* <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}
|
||||||
|
* additional constraints apply: The image being decoded (whether as a
|
||||||
|
* resource or as a stream) must be in jpeg or png format. Only equal
|
||||||
|
* sized bitmaps are supported, with {@link #inSampleSize} set to 1.
|
||||||
|
* Additionally, the {@link android.graphics.Bitmap.Config
|
||||||
|
* configuration} of the reused bitmap will override the setting of
|
||||||
|
* {@link #inPreferredConfig}, if set.</p>
|
||||||
|
*
|
||||||
|
* <h3>Usage with BitmapRegionDecoder</h3>
|
||||||
|
*
|
||||||
|
* <p>BitmapRegionDecoder will draw its requested content into the Bitmap
|
||||||
|
* provided, clipping if the output content size (post scaling) is larger
|
||||||
|
* than the provided Bitmap. The provided Bitmap's width, height, and
|
||||||
|
* {@link Bitmap.Config} will not be changed.
|
||||||
|
*
|
||||||
|
* <p class="note">BitmapRegionDecoder support for {@link #inBitmap} was
|
||||||
|
* introduced in {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. All
|
||||||
|
* formats supported by BitmapRegionDecoder support Bitmap reuse via
|
||||||
|
* {@link #inBitmap}.</p>
|
||||||
|
*
|
||||||
|
* @see Bitmap#reconfigure(int,int, android.graphics.Bitmap.Config)
|
||||||
|
*/
|
||||||
|
public Bitmap inBitmap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, decode methods will always return a mutable Bitmap instead of
|
||||||
|
* an immutable one. This can be used for instance to programmatically apply
|
||||||
|
* effects to a Bitmap loaded through BitmapFactory.
|
||||||
|
* <p>Can not be set simultaneously with inPreferredConfig =
|
||||||
|
* {@link android.graphics.Bitmap.Config#HARDWARE},
|
||||||
|
* because hardware bitmaps are always immutable.
|
||||||
|
*/
|
||||||
|
public boolean inMutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true, the decoder will return null (no bitmap), but
|
||||||
|
* the <code>out...</code> fields will still be set, allowing the caller to
|
||||||
|
* query the bitmap without having to allocate the memory for its pixels.
|
||||||
|
*/
|
||||||
|
public boolean inJustDecodeBounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to a value > 1, requests the decoder to subsample the original
|
||||||
|
* image, returning a smaller image to save memory. The sample size is
|
||||||
|
* the number of pixels in either dimension that correspond to a single
|
||||||
|
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
|
||||||
|
* an image that is 1/4 the width/height of the original, and 1/16 the
|
||||||
|
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
|
||||||
|
* decoder uses a final value based on powers of 2, any other value will
|
||||||
|
* be rounded down to the nearest power of 2.
|
||||||
|
*/
|
||||||
|
public int inSampleSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is non-null, the decoder will try to decode into this
|
||||||
|
* internal configuration. If it is null, or the request cannot be met,
|
||||||
|
* the decoder will try to pick the best matching config based on the
|
||||||
|
* system's screen depth, and characteristics of the original image such
|
||||||
|
* as if it has per-pixel alpha (requiring a config that also does).
|
||||||
|
*
|
||||||
|
* Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
public Bitmap.Config inPreferredConfig = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>If this is non-null, the decoder will try to decode into this
|
||||||
|
* color space. If it is null, or the request cannot be met,
|
||||||
|
* the decoder will pick either the color space embedded in the image
|
||||||
|
* or the color space best suited for the requested image configuration
|
||||||
|
* (for instance {@link ColorSpace.Named#SRGB sRGB} for
|
||||||
|
* {@link Bitmap.Config#ARGB_8888} configuration and
|
||||||
|
* {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for
|
||||||
|
* {@link Bitmap.Config#RGBA_F16}).</p>
|
||||||
|
*
|
||||||
|
* <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
|
||||||
|
* currently supported. An <code>IllegalArgumentException</code> will
|
||||||
|
* be thrown by the decode methods when setting a non-RGB color space
|
||||||
|
* such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
|
||||||
|
*
|
||||||
|
* <p class="note">
|
||||||
|
* Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
|
||||||
|
* the specified color space's transfer function must be
|
||||||
|
* an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
|
||||||
|
* <code>IllegalArgumentException</code> will be thrown by the decode methods
|
||||||
|
* if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
|
||||||
|
* specified color space returns null.
|
||||||
|
*
|
||||||
|
* Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
|
||||||
|
* non ICC parametric curve transfer function is allowed.
|
||||||
|
* E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.</p>
|
||||||
|
*
|
||||||
|
* <p>After decode, the bitmap's color space is stored in
|
||||||
|
* {@link #outColorSpace}.</p>
|
||||||
|
*/
|
||||||
|
public ColorSpace inPreferredColorSpace = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true (which is the default), the resulting bitmap will have its
|
||||||
|
* color channels pre-multiplied by the alpha channel.
|
||||||
|
*
|
||||||
|
* <p>This should NOT be set to false for images to be directly drawn by
|
||||||
|
* the view system or through a {@link Canvas}. The view system and
|
||||||
|
* {@link Canvas} assume all drawn images are pre-multiplied to simplify
|
||||||
|
* draw-time blending, and will throw a RuntimeException when
|
||||||
|
* un-premultiplied are drawn.</p>
|
||||||
|
*
|
||||||
|
* <p>This is likely only useful if you want to manipulate raw encoded
|
||||||
|
* image data, e.g. with RenderScript or custom OpenGL.</p>
|
||||||
|
*
|
||||||
|
* <p>This does not affect bitmaps without an alpha channel.</p>
|
||||||
|
*
|
||||||
|
* <p>Setting this flag to false while setting {@link #inScaled} to true
|
||||||
|
* may result in incorrect colors.</p>
|
||||||
|
*
|
||||||
|
* @see Bitmap#hasAlpha()
|
||||||
|
* @see Bitmap#isPremultiplied()
|
||||||
|
* @see #inScaled
|
||||||
|
*/
|
||||||
|
public boolean inPremultiplied;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if dither is
|
||||||
|
* true, the decoder will attempt to dither the decoded image.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inDither;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density to use for the bitmap. This will always result
|
||||||
|
* in the returned bitmap having a density set for it (see
|
||||||
|
* {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}). In addition,
|
||||||
|
* if {@link #inScaled} is set (which it is by default} and this
|
||||||
|
* density does not match {@link #inTargetDensity}, then the bitmap
|
||||||
|
* will be scaled to the target density before being returned.
|
||||||
|
*
|
||||||
|
* <p>If this is 0,
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int)},
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
|
||||||
|
* and {@link BitmapFactory#decodeResourceStream}
|
||||||
|
* will fill in the density associated with the resource. The other
|
||||||
|
* functions will leave it as-is and no density will be applied.
|
||||||
|
*
|
||||||
|
* @see #inTargetDensity
|
||||||
|
* @see #inScreenDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see Bitmap#setDensity(int)
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density of the destination this bitmap will be drawn to.
|
||||||
|
* This is used in conjunction with {@link #inDensity} and
|
||||||
|
* {@link #inScaled} to determine if and how to scale the bitmap before
|
||||||
|
* returning it.
|
||||||
|
*
|
||||||
|
* <p>If this is 0,
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int)},
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
|
||||||
|
* and {@link BitmapFactory#decodeResourceStream}
|
||||||
|
* will fill in the density associated the Resources object's
|
||||||
|
* DisplayMetrics. The other
|
||||||
|
* functions will leave it as-is and no scaling for density will be
|
||||||
|
* performed.
|
||||||
|
*
|
||||||
|
* @see #inDensity
|
||||||
|
* @see #inScreenDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inTargetDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density of the actual screen that is being used. This is
|
||||||
|
* purely for applications running in density compatibility code, where
|
||||||
|
* {@link #inTargetDensity} is actually the density the application
|
||||||
|
* sees rather than the real screen density.
|
||||||
|
*
|
||||||
|
* <p>By setting this, you
|
||||||
|
* allow the loading code to avoid scaling a bitmap that is currently
|
||||||
|
* in the screen density up/down to the compatibility density. Instead,
|
||||||
|
* if {@link #inDensity} is the same as {@link #inScreenDensity}, the
|
||||||
|
* bitmap will be left as-is. Anything using the resulting bitmap
|
||||||
|
* must also used {@link Bitmap#getScaledWidth(int)
|
||||||
|
* Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
|
||||||
|
* Bitmap.getScaledHeight} to account for any different between the
|
||||||
|
* bitmap's density and the target's density.
|
||||||
|
*
|
||||||
|
* <p>This is never set automatically for the caller by
|
||||||
|
* {@link BitmapFactory} itself. It must be explicitly set, since the
|
||||||
|
* caller must deal with the resulting bitmap in a density-aware way.
|
||||||
|
*
|
||||||
|
* @see #inDensity
|
||||||
|
* @see #inTargetDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inScreenDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this flag is set, if {@link #inDensity} and
|
||||||
|
* {@link #inTargetDensity} are not 0, the
|
||||||
|
* bitmap will be scaled to match {@link #inTargetDensity} when loaded,
|
||||||
|
* rather than relying on the graphics system scaling it each time it
|
||||||
|
* is drawn to a Canvas.
|
||||||
|
*
|
||||||
|
* <p>BitmapRegionDecoder ignores this flag, and will not scale output
|
||||||
|
* based on density. (though {@link #inSampleSize} is supported)</p>
|
||||||
|
*
|
||||||
|
* <p>This flag is turned on by default and should be turned off if you need
|
||||||
|
* a non-scaled version of the bitmap. Nine-patch bitmaps ignore this
|
||||||
|
* flag and are always scaled.
|
||||||
|
*
|
||||||
|
* <p>If {@link #inPremultiplied} is set to false, and the image has alpha,
|
||||||
|
* setting this flag to true may result in incorrect colors.
|
||||||
|
*/
|
||||||
|
public boolean inScaled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
|
||||||
|
* is set to true, then the resulting bitmap will allocate its
|
||||||
|
* pixels such that they can be purged if the system needs to reclaim
|
||||||
|
* memory. In that instance, when the pixels need to be accessed again
|
||||||
|
* (e.g. the bitmap is drawn, getPixels() is called), they will be
|
||||||
|
* automatically re-decoded.
|
||||||
|
*
|
||||||
|
* <p>For the re-decode to happen, the bitmap must have access to the
|
||||||
|
* encoded data, either by sharing a reference to the input
|
||||||
|
* or by making a copy of it. This distinction is controlled by
|
||||||
|
* inInputShareable. If this is true, then the bitmap may keep a shallow
|
||||||
|
* reference to the input. If this is false, then the bitmap will
|
||||||
|
* explicitly make a copy of the input data, and keep that. Even if
|
||||||
|
* sharing is allowed, the implementation may still decide to make a
|
||||||
|
* deep copy of the input data.</p>
|
||||||
|
*
|
||||||
|
* <p>While inPurgeable can help avoid big Dalvik heap allocations (from
|
||||||
|
* API level 11 onward), it sacrifices performance predictability since any
|
||||||
|
* image that the view system tries to draw may incur a decode delay which
|
||||||
|
* can lead to dropped frames. Therefore, most apps should avoid using
|
||||||
|
* inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
|
||||||
|
* allocations use the {@link #inBitmap} flag instead.</p>
|
||||||
|
*
|
||||||
|
* <p class="note"><strong>Note:</strong> This flag is ignored when used
|
||||||
|
* with {@link #decodeResource(Resources, int,
|
||||||
|
* android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
|
||||||
|
* android.graphics.BitmapFactory.Options)}.</p>
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inPurgeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
|
||||||
|
* field works in conjunction with inPurgeable. If inPurgeable is false,
|
||||||
|
* then this field is ignored. If inPurgeable is true, then this field
|
||||||
|
* determines whether the bitmap can share a reference to the input
|
||||||
|
* data (inputstream, array, etc.) or if it must make a deep copy.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inInputShareable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
|
||||||
|
* ignored. The output will always be high quality.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if
|
||||||
|
* inPreferQualityOverSpeed is set to true, the decoder will try to
|
||||||
|
* decode the reconstructed image to a higher quality even at the
|
||||||
|
* expense of the decoding speed. Currently the field only affects JPEG
|
||||||
|
* decode, in the case of which a more accurate, but slightly slower,
|
||||||
|
* IDCT method will be used instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inPreferQualityOverSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resulting width of the bitmap. If {@link #inJustDecodeBounds} is
|
||||||
|
* set to false, this will be width of the output bitmap after any
|
||||||
|
* scaling is applied. If true, it will be the width of the input image
|
||||||
|
* without any accounting for scaling.
|
||||||
|
*
|
||||||
|
* <p>outWidth will be set to -1 if there is an error trying to decode.</p>
|
||||||
|
*/
|
||||||
|
public int outWidth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resulting height of the bitmap. If {@link #inJustDecodeBounds} is
|
||||||
|
* set to false, this will be height of the output bitmap after any
|
||||||
|
* scaling is applied. If true, it will be the height of the input image
|
||||||
|
* without any accounting for scaling.
|
||||||
|
*
|
||||||
|
* <p>outHeight will be set to -1 if there is an error trying to decode.</p>
|
||||||
|
*/
|
||||||
|
public int outHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, this string is set to the mimetype of the decoded image.
|
||||||
|
* If not known, or there is an error, it is set to null.
|
||||||
|
*/
|
||||||
|
public String outMimeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, the config the decoded bitmap will have.
|
||||||
|
* If not known, or there is an error, it is set to null.
|
||||||
|
*/
|
||||||
|
public Bitmap.Config outConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, the color space the decoded bitmap will have. Note that the
|
||||||
|
* output color space is not guaranteed to be the color space the bitmap
|
||||||
|
* is encoded with. If not known (when the config is
|
||||||
|
* {@link Bitmap.Config#ALPHA_8} for instance), or there is an error,
|
||||||
|
* it is set to null.
|
||||||
|
*/
|
||||||
|
public ColorSpace outColorSpace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temp storage to use for decoding. Suggest 16K or so.
|
||||||
|
*/
|
||||||
|
public byte[] inTempStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, see
|
||||||
|
* comments on {@link #requestCancelDecode()}.
|
||||||
|
*
|
||||||
|
* Flag to indicate that cancel has been called on this object. This
|
||||||
|
* is useful if there's an intermediary that wants to first decode the
|
||||||
|
* bounds and then decode the image. In that case the intermediary
|
||||||
|
* can check, inbetween the bounds decode and the image decode, to see
|
||||||
|
* if the operation is canceled.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean mCancel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this
|
||||||
|
* will not affect the decode, though it will still set mCancel.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if this can
|
||||||
|
* be called from another thread while this options object is inside
|
||||||
|
* a decode... call. Calling this will notify the decoder that it
|
||||||
|
* should cancel its operation. This is not guaranteed to cancel the
|
||||||
|
* decode, but if it does, the decoder... operation will return null,
|
||||||
|
* or if inJustDecodeBounds is true, will set outWidth/outHeight
|
||||||
|
* to -1
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void requestCancelDecode() {
|
||||||
|
mCancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate(Options opts) {
|
||||||
|
if (opts == null) return;
|
||||||
|
|
||||||
|
if (opts.inBitmap != null) {
|
||||||
|
/*
|
||||||
|
if (opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Bitmaps with Config.HARDWARE are always immutable");
|
||||||
|
}
|
||||||
|
if (opts.inBitmap.isRecycled()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot reuse a recycled Bitmap");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
|
||||||
|
throw new IllegalArgumentException("Bitmaps with Config.HARDWARE cannot be " +
|
||||||
|
"decoded into - they are immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inPreferredColorSpace != null) {
|
||||||
|
if (!(opts.inPreferredColorSpace instanceof ColorSpace.Rgb)) {
|
||||||
|
throw new IllegalArgumentException("The destination color space must use the " +
|
||||||
|
"RGB color model");
|
||||||
|
}
|
||||||
|
if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
|
||||||
|
&& !opts.inPreferredColorSpace.equals(
|
||||||
|
ColorSpace.get(ColorSpace.Named.BT2020_PQ))
|
||||||
|
&& ((ColorSpace.Rgb) opts.inPreferredColorSpace)
|
||||||
|
.getTransferParameters() == null) {
|
||||||
|
throw new IllegalArgumentException("The destination color space must use an " +
|
||||||
|
"ICC parametric transfer function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Bitmap decodeStream(InputStream inputStream) {
|
public static Bitmap decodeStream(InputStream inputStream) {
|
||||||
|
return decodeStream(inputStream, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
|
||||||
|
@Nullable Options opts) {
|
||||||
|
if (is == null) return null;
|
||||||
|
if (outPadding != null) throw new RuntimeException("OutPadding is not implemented");
|
||||||
|
Options.validate(opts);
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
|
// TODO: Support options with in parameters
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
|
ImageInputStream imageInputStream = ImageIO.createImageInputStream(is);
|
||||||
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
||||||
|
|
||||||
if (!imageReaders.hasNext()) {
|
if (!imageReaders.hasNext()) {
|
||||||
@@ -24,8 +473,18 @@ public class BitmapFactory {
|
|||||||
ImageReader imageReader = imageReaders.next();
|
ImageReader imageReader = imageReaders.next();
|
||||||
imageReader.setInput(imageInputStream);
|
imageReader.setInput(imageInputStream);
|
||||||
|
|
||||||
BufferedImage image = imageReader.read(0, imageReader.getDefaultReadParam());
|
if (opts != null) {
|
||||||
bitmap = new Bitmap(image);
|
opts.outHeight = imageReader.getHeight(0);
|
||||||
|
opts.outWidth = imageReader.getWidth(0);
|
||||||
|
opts.outMimeType = imageReader.getOriginatingProvider().getMIMETypes()[0];
|
||||||
|
opts.outColorSpace = null; // TODO: support? see imageReader.getImageTypeSpecifier().getColorSpace()
|
||||||
|
opts.outConfig = null; // TODO: support?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts == null || !opts.inJustDecodeBounds) {
|
||||||
|
BufferedImage image = imageReader.read(0, imageReader.getDefaultReadParam());
|
||||||
|
bitmap = new Bitmap(image);
|
||||||
|
}
|
||||||
|
|
||||||
imageReader.dispose();
|
imageReader.dispose();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@@ -36,16 +495,11 @@ public class BitmapFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
|
||||||
Bitmap bitmap = null;
|
return decodeByteArray(data, offset, length, null);
|
||||||
|
}
|
||||||
|
|
||||||
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data);
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
|
||||||
try {
|
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data, offset, length);
|
||||||
BufferedImage image = ImageIO.read(byteArrayStream);
|
return decodeStream(byteArrayStream, null, opts);
|
||||||
bitmap = new Bitmap(image);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
package android.graphics;
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.ColorLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.util.Log;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
import java.awt.font.GlyphVector;
|
import java.awt.font.GlyphVector;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Ellipse2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.text.AttributedString;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
public final class Canvas {
|
public final class Canvas {
|
||||||
private BufferedImage canvasImage;
|
private BufferedImage canvasImage;
|
||||||
@@ -43,13 +47,16 @@ public final class Canvas {
|
|||||||
drawText(new String(text, index, count), x, y, paint);
|
drawText(new String(text, index, count), x, y, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
|
public void drawText(@NonNull String str, float x, float y, @NonNull Paint paint) {
|
||||||
applyPaint(paint);
|
applyPaint(paint);
|
||||||
GlyphVector glyphVector = paint.getFont().createGlyphVector(canvas.getFontRenderContext(), text);
|
AttributedString text = paint.getTypeface().createWithFallback(str);
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||||
|
// TODO: fix with fallback fonts
|
||||||
|
GlyphVector glyphVector = paint.getTypeface().getFont().createGlyphVector(canvas.getFontRenderContext(), text.getIterator());
|
||||||
Shape textShape = glyphVector.getOutline();
|
Shape textShape = glyphVector.getOutline();
|
||||||
switch (paint.getStyle()) {
|
switch (paint.getStyle()) {
|
||||||
case Paint.Style.FILL:
|
case Paint.Style.FILL:
|
||||||
canvas.drawString(text, x, y);
|
canvas.drawString(text.getIterator(), x, y);
|
||||||
break;
|
break;
|
||||||
case Paint.Style.STROKE:
|
case Paint.Style.STROKE:
|
||||||
save();
|
save();
|
||||||
@@ -164,11 +171,33 @@ public final class Canvas {
|
|||||||
return r.width != 0 && r.height != 0;
|
return r.width != 0 && r.height != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void drawColor(@ColorInt int colorInt) {
|
||||||
|
java.awt.Color color = Color.valueOf(colorInt).toJavaColor();
|
||||||
|
canvas.setColor(color);
|
||||||
|
canvas.fillRect(0, 0, canvasImage.getWidth(), canvasImage.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawColor(@ColorLong long colorLong) {
|
||||||
|
java.awt.Color color = Color.valueOf(colorLong).toJavaColor();
|
||||||
|
canvas.setColor(color);
|
||||||
|
canvas.fillRect(0, 0, canvasImage.getWidth(), canvasImage.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPoint(float x, float y, Paint paint) {
|
||||||
|
applyPaint(paint);
|
||||||
|
Shape shape = paintToShape(paint, x, y);
|
||||||
|
if (paint.getStyle() == Paint.Style.FILL) {
|
||||||
|
canvas.fill(shape);
|
||||||
|
} else {
|
||||||
|
canvas.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void applyPaint(Paint paint) {
|
private void applyPaint(Paint paint) {
|
||||||
canvas.setFont(paint.getFont());
|
canvas.setFont(paint.getTypeface().getFont());
|
||||||
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||||
canvas.setColor(color);
|
canvas.setColor(color);
|
||||||
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), paintToStrokeCap(paint), BasicStroke.JOIN_ROUND));
|
||||||
if (paint.isAntiAlias()) {
|
if (paint.isAntiAlias()) {
|
||||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
} else {
|
} else {
|
||||||
@@ -181,4 +210,34 @@ public final class Canvas {
|
|||||||
}
|
}
|
||||||
// TODO: use more from paint?
|
// TODO: use more from paint?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int paintToStrokeCap(Paint paint) {
|
||||||
|
switch (paint.getStrokeCap()) {
|
||||||
|
case BUTT:
|
||||||
|
return BasicStroke.CAP_BUTT;
|
||||||
|
case SQUARE:
|
||||||
|
return BasicStroke.CAP_SQUARE;
|
||||||
|
case ROUND:
|
||||||
|
return BasicStroke.CAP_ROUND;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Stroke cap " + paint.getStrokeCap() + " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Shape paintToShape(Paint paint, float x, float y) {
|
||||||
|
int width = (int)(paint.getStrokeWidth() * 2);
|
||||||
|
if (width <= 0) width = 1;
|
||||||
|
int upLeftX = (int) (x - (float) width / 2);
|
||||||
|
int upLeftY = (int) (y - (float) width / 2);
|
||||||
|
switch (paint.getStrokeCap()) {
|
||||||
|
case BUTT:
|
||||||
|
return new Rectangle((int)x, (int)y, 1, 1);
|
||||||
|
case SQUARE:
|
||||||
|
return new Rectangle(upLeftX, upLeftY, width, width);
|
||||||
|
case ROUND:
|
||||||
|
return new Ellipse2D.Float(upLeftX, upLeftY, width, width);
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Stroke cap " + paint.getStrokeCap() + " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ public class Paint {
|
|||||||
@ColorLong private long mShadowLayerColor;
|
@ColorLong private long mShadowLayerColor;
|
||||||
|
|
||||||
private int mFlags;
|
private int mFlags;
|
||||||
private Font mFont = new Font(null);
|
|
||||||
private Style mStyle = Style.FILL;
|
private Style mStyle = Style.FILL;
|
||||||
private float mStrokeWidth = 1.0f;
|
private float mStrokeWidth = 1.0f;
|
||||||
|
private Cap mStrokeCap = Cap.BUTT;
|
||||||
|
private Typeface mTypeface = Typeface.DEFAULT;
|
||||||
|
|
||||||
private static final Object sCacheLock = new Object();
|
private static final Object sCacheLock = new Object();
|
||||||
|
|
||||||
@@ -278,10 +279,11 @@ public class Paint {
|
|||||||
mShadowLayerDy = 0.0f;
|
mShadowLayerDy = 0.0f;
|
||||||
mShadowLayerColor = Color.pack(0);
|
mShadowLayerColor = Color.pack(0);
|
||||||
|
|
||||||
setFlags(ANTI_ALIAS_FLAG);
|
|
||||||
mFont = new Font(null);
|
|
||||||
mStyle = Style.FILL;
|
mStyle = Style.FILL;
|
||||||
mStrokeWidth = 1.0f;
|
mStrokeWidth = 1.0f;
|
||||||
|
mStrokeCap = Cap.BUTT;
|
||||||
|
mTypeface = Typeface.DEFAULT;
|
||||||
|
setFlags(ANTI_ALIAS_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(Paint src) {
|
public void set(Paint src) {
|
||||||
@@ -314,9 +316,10 @@ public class Paint {
|
|||||||
mShadowLayerColor = paint.mShadowLayerColor;
|
mShadowLayerColor = paint.mShadowLayerColor;
|
||||||
|
|
||||||
mFlags = paint.mFlags;
|
mFlags = paint.mFlags;
|
||||||
mFont = paint.mFont;
|
|
||||||
mStyle = paint.mStyle;
|
mStyle = paint.mStyle;
|
||||||
mStrokeWidth = paint.mStrokeWidth;
|
mStrokeWidth = paint.mStrokeWidth;
|
||||||
|
mStrokeCap = paint.mStrokeCap;
|
||||||
|
mTypeface = paint.mTypeface;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@@ -368,13 +371,13 @@ public class Paint {
|
|||||||
fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
Map<TextAttribute, Object> atts = mTypeface.getAttributes();
|
||||||
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, null);
|
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, null);
|
||||||
if (weight instanceof Float) {
|
if (weight instanceof Float) {
|
||||||
fontAttributes.put(TextAttribute.WEIGHT, weight);
|
fontAttributes.put(TextAttribute.WEIGHT, weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
mFont = mFont.deriveFont(fontAttributes);
|
mTypeface = mTypeface.deriveFont(fontAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHinting() {
|
public int getHinting() {
|
||||||
@@ -526,11 +529,11 @@ public class Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cap getStrokeCap() {
|
public Cap getStrokeCap() {
|
||||||
throw new RuntimeException("Stub!");
|
return mStrokeCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStrokeCap(Cap cap) {
|
public void setStrokeCap(Cap cap) {
|
||||||
throw new RuntimeException("Stub!");
|
mStrokeCap = cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Join getStrokeJoin() {
|
public Join getStrokeJoin() {
|
||||||
@@ -605,14 +608,14 @@ public class Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Typeface getTypeface() {
|
public Typeface getTypeface() {
|
||||||
return new Typeface(mFont);
|
return mTypeface;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Typeface setTypeface(Typeface typeface) {
|
public Typeface setTypeface(Typeface typeface) {
|
||||||
Map<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
|
Map<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
|
||||||
fontAttributes.put(TextAttribute.WEIGHT, typeface.getJavaWeight());
|
fontAttributes.put(TextAttribute.WEIGHT, typeface.getJavaWeight());
|
||||||
mFont = typeface.getFont()
|
mTypeface = typeface
|
||||||
.deriveFont(mFont.getStyle(), mFont.getSize())
|
.deriveFont(mTypeface.getFont().getStyle(), mTypeface.getFont().getSize())
|
||||||
.deriveFont(fontAttributes);
|
.deriveFont(fontAttributes);
|
||||||
setFlags(mFlags);
|
setFlags(mFlags);
|
||||||
return typeface;
|
return typeface;
|
||||||
@@ -693,16 +696,12 @@ public class Paint {
|
|||||||
throw new RuntimeException("Stub!");
|
throw new RuntimeException("Stub!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Font getFont() {
|
|
||||||
return mFont;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getTextSize() {
|
public float getTextSize() {
|
||||||
return mFont.getSize2D();
|
return mTypeface.getFont().getSize2D();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTextSize(float textSize) {
|
public void setTextSize(float textSize) {
|
||||||
mFont = mFont.deriveFont(textSize);
|
mTypeface = mTypeface.deriveFont(textSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getTextScaleX() {
|
public float getTextScaleX() {
|
||||||
@@ -836,7 +835,7 @@ public class Paint {
|
|||||||
|
|
||||||
public float getFontMetrics(FontMetrics metrics) {
|
public float getFontMetrics(FontMetrics metrics) {
|
||||||
java.awt.Canvas c = new java.awt.Canvas();
|
java.awt.Canvas c = new java.awt.Canvas();
|
||||||
java.awt.FontMetrics m = c.getFontMetrics(mFont);
|
java.awt.FontMetrics m = c.getFontMetrics(mTypeface.getFont());
|
||||||
metrics.top = m.getMaxDescent();
|
metrics.top = m.getMaxDescent();
|
||||||
metrics.ascent = m.getAscent();
|
metrics.ascent = m.getAscent();
|
||||||
metrics.descent = m.getDescent();
|
metrics.descent = m.getDescent();
|
||||||
|
|||||||
@@ -37,13 +37,27 @@ public final class Rect {
|
|||||||
this.right = 0;
|
this.right = 0;
|
||||||
this.bottom = 0;
|
this.bottom = 0;
|
||||||
} else {
|
} else {
|
||||||
this.left = left;
|
this.left = r.left;
|
||||||
this.top = top;
|
this.top = r.top;
|
||||||
this.right = right;
|
this.right = r.right;
|
||||||
this.bottom = bottom;
|
this.bottom = r.bottom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void set(int left, int top, int right, int bottom) {
|
||||||
|
this.left = left;
|
||||||
|
this.top = top;
|
||||||
|
this.right = right;
|
||||||
|
this.bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Rect r) {
|
||||||
|
this.left = r.left;
|
||||||
|
this.top = r.top;
|
||||||
|
this.right = r.right;
|
||||||
|
this.bottom = r.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
public final int getWidth() {
|
public final int getWidth() {
|
||||||
return right - left;
|
return right - left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ import java.io.FileDescriptor;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.text.AttributedString;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +71,7 @@ public class Typeface {
|
|||||||
public static final String DEFAULT_FAMILY = "sans-serif";
|
public static final String DEFAULT_FAMILY = "sans-serif";
|
||||||
|
|
||||||
private final Font mFont;
|
private final Font mFont;
|
||||||
|
private final List<Font> mFallbackFonts;
|
||||||
|
|
||||||
/** Returns the typeface's weight value */
|
/** Returns the typeface's weight value */
|
||||||
public int getWeight() {
|
public int getWeight() {
|
||||||
@@ -271,6 +278,76 @@ public class Typeface {
|
|||||||
|
|
||||||
public Typeface(Font fnt) {
|
public Typeface(Font fnt) {
|
||||||
mFont = fnt;
|
mFont = fnt;
|
||||||
|
mFallbackFonts = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface(Font fnt, List<Font> fallbackFonts) {
|
||||||
|
mFont = fnt;
|
||||||
|
mFallbackFonts = fallbackFonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<TextAttribute, Object> getAttributes() {
|
||||||
|
return (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(Map<TextAttribute, Object> attributes) {
|
||||||
|
Font mainFont = mFont.deriveFont(attributes);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(attributes))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(float size) {
|
||||||
|
Font mainFont = mFont.deriveFont(size);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(size))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(int style, float size) {
|
||||||
|
Font mainFont = mFont.deriveFont(style, size);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(style, size))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString createWithFallback(String text) {
|
||||||
|
AttributedString result = new AttributedString(text);
|
||||||
|
|
||||||
|
int textLength = text.length();
|
||||||
|
result.addAttribute(TextAttribute.FONT, mFont, 0, textLength);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (true) {
|
||||||
|
int until = mFont.canDisplayUpTo(result.getIterator(), i, textLength);
|
||||||
|
if (until == -1) break;
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
// find a fallback font from `until`
|
||||||
|
for (int j = 0; j < mFallbackFonts.size(); ++j) {
|
||||||
|
int fallbackUntil = until;
|
||||||
|
for (; fallbackUntil < textLength; ++fallbackUntil) {
|
||||||
|
if (mFont.canDisplay(text.charAt(fallbackUntil)) || !mFallbackFonts.get(j).canDisplay(text.charAt(fallbackUntil)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fallbackUntil > until) {
|
||||||
|
// use this and advance
|
||||||
|
int end = fallbackUntil >= 0 ? fallbackUntil : textLength;
|
||||||
|
result.addAttribute(TextAttribute.FONT, mFallbackFonts.get(j), until, end);
|
||||||
|
Log.v(TAG, String.format("Fallback: from %d to %d using %s", until, end, mFallbackFonts.get(j).getName()));
|
||||||
|
i = end;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) continue;
|
||||||
|
|
||||||
|
Log.w(TAG, String.format("No fallback font found at %d, skipping", until));
|
||||||
|
i = until + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Typeface createFromFile(@Nullable File file) {
|
public static Typeface createFromFile(@Nullable File file) {
|
||||||
@@ -316,12 +393,30 @@ public class Typeface {
|
|||||||
return mFont.hashCode();
|
return mFont.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Font loadFontAsset(String font) {
|
||||||
|
try (InputStream defaultNormalStream = ClassLoader.getSystemClassLoader().getResourceAsStream("font/" + font)) {
|
||||||
|
return Font.createFont(Font.TRUETYPE_FONT, defaultNormalStream).deriveFont(12.0f);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Failed to load " + font, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Typeface withFallback(Font baseFallback, String mainFont, String... fonts) {
|
||||||
|
Font main = loadFontAsset(mainFont);
|
||||||
|
if (main == null) main = new Font(null, 0, 12);
|
||||||
|
List<Font> fallbacks = Stream.concat(Arrays.stream(fonts).map(Typeface::loadFontAsset).filter(f -> f != null), Stream.of(baseFallback))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Log.v(TAG, String.format("Loaded font %s with %d fallback fonts", main.getName(), fallbacks.size()));
|
||||||
|
return new Typeface(main, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
DEFAULT = new Typeface(new Font(null, 0, 12));
|
DEFAULT = withFallback(new Font(null, 0, 12), "NotoSans/NotoSans-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
DEFAULT_BOLD = new Typeface(new Font(null, Font.BOLD, 12));
|
DEFAULT_BOLD = DEFAULT.deriveFont(Font.BOLD);
|
||||||
SANS_SERIF = new Typeface(new Font(Font.SANS_SERIF, 0, 12));
|
SANS_SERIF = DEFAULT;
|
||||||
SERIF = new Typeface(new Font(Font.SERIF, 0, 12));
|
SERIF = withFallback(new Font(Font.SERIF, 0, 12), "NotoSans/NotoSerif-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
MONOSPACE = new Typeface(new Font(Font.MONOSPACED, 0, 12));
|
MONOSPACE = withFallback(new Font(Font.MONOSPACED, 0, 12), "NotoSans/NotoSansMono-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -366,9 +366,8 @@ public class StaticLayout extends Layout {
|
|||||||
mRightIndents = b.mRightIndents;
|
mRightIndents = b.mRightIndents;
|
||||||
|
|
||||||
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||||
AttributedString text = new AttributedString(str);
|
AttributedString text = getPaint().getTypeface().createWithFallback(str);
|
||||||
text.addAttribute(TextAttribute.FONT, getPaint().getFont());
|
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
FontRenderContext frc = new FontRenderContext(getPaint().getFont().getTransform(), RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
|
||||||
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||||
// TODO: directions
|
// TODO: directions
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import android.text.Layout.Directions;
|
|||||||
import android.text.Layout.TabStops;
|
import android.text.Layout.TabStops;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.font.FontRenderContext;
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.TextMeasurer;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
|
||||||
public class TextLine {
|
public class TextLine {
|
||||||
@@ -149,7 +151,9 @@ public class TextLine {
|
|||||||
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||||
@Nullable LineInfo lineInfo) {
|
@Nullable LineInfo lineInfo) {
|
||||||
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
return (float) mPaint.getFont().getStringBounds(mText.toString(), mStart, mStart + mLen, frc).getWidth();
|
AttributedString text = mPaint.getTypeface().createWithFallback(mText.toString());
|
||||||
|
TextMeasurer tm = new TextMeasurer(text.getIterator(), frc);
|
||||||
|
return (float) tm.getLayout(mStart, mStart + mLen).getBounds().getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package android.util;
|
package android.util;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.event.Level;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -92,7 +93,7 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int log(int level, String tag, String msg) {
|
private static int log(int level, String tag, String msg) {
|
||||||
logger.info(formatLog(level, tag, msg));
|
logger.atLevel(intToLevel(level)).log(formatLog(tag, msg));
|
||||||
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,39 +102,35 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int log(int level, String tag, String msg, Throwable t) {
|
private static int log(int level, String tag, String msg, Throwable t) {
|
||||||
logger.info(formatLog(level, tag, msg), t);
|
logger.atLevel(intToLevel(level)).setCause(t).log(formatLog(tag, msg));
|
||||||
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatLog(int level, String tag, String msg) {
|
private static String formatLog(String tag, String msg) {
|
||||||
StringBuilder first = new StringBuilder("[");
|
StringBuilder first = new StringBuilder("[");
|
||||||
switch(level) {
|
|
||||||
case ASSERT:
|
|
||||||
first.append("ASSERT");
|
|
||||||
break;
|
|
||||||
case DEBUG:
|
|
||||||
first.append("DEBUG");
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
first.append("ERROR");
|
|
||||||
break;
|
|
||||||
case INFO:
|
|
||||||
first.append("INFO");
|
|
||||||
break;
|
|
||||||
case VERBOSE:
|
|
||||||
first.append("VERBOSE");
|
|
||||||
break;
|
|
||||||
case WARN:
|
|
||||||
first.append("WARN");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
first.append("UNKNOWN");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
first.append("] ");
|
|
||||||
first.append(tag);
|
first.append(tag);
|
||||||
first.append(": ");
|
first.append("]: ");
|
||||||
first.append(msg);
|
first.append(msg);
|
||||||
return first.toString();
|
return first.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Level intToLevel(int level) {
|
||||||
|
switch(level) {
|
||||||
|
case ASSERT:
|
||||||
|
return Level.ERROR;
|
||||||
|
case DEBUG:
|
||||||
|
return Level.DEBUG;
|
||||||
|
case ERROR:
|
||||||
|
return Level.ERROR;
|
||||||
|
case INFO:
|
||||||
|
return Level.INFO;
|
||||||
|
case VERBOSE:
|
||||||
|
return Level.TRACE;
|
||||||
|
case WARN:
|
||||||
|
return Level.WARN;
|
||||||
|
default:
|
||||||
|
return Level.INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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 android.util;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache that holds strong references to a limited number of values. Each time
|
||||||
|
* a value is accessed, it is moved to the head of a queue. When a value is
|
||||||
|
* added to a full cache, the value at the end of that queue is evicted and may
|
||||||
|
* become eligible for garbage collection.
|
||||||
|
*
|
||||||
|
* <p>If your cached values hold resources that need to be explicitly released,
|
||||||
|
* override {@link #entryRemoved}.
|
||||||
|
*
|
||||||
|
* <p>If a cache miss should be computed on demand for the corresponding keys,
|
||||||
|
* override {@link #create}. This simplifies the calling code, allowing it to
|
||||||
|
* assume a value will always be returned, even when there's a cache miss.
|
||||||
|
*
|
||||||
|
* <p>By default, the cache size is measured in the number of entries. Override
|
||||||
|
* {@link #sizeOf} to size the cache in different units. For example, this cache
|
||||||
|
* is limited to 4MiB of bitmaps:
|
||||||
|
* <pre> {@code
|
||||||
|
* int cacheSize = 4 * 1024 * 1024; // 4MiB
|
||||||
|
* LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
* protected int sizeOf(String key, Bitmap value) {
|
||||||
|
* return value.getByteCount();
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>This class is thread-safe. Perform multiple cache operations atomically by
|
||||||
|
* synchronizing on the cache: <pre> {@code
|
||||||
|
* synchronized (cache) {
|
||||||
|
* if (cache.get(key) == null) {
|
||||||
|
* cache.put(key, value);
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>This class does not allow null to be used as a key or value. A return
|
||||||
|
* value of null from {@link #get}, {@link #put} or {@link #remove} is
|
||||||
|
* unambiguous: the key was not in the cache.
|
||||||
|
*
|
||||||
|
* <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
|
||||||
|
* of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
|
||||||
|
* Support Package</a> for earlier releases.
|
||||||
|
*/
|
||||||
|
public class LruCache<K, V> {
|
||||||
|
private final LinkedHashMap<K, V> map;
|
||||||
|
|
||||||
|
/** Size of this cache in units. Not necessarily the number of elements. */
|
||||||
|
private int size;
|
||||||
|
private int maxSize;
|
||||||
|
|
||||||
|
private int putCount;
|
||||||
|
private int createCount;
|
||||||
|
private int evictionCount;
|
||||||
|
private int hitCount;
|
||||||
|
private int missCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param maxSize for caches that do not override {@link #sizeOf}, this is
|
||||||
|
* the maximum number of entries in the cache. For all other caches,
|
||||||
|
* this is the maximum sum of the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public LruCache(int maxSize) {
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxSize <= 0");
|
||||||
|
}
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the cache.
|
||||||
|
*
|
||||||
|
* @param maxSize The new maximum size.
|
||||||
|
*/
|
||||||
|
public void resize(int maxSize) {
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxSize <= 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
}
|
||||||
|
trimToSize(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value for {@code key} if it exists in the cache or can be
|
||||||
|
* created by {@code #create}. If a value was returned, it is moved to the
|
||||||
|
* head of the queue. This returns null if a value is not cached and cannot
|
||||||
|
* be created.
|
||||||
|
*/
|
||||||
|
public final V get(K key) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V mapValue;
|
||||||
|
synchronized (this) {
|
||||||
|
mapValue = map.get(key);
|
||||||
|
if (mapValue != null) {
|
||||||
|
hitCount++;
|
||||||
|
return mapValue;
|
||||||
|
}
|
||||||
|
missCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to create a value. This may take a long time, and the map
|
||||||
|
* may be different when create() returns. If a conflicting value was
|
||||||
|
* added to the map while create() was working, we leave that value in
|
||||||
|
* the map and release the created value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
V createdValue = create(key);
|
||||||
|
if (createdValue == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
createCount++;
|
||||||
|
mapValue = map.put(key, createdValue);
|
||||||
|
|
||||||
|
if (mapValue != null) {
|
||||||
|
// There was a conflict so undo that last put
|
||||||
|
map.put(key, mapValue);
|
||||||
|
} else {
|
||||||
|
size += safeSizeOf(key, createdValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapValue != null) {
|
||||||
|
entryRemoved(false, key, createdValue, mapValue);
|
||||||
|
return mapValue;
|
||||||
|
} else {
|
||||||
|
trimToSize(maxSize);
|
||||||
|
return createdValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches {@code value} for {@code key}. The value is moved to the head of
|
||||||
|
* the queue.
|
||||||
|
*
|
||||||
|
* @return the previous value mapped by {@code key}.
|
||||||
|
*/
|
||||||
|
public final V put(K key, V value) {
|
||||||
|
if (key == null || value == null) {
|
||||||
|
throw new NullPointerException("key == null || value == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V previous;
|
||||||
|
synchronized (this) {
|
||||||
|
putCount++;
|
||||||
|
size += safeSizeOf(key, value);
|
||||||
|
previous = map.put(key, value);
|
||||||
|
if (previous != null) {
|
||||||
|
size -= safeSizeOf(key, previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
entryRemoved(false, key, previous, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
trimToSize(maxSize);
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the eldest entries until the total of remaining entries is at or
|
||||||
|
* below the requested size.
|
||||||
|
*
|
||||||
|
* @param maxSize the maximum size of the cache before returning. May be -1
|
||||||
|
* to evict even 0-sized elements.
|
||||||
|
*/
|
||||||
|
public void trimToSize(int maxSize) {
|
||||||
|
while (true) {
|
||||||
|
K key;
|
||||||
|
V value;
|
||||||
|
synchronized (this) {
|
||||||
|
if (size < 0 || (map.isEmpty() && size != 0)) {
|
||||||
|
throw new IllegalStateException(getClass().getName()
|
||||||
|
+ ".sizeOf() is reporting inconsistent results!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= maxSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.Entry<K, V> toEvict = eldest();
|
||||||
|
if (toEvict == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = toEvict.getKey();
|
||||||
|
value = toEvict.getValue();
|
||||||
|
map.remove(key);
|
||||||
|
size -= safeSizeOf(key, value);
|
||||||
|
evictionCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
entryRemoved(true, key, value, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map.Entry<K, V> eldest() {
|
||||||
|
return map.firstEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the entry for {@code key} if it exists.
|
||||||
|
*
|
||||||
|
* @return the previous value mapped by {@code key}.
|
||||||
|
*/
|
||||||
|
public final V remove(K key) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V previous;
|
||||||
|
synchronized (this) {
|
||||||
|
previous = map.remove(key);
|
||||||
|
if (previous != null) {
|
||||||
|
size -= safeSizeOf(key, previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
entryRemoved(false, key, previous, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for entries that have been evicted or removed. This method is
|
||||||
|
* invoked when a value is evicted to make space, removed by a call to
|
||||||
|
* {@link #remove}, or replaced by a call to {@link #put}. The default
|
||||||
|
* implementation does nothing.
|
||||||
|
*
|
||||||
|
* <p>The method is called without synchronization: other threads may
|
||||||
|
* access the cache while this method is executing.
|
||||||
|
*
|
||||||
|
* @param evicted true if the entry is being removed to make space, false
|
||||||
|
* if the removal was caused by a {@link #put} or {@link #remove}.
|
||||||
|
* @param newValue the new value for {@code key}, if it exists. If non-null,
|
||||||
|
* this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
|
||||||
|
* an eviction or a {@link #remove}.
|
||||||
|
*/
|
||||||
|
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after a cache miss to compute a value for the corresponding key.
|
||||||
|
* Returns the computed value or null if no value can be computed. The
|
||||||
|
* default implementation returns null.
|
||||||
|
*
|
||||||
|
* <p>The method is called without synchronization: other threads may
|
||||||
|
* access the cache while this method is executing.
|
||||||
|
*
|
||||||
|
* <p>If a value for {@code key} exists in the cache when this method
|
||||||
|
* returns, the created value will be released with {@link #entryRemoved}
|
||||||
|
* and discarded. This can occur when multiple threads request the same key
|
||||||
|
* at the same time (causing multiple values to be created), or when one
|
||||||
|
* thread calls {@link #put} while another is creating a value for the same
|
||||||
|
* key.
|
||||||
|
*/
|
||||||
|
protected V create(K key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int safeSizeOf(K key, V value) {
|
||||||
|
int result = sizeOf(key, value);
|
||||||
|
if (result < 0) {
|
||||||
|
throw new IllegalStateException("Negative size: " + key + "=" + value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the entry for {@code key} and {@code value} in
|
||||||
|
* user-defined units. The default implementation returns 1 so that size
|
||||||
|
* is the number of entries and max size is the maximum number of entries.
|
||||||
|
*
|
||||||
|
* <p>An entry's size must not change while it is in the cache.
|
||||||
|
*/
|
||||||
|
protected int sizeOf(K key, V value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
|
||||||
|
*/
|
||||||
|
public final void evictAll() {
|
||||||
|
trimToSize(-1); // -1 will evict 0-sized elements
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For caches that do not override {@link #sizeOf}, this returns the number
|
||||||
|
* of entries in the cache. For all other caches, this returns the sum of
|
||||||
|
* the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For caches that do not override {@link #sizeOf}, this returns the maximum
|
||||||
|
* number of entries in the cache. For all other caches, this returns the
|
||||||
|
* maximum sum of the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int maxSize() {
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #get} returned a value that was
|
||||||
|
* already present in the cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int hitCount() {
|
||||||
|
return hitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #get} returned null or required a new
|
||||||
|
* value to be created.
|
||||||
|
*/
|
||||||
|
public synchronized final int missCount() {
|
||||||
|
return missCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #create(Object)} returned a value.
|
||||||
|
*/
|
||||||
|
public synchronized final int createCount() {
|
||||||
|
return createCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #put} was called.
|
||||||
|
*/
|
||||||
|
public synchronized final int putCount() {
|
||||||
|
return putCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values that have been evicted.
|
||||||
|
*/
|
||||||
|
public synchronized final int evictionCount() {
|
||||||
|
return evictionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the current contents of the cache, ordered from least
|
||||||
|
* recently accessed to most recently accessed.
|
||||||
|
*/
|
||||||
|
public synchronized final Map<K, V> snapshot() {
|
||||||
|
return new LinkedHashMap<K, V>(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized final String toString() {
|
||||||
|
int accesses = hitCount + missCount;
|
||||||
|
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
|
||||||
|
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
|
||||||
|
maxSize, hitCount, missCount, hitPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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 android.webkit;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines a permission request and is used when web content
|
||||||
|
* requests access to protected resources. The permission request related events
|
||||||
|
* are delivered via {@link WebChromeClient#onPermissionRequest} and
|
||||||
|
* {@link WebChromeClient#onPermissionRequestCanceled}.
|
||||||
|
*
|
||||||
|
* Either {@link #grant(String[]) grant()} or {@link #deny()} must be called in UI
|
||||||
|
* thread to respond to the request.
|
||||||
|
*
|
||||||
|
* New protected resources whose names are not defined here may be requested in
|
||||||
|
* future versions of WebView, even when running on an older Android release. To
|
||||||
|
* avoid unintentionally granting requests for new permissions, you should pass the
|
||||||
|
* specific permissions you intend to grant to {@link #grant(String[]) grant()},
|
||||||
|
* and avoid writing code like this example:
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* permissionRequest.grant(permissionRequest.getResources()) // This is wrong!!!
|
||||||
|
* </pre>
|
||||||
|
* See the WebView's release notes for information about new protected resources.
|
||||||
|
*/
|
||||||
|
public abstract class PermissionRequest {
|
||||||
|
/**
|
||||||
|
* Resource belongs to video capture device, like camera.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_VIDEO_CAPTURE = "android.webkit.resource.VIDEO_CAPTURE";
|
||||||
|
/**
|
||||||
|
* Resource belongs to audio capture device, like microphone.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_AUDIO_CAPTURE = "android.webkit.resource.AUDIO_CAPTURE";
|
||||||
|
/**
|
||||||
|
* Resource belongs to protected media identifier.
|
||||||
|
* After the user grants this resource, the origin can use EME APIs to generate the license
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_PROTECTED_MEDIA_ID =
|
||||||
|
"android.webkit.resource.PROTECTED_MEDIA_ID";
|
||||||
|
/**
|
||||||
|
* Resource will allow sysex messages to be sent to or received from MIDI devices. These
|
||||||
|
* messages are privileged operations, e.g. modifying sound libraries and sampling data, or
|
||||||
|
* even updating the MIDI device's firmware.
|
||||||
|
*
|
||||||
|
* Permission may be requested for this resource in API levels 21 and above, if the Android
|
||||||
|
* device has been updated to WebView 45 or above.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_MIDI_SYSEX = "android.webkit.resource.MIDI_SYSEX";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to get the origin of the web page which is trying to access
|
||||||
|
* the restricted resources.
|
||||||
|
*
|
||||||
|
* @return the origin of web content which attempt to access the restricted
|
||||||
|
* resources.
|
||||||
|
*/
|
||||||
|
public abstract Uri getOrigin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to get the resources the web page is trying to access.
|
||||||
|
*
|
||||||
|
* @return the array of resources the web content wants to access.
|
||||||
|
*/
|
||||||
|
public abstract String[] getResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to grant origin the permission to access the given resources.
|
||||||
|
* The granted permission is only valid for this WebView.
|
||||||
|
*
|
||||||
|
* @param resources the resources granted to be accessed by origin, to grant
|
||||||
|
* request, the requested resources returned by {@link #getResources()}
|
||||||
|
* must be equals or a subset of granted resources.
|
||||||
|
* This parameter is designed to avoid granting permission by accident
|
||||||
|
* especially when new resources are requested by web content.
|
||||||
|
*/
|
||||||
|
public abstract void grant(String[] resources);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to deny the request.
|
||||||
|
*/
|
||||||
|
public abstract void deny();
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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 android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringBufferInputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a resource response. Applications can return an instance of this
|
||||||
|
* class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
|
||||||
|
* response when the WebView requests a particular resource.
|
||||||
|
*/
|
||||||
|
public class WebResourceResponse {
|
||||||
|
private boolean mImmutable;
|
||||||
|
private String mMimeType;
|
||||||
|
private String mEncoding;
|
||||||
|
private int mStatusCode;
|
||||||
|
private String mReasonPhrase;
|
||||||
|
private Map<String, String> mResponseHeaders;
|
||||||
|
private InputStream mInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a resource response with the given MIME type, character encoding,
|
||||||
|
* and input stream. Callers must implement {@link InputStream#read(byte[])} for
|
||||||
|
* the input stream. {@link InputStream#close()} will be called after the WebView
|
||||||
|
* has finished with the response.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> The MIME type and character encoding must
|
||||||
|
* be specified as separate parameters (for example {@code "text/html"} and
|
||||||
|
* {@code "utf-8"}), not a single value like the {@code "text/html; charset=utf-8"}
|
||||||
|
* format used in the HTTP Content-Type header. Do not use the value of a HTTP
|
||||||
|
* Content-Encoding header for {@code encoding}, as that header does not specify a
|
||||||
|
* character encoding. Content without a defined character encoding (for example
|
||||||
|
* image resources) should pass {@code null} for {@code encoding}.
|
||||||
|
*
|
||||||
|
* @param mimeType the resource response's MIME type, for example {@code "text/html"}.
|
||||||
|
* @param encoding the resource response's character encoding, for example {@code "utf-8"}.
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public WebResourceResponse(String mimeType, String encoding,
|
||||||
|
InputStream data) {
|
||||||
|
mMimeType = mimeType;
|
||||||
|
mEncoding = encoding;
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a resource response with the given parameters. Callers must implement
|
||||||
|
* {@link InputStream#read(byte[])} for the input stream. {@link InputStream#close()} will be
|
||||||
|
* called after the WebView has finished with the response.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> See {@link #WebResourceResponse(String,String,InputStream)}
|
||||||
|
* for details on what should be specified for {@code mimeType} and {@code encoding}.
|
||||||
|
*
|
||||||
|
* @param mimeType the resource response's MIME type, for example {@code "text/html"}.
|
||||||
|
* @param encoding the resource response's character encoding, for example {@code "utf-8"}.
|
||||||
|
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
|
||||||
|
* Causing a redirect by specifying a 3xx code is not supported.
|
||||||
|
* @param reasonPhrase the phrase describing the status code, for example "OK". Must be
|
||||||
|
* non-empty.
|
||||||
|
* @param responseHeaders the resource response's headers represented as a mapping of header
|
||||||
|
* name -> header value.
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public WebResourceResponse(String mimeType, String encoding, int statusCode,
|
||||||
|
@NonNull String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||||
|
this(mimeType, encoding, data);
|
||||||
|
setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
|
||||||
|
setResponseHeaders(responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's MIME type, for example "text/html".
|
||||||
|
*
|
||||||
|
* @param mimeType The resource response's MIME type
|
||||||
|
*/
|
||||||
|
public void setMimeType(String mimeType) {
|
||||||
|
checkImmutable();
|
||||||
|
mMimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's MIME type.
|
||||||
|
*
|
||||||
|
* @return The resource response's MIME type
|
||||||
|
*/
|
||||||
|
public String getMimeType() {
|
||||||
|
return mMimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's encoding, for example "UTF-8". This is used
|
||||||
|
* to decode the data from the input stream.
|
||||||
|
*
|
||||||
|
* @param encoding The resource response's encoding
|
||||||
|
*/
|
||||||
|
public void setEncoding(String encoding) {
|
||||||
|
checkImmutable();
|
||||||
|
mEncoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's encoding.
|
||||||
|
*
|
||||||
|
* @return The resource response's encoding
|
||||||
|
*/
|
||||||
|
public String getEncoding() {
|
||||||
|
return mEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's status code and reason phrase.
|
||||||
|
*
|
||||||
|
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
|
||||||
|
* Causing a redirect by specifying a 3xx code is not supported.
|
||||||
|
* @param reasonPhrase the phrase describing the status code, for example "OK". Must be
|
||||||
|
* non-empty.
|
||||||
|
*/
|
||||||
|
public void setStatusCodeAndReasonPhrase(int statusCode, @NonNull String reasonPhrase) {
|
||||||
|
checkImmutable();
|
||||||
|
if (statusCode < 100)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be less than 100.");
|
||||||
|
if (statusCode > 599)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be greater than 599.");
|
||||||
|
if (statusCode > 299 && statusCode < 400)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be in the [300, 399] range.");
|
||||||
|
if (reasonPhrase == null)
|
||||||
|
throw new IllegalArgumentException("reasonPhrase can't be null.");
|
||||||
|
if (reasonPhrase.trim().isEmpty())
|
||||||
|
throw new IllegalArgumentException("reasonPhrase can't be empty.");
|
||||||
|
for (int i = 0; i < reasonPhrase.length(); i++) {
|
||||||
|
int c = reasonPhrase.charAt(i);
|
||||||
|
if (c > 0x7F) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"reasonPhrase can't contain non-ASCII characters.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mStatusCode = statusCode;
|
||||||
|
mReasonPhrase = reasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's status code.
|
||||||
|
*
|
||||||
|
* @return The resource response's status code.
|
||||||
|
*/
|
||||||
|
public int getStatusCode() {
|
||||||
|
return mStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the resource response's status code.
|
||||||
|
*
|
||||||
|
* @return The description of the resource response's status code.
|
||||||
|
*/
|
||||||
|
public String getReasonPhrase() {
|
||||||
|
return mReasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the headers for the resource response.
|
||||||
|
*
|
||||||
|
* @param headers Mapping of header name -> header value.
|
||||||
|
*/
|
||||||
|
public void setResponseHeaders(Map<String, String> headers) {
|
||||||
|
checkImmutable();
|
||||||
|
mResponseHeaders = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the headers for the resource response.
|
||||||
|
*
|
||||||
|
* @return The headers for the resource response.
|
||||||
|
*/
|
||||||
|
public Map<String, String> getResponseHeaders() {
|
||||||
|
return mResponseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input stream that provides the resource response's data. Callers
|
||||||
|
* must implement {@link InputStream#read(byte[])}. {@link InputStream#close()}
|
||||||
|
* will be called after the WebView has finished with the response.
|
||||||
|
*
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public void setData(InputStream data) {
|
||||||
|
checkImmutable();
|
||||||
|
// If data is (or is a subclass of) StringBufferInputStream
|
||||||
|
if (data != null && StringBufferInputStream.class.isAssignableFrom(data.getClass())) {
|
||||||
|
throw new IllegalArgumentException("StringBufferInputStream is deprecated and must " +
|
||||||
|
"not be passed to a WebResourceResponse");
|
||||||
|
}
|
||||||
|
mInputStream = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the input stream that provides the resource response's data.
|
||||||
|
*
|
||||||
|
* @return The input stream that provides the resource response's data
|
||||||
|
*/
|
||||||
|
public InputStream getData() {
|
||||||
|
return mInputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal version of the constructor that doesn't perform arguments checks.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SystemApi
|
||||||
|
public WebResourceResponse(boolean immutable, String mimeType, String encoding, int statusCode,
|
||||||
|
String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||||
|
mImmutable = immutable;
|
||||||
|
mMimeType = mimeType;
|
||||||
|
mEncoding = encoding;
|
||||||
|
mStatusCode = statusCode;
|
||||||
|
mReasonPhrase = reasonPhrase;
|
||||||
|
mResponseHeaders = responseHeaders;
|
||||||
|
mInputStream = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkImmutable() {
|
||||||
|
if (mImmutable)
|
||||||
|
throw new IllegalStateException("This WebResourceResponse instance is immutable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,14 +7,26 @@ package android.widget;
|
|||||||
* 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/. */
|
||||||
|
|
||||||
public class EditText {
|
public class EditText extends TextView {
|
||||||
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ package androidx.preference;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +25,7 @@ public class Preference {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
protected Context context;
|
protected Context context;
|
||||||
|
|
||||||
private boolean isVisible;
|
private boolean isVisible = true;
|
||||||
private boolean isEnabled = true;
|
private boolean isEnabled = true;
|
||||||
private String key;
|
private String key;
|
||||||
private CharSequence title;
|
private CharSequence title;
|
||||||
@@ -130,33 +133,34 @@ public class Preference {
|
|||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Object getCurrentValue() {
|
public Object getCurrentValue() {
|
||||||
switch (getDefaultValueType()) {
|
if (key == null) {
|
||||||
case "String":
|
return Objects.requireNonNullElseGet(defaultValue, () -> switch (getDefaultValueType()) {
|
||||||
return sharedPreferences.getString(key, (String)defaultValue);
|
case "String" -> "";
|
||||||
case "Boolean":
|
case "Boolean" -> false;
|
||||||
return sharedPreferences.getBoolean(key, (Boolean)defaultValue);
|
case "Set<String>" -> new HashSet<>();
|
||||||
case "Set<String>":
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
return sharedPreferences.getStringSet(key, (Set<String>)defaultValue);
|
});
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unsupported type");
|
|
||||||
}
|
}
|
||||||
|
return switch (getDefaultValueType()) {
|
||||||
|
case "String" -> sharedPreferences.getString(key, (String) defaultValue);
|
||||||
|
case "Boolean" -> sharedPreferences.getBoolean(key, (Boolean) defaultValue);
|
||||||
|
case "Set<String>" -> sharedPreferences.getStringSet(key, (Set<String>) defaultValue);
|
||||||
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void saveNewValue(Object value) {
|
public void saveNewValue(Object value) {
|
||||||
|
if (key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (getDefaultValueType()) {
|
switch (getDefaultValueType()) {
|
||||||
case "String":
|
case "String" -> sharedPreferences.edit().putString(key, (String) value).apply();
|
||||||
sharedPreferences.edit().putString(key, (String)value).apply();
|
case "Boolean" -> sharedPreferences.edit().putBoolean(key, (Boolean) value).apply();
|
||||||
break;
|
case "Set<String>" ->
|
||||||
case "Boolean":
|
sharedPreferences.edit().putStringSet(key, (Set<String>) value).apply();
|
||||||
sharedPreferences.edit().putBoolean(key, (Boolean)value).apply();
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
break;
|
|
||||||
case "Set<String>":
|
|
||||||
sharedPreferences.edit().putStringSet(key, (Set<String>)value).apply();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unsupported type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
@file:Suppress("UNUSED")
|
||||||
|
|
||||||
|
package kotlinx.coroutines.android
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Delay
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.DisposableHandle
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.InternalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.MainCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.NonDisposableHandle
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.internal.MainDispatcherFactory
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches execution onto Android [Handler].
|
||||||
|
*
|
||||||
|
* This class provides type-safety and a point for future extensions.
|
||||||
|
*/
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
public sealed class HandlerDispatcher :
|
||||||
|
MainCoroutineDispatcher(),
|
||||||
|
Delay {
|
||||||
|
/**
|
||||||
|
* Returns dispatcher that executes coroutines immediately when it is already in the right context
|
||||||
|
* (current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
|
||||||
|
* This dispatcher does not use [Handler.post] when current looper is the same as looper of the handler.
|
||||||
|
*
|
||||||
|
* Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined].
|
||||||
|
* The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
|
||||||
|
*
|
||||||
|
* Example of usage:
|
||||||
|
* ```
|
||||||
|
* suspend fun updateUiElement(val text: String) {
|
||||||
|
* /*
|
||||||
|
* * If it is known that updateUiElement can be invoked both from the Main thread and from other threads,
|
||||||
|
* * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch.
|
||||||
|
* *
|
||||||
|
* * In that case, when `updateUiElement` is invoked from the Main thread, `uiElement.text` will be
|
||||||
|
* * invoked immediately without any dispatching, otherwise, the `Dispatchers.Main` dispatch cycle via
|
||||||
|
* * `Handler.post` will be triggered.
|
||||||
|
* */
|
||||||
|
* withContext(Dispatchers.Main.immediate) {
|
||||||
|
* uiElement.text = text
|
||||||
|
* }
|
||||||
|
* // Do context-independent logic such as logging
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public abstract override val immediate: HandlerDispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
internal class AndroidDispatcherFactory : MainDispatcherFactory {
|
||||||
|
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
|
||||||
|
val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
|
||||||
|
return HandlerContext(mainLooper.asHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
|
||||||
|
|
||||||
|
override val loadPriority: Int
|
||||||
|
get() = Int.MAX_VALUE / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an arbitrary [Handler] as an implementation of [CoroutineDispatcher]
|
||||||
|
* with an optional [name] for nicer debugging
|
||||||
|
*
|
||||||
|
* ## Rejected execution
|
||||||
|
*
|
||||||
|
* If the underlying handler is closed and its message-scheduling methods start to return `false` on
|
||||||
|
* an attempt to submit a continuation task to the resulting dispatcher,
|
||||||
|
* then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
|
||||||
|
* [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
|
||||||
|
*/
|
||||||
|
@JvmName("from") // this is for a nice Java API, see issue #255
|
||||||
|
@JvmOverloads
|
||||||
|
public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatcher =
|
||||||
|
HandlerContext(this, name)
|
||||||
|
|
||||||
|
private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
|
||||||
|
|
||||||
|
internal fun Looper.asHandler(): Handler = Handler(this)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
|
||||||
|
internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler()) }.getOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
|
||||||
|
*/
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
internal class HandlerContext private constructor(
|
||||||
|
private val handler: Handler,
|
||||||
|
private val name: String?,
|
||||||
|
private val invokeImmediately: Boolean,
|
||||||
|
) : HandlerDispatcher(),
|
||||||
|
Delay {
|
||||||
|
/**
|
||||||
|
* Creates [CoroutineDispatcher] for the given Android [handler].
|
||||||
|
*
|
||||||
|
* @param handler a handler.
|
||||||
|
* @param name an optional name for debugging.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
handler: Handler,
|
||||||
|
name: String? = null,
|
||||||
|
) : this(handler, name, false)
|
||||||
|
|
||||||
|
override val immediate: HandlerContext =
|
||||||
|
if (invokeImmediately) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
HandlerContext(handler, name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDispatchNeeded(context: CoroutineContext): Boolean = !invokeImmediately || Looper.myLooper() != handler.looper
|
||||||
|
|
||||||
|
override fun dispatch(
|
||||||
|
context: CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) {
|
||||||
|
if (!handler.post(block)) {
|
||||||
|
cancelOnRejection(context, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
override fun scheduleResumeAfterDelay(
|
||||||
|
timeMillis: Long,
|
||||||
|
continuation: CancellableContinuation<Unit>,
|
||||||
|
) {
|
||||||
|
val block =
|
||||||
|
Runnable {
|
||||||
|
with(continuation) { resumeUndispatched(Unit) }
|
||||||
|
}
|
||||||
|
if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
|
||||||
|
continuation.invokeOnCancellation { handler.removeCallbacks(block) }
|
||||||
|
} else {
|
||||||
|
cancelOnRejection(continuation.context, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invokeOnTimeout(
|
||||||
|
timeMillis: Long,
|
||||||
|
block: Runnable,
|
||||||
|
context: CoroutineContext,
|
||||||
|
): DisposableHandle {
|
||||||
|
if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
|
||||||
|
return DisposableHandle { handler.removeCallbacks(block) }
|
||||||
|
}
|
||||||
|
cancelOnRejection(context, block)
|
||||||
|
return NonDisposableHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelOnRejection(
|
||||||
|
context: CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) {
|
||||||
|
context.cancel(CancellationException("The task was rejected, the handler underlying the dispatcher '${toString()}' was closed"))
|
||||||
|
Dispatchers.IO.dispatch(context, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
toStringInternalImpl() ?: run {
|
||||||
|
val str = name ?: handler.toString()
|
||||||
|
if (invokeImmediately) "$str.immediate" else str
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean =
|
||||||
|
other is HandlerContext && other.handler === handler && other.invokeImmediately == invokeImmediately
|
||||||
|
|
||||||
|
// inlining `Boolean.hashCode()` for Android compatibility, as requested by Animal Sniffer
|
||||||
|
override fun hashCode(): Int = System.identityHashCode(handler) xor if (invokeImmediately) 1231 else 1237
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package libcore.net;
|
package libcore.net;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -249,10 +251,12 @@ public final class MimeUtils {
|
|||||||
add("audio/x-scpls", "pls");
|
add("audio/x-scpls", "pls");
|
||||||
add("audio/x-sd2", "sd2");
|
add("audio/x-sd2", "sd2");
|
||||||
add("audio/x-wav", "wav");
|
add("audio/x-wav", "wav");
|
||||||
|
add("image/avif", "avif");
|
||||||
// image/bmp isn't IANA, so image/x-ms-bmp should come first.
|
// image/bmp isn't IANA, so image/x-ms-bmp should come first.
|
||||||
add("image/x-ms-bmp", "bmp");
|
add("image/x-ms-bmp", "bmp");
|
||||||
add("image/bmp", "bmp");
|
add("image/bmp", "bmp");
|
||||||
add("image/gif", "gif");
|
add("image/gif", "gif");
|
||||||
|
add("image/heif", "heif");
|
||||||
// image/ico isn't IANA, so image/x-icon should come first.
|
// image/ico isn't IANA, so image/x-icon should come first.
|
||||||
add("image/x-icon", "ico");
|
add("image/x-icon", "ico");
|
||||||
add("image/ico", "cur");
|
add("image/ico", "cur");
|
||||||
@@ -262,6 +266,7 @@ public final class MimeUtils {
|
|||||||
add("image/jpeg", "jpg");
|
add("image/jpeg", "jpg");
|
||||||
add("image/jpeg", "jpeg");
|
add("image/jpeg", "jpeg");
|
||||||
add("image/jpeg", "jpe");
|
add("image/jpeg", "jpe");
|
||||||
|
add("image/jxl", "jxl");
|
||||||
add("image/pcx", "pcx");
|
add("image/pcx", "pcx");
|
||||||
add("image/png", "png");
|
add("image/png", "png");
|
||||||
add("image/svg+xml", "svg");
|
add("image/svg+xml", "svg");
|
||||||
@@ -438,6 +443,7 @@ public final class MimeUtils {
|
|||||||
* @return The extension has been registered for
|
* @return The extension has been registered for
|
||||||
* the given case insensitive MIME type or null if there is none.
|
* the given case insensitive MIME type or null if there is none.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public static String guessExtensionFromMimeType(String mimeType) {
|
public static String guessExtensionFromMimeType(String mimeType) {
|
||||||
if (mimeType == null || mimeType.isEmpty()) {
|
if (mimeType == null || mimeType.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+10
-6
@@ -242,13 +242,14 @@ class JavaSharedPreferences(
|
|||||||
}
|
}
|
||||||
notify(it.key)
|
notify(it.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.Remove -> {
|
is Action.Remove -> {
|
||||||
preferences.remove(it.key)
|
preferences.remove(it.key)
|
||||||
/**
|
/*
|
||||||
* Set<String> are stored like
|
Set<String> are stored like
|
||||||
* key.0 = value1
|
key.0 = value1
|
||||||
* key.1 = value2
|
key.1 = value2
|
||||||
* key.size = 2
|
key.size = 2
|
||||||
*/
|
*/
|
||||||
preferences.keys.forEach { key ->
|
preferences.keys.forEach { key ->
|
||||||
if (key.startsWith(it.key + ".")) {
|
if (key.startsWith(it.key + ".")) {
|
||||||
@@ -258,7 +259,10 @@ class JavaSharedPreferences(
|
|||||||
|
|
||||||
notify(it.key)
|
notify(it.key)
|
||||||
}
|
}
|
||||||
Action.Clear -> preferences.clear()
|
|
||||||
|
Action.Clear -> {
|
||||||
|
preferences.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-14
@@ -31,6 +31,7 @@ import android.view.inputmethod.EditorInfo
|
|||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
import android.view.textclassifier.TextClassifier
|
import android.view.textclassifier.TextClassifier
|
||||||
import android.webkit.DownloadListener
|
import android.webkit.DownloadListener
|
||||||
|
import android.webkit.PermissionRequest
|
||||||
import android.webkit.RenderProcessGoneDetail
|
import android.webkit.RenderProcessGoneDetail
|
||||||
import android.webkit.ValueCallback
|
import android.webkit.ValueCallback
|
||||||
import android.webkit.WebBackForwardList
|
import android.webkit.WebBackForwardList
|
||||||
@@ -62,11 +63,13 @@ import org.cef.browser.CefFrame
|
|||||||
import org.cef.browser.CefMessageRouter
|
import org.cef.browser.CefMessageRouter
|
||||||
import org.cef.browser.CefRendering
|
import org.cef.browser.CefRendering
|
||||||
import org.cef.callback.CefCallback
|
import org.cef.callback.CefCallback
|
||||||
|
import org.cef.callback.CefMediaAccessCallback
|
||||||
import org.cef.callback.CefQueryCallback
|
import org.cef.callback.CefQueryCallback
|
||||||
import org.cef.handler.CefDisplayHandlerAdapter
|
import org.cef.handler.CefDisplayHandlerAdapter
|
||||||
import org.cef.handler.CefLoadHandler
|
import org.cef.handler.CefLoadHandler
|
||||||
import org.cef.handler.CefLoadHandlerAdapter
|
import org.cef.handler.CefLoadHandlerAdapter
|
||||||
import org.cef.handler.CefMessageRouterHandlerAdapter
|
import org.cef.handler.CefMessageRouterHandlerAdapter
|
||||||
|
import org.cef.handler.CefPermissionHandler
|
||||||
import org.cef.handler.CefRequestHandler
|
import org.cef.handler.CefRequestHandler
|
||||||
import org.cef.handler.CefRequestHandlerAdapter
|
import org.cef.handler.CefRequestHandlerAdapter
|
||||||
import org.cef.handler.CefResourceHandler
|
import org.cef.handler.CefResourceHandler
|
||||||
@@ -167,6 +170,30 @@ class KcefWebViewProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CefPermissionRequest(
|
||||||
|
private val url: String,
|
||||||
|
private val permissionMask: Int,
|
||||||
|
private val callback: CefMediaAccessCallback,
|
||||||
|
) : PermissionRequest() {
|
||||||
|
override fun getOrigin(): Uri = Uri.parse(url)
|
||||||
|
|
||||||
|
override fun getResources(): Array<String> {
|
||||||
|
val retVal = mutableListOf<String>()
|
||||||
|
if ((permissionMask and (1 shl 0)) > 0) retVal.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE)
|
||||||
|
if ((permissionMask and (1 shl 1)) > 0) retVal.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE)
|
||||||
|
return retVal.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun grant(resources: Array<String>) {
|
||||||
|
// TODO: respect given resource grant
|
||||||
|
callback.Continue(permissionMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deny() {
|
||||||
|
callback.Cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private inner class DisplayHandler : CefDisplayHandlerAdapter() {
|
private inner class DisplayHandler : CefDisplayHandlerAdapter() {
|
||||||
override fun onConsoleMessage(
|
override fun onConsoleMessage(
|
||||||
browser: CefBrowser,
|
browser: CefBrowser,
|
||||||
@@ -363,7 +390,9 @@ class KcefWebViewProvider(
|
|||||||
Log.v(TAG, "Handling request from client's response for ${request.url}")
|
Log.v(TAG, "Handling request from client's response for ${request.url}")
|
||||||
try {
|
try {
|
||||||
resolvedData = webResponse.data.readAllBytes()
|
resolvedData = webResponse.data.readAllBytes()
|
||||||
|
Log.v(TAG, "Resolved client response for ${resolvedData?.size} bytes")
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, "Failed to read client data", e)
|
||||||
}
|
}
|
||||||
callback.Continue()
|
callback.Continue()
|
||||||
return true
|
return true
|
||||||
@@ -375,7 +404,7 @@ class KcefWebViewProvider(
|
|||||||
redirectUrl: StringRef,
|
redirectUrl: StringRef,
|
||||||
) {
|
) {
|
||||||
super.getResponseHeaders(response, responseLength, redirectUrl)
|
super.getResponseHeaders(response, responseLength, redirectUrl)
|
||||||
webResponse.responseHeaders.forEach { response.setHeaderByName(it.key, it.value, true) }
|
webResponse.responseHeaders?.forEach { response.setHeaderByName(it.key, it.value, true) }
|
||||||
response.status = webResponse.statusCode
|
response.status = webResponse.statusCode
|
||||||
response.mimeType = webResponse.mimeType
|
response.mimeType = webResponse.mimeType
|
||||||
}
|
}
|
||||||
@@ -424,15 +453,22 @@ class KcefWebViewProvider(
|
|||||||
frame: CefFrame,
|
frame: CefFrame,
|
||||||
request: CefRequest,
|
request: CefRequest,
|
||||||
): CefResourceHandler? {
|
): CefResourceHandler? {
|
||||||
// TODO: we should be calling this on the handler, since CEF calls us on its IO thread
|
val isInitialLoad = frame.url == "" && request.method == "GET"
|
||||||
|
Log.v(TAG, "Request ${request.method} ${request.url} is initial? $isInitialLoad")
|
||||||
|
// NOTE: we should be calling this on the handler, since CEF calls us on its IO thread
|
||||||
|
// but docs say "This method is called on a thread other than the UI thread" so should be fine
|
||||||
val response =
|
val response =
|
||||||
viewClient.shouldInterceptRequest(
|
if (isInitialLoad) {
|
||||||
view,
|
null
|
||||||
CefWebResourceRequest(request, frame, false),
|
} else {
|
||||||
)
|
viewClient.shouldInterceptRequest(
|
||||||
|
view,
|
||||||
|
CefWebResourceRequest(request, frame, false),
|
||||||
|
)
|
||||||
|
}
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
// prefer user's response override
|
// prefer user's response override
|
||||||
urlHttpMapping.get(request.url)?.let {
|
urlHttpMapping.get(request.url.trimEnd('/'))?.let {
|
||||||
return HtmlResponseResourceHandler(it)
|
return HtmlResponseResourceHandler(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,6 +505,22 @@ class KcefWebViewProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class PermissionHandler : CefPermissionHandler {
|
||||||
|
override fun onRequestMediaAccessPermission(
|
||||||
|
browser: CefBrowser,
|
||||||
|
frame: CefFrame,
|
||||||
|
requesting_url: String,
|
||||||
|
requested_permissions: Int,
|
||||||
|
callback: CefMediaAccessCallback,
|
||||||
|
): Boolean {
|
||||||
|
handler.post {
|
||||||
|
Log.v(TAG, "Checking permission for $requesting_url: $requested_permissions")
|
||||||
|
chromeClient.onPermissionRequest(CefPermissionRequest(requesting_url, requested_permissions, callback))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun init(
|
override fun init(
|
||||||
javaScriptInterfaces: Map<String, Any>?,
|
javaScriptInterfaces: Map<String, Any>?,
|
||||||
privateBrowsing: Boolean,
|
privateBrowsing: Boolean,
|
||||||
@@ -480,6 +532,7 @@ class KcefWebViewProvider(
|
|||||||
addDisplayHandler(DisplayHandler())
|
addDisplayHandler(DisplayHandler())
|
||||||
addLoadHandler(LoadHandler())
|
addLoadHandler(LoadHandler())
|
||||||
addRequestHandler(RequestHandler())
|
addRequestHandler(RequestHandler())
|
||||||
|
addPermissionHandler(PermissionHandler())
|
||||||
|
|
||||||
val config = CefMessageRouter.CefMessageRouterConfig()
|
val config = CefMessageRouter.CefMessageRouterConfig()
|
||||||
config.jsQueryFunction = QUERY_FN
|
config.jsQueryFunction = QUERY_FN
|
||||||
@@ -595,8 +648,8 @@ class KcefWebViewProvider(
|
|||||||
|
|
||||||
override fun loadData(
|
override fun loadData(
|
||||||
data: String,
|
data: String,
|
||||||
mimeType: String,
|
mimeType: String?,
|
||||||
encoding: String,
|
encoding: String?,
|
||||||
) {
|
) {
|
||||||
loadDataWithBaseURL(null, data, mimeType, encoding, null)
|
loadDataWithBaseURL(null, data, mimeType, encoding, null)
|
||||||
}
|
}
|
||||||
@@ -604,8 +657,8 @@ class KcefWebViewProvider(
|
|||||||
override fun loadDataWithBaseURL(
|
override fun loadDataWithBaseURL(
|
||||||
baseUrl: String?,
|
baseUrl: String?,
|
||||||
data: String,
|
data: String,
|
||||||
mimeType: String,
|
mimeType: String?,
|
||||||
encoding: String,
|
encoding: String?,
|
||||||
historyUrl: String?,
|
historyUrl: String?,
|
||||||
) {
|
) {
|
||||||
browser?.close(true)
|
browser?.close(true)
|
||||||
@@ -615,7 +668,7 @@ class KcefWebViewProvider(
|
|||||||
browser =
|
browser =
|
||||||
(
|
(
|
||||||
baseUrl?.let { url ->
|
baseUrl?.let { url ->
|
||||||
urlHttpMapping.put(url, data)
|
urlHttpMapping.put(url.trimEnd('/'), data)
|
||||||
kcefClient!!.createBrowser(
|
kcefClient!!.createBrowser(
|
||||||
url,
|
url,
|
||||||
CefRendering.OFFSCREEN,
|
CefRendering.OFFSCREEN,
|
||||||
@@ -637,13 +690,13 @@ class KcefWebViewProvider(
|
|||||||
|
|
||||||
override fun evaluateJavaScript(
|
override fun evaluateJavaScript(
|
||||||
script: String,
|
script: String,
|
||||||
resultCallback: ValueCallback<String>,
|
resultCallback: ValueCallback<String>?,
|
||||||
) {
|
) {
|
||||||
browser!!.evaluateJavaScript(
|
browser!!.evaluateJavaScript(
|
||||||
script.removePrefix("javascript:"),
|
script.removePrefix("javascript:"),
|
||||||
{
|
{
|
||||||
Log.v(TAG, "JS returned: $it")
|
Log.v(TAG, "JS returned: $it")
|
||||||
it?.let { handler.post { resultCallback.onReceiveValue(it) } }
|
it?.let { handler.post { resultCallback?.onReceiveValue(it) } }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
+301
-1756
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -61,7 +61,7 @@ This structure is chosen to
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
You need these software packages installed in order to build the project
|
You need these software packages installed in order to build the project
|
||||||
|
|
||||||
- Java Development Kit version 21 or newer(we suggest using Temurin instead of Oracle JDK)
|
- Java Development Kit version 21 or newer(we suggest using Zulu or Temurin instead of Oracle JDK)
|
||||||
|
|
||||||
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle)
|
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle)
|
||||||
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
|
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
FROM eclipse-temurin:25.0.3_9-jdk-noble AS build
|
||||||
|
|
||||||
|
ARG TACHIDESK_ABORT_HANDLER_DOWNLOAD_URL
|
||||||
|
|
||||||
|
# build abort handler
|
||||||
|
RUN if [ -n "$TACHIDESK_ABORT_HANDLER_DOWNLOAD_URL" ]; then \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install -y curl gcc && \
|
||||||
|
cd /tmp && \
|
||||||
|
curl "$TACHIDESK_ABORT_HANDLER_DOWNLOAD_URL" -O && \
|
||||||
|
gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared catch_abort.c -lpthread -o /opt/catch_abort.so && \
|
||||||
|
rm -f catch_abort.c && \
|
||||||
|
apt-get -y purge gcc --auto-remove && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* || exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the server jar from source
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN GRADLE_OPTS="-Xmx4g" ./gradlew :server:shadowJar --no-daemon -x test
|
||||||
|
|
||||||
|
FROM eclipse-temurin:25.0.3_9-jre-noble
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG TACHIDESK_KCEF=n # y or n, leave empty for auto-detection
|
||||||
|
ARG TACHIDESK_KCEF_RELEASE_URL
|
||||||
|
|
||||||
|
# Install envsubst from GNU's gettext project
|
||||||
|
# install unzip to unzip the server-reference.conf from the jar
|
||||||
|
# Install tini for a tiny init system (handles orphan processes for graceful restart)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get -y install -y curl gettext-base unzip tini ca-certificates p11-kit && \
|
||||||
|
/usr/bin/p11-kit extract --format=java-cacerts --filter=certificates --overwrite --purpose server-auth $JAVA_HOME/lib/security/cacerts && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY scripts/kcef_download.sh /root/kcef_download.sh
|
||||||
|
RUN chmod +x /root/kcef_download.sh
|
||||||
|
|
||||||
|
# install CEF dependencies
|
||||||
|
RUN if [ "$TACHIDESK_KCEF" = "y" ] || ([ "$TACHIDESK_KCEF" = "" ] && ([ "$TARGETPLATFORM" = "linux/amd64" ] || [ "$TARGETPLATFORM" = "linux/arm64" ])); then \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install --no-install-recommends -y libxss1 libxext6 libxrender1 libxcomposite1 libxdamage1 libxkbcommon0 libxtst6 \
|
||||||
|
libjogl2-jni libgluegen2-jni libglib2.0-0t64 libnss3 libdbus-1-3 libpango-1.0-0 libcairo2 libasound2t64 \
|
||||||
|
libatk-bridge2.0-0t64 libcups2t64 libdrm2 libgbm1 xvfb \
|
||||||
|
curl jq gawk findutils && \
|
||||||
|
/root/kcef_download.sh "$TACHIDESK_KCEF_RELEASE_URL" "$TARGETPLATFORM" && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* || exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY --from=build /opt/*.so /opt/
|
||||||
|
|
||||||
|
# Create a user to run as
|
||||||
|
# .X11-unix must be created by root
|
||||||
|
# Ubuntu exposes libgluegen_rt.so as libgluegen2_rt.so for some reason, so rename it
|
||||||
|
# JCEF (or Java?) also does not search /usr/lib/jni, so copy them over into one it will search
|
||||||
|
RUN userdel -r ubuntu && \
|
||||||
|
groupadd --gid 1000 suwayomi && \
|
||||||
|
useradd --uid 1000 --gid suwayomi --no-log-init -G audio,video suwayomi && \
|
||||||
|
mkdir -p /home/suwayomi/.local/share/Tachidesk && \
|
||||||
|
if command -v Xvfb; then \
|
||||||
|
mkdir /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix && \
|
||||||
|
cp /usr/lib/jni/libgluegen2_rt.so /home/suwayomi/libgluegen_rt.so && \
|
||||||
|
cp /usr/lib/jni/*.so /home/suwayomi/; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY scripts/create_server_conf.sh /home/suwayomi/create_server_conf.sh
|
||||||
|
COPY scripts/startup_script.sh /home/suwayomi/startup_script.sh
|
||||||
|
RUN chmod +x /home/suwayomi/create_server_conf.sh /home/suwayomi/startup_script.sh
|
||||||
|
|
||||||
|
# Copy locally built jar; grant o+rwx so non-default UIDs can write server.conf
|
||||||
|
RUN mkdir -p /home/suwayomi/startup
|
||||||
|
COPY --from=build /app/server/build/*.jar /home/suwayomi/startup/tachidesk_latest.jar
|
||||||
|
RUN chmod 777 -R /home/suwayomi && \
|
||||||
|
chown -R suwayomi:suwayomi /home/suwayomi
|
||||||
|
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG TACHIDESK_RELEASE_TAG
|
||||||
|
ARG TACHIDESK_FILENAME
|
||||||
|
ARG TACHIDESK_DOCKER_GIT_COMMIT
|
||||||
|
LABEL maintainer="suwayomi" \
|
||||||
|
org.opencontainers.image.title="Suwayomi Docker" \
|
||||||
|
org.opencontainers.image.authors="https://github.com/suwayomi" \
|
||||||
|
org.opencontainers.image.url="https://github.com/suwayomi/docker-tachidesk/pkgs/container/tachidesk" \
|
||||||
|
org.opencontainers.image.source="https://github.com/suwayomi/docker-tachidesk" \
|
||||||
|
org.opencontainers.image.description="This image is used to start suwayomi server in a container" \
|
||||||
|
org.opencontainers.image.vendor="suwayomi" \
|
||||||
|
org.opencontainers.image.created=$BUILD_DATE \
|
||||||
|
org.opencontainers.image.version=$TACHIDESK_RELEASE_TAG \
|
||||||
|
tachidesk.docker_commit=$TACHIDESK_DOCKER_GIT_COMMIT \
|
||||||
|
tachidesk.release_tag=$TACHIDESK_RELEASE_TAG \
|
||||||
|
tachidesk.filename=$TACHIDESK_FILENAME \
|
||||||
|
org.opencontainers.image.licenses="MPL-2.0"
|
||||||
|
|
||||||
|
ENV HOME=/home/suwayomi
|
||||||
|
WORKDIR /home/suwayomi
|
||||||
|
USER suwayomi
|
||||||
|
EXPOSE 4567
|
||||||
|
|
||||||
|
ENTRYPOINT ["tini", "--"]
|
||||||
|
CMD ["/home/suwayomi/startup_script.sh"]
|
||||||
|
|
||||||
|
# vim: set ft=dockerfile:
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||||
|  | [](https://github.com/Suwayomi/Suwayomi-Server/releases) | [](https://github.com/Suwayomi/Suwayomi-Server-preview/releases/latest) | [](https://discord.gg/DDZdqZWaHA) |
|
|  | [](https://github.com/Suwayomi/Suwayomi-Server/releases) | [](https://github.com/Suwayomi/Suwayomi-Server-preview/releases/latest) | [](https://discord.gg/DDZdqZWaHA) |
|
||||||
|
|
||||||
## Table of Content
|
## Table of Contents
|
||||||
- [What is Suwayomi?](#what-is-suwayomi)
|
- [What is Suwayomi?](#what-is-suwayomi)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Suwayomi client projects](#suwayomi-client-projects)
|
- [Suwayomi client projects](#suwayomi-client-projects)
|
||||||
- [Actively Developed Clients](#actively-developed-clients)
|
- [Integrated clients](#integrated-clients)
|
||||||
- [Inactive Clients (functional but outdated)](#inactive-clients-functional-but-outdated)
|
- [Other clients](#other-clients-potentially-inactive-or-abondend)
|
||||||
- [Abandoned Clients (functionality unknown)](#abandoned-clients-functionality-unknown)
|
|
||||||
- [Downloading and Running the app](#downloading-and-running-the-app)
|
- [Downloading and Running the app](#downloading-and-running-the-app)
|
||||||
- [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
- [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||||
- [Windows](#windows)
|
- [Windows](#windows)
|
||||||
@@ -44,7 +43,7 @@ Suwayomi is an independent Mihon (Tachiyomi) compatible software and is **not a
|
|||||||
|
|
||||||
Suwayomi-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
|
Suwayomi-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
|
||||||
|
|
||||||
You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look [here](#syncing-with-mihon-tachiyomi).
|
You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look [here](#syncing-with-mihon-tachiyomi-and-neko).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
@@ -65,21 +64,24 @@ You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look
|
|||||||
- Automated WebUI updates (supports the default WebUI and VUI)
|
- Automated WebUI updates (supports the default WebUI and VUI)
|
||||||
- OPDS and OPDS-PSE support (endpoint: `/api/opds/v1.2`)
|
- OPDS and OPDS-PSE support (endpoint: `/api/opds/v1.2`)
|
||||||
|
|
||||||
# Suwayomi client projects
|
# Suwayomi Client Projects
|
||||||
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
|
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
|
||||||
|
|
||||||
Here's a list of known clients/user interfaces for Suwayomi-Server (checkout the respective GitHub repository for their features):
|
Here's a list of known clients/user interfaces for Suwayomi-Server (checkout the respective GitHub repository for their features):
|
||||||
##### Actively Developed Clients
|
|
||||||
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web front-end that Suwayomi-Server ships with by default.
|
##### Integrated clients
|
||||||
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A Suwayomi-Server preview focused web frontend built with svelte
|
|
||||||
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin.
|
These clients are built-in options, and the server can keep them automatically up-to-date.
|
||||||
##### Inactive Clients (functional but outdated)
|
|
||||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-Server.
|
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA
|
||||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android with a User Interface inspired by Mihon (Tachiyomi).
|
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA
|
||||||
##### Abandoned Clients (functionality unknown)
|
|
||||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
|
##### Other clients (potentially inactive or abandoned)
|
||||||
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
|
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance
|
||||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
|
- [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), can manage its own suwayomi server instance
|
||||||
|
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance
|
||||||
|
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server
|
||||||
|
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server
|
||||||
|
|
||||||
# Downloading and Running the app
|
# Downloading and Running the app
|
||||||
## Using Operating System Specific Bundles
|
## Using Operating System Specific Bundles
|
||||||
@@ -128,7 +130,7 @@ Check our Official Docker release [Suwayomi Container](https://github.com/orgs/S
|
|||||||
### Arch Linux
|
### Arch Linux
|
||||||
You can install Suwayomi from the AUR:
|
You can install Suwayomi from the AUR:
|
||||||
```
|
```
|
||||||
yay -S tachidesk
|
yay -S suwayomi-server-bin
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debian/Ubuntu
|
### Debian/Ubuntu
|
||||||
|
|||||||
+5
-4
@@ -31,8 +31,9 @@ allprojects {
|
|||||||
subprojects {
|
subprojects {
|
||||||
plugins.withType<JavaPlugin> {
|
plugins.withType<JavaPlugin> {
|
||||||
extensions.configure<JavaPluginExtension> {
|
extensions.configure<JavaPluginExtension> {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
val javaVersion = JavaVersion.toVersion(libs.versions.jvmTarget.get())
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = javaVersion
|
||||||
|
targetCompatibility = javaVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +52,8 @@ subprojects {
|
|||||||
dependsOn("ktlintFormat")
|
dependsOn("ktlintFormat")
|
||||||
}
|
}
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget = JvmTarget.JVM_21
|
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||||
freeCompilerArgs.add("-Xcontext-receivers")
|
freeCompilerArgs.add("-Xcontext-parameters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
`kotlin-dsl`
|
`kotlin-dsl`
|
||||||
}
|
}
|
||||||
@@ -9,3 +11,13 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.zip4j)
|
implementation(libs.zip4j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
val javaVersion = JavaVersion.toVersion(libs.versions.jvmTarget.get())
|
||||||
|
sourceCompatibility = javaVersion
|
||||||
|
targetCompatibility = javaVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||||
|
compilerOptions.jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
|||||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||||
|
|
||||||
// should be bumped with each stable release
|
// should be bumped with each stable release
|
||||||
val getTachideskVersion = { "v2.1.${getCommitCount()}" }
|
val getTachideskVersion = { "v2.2.${getCommitCount()}" }
|
||||||
|
|
||||||
val webUIRevisionTag = "r2643"
|
val webUIRevisionTag = "r3136"
|
||||||
|
|
||||||
private val getCommitCount = {
|
private val getCommitCount = {
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
services:
|
||||||
|
suwayomi:
|
||||||
|
build: .
|
||||||
|
platform: linux/amd64
|
||||||
|
image: registry.achmad.dev/suwayomi-server:latest
|
||||||
|
# user: 1000:1000
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ:-Etc/UTC} # Add a TZ variable to .env to change it
|
||||||
|
- DATABASE_TYPE=POSTGRESQL
|
||||||
|
- DATABASE_URL=postgresql://postgresql:5432/${POSTGRES_DB}
|
||||||
|
- DATABASE_USERNAME=${POSTGRES_USER}
|
||||||
|
- DATABASE_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
# - USE_HIKARI_CONNECTION_POOL=false # Hikari Connection Pool can cause issues with some installations, but it is much more performant.
|
||||||
|
# Comment these out if you do not use the flaresolverr container at the bottom of this file
|
||||||
|
- FLARESOLVERR_ENABLED=true
|
||||||
|
- FLARESOLVERR_URL=http://flaresolverr:8191
|
||||||
|
# #################################################################################################
|
||||||
|
#
|
||||||
|
# !!! IMPORTANT !!!
|
||||||
|
# - server settings can be changed during runtime in the WebUI
|
||||||
|
# - providing an environment variable will OVERWRITE the current setting value when starting the container
|
||||||
|
#
|
||||||
|
# #################################################################################################
|
||||||
|
#
|
||||||
|
# example for setting env vars:
|
||||||
|
#
|
||||||
|
# - BIND_IP=0.0.0.0
|
||||||
|
# - BIND_PORT=4567
|
||||||
|
# - SOCKS_PROXY_ENABLED=false
|
||||||
|
# - DOWNLOAD_AS_CBZ=true
|
||||||
|
# - AUTH_MODE=basic_auth
|
||||||
|
# - AUTH_USERNAME=manga
|
||||||
|
# - AUTH_PASSWORD=hello123
|
||||||
|
# - EXTENSION_REPOS=["http://github.com/orginazation-name/repo-name", "http://github.com/orginazation-name-2/repo-name-2"]
|
||||||
|
depends_on:
|
||||||
|
postgresql:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
|
volumes:
|
||||||
|
- ./data:/home/suwayomi/.local/share/Tachidesk
|
||||||
|
ports:
|
||||||
|
- "4567:4567"
|
||||||
|
restart: on-failure:3
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: postgres:18.3
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
- PGDATA=/data/postgres
|
||||||
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
|
- TZ=${TZ:-Etc/UTC}
|
||||||
|
- PGTZ=${TZ:-Etc/UTC}
|
||||||
|
volumes:
|
||||||
|
- ./postgres:/data/postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB} -U ${POSTGRES_USER}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
flaresolverr:
|
||||||
|
image: ghcr.io/thephaseless/byparr:latest
|
||||||
|
container_name: flaresolverr
|
||||||
|
init: true
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ:-Etc/UTC}
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
services:
|
||||||
|
suwayomi:
|
||||||
|
build: .
|
||||||
|
platform: linux/amd64
|
||||||
|
image: registry.achmad.dev/suwayomi-server:latest
|
||||||
|
# user: 1000:1000
|
||||||
|
environment:
|
||||||
|
- TZ=Etc/UTC # Use TZ database name from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
|
# Comment these out if you do not use the flaresolverr container at the bottom of this file
|
||||||
|
- FLARESOLVERR_ENABLED=true
|
||||||
|
- FLARESOLVERR_URL=http://flaresolverr:8191
|
||||||
|
# #################################################################################################
|
||||||
|
#
|
||||||
|
# !!! IMPORTANT !!!
|
||||||
|
# - server settings can be changed during runtime in the WebUI
|
||||||
|
# - providing an environment variable will OVERWRITE the current setting value when starting the container
|
||||||
|
#
|
||||||
|
# #################################################################################################
|
||||||
|
#
|
||||||
|
# example for setting env vars:
|
||||||
|
#
|
||||||
|
# - BIND_IP=0.0.0.0
|
||||||
|
# - BIND_PORT=4567
|
||||||
|
# - SOCKS_PROXY_ENABLED=false
|
||||||
|
# - DOWNLOAD_AS_CBZ=true
|
||||||
|
# - AUTH_MODE=basic_auth
|
||||||
|
# - AUTH_USERNAME=manga
|
||||||
|
# - AUTH_PASSWORD=hello123
|
||||||
|
# - EXTENSION_REPOS=["http://github.com/orginazation-name/repo-name", "http://github.com/orginazation-name-2/repo-name-2"]
|
||||||
|
volumes:
|
||||||
|
- ./data:/home/suwayomi/.local/share/Tachidesk
|
||||||
|
ports:
|
||||||
|
- "4567:4567"
|
||||||
|
restart: on-failure:3
|
||||||
|
|
||||||
|
flaresolverr:
|
||||||
|
image: ghcr.io/thephaseless/byparr:latest
|
||||||
|
container_name: flaresolverr
|
||||||
|
init: true
|
||||||
|
environment:
|
||||||
|
- TZ=Etc/UTC # Use TZ database name from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
Suwayomi-Server configuration file is named `server.conf` and is located inside [the data directory](https://github.com/Suwayomi/Suwayomi-Server/wiki/The-Data-Directory).
|
||||||
|
|
||||||
|
The configuration file is written in HOCON. Google is your friend if you want to know more.
|
||||||
|
|
||||||
|
**Note:** new keys might be added in the future. Suwayomi generally attempts to update your conf file automatically, but some keys may need to be updated manually.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
### I messed up my configuration file
|
||||||
|
Suwayomi will create a default configuration file when one doesn't exist, you can delete `server.conf` to get a copy of the reference configuration file after a restart.
|
||||||
|
|
||||||
|
### I am running Suwayomi in a headless environment (docker, NAS, VPS, etc.)
|
||||||
|
- Set `server.systemTrayEnabled` to false, it will prevent Suwayomi to attempt to create a System Tray icon.
|
||||||
|
- Set `server.initialOpenInBrowserEnabled`to false, it will prevent Suwayomi to attempt to open a browser on startup.
|
||||||
|
|
||||||
|
### My Suwayomi data directory/downloads size is getting to big
|
||||||
|
- Set `server.downloadsPath` to the desired path, if you only need to change where downloads are stored. You have to move/remove the existing downloads manually.
|
||||||
|
- Set the special `server.rootDir` key if you need Suwayomi to use a custom data directory path, refer to [this section](#overriding-tachidesk-servers-data-directory-path).
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
### Server ip and port bindings
|
||||||
|
```conf
|
||||||
|
server.ip = "0.0.0.0"
|
||||||
|
server.port = 4567
|
||||||
|
```
|
||||||
|
- `server.ip` can be an IP or domain name.
|
||||||
|
|
||||||
|
### Socks5 proxy
|
||||||
|
```
|
||||||
|
server.socksProxyEnabled = false
|
||||||
|
server.socksProxyVersion = 5 # 4 or 5
|
||||||
|
server.socksProxyHost = ""
|
||||||
|
server.socksProxyPort = ""
|
||||||
|
server.socksProxyUsername = ""
|
||||||
|
server.socksProxyPassword = ""
|
||||||
|
```
|
||||||
|
This section directs Suwayomi to connect to the network through a proxy server.
|
||||||
|
|
||||||
|
An example configuration can be:
|
||||||
|
```
|
||||||
|
server.socksProxyEnabled = true
|
||||||
|
server.socksProxyHost = "yourproxyhost.com"
|
||||||
|
server.socksProxyPort = "8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
### webUI
|
||||||
|
```
|
||||||
|
server.webUIEnabled = true
|
||||||
|
server.initialOpenInBrowserEnabled = true
|
||||||
|
server.webUIInterface = "browser" # "browser" or "electron"
|
||||||
|
server.electronPath = ""
|
||||||
|
server.webUIFlavor = "WebUI" # "WebUI" or "Custom"
|
||||||
|
server.webUIChannel = preview # "BUNDLED" or "STABLE" or "PREVIEW"
|
||||||
|
server.webUIUpdateCheckInterval = 23
|
||||||
|
server.webUISubpath = ""
|
||||||
|
```
|
||||||
|
- `server.webUIEnabled` controls if Suwayomi will serve `Suwayomi-WebUI` and if it downloads/updates it on startup.
|
||||||
|
- `server.initialOpenInBrowserEnabled` controls if Suwayomi will attempt to open a brwoser/electron window on startup, disabling this on headless servers is recommended.
|
||||||
|
- `server.webUIInterface` which web interface Suwayomi should launch on startup, options are `"browser"` and `"electron"`
|
||||||
|
- `server.electronPath` path of the main electron executable, should be in double quotes
|
||||||
|
- `server.webUIFlavor` set `"WebUI"` to make the server download and update Suwayomi-WebUI automatically or `"Custom"` if you want the server to serve a custom web interface that you manage by yourself.
|
||||||
|
- Note: "Custom" would be useful if you want to test preview versions of Suwayomi-WebUI or when you are using or developing other web interfaces like the web version of Suwayomi-Sorayomi.
|
||||||
|
- `server.webUIChannel` allows to choose which update channel to use (only valid when flavor is set to "WebUI"). Use `"BUNDLED"` to use the version included in the server download, `"STABLE"` to use the latest stable release or `"PREVIEW"` to use the latest preview release (potentially buggy).
|
||||||
|
- `server.webUIUpdateCheckInterval` the interval time in hours at which to check for updates. Use `0` to disable update checking.
|
||||||
|
- `server.webUISubpath` controls on which sub-path the UI is served; by default, it will be accessible on `/` (i.e. directly), with this setting it can also be set to appear at e.g. `/suwayomi`
|
||||||
|
|
||||||
|
### Downloader
|
||||||
|
```
|
||||||
|
server.downloadAsCbz = true
|
||||||
|
server.downloadsPath = ""
|
||||||
|
server.autoDownloadNewChapters = false
|
||||||
|
server.excludeEntryWithUnreadChapters = true
|
||||||
|
server.autoDownloadNewChaptersLimit = 0
|
||||||
|
server.autoDownloadIgnoreReUploads = false
|
||||||
|
server.downloadConversions = {}
|
||||||
|
```
|
||||||
|
- `server.downloadAsCbz = true` configures Suwayomi to automatically compress chapters into CBZ.
|
||||||
|
- `server.downloadsPath = ""` the path where manga downloads will be stored, if the value is empty, the default directory `downloads` inside [the data directory](https://github.com/Suwayomi/Suwayomi-Server/wiki/The-Data-Directory) will be used. If you are on Windows the slashes `\` needs to be doubled(`\\`) or replaced with `/`
|
||||||
|
- `server.autoDownloadNewChapters = false` controls if Suwayomi should automatically download new chapters after a library update.
|
||||||
|
- `server.excludeEntryWithUnreadChapters = true` controls if Suwayomi will download new chapters for titles with unread chapters (requires `server.autoDownloadNewChapters`).
|
||||||
|
- `server.autoDownloadNewChaptersLimit = 0` sets how many chapters should be downloaded at most, `0` to disable the limit; if the limit is reached, new chapters will not be downloaded (requires `server.autoDownloadNewChapters`).
|
||||||
|
- `server.autoDownloadIgnoreReUploads = false` controls if Suwayomi will re-download re-uploads on update (requires `server.autoDownloadNewChapters`).
|
||||||
|
- `server.downloadConversions = {}` configures optional image conversions for all downloads. This is an [JSON object](https://en.wikipedia.org/wiki/JSON#Syntax), with the source image [mime type](https://en.wikipedia.org/wiki/Media_type) as the key and an object with the target mime type or url and options as value.
|
||||||
|
The following options are all valid:
|
||||||
|
```
|
||||||
|
server.downloadConversions = { "image/webp" : { target : "image/jpeg", compressionLevel = 0.8 }}
|
||||||
|
# -- or --
|
||||||
|
server.downloadConversions."image/webp" = {
|
||||||
|
target = "image/jpeg" # image type to convert to
|
||||||
|
compressionLevel = 0.8 # quality in range [0,1], leave away to use default compression
|
||||||
|
}
|
||||||
|
# -- a url example --
|
||||||
|
server.downloadConversions = { "default" : { target : "http://localhost:9999/convert" }}
|
||||||
|
# -- a url with all parameters example --
|
||||||
|
server.downloadConversions = {
|
||||||
|
"default" : {
|
||||||
|
target : "http://localhost:9999/convert",
|
||||||
|
callTimeout : 10m,
|
||||||
|
connectTimeout : 10s,
|
||||||
|
headers : {
|
||||||
|
"authorization" : "MyPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
A source mime type `default` can be used as fallback to convert all images; a target mime type of `none` can be used to disable conversion for a particular format.
|
||||||
|
|
||||||
|
This is an example curl command for what Suwayomi-Server will send to the conversion url: `curl -X POST "http://localhost:9999/convert" -F "image=@cat.png;type=image/png"`
|
||||||
|
- `server.serveConversions = {}` configures optional image conversions before serving the image to the client. It follows the same format as `server.downloadConversions`.
|
||||||
|
|
||||||
|
|
||||||
|
### Updater
|
||||||
|
```
|
||||||
|
server.excludeUnreadChapters = true
|
||||||
|
server.excludeNotStarted = true
|
||||||
|
server.excludeCompleted = true
|
||||||
|
server.globalUpdateInterval = 12
|
||||||
|
server.updateMangas = false
|
||||||
|
```
|
||||||
|
- `server.excludeUnreadChapters = true` controls if Suwayomi should include titles with unread chapters in the library update.
|
||||||
|
- `server.excludeNotStarted = true` controls if Suwayomi should include titles which weren't started yet in the library update.
|
||||||
|
- `server.excludeCompleted = true` controls if Suwayomi should include titles which are marked completed in the library update.
|
||||||
|
- `server.globalUpdateInterval = 12` sets the time in hours for the automatic library internal, `0` to disable it. Range: 6 <= n < ∞
|
||||||
|
- `server.updateMangas = false` controls if Suwayomi should also update title metadata along with fetching new chapters in the library update.
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
```
|
||||||
|
server.authMode = "none" # none, basic_auth, simple_login or ui_login
|
||||||
|
server.authUsername = "user"
|
||||||
|
server.authPassword = "pass"
|
||||||
|
server.jwtAudience = "suwayomi-server-api"
|
||||||
|
server.jwtTokenExpiry = "5m"
|
||||||
|
server.jwtRefreshExpiry = "60d"
|
||||||
|
```
|
||||||
|
- `server.authMode = "none"`: Since v2.1.1867, Suwayomi supports two modes of authentication. Enabling authentication is useful when hosting on a public network/the Internet. If you used the original `server.basicAuth*` variables, it will be automatically migrated.
|
||||||
|
- `basic_auth` configures Suwayomi for [Basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
|
||||||
|
- `simple_login` works similarly to Basic Authentication, but presents a custom login page. The login is stored via a cookie and needs to be refreshed on every server restart or every 30 minutes.
|
||||||
|
- Starting with v2.1.1894, another mode is available:
|
||||||
|
`ui_login` is a new [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token)-based authentication scheme, which tightly integrates with the chosen UI. Instead of restricting access completely, this allows the user to still open the UI (e.g. WebUI). Unlike the other two modes, this means the login page is entirely customizable by the UI. The `jwt*` settings can be used to tune session duration in this mode.
|
||||||
|
- `server.authUsername` the username value that you have to provide when authenticating.
|
||||||
|
- `server.authPassword` the password value that you have to provide when authenticating.
|
||||||
|
- `server.jwtAudience` is any string to be embedded into the JWT token. See `aud` field in [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields).
|
||||||
|
- `server.jwtTokenExpiry` is the time any token is valid ("access token"); after expiry, the refresh token will be used to generate a new access token again without prompting for credentials.
|
||||||
|
- `server.jwtRefreshExpiry` is the time the refresh token is valid. After it has expired, the user will be prompted to login again; before that, the session can be quietly refreshed using this token. Note the security implication in this: While the access token is valid, the server fully trusts the holder of that token. After the access token has expired, the refresh token can be used to obtain a new access token; only at this time can the session be terminated by the server.
|
||||||
|
|
||||||
|
**Note**: Basic access authentication sends username and password in cleartext and is not completely secure over HTTP, it's recommended to pair this feature with a reverse proxy server like nginx and expose the server over HTTPS. Similarly, with `simple_login` and `ui_login`, the credentials are sent in cleartext on login, and the cookie/token is sent with every request. Also your browser caches the credentials/cookie, so you should be careful when accessing the server from non-private devices and use incognito mode.
|
||||||
|
|
||||||
|
### misc
|
||||||
|
```
|
||||||
|
server.debugLogsEnabled = false
|
||||||
|
server.systemTrayEnabled = true
|
||||||
|
server.maxLogFiles = 31
|
||||||
|
server.maxLogFileSize = "10mb"
|
||||||
|
server.maxLogFolderSize = "100mb"
|
||||||
|
server.extensionRepos = []
|
||||||
|
server.maxSourcesInParallel = 6
|
||||||
|
```
|
||||||
|
- `server.debugLogsEnabled` controls whether if Suwayomi-Server should print more information while being run inside a Terminal/CMD/Powershell window.
|
||||||
|
- `server.systemTrayEnabled = true` whether if Suwayomi-Server should show a System Tray Icon, disabling this on headless servers is recommended.
|
||||||
|
- `server.maxLogFiles = 31` sets the maximum number of days to keep files before they get deleted.
|
||||||
|
- `server.maxLogFileSize = "10mb"` sets the maximum size of a log file - values are formatted like: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)
|
||||||
|
- `server.maxLogFolderSize = "100mb"` sets the maximum size of all saved log files - values are formatted like: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)
|
||||||
|
- `server.extensionRepos` is a list of extension repositories for custom sources. Uses the same format as Mihon; each entry is expected to be a string URL pointing to a JSON file representing the repository.
|
||||||
|
- `server.maxSourcesInParallel = 6` sets how many sources can do requests (updates, downloads) in parallel. Updates/downloads are grouped by source and all mangas of a source are updated/downloaded synchronously. Range: 1 <= n <= 20.
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
```
|
||||||
|
server.backupPath = ""
|
||||||
|
server.backupTime = "00:00"
|
||||||
|
server.backupInterval = 1
|
||||||
|
server.backupTTL = 14
|
||||||
|
server.autoBackupIncludeManga = true
|
||||||
|
server.autoBackupIncludeCategories = true
|
||||||
|
server.autoBackupIncludeChapters = true
|
||||||
|
server.autoBackupIncludeTracking = true
|
||||||
|
server.autoBackupIncludeHistory = true
|
||||||
|
server.autoBackupIncludeClientData = true
|
||||||
|
server.autoBackupIncludeServerSettings = true
|
||||||
|
```
|
||||||
|
- `server.backupPath = ""` the path where backups will be stored, if the value is empty, the default directory `backups` inside [the data directory](https://github.com/Suwayomi/Suwayomi-Server/wiki/The-Data-Directory) will be used. If you are on Windows the slashes `\` needs to be doubled(`\\`) or replaced with `/`
|
||||||
|
- `server.backupTime = "00:00"` sets the time of day at which the automated backup should be triggered.
|
||||||
|
- `server.backupInterval = 1` sets the interval in which the server will automatically create a backup in days, `0` to disable it.
|
||||||
|
- `server.backupTTL = 14` sets how long backup files will be kept before they will get deleted in days, `0` to disable it.
|
||||||
|
- `server.autoBackupIncludeManga` whether to include manga data in automatic backups
|
||||||
|
- `server.autoBackupIncludeCategories` whether to include category data in automatic backups
|
||||||
|
- `server.autoBackupIncludeChapters` whether to include manga chapter data in automatic backups
|
||||||
|
- `server.autoBackupIncludeTracking` whether to include manga tracking data in automatic backups
|
||||||
|
- `server.autoBackupIncludeHistory` whether to include manga reading history in automatic backups
|
||||||
|
- `server.autoBackupIncludeClientData` whether to include client data in automatic backups
|
||||||
|
- `server.autoBackupIncludeServerSettings` whether to include server settings in automatic backups
|
||||||
|
|
||||||
|
### Local Source
|
||||||
|
```
|
||||||
|
server.localSourcePath = ""
|
||||||
|
```
|
||||||
|
- `server.localSourcePath = ""` the path from where local manga are loaded, if the value is empty, the default directory `local` inside [the data directory](https://github.com/Suwayomi/Suwayomi-Server/wiki/The-Data-Directory) will be used. If you are on Windows the slashes `\` needs to be doubled(`\\`) or replaced with `/`
|
||||||
|
|
||||||
|
### Cloudflare bypass
|
||||||
|
```
|
||||||
|
server.flareSolverrEnabled = false
|
||||||
|
server.flareSolverrUrl = "http://localhost:8191"
|
||||||
|
server.flareSolverrTimeout = 60 # time in seconds
|
||||||
|
server.flareSolverrSessionName = "suwayomi"
|
||||||
|
server.flareSolverrSessionTtl = 15 # time in minutes
|
||||||
|
server.flareSolverrAsResponseFallback = false
|
||||||
|
```
|
||||||
|
- `server.flareSolverrEnabled = false` controls if Suwayomi attempts to connect to FlareSolverr if a CloudFlare challenge is detected.
|
||||||
|
- `server.flareSolverrUrl = "http://localhost:8191"` sets the address where Suwayomi attempts to connect to FlareSolverr. The instance needs to run and be accessible from the server where Suwayomi is running for CloudFlare bypass to work.
|
||||||
|
- `server.flareSolverrTimeout = 60` sets the timeout to wait for FlareSolverr to solve a given challenge in seconds.
|
||||||
|
- `server.flareSolverrSessionName = "suwayomi"` sets the session name that FlareSolverr should use. Tells FlareSolverr which set of cookies to use.
|
||||||
|
- `server.flareSolverrSessionTtl = 15` sets the time for how long sessions in FlareSolverr should live in minutes. After this time, FlareSolverr will forget cookies.
|
||||||
|
- `server.flareSolverrAsResponseFallback = false` allows Suwayomi to use the contents of the request that FlareSolverr received in case Suwayomi sees a CloudFlare challenge but FlareSolverr does not (which prevents it from solving the challenge).
|
||||||
|
|
||||||
|
**Note:** Byparr is a popular alternative to FlareSolverr. It uses the same API schema as FlareSolverr, so you can also configure the address of a Byparr instance in `server.flareSolverrUrl`.
|
||||||
|
|
||||||
|
**Note:** The example [docker-compose.yml file](https://github.com/Suwayomi/Suwayomi-Server-docker/blob/main/docker-compose.yml) contains everything you need to get started with Suwayomi+Byparr.
|
||||||
|
|
||||||
|
### OPDS
|
||||||
|
```
|
||||||
|
server.opdsUseBinaryFileSizes = false
|
||||||
|
server.opdsItemsPerPage = 50
|
||||||
|
server.opdsEnablePageReadProgress = true
|
||||||
|
server.opdsMarkAsReadOnDownload = false
|
||||||
|
server.opdsShowOnlyUnreadChapters = false
|
||||||
|
server.opdsShowOnlyDownloadedChapters = false
|
||||||
|
server.opdsChapterSortOrder = "DESC"
|
||||||
|
server.opdsCbzMimetype = "MODERN"
|
||||||
|
```
|
||||||
|
- `server.opdsUseBinaryFileSizes = false` controls if Suwayomi should display file sizes in binary units (KiB, MiB, GiB) or decimal (KB, MB, GB) in OPDS listings.
|
||||||
|
- `server.opdsItemsPerPage = 50` sets the number of items per page in OPDS listings. Range: 10 <= n <= 5000.
|
||||||
|
- `server.opdsEnablePageReadProgress = true` controls if Suwayomi should include information to track chapter read progress in OPDS chapter page listings. This will cause the reader to mark the pages as read when it downloads the images.
|
||||||
|
- `server.opdsMarkAsReadOnDownload = false` controls if Suwayomi should mark the chapters as read when it is downloaded through the OPDS listing.
|
||||||
|
- `server.opdsShowOnlyUnreadChapters = false` controls if OPDS listings should only include unread chapters.
|
||||||
|
- `server.opdsShowOnlyDownloadedChapters = false` controls if OPDS listings should only include downloaded chapters.
|
||||||
|
- `server.opdsChapterSortOrder = "DESC"` sets the default chapter sort order in OPDS listings, either `"ASC"` or `"DESC"`
|
||||||
|
- `server.opdsCbzMimetype = "MODERN"` controls which mimetype to use for CBZ downloads. This affects the offered link in OPDS, as well as the content type of the CBZ download. Allowed is MODERN (current IANA standard), LEGACY (deprecated mimetype for .cbz) and COMPATIBLE (deprecated mimetype for all comic archives). Use LEGACY or COMPATIBLE if older clients don't offer the chapter download (note that the chapter needs to first be downloaded in Suwayomi, before it is available in OPDS).
|
||||||
|
|
||||||
|
### KOReader Sync
|
||||||
|
```
|
||||||
|
server.koreaderSyncServerUrl = "https://sync.koreader.rocks/"
|
||||||
|
server.koreaderSyncUsername = ""
|
||||||
|
server.koreaderSyncUserkey = ""
|
||||||
|
server.koreaderSyncDeviceId = ""
|
||||||
|
server.koreaderSyncChecksumMethod = BINARY # BINARY or FILENAME
|
||||||
|
server.koreaderSyncPercentageTolerance = 1.0E-15 # range: [1.0E-15, 1.0]
|
||||||
|
server.koreaderSyncStrategyForward = PROMPT # PROMPT, KEEP_LOCAL, KEEP_REMOTE, DISABLED
|
||||||
|
server.koreaderSyncStrategyBackward = DISABLED # PROMPT, KEEP_LOCAL, KEEP_REMOTE, DISABLED
|
||||||
|
```
|
||||||
|
- `server.koreaderSyncServerUrl` where KOReader Sync Server is running. Public servers (e.g., `https://sync.koreader.rocks/`, `https://kosync.ak-team.com:3042/`) or self-hosted instances can also be used.
|
||||||
|
- `server.koreaderSyncUsername` the username with which to authenticate at the KOReader instance.
|
||||||
|
- `server.koreaderSyncUserkey` the password/key with which to authenticate at the KOReader instance.
|
||||||
|
- `server.koreaderSyncDeviceId` a unique ID to identify Suwayomi at the KOReader Sync Server. Leave blank to auto-generate.
|
||||||
|
- `server.koreaderSyncChecksumMethod` the method by which to identify chapters at the KOReader Sync Server. BINARY includes the entire contents of the chapter, and is thus more expensive, but may be convenient to catch updated chapters.
|
||||||
|
- `server.koreaderSyncPercentageTolerance` when syncing read progress for a chapter from other devices, how much difference (absolute) is allowed to be ignored. When above this tolerance, Suwayomi's read progress will be replaced by the remote device's according to the specified strategy. The strategy is chosed from `server.koreaderSyncStrategyForward` and `server.koreaderSyncStrategyBackward` based on the timestamps of the last read.
|
||||||
|
- `server.koreaderSyncStrategyForward` the strategy to apply when remote progress is newer than local.
|
||||||
|
- `server.koreaderSyncStrategyBackward` the strategy to apply when remote progress is older than local.
|
||||||
|
|
||||||
|
### Database
|
||||||
|
```
|
||||||
|
server.databaseType = H2 # H2, POSTGRESQL
|
||||||
|
server.databaseUrl = "postgresql://localhost:5432/suwayomi"
|
||||||
|
server.databaseUsername = ""
|
||||||
|
server.databasePassword = ""
|
||||||
|
server.useHikariConnectionPool = true
|
||||||
|
```
|
||||||
|
- `server.databaseType` chooses which type of database to use. [H2](https://en.wikipedia.org/wiki/H2_Database_Engine) is the default; it is a simple file-based database for Java applications. Since it is only based on files without a server process, file corruption can be common when the server is not shut down properly. [PostgreSQL](https://en.wikipedia.org/wiki/PostgreSQL) is a popular cross-platform, stable database. To use PostgreSQL, you need to run an instance yourself.
|
||||||
|
- `server.databaseUrl` the URL where to find the PostgreSQL server, including the database name.
|
||||||
|
- `server.databaseUsername` the username with which to authenticate at the PostgreSQL instance.
|
||||||
|
- `server.databasePassword` the username with which to authenticate at the PostgreSQL instance.
|
||||||
|
- `server.useHikariConnectionPool` use Hikari Connection Pool to connect to the database.
|
||||||
|
|
||||||
|
**Note:** The example [docker-compose.yml file](https://github.com/Suwayomi/Suwayomi-Server-docker/blob/main/docker-compose.yml) contains everything you need to get started with Suwayomi+PostgreSQL. Please be aware that PostgreSQL support is currently still in beta.
|
||||||
|
|
||||||
|
**Note:** These settings are excluded from backups, so a backup can be used to easily switch database installations by setting up the connection first, then restoring the backup.
|
||||||
|
|
||||||
|
## Overriding configuration options with command-line arguments
|
||||||
|
You can override the above configuration options with command-line arguments.
|
||||||
|
You usually only need to set this when using custom setups like a portable version of Suwayomi or your if your User dir cannot be written to or your system administrator doesn't allow it.
|
||||||
|
|
||||||
|
Use the pattern bellow.
|
||||||
|
```
|
||||||
|
java -Dsuwayomi.tachidesk.config.<configuration option 1>=<configuration value 1> -Dsuwayomi.tachidesk.config.<configuration option 2>=<configuration value 2> ... -Dsuwayomi.tachidesk.config.<configuration option N>=<configuration value N> -jar <path to server jar>
|
||||||
|
```
|
||||||
|
for example to force launching Suwayomi-Server in electron you would have something like:
|
||||||
|
```
|
||||||
|
java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath="/path/to/electron" -jar Suwayomi-Server-v0.X.Y-rXXXX.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** you can put the command above in a custom launcher script like the ones found [here](https://github.com/Suwayomi/Suwayomi-Server/tree/master/scripts/resources).
|
||||||
|
|
||||||
|
## Overriding Suwayomi-Server's Data Directory path
|
||||||
|
The only possible way to override the default Data Directory path is with command-line arguments(the chicken and egg problem!).
|
||||||
|
Add the special config option `server.rootDir="<path to data directory>"` to your command-line arguments.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
java -Dsuwayomi.tachidesk.config.server.rootDir="/path/to/data/directory" -jar Suwayomi-Server-v0.X.Y-rXXXX.jar
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
1. **Install the latest version(or preview):** https://github.com/Suwayomi/Suwayomi-Server/releases/latest
|
||||||
|
2. Find an extensions repo, there is now no default extensions, and you have to use Google to find a Tachiyomi extension repo.
|
||||||
|
- Note: The repo should look like `https://raw.githubusercontent.com/user/reponame` or `https://www.github.com/user/reponame`
|
||||||
|
3. Configure it using one of the following:
|
||||||
|
- Suwayomi-Server option 1: With the **new launcher**, go to the `Extensions` tab and add the extensions repo.
|
||||||
|
- Suwayomi-Server option 2: Use the **server settings** in **WebUI** to add the extensions repo.
|
||||||
|
- Suwayomi-Server option 3: Go to the `server.conf` file in the data files and add the extensions repo.
|
||||||
|
- Suwayomi docker container: Edit the `EXTENSION_REPOS` environment variable and add the extension repo in the format listed in the container README.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Welcome to the Suwayomi wiki!
|
||||||
|
|
||||||
|
Look at the pages section for links and stuff.
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
Follow the steps below to create local manga.
|
||||||
|
|
||||||
|
1. Place correctly structured manga inside the `local` directory located in [Suwayomi's data directory](https://github.com/Suwayomi/Suwayomi-Server/wiki/The-Data-Directory).
|
||||||
|
1. You can then access the manga in the **Local source** source.
|
||||||
|
|
||||||
|
If you add more chapters then you'll have to manually refresh the chapter list.
|
||||||
|
|
||||||
|
Supported chapter formats are folder with pictures inside (such as `.jpg`, `.png`, etc.), `ZIP`/`CBZ`, `RAR`/`CBR` and `EPUB`. But expect better performance with directories and `ZIP`/`CBZ`.
|
||||||
|
|
||||||
|
**Note:** While Suwayomi does support chapters compressed as **RAR** or **CBR**, note that **RAR** or **CBR** files using the **RAR5** format are not supported yet.
|
||||||
|
|
||||||
|
**Note:** If **CBR** or **RAR** files do not work, you may need to extract and re-compress them into one of the supported formats.
|
||||||
|
|
||||||
|
|
||||||
|
## Folder Structure
|
||||||
|
|
||||||
|
Suwayomi requires a specific folder structure for local manga to be correctly processed. Local manga will be read from the `local` folder. Each manga must have a `Manga` folder and a `Chapter` folder. Images will then go into the chapter folder. See below for more information on archive files. You can refer to the following example:
|
||||||
|
|
||||||
|
|
||||||
|
<div class="side-by-side">
|
||||||
|
<ul class="file-tree">
|
||||||
|
<li>
|
||||||
|
local/
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">Manga title/</span>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">Chapter 01 - Prologue/</span>
|
||||||
|
<ul>
|
||||||
|
<li><span class="ft-icon ft-image">page01.png</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">page02.png</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">page03.png</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">...</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">Chapter 02 - A hero's journey/</span>
|
||||||
|
<ul>
|
||||||
|
<li><span class="ft-icon ft-image">01.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">02.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">03.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">...</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><span class="ft-icon ft-image">cover.jpg</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">Other Manga title/</span>
|
||||||
|
<ul>
|
||||||
|
<li><span class="ft-icon ft-image">cover.jpg</span></li>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">ch001/</span>
|
||||||
|
<ul>
|
||||||
|
<li><span class="ft-icon ft-image">01 - cover.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">02 - first page.jpeg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">03 - second.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">...</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">ch002/</span>
|
||||||
|
<ul>
|
||||||
|
<li><span class="ft-icon ft-image">001.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">002.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">003.jpg</span></li>
|
||||||
|
<li><span class="ft-icon ft-image">...</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>...</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
**Note:** Chapter and page names should be ordered alphanumerically, using zero padded name prefixes is usually the best option.
|
||||||
|
|
||||||
|
The path to the folder with images must contain both the manga title and the chapter name (as seen above).
|
||||||
|
|
||||||
|
## Archive Files
|
||||||
|
Archive files such as `ZIP`/`CBZ` are supported but the folder structure inside is not. Any folders inside the archive file are ignored. You must place the archive inside the `Manga` folder where the name will become the `Chapter` title. All images inside the archive regardless of folder structure will become pages for that chapter.
|
||||||
|
|
||||||
|
<ul class="file-tree">
|
||||||
|
<li>
|
||||||
|
local/
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-folder">Manga title/</span>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-zip">ch1.zip</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="ft-icon ft-zip">ch2.zip</span>
|
||||||
|
</li>
|
||||||
|
<span class="ft-icon ft-image">cover.jpg</span>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>...</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
### Editing local manga details
|
||||||
|
|
||||||
|
It is possible to add details to local manga. Like manga from other catalogs, you add information about the manga such as the author, artist, description, and genre tags.
|
||||||
|
|
||||||
|
To import details along with your local manga, you have to create a json file. It can be named anything but it must be placed within the **Manga** folder. A standard file name is `details.json`. This file will contain the extended details about the manga in the `JSON` format. You can see the example below on how to build the file. Once the file is there, the app should load the data when you first open the manga, or you can pull down to refresh the details.
|
||||||
|
|
||||||
|
You can copy the following example and edit the details as needed:
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"title": "Example Title",
|
||||||
|
"author": "Example Author",
|
||||||
|
"artist": "Example Artist",
|
||||||
|
"description": "Example Description",
|
||||||
|
"genre": ["genre 1", "genre 2", "etc"],
|
||||||
|
"status": "0",
|
||||||
|
"_status values": ["0 = Unknown", "1 = Ongoing", "2 = Completed", "3 = Licensed"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a custom cover image
|
||||||
|
|
||||||
|
It is also possible to use a custom image as a cover for each local manga.
|
||||||
|
|
||||||
|
To do this, you only need to place the image file, that needs to be named
|
||||||
|
`cover.jpg`, in the root of the manga folder. The app will then use your
|
||||||
|
custom image in the local source listing.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## Default data directory
|
||||||
|
The default data directory is located below depending on your operating system:
|
||||||
|
|
||||||
|
Replace `<Account>` with your account/username.
|
||||||
|
|
||||||
|
On Windows 7 and later : `C:\Users\<Account>\AppData\Local\Tachidesk`
|
||||||
|
|
||||||
|
On Windows XP : `C:\Documents and Settings\<Account>\Application Data\Local Settings\Tachidesk`
|
||||||
|
|
||||||
|
On Mac OS X : `/Users/<Account>/Library/Application Support/Tachidesk`
|
||||||
|
|
||||||
|
On Unix/Linux : `/home/<account>/.local/share/Tachidesk`
|
||||||
|
|
||||||
|
## Custom
|
||||||
|
You can set Suwayomi-Server to use a specific directory with the `-Dsuwayomi.tachidesk.config.server.rootDir` startup argument.
|
||||||
|
|
||||||
|
An example of this is `java -Dsuwayomi.tachidesk.config.server.rootDir="D:\Tachidesk Data" -jar Tachidesk-vX.Y.Z-rxxxx.jar`
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## General Troubleshooting
|
||||||
|
This guide will try to fix Suwayomi by reseting it to a clean installation state.
|
||||||
|
|
||||||
|
- Make sure you have a recent backup of your library or create one in the app (if possible) because we **are going to wipe all Suwayomi data**.
|
||||||
|
- Make sure Suwayomi is not running (right click on tray icon and quit or kill it through the way your Operating System provides)
|
||||||
|
- Clear all browsing data on your browser if you use Suwayomi from a browser.
|
||||||
|
- Delete the Suwayomi data directory located below and re-run the app.
|
||||||
|
|
||||||
|
Note: Replace `<Account>` with the currently logged in account/username on your pc.
|
||||||
|
|
||||||
|
On Mac OS X : `/Users/<Account>/Library/Application Support/Tachidesk`
|
||||||
|
|
||||||
|
On Windows XP : `C:\Documents and Settings\<Account>\Application Data\Local Settings\Tachidesk`
|
||||||
|
|
||||||
|
On Windows 7 and later : `C:\Users\<Account>\AppData\Local\Tachidesk`
|
||||||
|
|
||||||
|
On Unix/Linux : `/home/<account>/.local/share/Tachidesk`
|
||||||
|
|
||||||
|
- In the case that you have to periodically perform this fix or the problem persists or the method failed to fix it, open an issue or Join the [Suwayomi discord server](https://discord.gg/DDZdqZWaHA) to hang out with the community and to receive support and help.
|
||||||
+45
-39
@@ -1,21 +1,22 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.2.0"
|
kotlin = "2.3.21"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.11.0"
|
||||||
serialization = "1.9.0"
|
serialization = "1.11.0"
|
||||||
okhttp = "5.1.0" # Major version is locked by Tachiyomi extensions
|
jvmTarget = "21"
|
||||||
javalin = "6.7.0"
|
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
||||||
jte = "3.2.1"
|
javalin = "7.2.0"
|
||||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
jte = "3.2.4"
|
||||||
|
jackson = "3.1.2" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
exposed = "0.61.0"
|
exposed = "0.61.0"
|
||||||
dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed
|
dex2jar = "2.4.36"
|
||||||
polyglot = "24.2.2"
|
polyglot = "25.0.3"
|
||||||
settings = "1.3.0"
|
settings = "1.3.0"
|
||||||
twelvemonkeys = "3.12.0"
|
twelvemonkeys = "3.13.1"
|
||||||
graphqlkotlin = "8.8.1"
|
graphqlkotlin = "8.9.0"
|
||||||
xmlserialization = "0.91.1"
|
xmlserialization = "0.91.3"
|
||||||
ktlint = "1.6.0"
|
ktlint = "1.8.0"
|
||||||
koin = "4.1.0"
|
koin = "4.2.1"
|
||||||
moko = "0.25.0"
|
moko = "0.26.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# Kotlin
|
# Kotlin
|
||||||
@@ -37,23 +38,23 @@ serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", v
|
|||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
||||||
logback = "ch.qos.logback:logback-classic:1.5.18"
|
logback = "ch.qos.logback:logback-classic:1.5.32"
|
||||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.0.7"
|
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
|
||||||
|
|
||||||
# OkHttp
|
# OkHttp
|
||||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
||||||
okio = "com.squareup.okio:okio:3.15.0"
|
okio = "com.squareup.okio:okio:3.17.0"
|
||||||
|
|
||||||
# Javalin api
|
# Javalin api
|
||||||
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||||
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
||||||
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" }
|
javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
|
||||||
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||||
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||||
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
|
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.20"
|
||||||
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
||||||
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||||
|
|
||||||
@@ -67,7 +68,9 @@ exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "e
|
|||||||
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
||||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||||
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
||||||
|
postgres = "org.postgresql:postgresql:42.7.11"
|
||||||
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
||||||
|
hikaricp = "com.zaxxer:HikariCP:7.0.2"
|
||||||
|
|
||||||
# Exposed Migrations
|
# Exposed Migrations
|
||||||
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.8.0"
|
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.8.0"
|
||||||
@@ -83,10 +86,10 @@ systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
|
|||||||
# dependencies of Tachiyomi extensions
|
# dependencies of Tachiyomi extensions
|
||||||
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
||||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||||
jsoup = "org.jsoup:jsoup:1.21.1"
|
jsoup = "org.jsoup:jsoup:1.22.2"
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
config = "com.typesafe:config:1.4.4"
|
config = "com.typesafe:config:1.4.8"
|
||||||
config4k = "io.github.config4k:config4k:0.7.0"
|
config4k = "io.github.config4k:config4k:0.7.0"
|
||||||
|
|
||||||
# Sort
|
# Sort
|
||||||
@@ -96,13 +99,13 @@ sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
|||||||
android-stubs = "com.github.Suwayomi:android-jar:1.0.0"
|
android-stubs = "com.github.Suwayomi:android-jar:1.0.0"
|
||||||
|
|
||||||
# Asm modificiation
|
# Asm modificiation
|
||||||
asm = "org.ow2.asm:asm:9.5" # version locked by Dex2Jar
|
asm = "org.ow2.asm:asm:9.9.1" # version locked by Dex2Jar
|
||||||
dex2jar-translator = { module = "com.github.ThexXTURBOXx.dex2jar:dex-translator", version.ref = "dex2jar" }
|
dex2jar-translator = { module = "de.femtopedia.dex2jar:dex-translator", version.ref = "dex2jar" }
|
||||||
dex2jar-tools = { module = "com.github.ThexXTURBOXx.dex2jar:dex-tools", version.ref = "dex2jar" }
|
dex2jar-tools = { module = "de.femtopedia.dex2jar:dex-tools", version.ref = "dex2jar" }
|
||||||
|
|
||||||
# APK
|
# APK
|
||||||
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
||||||
apksig = "com.android.tools.build:apksig:8.11.1"
|
apksig = "com.android.tools.build:apksig:9.2.1"
|
||||||
|
|
||||||
# Xml
|
# Xml
|
||||||
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||||
@@ -110,15 +113,15 @@ xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
|||||||
# Disk & File
|
# Disk & File
|
||||||
appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
|
appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
|
||||||
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
||||||
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
|
zip4j = "net.lingala.zip4j:zip4j:2.11.6"
|
||||||
commonscompress = "org.apache.commons:commons-compress:1.27.1"
|
commonscompress = "org.apache.commons:commons-compress:1.28.0"
|
||||||
junrar = "com.github.junrar:junrar:7.5.5"
|
junrar = "com.github.junrar:junrar:7.5.10"
|
||||||
|
|
||||||
# AES/CBC/PKCS7Padding Cypher provider
|
# AES/CBC/PKCS7Padding Cypher provider
|
||||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81"
|
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||||
|
|
||||||
# AndroidX annotations
|
# AndroidX annotations
|
||||||
android-annotations = "androidx.annotation:annotation:1.9.1"
|
android-annotations = "androidx.annotation:annotation:1.10.0"
|
||||||
|
|
||||||
# Substitute for duktape-android
|
# Substitute for duktape-android
|
||||||
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
|
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
|
||||||
@@ -129,7 +132,7 @@ settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.r
|
|||||||
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
|
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
|
||||||
|
|
||||||
# ICU4J
|
# ICU4J
|
||||||
icu4j = "com.ibm.icu:icu4j:77.1"
|
icu4j = "com.ibm.icu:icu4j:78.3"
|
||||||
|
|
||||||
# Image Decoding implementation provider
|
# Image Decoding implementation provider
|
||||||
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
|
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
|
||||||
@@ -143,7 +146,7 @@ twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp"
|
|||||||
imageio-webp = "com.github.usefulness:webp-imageio:0.10.2"
|
imageio-webp = "com.github.usefulness:webp-imageio:0.10.2"
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
mockk = "io.mockk:mockk:1.14.5"
|
mockk = "io.mockk:mockk:1.14.9"
|
||||||
|
|
||||||
# cron scheduler
|
# cron scheduler
|
||||||
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
|
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
|
||||||
@@ -154,6 +157,9 @@ cronUtils = "com.cronutils:cron-utils:9.2.1"
|
|||||||
# Webview
|
# Webview
|
||||||
kcef = "dev.datlag:kcef:2024.04.20.4"
|
kcef = "dev.datlag:kcef:2024.04.20.4"
|
||||||
|
|
||||||
|
# User
|
||||||
|
jwt = "com.auth0:java-jwt:4.5.2"
|
||||||
|
|
||||||
# lint - used for renovate to update ktlint version
|
# lint - used for renovate to update ktlint version
|
||||||
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
||||||
|
|
||||||
@@ -167,16 +173,16 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
|
|||||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
||||||
|
|
||||||
# Linter
|
# Linter
|
||||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "13.0.0"}
|
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.2.0"}
|
||||||
|
|
||||||
# Build config
|
# Build config
|
||||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.6.7"}
|
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.9"}
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
download = { id = "de.undercouch.download", version = "5.6.0"}
|
download = { id = "de.undercouch.download", version = "5.7.0"}
|
||||||
|
|
||||||
# ShadowJar
|
# ShadowJar
|
||||||
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
|
shadowjar = { id = "com.gradleup.shadow", version = "8.3.10"}
|
||||||
|
|
||||||
# Moko
|
# Moko
|
||||||
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH="\\\"\\\""
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -172,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
|||||||
Vendored
+10
-22
@@ -23,8 +23,8 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -51,7 +51,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -65,30 +65,18 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
|||||||
+13
-5
@@ -11,11 +11,19 @@
|
|||||||
"/scripts/bundler.sh/"
|
"/scripts/bundler.sh/"
|
||||||
],
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"JRE_RELEASE=[\"'](?<currentValue>.+?)[\"']\\s+"
|
"JRE_ZULU=[\"'](?<currentValue>.+?)[\"']"
|
||||||
],
|
],
|
||||||
"datasourceTemplate": "github-releases",
|
"depNameTemplate": "zulu",
|
||||||
"depNameTemplate": "adoptium/temurin21-binaries",
|
"datasourceTemplate": "custom.zulu",
|
||||||
"versioningTemplate": "regex:^jdk-?(?<major>\\d+).(?<minor>\\d+).+?(?<patch>[\\d+]+)$"
|
"versioningTemplate": "regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+).*$"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"customDatasources": {
|
||||||
|
"zulu": {
|
||||||
|
"defaultRegistryUrlTemplate": "https://api.azul.com/metadata/v1/zulu/packages?availability_types=ca&release_status=both&java_package_type=jre&crac_supported=false&javafx_bundled=false&support_term=lts&arch=x86&os=linux&archive_type=zip&page_size=1000&include_fields=java_package_features,release_status,support_term,os,arch,hw_bitness,abi,java_package_type,javafx_bundled,sha256_hash,cpu_gen,size,archive_type,certifications,lib_c_type,crac_supported&page=1&azul_com=true",
|
||||||
|
"transformTemplates": [
|
||||||
|
"{\"releases\": $$.$join([$join($map(distro_version[[0..2]], $string), \".\"), \"_\", $join($map(java_version, $string), \".\")])}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-33
@@ -39,21 +39,25 @@ main() {
|
|||||||
download_launcher
|
download_launcher
|
||||||
|
|
||||||
if [ ! -f scripts/resources/catch_abort.so ]; then
|
if [ ! -f scripts/resources/catch_abort.so ]; then
|
||||||
gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so
|
gcc -fPIC -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
JRE_ZULU="25.30.17_25.0.1"
|
||||||
|
JRE_RELEASE="jre${JRE_ZULU#*_}" # e.g. jre25.0.1
|
||||||
|
ZULU_RELEASE="zulu${JRE_ZULU%_*}" # e.g. zulu25.30.17
|
||||||
|
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
debian-all)
|
debian-all)
|
||||||
RELEASE="$RELEASE_NAME.deb"
|
RELEASE="$RELEASE_NAME.deb"
|
||||||
|
download_jogamp "linux-*" # it's easier to bundle them ourselves than to handle Debian's path conventions
|
||||||
make_deb_package
|
make_deb_package
|
||||||
move_release_to_output_dir
|
move_release_to_output_dir
|
||||||
;;
|
;;
|
||||||
appimage)
|
appimage)
|
||||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
JRE="$ZULU_RELEASE-ca-$JRE_RELEASE-linux_x64.zip"
|
||||||
JRE_RELEASE="jdk-21.0.8+9"
|
JRE_DIR="${JRE%.*}"
|
||||||
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
|
||||||
JRE_DIR="$JRE_RELEASE-jre"
|
download_jogamp "linux-amd64"
|
||||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
|
||||||
setup_jre
|
setup_jre
|
||||||
|
|
||||||
RELEASE="$RELEASE_NAME.AppImage"
|
RELEASE="$RELEASE_NAME.AppImage"
|
||||||
@@ -67,14 +71,13 @@ main() {
|
|||||||
move_release_to_output_dir
|
move_release_to_output_dir
|
||||||
;;
|
;;
|
||||||
linux-x64)
|
linux-x64)
|
||||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
JRE="$ZULU_RELEASE-ca-$JRE_RELEASE-linux_x64.zip"
|
||||||
JRE_RELEASE="jdk-21.0.8+9"
|
JRE_DIR="${JRE%.*}"
|
||||||
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
|
||||||
JRE_DIR="$JRE_RELEASE-jre"
|
|
||||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
|
||||||
ELECTRON="electron-$electron_version-linux-x64.zip"
|
ELECTRON="electron-$electron_version-linux-x64.zip"
|
||||||
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
||||||
download_electron
|
download_electron
|
||||||
|
download_jogamp "linux-amd64"
|
||||||
setup_jre
|
setup_jre
|
||||||
tree "$RELEASE_NAME"
|
tree "$RELEASE_NAME"
|
||||||
|
|
||||||
@@ -83,14 +86,13 @@ main() {
|
|||||||
move_release_to_output_dir
|
move_release_to_output_dir
|
||||||
;;
|
;;
|
||||||
macOS-x64)
|
macOS-x64)
|
||||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
JRE="$ZULU_RELEASE-ca-$JRE_RELEASE-macosx_x64.zip"
|
||||||
JRE_RELEASE="jdk-21.0.8+9"
|
JRE_DIR="${JRE%.*}"
|
||||||
JRE="OpenJDK21U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
|
||||||
JRE_DIR="$JRE_RELEASE-jre"
|
|
||||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
|
||||||
ELECTRON="electron-$electron_version-darwin-x64.zip"
|
ELECTRON="electron-$electron_version-darwin-x64.zip"
|
||||||
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
||||||
download_electron
|
download_electron
|
||||||
|
download_jogamp "macosx-universal"
|
||||||
setup_jre
|
setup_jre
|
||||||
tree "$RELEASE_NAME"
|
tree "$RELEASE_NAME"
|
||||||
|
|
||||||
@@ -99,14 +101,13 @@ main() {
|
|||||||
move_release_to_output_dir
|
move_release_to_output_dir
|
||||||
;;
|
;;
|
||||||
macOS-arm64)
|
macOS-arm64)
|
||||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
JRE="$ZULU_RELEASE-ca-$JRE_RELEASE-macosx_aarch64.zip"
|
||||||
JRE_RELEASE="jdk-21.0.8+9"
|
JRE_DIR="${JRE%.*}"
|
||||||
JRE="OpenJDK21U-jre_aarch64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
|
||||||
JRE_DIR="$JRE_RELEASE-jre"
|
|
||||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
|
||||||
ELECTRON="electron-$electron_version-darwin-arm64.zip"
|
ELECTRON="electron-$electron_version-darwin-arm64.zip"
|
||||||
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
||||||
download_electron
|
download_electron
|
||||||
|
download_jogamp "macosx-universal"
|
||||||
setup_jre
|
setup_jre
|
||||||
tree "$RELEASE_NAME"
|
tree "$RELEASE_NAME"
|
||||||
|
|
||||||
@@ -115,14 +116,13 @@ main() {
|
|||||||
move_release_to_output_dir
|
move_release_to_output_dir
|
||||||
;;
|
;;
|
||||||
windows-x64)
|
windows-x64)
|
||||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
JRE="$ZULU_RELEASE-ca-$JRE_RELEASE-win_x64.zip"
|
||||||
JRE_RELEASE="jdk-21.0.8+9"
|
JRE_DIR="${JRE%.*}"
|
||||||
JRE="OpenJDK21U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').zip"
|
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
|
||||||
JRE_DIR="$JRE_RELEASE-jre"
|
|
||||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
|
||||||
ELECTRON="electron-$electron_version-win32-x64.zip"
|
ELECTRON="electron-$electron_version-win32-x64.zip"
|
||||||
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
|
||||||
download_electron
|
download_electron
|
||||||
|
download_jogamp "windows-amd64"
|
||||||
setup_jre
|
setup_jre
|
||||||
tree "$RELEASE_NAME"
|
tree "$RELEASE_NAME"
|
||||||
|
|
||||||
@@ -154,6 +154,18 @@ download_launcher() {
|
|||||||
mv "Suwayomi-Launcher.jar" "$RELEASE_NAME/Suwayomi-Launcher.jar"
|
mv "Suwayomi-Launcher.jar" "$RELEASE_NAME/Suwayomi-Launcher.jar"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download_jogamp() {
|
||||||
|
local platform="$1"
|
||||||
|
if [ ! -f jogamp-all-platforms.7z ]; then
|
||||||
|
curl "https://jogamp.org/deployment/jogamp-current/archive/jogamp-all-platforms.7z" -o jogamp-all-platforms.7z
|
||||||
|
fi
|
||||||
|
|
||||||
|
7z x jogamp-all-platforms.7z "jogamp-all-platforms/lib/$platform/"
|
||||||
|
mkdir -p "$RELEASE_NAME/natives/"
|
||||||
|
mv jogamp-all-platforms/lib/* "$RELEASE_NAME/natives/"
|
||||||
|
rm -rf jogamp-all-platforms
|
||||||
|
}
|
||||||
|
|
||||||
download_electron() {
|
download_electron() {
|
||||||
if [ ! -f "$ELECTRON" ]; then
|
if [ ! -f "$ELECTRON" ]; then
|
||||||
curl -L "$ELECTRON_URL" -o "$ELECTRON"
|
curl -L "$ELECTRON_URL" -o "$ELECTRON"
|
||||||
@@ -222,6 +234,7 @@ make_deb_package() {
|
|||||||
local upstream_source="suwayomi-server_$RELEASE_VERSION.orig.tar.gz"
|
local upstream_source="suwayomi-server_$RELEASE_VERSION.orig.tar.gz"
|
||||||
|
|
||||||
mkdir "$RELEASE_NAME/$source_dir/"
|
mkdir "$RELEASE_NAME/$source_dir/"
|
||||||
|
mv "$RELEASE_NAME/natives" "$RELEASE_NAME/$source_dir/natives"
|
||||||
mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar"
|
mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar"
|
||||||
cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar"
|
cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar"
|
||||||
copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/"
|
copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/"
|
||||||
@@ -232,8 +245,10 @@ make_deb_package() {
|
|||||||
sed -i "s/\$pkgver/$RELEASE_VERSION/" "$RELEASE_NAME/$source_dir/debian/changelog"
|
sed -i "s/\$pkgver/$RELEASE_VERSION/" "$RELEASE_NAME/$source_dir/debian/changelog"
|
||||||
sed -i "s/\$pkgrel/1/" "$RELEASE_NAME/$source_dir/debian/changelog"
|
sed -i "s/\$pkgrel/1/" "$RELEASE_NAME/$source_dir/debian/changelog"
|
||||||
|
|
||||||
sudo apt update
|
if [ "${CI:-}" = true ]; then
|
||||||
sudo apt install devscripts build-essential dh-exec
|
sudo apt update
|
||||||
|
sudo apt install devscripts build-essential dh-exec
|
||||||
|
fi
|
||||||
cd "$RELEASE_NAME/$source_dir/"
|
cd "$RELEASE_NAME/$source_dir/"
|
||||||
dpkg-buildpackage --no-sign --build=all
|
dpkg-buildpackage --no-sign --build=all
|
||||||
cd -
|
cd -
|
||||||
@@ -254,8 +269,10 @@ make_appimage() {
|
|||||||
cp "scripts/resources/appimage/AppRun" "$RELEASE_NAME/AppRun"
|
cp "scripts/resources/appimage/AppRun" "$RELEASE_NAME/AppRun"
|
||||||
chmod +x "$RELEASE_NAME/AppRun"
|
chmod +x "$RELEASE_NAME/AppRun"
|
||||||
|
|
||||||
sudo apt update
|
if [ "${CI:-}" = true ]; then
|
||||||
sudo apt install libfuse2
|
sudo apt update
|
||||||
|
sudo apt install libfuse2
|
||||||
|
fi
|
||||||
curl -L $APPIMAGE_URL -o $APPIMAGE_TOOLNAME
|
curl -L $APPIMAGE_URL -o $APPIMAGE_TOOLNAME
|
||||||
chmod +x $APPIMAGE_TOOLNAME
|
chmod +x $APPIMAGE_TOOLNAME
|
||||||
ARCH=x86_64 ./$APPIMAGE_TOOLNAME "$RELEASE_NAME" "$RELEASE"
|
ARCH=x86_64 ./$APPIMAGE_TOOLNAME "$RELEASE_NAME" "$RELEASE"
|
||||||
@@ -267,7 +284,7 @@ make_windows_bundle() {
|
|||||||
##./bundler.sh: line 250: wine: command not found
|
##./bundler.sh: line 250: wine: command not found
|
||||||
|
|
||||||
## check if running under github actions
|
## check if running under github actions
|
||||||
#if [ "$CI" = true ]; then
|
#if [ "${CI:-}" = true ]; then
|
||||||
## change electron executable's icon
|
## change electron executable's icon
|
||||||
#sudo dpkg --add-architecture i386
|
#sudo dpkg --add-architecture i386
|
||||||
#wget -qO - https://dl.winehq.org/wine-builds/winehq.key \
|
#wget -qO - https://dl.winehq.org/wine-builds/winehq.key \
|
||||||
@@ -298,7 +315,7 @@ make_windows_bundle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_windows_package() {
|
make_windows_package() {
|
||||||
if [ "$CI" = true ]; then
|
if [ "${CI:-}" = true ]; then
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y wixl
|
sudo apt install -y wixl
|
||||||
fi
|
fi
|
||||||
@@ -309,6 +326,9 @@ make_windows_package() {
|
|||||||
find "$RELEASE_NAME/electron" \
|
find "$RELEASE_NAME/electron" \
|
||||||
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
|
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
|
||||||
--directory-ref electron --component-group electron >"$RELEASE_NAME/electron.wxs"
|
--directory-ref electron --component-group electron >"$RELEASE_NAME/electron.wxs"
|
||||||
|
find "$RELEASE_NAME/natives" \
|
||||||
|
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
|
||||||
|
--directory-ref natives --component-group natives >"$RELEASE_NAME/natives.wxs"
|
||||||
|
|
||||||
find "$RELEASE_NAME/bin" \
|
find "$RELEASE_NAME/bin" \
|
||||||
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
|
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
|
||||||
@@ -319,7 +339,7 @@ make_windows_package() {
|
|||||||
|
|
||||||
wixl -D ProductVersion="$RELEASE_VERSION" -D SourceDir="$RELEASE_NAME" \
|
wixl -D ProductVersion="$RELEASE_VERSION" -D SourceDir="$RELEASE_NAME" \
|
||||||
-D Icon="$icon" --arch "$arch" "scripts/resources/msi/suwayomi-server-$arch.wxs" \
|
-D Icon="$icon" --arch "$arch" "scripts/resources/msi/suwayomi-server-$arch.wxs" \
|
||||||
"$RELEASE_NAME/jre.wxs" "$RELEASE_NAME/electron.wxs" "$RELEASE_NAME/bin.wxs" -o "$RELEASE"
|
"$RELEASE_NAME/jre.wxs" "$RELEASE_NAME/electron.wxs" "$RELEASE_NAME/natives.wxs" "$RELEASE_NAME/bin.wxs" -o "$RELEASE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Error handler
|
# Error handler
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# exit early in case the file already exists
|
||||||
|
if [ -f /home/suwayomi/.local/share/Tachidesk/server.conf ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /home/suwayomi/.local/share/Tachidesk
|
||||||
|
|
||||||
|
# extract the server reference config from the jar
|
||||||
|
unzip -q -j /home/suwayomi/startup/tachidesk_latest.jar "server-reference.conf" -d /home/suwayomi/startup
|
||||||
|
|
||||||
|
# move and rename the reference config
|
||||||
|
mv /home/suwayomi/startup/server-reference.conf /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
url="$1"
|
||||||
|
arch="$2"
|
||||||
|
installdir="${3:-/opt/kcef/jcef}"
|
||||||
|
|
||||||
|
echo "Will try to download matching KCEF to $installdir, arch=$arch, api url=$url"
|
||||||
|
|
||||||
|
if [ -d "$installdir" ] && [ -f "$installdir/install.lock" ]; then
|
||||||
|
echo "JCEF already downloaded to $installdir, nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
echo "Not downloading KCEF since no URL specified"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
arpath=/tmp/kcef/jcef.tar.gz
|
||||||
|
expath=/tmp/kcef/jcef
|
||||||
|
mkdir -p "$expath"
|
||||||
|
|
||||||
|
if [ ! -f "$arpath" ]; then
|
||||||
|
body="`curl -# -H 'accept: application/vnd.github+json' "$url" | jq -r '.body'`"
|
||||||
|
archive="`echo "$body" | gawk -F'|' '
|
||||||
|
function compare_sdk(i1, v1, i2, v2) {
|
||||||
|
# sort sdks last (https://github.com/DatL4g/KCEF/blob/0665269b7d6a91b0ee187f4432bb5be5ca41a112/kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt#L743-L755)
|
||||||
|
if (v1 ~ /sdk/ && v2 ~ /sdk/) return 0;
|
||||||
|
if (v1 ~ /sdk/) return 1;
|
||||||
|
if (v2 ~ /sdk/) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
# ensure urls is an array
|
||||||
|
delete urls[0];
|
||||||
|
# parse os/arch tuple
|
||||||
|
match("'"$arch"'", /(.*)\/(.*)/, a);
|
||||||
|
os=a[1];
|
||||||
|
arch=a[2];
|
||||||
|
if (arch == "amd64") arch="x64";
|
||||||
|
if (arch == "arm64") arch="aarch64";
|
||||||
|
}
|
||||||
|
# for each line, check that the third table column contains a url and if so, extract it
|
||||||
|
# also need to check that it contains JCEF and matches the os/arch tuple
|
||||||
|
match($4, /(https?:\/\/|www.)[-a-zA-Z0-9+&@#\/%?=~_|!:.;]*[-a-zA-Z0-9+&@#/%=~_|]/, m) {
|
||||||
|
# if so, push to the urls array; there is no push function, so do this cursed construction
|
||||||
|
# arrays by convention start at 1, so do that
|
||||||
|
if (m[0] ~ /jcef/ && m[0] ~ os && m[0] ~ arch && m[0] ~ /\.tar\.gz$/) urls[length(urls)+1] = m[0];
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
# now make sure sdk is sorted last, since we dont actually need the full sdk
|
||||||
|
asort(urls, sorted, "compare_sdk");
|
||||||
|
for (x in sorted) print sorted[x];
|
||||||
|
}
|
||||||
|
' | head -n1`"
|
||||||
|
|
||||||
|
if [ -z "$archive" ]; then
|
||||||
|
echo "No suitable archive found on release page, so not downloading"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found suitable JCEF release: $archive"
|
||||||
|
curl -# -L -H 'accept: application/x-tar' -o "$arpath" "$archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
tar -C "$expath" -xf "$arpath"
|
||||||
|
libfolder="`find "$expath" -type d -name lib`"
|
||||||
|
|
||||||
|
if [ -z "$libfolder" ]; then
|
||||||
|
echo "lib folder not found in extracted archive, aborting"
|
||||||
|
rm -rf /tmp/kcef
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$installdir"
|
||||||
|
rmdir "$installdir" # we abuse -p to make sure all parent directories are created, then delete the actual target, since mv would move the libfolder inside otherwise
|
||||||
|
mv "$libfolder" "$installdir"
|
||||||
|
chmod -R a+x "$installdir"
|
||||||
|
touch "$installdir/install.lock"
|
||||||
|
rm -rf /tmp/kcef
|
||||||
@@ -1 +1 @@
|
|||||||
start "" jre/bin/javaw -jar Suwayomi-Launcher.jar
|
start "" jre/bin/javaw --add-exports=java.desktop/sun.awt=ALL-UNNAMED -jar Suwayomi-Launcher.jar %*
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
cd "`dirname "$0"`"
|
cd "`dirname "$0"`"
|
||||||
|
|
||||||
./jre/bin/java -jar Suwayomi-Launcher.jar
|
./jre/bin/java --add-exports=java.desktop/sun.awt=ALL-UNNAMED -jar Suwayomi-Launcher.jar "$@"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd "$APPDIR"
|
||||||
exec $APPDIR/jre/bin/java -jar $APPDIR/bin/Suwayomi-Server.jar
|
exec $APPDIR/jre/bin/java -jar $APPDIR/bin/Suwayomi-Server.jar
|
||||||
|
|||||||
@@ -1,52 +1,19 @@
|
|||||||
// Linux only:
|
// Linux only:
|
||||||
// Attempts to catch SIGTRAP, inform Java, then exit the thread instead of bringing down the whole process
|
// Attempts to catch SIGTRAP and exit the thread instead of bringing down the whole process
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
JavaVM *g_vm;
|
|
||||||
|
|
||||||
void load_vm() {
|
|
||||||
if (g_vm) return;
|
|
||||||
JavaVM *vms[1];
|
|
||||||
jsize n = 0;
|
|
||||||
// JNI_OnLoad won't be called when loaded via LD_PRELOAD, so attempt to find the VM now
|
|
||||||
if (JNI_GetCreatedJavaVMs(vms, 1, &n) == JNI_OK && n > 0) {
|
|
||||||
g_vm = vms[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jint throwThreadDeath(JNIEnv *env, char *message) {
|
|
||||||
char *className = "java/lang/UnknownError";
|
|
||||||
jclass exClass = (*env)->FindClass(env, className);
|
|
||||||
if (exClass == NULL) return JNI_ERR;
|
|
||||||
return (*env)->ThrowNew(env, exClass, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void signalHandler(int signum, siginfo_t* si, void* uc) {
|
void signalHandler(int signum, siginfo_t* si, void* uc) {
|
||||||
void *retaddrs[64];
|
void *retaddrs[64];
|
||||||
int n = backtrace(retaddrs, sizeof(retaddrs) / sizeof(retaddrs[0]));
|
int n = backtrace(retaddrs, sizeof(retaddrs) / sizeof(retaddrs[0]));
|
||||||
printf("\n### ABORT :: Backtrace: ###\n");
|
printf("\n### ABORT :: Backtrace: ###\n");
|
||||||
backtrace_symbols_fd(retaddrs, n, STDERR_FILENO);
|
backtrace_symbols_fd(retaddrs, n, STDERR_FILENO);
|
||||||
printf("### ABORT :: Exiting this thread. If this causes problems, please report the above backtrace to Suwayomi. ###\n\n");
|
printf("### ABORT :: Exiting this thread. If this causes problems, please report the above backtrace to Suwayomi. ###\n\n");
|
||||||
|
|
||||||
load_vm();
|
|
||||||
if (g_vm) {
|
|
||||||
JNIEnv *env;
|
|
||||||
jint getEnvStat = (*g_vm)->GetEnv(g_vm, (void**) &env, JNI_VERSION_1_2);
|
|
||||||
if (getEnvStat == JNI_EDETACHED) (*g_vm)->AttachCurrentThread(g_vm, (void**) &env, NULL);
|
|
||||||
jint exStat = throwThreadDeath(env, "SIGTRAP caught");
|
|
||||||
if (exStat != 0) printf("Exception throwing failed: %d\n", exStat);
|
|
||||||
(*g_vm)->DetachCurrentThread(g_vm);
|
|
||||||
}
|
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,4 +26,7 @@ void dlmain() {
|
|||||||
if (sigaction(SIGTRAP, &sa, NULL) != 0) {
|
if (sigaction(SIGTRAP, &sa, NULL) != 0) {
|
||||||
printf("[FATAL] sigaction failed\n");
|
printf("[FATAL] sigaction failed\n");
|
||||||
}
|
}
|
||||||
|
if (sigaction(SIGILL, &sa, NULL) != 0) {
|
||||||
|
printf("[FATAL] sigaction failed\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Homepage: https://github.com/Suwayomi/Suwayomi-Server
|
|||||||
|
|
||||||
Package: suwayomi-server
|
Package: suwayomi-server
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${misc:Depends}, openjdk-21-jre | openjdk-21-jre-headless | openjdk-21-jdk | openjdk-21-jdk-headless | temurin-21-jre | temurin-21-jdk | zulu21-jre | zulu21-jre-headless | zulu21-jdk | zulu21-jdk-headless | msopenjdk-21 | java-21-amazon-corretto-jdk
|
Depends: ${misc:Depends}, zulu21-jre | zulu21-jre-headless | zulu21-jdk | zulu21-jdk-headless | openjdk-21-jre | openjdk-21-jre-headless | openjdk-21-jdk | openjdk-21-jdk-headless | temurin-21-jre | temurin-21-jdk | msopenjdk-21 | java-21-amazon-corretto-jdk
|
||||||
Description: Manga Reader
|
Description: Manga Reader
|
||||||
A free and open source manga reader server that runs extensions built for Tachiyomi.
|
A free and open source manga reader server that runs extensions built for Tachiyomi.
|
||||||
Suwayomi is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.
|
Suwayomi is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Suwayomi-Server.jar usr/share/java/suwayomi-server/bin/
|
Suwayomi-Server.jar usr/share/java/suwayomi-server/bin/
|
||||||
Suwayomi-Launcher.jar usr/share/java/suwayomi-server/
|
Suwayomi-Launcher.jar usr/share/java/suwayomi-server/
|
||||||
|
natives/* usr/share/java/suwayomi-server/natives/
|
||||||
suwayomi-server.png usr/share/pixmaps/
|
suwayomi-server.png usr/share/pixmaps/
|
||||||
suwayomi-server.desktop usr/share/applications/
|
suwayomi-server.desktop usr/share/applications/
|
||||||
suwayomi-launcher.desktop usr/share/applications/
|
suwayomi-launcher.desktop usr/share/applications/
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<Directory Id="INSTALLDIR" Name="Suwayomi-Server" >
|
<Directory Id="INSTALLDIR" Name="Suwayomi-Server" >
|
||||||
<Directory Id="jre"/>
|
<Directory Id="jre"/>
|
||||||
<Directory Id="electron"/>
|
<Directory Id="electron"/>
|
||||||
|
<Directory Id="natives"/>
|
||||||
<Directory Id="bin"/>
|
<Directory Id="bin"/>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
<ComponentRef Id="SuwayomiLauncherBAT" />
|
<ComponentRef Id="SuwayomiLauncherBAT" />
|
||||||
<ComponentRef Id="ProgramMenuDir" />
|
<ComponentRef Id="ProgramMenuDir" />
|
||||||
<ComponentGroupRef Id="electron" />
|
<ComponentGroupRef Id="electron" />
|
||||||
|
<ComponentGroupRef Id="natives" />
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
<Icon Id="Suwayomi.ico" SourceFile="$(var.Icon)" />
|
<Icon Id="Suwayomi.ico" SourceFile="$(var.Icon)" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
export LD_PRELOAD="/usr/share/java/suwayomi-server/bin/catch_abort.so"
|
export LD_PRELOAD="/usr/share/java/suwayomi-server/bin/catch_abort.so"
|
||||||
|
cd /usr/share/java/suwayomi-server/
|
||||||
|
|
||||||
if [ -z "$DISPLAY" ] && command -v Xvfb >/dev/null; then
|
if [ -z "$DISPLAY" ] && command -v Xvfb >/dev/null; then
|
||||||
echo "-- START: Spawning X server using xvfb-run --"
|
echo "-- START: Spawning X server using xvfb-run --"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
exec ./jre/bin/java -jar ./Suwayomi-Launcher.jar
|
exec ./jre/bin/java --add-exports=java.desktop/sun.awt=ALL-UNNAMED -jar ./Suwayomi-Launcher.jar "$@"
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Immediately bail out if any command fails:
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Suwayomi data location inside the container: /home/suwayomi/.local/share/Tachidesk"
|
||||||
|
|
||||||
|
# make sure the server.conf file exists
|
||||||
|
/home/suwayomi/create_server_conf.sh
|
||||||
|
|
||||||
|
# set default values for environment variables:
|
||||||
|
export TZ="${TZ:-Etc/UTC}"
|
||||||
|
|
||||||
|
# Getting passwords from files
|
||||||
|
if [ -f "${SOCKS_PROXY_PASSWORD_FILE}" ]; then
|
||||||
|
export SOCKS_PROXY_PASSWORD=$(cat "${SOCKS_PROXY_PASSWORD_FILE}")
|
||||||
|
fi
|
||||||
|
if [ -f "${AUTH_PASSWORD_FILE}" ]; then
|
||||||
|
export AUTH_PASSWORD=$(cat "${AUTH_PASSWORD_FILE}")
|
||||||
|
fi
|
||||||
|
if [ -f "${BASIC_AUTH_PASSWORD_FILE}" ]; then
|
||||||
|
export BASIC_AUTH_PASSWORD=$(cat "${BASIC_AUTH_PASSWORD_FILE}")
|
||||||
|
fi
|
||||||
|
if [ -f "${DATABASE_PASSWORD_FILE}" ]; then
|
||||||
|
export DATABASE_PASSWORD=$(cat "${DATABSE_PASSWORD_FILE}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Set default values for settings
|
||||||
|
sed -i -r "s/server.initialOpenInBrowserEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.initialOpenInBrowserEnabled = false #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.systemTrayEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.systemTrayEnabled = false #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# !!! IMPORTANT: make sure to add new env variables to the container.yml workflow step testing the container with providing environment variables
|
||||||
|
|
||||||
|
# Overwrite configuration values with environment variables
|
||||||
|
# the "( #)?" at the end of the regex prevents the settings comment from getting removed
|
||||||
|
# some settings might not have a comment, however, "sed" does not support non matching groups in a regex, thus, an empty
|
||||||
|
# comment will just be created for these settings
|
||||||
|
|
||||||
|
# Server ip and port bindings
|
||||||
|
sed -i -r "s/server.ip = \"(.*?)\"( #)?/server.ip = \"${BIND_IP:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.port = ([0-9]+|[a-zA-Z]+)( #)?/server.port = ${BIND_PORT:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# Socks5 proxy
|
||||||
|
sed -i -r "s/server.socksProxyEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.socksProxyEnabled = ${SOCKS_PROXY_ENABLED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.socksProxyVersion = ([0-9]+|[a-zA-Z]+)( #)?/server.socksProxyVersion = ${SOCKS_PROXY_VERSION:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.socksProxyHost = \"(.*?)\"( #)?/server.socksProxyHost = \"${SOCKS_PROXY_HOST:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.socksProxyPort = \"(.*?)\"( #)?/server.socksProxyPort = \"${SOCKS_PROXY_PORT:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.socksProxyUsername = \"(.*?)\"( #)?/server.socksProxyUsername = \"${SOCKS_PROXY_USERNAME:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.socksProxyPassword = \"(.*?)\"( #)?/server.socksProxyPassword = \"${SOCKS_PROXY_PASSWORD:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# webUI
|
||||||
|
sed -i -r "s/server.webUIEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.webUIEnabled = ${WEB_UI_ENABLED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.webUIFlavor = \"*([a-zA-Z0-9_]+)\"*( #)?/server.webUIFlavor = ${WEB_UI_FLAVOR:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.webUIChannel = \"*([a-zA-Z0-9_]+)\"*( #)?/server.webUIChannel = ${WEB_UI_CHANNEL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.webUIUpdateCheckInterval = ([0-9]+|[a-zA-Z]+)( #)?/server.webUIUpdateCheckInterval = ${WEB_UI_UPDATE_INTERVAL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# downloader
|
||||||
|
sed -i -r "s/server.downloadAsCbz = ([0-9]+|[a-zA-Z]+)( #)?/server.downloadAsCbz = ${DOWNLOAD_AS_CBZ:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoDownloadNewChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.autoDownloadNewChapters = ${AUTO_DOWNLOAD_CHAPTERS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.excludeEntryWithUnreadChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.excludeEntryWithUnreadChapters = ${AUTO_DOWNLOAD_EXCLUDE_UNREAD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoDownloadNewChaptersLimit = ([0-9]+|[a-zA-Z]+)( #)?/server.autoDownloadNewChaptersLimit = ${AUTO_DOWNLOAD_NEW_CHAPTERS_LIMIT:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoDownloadIgnoreReUploads = ([0-9]+|[a-zA-Z]+)( #)?/server.autoDownloadIgnoreReUploads = ${AUTO_DOWNLOAD_IGNORE_REUPLOADS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
if [ -n "$DOWNLOAD_CONVERSIONS" ]; then
|
||||||
|
perl -0777 -i -pe 's/server\.downloadConversions = ({[^#]*?}}?)/server.downloadConversions = $ENV{DOWNLOAD_CONVERSIONS}/gs' /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
fi
|
||||||
|
if [ -n "$SERVE_CONVERSIONS" ]; then
|
||||||
|
perl -0777 -i -pe 's/server\.serveConversions = ({[^#]*?}}?)/server.serveConversions = $ENV{SERVE_CONVERSIONS}/gs' /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# extension repos
|
||||||
|
if [ -n "$EXTENSION_REPOS" ]; then
|
||||||
|
perl -0777 -i -pe 's/server\.extensionRepos = (\[.*?\])/server.extensionRepos = $ENV{EXTENSION_REPOS}/gs' /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# requests
|
||||||
|
sed -i -r "s/server.maxSourcesInParallel = ([0-9]+|[a-zA-Z]+)( #)?/server.maxSourcesInParallel = ${MAX_SOURCES_IN_PARALLEL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# updater
|
||||||
|
sed -i -r "s/server.excludeUnreadChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.excludeUnreadChapters = ${UPDATE_EXCLUDE_UNREAD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.excludeNotStarted = ([0-9]+|[a-zA-Z]+)( #)?/server.excludeNotStarted = ${UPDATE_EXCLUDE_STARTED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.excludeCompleted = ([0-9]+|[a-zA-Z]+)( #)?/server.excludeCompleted = ${UPDATE_EXCLUDE_COMPLETED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.globalUpdateInterval = ([0-9\.]+|[a-zA-Z]+)( #)?/server.globalUpdateInterval = ${UPDATE_INTERVAL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.updateMangas = ([0-9]+|[a-zA-Z]+)( #)?/server.updateMangas = ${UPDATE_MANGA_INFO:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
AUTH_MODE_VAL="${AUTH_MODE:-$( [ "$BASIC_AUTH_ENABLED" = "true" ] && echo 'basic_auth' || echo "" )}"
|
||||||
|
AUTH_USERNAME_VAL="${AUTH_USERNAME:-$BASIC_AUTH_USERNAME}"
|
||||||
|
AUTH_PASSWORD_VAL="${AUTH_PASSWORD:-$BASIC_AUTH_PASSWORD}"
|
||||||
|
sed -i -r "s/server.authMode = \"*([a-zA-Z0-9_]+)\"*( #)?/server.authMode = ${AUTH_MODE_VAL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.authUsername = \"(.*?)\"( #)?/server.authUsername = \"${AUTH_USERNAME_VAL:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.authPassword = \"(.*?)\"( #)?/server.authPassword = \"${AUTH_PASSWORD_VAL:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.jwtAudience = \"(.*?)\"( #)?/server.jwtAudience = \"${JWT_AUDIENCE:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.jwtTokenExpiry = \"(.*?)\"( #)?/server.jwtTokenExpiry = \"${JWT_TOKEN_EXPIRY:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.jwtRefreshExpiry = \"(.*?)\"( #)?/server.jwtRefreshExpiry = \"${JWT_REFRESH_EXPIRY:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
sed -i -r "s/server.basicAuthEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.basicAuthEnabled = ${BASIC_AUTH_ENABLED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.basicAuthUsername = \"(.*?)\"( #)?/server.basicAuthUsername = \"${BASIC_AUTH_USERNAME:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.basicAuthPassword = \"(.*?)\"( #)?/server.basicAuthPassword = \"${BASIC_AUTH_PASSWORD:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# misc
|
||||||
|
sed -i -r "s/server.debugLogsEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.debugLogsEnabled = ${DEBUG:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.maxLogFiles = ([0-9]+|[a-zA-Z]+)( #)?/server.maxLogFiles = ${MAX_LOG_FILES:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.maxLogFileSize = \"(.*?)\"( #)?/server.maxLogFileSize = \"${MAX_LOG_FILE_SIZE:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.maxLogFolderSize = \"(.*?)\"( #)?/server.maxLogFolderSize = \"${MAX_LOG_FOLDER_SIZE:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# backup
|
||||||
|
sed -i -r "s/server.backupTime = \"(.*?)\"( #)?/server.backupTime = \"${BACKUP_TIME:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.backupInterval = ([0-9]+|[a-zA-Z]+)( #)?/server.backupInterval = ${BACKUP_INTERVAL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.backupTTL = ([0-9]+|[a-zA-Z]+)( #)?/server.backupTTL = ${BACKUP_TTL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeManga = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeManga = ${AUTO_BACKUP_INCLUDE_MANGA:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeCategories = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeCategories = ${AUTO_BACKUP_INCLUDE_CATEGORIES:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeChapters = ${AUTO_BACKUP_INCLUDE_CHAPTERS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeTracking = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeTracking = ${AUTO_BACKUP_INCLUDE_TRACKING:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeHistory = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeHistory = ${AUTO_BACKUP_INCLUDE_HISTORY:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeClientData = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeClientData = ${AUTO_BACKUP_INCLUDE_CLIENT_DATA:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.autoBackupIncludeServerSettings = ([0-9]+|[a-zA-Z]+)( #)?/server.autoBackupIncludeServerSettings = ${AUTO_BACKUP_INCLUDE_SERVER_SETTINGS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
|
||||||
|
# cloudflare bypass
|
||||||
|
sed -i -r "s/server.flareSolverrEnabled = ([0-9]+|[a-zA-Z]+)( #)?/server.flareSolverrEnabled = ${FLARESOLVERR_ENABLED:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s|server.flareSolverrUrl = \"(.*?)\"( #)?|server.flareSolverrUrl = \"${FLARESOLVERR_URL:-\1}\" #|" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.flareSolverrTimeout = ([0-9]+|[a-zA-Z]+)( #)?/server.flareSolverrTimeout = ${FLARESOLVERR_TIMEOUT:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.flareSolverrSessionName = \"(.*?)\"( #)?/server.flareSolverrSessionName = \"${FLARESOLVERR_SESSION_NAME:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.flareSolverrSessionTtl = ([0-9]+|[a-zA-Z]+)( #)?/server.flareSolverrSessionTtl = ${FLARESOLVERR_SESSION_TTL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.flareSolverrAsResponseFallback = ([0-9]+|[a-zA-Z]+)( #)?/server.flareSolverrAsResponseFallback = ${FLARESOLVERR_RESPONSE_AS_FALLBACK:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# opds
|
||||||
|
sed -i -r "s/server.opdsUseBinaryFileSizes = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsUseBinaryFileSizes = ${OPDS_USE_BINARY_FILE_SIZES:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsItemsPerPage = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsItemsPerPage = ${OPDS_ITEMS_PER_PAGE:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsEnablePageReadProgress = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsEnablePageReadProgress = ${OPDS_ENABLE_PAGE_READ_PROGRESS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsMarkAsReadOnDownload = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsMarkAsReadOnDownload = ${OPDS_MARK_AS_READ_ON_DOWNLOAD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsShowOnlyUnreadChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsShowOnlyUnreadChapters = ${OPDS_SHOW_ONLY_UNREAD_CHAPTERS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsShowOnlyDownloadedChapters = ([0-9]+|[a-zA-Z]+)( #)?/server.opdsShowOnlyDownloadedChapters = ${OPDS_SHOW_ONLY_DOWNLOADED_CHAPTERS:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsChapterSortOrder = \"*([a-zA-Z0-9_]+)\"*( #)?/server.opdsChapterSortOrder = ${OPDS_CHAPTER_SORT_ORDER:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.opdsCbzMimetype = \"*([a-zA-Z0-9_]+)\"*( #)?/server.opdsCbzMimetype = ${OPDS_CBZ_MIME_TYPE:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# koreader
|
||||||
|
sed -i -r "s/server.koreaderSyncChecksumMethod = \"*([a-zA-Z0-9_]+)\"*( #)?/server.koreaderSyncChecksumMethod = ${KOREADER_SYNC_CHECKSUM_METHOD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.koreaderSyncPercentageTolerance = ([-0-9\.Ee]+)?( #)/server.koreaderSyncPercentageTolerance = ${KOREADER_SYNC_PERCENTAGE_TOLERANCE:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.koreaderSyncStrategyForward = \"*([a-zA-Z0-9_]+)\"*( #)?/server.koreaderSyncStrategyForward = ${KOREADER_SYNC_STRATEGY_FORWARD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.koreaderSyncStrategyBackward = \"*([a-zA-Z0-9_]+)\"*( #)?/server.koreaderSyncStrategyBackward = ${KOREADER_SYNC_STRATEGY_BACKWARD:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
# database
|
||||||
|
sed -i -r "s/server.databaseType = \"*([a-zA-Z0-9_]+)\"*( #)?/server.databaseType = ${DATABASE_TYPE:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s|server.databaseUrl = \"(.*?)\"( #)?|server.databaseUrl = \"${DATABASE_URL:-\1}\" #|" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.databaseUsername = \"(.*?)\"( #)?/server.databaseUsername = \"${DATABASE_USERNAME:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.databasePassword = \"(.*?)\"( #)?/server.databasePassword = \"${DATABASE_PASSWORD:-\1}\" #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
sed -i -r "s/server.useHikariConnectionPool = ([0-9]+|[a-zA-Z]+)( #)?/server.useHikariConnectionPool = ${USE_HIKARI_CONNECTION_POOL:-\1} #/" /home/suwayomi/.local/share/Tachidesk/server.conf
|
||||||
|
|
||||||
|
rm -rf /home/suwayomi/.local/share/Tachidesk/cache/kcef/Singleton*
|
||||||
|
|
||||||
|
if command -v Xvfb >/dev/null; then
|
||||||
|
command="xvfb-run --auto-servernum java"
|
||||||
|
if [ -d /opt/kcef/jcef ]; then
|
||||||
|
# if we have KCEF downloaded in the container, attempt to link it into the data directory where Suwayomi expects it
|
||||||
|
if [ ! -d /home/suwayomi/.local/share/Tachidesk/bin ]; then
|
||||||
|
mkdir -p /home/suwayomi/.local/share/Tachidesk/bin
|
||||||
|
fi
|
||||||
|
if [ ! -d /home/suwayomi/.local/share/Tachidesk/bin/kcef ] && [ ! -L /home/suwayomi/.local/share/Tachidesk/bin/kcef ]; then
|
||||||
|
ln -s /opt/kcef/jcef /home/suwayomi/.local/share/Tachidesk/bin/kcef
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -d /home/suwayomi/.local/share/Tachidesk/bin/kcef ] || [ -L /home/suwayomi/.local/share/Tachidesk/bin/kcef ]; then
|
||||||
|
# make sure all files are always executable. KCEF (and our downloader) ensure this on creation, but if the flag is lost
|
||||||
|
# at some point, CEF will die
|
||||||
|
chmod -R a+x /home/suwayomi/.local/share/Tachidesk/bin/kcef 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
export LD_PRELOAD=/home/suwayomi/.local/share/Tachidesk/bin/kcef/libcef.so
|
||||||
|
else
|
||||||
|
command="java"
|
||||||
|
echo "Suwayomi built without KCEF support, not starting Xvfb"
|
||||||
|
fi
|
||||||
|
if [ -f /opt/catch_abort.so ]; then
|
||||||
|
export LD_PRELOAD="/opt/catch_abort.so $LD_PRELOAD"
|
||||||
|
fi
|
||||||
|
echo "LD_PRELOAD=$LD_PRELOAD"
|
||||||
|
exec $command -Duser.home=/home/suwayomi -jar "/home/suwayomi/startup/tachidesk_latest.jar";
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"stable": "v2.2.2100"
|
||||||
|
}
|
||||||
@@ -52,7 +52,9 @@ dependencies {
|
|||||||
|
|
||||||
// Exposed ORM
|
// Exposed ORM
|
||||||
implementation(libs.bundles.exposed)
|
implementation(libs.bundles.exposed)
|
||||||
|
implementation(libs.postgres)
|
||||||
implementation(libs.h2)
|
implementation(libs.h2)
|
||||||
|
implementation(libs.hikaricp)
|
||||||
|
|
||||||
// Exposed Migrations
|
// Exposed Migrations
|
||||||
implementation(libs.exposed.migrations)
|
implementation(libs.exposed.migrations)
|
||||||
@@ -92,6 +94,9 @@ dependencies {
|
|||||||
// i18n
|
// i18n
|
||||||
implementation(projects.server.i18n)
|
implementation(projects.server.i18n)
|
||||||
|
|
||||||
|
// Settings module
|
||||||
|
implementation(projects.server.serverConfig)
|
||||||
|
|
||||||
// uncomment to test extensions directly
|
// uncomment to test extensions directly
|
||||||
// implementation(fileTree("lib/"))
|
// implementation(fileTree("lib/"))
|
||||||
implementation(kotlin("script-runtime"))
|
implementation(kotlin("script-runtime"))
|
||||||
@@ -102,6 +107,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.cronUtils)
|
implementation(libs.cronUtils)
|
||||||
|
|
||||||
|
implementation(libs.jwt)
|
||||||
|
|
||||||
compileOnly(libs.kte)
|
compileOnly(libs.kte)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +128,15 @@ sourceSets {
|
|||||||
main {
|
main {
|
||||||
resources {
|
resources {
|
||||||
srcDir("src/main/resources")
|
srcDir("src/main/resources")
|
||||||
|
srcDir("build/generated/src/main/resources")
|
||||||
|
}
|
||||||
|
kotlin {
|
||||||
|
srcDir("build/generated/src/main/kotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
resources {
|
||||||
|
srcDir("build/generated/src/test/resources")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,4 +243,16 @@ tasks {
|
|||||||
runKtlintCheckOverMainSourceSet {
|
runKtlintCheckOverMainSourceSet {
|
||||||
mustRunAfter(generateJte)
|
mustRunAfter(generateJte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
dependsOn(":server:server-config-generate:generateSettings")
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
dependsOn(":server:server-config-generate:generateSettings")
|
||||||
|
}
|
||||||
|
|
||||||
|
processTestResources {
|
||||||
|
dependsOn(":server:server-config-generate:generateSettings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"langs":["en","de","es","ja","pl","pt","ta","vi","zh-CN"]}
|
{"langs":["en","de","es","fr","it","ja","pl","pt","ru","ta","vi","zh-CN"]}
|
||||||
@@ -1,88 +1,139 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_search_shortname">Suwayomi OPDS Search</string>
|
<!-- OPDS feed titles and descriptions -->
|
||||||
<string name="opds_search_description">Search manga in the catalog</string>
|
|
||||||
|
|
||||||
<string name="opds_feeds_root">Suwayomi OPDS Catalog</string>
|
<string name="opds_feeds_root">Suwayomi OPDS Catalog</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s Chapters</string>
|
<string name="opds_feeds_manga_chapters">%1$s Chapters</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
||||||
|
|
||||||
<string name="opds_feeds_all_manga_title">All Manga</string>
|
<string name="opds_feeds_explore_title">Explore</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Browse all manga in your library</string>
|
<string name="opds_feeds_explore_entry_content">Explore new series from your sources</string>
|
||||||
|
|
||||||
<string name="opds_feeds_search_results">Search Results</string>
|
<string name="opds_feeds_history_title">History</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Recently read chapters</string>
|
||||||
|
|
||||||
<string name="opds_feeds_sources_title">Sources</string>
|
<string name="opds_feeds_all_series_in_library_title">All Series</string>
|
||||||
<string name="opds_feeds_sources_entry_content">Browse manga by source</string>
|
<string name="opds_feeds_all_series_in_library_entry_content">Browse all series saved in your library</string>
|
||||||
|
|
||||||
|
<string name="opds_feeds_library_sources_title">Sources</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Browse series in your library filtered by source</string>
|
||||||
|
|
||||||
<string name="opds_feeds_categories_title">Categories</string>
|
<string name="opds_feeds_categories_title">Categories</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Browse manga organized by categories</string>
|
<string name="opds_feeds_categories_entry_content">Browse series organized by categories</string>
|
||||||
|
|
||||||
<string name="opds_feeds_genres_title">Genres</string>
|
<string name="opds_feeds_genres_title">Genres</string>
|
||||||
<string name="opds_feeds_genres_entry_content">Browse manga by genre tags</string>
|
<string name="opds_feeds_genres_entry_content">Browse series by genre tags</string>
|
||||||
|
|
||||||
<string name="opds_feeds_status_title">Status</string>
|
<string name="opds_feeds_status_title">Status</string>
|
||||||
<string name="opds_feeds_status_entry_content">Browse manga by publication status</string>
|
<string name="opds_feeds_status_entry_content">Browse series by publication status</string>
|
||||||
|
|
||||||
<string name="opds_feeds_languages_title">Languages</string>
|
<string name="opds_feeds_languages_title">Languages</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Browse manga by content language</string>
|
<string name="opds_feeds_languages_entry_content">Browse series by content language</string>
|
||||||
|
|
||||||
<string name="opds_feeds_library_updates_title">Library Update History</string>
|
<string name="opds_feeds_library_updates_title">Library Update History</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Recently updated chapters from your library</string>
|
<string name="opds_feeds_library_updates_entry_content">Recently updated chapters from your library</string>
|
||||||
|
|
||||||
|
<string name="opds_feeds_search_results_title">Search Results</string>
|
||||||
|
<string name="opds_feeds_sources_title">All Sources</string>
|
||||||
<string name="opds_feeds_category_specific_title">Category: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Category: %1$s</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">Language: %1$s</string>
|
<string name="opds_feeds_language_specific_title">Language: %1$s</string>
|
||||||
<string name="opds_feeds_source_specific_title">Source: %1$s</string>
|
<string name="opds_feeds_source_specific_title">Source: %1$s</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Library - Source: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Source: %1$s - Popular</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Source: %1$s - Latest</string>
|
||||||
|
|
||||||
<string name="opds_error_manga_not_found">Manga with ID %1$d not found</string>
|
<!-- OPDS search texts -->
|
||||||
<string name="opds_error_chapter_not_found">Chapter with index %1$d not found</string>
|
<string name="opds_search_shortname">Suwayomi OPDS Search</string>
|
||||||
|
<string name="opds_search_description">Search for series in the catalog.</string>
|
||||||
|
|
||||||
|
<!-- OPDS errors -->
|
||||||
|
<string name="opds_error_manga_not_found">Series with ID %1$d not found.</string>
|
||||||
|
<string name="opds_error_chapter_not_found">Chapter with index %1$d not found.</string>
|
||||||
|
|
||||||
|
<!-- OPDS facets (Filters and Sorting) -->
|
||||||
<string name="opds_facetgroup_sort_order">Sort Order</string>
|
<string name="opds_facetgroup_sort_order">Sort Order</string>
|
||||||
<string name="opds_facetgroup_read_status">Read Status</string>
|
<string name="opds_facetgroup_filter_read_status">Filter by Read Status</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Filter Content</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filter by Source</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filter by Category</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filter by Status</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filter by Language</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filter by Genre</string>
|
||||||
|
|
||||||
<string name="opds_facet_sort_oldest_first">Oldest First</string>
|
<string name="opds_facet_sort_oldest_first">Oldest First</string>
|
||||||
<string name="opds_facet_sort_newest_first">Newest First</string>
|
<string name="opds_facet_sort_newest_first">Newest First</string>
|
||||||
<string name="opds_facet_sort_date_asc">Date ascending</string>
|
<string name="opds_facet_sort_date_asc">Date ascending</string>
|
||||||
<string name="opds_facet_sort_date_desc">Date descending</string>
|
<string name="opds_facet_sort_date_desc">Date descending</string>
|
||||||
|
<string name="opds_facet_sort_popular">Popular</string>
|
||||||
|
<string name="opds_facet_sort_latest">Latest</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alphabetical A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alphabetical Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Last Read</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Latest Chapter</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Date Added</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Unread chapters</string>
|
||||||
|
|
||||||
|
<string name="opds_facet_filter_all">All</string>
|
||||||
<string name="opds_facet_filter_all_chapters">All Chapters</string>
|
<string name="opds_facet_filter_all_chapters">All Chapters</string>
|
||||||
<string name="opds_facet_filter_unread_only">Unread Only</string>
|
<string name="opds_facet_filter_unread_only">Unread</string>
|
||||||
<string name="opds_facet_filter_read_only">Read Only</string>
|
<string name="opds_facet_filter_read_only">Read</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Downloaded</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Ongoing</string>
|
||||||
|
<string name="opds_facet_filter_completed">Completed</string>
|
||||||
|
|
||||||
<string name="opds_linktitle_view_chapter_details">View Chapter Details & Get Pages</string>
|
<string name="opds_facet_all_sources">All Sources</string>
|
||||||
<string name="opds_linktitle_download_cbz">Download CBZ</string>
|
<string name="opds_facet_all_categories">All Categories</string>
|
||||||
<string name="opds_linktitle_stream_pages">View Pages (Streaming)</string>
|
<string name="opds_facet_all_statuses">All Statuses</string>
|
||||||
<string name="opds_linktitle_chapter_cover">Chapter Cover</string>
|
<string name="opds_facet_all_languages">All Languages</string>
|
||||||
<string name="opds_linktitle_current_page">Current Page</string>
|
<string name="opds_facet_all_genres">All Genres</string>
|
||||||
|
|
||||||
|
<!-- OPDS link texts -->
|
||||||
<string name="opds_linktitle_catalog_root">Catalog Root</string>
|
<string name="opds_linktitle_catalog_root">Catalog Root</string>
|
||||||
<string name="opds_linktitle_search_catalog">Search Catalog</string>
|
<string name="opds_linktitle_search_catalog">Search Catalog</string>
|
||||||
|
<string name="opds_linktitle_first_page">First Page</string>
|
||||||
<string name="opds_linktitle_previous_page">Previous Page</string>
|
<string name="opds_linktitle_previous_page">Previous Page</string>
|
||||||
<string name="opds_linktitle_next_page">Next Page</string>
|
<string name="opds_linktitle_next_page">Next Page</string>
|
||||||
|
<string name="opds_linktitle_last_page">Last Page</string>
|
||||||
<string name="opds_linktitle_self_feed">Current Feed</string>
|
<string name="opds_linktitle_self_feed">Current Feed</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">View on Web</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Read Online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Continue Reading Online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Read Online (Local Progress)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Continue Reading Online (Local Progress)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Read Online (Synced from %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Continue Reading Online (Synced from %1$s)</string>
|
||||||
|
<string name="opds_linktitle_download_cbz">Download CBZ</string>
|
||||||
|
<string name="opds_linktitle_chapter_cover">Chapter Cover</string>
|
||||||
|
<string name="opds_linktitle_view_chapter_details">View Chapter Details & Get Pages</string>
|
||||||
|
|
||||||
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
<!-- OPDS summary and details -->
|
||||||
<string name="opds_chapter_status_read">✅ </string>
|
<string name="opds_chapter_status_read">✅ </string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛ </string>
|
<string name="opds_chapter_status_in_progress">⌛ </string>
|
||||||
<string name="opds_chapter_status_error">⚠️ </string>
|
|
||||||
<string name="opds_chapter_status_unknown">❔ </string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕ </string>
|
<string name="opds_chapter_status_unread">⭕ </string>
|
||||||
|
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐 </string>
|
||||||
|
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">Series: %1$s | %2$s</string>
|
||||||
<string name="opds_chapter_details_scanlator"> | By %1$s</string>
|
<string name="opds_chapter_details_scanlator"> | By %1$s</string>
|
||||||
<string name="opds_chapter_details_progress"> | Progress: %1$d of %2$d</string>
|
<string name="opds_chapter_details_progress"> | Progress: %1$d of %2$d</string>
|
||||||
|
|
||||||
<string name="manga_status_unknown">Unknown</string>
|
<string name="opds_manga_summary_status">Status: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Source: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Language: %1$s</string>
|
||||||
|
|
||||||
|
<!-- Serie status labels -->
|
||||||
<string name="manga_status_ongoing">Ongoing</string>
|
<string name="manga_status_ongoing">Ongoing</string>
|
||||||
<string name="manga_status_completed">Completed</string>
|
<string name="manga_status_completed">Completed</string>
|
||||||
<string name="manga_status_licensed">Licensed</string>
|
<string name="manga_status_licensed">Licensed</string>
|
||||||
<string name="manga_status_publishing_finished">Publishing Finished</string>
|
<string name="manga_status_publishing_finished">Publishing Finished</string>
|
||||||
<string name="manga_status_cancelled">Cancelled</string>
|
<string name="manga_status_cancelled">Cancelled</string>
|
||||||
<string name="manga_status_on_hiatus">On Hiatus</string>
|
<string name="manga_status_on_hiatus">On Hiatus</string>
|
||||||
|
<string name="manga_status_unknown">Unknown</string>
|
||||||
|
|
||||||
<string name="label_error">Error</string>
|
<string name="label_error">Error</string>
|
||||||
<string name="label_version">Version <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
<string name="label_version">Version <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">Close</string>
|
||||||
|
|
||||||
<string name="webview_label_title">Suwayomi WebView</string>
|
<string name="webview_label_title">Suwayomi WebView</string>
|
||||||
<string name="webview_label_disconnected">Disconnected, please refresh</string>
|
<string name="webview_label_disconnected">Disconnected, please refresh</string>
|
||||||
@@ -91,6 +142,9 @@
|
|||||||
<string name="webview_label_init">Initializing... Please wait</string>
|
<string name="webview_label_init">Initializing... Please wait</string>
|
||||||
<string name="webview_label_getstarted">Enter a URL to get started</string>
|
<string name="webview_label_getstarted">Enter a URL to get started</string>
|
||||||
<string name="webview_label_loading">Loading page...</string>
|
<string name="webview_label_loading">Loading page...</string>
|
||||||
|
<string name="webview_label_copy">Copy to Clipboard</string>
|
||||||
|
<string name="webview_label_copy_description">Automatic clipboard copy failed, please use the input below to manually copy the value.</string>
|
||||||
|
<string name="webview_label_login_required">Your configuration requires you to login. Please enter username and password.</string>
|
||||||
<string name="webview_placeholder_url">Enter URL...</string>
|
<string name="webview_placeholder_url">Enter URL...</string>
|
||||||
|
|
||||||
<string name="login_label_title">Suwayomi Login</string>
|
<string name="login_label_title">Suwayomi Login</string>
|
||||||
|
|||||||
@@ -1,47 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_feeds_status_title">Status</string>
|
<string name="opds_feeds_status_title">Status</string>
|
||||||
<string name="opds_linktitle_current_page">Aktuelle Seite</string>
|
|
||||||
<string name="opds_feeds_categories_title">Kategorien</string>
|
<string name="opds_feeds_categories_title">Kategorien</string>
|
||||||
<string name="opds_facet_filter_read_only">Nur gelesen</string>
|
<string name="opds_facet_filter_read_only">Gelesen</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Alle Manga in Bibliothek durchsuchen</string>
|
|
||||||
<string name="opds_feeds_source_specific_title">Quelle: %1$s</string>
|
<string name="opds_feeds_source_specific_title">Quelle: %1$s</string>
|
||||||
<string name="opds_feeds_root">Suwayomi OPDS Katalog</string>
|
<string name="opds_feeds_root">Suwayomi OPDS Katalog</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
||||||
<string name="opds_feeds_all_manga_title">Alle Manga</string>
|
<string name="opds_feeds_sources_title">Alle Quellen</string>
|
||||||
<string name="opds_feeds_search_results">Suchergebnisse</string>
|
|
||||||
<string name="opds_feeds_sources_title">Quellen</string>
|
|
||||||
<string name="opds_feeds_sources_entry_content">Manga nach Quelle durchsuchen</string>
|
|
||||||
<string name="opds_feeds_genres_title">Genres</string>
|
<string name="opds_feeds_genres_title">Genres</string>
|
||||||
<string name="opds_feeds_genres_entry_content">Manga nach Genre durchsuchen</string>
|
<string name="opds_feeds_genres_entry_content">Durchsuche Serien nach Genre</string>
|
||||||
<string name="opds_feeds_status_entry_content">Manga nach Publikationsstatus durchsuchen</string>
|
<string name="opds_feeds_status_entry_content">Durchsuche Serien nach Publikationsstatus</string>
|
||||||
<string name="opds_feeds_languages_title">Sprachen</string>
|
<string name="opds_feeds_languages_title">Sprachen</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Manga nach Inhaltssprache durchsuchen</string>
|
<string name="opds_feeds_languages_entry_content">Durchsuche Serien nach Inhaltssprache</string>
|
||||||
<string name="opds_feeds_library_updates_title">Aktualisierungshistorie der Bibliothek</string>
|
<string name="opds_feeds_library_updates_title">Aktualisierungshistorie der Bibliothek</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Neulich aktualisierte Kapitel aus der Bibliothek</string>
|
<string name="opds_feeds_library_updates_entry_content">Neulich aktualisierte Kapitel aus der Bibliothek</string>
|
||||||
<string name="opds_feeds_category_specific_title">Kategorie: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Kategorie: %1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">Sprache: %1$s</string>
|
<string name="opds_feeds_language_specific_title">Sprache: %1$s</string>
|
||||||
<string name="opds_error_manga_not_found">Manga mit ID %1$d nicht gefunden</string>
|
<string name="opds_error_manga_not_found">Serie mit ID %1$d nicht gefunden.</string>
|
||||||
<string name="opds_facetgroup_sort_order">Sortieren nach</string>
|
<string name="opds_facetgroup_sort_order">Sortieren nach</string>
|
||||||
<string name="opds_facetgroup_read_status">Lese-Status</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">Älteste zuerst</string>
|
<string name="opds_facet_sort_oldest_first">Älteste zuerst</string>
|
||||||
<string name="opds_facet_sort_newest_first">Neuste zuerst</string>
|
<string name="opds_facet_sort_newest_first">Neuste zuerst</string>
|
||||||
<string name="opds_facet_sort_date_asc">Datum aufsteigend</string>
|
<string name="opds_facet_sort_date_asc">Datum aufsteigend</string>
|
||||||
<string name="opds_facet_sort_date_desc">Datum absteigend</string>
|
<string name="opds_facet_sort_date_desc">Datum absteigend</string>
|
||||||
<string name="opds_facet_filter_all_chapters">Alle Kapitel</string>
|
<string name="opds_facet_filter_all_chapters">Alle Kapitel</string>
|
||||||
<string name="opds_facet_filter_unread_only">Nur ungelesen</string>
|
<string name="opds_facet_filter_unread_only">Ungelesen</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">Kapitel Details ansehen & Seiten holen</string>
|
<string name="opds_linktitle_view_chapter_details">Kapitel Details ansehen & Seiten holen</string>
|
||||||
<string name="opds_linktitle_download_cbz">Als CBZ herunterladen</string>
|
<string name="opds_linktitle_download_cbz">Als CBZ herunterladen</string>
|
||||||
<string name="opds_linktitle_stream_pages">Seiten anzeigen (Streaming)</string>
|
|
||||||
<string name="opds_linktitle_search_catalog">Katalog durchsuchen</string>
|
<string name="opds_linktitle_search_catalog">Katalog durchsuchen</string>
|
||||||
<string name="opds_linktitle_previous_page">Vorherige Seite</string>
|
<string name="opds_linktitle_previous_page">Vorherige Seite</string>
|
||||||
<string name="opds_linktitle_next_page">Nächste Seite</string>
|
<string name="opds_linktitle_next_page">Nächste Seite</string>
|
||||||
<string name="opds_linktitle_self_feed">Aktueller Feed</string>
|
<string name="opds_linktitle_self_feed">Aktueller Feed</string>
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_chapter_details_scanlator">| Von %1$s</string>
|
<string name="opds_chapter_details_scanlator">| Von %1$s</string>
|
||||||
<string name="manga_status_unknown">Unbekannt</string>
|
<string name="manga_status_unknown">Unbekannt</string>
|
||||||
@@ -51,15 +43,83 @@
|
|||||||
<string name="manga_status_publishing_finished">Herausgabe abgeschlossen</string>
|
<string name="manga_status_publishing_finished">Herausgabe abgeschlossen</string>
|
||||||
<string name="manga_status_cancelled">Abgebrochen</string>
|
<string name="manga_status_cancelled">Abgebrochen</string>
|
||||||
<string name="manga_status_on_hiatus">Hiatus</string>
|
<string name="manga_status_on_hiatus">Hiatus</string>
|
||||||
<string name="opds_error_chapter_not_found">Kapitel mit Index %1$d nicht gefunden</string>
|
<string name="opds_error_chapter_not_found">Kapitel mit Index %1$d nicht gefunden.</string>
|
||||||
<string name="opds_search_shortname">Suwayomi OPDS Suche</string>
|
<string name="opds_search_shortname">Suwayomi OPDS Suche</string>
|
||||||
<string name="opds_search_description">Manga im Katalog suchen</string>
|
<string name="opds_search_description">Nach Serien im Katalog suchen.</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s Kapitel</string>
|
<string name="opds_feeds_manga_chapters">%1$s Kapitel</string>
|
||||||
<string name="opds_chapter_details_progress">| Fortschritt: %1$d von %2$d</string>
|
<string name="opds_chapter_details_progress">| Fortschritt: %1$d von %2$d</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Manga nach Kategorien organisiert durchsuchen</string>
|
<string name="opds_feeds_categories_entry_content">Durchsuche Serien nach Kategorien organisiert</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">Serie: %1$s | %2$s</string>
|
||||||
<string name="opds_linktitle_chapter_cover">Kapitel Cover</string>
|
<string name="opds_linktitle_chapter_cover">Kapitel Cover</string>
|
||||||
<string name="opds_linktitle_catalog_root">Katalog Root</string>
|
<string name="opds_linktitle_catalog_root">Katalog Root</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
<string name="opds_feeds_explore_title">Entdecken</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Entdecke neue Serien aus deinen Quellen</string>
|
||||||
|
<string name="opds_feeds_history_title">Verlauf</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Zuletzt gelesene Kapitel</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Alle Serien</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Durchsuche alle Serien in deiner Bibliothek</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Quellen</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Durchsuche Serien in deiner Bibliothek, gefiltert nach Quelle</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Suchergebnisse</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Bibliothek - Quelle: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Quelle: %1$s - Beliebt</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Quelle: %1$s - Neueste</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Nach Lesestatus filtern</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Inhalt filtern</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filter nach Quelle</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filter nach Kategorie</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filter nach Status</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filter nach Sprache</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filter nach Genre</string>
|
||||||
|
<string name="opds_facet_sort_popular">Beliebt</string>
|
||||||
|
<string name="opds_facet_sort_latest">Neueste</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alphabetisch A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alphabetisch Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Zuletzt gelesen</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Neuestes Kapitel</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Datum hinzugefügt</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Ungelesene Kapitel</string>
|
||||||
|
<string name="opds_facet_filter_all">Alle</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Heruntergeladen</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Laufend</string>
|
||||||
|
<string name="opds_facet_filter_completed">Abgeschlossen</string>
|
||||||
|
<string name="opds_facet_all_sources">Alle Quellen</string>
|
||||||
|
<string name="opds_facet_all_categories">Alle Kategorien</string>
|
||||||
|
<string name="opds_facet_all_statuses">Alle Status</string>
|
||||||
|
<string name="opds_facet_all_languages">Alle Sprachen</string>
|
||||||
|
<string name="opds_facet_all_genres">Alle Genres</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Im Web ansehen</string>
|
||||||
|
<string name="opds_manga_summary_status">Status: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Quelle: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Sprache: %1$s</string>
|
||||||
|
<string name="label_error">Fehler</string>
|
||||||
|
<string name="label_version">Version <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="webview_label_title">Suwayomi WebView</string>
|
||||||
|
<string name="webview_label_disconnected">Verbindung verloren, bitte lade die Seite neu</string>
|
||||||
|
<string name="webview_label_reversescroll">Maus-Scroll umdrehen</string>
|
||||||
|
<string name="webview_label_bindingshint">Hinweis: Während der Fokus auf dem WebView-Teil liegt, werden keine Tastenkombinationen, inklusive Neu-Laden, vom Browser bearbeitet.</string>
|
||||||
|
<string name="webview_label_init">Initialisierung... Bitte warte</string>
|
||||||
|
<string name="webview_label_getstarted">Gib eine URL ein, um zu beginnen</string>
|
||||||
|
<string name="webview_label_loading">Seite wird geladen...</string>
|
||||||
|
<string name="webview_placeholder_url">URL eingeben...</string>
|
||||||
|
<string name="login_label_title">Suwayomi Login</string>
|
||||||
|
<string name="login_label_username">Benutzername</string>
|
||||||
|
<string name="login_label_password">Passwort</string>
|
||||||
|
<string name="login_label_login">Anmelden</string>
|
||||||
|
<string name="login_placeholder_username">Benutzername eingeben...</string>
|
||||||
|
<string name="login_placeholder_password">Geheim...</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Online lesen</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Online weiter lesen</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Online lesen (mit lokalem Fortschritt)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Online weiter lesen (mit lokalem Fortschritt)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Online lesen (Synchronisiert mit %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Online weiter lesen (Synchronisiert mit %1$s)</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="label_close">Schließen</string>
|
||||||
|
<string name="webview_label_copy">In Zwischenablage kopieren</string>
|
||||||
|
<string name="webview_label_copy_description">Das automatische Ablegen in die Zwischenablage ist fehlgeschlagen, bitte nutze das Feld unten, um manuell zu kopieren.</string>
|
||||||
|
<string name="webview_label_login_required">Deine Konfiguration erfordert die Anmeldung. Bitte gib Benutzername und Passwort ein.</string>
|
||||||
|
<string name="opds_linktitle_first_page">Erste Seite</string>
|
||||||
|
<string name="opds_linktitle_last_page">Letzte Seite</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,46 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_search_shortname">Búsqueda OPDS de Suwayomi</string>
|
<string name="opds_search_shortname">Búsqueda OPDS de Suwayomi</string>
|
||||||
<string name="opds_search_description">Buscar mangas en el catálogo</string>
|
<string name="opds_search_description">Buscar series en el catálogo.</string>
|
||||||
<string name="opds_feeds_root">Catálogo OPDS de Suwayomi</string>
|
<string name="opds_feeds_root">Catálogo OPDS de Suwayomi</string>
|
||||||
<string name="opds_feeds_manga_chapters">Capítulos de %1$s</string>
|
<string name="opds_feeds_manga_chapters">Capítulos de %1$s</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | Detalles de %2$s</string>
|
<string name="opds_feeds_chapter_details">%1$s | Detalles de %2$s</string>
|
||||||
<string name="opds_feeds_all_manga_title">Todos los mangas</string>
|
<string name="opds_feeds_sources_title">Todas las fuentes</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Explorar todos los mangas en tu biblioteca</string>
|
|
||||||
<string name="opds_feeds_search_results">Resultados de búsqueda</string>
|
|
||||||
<string name="opds_feeds_sources_title">Fuentes</string>
|
|
||||||
<string name="opds_feeds_sources_entry_content">Explorar mangas por fuente</string>
|
|
||||||
<string name="opds_feeds_categories_title">Categorías</string>
|
<string name="opds_feeds_categories_title">Categorías</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Explorar mangas organizados por categorías</string>
|
<string name="opds_feeds_categories_entry_content">Explorar series organizadas por categorías</string>
|
||||||
<string name="opds_feeds_genres_title">Géneros</string>
|
<string name="opds_feeds_genres_title">Géneros</string>
|
||||||
<string name="opds_feeds_genres_entry_content">Explorar mangas por etiquetas de género</string>
|
<string name="opds_feeds_genres_entry_content">Explorar series por etiquetas de género</string>
|
||||||
<string name="opds_feeds_status_title">Estado</string>
|
<string name="opds_feeds_status_title">Estado</string>
|
||||||
<string name="opds_feeds_status_entry_content">Explorar mangas por estado de publicación</string>
|
<string name="opds_feeds_status_entry_content">Explorar series por estado de publicación</string>
|
||||||
<string name="opds_feeds_languages_title">Idiomas</string>
|
<string name="opds_feeds_languages_title">Idiomas</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Explorar mangas por idioma del contenido</string>
|
<string name="opds_feeds_languages_entry_content">Explorar series por idioma del contenido</string>
|
||||||
<string name="opds_feeds_library_updates_title">Historial de actualizaciones</string>
|
<string name="opds_feeds_library_updates_title">Historial de actualizaciones</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Capítulos recientemente actualizados de tu biblioteca</string>
|
<string name="opds_feeds_library_updates_entry_content">Capítulos de series actualizadas recientemente de tu biblioteca</string>
|
||||||
<string name="opds_feeds_category_specific_title">Categoría: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Categoría: %1$s</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Género: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Género: %1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">Estado: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Estado: %1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
||||||
<string name="opds_feeds_source_specific_title">Fuente: %1$s</string>
|
<string name="opds_feeds_source_specific_title">Fuente: %1$s</string>
|
||||||
<string name="opds_facetgroup_sort_order">Ordenar por</string>
|
<string name="opds_facetgroup_sort_order">Ordenar por</string>
|
||||||
<string name="opds_facetgroup_read_status">Estado de lectura</string>
|
<string name="opds_error_manga_not_found">Serie con ID %1$d no encontrada.</string>
|
||||||
<string name="opds_error_manga_not_found">Manga con ID %1$d no encontrado</string>
|
<string name="opds_error_chapter_not_found">Capítulo con índice %1$d no encontrado.</string>
|
||||||
<string name="opds_error_chapter_not_found">Capítulo con índice %1$d no encontrado</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">Más antiguos primero</string>
|
<string name="opds_facet_sort_oldest_first">Más antiguos primero</string>
|
||||||
<string name="opds_facet_sort_newest_first">Más recientes primero</string>
|
<string name="opds_facet_sort_newest_first">Más recientes primero</string>
|
||||||
<string name="opds_facet_sort_date_asc">Fecha ascendente</string>
|
<string name="opds_facet_sort_date_asc">Fecha ascendente</string>
|
||||||
<string name="opds_facet_sort_date_desc">Fecha descendente</string>
|
<string name="opds_facet_sort_date_desc">Fecha descendente</string>
|
||||||
<string name="opds_facet_filter_all_chapters">Todos los capítulos</string>
|
<string name="opds_facet_filter_all_chapters">Todos los capítulos</string>
|
||||||
<string name="opds_facet_filter_unread_only">Solo sin leer</string>
|
<string name="opds_facet_filter_unread_only">Sin leer</string>
|
||||||
<string name="opds_facet_filter_read_only">Solo leídos</string>
|
<string name="opds_facet_filter_read_only">Leídos</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">Ver detalles del capítulo y obtener páginas</string>
|
<string name="opds_linktitle_view_chapter_details">Ver detalles del capítulo y obtener páginas</string>
|
||||||
<string name="opds_linktitle_download_cbz">Descargar CBZ</string>
|
<string name="opds_linktitle_download_cbz">Descargar CBZ</string>
|
||||||
<string name="opds_linktitle_stream_pages">Ver páginas (streaming)</string>
|
|
||||||
<string name="opds_linktitle_chapter_cover">Portada del capítulo</string>
|
<string name="opds_linktitle_chapter_cover">Portada del capítulo</string>
|
||||||
<string name="opds_linktitle_current_page">Página actual</string>
|
|
||||||
<string name="opds_linktitle_catalog_root">Raíz del catálogo</string>
|
<string name="opds_linktitle_catalog_root">Raíz del catálogo</string>
|
||||||
<string name="opds_linktitle_search_catalog">Buscar en catálogo</string>
|
<string name="opds_linktitle_search_catalog">Buscar en catálogo</string>
|
||||||
<string name="opds_linktitle_previous_page">Página anterior</string>
|
<string name="opds_linktitle_previous_page">Página anterior</string>
|
||||||
@@ -49,10 +42,8 @@
|
|||||||
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
||||||
<string name="opds_chapter_status_read">✅ </string>
|
<string name="opds_chapter_status_read">✅ </string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛ </string>
|
<string name="opds_chapter_status_in_progress">⌛ </string>
|
||||||
<string name="opds_chapter_status_error">⚠️ </string>
|
|
||||||
<string name="opds_chapter_status_unknown">❔ </string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕ </string>
|
<string name="opds_chapter_status_unread">⭕ </string>
|
||||||
<string name="opds_chapter_details_base">Manga: %1$s | %2$s</string>
|
<string name="opds_chapter_details_base">Serie: %1$s | %2$s</string>
|
||||||
<string name="opds_chapter_details_scanlator"> | Publicado por: %1$s</string>
|
<string name="opds_chapter_details_scanlator"> | Publicado por: %1$s</string>
|
||||||
<string name="opds_chapter_details_progress"> | Progreso: %1$d de %2$d</string>
|
<string name="opds_chapter_details_progress"> | Progreso: %1$d de %2$d</string>
|
||||||
<string name="manga_status_unknown">Desconocido</string>
|
<string name="manga_status_unknown">Desconocido</string>
|
||||||
@@ -62,4 +53,73 @@
|
|||||||
<string name="manga_status_publishing_finished">Publicación finalizada</string>
|
<string name="manga_status_publishing_finished">Publicación finalizada</string>
|
||||||
<string name="manga_status_cancelled">Cancelado</string>
|
<string name="manga_status_cancelled">Cancelado</string>
|
||||||
<string name="manga_status_on_hiatus">En pausa</string>
|
<string name="manga_status_on_hiatus">En pausa</string>
|
||||||
|
<string name="opds_feeds_explore_title">Explorar</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Explorar nuevas series desde tus fuentes</string>
|
||||||
|
<string name="opds_feeds_history_title">Historial</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Capítulos leídos recientemente</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Todas las series</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Explorar todas las series guardadas en tu biblioteca</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Fuentes</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Explorar series en tu biblioteca filtradas por fuente</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Resultados de búsqueda</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Biblioteca - Fuente: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Fuente: %1$s - Populares</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Fuente: %1$s - Recientes</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Filtrar por estado de lectura</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Filtrar por contenido</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filtrar por fuente</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filtrar por categoría</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filtrar por estado de la serie</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filtrar por idioma</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filtrar por género</string>
|
||||||
|
<string name="opds_facet_sort_popular">Populares</string>
|
||||||
|
<string name="opds_facet_sort_latest">Recientes</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alfabético A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alfabético Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Último leído</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Último capítulo</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Fecha agregado</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Capítulos sin leer</string>
|
||||||
|
<string name="opds_facet_filter_all">Todos</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Descargados</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">En curso</string>
|
||||||
|
<string name="opds_facet_filter_completed">Completados</string>
|
||||||
|
<string name="opds_facet_all_sources">Todas las fuentes</string>
|
||||||
|
<string name="opds_facet_all_categories">Todas las categorías</string>
|
||||||
|
<string name="opds_facet_all_statuses">Todos los estados</string>
|
||||||
|
<string name="opds_facet_all_languages">Todos los idiomas</string>
|
||||||
|
<string name="opds_facet_all_genres">Todos los géneros</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Ver en la web</string>
|
||||||
|
<string name="opds_manga_summary_status">Estado: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Fuente: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Idioma: %1$s</string>
|
||||||
|
<string name="webview_label_title">Vista web de Suwayomi</string>
|
||||||
|
<string name="label_error">Error</string>
|
||||||
|
<string name="label_version">Versión <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="webview_label_disconnected">Desconectado, por favor actualizar</string>
|
||||||
|
<string name="webview_label_reversescroll">Desplazamiento invertido</string>
|
||||||
|
<string name="webview_label_bindingshint">Nota: Mientras el foco esté en la parte de vista web, ningún atajo de teclado, incluido actualizar, será manejado por el navegador.</string>
|
||||||
|
<string name="webview_label_init">Inicializando... Por favor espera</string>
|
||||||
|
<string name="webview_label_getstarted">Ingresa una URL para comenzar</string>
|
||||||
|
<string name="webview_label_loading">Cargando página...</string>
|
||||||
|
<string name="webview_placeholder_url">Ingresa URL...</string>
|
||||||
|
<string name="login_label_title">Inicio de sesión de Suwayomi</string>
|
||||||
|
<string name="login_label_username">Nombre de usuario</string>
|
||||||
|
<string name="login_label_password">Contraseña</string>
|
||||||
|
<string name="login_label_login">Iniciar sesión</string>
|
||||||
|
<string name="login_placeholder_username">Escribe nombre de usuario...</string>
|
||||||
|
<string name="login_placeholder_password">Secreto...</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Leer en línea</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Continuar leyendo en línea</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Leer en línea (progreso local)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Continuar leyendo en línea (progreso local)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Leer en línea (sincronizado de %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Continuar leyendo en línea (sincronizado de %1$s)</string>
|
||||||
|
<string name="label_close">Cerrar</string>
|
||||||
|
<string name="webview_label_copy">Copiar al portapapeles</string>
|
||||||
|
<string name="webview_label_copy_description">La copia automática al portapapeles falló, por favor use la entrada de abajo para copiar el valor manualmente.</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="webview_label_login_required">Su configuración requiere que inicie sesión. Introduzca su nombre de usuario y contraseña.</string>
|
||||||
|
<string name="opds_linktitle_first_page">Primera página</string>
|
||||||
|
<string name="opds_linktitle_last_page">Última página</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="opds_feeds_root">Catalogue OPDS Suwayomi</string>
|
||||||
|
<string name="opds_feeds_manga_chapters">%1$s Chapitres</string>
|
||||||
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Détails</string>
|
||||||
|
<string name="opds_feeds_explore_title">Explorer</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Explorez de nouvelles séries provenant de vos sources</string>
|
||||||
|
<string name="opds_feeds_history_title">Historique</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Chapitres récemment lus</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Toutes les séries</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Parcourez toutes les séries enregistrées dans votre bibliothèque</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Sources</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Parcourir les séries de votre bibliothèque filtrées par source</string>
|
||||||
|
<string name="opds_feeds_categories_title">Catégories</string>
|
||||||
|
<string name="opds_feeds_categories_entry_content">Parcourir les séries organisées par catégories</string>
|
||||||
|
<string name="opds_feeds_genres_title">Genres</string>
|
||||||
|
<string name="opds_feeds_genres_entry_content">Parcourir les séries par tags de genre</string>
|
||||||
|
<string name="opds_feeds_status_title">Statut</string>
|
||||||
|
<string name="opds_feeds_status_entry_content">Parcourir les séries par statut de publication</string>
|
||||||
|
<string name="opds_feeds_languages_title">Langues</string>
|
||||||
|
<string name="opds_feeds_languages_entry_content">Parcourir les séries par langue du contenu</string>
|
||||||
|
<string name="opds_feeds_library_updates_title">Historique des mises à jour de la bibliothèque</string>
|
||||||
|
<string name="opds_feeds_library_updates_entry_content">Chapitres récemment mis à jour depuis votre bibliothèque</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Résultats de recherche</string>
|
||||||
|
<string name="opds_feeds_sources_title">Toutes les sources</string>
|
||||||
|
<string name="opds_feeds_category_specific_title">Catégorie : %1$s</string>
|
||||||
|
<string name="opds_feeds_genre_specific_title">Genre : %1$s</string>
|
||||||
|
<string name="opds_feeds_status_specific_title">Statut : %1$s</string>
|
||||||
|
<string name="opds_feeds_language_specific_title">Langue : %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_title">Source : %1$s</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Bibliothèque – Source : %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Source : %1$s - Populaire</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Source : %1$s - Récent</string>
|
||||||
|
<string name="opds_search_shortname">Recherche OPDS Suwayomi</string>
|
||||||
|
<string name="opds_search_description">Rechercher des séries dans le catalogue.</string>
|
||||||
|
<string name="opds_error_manga_not_found">Série avec l\'ID %1$d non trouvée.</string>
|
||||||
|
<string name="opds_error_chapter_not_found">Chapitre avec l\'index %1$d non trouvé.</string>
|
||||||
|
<string name="opds_facetgroup_sort_order">Ordre de tri</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Filtrer par statut de lecture</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Filtrer le contenu</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filtrer par source</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filtrer par catégorie</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filtrer par statut</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filtrer par langue</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filtrer par genre</string>
|
||||||
|
<string name="opds_facet_sort_oldest_first">Anciens en premier</string>
|
||||||
|
<string name="opds_facet_sort_newest_first">Récents en premier</string>
|
||||||
|
<string name="opds_facet_sort_date_asc">Date croissante</string>
|
||||||
|
<string name="opds_facet_sort_date_desc">Date décroissante</string>
|
||||||
|
<string name="opds_facet_sort_popular">Populaire</string>
|
||||||
|
<string name="opds_facet_sort_latest">Récent</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alphabétique A‑Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alphabétique Z‑A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Dernière lecture</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Dernier chapitre</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Date d\'ajout</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Chapitres non lus</string>
|
||||||
|
<string name="opds_facet_filter_all">Tous</string>
|
||||||
|
<string name="opds_facet_filter_all_chapters">Tous les chapitres</string>
|
||||||
|
<string name="opds_facet_filter_unread_only">Non lus</string>
|
||||||
|
<string name="opds_facet_filter_read_only">Lu</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Téléchargé</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">En cours</string>
|
||||||
|
<string name="opds_facet_filter_completed">Achevé</string>
|
||||||
|
<string name="opds_facet_all_sources">Toutes les sources</string>
|
||||||
|
<string name="opds_facet_all_categories">Toutes les catégories</string>
|
||||||
|
<string name="opds_facet_all_statuses">Tous les statuts</string>
|
||||||
|
<string name="opds_facet_all_languages">Toutes les langues</string>
|
||||||
|
<string name="opds_facet_all_genres">Tous les genres</string>
|
||||||
|
<string name="opds_linktitle_catalog_root">Racine du catalogue</string>
|
||||||
|
<string name="opds_linktitle_search_catalog">Rechercher dans le catalogue</string>
|
||||||
|
<string name="opds_linktitle_first_page">Première page</string>
|
||||||
|
<string name="opds_linktitle_previous_page">Page précédente</string>
|
||||||
|
<string name="opds_linktitle_next_page">Page suivante</string>
|
||||||
|
<string name="opds_linktitle_last_page">Dernière page</string>
|
||||||
|
<string name="opds_linktitle_self_feed">Flux actuel</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Voir sur le web</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Lire en ligne</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Continuer la lecture en ligne</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Lire en ligne (progression locale)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Continuer la lecture en ligne (progression locale)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Lire en ligne (synchronisé depuis %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Continuer la lecture en ligne (synchronisé depuis %1$s)</string>
|
||||||
|
<string name="opds_linktitle_download_cbz">Télécharger CBZ</string>
|
||||||
|
<string name="opds_linktitle_chapter_cover">Couverture du chapitre</string>
|
||||||
|
<string name="opds_linktitle_view_chapter_details">Voir les détails du chapitre & obtenir les pages</string>
|
||||||
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="opds_chapter_details_base">Série : %1$s | %2$s</string>
|
||||||
|
<string name="opds_chapter_details_scanlator">| Par %1$s</string>
|
||||||
|
<string name="opds_chapter_details_progress">| Progression : %1$d sur %2$d</string>
|
||||||
|
<string name="opds_manga_summary_status">Statut : %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Source : %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Langue : %1$s</string>
|
||||||
|
<string name="manga_status_ongoing">En cours</string>
|
||||||
|
<string name="manga_status_completed">Achevé</string>
|
||||||
|
<string name="manga_status_licensed">Licencié</string>
|
||||||
|
<string name="manga_status_publishing_finished">Publication terminée</string>
|
||||||
|
<string name="manga_status_cancelled">Annulé</string>
|
||||||
|
<string name="manga_status_on_hiatus">En pause</string>
|
||||||
|
<string name="manga_status_unknown">Inconnu</string>
|
||||||
|
<string name="label_error">Erreur</string>
|
||||||
|
<string name="label_version">Version <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">Fermer</string>
|
||||||
|
<string name="webview_label_title">WebView Suwayomi</string>
|
||||||
|
<string name="webview_label_disconnected">Déconnecté, veuillez actualiser</string>
|
||||||
|
<string name="webview_label_reversescroll">Défilement inversé</string>
|
||||||
|
<string name="webview_label_bindingshint">Remarque : lorsque le focus est sur la partie WebView, aucune touche, y compris actualiser, ne sera gérée par le navigateur.</string>
|
||||||
|
<string name="webview_label_init">Initialisation… Veuillez patienter</string>
|
||||||
|
<string name="webview_label_getstarted">Entrez une URL pour commencer</string>
|
||||||
|
<string name="webview_label_loading">Chargement de la page…</string>
|
||||||
|
<string name="webview_label_copy">Copier dans le presse‑papier</string>
|
||||||
|
<string name="webview_label_copy_description">La copie automatique a échoué, utilisez l\'entrée ci‑dessous pour copier manuellement la valeur.</string>
|
||||||
|
<string name="webview_label_login_required">Votre configuration requiert une connexion. Veuillez entrer nom d\'utilisateur et mot de passe.</string>
|
||||||
|
<string name="webview_placeholder_url">Entrez l\'URL…</string>
|
||||||
|
<string name="login_label_title">Connexion Suwayomi</string>
|
||||||
|
<string name="login_label_username">Nom d\'utilisateur</string>
|
||||||
|
<string name="login_label_password">Mot de passe</string>
|
||||||
|
<string name="login_label_login">Se connecter</string>
|
||||||
|
<string name="login_placeholder_username">Tapez le nom d\'utilisateur…</string>
|
||||||
|
<string name="login_placeholder_password">Secret…</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="opds_feeds_root">Catalogo OPDS di Suwayomi</string>
|
||||||
|
<string name="opds_feeds_manga_chapters">%1$s Capitoli</string>
|
||||||
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Dettagli</string>
|
||||||
|
<string name="opds_feeds_explore_title">Esplora</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Esplora nuove serie dalle tue fonti</string>
|
||||||
|
<string name="opds_feeds_history_title">Cronologia</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Capitoli letti recentemente</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Tutte le serie</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Sfoglia tutte le serie salvate nella tua libreria</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Fonti</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Sfoglia le serie nella tua libreria filtrate per fonte</string>
|
||||||
|
<string name="opds_feeds_categories_title">Categorie</string>
|
||||||
|
<string name="opds_feeds_categories_entry_content">Sfoglia le serie organizzate per categorie</string>
|
||||||
|
<string name="opds_feeds_genres_title">Generi</string>
|
||||||
|
<string name="opds_feeds_genres_entry_content">Sfoglia le serie per etichette di genere</string>
|
||||||
|
<string name="opds_feeds_status_title">Stato</string>
|
||||||
|
<string name="opds_feeds_status_entry_content">Sfoglia le serie per stato di pubblicazione</string>
|
||||||
|
<string name="opds_feeds_languages_title">Lingue</string>
|
||||||
|
<string name="opds_feeds_languages_entry_content">Sfoglia le serie per lingua del contenuto</string>
|
||||||
|
<string name="opds_feeds_library_updates_title">Cronologia Aggiornamento Libreria</string>
|
||||||
|
<string name="opds_feeds_library_updates_entry_content">Capitoli della tua libreria recentemente aggiornati</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Risultati della ricerca</string>
|
||||||
|
<string name="opds_feeds_sources_title">Tutte le fonti</string>
|
||||||
|
<string name="opds_feeds_category_specific_title">Categoria: %1$s</string>
|
||||||
|
<string name="opds_feeds_genre_specific_title">Genere: %1$s</string>
|
||||||
|
<string name="opds_feeds_status_specific_title">Stato: %1$s</string>
|
||||||
|
<string name="opds_feeds_language_specific_title">Lingua: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_title">Fonte: %1$s</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Libreria - Fonte: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Fonte: %1$s - Popolare</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Fonte: %1$s - Ultimi</string>
|
||||||
|
<string name="opds_search_shortname">Ricerca OPDS di Suwayomi</string>
|
||||||
|
<string name="opds_search_description">Cerca una serie nel catalogo.</string>
|
||||||
|
<string name="opds_error_manga_not_found">La serie con ID %1$d non trovata.</string>
|
||||||
|
<string name="opds_error_chapter_not_found">Capitolo con indice %1$d non trovato.</string>
|
||||||
|
<string name="opds_facetgroup_sort_order">Tipo di ordinamento</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Filtra per Stato di Lettura</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Filtra per contenuto</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filtra per fonte</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filtra per categoria</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filtra per stato</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filtra per lingua</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filtra per genere</string>
|
||||||
|
<string name="opds_facet_sort_oldest_first">Prima i più vecchi</string>
|
||||||
|
<string name="opds_facet_sort_newest_first">Prima i più nuovi</string>
|
||||||
|
<string name="opds_facet_sort_date_asc">Data crescente</string>
|
||||||
|
<string name="opds_facet_sort_date_desc">Data decrescente</string>
|
||||||
|
<string name="opds_facet_sort_popular">Popolare</string>
|
||||||
|
<string name="opds_facet_sort_latest">Ultimi</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alfabetico A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alfabetico Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Ultimo Letto</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Ultimo Capitolo</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Data aggiunta</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Capitoli non letti</string>
|
||||||
|
<string name="opds_facet_filter_all">Tutto</string>
|
||||||
|
<string name="opds_facet_filter_all_chapters">Tutti i capitoli</string>
|
||||||
|
<string name="opds_facet_filter_unread_only">Non letti</string>
|
||||||
|
<string name="opds_facet_filter_read_only">Letti</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Scaricati</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">In corso</string>
|
||||||
|
<string name="opds_facet_filter_completed">Completato</string>
|
||||||
|
<string name="opds_facet_all_sources">Tutte le fonti</string>
|
||||||
|
<string name="opds_facet_all_categories">Tutte le categorie</string>
|
||||||
|
<string name="opds_facet_all_statuses">Tutt gli stati</string>
|
||||||
|
<string name="opds_facet_all_languages">Tutte le lingue</string>
|
||||||
|
<string name="opds_facet_all_genres">Tutti i generi</string>
|
||||||
|
<string name="opds_linktitle_catalog_root">Radice del catalogo</string>
|
||||||
|
<string name="opds_linktitle_search_catalog">Cerca nel catalogo</string>
|
||||||
|
<string name="opds_linktitle_first_page">Prima Pagina</string>
|
||||||
|
<string name="opds_linktitle_previous_page">Pagina Precedente</string>
|
||||||
|
<string name="opds_linktitle_next_page">Pagina Successiva</string>
|
||||||
|
<string name="opds_linktitle_last_page">Ultima Pagina</string>
|
||||||
|
<string name="opds_linktitle_self_feed">Feed corrente</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Guarda sul Web</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Leggi online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Continua lettura online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Leggi online (Progressi Locali)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Continua lettura online (Progressi Locali)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Leggi online (Sincronizzato da %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Continua lettura online (Sincronizzato da %1$s)</string>
|
||||||
|
<string name="opds_linktitle_download_cbz">Scarica come CBZ</string>
|
||||||
|
<string name="opds_linktitle_chapter_cover">Copertina Capitolo</string>
|
||||||
|
<string name="opds_linktitle_view_chapter_details">Visualizza i dettagli del capitolo e ottieni le pagine</string>
|
||||||
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="opds_chapter_details_base">Serie: %1$s | %2$s</string>
|
||||||
|
<string name="opds_chapter_details_scanlator">| di %1$s</string>
|
||||||
|
<string name="opds_chapter_details_progress">| Progresso: %1$d of %2$d</string>
|
||||||
|
<string name="opds_manga_summary_status">Stato: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Fonte: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Lingua: %1$s</string>
|
||||||
|
<string name="manga_status_ongoing">In corso</string>
|
||||||
|
<string name="manga_status_completed">Concluso</string>
|
||||||
|
<string name="manga_status_licensed">Licenziato</string>
|
||||||
|
<string name="manga_status_publishing_finished">Pubblicazione Terminata</string>
|
||||||
|
<string name="manga_status_cancelled">Cancellato</string>
|
||||||
|
<string name="manga_status_on_hiatus">In Pausa</string>
|
||||||
|
<string name="manga_status_unknown">Sconosciuto</string>
|
||||||
|
<string name="label_error">Errore</string>
|
||||||
|
<string name="label_version">Versione <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">Chiudi</string>
|
||||||
|
<string name="webview_label_title">WebView di Suwayomi</string>
|
||||||
|
<string name="webview_label_disconnected">Disconnesso, ricarica per favore</string>
|
||||||
|
<string name="webview_label_reversescroll">Scorrimento inverso</string>
|
||||||
|
<string name="webview_label_bindingshint">Nota: finché il focus è nella parte WebView, il browser non gestirà alcuna combinazione di tasti, incluso l\'aggiornamento.</string>
|
||||||
|
<string name="webview_label_init">Inizializzando... Attendere, prego</string>
|
||||||
|
<string name="webview_label_getstarted">Inserisci un URL per iniziare</string>
|
||||||
|
<string name="webview_label_loading">Caricando la pagina...</string>
|
||||||
|
<string name="webview_label_copy">Copia negli Appunti</string>
|
||||||
|
<string name="webview_label_copy_description">La copia automatica negli appunti non è riuscita. Utilizzare il campo sottostante per copiare manualmente il valore.</string>
|
||||||
|
<string name="webview_label_login_required">La tua configurazione richiede l\'accesso. Inserisci nome utente e password.</string>
|
||||||
|
<string name="webview_placeholder_url">Inserisci URL...</string>
|
||||||
|
<string name="login_label_title">Accesso Suwayomi</string>
|
||||||
|
<string name="login_label_username">Nome Utente</string>
|
||||||
|
<string name="login_label_password">Password</string>
|
||||||
|
<string name="login_label_login">Accedi</string>
|
||||||
|
<string name="login_placeholder_username">Digita il nome utente...</string>
|
||||||
|
<string name="login_placeholder_password">Segreto...</string>
|
||||||
|
</resources>
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="opds_linktitle_search_catalog">カタログ検索</string>
|
<string name="opds_linktitle_search_catalog">カタログ検索</string>
|
||||||
<string name="manga_status_unknown">不明</string>
|
<string name="manga_status_unknown">不明</string>
|
||||||
<string name="opds_linktitle_stream_pages">ページを表示 (ストリーミング)</string>
|
|
||||||
<string name="opds_linktitle_next_page">次のページ</string>
|
<string name="opds_linktitle_next_page">次のページ</string>
|
||||||
<string name="opds_search_shortname">Suwayomi OPDS検索</string>
|
<string name="opds_search_shortname">Suwayomi OPDS検索</string>
|
||||||
<string name="opds_feeds_genres_entry_content">ジャンルタグでマンガをブラウズする</string>
|
<string name="opds_feeds_genres_entry_content">ジャンルタグでマンガをブラウズする</string>
|
||||||
@@ -13,8 +12,6 @@
|
|||||||
<string name="opds_feeds_manga_chapters">%1$s 章</string>
|
<string name="opds_feeds_manga_chapters">%1$s 章</string>
|
||||||
<string name="opds_search_description">カタログ内でマンガを検索する</string>
|
<string name="opds_search_description">カタログ内でマンガを検索する</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | 詳細</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | 詳細</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">ライブラリ内のすべてのマンガを閲覧</string>
|
|
||||||
<string name="opds_feeds_sources_entry_content">ソースでマンガをブラウズ</string>
|
|
||||||
<string name="opds_feeds_categories_title">カテゴリー</string>
|
<string name="opds_feeds_categories_title">カテゴリー</string>
|
||||||
<string name="opds_feeds_categories_entry_content">カテゴリー別にマンガを閲覧</string>
|
<string name="opds_feeds_categories_entry_content">カテゴリー別にマンガを閲覧</string>
|
||||||
<string name="opds_feeds_genres_title">ジャンル</string>
|
<string name="opds_feeds_genres_title">ジャンル</string>
|
||||||
@@ -32,19 +29,13 @@
|
|||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||||
<string name="manga_status_on_hiatus">休止中</string>
|
<string name="manga_status_on_hiatus">休止中</string>
|
||||||
<string name="opds_error_manga_not_found">ID %1$d のマンガが見つかりません</string>
|
<string name="opds_error_manga_not_found">ID %1$d のマンガが見つかりません</string>
|
||||||
<string name="opds_feeds_root">Suwayomi OPDSカタログ</string>
|
<string name="opds_feeds_root">Suwayomi OPDSカタログ</string>
|
||||||
<string name="opds_facetgroup_read_status">読書状況</string>
|
|
||||||
<string name="opds_feeds_all_manga_title">すべてのマンガ</string>
|
|
||||||
<string name="opds_feeds_search_results">検索結果</string>
|
|
||||||
<string name="opds_feeds_sources_title">ソース</string>
|
<string name="opds_feeds_sources_title">ソース</string>
|
||||||
<string name="opds_linktitle_catalog_root">カタログトップ</string>
|
<string name="opds_linktitle_catalog_root">カタログトップ</string>
|
||||||
<string name="opds_facet_filter_unread_only">未読のみ</string>
|
<string name="opds_facet_filter_unread_only">未読のみ</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_linktitle_current_page">現在のページ</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_feeds_language_specific_title">言語: %1$s</string>
|
<string name="opds_feeds_language_specific_title">言語: %1$s</string>
|
||||||
<string name="opds_feeds_languages_entry_content">コンテンツの言語でマンガを閲覧</string>
|
<string name="opds_feeds_languages_entry_content">コンテンツの言語でマンガを閲覧</string>
|
||||||
@@ -62,4 +53,11 @@
|
|||||||
<string name="manga_status_licensed">正式版</string>
|
<string name="manga_status_licensed">正式版</string>
|
||||||
<string name="manga_status_publishing_finished">連載終了</string>
|
<string name="manga_status_publishing_finished">連載終了</string>
|
||||||
<string name="manga_status_cancelled">打ち切り</string>
|
<string name="manga_status_cancelled">打ち切り</string>
|
||||||
|
<string name="opds_feeds_history_title">履歴</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">最近読んだ章</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">すべてのマンガ</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">ライブラリに保存されたマンガを閲覧</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">ソース</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string>
|
||||||
|
<string name="opds_feeds_search_results_title">検索結果</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="opds_search_description">Wyszukiwanie mangi w katalogu</string>
|
<string name="opds_search_description">Wyszukaj serie w katalogu.</string>
|
||||||
<string name="manga_status_on_hiatus">Zawieszone</string>
|
<string name="manga_status_on_hiatus">Zawieszone</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Gatunek: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Gatunek: %1$s</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Szczegóły</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Szczegóły</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_feeds_library_updates_title">Historia Aktualizacji Biblioteki</string>
|
<string name="opds_feeds_library_updates_title">Historia Aktualizacji Biblioteki</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Przeglądaj mangi uporządkowane według kategorii</string>
|
<string name="opds_feeds_categories_entry_content">Przeglądaj serie uporządkowane według kategorii</string>
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_linktitle_self_feed">Aktualny Kanał</string>
|
<string name="opds_linktitle_self_feed">Aktualny Kanał</string>
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Przeglądaj wszystkie mangi w swojej bibliotece</string>
|
|
||||||
<string name="opds_linktitle_next_page">Następna Strona</string>
|
<string name="opds_linktitle_next_page">Następna Strona</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s Rozdziały</string>
|
<string name="opds_feeds_manga_chapters">%1$s Rozdziały</string>
|
||||||
<string name="opds_feeds_all_manga_title">Wszystkie mangi</string>
|
|
||||||
<string name="opds_search_shortname">Suwayomi Wyszukiwanie OPDS</string>
|
<string name="opds_search_shortname">Suwayomi Wyszukiwanie OPDS</string>
|
||||||
<string name="opds_feeds_root">Suwayomi Katalog OPDS</string>
|
<string name="opds_feeds_root">Suwayomi Katalog OPDS</string>
|
||||||
<string name="opds_feeds_search_results">Wyniki wyszukiwania</string>
|
<string name="opds_feeds_sources_title">Wszystkie Źródła</string>
|
||||||
<string name="opds_feeds_sources_title">Źródła</string>
|
|
||||||
<string name="opds_feeds_sources_entry_content">Przeglądaj mangi według źródła</string>
|
|
||||||
<string name="opds_feeds_genres_title">Gatunki</string>
|
<string name="opds_feeds_genres_title">Gatunki</string>
|
||||||
<string name="opds_feeds_status_title">Status</string>
|
<string name="opds_feeds_status_title">Status</string>
|
||||||
<string name="opds_feeds_languages_title">Języki</string>
|
<string name="opds_feeds_languages_title">Języki</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Przeglądaj mangi według języka treści</string>
|
<string name="opds_feeds_languages_entry_content">Przeglądaj serie według języka treści</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Ostatnio zaktualizowane rozdziały z biblioteki</string>
|
<string name="opds_feeds_library_updates_entry_content">Ostatnio zaktualizowane rozdziały z biblioteki</string>
|
||||||
<string name="opds_feeds_category_specific_title">Kategoria: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Kategoria: %1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||||
@@ -33,19 +27,16 @@
|
|||||||
<string name="opds_error_manga_not_found">Nie znaleziono mangi o identyfikatorze %1$d</string>
|
<string name="opds_error_manga_not_found">Nie znaleziono mangi o identyfikatorze %1$d</string>
|
||||||
<string name="opds_error_chapter_not_found">Nie znaleziono rozdziału z indeksem %1$d</string>
|
<string name="opds_error_chapter_not_found">Nie znaleziono rozdziału z indeksem %1$d</string>
|
||||||
<string name="opds_facetgroup_sort_order">Kolejność Sortowania</string>
|
<string name="opds_facetgroup_sort_order">Kolejność Sortowania</string>
|
||||||
<string name="opds_facetgroup_read_status">Status Czytania</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">Najpierw Najstarszy</string>
|
<string name="opds_facet_sort_oldest_first">Najpierw Najstarszy</string>
|
||||||
<string name="opds_facet_sort_newest_first">Najpierw Najnowszy</string>
|
<string name="opds_facet_sort_newest_first">Najpierw Najnowszy</string>
|
||||||
<string name="opds_facet_sort_date_asc">Data rosnąco</string>
|
<string name="opds_facet_sort_date_asc">Data rosnąco</string>
|
||||||
<string name="opds_facet_sort_date_desc">Data malejąco</string>
|
<string name="opds_facet_sort_date_desc">Data malejąco</string>
|
||||||
<string name="opds_facet_filter_all_chapters">Wszystkie Rozdziały</string>
|
<string name="opds_facet_filter_all_chapters">Wszystkie Rozdziały</string>
|
||||||
<string name="opds_facet_filter_unread_only">Tylko Nieprzeczytane</string>
|
<string name="opds_facet_filter_unread_only">Nieprzeczytane</string>
|
||||||
<string name="opds_facet_filter_read_only">Tylko Przeczytane</string>
|
<string name="opds_facet_filter_read_only">Przeczytane</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">Wyświetl Szczegóły Rozdziału i Pobierz Strony</string>
|
<string name="opds_linktitle_view_chapter_details">Wyświetl Szczegóły Rozdziału i Pobierz Strony</string>
|
||||||
<string name="opds_linktitle_download_cbz">Pobierz CBZ</string>
|
<string name="opds_linktitle_download_cbz">Pobierz CBZ</string>
|
||||||
<string name="opds_linktitle_stream_pages">Wyświetlanie Stron (Strumieniowanie)</string>
|
|
||||||
<string name="opds_linktitle_chapter_cover">Okładka Rozdziału</string>
|
<string name="opds_linktitle_chapter_cover">Okładka Rozdziału</string>
|
||||||
<string name="opds_linktitle_current_page">Bieżąca Strona</string>
|
|
||||||
<string name="opds_linktitle_catalog_root">Katalog Główny</string>
|
<string name="opds_linktitle_catalog_root">Katalog Główny</string>
|
||||||
<string name="opds_linktitle_search_catalog">Katalog Wyszukiwania</string>
|
<string name="opds_linktitle_search_catalog">Katalog Wyszukiwania</string>
|
||||||
<string name="opds_linktitle_previous_page">Poprzednia Strona</string>
|
<string name="opds_linktitle_previous_page">Poprzednia Strona</string>
|
||||||
@@ -60,6 +51,29 @@
|
|||||||
<string name="manga_status_publishing_finished">Publikacja Zakończona</string>
|
<string name="manga_status_publishing_finished">Publikacja Zakończona</string>
|
||||||
<string name="manga_status_cancelled">Anulowano</string>
|
<string name="manga_status_cancelled">Anulowano</string>
|
||||||
<string name="opds_feeds_categories_title">Kategorie</string>
|
<string name="opds_feeds_categories_title">Kategorie</string>
|
||||||
<string name="opds_feeds_genres_entry_content">Przeglądaj mangi według tagów gatunku</string>
|
<string name="opds_feeds_genres_entry_content">Przeglądaj serie według tagów gatunku</string>
|
||||||
<string name="opds_feeds_status_entry_content">Przeglądaj mangi według statusu publikacji</string>
|
<string name="opds_feeds_status_entry_content">Przeglądaj serie według statusu publikacji</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Źródło: %1$s - Popularne</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Biblioteka - Źródło: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Źródło: %1$s - Ostatnie</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Wyniki Wyszukiwania</string>
|
||||||
|
<string name="opds_feeds_history_title">Historia</string>
|
||||||
|
<string name="opds_feeds_explore_title">Odkrywaj</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Odkryj nowe serie ze swoich źródeł</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Ostatnio przeczytane rozdziały</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Wszystkie serie</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Przeglądaj wszystkie serie zapisane w bibliotece</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Źródła</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Przeglądaj serie w swojej bibliotece filtrowane według źródła</string>
|
||||||
|
<string name="opds_facet_sort_popular">Popularność</string>
|
||||||
|
<string name="opds_facet_sort_latest">Najnowsze</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alfabetycznie od A do Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alfabetycznie Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Ostatnio czytane</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Najnowszy rozdział</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Data dodania</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Nieprzeczytane rozdziały</string>
|
||||||
|
<string name="opds_facet_filter_all">Wszystkie</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Pobrane</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Trwające</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,65 +1,125 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_search_description">Pesquisar mangá no catálogo</string>
|
<string name="opds_search_description">Busque por séries no catálogo.</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s Capítulos</string>
|
<string name="opds_feeds_manga_chapters">Capítulos de %1$s</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Detalhes</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Detalhes</string>
|
||||||
<string name="opds_feeds_all_manga_title">Todos os Mangás</string>
|
|
||||||
<string name="opds_feeds_search_results">Resultados da pesquisa</string>
|
|
||||||
<string name="opds_feeds_categories_title">Categorias</string>
|
<string name="opds_feeds_categories_title">Categorias</string>
|
||||||
<string name="opds_feeds_genres_title">Gêneros</string>
|
<string name="opds_feeds_genres_title">Gêneros</string>
|
||||||
<string name="opds_feeds_languages_title">Idiomas</string>
|
<string name="opds_feeds_languages_title">Idiomas</string>
|
||||||
<string name="opds_feeds_category_specific_title">Categoria: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Categoria: %1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
||||||
<string name="opds_feeds_source_specific_title">Fonte: %1$s</string>
|
<string name="opds_feeds_source_specific_title">Fonte: %1$s</string>
|
||||||
<string name="opds_feeds_sources_title">Fontes</string>
|
<string name="opds_feeds_sources_title">Todas as Fontes</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Gênero: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Gênero: %1$s</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Explorar todos mangás na sua biblioteca</string>
|
<string name="opds_feeds_categories_entry_content">Navegue por séries organizadas por categorias</string>
|
||||||
<string name="opds_feeds_sources_entry_content">Explorar mangás por fonte</string>
|
<string name="opds_feeds_genres_entry_content">Navegue por séries por tags de gênero</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Explorar mangás organizados por categorias</string>
|
|
||||||
<string name="opds_feeds_genres_entry_content">Explorar mangás por marcadores de gênero</string>
|
|
||||||
<string name="opds_feeds_status_title">Status</string>
|
<string name="opds_feeds_status_title">Status</string>
|
||||||
<string name="opds_feeds_status_entry_content">Explorar mangás por status de publicação</string>
|
<string name="opds_feeds_status_entry_content">Navegue por séries por status de publicação</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Explorar mangás por idioma do conteúdo</string>
|
<string name="opds_feeds_languages_entry_content">Navegue por séries por idioma do conteúdo</string>
|
||||||
<string name="opds_feeds_library_updates_title">Histórico de atualização da biblioteca</string>
|
<string name="opds_feeds_library_updates_title">Histórico de Atualizações da Biblioteca</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Capítulos de sua biblioteca recentemente atualizados</string>
|
<string name="opds_feeds_library_updates_entry_content">Capítulos atualizados recentemente da sua biblioteca</string>
|
||||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||||
<string name="opds_feeds_root">Catálogo OPDS do Suwayomi</string>
|
<string name="opds_feeds_root">Catálogo OPDS Suwayomi</string>
|
||||||
<string name="opds_search_shortname">Pesquisa no Suwayomi OPDS</string>
|
<string name="opds_search_shortname">Busca OPDS Suwayomi</string>
|
||||||
<string name="opds_error_manga_not_found">Manga com ID %1$d não encontrado</string>
|
<string name="opds_error_manga_not_found">Série com ID %1$d não encontrada.</string>
|
||||||
<string name="opds_error_chapter_not_found">Capítulo com índice %1$d não encontrado</string>
|
<string name="opds_error_chapter_not_found">Capítulo com índice %1$d não encontrado.</string>
|
||||||
<string name="opds_facetgroup_sort_order">Ordenar por</string>
|
<string name="opds_facetgroup_sort_order">Ordem de Classificação</string>
|
||||||
<string name="opds_facetgroup_read_status">Status de leitura</string>
|
<string name="opds_facet_sort_oldest_first">Mais Antigos Primeiro</string>
|
||||||
<string name="opds_facet_sort_oldest_first">O mais velho primeiro</string>
|
<string name="opds_facet_sort_newest_first">Mais Novos Primeiro</string>
|
||||||
<string name="opds_facet_sort_newest_first">O mais novo primeiro</string>
|
<string name="opds_facet_sort_date_asc">Data crescente</string>
|
||||||
<string name="opds_facet_sort_date_asc">Data ascendente</string>
|
<string name="opds_facet_sort_date_desc">Data decrescente</string>
|
||||||
<string name="opds_facet_sort_date_desc">Data descendente</string>
|
<string name="opds_facet_filter_all_chapters">Todos os Capítulos</string>
|
||||||
<string name="opds_facet_filter_all_chapters">Todos os capítulos</string>
|
<string name="opds_facet_filter_unread_only">Não lidos</string>
|
||||||
<string name="opds_facet_filter_unread_only">Somente não lidos</string>
|
<string name="opds_facet_filter_read_only">Lidos</string>
|
||||||
<string name="opds_facet_filter_read_only">Somente lidos</string>
|
<string name="opds_linktitle_download_cbz">Baixar CBZ</string>
|
||||||
<string name="opds_linktitle_download_cbz">Baixar como CBZ</string>
|
<string name="opds_linktitle_view_chapter_details">Ver Detalhes do Capítulo e Obter Páginas</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">Ver detalhes do capítulo & Obter Páginas</string>
|
<string name="opds_linktitle_chapter_cover">Capa do Capítulo</string>
|
||||||
<string name="opds_linktitle_stream_pages">Ver Páginas (Streaming)</string>
|
<string name="opds_linktitle_catalog_root">Raiz do Catálogo</string>
|
||||||
<string name="opds_linktitle_chapter_cover">Capa do capítulo</string>
|
<string name="opds_linktitle_search_catalog">Buscar no Catálogo</string>
|
||||||
<string name="opds_linktitle_current_page">Página atual</string>
|
<string name="opds_linktitle_previous_page">Página Anterior</string>
|
||||||
<string name="opds_linktitle_catalog_root">Raiz do catálogo</string>
|
<string name="opds_linktitle_next_page">Próxima Página</string>
|
||||||
<string name="opds_linktitle_search_catalog">Pesquisar no catálogo</string>
|
|
||||||
<string name="opds_linktitle_previous_page">Página anterior</string>
|
|
||||||
<string name="opds_linktitle_next_page">Página seguinte</string>
|
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_linktitle_self_feed">Feed atual</string>
|
<string name="opds_linktitle_self_feed">Feed Atual</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">Série: %1$s | %2$s</string>
|
||||||
<string name="opds_chapter_details_progress">| Progresso: %1$d de %2$d</string>
|
<string name="opds_chapter_details_progress">| Progresso: %1$d de %2$d</string>
|
||||||
<string name="manga_status_unknown">Desconhecido</string>
|
<string name="manga_status_unknown">Desconhecido</string>
|
||||||
<string name="manga_status_ongoing">Em andamento</string>
|
<string name="manga_status_ongoing">Em Andamento</string>
|
||||||
<string name="manga_status_completed">Completo</string>
|
<string name="manga_status_completed">Concluído</string>
|
||||||
<string name="manga_status_licensed">Licenciado</string>
|
<string name="manga_status_licensed">Licenciado</string>
|
||||||
<string name="manga_status_publishing_finished">Publicação concluída</string>
|
<string name="manga_status_publishing_finished">Publicação Finalizada</string>
|
||||||
<string name="manga_status_cancelled">Cancelado</string>
|
<string name="manga_status_cancelled">Cancelado</string>
|
||||||
<string name="manga_status_on_hiatus">Em Hiatus</string>
|
<string name="manga_status_on_hiatus">Em Hiato</string>
|
||||||
<string name="opds_chapter_details_scanlator">| Por %1$s</string>
|
<string name="opds_chapter_details_scanlator">| Por %1$s</string>
|
||||||
|
<string name="opds_feeds_explore_title">Explorar</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Explore novas séries de suas fontes</string>
|
||||||
|
<string name="opds_feeds_history_title">Histórico</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Capítulos lidos recentemente</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Todas as Séries</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Navegue por todas as séries salvas em sua biblioteca</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Fontes</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Navegue por séries em sua biblioteca filtradas por fonte</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Resultados da Busca</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Biblioteca - Fonte: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Fonte: %1$s - Popular</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Fonte: %1$s - Mais Recente</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Filtrar por Status de Leitura</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Filtrar Conteúdo</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Filtrar por Fonte</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Filtrar por Categoria</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Filtrar por Status</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Filtrar por Idioma</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Filtrar por Gênero</string>
|
||||||
|
<string name="opds_facet_sort_popular">Popular</string>
|
||||||
|
<string name="opds_facet_sort_latest">Mais Recente</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Alfabética A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Alfabética Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Lido por Último</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Capítulo Mais Recente</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Data de Adição</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Capítulos não lidos</string>
|
||||||
|
<string name="opds_facet_filter_all">Todos</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Baixados</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Em andamento</string>
|
||||||
|
<string name="opds_facet_filter_completed">Concluídos</string>
|
||||||
|
<string name="opds_facet_all_sources">Todas as Fontes</string>
|
||||||
|
<string name="opds_facet_all_categories">Todas as Categorias</string>
|
||||||
|
<string name="opds_facet_all_statuses">Todos os Status</string>
|
||||||
|
<string name="opds_facet_all_languages">Todos os Idiomas</string>
|
||||||
|
<string name="opds_facet_all_genres">Todos os Gêneros</string>
|
||||||
|
<string name="opds_linktitle_first_page">Primeira Página</string>
|
||||||
|
<string name="opds_linktitle_last_page">Última Página</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Ver na Web</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Ler Online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Continuar Lendo Online</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Ler Online (Progresso Local)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Continuar Lendo Online (Progresso Local)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Ler Online (Sincronizado de %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Continuar Lendo Online (Sincronizado de %1$s)</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="opds_manga_summary_status">Status: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Fonte: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Idioma: %1$s</string>
|
||||||
|
<string name="label_error">Erro</string>
|
||||||
|
<string name="label_version">Versão <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">Fechar</string>
|
||||||
|
<string name="webview_label_title">WebView Suwayomi</string>
|
||||||
|
<string name="webview_label_disconnected">Desconectado, por favor, atualize</string>
|
||||||
|
<string name="webview_label_reversescroll">Rolagem Reversa</string>
|
||||||
|
<string name="webview_label_bindingshint">Nota: Enquanto o foco estiver na parte do WebView, nenhum atalho de teclado, incluindo o de atualizar, será gerenciado pelo navegador.</string>
|
||||||
|
<string name="webview_label_init">Inicializando... Por favor, aguarde</string>
|
||||||
|
<string name="webview_label_getstarted">Insira uma URL para começar</string>
|
||||||
|
<string name="webview_label_loading">Carregando página...</string>
|
||||||
|
<string name="webview_label_copy">Copiar para a Área de Transferência</string>
|
||||||
|
<string name="webview_label_copy_description">A cópia automática para a área de transferência falhou. Por favor, use o campo abaixo para copiar o valor manualmente.</string>
|
||||||
|
<string name="webview_label_login_required">Sua configuração exige que você faça login. Por favor, insira nome de usuário e senha.</string>
|
||||||
|
<string name="webview_placeholder_url">Insira a URL...</string>
|
||||||
|
<string name="login_label_title">Login Suwayomi</string>
|
||||||
|
<string name="login_label_username">Nome de usuário</string>
|
||||||
|
<string name="login_label_password">Senha</string>
|
||||||
|
<string name="login_label_login">Entrar</string>
|
||||||
|
<string name="login_placeholder_username">Digite o nome de usuário...</string>
|
||||||
|
<string name="login_placeholder_password">Segredo...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="opds_feeds_history_title">История</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Источники</string>
|
||||||
|
<string name="opds_feeds_category_specific_title">Категория: %1$s</string>
|
||||||
|
<string name="opds_facetgroup_sort_order">Порядок сортировки</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Результаты поиска</string>
|
||||||
|
<string name="opds_feeds_sources_title">Все источники</string>
|
||||||
|
<string name="opds_feeds_genres_title">Жанры</string>
|
||||||
|
<string name="opds_feeds_categories_title">Категории</string>
|
||||||
|
<string name="opds_feeds_language_specific_title">Язык: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_title">Источник: %1$s</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Библиотека - Источник: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Источник: %1$s - Популярное</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Источник: %1$s - Последнее</string>
|
||||||
|
<string name="opds_feeds_genre_specific_title">Жанр: %1$s</string>
|
||||||
|
<string name="opds_feeds_status_specific_title">Статус: %1$s</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Фильтр по Языку</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Фильтр по Жанру</string>
|
||||||
|
<string name="opds_facet_sort_popular">Популярное</string>
|
||||||
|
<string name="opds_facet_sort_latest">Последнее</string>
|
||||||
|
<string name="opds_facet_filter_all">Все</string>
|
||||||
|
<string name="opds_facet_filter_all_chapters">Все главы</string>
|
||||||
|
<string name="opds_facet_filter_unread_only">Непрочитанное</string>
|
||||||
|
<string name="opds_facet_filter_read_only">Прочитанное</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Онгоинг</string>
|
||||||
|
<string name="opds_facet_filter_completed">Завершенное</string>
|
||||||
|
<string name="opds_facet_all_sources">Все источники</string>
|
||||||
|
<string name="opds_facet_all_categories">Все категории</string>
|
||||||
|
<string name="opds_facet_all_statuses">Все статусы</string>
|
||||||
|
<string name="opds_facet_all_languages">Все языки</string>
|
||||||
|
<string name="opds_facet_all_genres">Все жанры</string>
|
||||||
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="label_error">Ошибка</string>
|
||||||
|
<string name="label_version">Версия <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">Закрыть</string>
|
||||||
|
<string name="login_label_password">Пароль</string>
|
||||||
|
<string name="webview_placeholder_url">Введите URL...</string>
|
||||||
|
<string name="login_label_username">Имя пользователя</string>
|
||||||
|
<string name="login_placeholder_username">Введите имя пользователя...</string>
|
||||||
|
<string name="webview_label_copy">Копировать в буфер обмена</string>
|
||||||
|
<string name="manga_status_cancelled">Отменено</string>
|
||||||
|
<string name="manga_status_unknown">Неизвестно</string>
|
||||||
|
<string name="opds_manga_summary_status">Статус: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Источник: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Язык: %1$s</string>
|
||||||
|
<string name="manga_status_ongoing">Онгоинг</string>
|
||||||
|
<string name="manga_status_completed">Завершен</string>
|
||||||
|
<string name="manga_status_licensed">Лицензировано</string>
|
||||||
|
<string name="manga_status_publishing_finished">Публикация завершена</string>
|
||||||
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Детали</string>
|
||||||
|
<string name="opds_feeds_explore_title">Обзор</string>
|
||||||
|
<string name="opds_feeds_status_title">Статус</string>
|
||||||
|
<string name="opds_feeds_languages_title">Языки</string>
|
||||||
|
<string name="opds_feeds_manga_chapters">%1$s Главы</string>
|
||||||
|
<string name="opds_feeds_root">OPDS-каталог Suwayomi</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Недавно читаемые главы</string>
|
||||||
|
<string name="opds_feeds_library_updates_title">История обновлений библиотеки</string>
|
||||||
|
<string name="opds_feeds_library_updates_entry_content">Недавно обновленные главы из вашей библиотеки</string>
|
||||||
|
<string name="opds_search_shortname">Поиск по Suwayomi</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Фильтр контента</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Фильтр по Источникам</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Фильтр по Категориям</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Фильтр по Статусу</string>
|
||||||
|
<string name="opds_facet_sort_date_asc">Дата (по возрастанию)</string>
|
||||||
|
<string name="opds_facet_sort_date_desc">Дата (по убыванию)</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Символы (по возрастанию)</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Символы (по убыванию)</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Последняя глава</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Дата добавления</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Непрочитанные главы</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Скачанное</string>
|
||||||
|
<string name="opds_linktitle_catalog_root">Корень каталога</string>
|
||||||
|
<string name="opds_linktitle_search_catalog">Поиск по каталогу</string>
|
||||||
|
<string name="opds_linktitle_first_page">Первая страница</string>
|
||||||
|
<string name="opds_linktitle_previous_page">Предыдущая страница</string>
|
||||||
|
<string name="opds_linktitle_next_page">Следующая страница</string>
|
||||||
|
<string name="opds_linktitle_last_page">Последняя страница</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Читать</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Продолжить чтение</string>
|
||||||
|
<string name="opds_linktitle_view_chapter_details">Получить информацию о главе и получить страницы</string>
|
||||||
|
<string name="opds_chapter_details_scanlator">| %1$s</string>
|
||||||
|
<string name="opds_chapter_details_progress">| Прогресс: %1$d из %2$d</string>
|
||||||
|
<string name="webview_label_disconnected">Отключено, пожалуйста обновите</string>
|
||||||
|
<string name="webview_label_loading">Загрузка страницы...</string>
|
||||||
|
<string name="webview_label_getstarted">Введите URL, чтобы начать</string>
|
||||||
|
<string name="webview_label_login_required">В соответствии с вашими настройками вам необходимо войти в систему. Пожалуйста, введите имя пользователя и пароль.</string>
|
||||||
|
<string name="login_label_login">Войти</string>
|
||||||
|
<string name="login_placeholder_password">Пароль...</string>
|
||||||
|
<string name="opds_error_chapter_not_found">Глава №%1$d не найдена.</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Фильтр по Статусу чтения</string>
|
||||||
|
<string name="opds_facet_sort_oldest_first">Сначала старое</string>
|
||||||
|
<string name="opds_facet_sort_newest_first">Сначала новое</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Последнее прочитанное</string>
|
||||||
|
<string name="opds_linktitle_self_feed">Текущая лента</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Показать в Интернете</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Читать (Локальный прогресс)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Продолжить чтение (Локальный прогресс)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Читать (Синхронизировано с %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Продолжить читать (Синхронизировано с %1$s)</string>
|
||||||
|
<string name="opds_linktitle_download_cbz">Скачать CBZ</string>
|
||||||
|
<string name="opds_linktitle_chapter_cover">Обложка главы</string>
|
||||||
|
<string name="manga_status_on_hiatus">На Перерыв</string>
|
||||||
|
<string name="webview_label_title">Веб-просмотр Suwayomi</string>
|
||||||
|
<string name="webview_label_reversescroll">Обратная прокрутка</string>
|
||||||
|
<string name="webview_label_bindingshint">Примечание: Пока основное внимание уделяется веб-просмотру, браузер не будет обрабатывать никакие комбинации клавиш, включая обновление.</string>
|
||||||
|
<string name="webview_label_init">Инициализируется... Пожалуйста подождите</string>
|
||||||
|
<string name="webview_label_copy_description">Не удалось выполнить автоматическое копирование из буфера обмена, пожалуйста, используйте приведенные ниже данные для копирования значения вручную.</string>
|
||||||
|
<string name="login_label_title">Авторизация в Suwayomi</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Изучайте новые тайтлы из ваших источников</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Все тайтлы</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Просматривайте все тайтлы, сохранённые в вашей библиотеке</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Просматривайте все тайтлы, сохранённые в вашей библиотеке, отфильтрованные по источнику</string>
|
||||||
|
<string name="opds_feeds_categories_entry_content">Просматривайте тайтлы, организованные по категориям</string>
|
||||||
|
<string name="opds_feeds_genres_entry_content">Просматривайте тайтлы, по жанрам</string>
|
||||||
|
<string name="opds_feeds_status_entry_content">Просматривайте тайтлы, по статусу публикации</string>
|
||||||
|
<string name="opds_feeds_languages_entry_content">Просматривайте тайтлы, по языку</string>
|
||||||
|
<string name="opds_search_description">Ищите тайтлы в каталоге.</string>
|
||||||
|
<string name="opds_error_manga_not_found">Тайтл с ID %1$d не найден.</string>
|
||||||
|
<string name="opds_chapter_details_base">Тайтл: %1$s | %2$s</string>
|
||||||
|
</resources>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="opds_feeds_search_results">தேடல் முடிவுகள்</string>
|
|
||||||
<string name="opds_feeds_status_title">நிலை</string>
|
<string name="opds_feeds_status_title">நிலை</string>
|
||||||
<string name="opds_feeds_genres_title">வகைகள்</string>
|
<string name="opds_feeds_genres_title">வகைகள்</string>
|
||||||
<string name="opds_feeds_status_entry_content">வெளியீட்டு நிலை மூலம் மங்காவை உலாவுக</string>
|
<string name="opds_feeds_status_entry_content">வெளியீட்டு நிலை மூலம் மங்காவை உலாவுக</string>
|
||||||
@@ -25,9 +24,6 @@
|
|||||||
<string name="opds_search_description">பட்டியலில் மங்காவைத் தேடுங்கள்</string>
|
<string name="opds_search_description">பட்டியலில் மங்காவைத் தேடுங்கள்</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s அத்தியாயங்கள்</string>
|
<string name="opds_feeds_manga_chapters">%1$s அத்தியாயங்கள்</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | விவரங்கள்</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | விவரங்கள்</string>
|
||||||
<string name="opds_feeds_all_manga_title">அனைத்து மங்கா</string>
|
|
||||||
<string name="opds_feeds_all_manga_entry_content">உங்கள் நூலகத்தில் அனைத்து மங்காவையும் உலாவுக</string>
|
|
||||||
<string name="opds_feeds_sources_entry_content">மங்காவை மூலத்தால் உலாவுக</string>
|
|
||||||
<string name="opds_feeds_categories_entry_content">வகைகளால் ஏற்பாடு செய்யப்பட்ட மங்காவை உலாவுக</string>
|
<string name="opds_feeds_categories_entry_content">வகைகளால் ஏற்பாடு செய்யப்பட்ட மங்காவை உலாவுக</string>
|
||||||
<string name="opds_feeds_genres_entry_content">வகை குறிச்சொற்களால் மங்காவை உலாவுக</string>
|
<string name="opds_feeds_genres_entry_content">வகை குறிச்சொற்களால் மங்காவை உலாவுக</string>
|
||||||
<string name="opds_feeds_languages_entry_content">உள்ளடக்க மொழியால் மங்காவை உலாவுக</string>
|
<string name="opds_feeds_languages_entry_content">உள்ளடக்க மொழியால் மங்காவை உலாவுக</string>
|
||||||
@@ -40,7 +36,6 @@
|
|||||||
<string name="opds_error_manga_not_found">அடையாளம் %1$d உடன் மங்கா காணப்படவில்லை</string>
|
<string name="opds_error_manga_not_found">அடையாளம் %1$d உடன் மங்கா காணப்படவில்லை</string>
|
||||||
<string name="opds_error_chapter_not_found">குறியீட்டு %1$d உடன் அத்தியாயம் காணப்படவில்லை</string>
|
<string name="opds_error_chapter_not_found">குறியீட்டு %1$d உடன் அத்தியாயம் காணப்படவில்லை</string>
|
||||||
<string name="opds_facetgroup_sort_order">வரிசைப்படுத்தும் முறை</string>
|
<string name="opds_facetgroup_sort_order">வரிசைப்படுத்தும் முறை</string>
|
||||||
<string name="opds_facetgroup_read_status">நிலையைப் படியுங்கள்</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">முதலில் பழமையானது</string>
|
<string name="opds_facet_sort_oldest_first">முதலில் பழமையானது</string>
|
||||||
<string name="opds_facet_sort_newest_first">புதிய முதல்</string>
|
<string name="opds_facet_sort_newest_first">புதிய முதல்</string>
|
||||||
<string name="opds_facet_sort_date_asc">தேதி ஏறுதல்</string>
|
<string name="opds_facet_sort_date_asc">தேதி ஏறுதல்</string>
|
||||||
@@ -50,15 +45,11 @@
|
|||||||
<string name="opds_facet_filter_read_only">படிக்கவும்</string>
|
<string name="opds_facet_filter_read_only">படிக்கவும்</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">அத்தியாயம் விவரங்களைக் காண்க & பக்கங்களைப் பெறுங்கள்</string>
|
<string name="opds_linktitle_view_chapter_details">அத்தியாயம் விவரங்களைக் காண்க & பக்கங்களைப் பெறுங்கள்</string>
|
||||||
<string name="opds_linktitle_download_cbz">CBZ ஐ பதிவிறக்கவும்</string>
|
<string name="opds_linktitle_download_cbz">CBZ ஐ பதிவிறக்கவும்</string>
|
||||||
<string name="opds_linktitle_stream_pages">பக்கங்களைக் காண்க (ச்ட்ரீமிங்)</string>
|
|
||||||
<string name="opds_linktitle_chapter_cover">அத்தியாயம் கவர்</string>
|
<string name="opds_linktitle_chapter_cover">அத்தியாயம் கவர்</string>
|
||||||
<string name="opds_linktitle_current_page">தற்போதைய பக்கம்</string>
|
|
||||||
<string name="opds_linktitle_self_feed">தற்போதைய ஊட்டம்</string>
|
<string name="opds_linktitle_self_feed">தற்போதைய ஊட்டம்</string>
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||||
<string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string>
|
||||||
|
|||||||
@@ -1,53 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_feeds_search_results">Kết quả tìm kiếm</string>
|
<string name="opds_search_description">Tìm kiếm truyện trong danh mục.</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">Duyệt tất cả manga trong thư viện của bạn</string>
|
|
||||||
<string name="opds_feeds_all_manga_title">Tất cả Manga</string>
|
|
||||||
<string name="opds_search_description">Tìm kiếm manga trong danh mục</string>
|
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Chi tiết</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | Chi tiết</string>
|
||||||
<string name="opds_feeds_categories_title">Danh mục</string>
|
<string name="opds_feeds_categories_title">Danh mục</string>
|
||||||
<string name="opds_feeds_sources_entry_content">Duyệt manga theo nguồn</string>
|
<string name="opds_feeds_categories_entry_content">Duyệt truyện được sắp xếp theo danh mục</string>
|
||||||
<string name="opds_feeds_categories_entry_content">Duyệt manga được sắp xếp theo danh mục</string>
|
|
||||||
<string name="opds_feeds_genres_title">Thể loại</string>
|
<string name="opds_feeds_genres_title">Thể loại</string>
|
||||||
<string name="opds_feeds_root">Suwayomi OPDS Danh mục</string>
|
<string name="opds_feeds_root">Suwayomi OPDS Danh mục</string>
|
||||||
<string name="opds_feeds_sources_title">Nguồn</string>
|
<string name="opds_feeds_sources_title">Tất cả các nguồn</string>
|
||||||
<string name="opds_search_shortname">Suwayomi OPDS Tìm kiếm</string>
|
<string name="opds_search_shortname">Suwayomi OPDS Tìm kiếm</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s Chương</string>
|
<string name="opds_feeds_manga_chapters">%1$s Chương</string>
|
||||||
<string name="opds_feeds_genres_entry_content">Duyệt manga theo thể loại</string>
|
<string name="opds_feeds_genres_entry_content">Duyệt truyện theo thể loại</string>
|
||||||
<string name="opds_feeds_status_title">Trạng thái</string>
|
<string name="opds_feeds_status_title">Trạng thái</string>
|
||||||
<string name="opds_feeds_languages_title">Ngôn ngữ</string>
|
<string name="opds_feeds_languages_title">Ngôn ngữ</string>
|
||||||
<string name="opds_feeds_languages_entry_content">Duyệt manga theo ngôn ngữ</string>
|
<string name="opds_feeds_languages_entry_content">Duyệt truyện theo ngôn ngữ</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">Các chương mới cập nhật gần đây từ thư viện của bạn</string>
|
<string name="opds_feeds_library_updates_entry_content">Các chương mới cập nhật gần đây từ thư viện của bạn</string>
|
||||||
<string name="manga_status_ongoing">Đang tiến hành</string>
|
<string name="manga_status_ongoing">Đang tiến hành</string>
|
||||||
<string name="manga_status_unknown">Không rõ</string>
|
<string name="manga_status_unknown">Không rõ</string>
|
||||||
<string name="manga_status_completed">Đã hoàn thành</string>
|
<string name="manga_status_completed">Đã hoàn thành</string>
|
||||||
<string name="manga_status_on_hiatus">Đang tạm ngưng</string>
|
<string name="manga_status_on_hiatus">Đang tạm ngưng</string>
|
||||||
<string name="opds_feeds_status_entry_content">Duyệt manga theo trạng thái xuất bản</string>
|
<string name="opds_feeds_status_entry_content">Duyệt truyện theo trạng thái xuất bản</string>
|
||||||
<string name="opds_feeds_library_updates_title">Lịch sử cập nhật thư viện</string>
|
<string name="opds_feeds_library_updates_title">Lịch sử cập nhật thư viện</string>
|
||||||
<string name="opds_feeds_category_specific_title">Danh mục: %1$s</string>
|
<string name="opds_feeds_category_specific_title">Danh mục: %1$s</string>
|
||||||
<string name="opds_feeds_genre_specific_title">Thể loại: %1$s</string>
|
<string name="opds_feeds_genre_specific_title">Thể loại: %1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">Trạng thái: %1$s</string>
|
<string name="opds_feeds_status_specific_title">Trạng thái: %1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">Ngôn ngữ: %1$s</string>
|
<string name="opds_feeds_language_specific_title">Ngôn ngữ: %1$s</string>
|
||||||
<string name="opds_feeds_source_specific_title">Nguồn: %1$s</string>
|
<string name="opds_feeds_source_specific_title">Nguồn: %1$s</string>
|
||||||
<string name="opds_error_manga_not_found">Manga có ID %1$d không tìm thấy</string>
|
<string name="opds_error_manga_not_found">Truyện có ID %1$d không tìm thấy.</string>
|
||||||
<string name="opds_error_chapter_not_found">Chương có chỉ mục %1$d không tìm thấy</string>
|
<string name="opds_error_chapter_not_found">Chương có chỉ mục %1$d không tìm thấy.</string>
|
||||||
<string name="opds_facetgroup_sort_order">Thứ tự sắp xếp</string>
|
<string name="opds_facetgroup_sort_order">Thứ tự sắp xếp</string>
|
||||||
<string name="opds_facetgroup_read_status">Đọc trạng thái</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">Cũ nhất trước</string>
|
<string name="opds_facet_sort_oldest_first">Cũ nhất trước</string>
|
||||||
<string name="opds_facet_sort_newest_first">Mới nhất trước</string>
|
<string name="opds_facet_sort_newest_first">Mới nhất trước</string>
|
||||||
<string name="opds_facet_sort_date_asc">Ngày tăng dần</string>
|
<string name="opds_facet_sort_date_asc">Ngày tăng dần</string>
|
||||||
<string name="opds_facet_filter_unread_only">Chỉ chưa đọc</string>
|
<string name="opds_facet_filter_unread_only">Chưa đọc</string>
|
||||||
<string name="opds_facet_filter_read_only">Chỉ đọc</string>
|
<string name="opds_facet_filter_read_only">Đã đọc</string>
|
||||||
<string name="opds_linktitle_stream_pages">Xem trang (Trực tuyến)</string>
|
|
||||||
<string name="opds_linktitle_chapter_cover">Bìa chương</string>
|
<string name="opds_linktitle_chapter_cover">Bìa chương</string>
|
||||||
<string name="opds_linktitle_current_page">Trang hiện tại</string>
|
|
||||||
<string name="opds_linktitle_catalog_root">Danh mục gốc</string>
|
<string name="opds_linktitle_catalog_root">Danh mục gốc</string>
|
||||||
<string name="opds_linktitle_search_catalog">Tìm kiếm danh mục</string>
|
<string name="opds_linktitle_search_catalog">Tìm kiếm danh mục</string>
|
||||||
<string name="opds_linktitle_previous_page">Trang trước</string>
|
<string name="opds_linktitle_previous_page">Trang trước</string>
|
||||||
<string name="opds_linktitle_next_page">Trang tiếp theo</string>
|
<string name="opds_linktitle_next_page">Trang tiếp theo</string>
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_chapter_details_scanlator">| Bởi %1$s</string>
|
<string name="opds_chapter_details_scanlator">| Bởi %1$s</string>
|
||||||
<string name="opds_chapter_details_progress">| Tiến triển: %1$d of %2$d</string>
|
<string name="opds_chapter_details_progress">| Tiến triển: %1$d of %2$d</string>
|
||||||
@@ -60,6 +52,74 @@
|
|||||||
<string name="opds_linktitle_view_chapter_details">Xem Chi Tiết Chương & Nhận Trang</string>
|
<string name="opds_linktitle_view_chapter_details">Xem Chi Tiết Chương & Nhận Trang</string>
|
||||||
<string name="opds_facet_filter_all_chapters">Tất cả các chương</string>
|
<string name="opds_facet_filter_all_chapters">Tất cả các chương</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
<string name="opds_chapter_details_base">Loạt: %1$s | %2$s</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_feeds_explore_title">Khám phá</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">Khám phá loạt truyện từ nguồn của bạn</string>
|
||||||
|
<string name="opds_feeds_history_title">Lịch sử</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">Các chương đã đọc gần đây</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">Nguồn</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">Duyệt các loạt truyện trong thư viện của bạn được lọc theo nguồn</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">Duyệt tất cả các bộ truyện đã lưu trong thư viện của bạn</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">Tất cả các bộ truyện</string>
|
||||||
|
<string name="opds_feeds_search_results_title">Kết quả tìm kiếm</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">Thư viện - Nguồn: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">Nguồn: %1$s - Phổ biển</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">Nguồn: %1$s - Mới nhất</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">Lọc theo Trạng thái đã đọc</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">Lọc nội dung</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">Lọc theo Nguồn</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">Lọc theo danh mục</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">Lọc theo Trạng thái</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">Lọc theo ngôn ngữ</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">Lọc theo thể loại</string>
|
||||||
|
<string name="opds_facet_sort_popular">Phổ biến</string>
|
||||||
|
<string name="opds_facet_sort_latest">Mới nhất</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">Theo thứ tự chữ cái A-Z</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">Theo thứ tự chữ cái Z-A</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">Đọc lần cuối</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">Chương mới nhất</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">Ngày thêm</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">Các chương chưa đọc</string>
|
||||||
|
<string name="opds_facet_filter_all">Tất cả</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">Đã tải xuống</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">Đang tiến hành</string>
|
||||||
|
<string name="opds_facet_filter_completed">Đã hoàn thành</string>
|
||||||
|
<string name="opds_facet_all_sources">Tất cả các nguồn</string>
|
||||||
|
<string name="opds_facet_all_categories">Tất cả các danh mục</string>
|
||||||
|
<string name="opds_facet_all_statuses">Tất cả các trạng thái</string>
|
||||||
|
<string name="opds_facet_all_languages">Tất cả các ngôn ngữ</string>
|
||||||
|
<string name="opds_facet_all_genres">Tất cả các thể loại</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">Xem trên Web</string>
|
||||||
|
<string name="opds_manga_summary_status">Trạng thái: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">Nguồn: %1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">Ngôn ngữ: %1$s</string>
|
||||||
|
<string name="label_error">Lỗi</string>
|
||||||
|
<string name="label_version">Phiên bản <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="webview_label_title">Suwayomi WebView</string>
|
||||||
|
<string name="webview_label_disconnected">Đã ngắt kết nối, vui lòng làm mới</string>
|
||||||
|
<string name="webview_label_reversescroll">Cuộn ngược</string>
|
||||||
|
<string name="webview_label_bindingshint">Lưu ý: Khi tập trung vào phần WebView, trình duyệt sẽ không xử lý bất kỳ phím tắt nào, kể cả phím làm mới.</string>
|
||||||
|
<string name="webview_label_init">Đang khởi tạo... Vui lòng đợi</string>
|
||||||
|
<string name="webview_label_getstarted">Nhập URL để bắt đầu</string>
|
||||||
|
<string name="webview_label_loading">Đang tải trang...</string>
|
||||||
|
<string name="webview_placeholder_url">Nhập URL...</string>
|
||||||
|
<string name="login_label_title">Đăng nhập Suwayomi</string>
|
||||||
|
<string name="login_label_username">Tên người dùng</string>
|
||||||
|
<string name="login_label_password">Mật khẩu</string>
|
||||||
|
<string name="login_label_login">Đăng nhập</string>
|
||||||
|
<string name="login_placeholder_username">Nhập tên người dùng...</string>
|
||||||
|
<string name="login_placeholder_password">Bí mật...</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">Đọc trực tuyến</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">Tiếp tục đọc trực tuyến</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">Đọc trực tuyến (Tiến độ địa phương)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">Tiếp tục đọc trực tuyến (Tiến độ địa phương)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">Đọc trực tuyến (Đã đồng bộ từ %1$s)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">Tiếp tục đọc trực tuyến (Đã đồng bộ từ %1$s)</string>
|
||||||
|
<string name="label_close">Đóng</string>
|
||||||
|
<string name="webview_label_copy">Sao chép vào bảng tạm</string>
|
||||||
|
<string name="webview_label_copy_description">Không thể sao chép bảng tạm tự động, vui lòng sử dụng thông tin bên dưới để sao chép giá trị theo cách thủ công.</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="webview_label_login_required">Cấu hình của bạn yêu cầu bạn phải đăng nhập. Vui lòng nhập tên người dùng và mật khẩu.</string>
|
||||||
|
<string name="opds_linktitle_first_page">Trang đầu</string>
|
||||||
|
<string name="opds_linktitle_last_page">Trang cuối</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="opds_search_shortname">Suwayomi OPDS 搜索</string>
|
<string name="opds_search_shortname">Suwayomi OPDS 搜索</string>
|
||||||
<string name="opds_search_description">在目录中搜索漫画</string>
|
<string name="opds_search_description">在目录中搜索漫画。</string>
|
||||||
<string name="opds_facet_sort_newest_first">最新优先</string>
|
<string name="opds_facet_sort_newest_first">最新优先</string>
|
||||||
<string name="opds_facet_sort_date_asc">日期升序</string>
|
<string name="opds_facet_sort_date_asc">日期升序</string>
|
||||||
<string name="opds_linktitle_chapter_cover">章节封面</string>
|
<string name="opds_linktitle_chapter_cover">章节封面</string>
|
||||||
<string name="opds_linktitle_current_page">当前页面</string>
|
|
||||||
<string name="opds_linktitle_catalog_root">目录根目录</string>
|
<string name="opds_linktitle_catalog_root">目录根目录</string>
|
||||||
<string name="opds_linktitle_search_catalog">搜索目录</string>
|
<string name="opds_linktitle_search_catalog">搜索目录</string>
|
||||||
<string name="opds_linktitle_previous_page">上一页</string>
|
<string name="opds_linktitle_previous_page">上一页</string>
|
||||||
@@ -15,51 +14,112 @@
|
|||||||
<string name="manga_status_unknown">未知</string>
|
<string name="manga_status_unknown">未知</string>
|
||||||
<string name="manga_status_ongoing">连载中</string>
|
<string name="manga_status_ongoing">连载中</string>
|
||||||
<string name="manga_status_completed">已完结</string>
|
<string name="manga_status_completed">已完结</string>
|
||||||
<string name="opds_feeds_search_results">搜索结果</string>
|
<string name="opds_feeds_sources_title">所有来源</string>
|
||||||
<string name="opds_feeds_sources_title">来源</string>
|
|
||||||
<string name="opds_feeds_root">Suwayomi OPDS 目录</string>
|
<string name="opds_feeds_root">Suwayomi OPDS 目录</string>
|
||||||
<string name="opds_feeds_manga_chapters">%1$s 章节</string>
|
<string name="opds_feeds_manga_chapters">%1$s 章节</string>
|
||||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | 详情</string>
|
<string name="opds_feeds_chapter_details">%1$s | %2$s | 详情</string>
|
||||||
<string name="opds_feeds_all_manga_title">所有漫画</string>
|
<string name="opds_feeds_categories_title">分类</string>
|
||||||
<string name="opds_feeds_all_manga_entry_content">浏览您的图书馆中的所有漫画</string>
|
<string name="opds_feeds_categories_entry_content">按组织类别浏览漫画</string>
|
||||||
<string name="opds_feeds_sources_entry_content">按来源浏览漫画</string>
|
|
||||||
<string name="opds_feeds_categories_title">类别</string>
|
|
||||||
<string name="opds_feeds_categories_entry_content">按类别组织的漫画浏览</string>
|
|
||||||
<string name="opds_feeds_genres_title">类型</string>
|
<string name="opds_feeds_genres_title">类型</string>
|
||||||
<string name="opds_feeds_genres_entry_content">按类型标签浏览漫画</string>
|
<string name="opds_feeds_genres_entry_content">按标签类型浏览漫画</string>
|
||||||
<string name="opds_feeds_status_title">状态</string>
|
<string name="opds_feeds_status_title">状态</string>
|
||||||
<string name="opds_feeds_status_entry_content">按出版状态浏览漫画</string>
|
<string name="opds_feeds_status_entry_content">按发布状态浏览漫画</string>
|
||||||
<string name="opds_feeds_languages_title">语言</string>
|
<string name="opds_feeds_languages_title">语言</string>
|
||||||
<string name="opds_feeds_genre_specific_title">类型:%1$s</string>
|
<string name="opds_feeds_genre_specific_title">类型:%1$s</string>
|
||||||
<string name="opds_feeds_languages_entry_content">按内容语言浏览漫画</string>
|
<string name="opds_feeds_languages_entry_content">按语言内容浏览漫画</string>
|
||||||
<string name="opds_feeds_library_updates_title">图书馆更新历史</string>
|
<string name="opds_feeds_library_updates_title">图书馆更新历史</string>
|
||||||
<string name="opds_feeds_library_updates_entry_content">您图书馆中最近更新的章节</string>
|
<string name="opds_feeds_library_updates_entry_content">您图书馆中最近更新的章节</string>
|
||||||
<string name="opds_feeds_category_specific_title">类别:%1$s</string>
|
<string name="opds_feeds_category_specific_title">分类:%1$s</string>
|
||||||
<string name="opds_feeds_status_specific_title">状态:%1$s</string>
|
<string name="opds_feeds_status_specific_title">状态:%1$s</string>
|
||||||
<string name="opds_feeds_language_specific_title">语言:%1$s</string>
|
<string name="opds_feeds_language_specific_title">语言:%1$s</string>
|
||||||
<string name="opds_feeds_source_specific_title">来源:%1$s</string>
|
<string name="opds_feeds_source_specific_title">来源:%1$s</string>
|
||||||
<string name="opds_error_manga_not_found">未找到 ID 为 %1$d 的漫画</string>
|
<string name="opds_error_manga_not_found">未找到 ID 为 %1$d 的漫画。</string>
|
||||||
<string name="opds_error_chapter_not_found">未找到索引为 %1$d 的章节</string>
|
<string name="opds_error_chapter_not_found">未找到索引为 %1$d 的章节。</string>
|
||||||
<string name="opds_facetgroup_sort_order">排序方式</string>
|
<string name="opds_facetgroup_sort_order">排序方式</string>
|
||||||
<string name="opds_facetgroup_read_status">阅读状态</string>
|
|
||||||
<string name="opds_facet_sort_oldest_first">最旧优先</string>
|
<string name="opds_facet_sort_oldest_first">最旧优先</string>
|
||||||
<string name="opds_facet_sort_date_desc">日期降序</string>
|
<string name="opds_facet_sort_date_desc">日期降序</string>
|
||||||
<string name="opds_facet_filter_all_chapters">所有章节</string>
|
<string name="opds_facet_filter_all_chapters">所有章节</string>
|
||||||
<string name="opds_facet_filter_unread_only">仅未读</string>
|
<string name="opds_facet_filter_unread_only">未读</string>
|
||||||
<string name="opds_facet_filter_read_only">仅已读</string>
|
<string name="opds_facet_filter_read_only">已读</string>
|
||||||
<string name="opds_linktitle_view_chapter_details">查看章节详情 & 获取页面</string>
|
<string name="opds_linktitle_view_chapter_details">查看章节详情 & 获取页面</string>
|
||||||
<string name="opds_linktitle_download_cbz">下载 CBZ</string>
|
<string name="opds_linktitle_download_cbz">下载 CBZ</string>
|
||||||
<string name="opds_linktitle_stream_pages">查看页面(流式)</string>
|
|
||||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||||
<string name="opds_chapter_status_read">✅</string>
|
<string name="opds_chapter_status_read">✅</string>
|
||||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||||
<string name="opds_chapter_status_error">⚠️</string>
|
|
||||||
<string name="opds_chapter_status_unknown">❔</string>
|
|
||||||
<string name="opds_chapter_status_unread">⭕</string>
|
<string name="opds_chapter_status_unread">⭕</string>
|
||||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
<string name="opds_chapter_details_base">漫画:%1$s | %2$s</string>
|
||||||
<string name="opds_chapter_details_scanlator">| 由 %1$s</string>
|
<string name="opds_chapter_details_scanlator">| 由 %1$s</string>
|
||||||
<string name="manga_status_licensed">授权</string>
|
<string name="manga_status_licensed">授权</string>
|
||||||
<string name="manga_status_publishing_finished">出版完成</string>
|
<string name="manga_status_publishing_finished">出版完成</string>
|
||||||
<string name="manga_status_cancelled">已取消</string>
|
<string name="manga_status_cancelled">已取消</string>
|
||||||
<string name="manga_status_on_hiatus">暂停中</string>
|
<string name="manga_status_on_hiatus">暂停中</string>
|
||||||
|
<string name="opds_feeds_explore_title">探索</string>
|
||||||
|
<string name="opds_feeds_explore_entry_content">从图源中探索新漫画</string>
|
||||||
|
<string name="opds_feeds_history_title">历史</string>
|
||||||
|
<string name="opds_feeds_history_entry_content">最近阅读的章节</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_title">所有漫画</string>
|
||||||
|
<string name="opds_feeds_all_series_in_library_entry_content">从图书馆中浏览保存的漫画</string>
|
||||||
|
<string name="opds_feeds_library_sources_title">来源</string>
|
||||||
|
<string name="opds_feeds_library_sources_entry_content">按筛选类别浏览漫画</string>
|
||||||
|
<string name="opds_feeds_search_results_title">搜索结果</string>
|
||||||
|
<string name="opds_feeds_source_specific_popular_title">来源: %1$s - 流行</string>
|
||||||
|
<string name="opds_feeds_library_source_specific_title">图书馆 - 来源: %1$s</string>
|
||||||
|
<string name="opds_feeds_source_specific_latest_title">来源:%1$s - 最新</string>
|
||||||
|
<string name="opds_facetgroup_filter_read_status">按阅读状态筛选</string>
|
||||||
|
<string name="opds_facetgroup_filter_content">筛选内容</string>
|
||||||
|
<string name="opds_facetgroup_filter_source">按来源筛选</string>
|
||||||
|
<string name="opds_facetgroup_filter_category">按分类筛选</string>
|
||||||
|
<string name="opds_facetgroup_filter_status">按状态筛选</string>
|
||||||
|
<string name="opds_facetgroup_filter_language">按语言筛选</string>
|
||||||
|
<string name="opds_facetgroup_filter_genre">按类型筛选</string>
|
||||||
|
<string name="opds_facet_sort_popular">流行</string>
|
||||||
|
<string name="opds_facet_sort_latest">最新</string>
|
||||||
|
<string name="opds_facet_sort_alpha_asc">按字母顺序升序 (A-Z)</string>
|
||||||
|
<string name="opds_facet_sort_alpha_desc">按字母顺序降序 (Z-A)</string>
|
||||||
|
<string name="opds_facet_sort_last_read_desc">最后阅读</string>
|
||||||
|
<string name="opds_facet_sort_latest_chapter_desc">最新章节</string>
|
||||||
|
<string name="opds_facet_sort_date_added_desc">添加数据</string>
|
||||||
|
<string name="opds_facet_sort_unread_desc">未阅读章节</string>
|
||||||
|
<string name="opds_facet_filter_all">所有</string>
|
||||||
|
<string name="opds_facet_filter_downloaded">已下载</string>
|
||||||
|
<string name="opds_facet_filter_ongoing">正在进行</string>
|
||||||
|
<string name="opds_facet_filter_completed">已完成</string>
|
||||||
|
<string name="opds_facet_all_sources">所有来源</string>
|
||||||
|
<string name="opds_facet_all_categories">所有类别</string>
|
||||||
|
<string name="opds_facet_all_statuses">所有状态</string>
|
||||||
|
<string name="opds_facet_all_languages">所有语言</string>
|
||||||
|
<string name="opds_facet_all_genres">所有类型</string>
|
||||||
|
<string name="opds_linktitle_first_page">首页</string>
|
||||||
|
<string name="opds_linktitle_last_page">尾页</string>
|
||||||
|
<string name="opds_linktitle_view_on_web">浏览网页</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start">在线阅读</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue">继续在线阅读</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_local">在线阅读(本地进度)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_local">继续在线阅读(本地进度)</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_start_remote">在线阅读(同步于 %1$s )</string>
|
||||||
|
<string name="opds_linktitle_stream_pages_continue_remote">继续在线阅读(同步于 %1$s)</string>
|
||||||
|
<string name="opds_chapter_status_synced">🌐</string>
|
||||||
|
<string name="opds_manga_summary_status">状态:%1$s</string>
|
||||||
|
<string name="opds_manga_summary_source">来源:%1$s</string>
|
||||||
|
<string name="opds_manga_summary_language">语言:%1$s</string>
|
||||||
|
<string name="label_version">版本 <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||||
|
<string name="label_close">关闭</string>
|
||||||
|
<string name="webview_label_title">Suwayomi 内置浏览器</string>
|
||||||
|
<string name="webview_label_disconnected">连接失败,请刷新</string>
|
||||||
|
<string name="webview_label_reversescroll">反向滚动</string>
|
||||||
|
<string name="webview_label_bindingshint">注意:当焦点在 WebView 部分时,包括刷新在内的所有快捷键都不会由浏览器处理。</string>
|
||||||
|
<string name="webview_label_init">初始化……请稍后</string>
|
||||||
|
<string name="webview_label_getstarted">输入 URL 开始</string>
|
||||||
|
<string name="webview_label_loading">正在加载中……</string>
|
||||||
|
<string name="webview_label_copy">复制到剪切板</string>
|
||||||
|
<string name="webview_label_copy_description">自动复制到剪贴板失败,请使用下面的输入框手动复制该值。</string>
|
||||||
|
<string name="webview_label_login_required">您的配置需要登录。请输入用户名和密码。</string>
|
||||||
|
<string name="webview_placeholder_url">输入 URL……</string>
|
||||||
|
<string name="login_label_title">Suwayomi 登陆</string>
|
||||||
|
<string name="login_label_username">用户名</string>
|
||||||
|
<string name="login_label_password">密码</string>
|
||||||
|
<string name="login_label_login">登入</string>
|
||||||
|
<string name="login_placeholder_username">输入用户名…</string>
|
||||||
|
<string name="login_placeholder_password">密匙…</string>
|
||||||
|
<string name="label_error">错误</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
plugins {
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.jvm
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Core Kotlin
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
|
implementation(kotlin("reflect"))
|
||||||
|
|
||||||
|
// Config handling
|
||||||
|
implementation(libs.config)
|
||||||
|
implementation(libs.config4k)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation(libs.slf4japi)
|
||||||
|
implementation(libs.kotlinlogging)
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
implementation(libs.serialization.json)
|
||||||
|
implementation(libs.serialization.protobuf)
|
||||||
|
|
||||||
|
// Depend on server-config module for access to ServerConfig and SettingsRegistry
|
||||||
|
implementation(projects.server.serverConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
register<JavaExec>("generateSettings") {
|
||||||
|
group = "build setup"
|
||||||
|
description = "Generates settings from ServerConfig"
|
||||||
|
|
||||||
|
dependsOn(compileKotlin)
|
||||||
|
|
||||||
|
// Use this module's classpath which includes server-config as dependency
|
||||||
|
classpath = sourceSets.main.get().runtimeClasspath
|
||||||
|
|
||||||
|
mainClass.set("suwayomi.tachidesk.server.settings.generation.SettingsGeneratorKt")
|
||||||
|
|
||||||
|
// Get reference to server project for file paths
|
||||||
|
val serverProject = project(":server")
|
||||||
|
|
||||||
|
// Set working directory to the server module directory
|
||||||
|
workingDir = serverProject.projectDir
|
||||||
|
|
||||||
|
inputs.files(
|
||||||
|
serverProject.sourceSets.main.get().allSource.filter {
|
||||||
|
it.name.contains("ServerConfig") || it.name.contains("Settings")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
outputs.files(
|
||||||
|
serverProject.file("build/generated/src/main/resources/server-reference.conf"),
|
||||||
|
serverProject.file("build/generated/src/test/resources/server-reference.conf"),
|
||||||
|
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt"),
|
||||||
|
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupServerSettings.kt"),
|
||||||
|
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/handlers/BackupSettingsHandler.kt"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
@file:JvmName("SettingsGeneratorKt")
|
||||||
|
|
||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to generate settings files from ServerConfig
|
||||||
|
* This is called by the generateSettingsFiles Gradle task
|
||||||
|
*/
|
||||||
|
fun main() {
|
||||||
|
println("Generating settings files from ServerConfig registry...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set output directories relative to the current working directory (server module)
|
||||||
|
val outputDir = File("build/generated/src/main/resources")
|
||||||
|
val testOutputDir = File("build/generated/src/test/resources")
|
||||||
|
val graphqlOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/graphql/types")
|
||||||
|
val backupSettingsOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models")
|
||||||
|
val backupSettingsHandlerOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/handlers")
|
||||||
|
|
||||||
|
SettingsGenerator.generate(
|
||||||
|
outputDir = outputDir,
|
||||||
|
testOutputDir = testOutputDir,
|
||||||
|
graphqlOutputDir = graphqlOutputDir,
|
||||||
|
backupSettingsOutputDir = backupSettingsOutputDir,
|
||||||
|
backupSettingsHandlerOutputDir = backupSettingsHandlerOutputDir,
|
||||||
|
)
|
||||||
|
|
||||||
|
println("✅ Settings files generation completed successfully!")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ Error generating settings files: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
|
||||||
|
internal fun String.addIndentation(times: Int): String = this.prependIndent(" ".repeat(times))
|
||||||
|
|
||||||
|
object KotlinFileGeneratorHelper {
|
||||||
|
fun createFileHeader(packageName: String): String =
|
||||||
|
buildString {
|
||||||
|
appendLine("@file:Suppress(\"ktlint\")")
|
||||||
|
appendLine()
|
||||||
|
appendLine("/*")
|
||||||
|
appendLine(" * Copyright (C) Contributors to the Suwayomi project")
|
||||||
|
appendLine(" *")
|
||||||
|
appendLine(" * This Source Code Form is subject to the terms of the Mozilla Public")
|
||||||
|
appendLine(" * License, v. 2.0. If a copy of the MPL was not distributed with this")
|
||||||
|
appendLine(" * file, You can obtain one at https://mozilla.org/MPL/2.0/. */")
|
||||||
|
appendLine()
|
||||||
|
appendLine("package $packageName")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createImports(
|
||||||
|
staticImports: List<String>,
|
||||||
|
settings: List<SettingsRegistry.SettingMetadata>,
|
||||||
|
): String =
|
||||||
|
buildString {
|
||||||
|
staticImports.forEach { appendLine("import $it") }
|
||||||
|
settings
|
||||||
|
.mapNotNull { it.typeInfo.imports }
|
||||||
|
.flatten()
|
||||||
|
.distinct()
|
||||||
|
.forEach { appendLine("import $it") }
|
||||||
|
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.text.appendLine
|
||||||
|
|
||||||
|
object SettingsBackupServerSettingsGenerator {
|
||||||
|
fun generate(
|
||||||
|
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||||
|
outputFile: File,
|
||||||
|
) {
|
||||||
|
outputFile.parentFile.mkdirs()
|
||||||
|
|
||||||
|
val settingsToInclude = settings.values
|
||||||
|
|
||||||
|
if (settingsToInclude.isEmpty()) {
|
||||||
|
println("Warning: No settings found to create BackupServerSettings from.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedSettings = settingsToInclude.sortedBy { it.protoNumber }
|
||||||
|
|
||||||
|
outputFile.writeText(
|
||||||
|
buildString {
|
||||||
|
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.manga.impl.backup.proto.models"))
|
||||||
|
writeImports(sortedSettings)
|
||||||
|
writeClass(sortedSettings)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
println("BackupServerSettingsGenerator generated successfully! Total settings: ${settingsToInclude.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||||
|
appendLine(
|
||||||
|
KotlinFileGeneratorHelper.createImports(
|
||||||
|
listOf(
|
||||||
|
"kotlinx.serialization.Serializable",
|
||||||
|
"kotlinx.serialization.protobuf.ProtoNumber",
|
||||||
|
"suwayomi.tachidesk.graphql.types.Settings",
|
||||||
|
),
|
||||||
|
settings,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeClass(sortedSettings: List<SettingsRegistry.SettingMetadata>) {
|
||||||
|
appendLine("@Serializable")
|
||||||
|
appendLine("data class BackupServerSettings(")
|
||||||
|
|
||||||
|
writeSettings(sortedSettings, indentation = 4)
|
||||||
|
|
||||||
|
appendLine(") : Settings")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettings(
|
||||||
|
sortedSettings: List<SettingsRegistry.SettingMetadata>,
|
||||||
|
indentation: Int,
|
||||||
|
) {
|
||||||
|
sortedSettings.forEach { setting ->
|
||||||
|
val deprecated = setting.deprecated
|
||||||
|
if (deprecated != null) {
|
||||||
|
val replaceWithSuffix = deprecated.replaceWith?.let { ", ReplaceWith(\"$it\")" } ?: ""
|
||||||
|
appendLine(
|
||||||
|
"@Deprecated(\"${deprecated.message}\"$replaceWithSuffix)".addIndentation(
|
||||||
|
indentation,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine(
|
||||||
|
"@ProtoNumber(${setting.protoNumber}) override var ${setting.name}: ${getSettingType(setting)}? = null,"
|
||||||
|
.addIndentation(indentation),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSettingType(setting: SettingsRegistry.SettingMetadata): String =
|
||||||
|
setting.typeInfo.backupType
|
||||||
|
?: setting.typeInfo.specificType
|
||||||
|
?: setting.typeInfo.type.simpleName
|
||||||
|
?: throw RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.text.appendLine
|
||||||
|
|
||||||
|
object SettingsBackupSettingsHandlerGenerator {
|
||||||
|
fun generate(
|
||||||
|
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||||
|
outputFile: File,
|
||||||
|
) {
|
||||||
|
outputFile.parentFile.mkdirs()
|
||||||
|
|
||||||
|
val settingsToInclude = settings.values
|
||||||
|
|
||||||
|
if (settingsToInclude.isEmpty()) {
|
||||||
|
println("Warning: No settings found to create BackupServerSettings from.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupedSettings = settingsToInclude.groupBy { it.group }
|
||||||
|
|
||||||
|
outputFile.writeText(
|
||||||
|
buildString {
|
||||||
|
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.manga.impl.backup.proto.handlers"))
|
||||||
|
writeImports(groupedSettings.values.flatten())
|
||||||
|
writeHandler(groupedSettings)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
println("BackupServerSettings generated successfully! Total settings: ${settingsToInclude.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||||
|
appendLine(
|
||||||
|
KotlinFileGeneratorHelper.createImports(
|
||||||
|
listOf(
|
||||||
|
"suwayomi.tachidesk.server.settings.SettingsUpdater",
|
||||||
|
"suwayomi.tachidesk.manga.impl.backup.BackupFlags",
|
||||||
|
"suwayomi.tachidesk.manga.impl.backup.proto.models.BackupServerSettings",
|
||||||
|
"suwayomi.tachidesk.server.serverConfig",
|
||||||
|
"suwayomi.tachidesk.server.settings.SettingsRegistry",
|
||||||
|
),
|
||||||
|
settings,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeHandler(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
appendLine("object BackupSettingsHandler {")
|
||||||
|
|
||||||
|
writeBackupFunction(groupedSettings)
|
||||||
|
appendLine()
|
||||||
|
writeRestoreFunction(groupedSettings.values.flatten())
|
||||||
|
|
||||||
|
appendLine("}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeBackupFunction(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
val indentation = 4
|
||||||
|
val contentIndentation = indentation * 2
|
||||||
|
|
||||||
|
appendLine("fun backup(flags: BackupFlags): BackupServerSettings? {".addIndentation(indentation))
|
||||||
|
appendLine("if (!flags.includeServerSettings) { return null }".addIndentation(contentIndentation))
|
||||||
|
appendLine()
|
||||||
|
appendLine("return BackupServerSettings(".addIndentation(contentIndentation))
|
||||||
|
writeSettings(groupedSettings, indentation * 3)
|
||||||
|
appendLine(")".addIndentation(contentIndentation))
|
||||||
|
appendLine("}".addIndentation(indentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeRestoreFunction(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||||
|
val indentation = 4
|
||||||
|
val contentIndentation = indentation * 2
|
||||||
|
|
||||||
|
appendLine("fun restore(backupServerSettings: BackupServerSettings?) {".addIndentation(indentation))
|
||||||
|
appendLine("if (backupServerSettings == null) { return }".addIndentation(contentIndentation))
|
||||||
|
appendLine()
|
||||||
|
appendLine("SettingsUpdater.updateAll(".addIndentation(contentIndentation))
|
||||||
|
appendLine("backupServerSettings.copy(".addIndentation(indentation * 3))
|
||||||
|
|
||||||
|
val deprecatedSettings = settings.filter { it.typeInfo.restoreLegacy != null }
|
||||||
|
deprecatedSettings.forEach { setting ->
|
||||||
|
appendLine(
|
||||||
|
"${setting.name} = SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.restoreLegacy!!(".addIndentation(indentation * 4) +
|
||||||
|
"backupServerSettings.${setting.name}" +
|
||||||
|
") as? ${getSettingType(setting, false)},",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val excludedSettings = settings.filter { it.excludeFromBackup == true }
|
||||||
|
excludedSettings.forEach { setting ->
|
||||||
|
appendLine(
|
||||||
|
"${setting.name} = null,".addIndentation(indentation * 4),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
appendLine("),".addIndentation(indentation * 3))
|
||||||
|
appendLine(")".addIndentation(contentIndentation))
|
||||||
|
appendLine("}".addIndentation(indentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettings(
|
||||||
|
groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>,
|
||||||
|
indentation: Int,
|
||||||
|
) {
|
||||||
|
groupedSettings.forEach { (group, settings) ->
|
||||||
|
appendLine("// $group".addIndentation(indentation))
|
||||||
|
settings.forEach { setting -> writeSetting(setting, indentation) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSetting(
|
||||||
|
setting: SettingsRegistry.SettingMetadata,
|
||||||
|
indentation: Int,
|
||||||
|
) {
|
||||||
|
appendLine("${setting.name} = ${getConfigAccess(setting)},".addIndentation(indentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSettingType(
|
||||||
|
setting: SettingsRegistry.SettingMetadata,
|
||||||
|
asBackup: Boolean,
|
||||||
|
): String {
|
||||||
|
val possibleType = setting.typeInfo.specificType ?: setting.typeInfo.type.simpleName
|
||||||
|
|
||||||
|
val exception = RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||||
|
|
||||||
|
if (asBackup) {
|
||||||
|
return setting.typeInfo.backupType ?: possibleType ?: throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
return possibleType ?: throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getConfigAccess(setting: SettingsRegistry.SettingMetadata): String {
|
||||||
|
if (setting.excludeFromBackup == true || setting.deprecated != null) {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
if (setting.typeInfo.convertToBackupType != null) {
|
||||||
|
return "SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.convertToBackupType!!(" +
|
||||||
|
"serverConfig.${setting.name}.value" +
|
||||||
|
") as? ${getSettingType(setting, true)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "serverConfig.${setting.name}.value"
|
||||||
|
}
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
|
import io.github.config4k.toConfig
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object SettingsConfigFileGenerator {
|
||||||
|
private const val SERVER_PREFIX = "server."
|
||||||
|
|
||||||
|
fun generate(
|
||||||
|
outputDir: File,
|
||||||
|
testOutputDir: File,
|
||||||
|
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||||
|
) {
|
||||||
|
// Config files only include up-to-date settings.
|
||||||
|
val settingsToInclude = settings.filterValues { it.deprecated == null }
|
||||||
|
|
||||||
|
if (settingsToInclude.isEmpty()) {
|
||||||
|
println("Warning: No settings found to write to config files.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
generateServerReferenceConf(settingsToInclude, outputDir)
|
||||||
|
generateServerReferenceConf(settingsToInclude, testOutputDir)
|
||||||
|
|
||||||
|
println("Settings config file generated successfully! Total settings: ${settingsToInclude.size}")
|
||||||
|
println("- Main config: ${outputDir.resolve("server-reference.conf").absolutePath}")
|
||||||
|
println("- Test config: ${testOutputDir.resolve("server-reference.conf").absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateServerReferenceConf(
|
||||||
|
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||||
|
outputDir: File,
|
||||||
|
) {
|
||||||
|
outputDir.mkdirs()
|
||||||
|
val outputFile = outputDir.resolve("server-reference.conf")
|
||||||
|
|
||||||
|
val groupedSettings = settings.values.groupBy { it.group }
|
||||||
|
|
||||||
|
// Write the config with comments
|
||||||
|
outputFile.writeText(
|
||||||
|
buildString {
|
||||||
|
writeSettings(groupedSettings)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettings(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
val renderOptions =
|
||||||
|
ConfigRenderOptions
|
||||||
|
.defaults()
|
||||||
|
.setOriginComments(false)
|
||||||
|
.setComments(false)
|
||||||
|
.setFormatted(true)
|
||||||
|
.setJson(false)
|
||||||
|
|
||||||
|
var isFirstGroup = true
|
||||||
|
groupedSettings.forEach { (groupName, groupSettings) ->
|
||||||
|
// Prevent empty line at start of the file
|
||||||
|
if (!isFirstGroup) {
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
isFirstGroup = false
|
||||||
|
|
||||||
|
appendLine("# $groupName")
|
||||||
|
|
||||||
|
groupSettings.forEach { setting ->
|
||||||
|
writeSetting(setting, renderOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSetting(
|
||||||
|
setting: SettingsRegistry.SettingMetadata,
|
||||||
|
renderOptions: ConfigRenderOptions,
|
||||||
|
) {
|
||||||
|
val key = "$SERVER_PREFIX${setting.name}"
|
||||||
|
|
||||||
|
val configValue = setting.defaultValue.toConfig("internal").getValue("internal")
|
||||||
|
var renderedValue = configValue.render(renderOptions)
|
||||||
|
|
||||||
|
// Force quotes on all string values for consistency
|
||||||
|
// Check if it's a string value that's not already quoted
|
||||||
|
if (setting.defaultValue is String && !renderedValue.startsWith("\"")) {
|
||||||
|
renderedValue = "\"$renderedValue\""
|
||||||
|
}
|
||||||
|
|
||||||
|
val settingString = "$key = $renderedValue"
|
||||||
|
|
||||||
|
val description = setting.description
|
||||||
|
if (description != null) {
|
||||||
|
val descriptionLines = description.split("\n")
|
||||||
|
|
||||||
|
if (descriptionLines.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine("$settingString # ${descriptionLines[0]}")
|
||||||
|
descriptionLines.drop(1).forEach { line ->
|
||||||
|
appendLine("# $line")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine(settingString)
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import suwayomi.tachidesk.server.ServerConfig
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
import suwayomi.tachidesk.server.util.ConfigTypeRegistration
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to generate settings files from ServerConfig and SettingsRegistry
|
||||||
|
* This can be run as a standalone main function to generate all required files
|
||||||
|
*/
|
||||||
|
object SettingsGenerator {
|
||||||
|
init {
|
||||||
|
// Register custom types for config serialization
|
||||||
|
ConfigTypeRegistration.registerCustomTypes()
|
||||||
|
triggerSettingRegistration()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force registration of all settings without full ServerConfig instantiation
|
||||||
|
*/
|
||||||
|
private fun triggerSettingRegistration() {
|
||||||
|
// This creates a minimal instance just to trigger delegate registration
|
||||||
|
try {
|
||||||
|
val mockConfig =
|
||||||
|
ConfigFactory.parseString(
|
||||||
|
"""
|
||||||
|
server {
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
port = 4567
|
||||||
|
}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val tempConfig = ServerConfig { mockConfig.getConfig("server") }
|
||||||
|
// Access all properties to trigger delegate registrations
|
||||||
|
tempConfig::class.memberProperties.forEach { prop ->
|
||||||
|
try {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(prop as KProperty1<ServerConfig, Any?>).get(tempConfig)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Ignore errors during registration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Registration failed, but we tried
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generate(
|
||||||
|
outputDir: File,
|
||||||
|
testOutputDir: File,
|
||||||
|
graphqlOutputDir: File,
|
||||||
|
backupSettingsOutputDir: File,
|
||||||
|
backupSettingsHandlerOutputDir: File,
|
||||||
|
) {
|
||||||
|
val settings = SettingsRegistry.getAll()
|
||||||
|
|
||||||
|
if (settings.isEmpty()) {
|
||||||
|
println("Warning: No settings found in registry. Settings might not be initialized.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" - Total: ${settings.size}")
|
||||||
|
println(" - Deprecated: ${settings.values.count { it.deprecated != null }}")
|
||||||
|
println(" - Require restart: ${settings.values.count { it.requiresRestart }}")
|
||||||
|
|
||||||
|
SettingsConfigFileGenerator.generate(outputDir, testOutputDir, settings)
|
||||||
|
|
||||||
|
val settingsTypeFile = graphqlOutputDir.resolve("SettingsType.kt")
|
||||||
|
SettingsGraphqlTypeGenerator.generate(settings, settingsTypeFile)
|
||||||
|
|
||||||
|
val backupServerSettingsFile = backupSettingsOutputDir.resolve("BackupServerSettings.kt")
|
||||||
|
SettingsBackupServerSettingsGenerator.generate(settings, backupServerSettingsFile)
|
||||||
|
|
||||||
|
val backupSettingsHandlerFile = backupSettingsHandlerOutputDir.resolve("BackupSettingsHandler.kt")
|
||||||
|
SettingsBackupSettingsHandlerGenerator.generate(settings, backupSettingsHandlerFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
package suwayomi.tachidesk.server.settings.generation
|
||||||
|
|
||||||
|
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.text.appendLine
|
||||||
|
|
||||||
|
object SettingsGraphqlTypeGenerator {
|
||||||
|
fun generate(
|
||||||
|
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||||
|
outputFile: File,
|
||||||
|
) {
|
||||||
|
outputFile.parentFile.mkdirs()
|
||||||
|
|
||||||
|
val settingsToInclude = settings.values
|
||||||
|
|
||||||
|
if (settingsToInclude.isEmpty()) {
|
||||||
|
println("Warning: No settings found to create graphql type from.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupedSettings = settingsToInclude.groupBy { it.group }
|
||||||
|
|
||||||
|
outputFile.writeText(
|
||||||
|
buildString {
|
||||||
|
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.graphql.types"))
|
||||||
|
writeImports(groupedSettings.values.flatten())
|
||||||
|
writeSettingsInterface(groupedSettings)
|
||||||
|
writePartialSettingsType(groupedSettings)
|
||||||
|
writeSettingsType(groupedSettings)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
println("Graphql type generated successfully! Total settings: ${settingsToInclude.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||||
|
appendLine(
|
||||||
|
KotlinFileGeneratorHelper.createImports(
|
||||||
|
listOf(
|
||||||
|
"com.expediagroup.graphql.generator.annotations.GraphQLDeprecated",
|
||||||
|
"com.expediagroup.graphql.generator.annotations.GraphQLIgnore",
|
||||||
|
"suwayomi.tachidesk.graphql.server.primitives.Node",
|
||||||
|
"suwayomi.tachidesk.server.ServerConfig",
|
||||||
|
"suwayomi.tachidesk.server.serverConfig",
|
||||||
|
"suwayomi.tachidesk.server.settings.SettingsRegistry",
|
||||||
|
),
|
||||||
|
settings,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettingsInterface(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
appendLine("interface Settings : Node {")
|
||||||
|
|
||||||
|
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = false, isNullable = true, isInterface = true)
|
||||||
|
|
||||||
|
appendLine("}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writePartialSettingsType(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
appendLine("data class PartialSettingsType(")
|
||||||
|
|
||||||
|
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = true, isNullable = true, isInterface = false)
|
||||||
|
|
||||||
|
appendLine(") : Settings")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettingsType(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||||
|
appendLine("class SettingsType(")
|
||||||
|
|
||||||
|
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = true, isNullable = false, isInterface = false)
|
||||||
|
|
||||||
|
appendLine(") : Settings {")
|
||||||
|
|
||||||
|
// Write secondary constructor
|
||||||
|
val indentation = 4
|
||||||
|
appendLine("@Suppress(\"UNCHECKED_CAST\")".addIndentation(indentation))
|
||||||
|
appendLine("constructor(config: ServerConfig = serverConfig) : this(".addIndentation(indentation))
|
||||||
|
|
||||||
|
writeSettings(
|
||||||
|
groupedSettings,
|
||||||
|
indentation = indentation * 2,
|
||||||
|
asType = false,
|
||||||
|
isOverride = false,
|
||||||
|
isNullable = false,
|
||||||
|
isInterface = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
appendLine(")".addIndentation(indentation))
|
||||||
|
|
||||||
|
appendLine("}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSettings(
|
||||||
|
groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>,
|
||||||
|
indentation: Int,
|
||||||
|
asType: Boolean,
|
||||||
|
isOverride: Boolean,
|
||||||
|
isNullable: Boolean,
|
||||||
|
isInterface: Boolean,
|
||||||
|
) {
|
||||||
|
groupedSettings.forEach { (group, settings) ->
|
||||||
|
appendLine("// $group".addIndentation(indentation))
|
||||||
|
settings.forEach { setting -> writeSetting(setting, indentation, asType, isOverride, isNullable, isInterface) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.writeSetting(
|
||||||
|
setting: SettingsRegistry.SettingMetadata,
|
||||||
|
indentation: Int,
|
||||||
|
asType: Boolean,
|
||||||
|
isOverride: Boolean,
|
||||||
|
isNullable: Boolean,
|
||||||
|
isInterface: Boolean,
|
||||||
|
) {
|
||||||
|
if (!asType) {
|
||||||
|
appendLine("${getConfigAccess(setting)},".addIndentation(indentation))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting.requiresRestart) {
|
||||||
|
appendLine("@GraphQLIgnore".addIndentation(indentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
val deprecated = setting.deprecated
|
||||||
|
if (deprecated != null) {
|
||||||
|
val replaceWithSuffix = deprecated.replaceWith?.let { ", ReplaceWith(\"$it\")" } ?: ""
|
||||||
|
appendLine(
|
||||||
|
"@GraphQLDeprecated(\"${deprecated.message}\"$replaceWithSuffix)".addIndentation(
|
||||||
|
indentation,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val overridePrefix = if (isOverride) "override " else ""
|
||||||
|
val nullableSuffix = if (isNullable) "?" else ""
|
||||||
|
val commaSuffix = if (isOverride) "," else ""
|
||||||
|
appendLine(
|
||||||
|
"${overridePrefix}val ${setting.name}: ${getGraphQLType(
|
||||||
|
setting,
|
||||||
|
isInterface,
|
||||||
|
)}$nullableSuffix$commaSuffix".addIndentation(indentation),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getGraphQLType(
|
||||||
|
setting: SettingsRegistry.SettingMetadata,
|
||||||
|
isInterface: Boolean,
|
||||||
|
): String {
|
||||||
|
val possibleType = setting.typeInfo.specificType ?: setting.typeInfo.type.simpleName
|
||||||
|
|
||||||
|
val exception = RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||||
|
|
||||||
|
if (isInterface) {
|
||||||
|
return setting.typeInfo.interfaceType ?: possibleType ?: throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
return possibleType ?: throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getConfigAccess(setting: SettingsRegistry.SettingMetadata): String {
|
||||||
|
if (setting.typeInfo.convertToGqlType != null) {
|
||||||
|
return "SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.convertToGqlType!!(" +
|
||||||
|
"config.${setting.name}.value" +
|
||||||
|
") as ${getGraphQLType(setting, false)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "config.${setting.name}.value"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
plugins {
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.jvm
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.serialization
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Core Kotlin
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
|
implementation(kotlin("reflect"))
|
||||||
|
|
||||||
|
// Coroutines for MutableStateFlow
|
||||||
|
implementation(libs.coroutines.core)
|
||||||
|
implementation(libs.coroutines.jdk8)
|
||||||
|
|
||||||
|
// Config handling
|
||||||
|
implementation(libs.config)
|
||||||
|
implementation(libs.config4k)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation(libs.slf4japi)
|
||||||
|
implementation(libs.kotlinlogging)
|
||||||
|
|
||||||
|
// Database (for SortOrder enum used in ServerConfig)
|
||||||
|
implementation(libs.exposed.core)
|
||||||
|
|
||||||
|
// GraphQL types used in ServerConfig
|
||||||
|
implementation(libs.graphql.kotlin.scheme)
|
||||||
|
|
||||||
|
// Dependency Injection
|
||||||
|
implementation(libs.injekt)
|
||||||
|
|
||||||
|
// AndroidCompat for SystemPropertyOverridableConfigModule
|
||||||
|
implementation(projects.androidCompat.config)
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
implementation(libs.serialization.json)
|
||||||
|
implementation(libs.serialization.protobuf)
|
||||||
|
implementation(project(":AndroidCompat"))
|
||||||
|
}
|
||||||
|
|
||||||
+1
@@ -4,6 +4,7 @@ enum class AuthMode {
|
|||||||
NONE,
|
NONE,
|
||||||
BASIC_AUTH,
|
BASIC_AUTH,
|
||||||
SIMPLE_LOGIN,
|
SIMPLE_LOGIN,
|
||||||
|
UI_LOGIN,
|
||||||
// TODO: ACCOUNT for #623
|
// TODO: ACCOUNT for #623
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
|
enum class DatabaseType {
|
||||||
|
H2,
|
||||||
|
POSTGRESQL,
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
|
enum class KoreaderSyncChecksumMethod {
|
||||||
|
BINARY,
|
||||||
|
FILENAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the resolution strategy for synchronization conflicts.
|
||||||
|
* This is applied separately for when the remote progress is newer (Forward)
|
||||||
|
* or older (Backward) than the local progress.
|
||||||
|
*/
|
||||||
|
enum class KoreaderSyncConflictStrategy {
|
||||||
|
/** Ask the client application to prompt the user for a decision. */
|
||||||
|
PROMPT,
|
||||||
|
/** Always keep the local progress, ignoring the remote version. */
|
||||||
|
KEEP_LOCAL,
|
||||||
|
/** Always overwrite local progress with the remote version. */
|
||||||
|
KEEP_REMOTE,
|
||||||
|
/** Do not perform any sync action for this scenario. */
|
||||||
|
DISABLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy enum for migrating the old, single sync strategy setting.
|
||||||
|
*/
|
||||||
|
@Deprecated("Used for migration purposes only. Use KoreaderSyncConflictStrategy instead.")
|
||||||
|
enum class KoreaderSyncLegacyStrategy {
|
||||||
|
PROMPT, // Ask on conflict
|
||||||
|
SILENT, // Always use latest
|
||||||
|
SEND, // Send changes only
|
||||||
|
RECEIVE, // Receive changes only
|
||||||
|
DISABLED,
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
|
enum class CbzMediaType(
|
||||||
|
val mediaType: String,
|
||||||
|
) {
|
||||||
|
MODERN("application/vnd.comicbook+zip"),
|
||||||
|
LEGACY("application/x-cbz"),
|
||||||
|
COMPATIBLE("application/x-cbr"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(channel: String): CbzMediaType = entries.find { it.name.lowercase() == channel.lowercase() } ?: MODERN
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
// These types belong to SettingsType.kt. However, since that file is auto-generated, these types need to be placed in
|
||||||
|
// a "static" file.
|
||||||
|
|
||||||
|
class DownloadConversion(
|
||||||
|
val target: String,
|
||||||
|
val compressionLevel: Double? = null,
|
||||||
|
val callTimeout: Duration? = null,
|
||||||
|
val connectTimeout: Duration? = null,
|
||||||
|
val headers: Map<String, String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SettingsDownloadConversion {
|
||||||
|
val mimeType: String
|
||||||
|
val target: String
|
||||||
|
val compressionLevel: Double?
|
||||||
|
val callTimeout: Duration?
|
||||||
|
val connectTimeout: Duration?
|
||||||
|
val headers: List<SettingsDownloadConversionHeader>?
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDownloadConversionType(
|
||||||
|
override val mimeType: String,
|
||||||
|
override val target: String,
|
||||||
|
override val compressionLevel: Double?,
|
||||||
|
override val callTimeout: Duration?,
|
||||||
|
override val connectTimeout: Duration?,
|
||||||
|
override val headers: List<SettingsDownloadConversionHeaderType>?
|
||||||
|
) : SettingsDownloadConversion
|
||||||
|
|
||||||
|
interface SettingsDownloadConversionHeader {
|
||||||
|
val name: String
|
||||||
|
val value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDownloadConversionHeaderType(
|
||||||
|
override val name: String,
|
||||||
|
override val value: String,
|
||||||
|
) : SettingsDownloadConversionHeader
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user