Compare commits

..

1 Commits

Author SHA1 Message Date
Aria Moradi
53c3ac5676 token auth 2022-04-16 18:26:44 +04:30
439 changed files with 4947 additions and 27389 deletions

View File

@@ -1,11 +0,0 @@
[*.{kt,kts}]
indent_size=4
insert_final_newline=true
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_name_count_to_use_star_import=2147483647
ij_kotlin_name_count_to_use_star_import_for_members=2147483647
ktlint_standard_discouraged-comment-location=disabled
ktlint_standard_if-else-wrapping=disabled
ktlint_standard_no-consecutive-comments=disabled

5
.gitattributes vendored
View File

@@ -25,7 +25,4 @@
*.pyc binary *.pyc binary
*.swp binary *.swp binary
*.pdf binary *.pdf binary
*.exe binary *.exe binary
*.avif binary
*.heif binary
*.jxl binary

View File

@@ -11,7 +11,7 @@ I acknowledge that:
- I have updated to the latest version of the app. - I have updated to the latest version of the app.
- I have tried the troubleshooting guide described in `README.md` - I have tried the troubleshooting guide described in `README.md`
- If this is a request for adding/changing an extension it should be brought up to your extension repo. - If this is a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
- If this is an issue with some extension not working properly, It does work inside Tachiyomi as intended. - If this is an issue with some extension not working properly, It does work inside Tachiyomi as intended.
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue - I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template - I will fill out the title and the information in this template
@@ -23,7 +23,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
--- ---
## Device information ## Device information
- Suwayomi-Server version: (Example: v1.0.0-r1438-win32) - Tachidesk version: (Example: v0.2.3-r255-win32)
- Server Operating System: (Example: Ubuntu 20.04) - Server Operating System: (Example: Ubuntu 20.04)
- Server Desktop Environment: N/A or (Example: Gnome 40) - Server Desktop Environment: N/A or (Example: Gnome 40)
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281) - Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)

View File

@@ -11,7 +11,7 @@ I acknowledge that:
- I have updated to the latest version of the app. - I have updated to the latest version of the app.
- I have tried the troubleshooting guide described in `README.md` - I have tried the troubleshooting guide described in `README.md`
- If this is a request for adding/changing an extension it should be brought up to your extension repo. - If this is a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
- If this is an issue with some extension not working properly, It does work in Tachiyomi application as intended. - If this is an issue with some extension not working properly, It does work in Tachiyomi application as intended.
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue - I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template - I will fill out the title and the information in this template
@@ -22,7 +22,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
--- ---
## What feature should be added to Suwayomi? ## What feature should be added to Tachidesk?
Explain What the feature is and how it should work in detail. Remove this line after you are done. Explain What the feature is and how it should work in detail. Remove this line after you are done.
## Why/Project's Benefit/Existing Problem ## Why/Project's Benefit/Existing Problem

View File

@@ -3,10 +3,6 @@ name: CI Pull Request
on: on:
pull_request: pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check_wrapper: check_wrapper:
name: Validate Gradle Wrapper name: Validate Gradle Wrapper
@@ -14,7 +10,7 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1
@@ -22,21 +18,26 @@ jobs:
build: build:
name: Build pull request name: Build pull request
needs: check_wrapper needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.9.0
with:
access_token: ${{ github.token }}
- name: Checkout pull request - name: Checkout pull request
uses: actions/checkout@v4 uses: actions/checkout@v2
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 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v4 uses: actions/setup-java@v1
with: with:
java-version: 8 java-version: 1.8
distribution: 'temurin'
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: | run: |
@@ -44,9 +45,13 @@ 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 Jar
uses: gradle/gradle-build-action@v2 uses: eskatos/gradle-command-action@v1
with: with:
build-root-directory: master build-root-directory: master
arguments: ktlintCheck :server:shadowJar --stacktrace wrapper-directory: master
arguments: :server:shadowJar --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true

View File

@@ -5,38 +5,41 @@ on:
branches: branches:
- master - master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check_wrapper: check_wrapper:
name: Validate Gradle Wrapper name: Validate Gradle Wrapper
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1
build: build:
name: Build Jar name: Build artifacts and deploy preview
needs: check_wrapper needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.9.0
with:
access_token: ${{ github.token }}
- name: Checkout master branch - name: Checkout master branch
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
ref: master ref: master
path: master path: master
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v4 uses: actions/setup-java@v1
with: with:
java-version: 8 java-version: 1.8
distribution: 'temurin'
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: | run: |
@@ -45,136 +48,42 @@ jobs:
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 Jar
uses: gradle/gradle-build-action@v2 uses: eskatos/gradle-command-action@v1
env: env:
ProductBuildType: "Preview" ProductBuildType: "Preview"
with: with:
build-root-directory: master build-root-directory: master
wrapper-directory: master
arguments: :server:shadowJar --stacktrace arguments: :server:shadowJar --stacktrace
wrapper-cache-enabled: true
- name: Upload Jar dependencies-cache-enabled: true
uses: actions/upload-artifact@v3 configuration-cache-enabled: true
with:
name: jar
path: master/server/build/*.jar
if-no-files-found: error
- name: Upload icons
uses: actions/upload-artifact@v3
with:
name: icon
path: master/server/src/main/resources/icon
if-no-files-found: error
- name: Tar scripts dir to maintain file permissions
run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz
uses: actions/upload-artifact@v3
with:
name: scripts
path: scripts.tar.gz
if-no-files-found: error
bundle:
strategy:
fail-fast: false
matrix:
os:
- debian-all
- linux-assets
- linux-x64
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v3
with:
name: jar
path: server/build
- name: Download icons
uses: actions/download-artifact@v3
with:
name: icon
path: server/src/main/resources/icon
- name: Download scripts.tar.gz
uses: actions/download-artifact@v3
with:
name: scripts
- name: Make ${{ matrix.os }} release
run: |
mkdir upload
tar -xvpf scripts.tar.gz
scripts/bundler.sh -o upload/ ${{ matrix.os }}
- name: Upload ${{ matrix.os }} release
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}
path: upload/*
if-no-files-found: error
release:
needs: bundle
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: jar
path: release
- uses: actions/download-artifact@v3
with:
name: debian-all
path: release
- uses: actions/download-artifact@v3
with:
name: linux-assets
path: release
- uses: actions/download-artifact@v3
with:
name: linux-x64
path: release
- uses: actions/download-artifact@v3
with:
name: macOS-x64
path: release
- uses: actions/download-artifact@v3
with:
name: macOS-arm64
path: release
- uses: actions/download-artifact@v3
with:
name: windows-x64
path: release
- uses: actions/download-artifact@v3
with:
name: windows-x86
path: release
- name: Checkout Preview branch
uses: actions/checkout@v3
with:
repository: "Suwayomi/Suwayomi-Server-preview"
ref: main
path: preview
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
- name: Generate Tag Name - name: Generate Tag Name
id: GenTagName id: GenTagName
run: | run: |
cd release cd master/server/build
genTag=$(ls *.jar | sed -e's/Suwayomi-Server-\|.jar//g') genTag=$(ls *.jar | sed -e's/Tachidesk-Server-\|.jar//g')
echo "$genTag" echo "$genTag"
echo "value=$genTag" >> $GITHUB_OUTPUT echo "::set-output name=value::$genTag"
- name: make bundle packages
run: |
cd master/scripts
./windows-bundler.sh win32
./windows-bundler.sh win64
./unix-bundler.sh linux-x64
./debian-packager.sh
./unix-bundler.sh macOS-x64
./unix-bundler.sh macOS-arm64
- name: Checkout preview branch
uses: actions/checkout@v2
with:
repository: 'Suwayomi/Tachidesk-Server-preview'
ref: main
path: preview
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
- name: Create Tag - name: Create Tag
run: | run: |
@@ -183,8 +92,7 @@ jobs:
cd preview cd preview
echo "{ \"latest\": \"$TAG\" }" > index.json echo "{ \"latest\": \"$TAG\" }" > index.json
git add index.json git add index.json
git config --global user.email \ git config --global user.email "github-actions[bot]@users.noreply.github.com"
"github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]" git config --global user.name "github-actions[bot]"
git commit -m "Updated to $TAG" git commit -m "Updated to $TAG"
git push origin main git push origin main
@@ -193,10 +101,10 @@ jobs:
git push origin $TAG git push origin $TAG
- name: Upload Preview Release - name: Upload Preview Release
uses: softprops/action-gh-release@v1 uses: ncipollo/release-action@v1
with: with:
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }} token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
repository: "Suwayomi/Suwayomi-Server-preview" artifacts: "master/server/build/*.jar,master/server/build/*.msi,master/server/build/*.zip,master/server/build/*.tar.gz,master/server/build/*.deb"
tag_name: ${{ steps.GenTagName.outputs.value }} owner: "Suwayomi"
files: release/* repo: "Tachidesk-Server-preview"
tag: ${{ steps.GenTagName.outputs.value }}

View File

@@ -1,35 +1,24 @@
name: Issue moderator name: Issue closer
on: on:
issues: issues:
types: [opened, edited, reopened] types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs: jobs:
autoclose: autoclose:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Moderate issues - name: Autoclose issues
uses: tachiyomiorg/issue-moderator-action@v1 uses: arkon/issue-closer-action@v3.0
with: with:
repo-token: ${{ github.token }} repo-token: ${{ github.token }}
duplicate-check-enabled: true rules: |
duplicate-check-label: Source request
existing-check-enabled: true
existing-check-label: Source request
auto-close-rules: |
[ [
{ {
"type": "title", "type": "title",
"regex": ".*<short description>.*", "regex": ".*<short description>*",
"message": "You did not fill out the description in the title" "message": "You did not fill out the description in the title"
}, },
{
"type": "title",
"regex": ".*(<|>)+.*",
"message": "You did not remove Angle brackets(< and >) from the title"
},
{ {
"type": "body", "type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*", "regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
@@ -37,7 +26,7 @@ jobs:
}, },
{ {
"type": "body", "type": "body",
"regex": ".*(Suwayomi-Server version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*", "regex": "(Tachidesk version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
"message": "The requested information was not filled out" "message": "The requested information was not filled out"
}, },
{ {

View File

@@ -1,174 +1,79 @@
name: CI Publish name: CI Publish
on: on:
workflow_dispatch:
push: push:
tags: tags:
- "v*" - "v*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check_wrapper: check_wrapper:
name: Validate Gradle Wrapper name: Validate Gradle Wrapper
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1
build: build:
name: Build Jar name: Build artifacts and release
needs: check_wrapper needs: check_wrapper
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.9.0
with:
access_token: ${{ github.token }}
- name: Checkout ${{ github.ref }} - name: Checkout ${{ github.ref }}
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
path: master path: master
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v4 uses: actions/setup-java@v1
with: with:
java-version: 8 java-version: 1.8
distribution: 'temurin'
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: | run: |
cd master cd master
mkdir -p ~/.gradle mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties \ cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
~/.gradle/gradle.properties
- name: Build and copy webUI, Build Jar - name: Build and copy webUI, Build Jar
uses: gradle/gradle-build-action@v2 uses: eskatos/gradle-command-action@v1
env: env:
ProductBuildType: "Stable" ProductBuildType: "Stable"
with: with:
build-root-directory: master build-root-directory: master
wrapper-directory: master
arguments: :server:downloadWebUI :server:shadowJar --stacktrace arguments: :server:downloadWebUI :server:shadowJar --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Upload Jar - name: Make bundle packages
uses: actions/upload-artifact@v4
with:
name: jar
path: master/server/build/*.jar
if-no-files-found: error
- name: Upload icons
uses: actions/upload-artifact@v4
with:
name: icon
path: master/server/src/main/resources/icon
if-no-files-found: error
- name: Tar scripts dir to maintain file permissions
run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz
uses: actions/upload-artifact@v4
with:
name: scripts
path: scripts.tar.gz
if-no-files-found: error
bundle:
strategy:
fail-fast: false
matrix:
os:
- debian-all
- linux-assets
- linux-x64
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v4
with:
name: jar
path: server/build
- name: Download icons
uses: actions/download-artifact@v4
with:
name: icon
path: server/src/main/resources/icon
- name: Download scripts.tar.gz
uses: actions/download-artifact@v4
with:
name: scripts
- name: Make ${{ matrix.os }} release
run: | run: |
mkdir upload/ cd master/scripts
tar -xvpf scripts.tar.gz ./windows-bundler.sh win32
scripts/bundler.sh -o upload/ ${{ matrix.os }} ./windows-bundler.sh win64
./unix-bundler.sh linux-x64
./debian-packager.sh
./unix-bundler.sh macOS-x64
./unix-bundler.sh macOS-arm64
- name: Upload ${{ matrix.os }} files - name: Upload Release
uses: actions/upload-artifact@v4 uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
name: ${{ matrix.os }} file: "master/server/build/*.jar;master/server/build/*.msi;master/server/build/*.zip;master/server/build/*.tar.gz;master/server/build/*.deb"
path: upload/* tags: true
if-no-files-found: error
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: bundle
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: jar
path: release
- uses: actions/download-artifact@v4
with:
name: debian-all
path: release
- uses: actions/download-artifact@v4
with:
name: linux-assets
path: release
- uses: actions/download-artifact@v4
with:
name: linux-x64
path: release
- uses: actions/download-artifact@v4
with:
name: macOS-x64
path: release
- uses: actions/download-artifact@v4
with:
name: macOS-arm64
path: release
- uses: actions/download-artifact@v4
with:
name: windows-x64
path: release
- uses: actions/download-artifact@v4
with:
name: windows-x86
path: release
- name: Generate checksums
run: cd release && sha256sum * > Checksums.sha256
- name: Release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
draft: true draft: true
files: release/* verbose: true

View File

@@ -1,15 +0,0 @@
name: Publish to WinGet
on:
workflow_run:
workflows: ["CI Publish"]
types:
- completed
jobs:
publish:
runs-on: windows-latest # action can only be run on windows
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: Suwayomi.Tachidesk-Server
installers-regex: '.*x64.msi$'
token: ${{ secrets.WINGET_PUBLISH_PAT }}

2
.gitignore vendored
View File

@@ -2,7 +2,7 @@
.gradle .gradle
.idea .idea
gradle.properties gradle.properties
.fleet
# But we need these # But we need these
!.idea/runConfigurations !.idea/runConfigurations

View File

@@ -1,11 +0,0 @@
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
}
dependencies {
// Shared
implementation(libs.bundles.shared)
testImplementation(libs.bundles.sharedTest)
}

View File

@@ -15,6 +15,6 @@ val ApplicationRootDir: String
get(): String { get(): String {
return System.getProperty( return System.getProperty(
"$CONFIG_PREFIX.server.rootDir", "$CONFIG_PREFIX.server.rootDir",
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null), AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
) )
} }

View File

@@ -5,9 +5,8 @@ import org.kodein.di.bind
import org.kodein.di.singleton import org.kodein.di.singleton
class ConfigKodeinModule { class ConfigKodeinModule {
fun create() = fun create() = DI.Module("ConfigManager") {
DI.Module("ConfigManager") { // Config module
// Config module bind<ConfigManager>() with singleton { GlobalConfigManager }
bind<ConfigManager>() with singleton { GlobalConfigManager } }
}
} }

View File

@@ -10,12 +10,7 @@ package xyz.nulldev.ts.config
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValue import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.parser.ConfigDocument
import com.typesafe.config.parser.ConfigDocumentFactory
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import mu.KotlinLogging import mu.KotlinLogging
import java.io.File import java.io.File
@@ -23,18 +18,14 @@ import java.io.File
* Manages app config. * Manages app config.
*/ */
open class ConfigManager { open class ConfigManager {
val logger = KotlinLogging.logger {}
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>() private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
private val userConfigFile = File(ApplicationRootDir, "server.conf") val config by lazy { loadConfigs() }
private var internalConfig = loadConfigs()
val config: Config
get() = internalConfig
// Public read-only view of modules // Public read-only view of modules
val loadedModules: Map<Class<out ConfigModule>, ConfigModule> val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
get() = generatedModules get() = generatedModules
private val mutex = Mutex() val logger = KotlinLogging.logger {}
/** /**
* Get a config module * Get a config module
@@ -47,12 +38,6 @@ open class ConfigManager {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
private fun getUserConfig(): Config {
return userConfigFile.let {
ConfigFactory.parseFile(it)
}
}
/** /**
* Load configs * Load configs
*/ */
@@ -63,25 +48,30 @@ open class ConfigManager {
val baseConfig = val baseConfig =
ConfigFactory.parseMap( ConfigFactory.parseMap(
mapOf( mapOf(
// override AndroidCompat's rootDir "androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat", )
),
) )
// Load user config // Load user config
val userConfig = getUserConfig() val userConfig =
File(ApplicationRootDir, "server.conf").let {
ConfigFactory.parseFile(it)
}
val config = val config = ConfigFactory.empty()
ConfigFactory.empty() .withFallback(baseConfig)
.withFallback(baseConfig) .withFallback(userConfig)
.withFallback(userConfig) .withFallback(compatConfig)
.withFallback(compatConfig) .withFallback(serverConfig)
.withFallback(serverConfig) .resolve()
.resolve()
// set log level early // set log level early
if (debugLogsEnabled(config)) { if (debugLogsEnabled(config)) {
setLogLevelFor(BASE_LOGGER_NAME, Level.DEBUG) setLogLevel(Level.DEBUG)
}
logger.debug {
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
} }
return config return config
@@ -96,71 +86,6 @@ open class ConfigManager {
registerModule(it) registerModule(it)
} }
} }
private fun updateUserConfigFile(
path: String,
value: ConfigValue,
) {
val userConfigDoc = ConfigDocumentFactory.parseFile(userConfigFile)
val updatedConfigDoc = userConfigDoc.withValue(path, value)
val newFileContent = updatedConfigDoc.render()
userConfigFile.writeText(newFileContent)
}
suspend fun updateValue(
path: String,
value: Any,
) {
mutex.withLock {
val configValue = ConfigValueFactory.fromAnyRef(value)
updateUserConfigFile(path, configValue)
internalConfig = internalConfig.withValue(path, configValue)
}
}
fun resetUserConfig(updateInternalConfig: Boolean = true): ConfigDocument {
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
val serverConfigDoc = ConfigDocumentFactory.parseString(serverConfigFileContent)
userConfigFile.writeText(serverConfigDoc.render())
if (updateInternalConfig) {
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
}
return serverConfigDoc
}
/**
* Makes sure the "UserConfig" is up-to-date.
*
* - adds missing settings
* - removes outdated settings
*/
fun updateUserConfig() {
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
val userConfig = getUserConfig()
val hasMissingSettings = serverConfig.entrySet().any { !userConfig.hasPath(it.key) }
val hasOutdatedSettings = userConfig.entrySet().any { !serverConfig.hasPath(it.key) }
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
if (!isUserConfigOutdated) {
return
}
logger.debug {
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings"
}
var newUserConfigDoc: ConfigDocument = resetUserConfig(false)
userConfig.entrySet().filter {
serverConfig.hasPath(
it.key,
)
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
userConfigFile.writeText(newUserConfigDoc.render())
}
} }
object GlobalConfigManager : ConfigManager() object GlobalConfigManager : ConfigManager()

View File

@@ -8,8 +8,6 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import io.github.config4k.getValue import io.github.config4k.getValue
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@@ -17,39 +15,28 @@ import kotlin.reflect.KProperty
* Abstract config module. * Abstract config module.
*/ */
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
abstract class ConfigModule(getConfig: () -> Config) abstract class ConfigModule(config: Config)
/** /**
* Abstract jvm-commandline-argument-overridable config module. * Abstract jvm-commandline-argument-overridable config module.
*/ */
abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, moduleName: String) : ConfigModule(getConfig) { abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String) : ConfigModule(config) {
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName) val overridableConfig = SystemPropertyOverrideDelegate(config, moduleName)
} }
/** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */ /** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */
class SystemPropertyOverrideDelegate(val getConfig: () -> Config, val moduleName: String) { class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String) {
inline operator fun <R, reified T> getValue( inline operator fun <R, reified T> getValue(thisRef: R, property: KProperty<*>): T {
thisRef: R,
property: KProperty<*>,
): T {
val config = getConfig()
val configValue: T = config.getValue(thisRef, property) val configValue: T = config.getValue(thisRef, property)
val combined = val combined = System.getProperty(
System.getProperty( "$CONFIG_PREFIX.$moduleName.${property.name}",
"$CONFIG_PREFIX.$moduleName.${property.name}", configValue.toString()
if (T::class.simpleName == "List") { )
ConfigValueFactory.fromAnyRef(configValue).render()
} else {
configValue.toString()
},
)
return when (T::class.simpleName) { return when (T::class.simpleName) {
"Int" -> combined.toInt() "Int" -> combined.toInt()
"Boolean" -> combined.toBoolean() "Boolean" -> combined.toBoolean()
"Double" -> combined.toDouble()
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
// add more types as needed // add more types as needed
else -> combined // covers String else -> combined // covers String
} as T } as T

View File

@@ -8,88 +8,12 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
import ch.qos.logback.core.util.FileSize
import com.typesafe.config.Config import com.typesafe.config.Config
import mu.KotlinLogging import mu.KotlinLogging
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory
private fun createRollingFileAppender( fun setLogLevel(level: Level) {
logContext: LoggerContext, (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
logDirPath: String,
): RollingFileAppender<ILoggingEvent> {
val logFilename = "application"
val logEncoder =
PatternLayoutEncoder().apply {
pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n"
context = logContext
start()
}
val appender =
RollingFileAppender<ILoggingEvent>().apply {
name = "FILE"
context = logContext
encoder = logEncoder
file = "$logDirPath/$logFilename.log"
}
val rollingPolicy =
SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
context = logContext
setParent(appender)
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
setMaxFileSize(FileSize.valueOf("10mb"))
maxHistory = 14
setTotalSizeCap(FileSize.valueOf("1gb"))
start()
}
appender.rollingPolicy = rollingPolicy
appender.start()
return appender
}
private fun getBaseLogger(): ch.qos.logback.classic.Logger {
return (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger)
}
private fun getLogger(name: String): ch.qos.logback.classic.Logger {
val context = LoggerFactory.getILoggerFactory() as LoggerContext
return context.getLogger(name)
}
fun initLoggerConfig(appRootPath: String) {
val context = LoggerFactory.getILoggerFactory() as LoggerContext
val logger = getBaseLogger()
// logback logs to the console by default (at least when adding a console appender logs in the console are duplicated)
logger.addAppender(createRollingFileAppender(context, "$appRootPath/logs"))
// set "kotlin exposed" log level
setLogLevelFor("Exposed", Level.ERROR)
}
const val BASE_LOGGER_NAME = "_BaseLogger"
fun setLogLevelFor(
name: String,
level: Level,
) {
val logger =
if (name == BASE_LOGGER_NAME) {
getBaseLogger()
} else {
getLogger(name)
}
logger.level = level
} }
fun debugLogsEnabled(config: Config) = fun debugLogsEnabled(config: Config) =

View File

@@ -2,6 +2,5 @@ package xyz.nulldev.ts.config.util
import com.typesafe.config.Config import com.typesafe.config.Config
operator fun Config.get(key: String) = operator fun Config.get(key: String) = getString(key)
getString(key) ?: throw IllegalStateException("Could not find value for config entry: $key!")
?: throw IllegalStateException("Could not find value for config entry: $key!")

View File

@@ -1,38 +1,28 @@
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
}
dependencies { dependencies {
// Shared
implementation(libs.bundles.shared)
testImplementation(libs.bundles.sharedTest)
// Android stub library // Android stub library
implementation(libs.android.stubs) implementation("com.github.Suwayomi:android-jar:1.0.0")
// XML // XML
compileOnly(libs.xmlpull) compileOnly("xmlpull:xmlpull:1.1.3.4a")
// Config API // Config API
implementation(projects.androidCompat.config) implementation(project(":AndroidCompat:Config"))
// APK sig verifier // APK sig verifier
compileOnly(libs.apksig) compileOnly("com.android.tools.build:apksig:7.1.0-beta05")
// AndroidX annotations // AndroidX annotations
compileOnly(libs.android.annotations) compileOnly("androidx.annotation:annotation:1.3.0")
// substitute for duktape-android // substitute for duktape-android
implementation(libs.bundles.rhino) implementation("org.mozilla:rhino-runtime:1.7.14") // slimmer version of 'org.mozilla:rhino'
implementation("org.mozilla:rhino-engine:1.7.14") // provides the same interface as 'javax.script' a.k.a Nashorn
// Kotlin wrapper around Java Preferences, makes certain things easier // Kotlin wrapper around Java Preferences, makes certain things easier
implementation(libs.bundles.settings) val multiplatformSettingsVersion = "0.8.1"
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
// Android version of SimpleDateFormat // Android version of SimpleDateFormat
implementation(libs.icu4j) implementation("com.ibm.icu:icu4j:70.1")
// OpenJDK lacks native JPEG encoder and native WEBP decoder
implementation(libs.bundles.twelvemonkeys)
} }

View File

@@ -1,235 +0,0 @@
package android.graphics;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
public final class Bitmap {
private final int width;
private final int height;
private final BufferedImage image;
public Bitmap(BufferedImage image) {
this.image = image;
this.width = image.getWidth();
this.height = image.getHeight();
}
public BufferedImage getImage() {
return image;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2),
WEBP_LOSSY (3),
WEBP_LOSSLESS (4);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
public enum Config {
ALPHA_8(1),
RGB_565(3),
ARGB_4444(4),
ARGB_8888(5),
RGBA_F16(6),
HARDWARE(7),
RGBA_1010102(8);
final int nativeInt;
private static final Config[] sConfigs = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
};
Config(int ni) {
this.nativeInt = ni;
}
static Config nativeToConfig(int ni) {
return sConfigs[ni];
}
}
/**
* Common code for checking that x and y are >= 0
*
* @param x x coordinate to ensure is >= 0
* @param y y coordinate to ensure is >= 0
*/
private static void checkXYSign(int x, int y) {
if (x < 0) {
throw new IllegalArgumentException("x must be >= 0");
}
if (y < 0) {
throw new IllegalArgumentException("y must be >= 0");
}
}
/**
* Common code for checking that width and height are > 0
*
* @param width width to ensure is > 0
* @param height height to ensure is > 0
*/
private static void checkWidthHeight(int width, int height) {
if (width <= 0) {
throw new IllegalArgumentException("width must be > 0");
}
if (height <= 0) {
throw new IllegalArgumentException("height must be > 0");
}
}
public static Bitmap createBitmap(int width, int height, Config config) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
return new Bitmap(image);
}
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
checkXYSign(x, y);
checkWidthHeight(width, height);
if (x + width > source.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
// Android will make a copy when creating a sub image,
// so we do the same here
BufferedImage subImage = source.image.getSubimage(x, y, width, height);
BufferedImage newImage = new BufferedImage(subImage.getWidth(), subImage.getHeight(), subImage.getType());
newImage.setData(subImage.getData());
return new Bitmap(newImage);
}
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
float qualityFloat = ((float) quality) / 100;
String formatString;
if (format == Bitmap.CompressFormat.PNG) {
formatString = "png";
} else if (format == Bitmap.CompressFormat.JPEG) {
formatString = "jpg";
} else {
throw new IllegalArgumentException("unsupported compression format!");
}
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
if (!writers.hasNext()) {
throw new IllegalStateException("no image writers found for this format!");
}
ImageWriter writer = writers.next();
ImageOutputStream ios;
try {
ios = ImageIO.createImageOutputStream(stream);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
if ("jpg".equals(formatString)) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(qualityFloat);
}
try {
writer.write(null, new IIOImage(image, null, null), param);
ios.close();
writer.dispose();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return true;
}
/**
* Shared code to check for illegal arguments passed to getPixels()
* or setPixels()
*
* @param x left edge of the area of pixels to access
* @param y top edge of the area of pixels to access
* @param width width of the area of pixels to access
* @param height height of the area of pixels to access
* @param offset offset into pixels[] array
* @param stride number of elements in pixels[] between each logical row
* @param pixels array to hold the area of pixels being accessed
*/
private void checkPixelsAccess(int x, int y, int width, int height,
int offset, int stride, int[] pixels) {
checkXYSign(x, y);
if (width < 0) {
throw new IllegalArgumentException("width must be >= 0");
}
if (height < 0) {
throw new IllegalArgumentException("height must be >= 0");
}
if (x + width > getWidth()) {
throw new IllegalArgumentException(
"x + width must be <= bitmap.width()");
}
if (y + height > getHeight()) {
throw new IllegalArgumentException(
"y + height must be <= bitmap.height()");
}
if (Math.abs(stride) < width) {
throw new IllegalArgumentException("abs(stride) must be >= width");
}
int lastScanline = offset + (height - 1) * stride;
int length = pixels.length;
if (offset < 0 || (offset + width > length)
|| lastScanline < 0
|| (lastScanline + width > length)) {
throw new ArrayIndexOutOfBoundsException();
}
}
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
Raster raster = image.getData();
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
for (int ht = 0; ht < height; ht++) {
int rowOffset = offset + stride * ht;
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
}
}
}

View File

@@ -1,51 +0,0 @@
package android.graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
public class BitmapFactory {
public static Bitmap decodeStream(InputStream inputStream) {
Bitmap bitmap = null;
try {
ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
if (!imageReaders.hasNext()) {
throw new IllegalArgumentException("no reader for image");
}
ImageReader imageReader = imageReaders.next();
imageReader.setInput(imageInputStream);
BufferedImage image = imageReader.read(0, imageReader.getDefaultReadParam());
bitmap = new Bitmap(image);
imageReader.dispose();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return bitmap;
}
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
Bitmap bitmap = null;
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data);
try {
BufferedImage image = ImageIO.read(byteArrayStream);
bitmap = new Bitmap(image);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return bitmap;
}
}

View File

@@ -1,21 +0,0 @@
package android.graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
public final class Canvas {
private BufferedImage canvasImage;
private Graphics2D canvas;
public Canvas(Bitmap bitmap) {
canvasImage = bitmap.getImage();
canvas = canvasImage.createGraphics();
}
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
BufferedImage sourceImage = sourceBitmap.getImage();
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
canvas.drawImage(sourceImageCropped, null, dst.left, dst.top);
}
}

View File

@@ -1,122 +0,0 @@
package android.graphics;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Rect {
int left;
int top;
int right;
int bottom;
private static final class UnflattenHelper {
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
"(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)");
static Matcher getMatcher(String str) {
return FLATTENED_PATTERN.matcher(str);
}
}
public Rect() {
}
public Rect(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public Rect(Rect r) {
if (r == null) {
this.left = 0;
this.top = 0;
this.right = 0;
this.bottom = 0;
} else {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
public final int getWidth() {
return right - left;
}
public final int getHeight() {
return bottom - top;
}
public static Rect unflattenFromString(String str) {
if (str.isEmpty()) {
return null;
}
Matcher matcher = UnflattenHelper.getMatcher(str);
if (!matcher.matches()) {
return null;
}
return new Rect(Integer.parseInt(matcher.group(1)),
Integer.parseInt(matcher.group(2)),
Integer.parseInt(matcher.group(3)),
Integer.parseInt(matcher.group(4)));
}
public String toShortString() {
return toShortString(new StringBuilder(32));
}
public String toShortString(StringBuilder sb) {
sb.setLength(0);
sb.append('['); sb.append(left); sb.append(',');
sb.append(top); sb.append("]["); sb.append(right);
sb.append(','); sb.append(bottom); sb.append(']');
return sb.toString();
}
public String flattenToString() {
StringBuilder sb = new StringBuilder(32);
sb.append(left);
sb.append(' ');
sb.append(top);
sb.append(' ');
sb.append(right);
sb.append(' ');
sb.append(bottom);
return sb.toString();
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
@Override
public Rect createFromParcel(Parcel in) {
Rect r = new Rect();
r.readFromParcel(in);
return r;
}
@Override
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
}

View File

@@ -12,7 +12,7 @@ class PreferenceManager {
fun getDefaultSharedPreferences(context: Context) = fun getDefaultSharedPreferences(context: Context) =
context.getSharedPreferences( context.getSharedPreferences(
context.applicationInfo.packageName, context.applicationInfo.packageName,
Context.MODE_PRIVATE, Context.MODE_PRIVATE
)!! )!!
} }
} }

View File

@@ -4,7 +4,7 @@ package android.text;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist; import org.jsoup.safety.Whitelist;
import org.xml.sax.XMLReader; import org.xml.sax.XMLReader;
/** /**
@@ -18,7 +18,7 @@ import org.xml.sax.XMLReader;
public class Html { public class Html {
public static Spanned fromHtml(String source) { public static Spanned fromHtml(String source) {
return new FakeSpanned(Jsoup.clean(source, Safelist.none())); return new FakeSpanned(Jsoup.clean(source, Whitelist.none()));
} }
public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) { public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {

View File

@@ -1,247 +0,0 @@
package android.webkit;
import android.annotation.Nullable;
import xyz.nulldev.androidcompat.webkit.CookieManagerImpl;
public abstract class CookieManager {
/**
* @deprecated This class should not be constructed by applications, use {@link #getInstance}
* instead to fetch the singleton instance.
*/
// TODO(ntfschr): mark this as @SystemApi after a year.
@Deprecated
public CookieManager() {}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("doesn't implement Cloneable");
}
private static CookieManager INSTANCE = null;
private static final Object lock = new Object();
/**
* Gets the singleton CookieManager instance.
*
* @return the singleton CookieManager instance
*/
public static CookieManager getInstance() {
if (INSTANCE != null) {
return INSTANCE;
} else {
synchronized (lock) {
if (INSTANCE == null) {
INSTANCE = new CookieManagerImpl();
}
return INSTANCE;
}
}
}
/**
* Sets whether the application's {@link WebView} instances should send and
* accept cookies.
* By default this is set to {@code true} and the WebView accepts cookies.
* <p>
* When this is {@code true}
* {@link CookieManager#setAcceptThirdPartyCookies setAcceptThirdPartyCookies} and
* {@link CookieManager#setAcceptFileSchemeCookies setAcceptFileSchemeCookies}
* can be used to control the policy for those specific types of cookie.
*
* @param accept whether {@link WebView} instances should send and accept
* cookies
*/
public abstract void setAcceptCookie(boolean accept);
/**
* Gets whether the application's {@link WebView} instances send and accept
* cookies.
*
* @return {@code true} if {@link WebView} instances send and accept cookies
*/
public abstract boolean acceptCookie();
/**
* Sets whether the {@link WebView} should allow third party cookies to be set.
* Allowing third party cookies is a per WebView policy and can be set
* differently on different WebView instances.
* <p>
* Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
* default to allowing third party cookies. Apps targeting
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later default to disallowing
* third party cookies.
*
* @param webview the {@link WebView} instance to set the cookie policy on
* @param accept whether the {@link WebView} instance should accept
* third party cookies
*/
public abstract void setAcceptThirdPartyCookies(WebView webview, boolean accept);
/**
* Gets whether the {@link WebView} should allow third party cookies to be set.
*
* @param webview the {@link WebView} instance to get the cookie policy for
* @return {@code true} if the {@link WebView} accepts third party cookies
*/
public abstract boolean acceptThirdPartyCookies(WebView webview);
/**
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
* host, path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired. To set multiple cookies, your application should invoke
* this method multiple times.
*
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
* response header defined by
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
* consult the RFC specification for a list of valid attributes.
*
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
* attribute, {@code url} must use the {@code "https://"} scheme.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
*/
public abstract void setCookie(String url, String value);
/**
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
* host, path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired. To set multiple cookies, your application should invoke
* this method multiple times.
*
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
* response header defined by
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
* consult the RFC specification for a list of valid attributes.
*
* <p>This method is asynchronous. If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether the cookie was set successfully.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether it succeeded, and in this case it is safe to call the method from a
* thread without a Looper.
*
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
* attribute, {@code url} must use the {@code "https://"} scheme.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
* @param callback a callback to be executed when the cookie has been set
*/
public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
callback);
/**
* Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
* cookies are associated with this URL, in which case each cookie will be delimited by {@code
* "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
* {@code "key=value"}.
*
* @param url the URL for which the cookies are requested
* @return value the cookies as a string, using the format of the 'Cookie'
* HTTP request header
*/
public abstract String getCookie(String url);
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* @deprecated use {@link #removeSessionCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeSessionCookie();
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookie were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the session cookies have been removed
*/
public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Removes all cookies.
* @deprecated Use {@link #removeAllCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeAllCookie();
/**
* Removes all cookies.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookies were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the cookies have been removed
*/
public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Gets whether there are stored cookies.
*
* @return {@code true} if there are stored cookies
*/
public abstract boolean hasCookies();
/**
* Removes all expired cookies.
* @deprecated The WebView handles removing expired cookies automatically.
*/
@Deprecated
public abstract void removeExpiredCookie();
/**
* Ensures all cookies currently accessible through the getCookie API are
* written to persistent storage.
* This call will block the caller until it is done and may perform I/O.
*/
public abstract void flush();
/**
* Gets whether the application's {@link WebView} instances send and accept
* cookies for file scheme URLs.
*
* @return {@code true} if {@link WebView} instances send and accept cookies for
* file scheme URLs
*/
// Static for backward compatibility.
public static boolean allowFileSchemeCookies() {
return getInstance().allowFileSchemeCookiesImpl();
}
public abstract boolean allowFileSchemeCookiesImpl();
/**
* Sets whether the application's {@link WebView} instances should send and accept cookies for
* file scheme URLs.
* <p>
* Use of cookies with file scheme URLs is potentially insecure and turned off by default. All
* {@code file://} URLs share all their cookies, which may lead to leaking private app cookies
* (ex. any malicious file can access cookies previously set by other (trusted) files).
* <p class="note">
* Loading content via {@code file://} URLs is generally discouraged. See the note in
* {@link WebSettings#setAllowFileAccess}.
* Using <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
* androidx.webkit.WebViewAssetLoader</a> to load files over {@code http(s)://} URLs allows
* the standard web security model to be used for setting and sharing cookies for local files.
* <p>
* Note that calls to this method will have no effect if made after calling other
* {@link CookieManager} APIs.
*
* @deprecated This setting is not secure, please use
* <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
* androidx.webkit.WebViewAssetLoader</a> instead.
*/
// Static for backward compatibility.
@Deprecated
public static void setAcceptFileSchemeCookies(boolean accept) {
getInstance().setAcceptFileSchemeCookiesImpl(accept);
}
public abstract void setAcceptFileSchemeCookiesImpl(boolean accept);
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2017 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.
*/
@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
package androidx.core.net
import android.net.Uri
import java.io.File
/**
* Creates a Uri from the given encoded URI string.
*
* @see Uri.parse
*/
public inline fun String.toUri(): Uri = Uri.parse(this)
/**
* Creates a Uri from the given file.
*
* @see Uri.fromFile
*/
public inline fun File.toUri(): Uri = Uri.fromFile(this)
/**
* Creates a [File] from the given [Uri]. Note that this will throw an
* [IllegalArgumentException] when invoked on a [Uri] that lacks `file` scheme.
*/
public fun Uri.toFile(): File {
require(scheme == "file") { "Uri lacks 'file' scheme: $this" }
return File(requireNotNull(path) { "Uri path is null: $this" })
}

View File

@@ -22,7 +22,6 @@ public class Preference {
@JsonIgnore @JsonIgnore
protected Context context; protected Context context;
private boolean isVisible;
private String key; private String key;
private CharSequence title; private CharSequence title;
private CharSequence summary; private CharSequence summary;
@@ -101,14 +100,6 @@ public class Preference {
return sharedPreferences; return sharedPreferences;
} }
public void setVisible(boolean visible) {
isVisible = visible;
}
public boolean getVisible() {
return isVisible;
}
/** Tachidesk specific API */ /** Tachidesk specific API */
public void setSharedPreferences(SharedPreferences sharedPreferences) { public void setSharedPreferences(SharedPreferences sharedPreferences) {
this.sharedPreferences = sharedPreferences; this.sharedPreferences = sharedPreferences;

View File

@@ -1,69 +0,0 @@
package app.cash.quickjs;
import org.mozilla.javascript.ConsString;
import org.mozilla.javascript.NativeArray;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.Closeable;
public final class QuickJs implements Closeable {
private ScriptEngine engine;
public static QuickJs create() {
return new QuickJs(new ScriptEngineManager());
}
public QuickJs(ScriptEngineManager manager) {
this.engine = manager.getEngineByName("rhino");
}
public Object evaluate(String script, String fileName) {
return this.evaluate(script);
}
public Object evaluate(String script) {
try {
Object value = engine.eval(script);
return translateType(value);
} catch (Exception exception) {
throw new QuickJsException(exception.getMessage(), exception);
}
}
private Object translateType(Object obj) {
if (obj instanceof NativeArray) {
NativeArray array = (NativeArray) obj;
long length = array.getLength();
Object[] objects = new Object[(int) length];
for (int i = 0; i < (int) length; i++) {
objects[i] = translateType(array.get(i));
}
return objects;
}
if (obj instanceof ConsString) {
ConsString consString = (ConsString) obj;
return consString.toString();
}
if (obj instanceof Long) {
Long value = (Long) obj;
return value.intValue();
}
return obj;
}
public byte[] compile(String sourceCode, String fileName) {
return sourceCode.getBytes();
}
public Object execute(byte[] bytecode) {
return this.evaluate(new String(bytecode));
}
@Override
public void close() {
this.engine = null;
}
}

View File

@@ -1,7 +0,0 @@
package app.cash.quickjs;
public final class QuickJsException extends RuntimeException {
public QuickJsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -7,6 +7,7 @@ import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext import xyz.nulldev.androidcompat.androidimpl.CustomContext
class AndroidCompat { class AndroidCompat {
val context: CustomContext by DI.global.instance() val context: CustomContext by DI.global.instance()
fun startApp(application: Application) { fun startApp(application: Application) {

View File

@@ -18,13 +18,10 @@ class AndroidCompatInitializer {
GlobalConfigManager.registerModules( GlobalConfigManager.registerModules(
FilesConfigModule.register(GlobalConfigManager.config), FilesConfigModule.register(GlobalConfigManager.config),
ApplicationInfoConfigModule.register(GlobalConfigManager.config), ApplicationInfoConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config), SystemConfigModule.register(GlobalConfigManager.config)
) )
// Set some properties extensions use // Set some properties extensions use
System.setProperty( System.setProperty("http.agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
"http.agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
)
} }
} }

View File

@@ -18,24 +18,22 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
*/ */
class AndroidCompatModule { class AndroidCompatModule {
fun create() = fun create() = DI.Module("AndroidCompat") {
DI.Module("AndroidCompat") { bind<AndroidFiles>() with singleton { AndroidFiles() }
bind<AndroidFiles>() with singleton { AndroidFiles() }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() } bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
bind<ServiceSupport>() with singleton { ServiceSupport() } bind<ServiceSupport>() with singleton { ServiceSupport() }
bind<FakePackageManager>() with singleton { FakePackageManager() } bind<FakePackageManager>() with singleton { FakePackageManager() }
bind<PackageController>() with singleton { PackageController() } bind<PackageController>() with singleton { PackageController() }
// Context // Context
bind<CustomContext>() with singleton { CustomContext() } bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with bind<Context>() with singleton {
singleton { val context: Context by DI.global.instance<CustomContext>()
val context: Context by DI.global.instance<CustomContext>() context
context
}
} }
}
} }

View File

@@ -8,11 +8,12 @@ import xyz.nulldev.ts.config.ConfigModule
* Application info config. * Application info config.
*/ */
class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) { class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
val packageName: String by getConfig() val packageName: String by config
val debug: Boolean by getConfig() val debug: Boolean by config
companion object { companion object {
fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") } fun register(config: Config) =
ApplicationInfoConfigModule(config.getConfig("android.app"))
} }
} }

View File

@@ -8,26 +8,27 @@ import xyz.nulldev.ts.config.ConfigModule
* Files configuration modules. Specifies where to store the Android files. * Files configuration modules. Specifies where to store the Android files.
*/ */
class FilesConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) { class FilesConfigModule(config: Config) : ConfigModule(config) {
val dataDir: String by getConfig() val dataDir: String by config
val filesDir: String by getConfig() val filesDir: String by config
val noBackupFilesDir: String by getConfig() val noBackupFilesDir: String by config
val externalFilesDirs: MutableList<String> by getConfig() val externalFilesDirs: MutableList<String> by config
val obbDirs: MutableList<String> by getConfig() val obbDirs: MutableList<String> by config
val cacheDir: String by getConfig() val cacheDir: String by config
val codeCacheDir: String by getConfig() val codeCacheDir: String by config
val externalCacheDirs: MutableList<String> by getConfig() val externalCacheDirs: MutableList<String> by config
val externalMediaDirs: MutableList<String> by getConfig() val externalMediaDirs: MutableList<String> by config
val rootDir: String by getConfig() val rootDir: String by config
val externalStorageDir: String by getConfig() val externalStorageDir: String by config
val downloadCacheDir: String by getConfig() val downloadCacheDir: String by config
val databasesDir: String by getConfig() val databasesDir: String by config
val prefsDir: String by getConfig() val prefsDir: String by config
val packageDir: String by getConfig() val packageDir: String by config
companion object { companion object {
fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") } fun register(config: Config) =
FilesConfigModule(config.getConfig("android.files"))
} }
} }

View File

@@ -4,22 +4,19 @@ import com.typesafe.config.Config
import io.github.config4k.getValue import io.github.config4k.getValue
import xyz.nulldev.ts.config.ConfigModule import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig) { class SystemConfigModule(val config: Config) : ConfigModule(config) {
val isDebuggable: Boolean by getConfig() val isDebuggable: Boolean by config
val propertyPrefix = "properties." val propertyPrefix = "properties."
fun getStringProperty(property: String) = getConfig().getString("$propertyPrefix$property")!! fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property")
fun getIntProperty(property: String) = getConfig().getInt("$propertyPrefix$property") fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property")
fun getLongProperty(property: String) = getConfig().getLong("$propertyPrefix$property") fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
fun getBooleanProperty(property: String) = getConfig().getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = getConfig().hasPath("$propertyPrefix$property")
companion object { companion object {
fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") } fun register(config: Config) =
SystemConfigModule(config.getConfig("android.system"))
} }
} }

View File

@@ -20,6 +20,7 @@ import java.util.Calendar
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
private val cachedContent = mutableListOf<ResultSetEntry>() private val cachedContent = mutableListOf<ResultSetEntry>()
private val columnCache = mutableMapOf<String, Int>() private val columnCache = mutableMapOf<String, Int>()
private var lastReturnWasNull = false private var lastReturnWasNull = false
@@ -28,10 +29,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
val parentMetadata = parent.metaData val parentMetadata = parent.metaData
val columnCount = parentMetadata.columnCount val columnCount = parentMetadata.columnCount
val columnLabels = val columnLabels = (1..columnCount).map {
(1..columnCount).map { parentMetadata.getColumnLabel(it)
parentMetadata.getColumnLabel(it) }.toTypedArray()
}.toTypedArray()
init { init {
val columnCount = columnCount val columnCount = columnCount
@@ -43,11 +43,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
// Fill cache // Fill cache
while (parent.next()) { while (parent.next()) {
cachedContent += cachedContent += ResultSetEntry().apply {
ResultSetEntry().apply { for (i in 1..columnCount)
for (i in 1..columnCount) data += parent.getObject(i)
data += parent.getObject(i) }
}
resultSetLength++ resultSetLength++
} }
} }
@@ -61,13 +60,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
} }
private fun internalMove(row: Int) { private fun internalMove(row: Int) {
if (cursor < 0) { if (cursor < 0) cursor = 0
cursor = 0 else if (cursor > resultSetLength + 1) cursor = resultSetLength + 1
} else if (cursor > resultSetLength + 1) { else cursor = row
cursor = resultSetLength + 1
} else {
cursor = row
}
} }
private fun obj(column: Int): Any? { private fun obj(column: Int): Any? {
@@ -93,121 +88,67 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as NClob return obj(columnLabel) as NClob
} }
override fun updateNString( override fun updateNString(columnIndex: Int, nString: String?) {
columnIndex: Int,
nString: String?,
) {
notImplemented() notImplemented()
} }
override fun updateNString( override fun updateNString(columnLabel: String?, nString: String?) {
columnLabel: String?,
nString: String?,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Int) {
columnIndex: Int,
x: InputStream?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Int) {
columnLabel: String?,
x: InputStream?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Long) {
columnIndex: Int,
x: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Long) {
columnLabel: String?,
x: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnIndex: Int, x: InputStream?) {
columnIndex: Int,
x: InputStream?,
) {
notImplemented() notImplemented()
} }
override fun updateBinaryStream( override fun updateBinaryStream(columnLabel: String?, x: InputStream?) {
columnLabel: String?,
x: InputStream?,
) {
notImplemented() notImplemented()
} }
override fun updateTimestamp( override fun updateTimestamp(columnIndex: Int, x: Timestamp?) {
columnIndex: Int,
x: Timestamp?,
) {
notImplemented() notImplemented()
} }
override fun updateTimestamp( override fun updateTimestamp(columnLabel: String?, x: Timestamp?) {
columnLabel: String?,
x: Timestamp?,
) {
notImplemented() notImplemented()
} }
override fun updateNCharacterStream( override fun updateNCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
columnIndex: Int,
x: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateNCharacterStream( override fun updateNCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
columnLabel: String?,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateNCharacterStream( override fun updateNCharacterStream(columnIndex: Int, x: Reader?) {
columnIndex: Int,
x: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateNCharacterStream( override fun updateNCharacterStream(columnLabel: String?, reader: Reader?) {
columnLabel: String?,
reader: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateInt( override fun updateInt(columnIndex: Int, x: Int) {
columnIndex: Int,
x: Int,
) {
notImplemented() notImplemented()
} }
override fun updateInt( override fun updateInt(columnLabel: String?, x: Int) {
columnLabel: String?,
x: Int,
) {
notImplemented() notImplemented()
} }
@@ -225,18 +166,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun getDate( override fun getDate(columnIndex: Int, cal: Calendar?): Date {
columnIndex: Int,
cal: Calendar?,
): Date {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun getDate( override fun getDate(columnLabel: String?, cal: Calendar?): Date {
columnLabel: String?,
cal: Calendar?,
): Date {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
@@ -246,17 +181,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun updateFloat( override fun updateFloat(columnIndex: Int, x: Float) {
columnIndex: Int,
x: Float,
) {
notImplemented() notImplemented()
} }
override fun updateFloat( override fun updateFloat(columnLabel: String?, x: Float) {
columnLabel: String?,
x: Float,
) {
notImplemented() notImplemented()
} }
@@ -272,18 +201,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursor - 1 < resultSetLength return cursor - 1 < resultSetLength
} }
override fun getBigDecimal( override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
columnIndex: Int,
scale: Int,
): BigDecimal {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun getBigDecimal( override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
columnLabel: String?,
scale: Int,
): BigDecimal {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
@@ -296,17 +219,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as BigDecimal return obj(columnLabel) as BigDecimal
} }
override fun updateBytes( override fun updateBytes(columnIndex: Int, x: ByteArray?) {
columnIndex: Int,
x: ByteArray?,
) {
notImplemented() notImplemented()
} }
override fun updateBytes( override fun updateBytes(columnLabel: String?, x: ByteArray?) {
columnLabel: String?,
x: ByteArray?,
) {
notImplemented() notImplemented()
} }
@@ -328,18 +245,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun getTime( override fun getTime(columnIndex: Int, cal: Calendar?): Time {
columnIndex: Int,
cal: Calendar?,
): Time {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun getTime( override fun getTime(columnLabel: String?, cal: Calendar?): Time {
columnLabel: String?,
cal: Calendar?,
): Time {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
@@ -382,11 +293,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
} }
override fun <T : Any?> unwrap(iface: Class<T>?): T { override fun <T : Any?> unwrap(iface: Class<T>?): T {
if (thisIsWrapperFor(iface)) { if (thisIsWrapperFor(iface))
return this as T return this as T
} else { else
return parent.unwrap(iface) return parent.unwrap(iface)
}
} }
override fun next(): Boolean { override fun next(): Boolean {
@@ -413,49 +323,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursorValid() return cursorValid()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Int) {
columnIndex: Int,
x: InputStream?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Int) {
columnLabel: String?,
x: InputStream?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Long) {
columnIndex: Int,
x: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Long) {
columnLabel: String?,
x: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnIndex: Int, x: InputStream?) {
columnIndex: Int,
x: InputStream?,
) {
notImplemented() notImplemented()
} }
override fun updateAsciiStream( override fun updateAsciiStream(columnLabel: String?, x: InputStream?) {
columnLabel: String?,
x: InputStream?,
) {
notImplemented() notImplemented()
} }
@@ -467,107 +355,61 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as URL return obj(columnLabel) as URL
} }
override fun updateShort( override fun updateShort(columnIndex: Int, x: Short) {
columnIndex: Int,
x: Short,
) {
notImplemented() notImplemented()
} }
override fun updateShort( override fun updateShort(columnLabel: String?, x: Short) {
columnLabel: String?,
x: Short,
) {
notImplemented() notImplemented()
} }
override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE
override fun updateNClob( override fun updateNClob(columnIndex: Int, nClob: NClob?) {
columnIndex: Int,
nClob: NClob?,
) {
notImplemented() notImplemented()
} }
override fun updateNClob( override fun updateNClob(columnLabel: String?, nClob: NClob?) {
columnLabel: String?,
nClob: NClob?,
) {
notImplemented() notImplemented()
} }
override fun updateNClob( override fun updateNClob(columnIndex: Int, reader: Reader?, length: Long) {
columnIndex: Int,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateNClob( override fun updateNClob(columnLabel: String?, reader: Reader?, length: Long) {
columnLabel: String?,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateNClob( override fun updateNClob(columnIndex: Int, reader: Reader?) {
columnIndex: Int,
reader: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateNClob( override fun updateNClob(columnLabel: String?, reader: Reader?) {
columnLabel: String?,
reader: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateRef( override fun updateRef(columnIndex: Int, x: Ref?) {
columnIndex: Int,
x: Ref?,
) {
notImplemented() notImplemented()
} }
override fun updateRef( override fun updateRef(columnLabel: String?, x: Ref?) {
columnLabel: String?,
x: Ref?,
) {
notImplemented() notImplemented()
} }
override fun updateObject( override fun updateObject(columnIndex: Int, x: Any?, scaleOrLength: Int) {
columnIndex: Int,
x: Any?,
scaleOrLength: Int,
) {
notImplemented() notImplemented()
} }
override fun updateObject( override fun updateObject(columnIndex: Int, x: Any?) {
columnIndex: Int,
x: Any?,
) {
notImplemented() notImplemented()
} }
override fun updateObject( override fun updateObject(columnLabel: String?, x: Any?, scaleOrLength: Int) {
columnLabel: String?,
x: Any?,
scaleOrLength: Int,
) {
notImplemented() notImplemented()
} }
override fun updateObject( override fun updateObject(columnLabel: String?, x: Any?) {
columnLabel: String?,
x: Any?,
) {
notImplemented() notImplemented()
} }
@@ -575,17 +417,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
internalMove(resultSetLength + 1) internalMove(resultSetLength + 1)
} }
override fun updateLong( override fun updateLong(columnIndex: Int, x: Long) {
columnIndex: Int,
x: Long,
) {
notImplemented() notImplemented()
} }
override fun updateLong( override fun updateLong(columnLabel: String?, x: Long) {
columnLabel: String?,
x: Long,
) {
notImplemented() notImplemented()
} }
@@ -599,47 +435,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnIndex: Int, x: Clob?) {
columnIndex: Int,
x: Clob?,
) {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnLabel: String?, x: Clob?) {
columnLabel: String?,
x: Clob?,
) {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnIndex: Int, reader: Reader?, length: Long) {
columnIndex: Int,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnLabel: String?, reader: Reader?, length: Long) {
columnLabel: String?,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnIndex: Int, reader: Reader?) {
columnIndex: Int,
reader: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateClob( override fun updateClob(columnLabel: String?, reader: Reader?) {
columnLabel: String?,
reader: Reader?,
) {
notImplemented() notImplemented()
} }
@@ -659,31 +475,19 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as String? return obj(columnLabel) as String?
} }
override fun updateSQLXML( override fun updateSQLXML(columnIndex: Int, xmlObject: SQLXML?) {
columnIndex: Int,
xmlObject: SQLXML?,
) {
notImplemented() notImplemented()
} }
override fun updateSQLXML( override fun updateSQLXML(columnLabel: String?, xmlObject: SQLXML?) {
columnLabel: String?,
xmlObject: SQLXML?,
) {
notImplemented() notImplemented()
} }
override fun updateDate( override fun updateDate(columnIndex: Int, x: Date?) {
columnIndex: Int,
x: Date?,
) {
notImplemented() notImplemented()
} }
override fun updateDate( override fun updateDate(columnLabel: String?, x: Date?) {
columnLabel: String?,
x: Date?,
) {
notImplemented() notImplemented()
} }
@@ -695,33 +499,21 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) return obj(columnLabel)
} }
override fun getObject( override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
columnIndex: Int,
map: MutableMap<String, Class<*>>?,
): Any {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun getObject( override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
columnLabel: String?,
map: MutableMap<String, Class<*>>?,
): Any {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun <T : Any?> getObject( override fun <T : Any?> getObject(columnIndex: Int, type: Class<T>?): T {
columnIndex: Int,
type: Class<T>?,
): T {
return obj(columnIndex) as T return obj(columnIndex) as T
} }
override fun <T : Any?> getObject( override fun <T : Any?> getObject(columnLabel: String?, type: Class<T>?): T {
columnLabel: String?,
type: Class<T>?,
): T {
return obj(columnLabel) as T return obj(columnLabel) as T
} }
@@ -730,30 +522,19 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursorValid() return cursorValid()
} }
override fun updateDouble( override fun updateDouble(columnIndex: Int, x: Double) {
columnIndex: Int,
x: Double,
) {
notImplemented() notImplemented()
} }
override fun updateDouble( override fun updateDouble(columnLabel: String?, x: Double) {
columnLabel: String?,
x: Double,
) {
notImplemented() notImplemented()
} }
private fun castToLong(obj: Any?): Long { private fun castToLong(obj: Any?): Long {
if (obj == null) { if (obj == null) return 0
return 0 else if (obj is Long) return obj
} else if (obj is Long) { else if (obj is Number) return obj.toLong()
return obj else throw IllegalStateException("Object is not a long!")
} else if (obj is Number) {
return obj.toLong()
} else {
throw IllegalStateException("Object is not a long!")
}
} }
override fun getLong(columnIndex: Int): Long { override fun getLong(columnIndex: Int): Long {
@@ -774,61 +555,35 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnIndex: Int, x: Blob?) {
columnIndex: Int,
x: Blob?,
) {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnLabel: String?, x: Blob?) {
columnLabel: String?,
x: Blob?,
) {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnIndex: Int, inputStream: InputStream?, length: Long) {
columnIndex: Int,
inputStream: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnLabel: String?, inputStream: InputStream?, length: Long) {
columnLabel: String?,
inputStream: InputStream?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnIndex: Int, inputStream: InputStream?) {
columnIndex: Int,
inputStream: InputStream?,
) {
notImplemented() notImplemented()
} }
override fun updateBlob( override fun updateBlob(columnLabel: String?, inputStream: InputStream?) {
columnLabel: String?,
inputStream: InputStream?,
) {
notImplemented() notImplemented()
} }
override fun updateByte( override fun updateByte(columnIndex: Int, x: Byte) {
columnIndex: Int,
x: Byte,
) {
notImplemented() notImplemented()
} }
override fun updateByte( override fun updateByte(columnLabel: String?, x: Byte) {
columnLabel: String?,
x: Byte,
) {
notImplemented() notImplemented()
} }
@@ -862,17 +617,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun updateString( override fun updateString(columnIndex: Int, x: String?) {
columnIndex: Int,
x: String?,
) {
notImplemented() notImplemented()
} }
override fun updateString( override fun updateString(columnLabel: String?, x: String?) {
columnLabel: String?,
x: String?,
) {
notImplemented() notImplemented()
} }
@@ -892,17 +641,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursor - 1 < resultSetLength return cursor - 1 < resultSetLength
} }
override fun updateBoolean( override fun updateBoolean(columnIndex: Int, x: Boolean) {
columnIndex: Int,
x: Boolean,
) {
notImplemented() notImplemented()
} }
override fun updateBoolean( override fun updateBoolean(columnLabel: String?, x: Boolean) {
columnLabel: String?,
x: Boolean,
) {
notImplemented() notImplemented()
} }
@@ -912,17 +655,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun rowUpdated() = false override fun rowUpdated() = false
override fun updateBigDecimal( override fun updateBigDecimal(columnIndex: Int, x: BigDecimal?) {
columnIndex: Int,
x: BigDecimal?,
) {
notImplemented() notImplemented()
} }
override fun updateBigDecimal( override fun updateBigDecimal(columnLabel: String?, x: BigDecimal?) {
columnLabel: String?,
x: BigDecimal?,
) {
notImplemented() notImplemented()
} }
@@ -942,17 +679,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return getBinaryStream(columnLabel) return getBinaryStream(columnLabel)
} }
override fun updateTime( override fun updateTime(columnIndex: Int, x: Time?) {
columnIndex: Int,
x: Time?,
) {
notImplemented() notImplemented()
} }
override fun updateTime( override fun updateTime(columnLabel: String?, x: Time?) {
columnLabel: String?,
x: Time?,
) {
notImplemented() notImplemented()
} }
@@ -966,18 +697,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented() notImplemented()
} }
override fun getTimestamp( override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
columnIndex: Int,
cal: Calendar?,
): Timestamp {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
override fun getTimestamp( override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
columnLabel: String?,
cal: Calendar?,
): Timestamp {
// TODO Maybe? // TODO Maybe?
notImplemented() notImplemented()
} }
@@ -994,17 +719,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY
override fun updateRowId( override fun updateRowId(columnIndex: Int, x: RowId?) {
columnIndex: Int,
x: RowId?,
) {
notImplemented() notImplemented()
} }
override fun updateRowId( override fun updateRowId(columnLabel: String?, x: RowId?) {
columnLabel: String?,
x: RowId?,
) {
notImplemented() notImplemented()
} }
@@ -1016,17 +735,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return getBinaryStream(columnLabel).reader() return getBinaryStream(columnLabel).reader()
} }
override fun updateArray( override fun updateArray(columnIndex: Int, x: Array?) {
columnIndex: Int,
x: Array?,
) {
notImplemented() notImplemented()
} }
override fun updateArray( override fun updateArray(columnLabel: String?, x: Array?) {
columnLabel: String?,
x: Array?,
) {
notImplemented() notImplemented()
} }
@@ -1091,13 +804,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun getMetaData(): ResultSetMetaData { override fun getMetaData(): ResultSetMetaData {
return object : ResultSetMetaData by parentMetadata { return object : ResultSetMetaData by parentMetadata {
override fun isReadOnly(column: Int) = true override fun isReadOnly(column: Int) = true
override fun isWritable(column: Int) = false override fun isWritable(column: Int) = false
override fun isDefinitelyWritable(column: Int) = false override fun isDefinitelyWritable(column: Int) = false
override fun getColumnCount() = this@ScrollableResultSet.columnCount override fun getColumnCount() = this@ScrollableResultSet.columnCount
override fun getColumnLabel(column: Int): String { override fun getColumnLabel(column: Int): String {
return columnLabels[column - 1] return columnLabels[column - 1]
} }
@@ -1112,49 +821,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return (obj(columnLabel) as ByteArray).inputStream() return (obj(columnLabel) as ByteArray).inputStream()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Int) {
columnIndex: Int,
x: Reader?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Int) {
columnLabel: String?,
reader: Reader?,
length: Int,
) {
notImplemented() notImplemented()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
columnIndex: Int,
x: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
columnLabel: String?,
reader: Reader?,
length: Long,
) {
notImplemented() notImplemented()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnIndex: Int, x: Reader?) {
columnIndex: Int,
x: Reader?,
) {
notImplemented() notImplemented()
} }
override fun updateCharacterStream( override fun updateCharacterStream(columnLabel: String?, reader: Reader?) {
columnLabel: String?,
reader: Reader?,
) {
notImplemented() notImplemented()
} }

View File

@@ -9,8 +9,8 @@ package xyz.nulldev.androidcompat.io.sharedprefs
import android.content.SharedPreferences import android.content.SharedPreferences
import com.russhwolf.settings.ExperimentalSettingsApi import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.PropertiesSettings import com.russhwolf.settings.ExperimentalSettingsImplementation
import com.russhwolf.settings.Settings import com.russhwolf.settings.JvmPreferencesSettings
import com.russhwolf.settings.serialization.decodeValue import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue import com.russhwolf.settings.serialization.encodeValue
@@ -18,68 +18,21 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.SetSerializer import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import mu.KotlinLogging import java.util.prefs.PreferenceChangeListener
import xyz.nulldev.androidcompat.util.SafePath import java.util.prefs.Preferences
import xyz.nulldev.ts.config.ApplicationRootDir
import java.util.Properties
import kotlin.io.path.Path
import kotlin.io.path.createParentDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.outputStream
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) @OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(key: String) : SharedPreferences { class JavaSharedPreferences(key: String) : SharedPreferences {
companion object { private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
private val logger = KotlinLogging.logger {} private val preferences = JvmPreferencesSettings(javaPreferences)
} private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
private val file =
Path(
ApplicationRootDir,
"settings",
"${SafePath.buildValidFilename(key)}.xml",
)
private val properties =
Properties().also { properties ->
try {
if (file.exists()) {
file.inputStream().use { properties.loadFromXML(it) }
}
} catch (e: Exception) {
logger.error(e) { "Error loading settings from $key" }
}
}
private val preferences =
PropertiesSettings(
properties,
onModify = { properties ->
try {
if (properties.isEmpty) {
file.deleteIfExists()
} else {
file.createParentDirectories()
file.outputStream().use {
properties.storeToXML(it, null)
}
}
} catch (e: Exception) {
logger.error(e) { "Error saving settings in $key" }
}
},
)
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
// TODO: 2021-05-29 Need to find a way to get this working with all pref types // TODO: 2021-05-29 Need to find a way to get this working with all pref types
override fun getAll(): MutableMap<String, *> { override fun getAll(): MutableMap<String, *> {
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap() return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
} }
override fun getString( override fun getString(key: String, defValue: String?): String? {
key: String,
defValue: String?,
): String? {
return if (defValue != null) { return if (defValue != null) {
preferences.getString(key, defValue) preferences.getString(key, defValue)
} else { } else {
@@ -87,10 +40,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
} }
} }
override fun getStringSet( override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
key: String,
defValues: Set<String>?,
): Set<String>? {
try { try {
return if (defValues != null) { return if (defValues != null) {
preferences.decodeValue(SetSerializer(String.serializer()), key, defValues) preferences.decodeValue(SetSerializer(String.serializer()), key, defValues)
@@ -102,31 +52,19 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
} }
} }
override fun getInt( override fun getInt(key: String, defValue: Int): Int {
key: String,
defValue: Int,
): Int {
return preferences.getInt(key, defValue) return preferences.getInt(key, defValue)
} }
override fun getLong( override fun getLong(key: String, defValue: Long): Long {
key: String,
defValue: Long,
): Long {
return preferences.getLong(key, defValue) return preferences.getLong(key, defValue)
} }
override fun getFloat( override fun getFloat(key: String, defValue: Float): Float {
key: String,
defValue: Float,
): Float {
return preferences.getFloat(key, defValue) return preferences.getFloat(key, defValue)
} }
override fun getBoolean( override fun getBoolean(key: String, defValue: Boolean): Boolean {
key: String,
defValue: Boolean,
): Boolean {
return preferences.getBoolean(key, defValue) return preferences.getBoolean(key, defValue)
} }
@@ -135,86 +73,60 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
} }
override fun edit(): SharedPreferences.Editor { override fun edit(): SharedPreferences.Editor {
return Editor(preferences) { key -> return Editor(preferences)
listeners.forEach { (_, listener) ->
listener(key)
}
}
} }
class Editor(private val preferences: Settings, private val notify: (String) -> Unit) : SharedPreferences.Editor { class Editor(private val preferences: JvmPreferencesSettings) : SharedPreferences.Editor {
private val actions = mutableListOf<Action>() val itemsToAdd = mutableMapOf<String, Any>()
private sealed class Action { override fun putString(key: String, value: String?): SharedPreferences.Editor {
data class Add(val key: String, val value: Any) : Action()
data class Remove(val key: String) : Action()
data object Clear : Action()
}
override fun putString(
key: String,
value: String?,
): SharedPreferences.Editor {
if (value != null) { if (value != null) {
actions += Action.Add(key, value) itemsToAdd[key] = value
} else { } else {
actions += Action.Remove(key) remove(key)
} }
return this return this
} }
override fun putStringSet( override fun putStringSet(
key: String, key: String,
values: MutableSet<String>?, values: MutableSet<String>?
): SharedPreferences.Editor { ): SharedPreferences.Editor {
if (values != null) { if (values != null) {
actions += Action.Add(key, values) itemsToAdd[key] = values
} else { } else {
actions += Action.Remove(key) remove(key)
} }
return this return this
} }
override fun putInt( override fun putInt(key: String, value: Int): SharedPreferences.Editor {
key: String, itemsToAdd[key] = value
value: Int,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this return this
} }
override fun putLong( override fun putLong(key: String, value: Long): SharedPreferences.Editor {
key: String, itemsToAdd[key] = value
value: Long,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this return this
} }
override fun putFloat( override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
key: String, itemsToAdd[key] = value
value: Float,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this return this
} }
override fun putBoolean( override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
key: String, itemsToAdd[key] = value
value: Boolean,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this return this
} }
override fun remove(key: String): SharedPreferences.Editor { override fun remove(key: String): SharedPreferences.Editor {
actions += Action.Remove(key) itemsToAdd.remove(key)
return this return this
} }
override fun clear(): SharedPreferences.Editor { override fun clear(): SharedPreferences.Editor {
actions.add(Action.Clear) itemsToAdd.clear()
return this return this
} }
@@ -228,56 +140,38 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
} }
private fun addToPreferences() { private fun addToPreferences() {
actions.forEach { itemsToAdd.forEach { (key, value) ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
when (it) { when (value) {
is Action.Add -> { is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set<String>)
when (val value = it.value) { is String -> preferences.putString(key, value)
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>) is Int -> preferences.putInt(key, value)
is String -> preferences.putString(it.key, value) is Long -> preferences.putLong(key, value)
is Int -> preferences.putInt(it.key, value) is Float -> preferences.putFloat(key, value)
is Long -> preferences.putLong(it.key, value) is Double -> preferences.putDouble(key, value)
is Float -> preferences.putFloat(it.key, value) is Boolean -> preferences.putBoolean(key, value)
is Double -> preferences.putDouble(it.key, value)
is Boolean -> preferences.putBoolean(it.key, value)
}
notify(it.key)
}
is Action.Remove -> {
preferences.remove(it.key)
/**
* Set<String> are stored like
* key.0 = value1
* key.1 = value2
* key.size = 2
*/
preferences.keys.forEach { key ->
if (key.startsWith(it.key + ".")) {
preferences.remove(key)
}
}
notify(it.key)
}
Action.Clear -> preferences.clear()
} }
} }
} }
} }
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
val javaListener: (String) -> Unit = { val javaListener = PreferenceChangeListener {
listener.onSharedPreferenceChanged(this, it) listener.onSharedPreferenceChanged(this, it.key)
} }
listeners[listener] = javaListener listeners[listener] = javaListener
javaPreferences.addPreferenceChangeListener(javaListener)
} }
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
listeners.remove(listener) val registeredListener = listeners.remove(listener)
if (registeredListener != null) {
javaPreferences.removePreferenceChangeListener(registeredListener)
}
} }
fun deleteAll(): Boolean { fun deleteAll(): Boolean {
preferences.clear() javaPreferences.removeNode()
return true return true
} }
} }

View File

@@ -20,47 +20,42 @@ data class InstalledPackage(val root: File) {
val icon = File(root, "icon.png") val icon = File(root, "icon.png")
val info: PackageInfo val info: PackageInfo
get() = get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also { val parsed = ApkFile(apk)
val parsed = ApkFile(apk) val dbFactory = DocumentBuilderFactory.newInstance()
val dbFactory = DocumentBuilderFactory.newInstance() val dBuilder = dbFactory.newDocumentBuilder()
val dBuilder = dbFactory.newDocumentBuilder() val doc = parsed.manifestXml.byteInputStream().use {
val doc = dBuilder.parse(it)
parsed.manifestXml.byteInputStream().use {
dBuilder.parse(it)
}
it.applicationInfo.metaData =
Bundle().apply {
val appTag = doc.getElementsByTagName("application").item(0)
appTag?.childNodes?.toList()?.filter {
it.nodeType == Node.ELEMENT_NODE
}?.map {
it as Element
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(
it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue,
)
}
}
it.signatures =
(
parsed.apkSingers.flatMap { it.certificateMetas }
// + parsed.apkV2Singers.flatMap { it.certificateMetas }
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
.map { Signature(it.data) }.toTypedArray()
} }
it.applicationInfo.metaData = Bundle().apply {
val appTag = doc.getElementsByTagName("application").item(0)
appTag?.childNodes?.toList()?.filter {
it.nodeType == Node.ELEMENT_NODE
}?.map {
it as Element
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(
it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue
)
}
}
it.signatures = (
parsed.apkSingers.flatMap { it.certificateMetas }
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
.map { Signature(it.data) }.toTypedArray()
}
fun verify(): Boolean { fun verify(): Boolean {
val res = val res = ApkVerifier.Builder(apk)
ApkVerifier.Builder(apk) .build()
.build() .verify()
.verify()
return res.isVerified return res.isVerified
} }
@@ -69,12 +64,11 @@ data class InstalledPackage(val root: File) {
try { try {
val icons = ApkFile(apk).allIcons val icons = ApkFile(apk).allIcons
val read = val read = icons.filter { it.isFile }.map {
icons.filter { it.isFile }.map { it.data.inputStream().use {
it.data.inputStream().use { ImageIO.read(it)
ImageIO.read(it) }
} }.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
ImageIO.write(read, "png", icon) ImageIO.write(read, "png", icon)
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -25,10 +25,7 @@ class PackageController {
return File(androidFiles.packagesDir, pn) return File(androidFiles.packagesDir, pn)
} }
fun installPackage( fun installPackage(apk: File, allowReinstall: Boolean) {
apk: File,
allowReinstall: Boolean,
) {
val root = findRoot(apk) val root = findRoot(apk)
if (root.exists()) { if (root.exists()) {
@@ -77,11 +74,10 @@ class PackageController {
fun findPackage(packageName: String): InstalledPackage? { fun findPackage(packageName: String): InstalledPackage? {
val file = File(androidFiles.packagesDir, packageName) val file = File(androidFiles.packagesDir, packageName)
return if (file.exists()) { return if (file.exists())
InstalledPackage(file) InstalledPackage(file)
} else { else
null null
}
} }
fun findJarFromApk(apkFile: File): File? { fun findJarFromApk(apkFile: File): File? {

View File

@@ -12,18 +12,16 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
it.versionCode = versionCode.toInt() it.versionCode = versionCode.toInt()
it.versionName = versionName it.versionName = versionName
it.reqFeatures = it.reqFeatures = usesFeatures.map {
usesFeatures.map { FeatureInfo().apply {
FeatureInfo().apply { name = it.name
name = it.name
}
}.toTypedArray()
it.applicationInfo =
ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
} }
}.toTypedArray()
it.applicationInfo = ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
}
} }
} }

View File

@@ -18,10 +18,7 @@ class ServiceSupport {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
fun startService( fun startService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
val name = intentToClassName(intent) val name = intentToClassName(intent)
logger.debug { "Starting service: $name" } logger.debug { "Starting service: $name" }
@@ -38,10 +35,7 @@ class ServiceSupport {
} }
} }
fun stopService( fun stopService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
val name = intentToClassName(intent) val name = intentToClassName(intent)
stopService(name) stopService(name)
} }

View File

@@ -26,10 +26,7 @@ object KodeinGlobalHelper {
*/ */
@JvmStatic @JvmStatic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : Any> instance( fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
type: Class<T>,
kodein: DI? = null,
): T {
return when (type) { return when (type) {
AndroidFiles::class.java -> { AndroidFiles::class.java -> {
val instance: AndroidFiles by (kodein ?: kodein()).instance() val instance: AndroidFiles by (kodein ?: kodein()).instance()

View File

@@ -24,7 +24,5 @@ import java.net.URI
* Utilites to convert between Java and Android Uris. * Utilites to convert between Java and Android Uris.
*/ */
fun Uri.java() = URI(this.toString()) fun Uri.java() = URI(this.toString())
fun Uri.file() = File(this.path) fun Uri.file() = File(this.path)
fun URI.android() = Uri.parse(this.toString())!! fun URI.android() = Uri.parse(this.toString())!!

View File

@@ -1,103 +0,0 @@
package xyz.nulldev.androidcompat.webkit
import android.webkit.CookieManager
import android.webkit.ValueCallback
import android.webkit.WebView
import java.net.CookieHandler
import java.net.HttpCookie
import java.net.URI
@Suppress("DEPRECATION")
class CookieManagerImpl : CookieManager() {
private val cookieHandler = CookieHandler.getDefault() as java.net.CookieManager
private var acceptCookie = true
private var acceptThirdPartyCookies = true
private var allowFileSchemeCookies = false
override fun setAcceptCookie(accept: Boolean) {
acceptCookie = accept
}
override fun acceptCookie(): Boolean {
return acceptCookie
}
override fun setAcceptThirdPartyCookies(
webview: WebView?,
accept: Boolean,
) {
acceptThirdPartyCookies = accept
}
override fun acceptThirdPartyCookies(webview: WebView?): Boolean {
return acceptThirdPartyCookies
}
override fun setCookie(
url: String,
value: String?,
) {
val uri =
if (url.startsWith("http")) {
URI(url)
} else {
URI("http://$url")
}
HttpCookie.parse(value).forEach {
cookieHandler.cookieStore.add(uri, it)
}
}
override fun setCookie(
url: String,
value: String?,
callback: ValueCallback<Boolean>?,
) {
setCookie(url, value)
callback?.onReceiveValue(true)
}
override fun getCookie(url: String): String {
val uri =
if (url.startsWith("http")) {
URI(url)
} else {
URI("http://$url")
}
return cookieHandler.cookieStore.get(uri)
.joinToString("; ") { "${it.name}=${it.value}" }
}
@Deprecated("Deprecated in Java")
override fun removeSessionCookie() {}
override fun removeSessionCookies(callback: ValueCallback<Boolean>?) {}
@Deprecated("Deprecated in Java")
override fun removeExpiredCookie() {}
@Deprecated("Deprecated in Java")
override fun removeAllCookie() {
cookieHandler.cookieStore.removeAll()
}
override fun removeAllCookies(callback: ValueCallback<Boolean>?) {
val removedCookies = cookieHandler.cookieStore.removeAll()
callback?.onReceiveValue(removedCookies)
}
override fun hasCookies(): Boolean {
return cookieHandler.cookieStore.cookies.isNotEmpty()
}
override fun flush() {}
override fun allowFileSchemeCookiesImpl(): Boolean {
return allowFileSchemeCookies
}
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
allowFileSchemeCookies = acceptCookie
}
}

View File

@@ -2,10 +2,10 @@
## TL;DR ## TL;DR
- N/A - N/A
## Suwayomi-Server Changelog ## Tachidesk-Server Changelog
- N/A - N/A
## Suwayomi-WebUI Changelog ## Tachidesk-WebUI Changelog
- N/A - N/A

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,31 @@
# Contributing # Contributing
## Where should I start? ## Where should I start?
Checkout [This Kanban Board](https://github.com/Suwayomi/Suwayomi-Server/projects/1) to see the rough development roadmap. Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
### Important notes **Note 1:** Notify the developers on [Suwayomi discord](https://discord.gg/DDZdqZWaHA) (#tachidesk-server and #tachidesk-webui channels) or open a WIP pull request before starting if you decide to take on working on anything from/not from the roadmap in order to avoid parallel efforts on the same issue/feature.
- Notify the developers on [Suwayomi discord](https://discord.gg/DDZdqZWaHA) (#tachidesk-server and #tachidesk-webui channels) or open a WIP pull request before starting if you decide to take on working on anything from/not from the roadmap in order to avoid parallel efforts on the same issue/feature.
- Your pull request will be squashed into a single commit. **Note 2:** Your pull request will be squashed into a single commit.
- We hate big pull requests, make them as small as possible, change one meaningful thing. Spam pull requests, we don't mind.
### Project goals and vision ### Project goals and vision
- Porting Tachiyomi and covering its features - Porting Tachiyomi and covering it's features
- Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159) - Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159)
- Generally rejecting features that Tachiyomi(main app) doesn't have, - Generally rejecting features that Tachiyomi(main app) doesn't have,
- Unless it's something that makes sense for desktop sizes or desktop form factor (keyboard + mouse) - Unless it's something that makes sense for desktop sizes or desktop form factor (keyboard + mouse)
- Additional/crazy features can go in forks and alternative clients - Additional/crazy features can go in forks and alternative clients
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) should - [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) should
- be responsive - be responsive
- support both desktop and mobile form factors well - support both desktop and mobile form factors well
## How does Suwayomi-Server work? ## How does Tachidesk-Server work?
This project has two components: This project has two components:
1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a GraphQL API. 1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a REST API.
2. **WebUI:** A React SPA(`create-react-app`) project that works with the server to do the presentation located at https://github.com/Suwayomi/Suwayomi-WebUI 2. **WebUI:** A React SPA(`create-react-app`) project that works with the server to do the presentation located at https://github.com/Suwayomi/Tachidesk-WebUI
### API
#### GraphQL
*Only available in the preview at the moment*
The GraphQL API can be queried with a POST request to `/api/graphql`. There is also the GraphiQL IDE accessible by the browser at `/api/graphql` to perform ad-hoc queries and explore the API.
#### REST
> [!WARNING]
>
> Soon to be deprecated
The REST API can be queried at `/api/v1`. An interactive Swagger API explorer is available at `/api/swagger-ui`.
### Tracker client authorization
#### OAuth
Since the url of a Suwayomi-Server is not known, it is not possible to redirect directly to the client.<br/>
Thus, to provide tracker support via oauth, the tracker clients redirect to the [suwayomi website](https://suwayomi.org/)
and there the actual redirection to the client takes place.
When implementing the login process in your client you have to make sure to follow some preconditions:
To be able to redirect to the client you have to attach a `state` object to the query of the auth url
- this `state` object has to have a `redirectUrl` which points to the client route at which you want to handle the auth result
- besides the `redirectUrl` you can pass any information you require to handle the result (e.g. the server `id` of the tracker client)
- example URL for AniList: `https://anilist.co/api/v2/oauth/authorize?client_id=ID&response_type=token&state={ redirectUrl: "http://localhost:4567/handle/oauth/result", trackerId: 1, anyOtherInfo: "your client requires" }`
Once the permission has been granted, you will get redirected to the client at the provided route (`redirectUrl`).<br/>
- Example URL (decoded) for AniList: `http://localhost:4567/handle/oauth/result?access_token=TOKEN&token_type=Bearer&expires_in=31622400&state={ redirectUrl: "http://localhost:4567/handle/oauth/result", trackerId: 1, anyOtherInfo: "your client requires" }`).<br/>
Finally, to finish the login process, you just have to pass this URL to the server as the `callbackUrl`.
## Why a web app? ## Why a web app?
This structure is chosen to This structure is chosen to
- Achieve the maximum multi-platform-ness - Achieve the maximum multi-platform-ness
- Gives the ability to access Suwayomi-Server from a remote client e.g., your phone, tablet or smart TV - Gives the ability to access Tachidesk-Server from a remote client e.g., your phone, tablet or smart TV
- Ease development of user interfaces for Suwayomi - Ease development of user interfaces for Tachidesk
## Building from source ## Building from source
### Prerequisites ### Prerequisites
@@ -65,14 +33,14 @@ You need these software packages installed in order to build the project
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works) - Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle) ### building the full-blown jar (Tachidesk-Server + Tachidesk-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/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
### building without `webUI` bundled (server only) ### building without `webUI` bundled (server only)
Delete `server/src/main/resources/WebUI.zip` if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`. Delete `server/src/main/resources/WebUI.zip` if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
### building the Windows package ### building the Windows package
First Build the jar, then cd into the `scripts` directory and run `./windows-bundler.sh win32` or `./windows-bundler.sh win64` depending on the target architecture, the resulting built zip package file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx-winXX.zip`. First Build the jar, then cd into the `scripts` directory and run `./windows-bundler.sh win32` or `./windows-bundler.sh win64` depending on the target architecture, the resulting built zip package file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx-winXX.zip`.
## Running in development mode ## Running in development mode
run `./gradlew :server:run --stacktrace` to run the server run `./gradlew :server:run --stacktrace` to run the server

122
README.md
View File

@@ -1,11 +1,11 @@
| Build | Stable | Preview | Support Server | | Build | Stable | Preview | Support Server |
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| |-------|----------|---------|---------|
| ![CI](https://github.com/Suwayomi/Suwayomi-Server/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Suwayomi-Server.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Suwayomi-Server/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Suwayomi-Server-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Suwayomi-Server-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) | | ![CI](https://github.com/Suwayomi/Tachidesk/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Tachidesk.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Tachidesk/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Tachidesk-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) |
## Table of Content ## Table of Content
- [What is Suwayomi?](#what-is-suwayomi) - [What is Tachidesk?](#what-is-tachidesk)
- [Suwayomi client projects](#Suwayomi-client-projects) - [Tachidesk client projects](#tachidesk-client-projects)
* [Is this application usable? Should I test it?](#is-this-application-usable-should-i-test-it) * [Is this application usable? Should I test it?](#is-this-application-usable-should-i-test-it)
- [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)
@@ -13,13 +13,13 @@
+ [Windows](#windows) + [Windows](#windows)
+ [macOS](#macos) + [macOS](#macos)
+ [GNU/Linux](#gnulinux) + [GNU/Linux](#gnulinux)
* [Other methods of getting Suwayomi](#other-methods-of-getting-suwayomi) * [Other methods of getting Tachidesk](#other-methods-of-getting-tachidesk)
+ [Arch Linux](#arch-linux) + [Arch Linux](#arch-linux)
+ [Ubuntu-based distributions](#ubuntu-based-distributions) + [Ubuntu-based distributions](#ubuntu-based-distributions)
+ [Docker](#docker) + [Docker](#docker)
* [Advanced Methods](#advanced-methods) * [Advanced Methods](#advanced-methods)
+ [Running the jar release directly](#running-the-jar-release-directly) + [Running the jar release directly](#running-the-jar-release-directly)
+ [Using Suwayomi Remotely](#using-suwayomi-remotely) + [Using Tachidesk Remotely](#using-tachidesk-remotely)
- [Syncing With Tachiyomi](#syncing-with-tachiyomi) - [Syncing With Tachiyomi](#syncing-with-tachiyomi)
- [Troubleshooting and Support](#troubleshooting-and-support) - [Troubleshooting and Support](#troubleshooting-and-support)
- [Contributing and Technical info](#contributing-and-technical-info) - [Contributing and Technical info](#contributing-and-technical-info)
@@ -27,31 +27,32 @@
- [License](#license) - [License](#license)
<!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ --> <!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ -->
# What is Suwayomi? # What is Tachidesk?
<img src="https://github.com/Suwayomi/Suwayomi-Server/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/> <img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
A free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org/). A free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
Suwayomi is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi. Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
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. `Tachidesk` is a general term used to describe the combination of Tachidesk-Server(this project) and one of our clients.
Think of it roughly like the concept of "distribution" in GNU/Linux distributions, in which Linux(Tachidesk-Server) is the kernel and the difference is which desktop environment(Tachidesk client) you get with it.
Ability to sync with Tachiyomi is a planned feature, for more info look [here](#syncing-with-tachiyomi). Tachidesk-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 client projects Ability to sync with Tachiyomi is a planned feature.
**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: # Tachidesk client projects
##### Actively Developed Clients **You need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.**
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web/ElectronJS front-end that Suwayomi-Server ships with by default.
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-Server. Currently, the most advanced. Here's a list of known clients/user interfaces for Tachidesk-Server:
- [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 Tachiyomi. ##### Actively Developed Cients
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin. - [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server is traditionally shipped with. Usually gets new features faster.
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A preview focused web frontend built with svelte with some features the other UIs might not have (migration) - [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Tachidesk-Server. Currently the most advanced.
##### Inctive/Abandoned Clients - [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic. - [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android. UI and UX similar to Tachiyomi.
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client. ##### Inctive/Abandoned Cients
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js. - [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client, in super early stage of development.
## Is this application usable? Should I test it? ## Is this application usable? Should I test it?
Here is a list of current features: Here is a list of current features:
@@ -63,32 +64,39 @@ Here is a list of current features:
- Backup and restore support powered by Tachiyomi-compatible Backups - Backup and restore support powered by Tachiyomi-compatible Backups
- Viewing latest updated chapters. - Viewing latest updated chapters.
**Note:** These are capabilities of Suwayomi-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info. **Note:** These are capabilities of Tachidesk-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info.
# Downloading and Running the app # Downloading and Running the app
## Using Operating System Specific Bundles ## Using Operating System Specific Bundles
To facilitate the use of Suwayomi we provide bundle releases that include The Java Runtime Environment, ElectronJS and the Suwayomi-Launcher. To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods) If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
#### Launcher Scripts
- `Tachidesk Electron Launcher`: Launches Tachidesk inside Electron as a desktop applicaton
- `Tachidesk Browser Launcher`: Launches Tachidesk in a browser window
- `Tachidesk Debug Launcher`: Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why.
**Node:** Linux launcher scripts are named a bit differently but work the same.
### Windows ### Windows
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases). Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
Unzip the downloaded file and double-click on one of the launcher scripts. Unzip the downloaded file and double click on one of the launcher scripts.
### macOS ### macOS
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1 and newer) release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases). Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
Unzip the downloaded file and double-click on one of the launcher scripts. Unzip the downloaded file and double click on one of the launcher scripts.
### GNU/Linux ### GNU/Linux
Download the latest `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases). Download the latest `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
`tar xvf` the downloaded file and double-click on one of the launcher scripts or run them using the terminal. `tar xvf` the downloaded file and double click on one of the launcher scripts or run them using the terminal.
## Other methods of getting Suwayomi ## Other methods of getting Tachidesk
### Arch Linux ### Arch Linux
You can install Suwayomi from the AUR: You can install Tachidesk from the AUR:
``` ```
yay -S tachidesk yay -S tachidesk
``` ```
@@ -96,20 +104,20 @@ yay -S tachidesk
### Debian/Ubuntu ### Debian/Ubuntu
Download the latest deb package from the release section or Install from the MPR Download the latest deb package from the release section or Install from the MPR
``` ```
git clone https://mpr.makedeb.org/suwayomi-server.git git clone https://mpr.makedeb.org/tachidesk-server.git
cd suwayomi-server cd tachidesk-server
makedeb -si makedeb -si
``` ```
### Ubuntu ### Ubuntu
``` ```
sudo add-apt-repository ppa:suwayomi/suwayomi-server sudo add-apt-repository ppa:suwayomi/tachidesk-server
sudo apt update sudo apt update
sudo apt install suwayomi-server sudo apt install tachidesk-server
``` ```
### Docker ### Docker
Check our Official Docker release [Suwayomi Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Suwayomi Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default, the server will be running on http://localhost:4567 open this url in your browser. Check our Official Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Tachidesk Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default the server will be running on http://localhost:4567 open this url in your browser.
Install from the command line: Install from the command line:
``` ```
@@ -123,35 +131,33 @@ Run Container from the command line:
## Advanced Methods ## Advanced Methods
### Running the jar release directly ### Running the jar release directly
In order to run the app you need the following: In order to run the app you need the following:
- The jar release of Suwayomi-Server - The jar release of Tachidesk-Server
- The Java Runtime Environment(JRE) 8 or newer - The Java Runtime Environment(JRE) 8 or newer
- A Browser like Google Chrome, Firefox, Edge, etc. - A Browser like Google Chrome, Firefox, Edge, etc.
- ElectronJS (optional) - ElectronJS (optional)
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases). Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
Make sure you have The Java Runtime Environment installed on your system, Double-click on the jar file or run `java -jar Suwayomi-Server-vX.Y.Z-rxxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically. Make sure you have The Java Runtime Environment installed on your system, Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically.
### Using Suwayomi Remotely ### Using Tachidesk Remotely
You can run Suwayomi on your computer or a server and connect to it remotely through one of our clients or the bundled web interface with a web browser. This method of using Suwayomi is requiring a bit of networking/firewall/port forwarding/server configuration/etc. knowledge on your side, if you can run a Minecraft server and configure it, then you are good to go. You can run Tachidesk on your computer or a server and connect to it remotely through one of our clients or the bundled web interface with a web browser. This method of using Tachidesk is requires a bit of networking/firewall/port forwarding/server configuration/etc. knowledge on your side, if you can run a Minecraft server and configure it, then you are good to go.
Check out [this wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Suwayomi-Server. Check out [this wiki page](https://github.com/Suwayomi/Tachidesk-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Tachidesk-Server.
If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click-clack away!). If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click clack away!).
## Syncing With Tachiyomi ## Syncing With Tachiyomi
### The Suwayomi extension and tracker ### The Tachidesk extension
- You can install the `Suwayomi` extension inside tachiyomi. - You can install the `Tachidesk` extension inside tachiyomi.
- The extension will load your Suwayomi library. - The extension will load Tachidesk library.
- By manipulating extension search filters you can browse your categories. - By manipulating filters you can browse your categories.
- You can enable the Suwayomi tracker to track reading progress with your Suwayomi server.
- Note: Tachiyomi [only allows tracking one way](https://github.com/tachiyomiorg/tachiyomi/issues/1626), meaning that by reading chapters on other Suwayomi clients the last read chapter number will update on the tracker but tachiyomi won't automatically mark them as read for you.
### Other methods ### Other methods
Checkout [this issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159) for tracking progress. Checkout [this issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159) for tracking progress.
## Troubleshooting and Support ## Troubleshooting and Support
See [this troubleshooting wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Troubleshooting). See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
## Contributing and Technical info ## Contributing and Technical info
See [CONTRIBUTING.md](./CONTRIBUTING.md). See [CONTRIBUTING.md](./CONTRIBUTING.md).
@@ -174,7 +180,3 @@ Changes to both codebases is licensed under `MPL v. 2.0` as the rest of this pro
This Source Code Form is subject to the terms of the Mozilla Public This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Disclaimer
The developer of this application does not have any affiliation with the content providers available.

View File

@@ -1,13 +1,12 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jlleitschuh.gradle.ktlint.KtlintExtension import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jlleitschuh.gradle.ktlint.KtlintPlugin import org.jmailen.gradle.kotlinter.tasks.LintTask
plugins { plugins {
alias(libs.plugins.kotlin.jvm) kotlin("jvm") version kotlinVersion
alias(libs.plugins.kotlin.serialization) kotlin("plugin.serialization") version kotlinVersion
alias(libs.plugins.ktlint) id("org.jmailen.kotlinter") version "3.8.0"
alias(libs.plugins.buildconfig) apply false id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
alias(libs.plugins.download)
} }
allprojects { allprojects {
@@ -18,38 +17,99 @@ allprojects {
repositories { repositories {
mavenCentral() mavenCentral()
google() google()
maven("https://github.com/Suwayomi/Suwayomi-Server/raw/android-jar/")
maven("https://jitpack.io") maven("https://jitpack.io")
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
} }
} }
subprojects { val projects = listOf(
plugins.withType<JavaPlugin> { project(":AndroidCompat"),
extensions.configure<JavaPluginExtension> { project(":AndroidCompat:Config"),
sourceCompatibility = JavaVersion.VERSION_1_8 project(":server")
targetCompatibility = JavaVersion.VERSION_1_8 )
}
}
plugins.withType<KtlintPlugin> { configure(projects) {
extensions.configure<KtlintExtension>("ktlint") { apply(plugin = "org.jetbrains.kotlin.jvm")
version.set(libs.versions.ktlint.get()) apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
filter { apply(plugin = "org.jmailen.kotlinter")
exclude("**/generated/**")
} java {
} sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
} }
tasks { tasks {
withType<KotlinJvmCompile> { withType<KotlinCompile> {
dependsOn("ktlintFormat") dependsOn(formatKotlin)
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs = listOf(
freeCompilerArgs += listOf( "-Xopt-in=kotlin.RequiresOptIn",
"-Xcontext-receivers", "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
) )
} }
} }
withType<LintTask> {
source(files("src/kotlin"))
}
withType<FormatTask> {
source(files("src/kotlin"))
}
} }
}
dependencies {
// Kotlin
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
testImplementation(kotlin("test-junit5"))
// coroutines
val coroutinesVersion = "1.6.0"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
val kotlinSerializationVersion = "1.3.2"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
// Dependency Injection
implementation("org.kodein.di:kodein-di-conf-jvm:7.10.0")
// Logging
implementation("org.slf4j:slf4j-api:1.7.32")
implementation("ch.qos.logback:logback-classic:1.2.6")
implementation("io.github.microutils:kotlin-logging:2.1.21")
// ReactiveX
implementation("io.reactivex:rxjava:1.3.8")
implementation("io.reactivex:rxkotlin:1.0.0")
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
implementation("org.jsoup:jsoup:1.14.3")
// dependency of :AndroidCompat:Config
implementation("com.typesafe:config:1.4.1")
implementation("io.github.config4k:config4k:0.4.2")
// to get application content root
implementation("net.harawata:appdirs:1.2.1")
// dex2jar
val dex2jarVersion = "v35"
implementation("com.github.ThexXTURBOXx.dex2jar:dex-translator:$dex2jarVersion")
implementation("com.github.ThexXTURBOXx.dex2jar:dex-tools:$dex2jarVersion")
// APK parser
implementation("net.dongliu:apk-parser:2.6.10")
// dependency both in AndroidCompat and server, version locked by javalin
implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.4")
}
}

View File

@@ -7,5 +7,5 @@ repositories {
} }
dependencies { dependencies {
implementation(libs.zip4j) implementation("net.lingala.zip4j:zip4j:2.9.0")
} }

View File

@@ -1,9 +0,0 @@
rootProject.name = "buildSrc"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@@ -7,27 +7,27 @@ import java.io.BufferedReader
* 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/. */
const val kotlinVersion = "1.6.10"
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 tachideskVersion = System.getenv("ProductVersion") ?: "v1.0.0" val tachideskVersion = System.getenv("ProductVersion") ?: "v0.6.3"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1409" val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r942"
// counts commits on the current checked out branch // counts commits on the master branch
val getTachideskRevision = { val tachideskRevision = runCatching {
runCatching { System.getenv("ProductRevision") ?: Runtime
System.getenv("ProductRevision") ?: ProcessBuilder() .getRuntime()
.command("git", "rev-list", "HEAD", "--count") .exec("git rev-list HEAD --count")
.start() .let { process ->
.let { process -> process.waitFor()
process.waitFor() val output = process.inputStream.use {
val output = process.inputStream.use { it.bufferedReader().use(BufferedReader::readText)
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
"r" + output.trim()
} }
}.getOrDefault("r0") process.destroy()
} "r" + output.trim()
}
}.getOrDefault("r0")

View File

@@ -1,233 +0,0 @@
[versions]
kotlin = "1.9.10"
coroutines = "1.7.3"
serialization = "1.6.0"
okhttp = "5.0.0-alpha.11" # Major version is locked by Tachiyomi extensions
javalin = "4.6.8" # Javalin 5.0.0+ requires Java 11
jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.40.1"
dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed
rhino = "1.7.14"
settings = "1.0.0-RC"
twelvemonkeys = "3.9.4"
graphqlkotlin = "6.5.6"
xmlserialization = "0.86.2"
ktlint = "1.0.0"
[libraries]
# Kotlin
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" }
# Coroutines
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "coroutines" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
# Serialization
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization" }
serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization" }
serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-jvm", version.ref = "xmlserialization" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging
slf4japi = "org.slf4j:slf4j-api:2.0.9"
logback = "ch.qos.logback:logback-classic:1.3.11"
kotlinlogging = "io.github.microutils:kotlin-logging:3.0.5"
# OkHttp
okhttp-core = { module = "com.squareup.okhttp3:okhttp", 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-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.3.0"
# Javalin api
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
# GraphQL
graphql-kotlin-server = { module = "com.expediagroup:graphql-kotlin-server", version.ref = "graphqlkotlin" }
graphql-kotlin-scheme = { module = "com.expediagroup:graphql-kotlin-schema-generator", version.ref = "graphqlkotlin" }
graphql-scalars = "com.graphql-java:graphql-java-extended-scalars:20.2"
# Exposed ORM
exposed-core = { module = "org.jetbrains.exposed:exposed-core", 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-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
# Exposed Migrations
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.2.0"
# Dependency Injection
kodein = "org.kodein.di:kodein-di-conf-jvm:7.20.2"
# tray icon
systemtray-core = "com.dorkbox:SystemTray:4.2.1"
systemtray-utils = "com.dorkbox:Utilities:1.39" # version locked by SystemTray
systemtray-desktop = "com.dorkbox:Desktop:1.0"
# dependencies of Tachiyomi extensions
injekt = "com.github.inorichi.injekt:injekt-core:65b0440"
rxjava = "io.reactivex:rxjava:1.3.8"
jsoup = "org.jsoup:jsoup:1.16.1"
# Config
config = "com.typesafe:config:1.4.2"
config4k = "io.github.config4k:config4k:0.5.0"
# Sort
sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
# Android stub library
android-stubs = "com.github.Suwayomi:android-jar:1.0.0"
# Asm modificiation
asm = "org.ow2.asm:asm:9.5" # version locked by Dex2Jar
dex2jar-translator = { module = "com.github.ThexXTURBOXx.dex2jar:dex-translator", version.ref = "dex2jar" }
dex2jar-tools = { module = "com.github.ThexXTURBOXx.dex2jar:dex-tools", version.ref = "dex2jar" }
# APK
apk-parser = "net.dongliu:apk-parser:2.6.10"
apksig = "com.android.tools.build:apksig:7.2.1"
# Xml
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
# Disk & File
appdirs = "net.harawata:appdirs:1.2.1"
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
commonscompress = "org.apache.commons:commons-compress:1.24.0"
junrar = "com.github.junrar:junrar:7.5.5"
# AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.76"
# AndroidX annotations
android-annotations = "androidx.annotation:annotation:1.7.0"
# Substitute for duktape-android
rhino-runtime = { module = "org.mozilla:rhino-runtime", version.ref = "rhino" } # slimmer version of 'org.mozilla:rhino'
rhino-engine = { module = "org.mozilla:rhino-engine", version.ref = "rhino" } # provides the same interface as 'javax.script' a.k.a Nashorn
# Settings
settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.ref = "settings" }
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
# ICU4J
icu4j = "com.ibm.icu:icu4j:73.2"
# Image Decoding implementation provider
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
twelvemonkeys-common-io = { module = "com.twelvemonkeys.common:common-io", version.ref = "twelvemonkeys" }
twelvemonkeys-common-image = { module = "com.twelvemonkeys.common:common-image", version.ref = "twelvemonkeys" }
twelvemonkeys-imageio-core = { module = "com.twelvemonkeys.imageio:imageio-core", version.ref = "twelvemonkeys" }
twelvemonkeys-imageio-metadata = { module = "com.twelvemonkeys.imageio:imageio-metadata", version.ref = "twelvemonkeys" }
twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg", version.ref = "twelvemonkeys" }
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
# Testing
mockk = "io.mockk:mockk:1.13.7"
# cron scheduler
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
# cron-utils
cronUtils = "com.cronutils:cron-utils:9.2.1"
[plugins]
# Kotlin
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
# Linter
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "11.6.0"}
# Build config
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"}
# Download
download = { id = "de.undercouch.download", version = "5.4.0"}
# ShadowJar
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
[bundles]
shared = [
"kotlin-stdlib-jdk8",
"kotlin-reflect",
"coroutines-core",
"coroutines-jdk8",
"serialization-json",
"serialization-json-okio",
"serialization-protobuf",
"kodein",
"slf4japi",
"logback",
"kotlinlogging",
"appdirs",
"rxjava",
"jsoup",
"config",
"config4k",
"dex2jar-translator",
"dex2jar-tools",
"apk-parser",
"jackson-annotations"
]
sharedTest = [
"kotlin-test-junit5",
"coroutines-test",
]
okhttp = [
"okhttp-core",
"okhttp-logging",
"okhttp-dnsoverhttps",
"okhttp-brotli",
]
javalin = [
"javalin-core",
"javalin-openapi",
]
jackson = [
"jackson-databind",
"jackson-kotlin",
"jackson-annotations",
]
exposed = [
"exposed-core",
"exposed-dao",
"exposed-jdbc",
"exposed-javatime",
]
systemtray = [
"systemtray-core",
"systemtray-utils",
"systemtray-desktop"
]
rhino = [
"rhino-runtime",
"rhino-engine",
]
settings = [
"settings-core",
"settings-serialization",
]
twelvemonkeys = [
"twelvemonkeys-common-lang",
"twelvemonkeys-common-io",
"twelvemonkeys-common-image",
"twelvemonkeys-imageio-core",
"twelvemonkeys-imageio-metadata",
"twelvemonkeys-imageio-jpeg",
"twelvemonkeys-imageio-webp",
]

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

16
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015-2021 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.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
# * functions; # * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»; # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»; # * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit». # * various built-in commands including «command», «set», and «ulimit».
# #
# Important for patching: # Important for patching:
# #
@@ -205,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

14
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
if %EXIT_CODE% equ 0 set EXIT_CODE=1 exit /b 1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@@ -1,314 +0,0 @@
#!/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
main() {
POSITIONAL_ARGS=()
OUTPUT_DIR=.
while [ "$#" -gt 0 ]; do
case "$1" in
-o|--output-dir)
OUTPUT_DIR="$(readlink -e "$2" || exit 1)"
shift
shift
;;
*)
POSITIONAL_ARGS+=("$1")
shift
;;
esac
done
# restore positional parameters
set -- "${POSITIONAL_ARGS[@]}"
OS="$1"
JAR="$(ls server/build/*.jar | tail -n1)"
RELEASE_NAME="$(echo "${JAR%.*}" | xargs basename)-$OS"
RELEASE_VERSION="$(tmp="${JAR%-*}"; echo "${tmp##*-}" | tr -d v)"
#RELEASE_REVISION_NUMBER="$(tmp="${JAR%.*}" && echo "${tmp##*-}" | tr -d r)"
local electron_version="v28.1.3"
# clean temporary directory on function return
trap "rm -rf $RELEASE_NAME/" RETURN
mkdir "$RELEASE_NAME/"
download_launcher
case "$OS" in
debian-all)
RELEASE="$RELEASE_NAME.deb"
make_deb_package
move_release_to_output_dir
;;
linux-assets)
RELEASE="$RELEASE_NAME.tar.gz"
copy_linux_package_assets_to "$RELEASE_NAME/"
tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/"
move_release_to_output_dir
;;
linux-x64)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-linux-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.tar.gz"
make_linux_bundle
move_release_to_output_dir
;;
macOS-x64)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-darwin-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
macOS-arm64)
# https://cdn.azul.com/zulu/bin/
JRE="zulu8.74.0.17-ca-jre8.0.392-macosx_aarch64.tar.gz"
JRE_RELEASE="zulu8.74.0.17-ca-jre8.0.392-macosx_aarch64"
JRE_DIR="$JRE_RELEASE/zulu-8.jre"
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
ELECTRON="electron-$electron_version-darwin-arm64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
windows-x86)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x86-32_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').zip"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-win32-ia32.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
move_release_to_output_dir
RELEASE="$RELEASE_NAME.msi"
make_windows_package
move_release_to_output_dir
;;
windows-x64)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').zip"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-win32-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
move_release_to_output_dir
RELEASE="$RELEASE_NAME.msi"
make_windows_package
move_release_to_output_dir
;;
*)
error $LINENO "Unsupported operating system: $OS" 2
;;
esac
}
move_release_to_output_dir() {
# clean up from possible previous runs
if [ -f "$OUTPUT_DIR/$RELEASE" ]; then
rm "$OUTPUT_DIR/$RELEASE"
fi
mv "$RELEASE" "$OUTPUT_DIR/"
}
download_launcher() {
LAUNCHER_URL=$(curl -s "https://api.github.com/repos/Suwayomi/Suwayomi-Launcher/releases/latest" | grep "browser_download_url" | grep ".jar" | head -n 1 | cut -d '"' -f 4)
curl -L "$LAUNCHER_URL" -o "Suwayomi-Launcher.jar"
mv "Suwayomi-Launcher.jar" "$RELEASE_NAME/Suwayomi-Launcher.jar"
}
download_jre_and_electron() {
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
fi
if [ ! -f "$ELECTRON" ]; then
curl -L "$ELECTRON_URL" -o "$ELECTRON"
fi
local ext="${JRE##*.}"
if [ "$ext" = "zip" ]; then
unzip "$JRE"
else
tar xvf "$JRE"
fi
mv "$JRE_DIR" "$RELEASE_NAME/jre"
unzip "$ELECTRON" -d "$RELEASE_NAME/electron/"
mkdir "$RELEASE_NAME/bin"
tree
}
copy_linux_package_assets_to() {
local output_dir
output_dir="$(readlink -e "$1" || exit 1)"
cp "scripts/resources/pkg/suwayomi-server.sh" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-server.desktop" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-launcher.desktop" "$output_dir/"
cp "scripts/resources/pkg/systemd"/* "$output_dir/"
cp "server/src/main/resources/icon/faviconlogo-128.png" \
"$output_dir/suwayomi-server.png"
}
make_linux_bundle() {
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/suwayomi-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/suwayomi-server.sh" "$RELEASE_NAME/"
tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/"
}
make_macos_bundle() {
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.command" "$RELEASE_NAME/"
zip -9 -r "$RELEASE" "$RELEASE_NAME/"
}
# https://wiki.debian.org/SimplePackagingTutorial
# https://www.debian.org/doc/manuals/packaging-tutorial/packaging-tutorial.pdf
make_deb_package() {
#behind $RELEASE_VERSION is hyphen "-"
local source_dir="suwayomi-server-$RELEASE_VERSION"
#behind $RELEASE_VERSION is underscore "_"
local upstream_source="suwayomi-server_$RELEASE_VERSION.orig.tar.gz"
mkdir "$RELEASE_NAME/$source_dir/"
mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar"
cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar"
copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/"
tar -I "gzip" -C "$RELEASE_NAME/" -cvf "$upstream_source" "$source_dir"
cp -r "scripts/resources/deb/" "$RELEASE_NAME/$source_dir/debian/"
sed -i "s/\$pkgver/$RELEASE_VERSION/" "$RELEASE_NAME/$source_dir/debian/changelog"
sed -i "s/\$pkgrel/1/" "$RELEASE_NAME/$source_dir/debian/changelog"
sudo apt update
sudo apt install devscripts build-essential dh-exec
cd "$RELEASE_NAME/$source_dir/"
dpkg-buildpackage --no-sign --build=all
cd -
local deb="suwayomi-server_$RELEASE_VERSION-1_all.deb"
mv "$RELEASE_NAME/$deb" "$RELEASE"
}
make_windows_bundle() {
## I disabled this section until someone find a solution to this error:
##E: Unable to correct problems, you have held broken packages.
##./bundler.sh: line 250: wine: command not found
## check if running under github actions
#if [ "$CI" = true ]; then
## change electron executable's icon
#sudo dpkg --add-architecture i386
#wget -qO - https://dl.winehq.org/wine-builds/winehq.key \
#| sudo apt-key add -
#sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
#sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu \
#$(lsb_release -cs) main"
#sudo apt install --install-recommends winehq-stable
#fi
## this script assumes that wine is installed here on out
#local rcedit="rcedit-x85.exe"
#local rcedit_url="https://github.com/electron/rcedit/releases/download/v0.1.1/$rcedit"
## change electron's icon
#if [ ! -f "$rcedit" ]; then
#curl -L "$rcedit_url" -o "$rcedit"
#fi
#local icon="server/src/main/resources/icon/faviconlogo.ico"
#WINEARCH=win32 wine "$rcedit" "$RELEASE_NAME/electron/electron.exe" \
# --set-icon "$icon"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.bat" "$RELEASE_NAME"
zip -9 -r "$RELEASE" "$RELEASE_NAME"
}
make_windows_package() {
if [ "$CI" = true ]; then
sudo apt update
sudo apt install -y wixl
fi
find "$RELEASE_NAME/jre" \
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
--directory-ref jre --component-group jre >"$RELEASE_NAME/jre.wxs"
find "$RELEASE_NAME/electron" \
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
--directory-ref electron --component-group electron >"$RELEASE_NAME/electron.wxs"
find "$RELEASE_NAME/bin" \
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
--directory-ref bin --component-group bin >"$RELEASE_NAME/bin.wxs"
local icon="server/src/main/resources/icon/faviconlogo.ico"
local arch=${OS##*-}
wixl -D ProductVersion="$RELEASE_VERSION" -D SourceDir="$RELEASE_NAME" \
-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"
}
# Error handler
# set -u: Treat unset variables as an error when substituting.
# set -o pipefail: Prevents errors in pipeline from being masked.
# set -e: Immediatly exit if any command has a non-zero exit status.
# set -E: Inherit the trap ERR function before exiting by set.
#
# set -e is not recommended and unpredictable.
# see https://stackoverflow.com/questions/64786/error-handling-in-bash
# and http://mywiki.wooledge.org/BashFAO/105
set -uo pipefail
error() {
local parent_lineno="$1"
local message="$2"
local code="${3:-1}"
if [ -z "$message" ]; then
echo "$0: line $parent_lineno: exiting with status $code"
else
echo "$0: line $parent_lineno: $message: exiting with status $code"
fi
exit "$code"
}
trap 'error $LINENO ""' ERR
main "$@"; exit

51
scripts/debian-packager.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
echo "Creating DEB Package"
pkgname="tachidesk-server"
PkgName="Tachidesk-Server"
jar=$(ls ../server/build/*.jar | tail -n1)
pkgver="$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v)"
pkgrel=1
srcdir="$pkgname-$pkgver" # uses hyphen "-"
srctgz="${pkgname}_$pkgver.orig.tar.gz" # uses underscore "_"
deb="${pkgname}_$pkgver-${pkgrel}_all.deb"
Deb="${PkgName}_$pkgver-${pkgrel}_all.deb"
# Prepare
mkdir "$srcdir/"
cp "$jar" "$srcdir/$pkgname.jar"
cp "resources/$pkgname-browser-launcher.sh" "$srcdir/"
cp "resources/$pkgname-debug-launcher.sh" "$srcdir/"
cp "resources/$pkgname-electron-launcher-debian.sh" "$srcdir/$pkgname-electron-launcher.sh"
cp "resources/$pkgname.desktop" "$srcdir/"
cp "../server/src/main/resources/icon/faviconlogo.png" "$srcdir/$pkgname.png"
GZIP=-9 tar -cvzf "$srctgz" "$srcdir/"
cp -r "resources/debian" "$srcdir/"
sed -i "s/\${pkgver}/$pkgver/" "$srcdir/debian/changelog"
sed -i "s/\${pkgrel}/$pkgrel/" "$srcdir/debian/changelog"
# Build
mkdir "debuild/"
mv "$srctgz" "$srcdir/" "debuild/"
sudo apt install devscripts build-essential dh-exec
# --lintian-opts --profile are for building Debian packages on Ubuntu
cd "debuild/$srcdir/debian"
debuild -uc -us --lintian-opts --profile debian
cd -
# clean up from possible previous runs
if [ -f "../server/build/$Deb" ]; then
rm "../server/build/$Deb"
fi
mv "debuild/$deb" "../server/build/$Deb"
rm -rf "debuild/"

View File

@@ -1 +0,0 @@
start "" jre/bin/javaw -jar Suwayomi-Launcher.jar

View File

@@ -1,3 +0,0 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -jar Suwayomi-Launcher.jar

View File

@@ -0,0 +1 @@
start "" jre/bin/javaw -jar Tachidesk.jar

View File

@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -jar Tachidesk.jar

View File

@@ -0,0 +1,7 @@
:: cleaner output
@echo off
jre\bin\java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk.jar
:: Prevent cmd from closing when Tachidesk crashes
pause

View File

@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk.jar

View File

@@ -0,0 +1 @@
jre\bin\javaw "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron.exe" -jar Tachidesk.jar

View File

@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/Electron.app/Contents/MacOS/Electron" -jar Tachidesk.jar

View File

@@ -1,5 +0,0 @@
suwayomi-server ($pkgver-$pkgrel) unstable; urgency=medium
* See CHANGELOG.md on https://github.com/Suwayomi/Suwayomi-Server
-- Mahor1221 <mahor1221@pm.me> Fri, 14 Jan 2022 00:00:00 +0000

View File

@@ -1,14 +0,0 @@
Source: suwayomi-server
Section: web
Priority: optional
Maintainer: Mahor1221 <mahor1221@pm.me>
Build-Depends: debhelper-compat (= 13), dh-exec
Standards-Version: 4.5.1
Homepage: https://github.com/Suwayomi/Suwayomi-Server
Package: suwayomi-server
Architecture: all
Depends: ${misc:Depends}, java8-runtime, libc++-dev
Description: Manga Reader
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.

View File

@@ -1,13 +0,0 @@
#!/usr/bin/dh-exec
Suwayomi-Server.jar usr/share/java/suwayomi-server/bin/
Suwayomi-Launcher.jar usr/share/java/suwayomi-server/
suwayomi-server.png usr/share/pixmaps/
suwayomi-server.desktop usr/share/applications/
suwayomi-launcher.desktop usr/share/applications/
suwayomi-server.service usr/lib/systemd/system/
suwayomi-server.sysusers => usr/lib/sysusers.d/suwayomi-server.conf
suwayomi-server.tmpfiles => usr/lib/tmpfiles.d/suwayomi-server.conf
suwayomi-server.conf => etc/suwayomi/server.conf
suwayomi-server.sh => usr/bin/suwayomi-server
suwayomi-launcher.sh => usr/bin/suwayomi-launcher

View File

@@ -1 +0,0 @@
suwayomi-server.png

View File

@@ -0,0 +1,5 @@
tachidesk-server (${pkgver}-${pkgrel}) unstable; urgency=medium
* See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server
-- Mahor Foruzesh <mahorforuzesh@pm.me> Fri, 14 Jan 2022 00:00:00 +0000

View File

@@ -0,0 +1,14 @@
Source: tachidesk-server
Section: web
Priority: optional
Maintainer: Mahor Foruzesh <mahorforuzesh@pm.me>
Build-Depends: debhelper-compat (= 12), dh-exec
Standards-Version: 4.5.1
Homepage: https://github.com/Suwayomi/Tachidesk-Server
Package: tachidesk-server
Architecture: all
Depends: ${misc:Depends}, default-jre-headless (>= 8)
Description: Manga Reader
A free and open source manga reader server that runs extensions built for Tachiyomi.
Tachidesk is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.

View File

@@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: suwayomi-server Upstream-Name: tachidesk-server
Upstream-Contact: https://discord.gg/DDZdqZWaHA Upstream-Contact: https://discord.gg/DDZdqZWaHA
Source: https://github.com/Suwayomi/Suwayomi-Server Source: https://github.com/Suwayomi/Tachidesk-Server
Files: * Files: *
Copyright: Contributors to the Suwayomi project Copyright: Contributors to the Suwayomi project

View File

@@ -0,0 +1,8 @@
#!/usr/bin/dh-exec
tachidesk-server-browser-launcher.sh => usr/bin/tachidesk-server-browser
tachidesk-server-debug-launcher.sh => usr/bin/tachidesk-server-debug
tachidesk-server-electron-launcher.sh => usr/bin/tachidesk-server-electron
tachidesk-server.jar usr/share/java/tachidesk-server/
tachidesk-server.png usr/share/pixmaps/
tachidesk-server.desktop usr/share/applications/

View File

@@ -0,0 +1 @@
tachidesk-server.png

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Suwayomi Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Suwayomi_Server.cab" EmbedCab="yes" />
<Condition Message="This version of Windows does not support deploying 64-bit packages.">
VersionNT64
</Condition>
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLDIR" Name="Suwayomi-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
<Directory Id="bin"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Suwayomi-Server">
<Component Id="ProgramMenuDir" Guid="*">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" />
</Directory>
<!-- Component -->
<DirectoryRef Id="INSTALLDIR">
<Component Id="SuwayomiJAR" Guid="*" Win64="yes">
<File Id="Suwayomi-Launcher.jar" Source="$(var.SourceDir)/Suwayomi-Launcher.jar" KeyPath="yes" />
</Component>
<Component Id="SuwayomiLauncherBAT" Guid="*" Win64="yes">
<File Id="SuwayomiLauncher.bat" Source="$(var.SourceDir)/Suwayomi Launcher.bat" KeyPath="yes" >
<Shortcut Id="SuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Suwayomi_Server" Title="Suwayomi-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentGroupRef Id="bin" />
<ComponentRef Id="SuwayomiJAR" />
<ComponentRef Id="SuwayomiLauncherBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
</Feature>
<Icon Id="Suwayomi.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Suwayomi.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Suwayomi Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Suwayomi_Server.cab" EmbedCab="yes" />
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="Suwayomi-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
<Directory Id="bin"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Suwayomi-Server">
<Component Id="ProgramMenuDir" Guid="*">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" />
</Directory>
<!-- Component -->
<DirectoryRef Id="INSTALLDIR">
<Component Id="SuwayomiJAR" Guid="*">
<File Id="Suwayomi-Launcher.jar" Source="$(var.SourceDir)/Suwayomi-Launcher.jar" KeyPath="yes" />
</Component>
<Component Id="SuwayomiLauncherBAT" Guid="*" Win64="yes">
<File Id="SuwayomiLauncher.bat" Source="$(var.SourceDir)/Suwayomi Launcher.bat" KeyPath="yes" >
<Shortcut Id="SuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Suwayomi_Server" Title="Suwayomi-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentGroupRef Id="bin" />
<ComponentRef Id="SuwayomiJAR" />
<ComponentRef Id="SuwayomiLauncherBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
</Feature>
<Icon Id="Suwayomi.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Suwayomi.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Tachidesk Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
<Condition Message="This version of Windows does not support deploying 64-bit packages.">
VersionNT64
</Condition>
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Tachidesk-Server">
<Component Id="ProgramMenuDir" Guid="*">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" />
</Directory>
<!-- Component -->
<DirectoryRef Id="INSTALLDIR">
<Component Id="TachideskJAR" Guid="*" Win64="yes">
<File Id="Tachidesk.jar" Source="$(var.SourceDir)/Tachidesk.jar" KeyPath="yes" />
</Component>
<Component Id="TachideskBrowserBAT" Guid="*" Win64="yes">
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
<Component Id="TachideskDebugBAT" Guid="*" Win64="yes">
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
</File>
</Component>
<Component Id="TachideskElectronBAT" Guid="*" Win64="yes">
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentRef Id="TachideskJAR" />
<ComponentRef Id="TachideskBrowserBAT" />
<ComponentRef Id="TachideskDebugBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
<ComponentRef Id="TachideskElectronBAT" />
</Feature>
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Tachidesk Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Tachidesk-Server">
<Component Id="ProgramMenuDir" Guid="*">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" />
</Directory>
<!-- Component -->
<DirectoryRef Id="INSTALLDIR">
<Component Id="TachideskJAR" Guid="*">
<File Id="Tachidesk.jar" Source="$(var.SourceDir)/Tachidesk.jar" KeyPath="yes" />
</Component>
<Component Id="TachideskBrowserBAT" Guid="*">
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
<Component Id="TachideskDebugBAT" Guid="*">
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
</File>
</Component>
<Component Id="TachideskElectronBAT" Guid="*">
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentRef Id="TachideskJAR" />
<ComponentRef Id="TachideskBrowserBAT" />
<ComponentRef Id="TachideskDebugBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
<ComponentRef Id="TachideskElectronBAT" />
</Feature>
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -1,8 +0,0 @@
[Desktop Entry]
Type=Application
Name=Suwayomi-Launcher
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/suwayomi-server/Suwayomi-Launcher.jar "\\$@"
Icon=suwayomi-server
Terminal=false
Categories=Network;

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/Suwayomi-Launcher.jar

View File

@@ -1,8 +0,0 @@
[Desktop Entry]
Type=Application
Name=Suwayomi-Server
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar "\\$@"
Icon=suwayomi-server
Terminal=false
Categories=Network;

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar

View File

@@ -1,5 +0,0 @@
TACHIDESK_ROOT_DIR="/var/lib/suwayomi"
# Extra arguments passed to the java command
# The default value disables the system tray icon, and launching a browser on service start.
JAVA_ARGS=-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false -Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false

View File

@@ -1,31 +0,0 @@
[Unit]
Description=A free and open source manga reader server that runs extensions built for Tachiyomi.
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=suwayomi-server
Group=suwayomi-server
SyslogIdentifier=suwayomi-server
EnvironmentFile=/etc/suwayomi/server.conf
ExecStart=/usr/bin/java $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}" -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
Restart=on-failure
ProtectSystem=full
ProtectHome=true
PrivateTmp=yes
PrivateDevices=yes
ProtectClock=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target

View File

@@ -1,2 +0,0 @@
#Type Name ID GECOS Home directory Shell
u suwayomi-server - "Suwayomi Manga Server" /var/lib/suwayomi

View File

@@ -1,2 +0,0 @@
#Type Path Mode User Group Age Argument
d /var/lib/suwayomi 0755 suwayomi-server suwayomi-server

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec ./jre/bin/java -jar ./Suwayomi-Launcher.jar

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec ./jre/bin/java -jar ./bin/Suwayomi-Server.jar

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/tachidesk-server/tachidesk-server.jar

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar /usr/share/java/tachidesk-server/tachidesk-server.jar

View File

@@ -0,0 +1,12 @@
#!/bin/sh
if [ ! -f /usr/bin/electron ]; then
echo "Electron executable was not found
In order to run this launcher, you need Electron installed.
You can install it with these commands:
sudo apt install npm
sudo npm install electron -g"
exit 1
fi
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk-server/tachidesk-server.jar

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk-server/tachidesk-server.jar

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Tachidesk-Server
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/tachidesk-server/tachidesk-server.jar "\\$@"
Icon=tachidesk-server
Terminal=false
Categories=Network;

87
scripts/unix-bundler.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
electron_version="v14.0.0"
if [ $1 = "linux-x64" ]; then
jre="OpenJDK8U-jre_x64_linux_hotspot_8u302b08.tar.gz"
jre_release="jdk8u302-b08"
jre_url="https://github.com/adoptium/temurin8-binaries/releases/download/$jre_release/$jre"
jre_dir="$jre_release-jre"
electron="electron-$electron_version-linux-x64.zip"
elif [ $1 = "macOS-x64" ]; then
jre="OpenJDK8U-jre_x64_mac_hotspot_8u302b08.tar.gz"
jre_release="jdk8u302-b08"
jre_url="https://github.com/adoptium/temurin8-binaries/releases/download/$jre_release/$jre"
jre_dir="$jre_release-jre"
electron="electron-$electron_version-darwin-x64.zip"
elif [ $1 = "macOS-arm64" ]; then
jre="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64.tar.gz"
jre_release="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64"
jre_url="https://cdn.azul.com/zulu/bin/$jre"
jre_dir="$jre_release/zulu-8.jre"
electron="electron-$electron_version-darwin-arm64.zip"
else
echo "Unsupported arch value: $1"
exit 1
fi
arch="$1"
os=$(echo $arch | cut -d '-' -f1)
echo "creating $arch bundle"
jar=$(ls ../server/build/*.jar | tail -n1)
jar_name=$(echo $jar | cut -d'/' -f4)
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
# make release dir
mkdir $release_name
echo "Dealing with jre..."
if [ ! -f $jre ]; then
curl -L $jre_url -o $jre
fi
tar xvf $jre
mv $jre_dir $release_name/jre
echo "Dealing with electron"
if [ ! -f $electron ]; then
curl -L "https://github.com/electron/electron/releases/download/$electron_version/$electron" -o $electron
fi
unzip $electron -d $release_name/electron
# copy artifacts
cp $jar $release_name/Tachidesk-Server.jar
if [ $os = linux ]; then
cp "resources/tachidesk-server-browser-launcher.sh" $release_name
cp "resources/tachidesk-server-debug-launcher.sh" $release_name
cp "resources/tachidesk-server-electron-launcher.sh" $release_name
elif [ $os = macOS ]; then
cp "resources/Tachidesk Browser Launcher.command" $release_name
cp "resources/Tachidesk Debug Launcher.command" $release_name
cp "resources/Tachidesk Electron Launcher.command" $release_name
fi
archive_name=""
if [ $os = linux ]; then
archive_name=$release_name.tar.gz
GZIP=-9 tar cvzf $archive_name $release_name
elif [ $os = macOS ]; then
archive_name=$release_name.zip
zip -9 -r $archive_name $release_name
fi
rm -rf $release_name
# clean up from possible previous runs
if [ -f ../server/build/$archive_name ]; then
rm ../server/build/$archive_name
fi
mv $archive_name ../server/build/

104
scripts/windows-bundler.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
electron_version="v14.0.0"
arch=$1
if [ $arch = "win32" ]; then
jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
jre_release="jdk8u292-b10"
jre_url="https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/$jre_release/$jre"
arch="windows-x86"
electron="electron-$electron_version-win32-ia32.zip"
else
jre="OpenJDK8U-jre_x64_windows_hotspot_8u302b08.zip"
jre_release="jdk8u302-b08"
jre_url="https://github.com/adoptium/temurin8-binaries/releases/download/$jre_release/$jre"
arch="windows-x64"
electron="electron-$electron_version-win32-x64.zip"
fi
jre_dir="$jre_release-jre"
echo "creating windows bundle"
jar=$(ls ../server/build/*.jar | tail -n1)
jar_name=$(echo $jar | cut -d'/' -f4)
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
# make release dir
mkdir $release_name
echo "Dealing with jre..."
if [ ! -f $jre ]; then
curl -L $jre_url -o $jre
fi
unzip $jre
mv $jre_dir $release_name/jre
echo "Dealing with electron"
if [ ! -f $electron ]; then
curl -L "https://github.com/electron/electron/releases/download/$electron_version/$electron" -o $electron
fi
unzip $electron -d $release_name/electron
# change electron's icon
rcedit="rcedit-x86.exe"
if [ ! -f $rcedit ]; then
curl -L "https://github.com/electron/rcedit/releases/download/v1.1.1/$rcedit" -o $rcedit
fi
# check if running under github actions
if [ $CI = true ]; then
# change electron executable's icon
sudo dpkg --add-architecture i386
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
sudo apt install --install-recommends winehq-stable
sudo apt install -y wixl
fi
# this script assumes that wine is installed here on out
WINEARCH=win32 wine $rcedit $release_name/electron/electron.exe --set-icon ../server/src/main/resources/icon/faviconlogo.ico
# copy artifacts
cp $jar $release_name/Tachidesk.jar
cp "resources/Tachidesk Browser Launcher.bat" $release_name
cp "resources/Tachidesk Debug Launcher.bat" $release_name
cp "resources/Tachidesk Electron Launcher.bat" $release_name
zip_name=$release_name.zip
zip -9 -r $zip_name $release_name
# create msi package
msi_name=$release_name.msi
release_ver=$(tmp=${jar%-*} && echo ${tmp##*-} | tr -d v)
icon="../server/src/main/resources/icon/faviconlogo.ico"
find $release_name/jre | wixl-heat --var var.SourceDir -p $release_name/ --directory-ref jre --component-group jre >jre.wxs
find $release_name/electron | wixl-heat --var var.SourceDir -p $release_name/ --directory-ref electron --component-group electron >electron.wxs
if [ $arch = "win32" ]; then
wixl -D ProductVersion=$release_ver -D SourceDir=$release_name -D Icon=$icon \
--arch x86 resources/msi/tachidesk-server-x86.wxs jre.wxs electron.wxs -o $msi_name
else
wixl -D ProductVersion=$release_ver -D SourceDir=$release_name -D Icon=$icon \
--arch x64 resources/msi/tachidesk-server-x64.wxs jre.wxs electron.wxs -o $msi_name
fi
rm -rf $release_name
# clean up from possible previous runs
if [ -f ../server/build/$zip_name ]; then
rm ../server/build/$zip_name
fi
if [ -f ../server/build/$msi_name ]; then
rm ../server/build/$msi_name
fi
mv $zip_name ../server/build/
mv $msi_name ../server/build/

View File

@@ -1,88 +1,80 @@
import de.undercouch.gradle.tasks.download.Download import de.undercouch.gradle.tasks.download.Download
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.time.Instant import java.time.Instant
plugins { plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
application application
alias(libs.plugins.shadowjar) id("com.github.johnrengelman.shadow") version "7.1.2"
id(libs.plugins.buildconfig.get().pluginId) id("com.github.gmazzo.buildconfig")
} }
dependencies { dependencies {
// Shared // okhttp
implementation(libs.bundles.shared) val okhttpVersion = "4.9.3" // Major version is locked by Tachiyomi extensions
testImplementation(libs.bundles.sharedTest) implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
// OkHttp implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
implementation(libs.bundles.okhttp) implementation("com.squareup.okio:okio:3.0.0")
implementation(libs.okio)
// Javalin api // Javalin api
implementation(libs.bundles.javalin) implementation("io.javalin:javalin:4.2.0")
implementation(libs.bundles.jackson) implementation("io.javalin:javalin-openapi:4.2.0")
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
// GraphQL val jacksonVersion = "2.12.4"
implementation(libs.graphql.kotlin.server) implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
implementation(libs.graphql.kotlin.scheme) implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation(libs.graphql.scalars)
// Exposed ORM // Exposed ORM
implementation(libs.bundles.exposed) val exposedVersion = "0.34.1"
implementation(libs.h2) implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
// current database driver
implementation("com.h2database:h2:1.4.200")
// Exposed Migrations // Exposed Migrations
implementation(libs.exposed.migrations) implementation("com.github.Suwayomi:exposed-migrations:3.1.4")
// tray icon // tray icon
implementation(libs.bundles.systemtray) implementation("com.dorkbox:SystemTray:4.1")
implementation("com.dorkbox:Utilities:1.9") // version locked by SystemTray
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference // dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
implementation(libs.injekt) implementation("com.github.inorichi.injekt:injekt-core:65b0440")
implementation(libs.okhttp.core) implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation(libs.rxjava) implementation("io.reactivex:rxjava:1.3.8")
implementation(libs.jsoup) implementation("org.jsoup:jsoup:1.14.3")
implementation("app.cash.quickjs:quickjs-jvm:0.9.2")
// ComicInfo
implementation(libs.serialization.xml.core)
implementation(libs.serialization.xml)
// Sort // Sort
implementation(libs.sort) implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
// asm for ByteCodeEditor(fixing SimpleDateFormat) (must match Dex2Jar version) // asm for ByteCodeEditor(fixing SimpleDateFormat) (must match Dex2Jar version)
implementation(libs.asm) implementation("org.ow2.asm:asm:9.2")
// Disk & File // Disk & File
implementation(libs.zip4j) implementation("net.lingala.zip4j:zip4j:2.9.1")
implementation(libs.commonscompress) implementation("com.github.junrar:junrar:7.4.0")
implementation(libs.junrar)
// AES/CBC/PKCS7Padding Cypher provider for zh.copymanga // CloudflareInterceptor
implementation(libs.bouncycastle) implementation("net.sourceforge.htmlunit:htmlunit:2.56.0")
// Source models and interfaces from Tachiyomi 1.x
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
// implementation("tachiyomi.sourceapi:source-api:1.1")
// AndroidCompat // AndroidCompat
implementation(projects.androidCompat) implementation(project(":AndroidCompat"))
implementation(projects.androidCompat.config) implementation(project(":AndroidCompat:Config"))
// uncomment to test extensions directly // uncomment to test extensions directly
// implementation(fileTree("lib/")) // implementation(fileTree("lib/"))
implementation(kotlin("script-runtime")) implementation(kotlin("script-runtime"))
testImplementation(libs.mockk) testImplementation("io.mockk:mockk:1.12.2")
implementation(libs.cron4j)
implementation(libs.cronUtils)
} }
application { application {
applicationDefaultJvmArgs =
listOf(
"-Djunrar.extractor.thread-keep-alive-seconds=30",
)
mainClass.set(MainClass) mainClass.set(MainClass)
} }
@@ -96,7 +88,7 @@ sourceSets {
buildConfig { buildConfig {
className("BuildConfig") className("BuildConfig")
packageName("suwayomi.tachidesk.server.generated") packageName("suwayomi.tachidesk.server")
useKotlinOutput() useKotlinOutput()
@@ -104,31 +96,33 @@ buildConfig {
buildConfigField("String", "NAME", quoteWrap(rootProject.name)) buildConfigField("String", "NAME", quoteWrap(rootProject.name))
buildConfigField("String", "VERSION", quoteWrap(tachideskVersion)) buildConfigField("String", "VERSION", quoteWrap(tachideskVersion))
buildConfigField("String", "REVISION", quoteWrap(getTachideskRevision())) buildConfigField("String", "REVISION", quoteWrap(tachideskRevision))
buildConfigField("String", "BUILD_TYPE", quoteWrap(if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview")) buildConfigField("String", "BUILD_TYPE", quoteWrap(if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview"))
buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString()) buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
buildConfigField("String", "WEBUI_REPO", quoteWrap("https://github.com/Suwayomi/Tachidesk-WebUI-preview"))
buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag)) buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag))
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Suwayomi-Server"))
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Tachidesk-Server"))
buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA")) buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA"))
} }
tasks { tasks {
shadowJar { shadowJar {
isZip64 = true
manifest { manifest {
attributes( attributes(
"Main-Class" to MainClass, "Main-Class" to MainClass,
"Implementation-Title" to rootProject.name, "Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Suwayomi Project", "Implementation-Vendor" to "The Suwayomi Project",
"Specification-Version" to tachideskVersion, "Specification-Version" to tachideskVersion,
"Implementation-Version" to getTachideskRevision(), "Implementation-Version" to tachideskRevision
) )
} }
archiveBaseName.set(rootProject.name) archiveBaseName.set(rootProject.name)
archiveVersion.set(tachideskVersion) archiveVersion.set(tachideskVersion)
archiveClassifier.set(getTachideskRevision()) archiveClassifier.set(tachideskRevision)
destinationDirectory.set(File("$rootDir/server/build")) destinationDirectory.set(File("$rootDir/server/build"))
} }
@@ -140,34 +134,25 @@ tasks {
} }
} }
withType<KotlinJvmCompile> {
kotlinOptions {
freeCompilerArgs +=
listOf(
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}
named<Copy>("processResources") { named<Copy>("processResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE duplicatesStrategy = DuplicatesStrategy.INCLUDE
mustRunAfter("downloadWebUI") mustRunAfter("downloadWebUI")
} }
register<Download>("downloadWebUI") { register<Download>("downloadWebUI") {
src("https://github.com/Suwayomi/Suwayomi-WebUI-preview/releases/download/$webUIRevisionTag/Suwayomi-WebUI-$webUIRevisionTag.zip") src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip")
dest("src/main/resources/WebUI.zip") dest("src/main/resources/WebUI.zip")
fun shouldOverwrite(): Boolean { fun shouldOverwrite(): Boolean {
val zipPath = project.projectDir.absolutePath + "/src/main/resources/WebUI.zip" val zipPath = project.projectDir.absolutePath + "/src/main/resources/WebUI.zip"
val zipFile = net.lingala.zip4j.ZipFile(zipPath) val zipFile = net.lingala.zip4j.ZipFile(zipPath)
var shouldOverwrite = true var shouldOverwrite = true
if (zipFile.isValidZipFile) { if (zipFile.isValidZipFile) {
val zipRevision = val zipRevision = zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use { it.readText().trim()
it.readText().trim() }
}
if (zipRevision == webUIRevisionTag) { if (zipRevision == webUIRevisionTag) {
shouldOverwrite = false shouldOverwrite = false
@@ -184,12 +169,11 @@ tasks {
group = "application" group = "application"
finalizedBy(run) finalizedBy(run)
doFirst { doFirst {
application.applicationDefaultJvmArgs = application.applicationDefaultJvmArgs = listOf(
listOf( "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron", // Change this to the installed electron application
// Change this to the installed electron application "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron", )
)
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More