Compare commits

...

530 Commits

Author SHA1 Message Date
Syer10 22df7e3074 Release v2.0.1727
CI Publish / Validate Gradle Wrapper (push) Successful in 2m14s
CI Publish / jlink (linux-x64, ubuntu-latest) (push) Failing after 1m6s
CI Publish / Build Jar (push) Failing after 2m8s
CI Publish / jlink (macOS-arm64, macos-14) (push) Has been cancelled
CI Publish / jlink (macOS-x64, macos-13) (push) Has been cancelled
CI Publish / jlink (windows-x64, windows-latest) (push) Has been cancelled
CI Publish / Make debian-all release (push) Has been cancelled
CI Publish / Make linux-assets release (push) Has been cancelled
CI Publish / Make linux-x64 release (push) Has been cancelled
CI Publish / Make macOS-arm64 release (push) Has been cancelled
CI Publish / Make macOS-x64 release (push) Has been cancelled
CI Publish / Make windows-x64 release (push) Has been cancelled
CI Publish / release (push) Has been cancelled
2025-04-21 13:32:42 -04:00
schroda 1d5323a477 [skip ci] Add link to discord in issue templates (#1347) 2025-04-16 18:10:37 -04:00
KAAAsS f8d73819ea [Feature] Support Bangumi Tracker (#1343)
* feat: Support Bangumi Tracker

Credits: Andreas, AntsyLich, Caleb Morris, Gauthier, MCAxiaz, MajorTanya, NarwhalHorns, arkon, fei long, jmir1, mutsumi, stevenyomi

* Use Suwayomi api keys

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2025-04-12 19:34:04 -04:00
Syer10 cbe26b7291 Chmod in build script 2025-04-08 12:48:09 -04:00
Syer10 93477f60c2 Fix release name 2025-04-08 12:36:42 -04:00
Syer10 9feebbfe17 Use tar for MacOS 2025-04-08 12:34:24 -04:00
Mitchell Syer 6e365491a9 Add permissions to jspawnhelper (#1339) 2025-04-08 12:16:37 -04:00
Mitchell Syer 2e58658129 Fix MacOS builds (#1338) 2025-04-08 11:59:38 -04:00
schroda 256c564b91 Fix release version extraction from jar name (#1335)
Broke due to the changes made in 3167d8aa15
2025-04-06 15:53:04 -04:00
schroda 96b50f52ec Ensure webui "channel" is always of corresponding enum (#1334)
The "channel" was just the string from the config file, which will never equal the enum unless via case-insensitive comparison
2025-04-06 15:10:07 -04:00
schroda 3167d8aa15 Fix/startup jvm error after installation update via msi (#1229)
* Remove existing installations with msi installer

* Remove unused x86 wxs file

* Uninstall old msi versions with different upgrade code

* Progress but error 2721 happens on install

* Remove added uninstall previous version wxs stuff

* Use revision as patch number

MSI only uninstalls previous versions in case the version number changed (it only checks the first three numbers (major, minor, patch)).
Thus, to prevent each preview install to result in it getting registered as a new "app" and for it to uninstall the old versions, we have to change the version on each release.

* Deprecate "BuildConfig.REVISION"

* Remove outdated env vars

---------

Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2025-04-06 15:09:56 -04:00
schroda 78fd09c728 Prevent IndexOutOfBoundsException in "libraryUpdate" subscription (#1320) 2025-03-22 22:50:25 -04:00
schroda 4c5598cedf Feature/graphql log execution exceptions (#1319)
* Log exceptions during graphql execution

Exceptions got swallowed by graphql

* Add stack trace to error in graphql response

Depending on the exceptions error message, the error in the response might be quite useless (e.g. "Stub!" error in android classes)
2025-03-22 19:35:16 -04:00
schroda c3347d94ab Feature/use GitHub issue yml format (#1314)
* Update github issue templates to yml format

* Remove "issue moderator" workflow

* Require more client info for "bug issues"

- client name
- client version
2025-03-22 19:35:08 -04:00
Mitchell Syer 7ca4aa75a8 Fix checkbox preference title nullability (#1313) 2025-03-22 19:35:02 -04:00
schroda 226fad5594 Remove "default" category from backups (#1307)
Restoring a suwayomi backup in mihon created a category named "Default"
2025-03-22 19:34:57 -04:00
schroda d0ee1ba5af Align kitsu icon with icons of other trackers (#1303)
The used icon for kitsu has a transparent background while all other tracker icons have a background color
2025-03-22 19:34:50 -04:00
schroda 439e0c8284 Emit only updater job changes instead of full status (#1302)
The update subscription emitted the full update status, which, depending on how big the status was, took forever because the graphql subscription does not support data loader batching, causing it to run into the n+1 problem
2025-03-22 19:34:43 -04:00
renovate[bot] 7d079a8728 Update kotlin monorepo to v2.1.20 (#1315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:34:30 -04:00
renovate[bot] 945a52653e Update graphqlkotlin to v8.4.0 (#1311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:34:23 -04:00
renovate[bot] bdafc86990 Update polyglot to v24.2.0 (#1310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:34:13 -04:00
renovate[bot] 57d425ab9f Update dependency ch.qos.logback:logback-classic to v1.5.18 (#1309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:34:06 -04:00
renovate[bot] 395ac8e944 Update dependency com.ibm.icu:icu4j to v77 (#1305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:34:00 -04:00
renovate[bot] 2f801e7571 Update plugin buildconfig to v5.5.4 (#1304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:33:53 -04:00
renovate[bot] d7636045fe Update dependency io.javalin:javalin to v6.5.0 (#1301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:33:46 -04:00
renovate[bot] b745f10870 Update dependency org.jsoup:jsoup to v1.19.1 (#1292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 19:33:38 -04:00
renovate[bot] d76849942c Update plugin buildconfig to v5.5.2 (#1299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:31:38 -05:00
renovate[bot] b7a8a3ffe8 Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.5 (#1298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:31:26 -05:00
Shirish 95d9293fe0 Add support for opds-pse for undownloaded chapters (#1278)
* Add OPDS page streaming for undownloaded chapters

* Add [D] in chapter title prefix when isDownloaded

* Removed Chapter.isDownloaded check in query for other opds endpoints

* Add chapter progression tracking for streaming and refactor code

* dd  Unicode for chapters with 0 pages [post pageRefresh]

* Add Library Updates feed and remove redundant metadata fetching for OPDS chapters and manga

* Address PR comments & add chapter markAsRead for cbzDownload

* Address PR comment/s

* Rem. markAsRead for chapter download

* Rem. markAsRead for chapter download

---------

Co-authored-by: ShowY <showypro@gmail.com>
2025-03-08 11:31:07 -05:00
Constantin Piber 3be165a551 Initial import of Kitsu tracker (#1297)
* Initial import of Kitsu tracker

Based on Mihon 6c6ea84509cc1bd859c880bebbc69067a241b358 because its
successor 9f99f03 relies on incompatible changes

* Kitsu: Avoid stupid long/int cast
2025-03-08 11:30:59 -05:00
renovate[bot] cb498e2128 Update dependency org.slf4j:slf4j-api to v2.0.17 (#1284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:30:35 -05:00
renovate[bot] 973f4d66e2 Update jackson monorepo to v2.18.3 (#1289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:30:02 -05:00
renovate[bot] 2c80672f6e Update plugin ktlint to v12.2.0 (#1288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:29:55 -05:00
renovate[bot] 2599813ef1 Update dependency io.mockk:mockk to v1.13.17 (#1287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:29:48 -05:00
renovate[bot] 86f849a185 Update dependency net.harawata:appdirs to v1.4.0 (#1286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:29:39 -05:00
renovate[bot] 875f1f1506 Update dependency com.android.tools.build:apksig to v8.9.0 (#1285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:29:22 -05:00
renovate[bot] e418375963 Update dependency ch.qos.logback:logback-classic to v1.5.17 (#1283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:29:07 -05:00
renovate[bot] d528fc7f9e Update dependency gradle to v8.13 (#1282)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 11:28:59 -05:00
schroda 633ea97848 Feature/optimize backup import (#1270)
* Optimize restoring manga chapters

* Streamline restoring manga data

* Optimize restoring manga trackers

* Simplify passing manga category restore data

* Properly prevent mangas from getting added to default category

76595233fc never actually worked...

* Extract logic to add manga to categories from gql mutation

* Optimize restoring manga categories

* Optimize restoring categories
2025-02-16 13:00:26 -05:00
schroda 36cb899b91 Prevent chapter lastReadPage coerceIn error (#1272)
coerceIn throws an error in case the max value is less than the min value ("Cannot coerce value to an empty range: maximum <max> is less than minimum <min>")

Regression from c8bd39b4bf
2025-02-14 23:05:04 -05:00
schroda c4d849d6a3 Fix invalid chapter download state in database (#1271)
* Fix invalid chapter download state in database

Should have been added with 37f57c0c55

* Improve "fix chapter invalid download state" migrations
2025-02-14 21:02:10 -05:00
schroda c8bd39b4bf Prevent negative lastPageRead values (#1267)
Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2025-02-14 19:06:54 -05:00
schroda 733ba16af2 Fix/backup with duplicated chapters failure (#1269)
* Extract logic to restore manga chapters into function

* Extract logic to restore manga categories into function

* Extract logic to restore manga trackers into function

* Handle duplicated chapters in backup

In case a backup contained duplicated chapters for a manga, the manga failed to restore since the ChapterTable has a unique constraint to prevent multiple chapters with the same "url" and "mangaId"
2025-02-14 19:02:02 -05:00
schroda 37f57c0c55 Prevent marking chapter as downloaded without pages (#1268)
The Downloader marked chapters without any pages incorrectly as downloaded.
2025-02-14 19:01:46 -05:00
renovate[bot] 013dbd79b4 Update dependency com.android.tools.build:apksig to v8.8.1 (#1264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 19:01:37 -05:00
Zeedif f76d0b3258 Remove redundant code and add next/prev links (#1263) 2025-02-10 20:34:24 -05:00
Zeedif c2f7cdd72e Refactoring OPDS API for a more versatile root, allowing selection of manga listing by: all, source, genre, category, language, status. (#1262)
* Añadiendo algunos cambios iniciales para probar OPDS

* Add suport to OPDS v1.2

* Added support for OPDS-PSE and reorganized controllers

* Rename chapterIndex to chapterId in the API and controller, and update descriptions in OPDS

* Refactor OPDS to use formatted timestamps and proxy thumbnail URLs

* Refactor OPDS to use formatted timestamps and proxy thumbnail URLs

* Update Manga API to download chapters cbz using only chapterId and improve chapter download query

* Optimize OPDS queries

* Update Manga API to download chapters cbz using only chapterId and improve chapter download query

* Optimize OPDS queries

* Use SourceDataClass to map sources and optimize thumbnail URL retrieval

* Kotlin lint errors in ChapterDownloadHelper and Opds

* Kotlin lint errors in ChapterDownloadHelper and Opds

* Refactor OPDS API endpoints and rename OpdsController to OpdsV1Controller

* Translate OpdsV1Controller comments to English and remove unused imports

* Translate comments in OpdsAPI.kt to English

* Add SearchCriteria class and update OpdsV1Controller

* Remove spanish comments

* Refactor search handling in OpdsV1Controller and update search feed endpoint

* Fix search
2025-02-09 16:03:18 -05:00
schroda 01c37cb0ba Ignore authentication for preflight requests (#1261)
Cors preflight requests never include credentials (https://fetch.spec.whatwg.org/#cors-protocol-and-credentials), thus, they always failed due to being unauthorized
2025-02-08 11:53:32 -05:00
renovate[bot] 0dd0af1b84 Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.4 (#1260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:53:21 -05:00
Zeedif 26aa684300 Add support for OPDS v1.2 to browse stored CBZ files (#1257)
* Añadiendo algunos cambios iniciales para probar OPDS

* Add suport to OPDS v1.2

* Added support for OPDS-PSE and reorganized controllers

* Rename chapterIndex to chapterId in the API and controller, and update descriptions in OPDS

* Refactor OPDS to use formatted timestamps and proxy thumbnail URLs

* Refactor OPDS to use formatted timestamps and proxy thumbnail URLs

* Update Manga API to download chapters cbz using only chapterId and improve chapter download query

* Optimize OPDS queries

* Update Manga API to download chapters cbz using only chapterId and improve chapter download query

* Optimize OPDS queries

* Use SourceDataClass to map sources and optimize thumbnail URL retrieval

* Kotlin lint errors in ChapterDownloadHelper and Opds

* Kotlin lint errors in ChapterDownloadHelper and Opds
2025-02-08 11:53:06 -05:00
renovate[bot] 9669cdfb76 Update kotlin monorepo to v2.1.10 (#1251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:52:54 -05:00
renovate[bot] 2c5e5e283e Update dependency com.github.Suwayomi:exposed-migrations to v3.7.0 (#1248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:52:43 -05:00
renovate[bot] 303921c6ea Update dependency gradle to v8.12.1 (#1247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:52:31 -05:00
renovate[bot] 593291a60f Update exposed to v0.59.0 (#1228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:52:18 -05:00
Mitchell Syer 3af8e395bd Check if file exists (#1246) 2025-01-23 09:38:52 -05:00
Mitchell Syer 0b192cfa52 Normalize Paths (#1245)
* Normalize Paths

* Formatting

* Different format
2025-01-23 09:36:10 -05:00
Mitchell Syer fb8f20f31a Fix MacOS .command file with new JRE (#1243) 2025-01-22 15:49:22 -05:00
renovate[bot] e53386cf72 Update dependency adoptium/temurin21-binaries to jdk-21.0.6+7 (#1241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:33:30 -05:00
renovate[bot] b0e9b9b307 Update polyglot to v24.1.2 (#1240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:33:16 -05:00
renovate[bot] 789678a45d Update dependency io.insert-koin:koin-core to v4.0.2 (#1239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:33:04 -05:00
renovate[bot] 4ad01d3451 Update graphqlkotlin to v8.3.0 (#1235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:32:49 -05:00
renovate[bot] 6a87daa0b3 Update dependency org.bouncycastle:bcprov-jdk18on to v1.80 (#1231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:32:35 -05:00
renovate[bot] aebef87076 Update dependency io.github.reactivecircus.cache4k:cache4k to v0.14.0 (#1230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 13:32:13 -05:00
Mitchell Syer 32ff58598f Merge Service Files during build to fix GraalJS (#1242) 2025-01-22 13:32:01 -05:00
renovate[bot] 2d56cbe227 Update serialization to v1.8.0 (#1220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:50:49 -05:00
renovate[bot] fbef5f592b Update dependency com.android.tools.build:apksig to v8.8.0 (#1227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:47:47 -05:00
renovate[bot] 090af36f5e Update dependency com.squareup.okio:okio to v3.10.2 (#1223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:47:37 -05:00
renovate[bot] 885f27fcf1 Update dependency net.harawata:appdirs to v1.3.0 (#1219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:47:09 -05:00
renovate[bot] 8dc4eaf77a Update dependency io.insert-koin:koin-core to v4.0.1 (#1213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:46:58 -05:00
renovate[bot] a55bef08a8 Update kotlinx-coroutines monorepo to v1.10.1 (#1212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:46:32 -05:00
renovate[bot] 0dc3089739 Update dependency gradle to v8.12 (#1211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:46:22 -05:00
renovate[bot] 789ef0d783 Update dependency io.mockk:mockk to v1.13.16 (#1210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:46:11 -05:00
renovate[bot] 092db1106d Update dependency ch.qos.logback:logback-classic to v1.5.16 (#1209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:46:01 -05:00
renovate[bot] 5f4b5bc570 Update dependency io.javalin:javalin to v6.4.0 (#1205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 14:45:36 -05:00
Mitchell Syer 32581fcd5a [WIP] Customize JRE (#1177)
* Customize JRE

* Fix build push

* Run test

* Where is jre

* Try this

* Fix debain-all and linux-assets

* Stop ref-master for test

* Revert "Stop ref-master for test"

This reverts commit 8e34a12247087eff643676ef0ac692df4c2700ff.

* Revert "Run test"

This reverts commit dad629aaff2cf5c270b7fffeb98dfb9e3d1c93e5.
2025-01-12 14:45:22 -05:00
robo b14d28c406 new icons (#1222)
Co-authored-by: Robonau <30987265+Robonau@users.noreply.github>
2025-01-09 09:02:37 -05:00
schroda 1d1535dc55 Send dequeue download mutation response (#1218)
Response was never sent due to incorrect updates filter condition
2025-01-01 21:26:01 -05:00
schroda de942440e3 Handle missing track search results on bind (#1196)
It's possible that a manga is bound to a tracker while there is no search result.
This happens when e.g. restoring a backup which includes track bindings for which there was never a tracker search.

In that case when trying to e.g. copy the binding to another manga, the mutation would fail due to not finding a search result.
These cases can be handled by additionally checking the TrackRecordTable to get the necessary track info.
2024-12-10 19:50:16 -05:00
renovate[bot] b58fc39cf1 Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.3 (#1191)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 23:56:51 -05:00
Mitchell Syer 2e3af25dd4 Fix usage of deprecated functions (#1192)
* Fix usage of deprecated functions

* lint

* Lint

* Another
2024-12-07 23:56:42 -05:00
schroda 1d541a30ae Feature/update to exposed v0.57.0 (#1150)
* Update to exposed-migrations v3.5.0

* Update to kotlin-logging v7.0.0

* Update to exposed v0.46.0

* Update to exposed v0.47.0

* Update to exposed v0.55.0

* Update to exposed v0.56.0

* Update to exposed v0.57.0
2024-12-07 23:49:11 -05:00
renovate[bot] f926714544 Update dependency com.pinterest.ktlint:ktlint-cli to v1.5.0 (#1182)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:28:09 -05:00
renovate[bot] f68849d3a5 Update dependency com.russhwolf:multiplatform-settings-jvm to v1.3.0 (#1176)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:27:54 -05:00
renovate[bot] 2111232f42 Update kotlin monorepo to v2.1.0 (#1170)
* Update kotlin monorepo to v2.1.0

* Fix build warnings

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-12-07 14:27:47 -05:00
Mitchell Syer 088552bf56 Fix Deprecation Warnings (#1187) 2024-12-07 14:13:06 -05:00
Mitchell Syer 3eabbc9770 Manually update GraphQL-Java to fix subscription data loaders (#1186) 2024-12-07 14:12:56 -05:00
renovate[bot] 8e3b8df497 Update dependency com.android.tools.build:apksig to v8.7.3 (#1179)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:12:25 -05:00
renovate[bot] 06a5aaaa72 Update dependency com.fasterxml.jackson.core:jackson-databind to v2.18.2 (#1175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:12:00 -05:00
renovate[bot] 97c4f14094 Update dependency org.jsoup:jsoup to v1.18.3 (#1169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:11:43 -05:00
renovate[bot] 1fa7f18235 Update plugin ktlint to v12.1.2 (#1166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:11:33 -05:00
renovate[bot] b309d2fd4a Update dependency gradle to v8.11.1 (#1161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-07 14:11:22 -05:00
are-are-are 372b56bb1b add manga description (#1165) 2024-11-23 18:22:21 -05:00
schroda 3325a36cae Allow cors with credentials (#1163)
"anyHost" is not allowed in combination with "Access-Control-Allow-Credentials" (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sect2).
At least the default webUI always includes credentials which causes a cors policy violation
2024-11-22 20:00:25 -05:00
schroda 38673bbff4 Handle missing credentials as being invalid (#1164)
In case the credentials were missing the basic authentication was just bypassed
2024-11-22 20:00:16 -05:00
Mitchell Syer fb51834153 Fix RealUrl (#1162) 2024-11-20 22:57:42 -05:00
renovate[bot] 3a932a1e8a [skip ci] Update plugin buildconfig to v5.5.1 (#1157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 17:09:04 -05:00
schroda 53c61bcb17 Serve webui on all unmatched routes (#1156)
with "/root" only "http:localhost:4567" opened the webui all other endpoints resulted in "Endpoint GET /endpoint not found"
2024-11-17 21:11:40 -05:00
schroda 6951b4b20d Remove "grapqhl log level" setting (#1155)
internal logging was removed with graphql-java v22.0
2024-11-17 21:11:26 -05:00
Mitchell Syer 746f9f1a11 [WIP] Switch to GraalJS Engine (#793)
* Switch to GraalJS Engine

* Update Polygot
2024-11-17 15:24:08 -05:00
renovate[bot] 9a7344ccbe Update dependency ch.qos.logback:logback-classic to v1.5.12 (#1151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 15:07:59 -05:00
renovate[bot] ab2fb8747f Update jackson monorepo to v2.18.1 (#1148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 15:04:28 -05:00
renovate[bot] 9cd8cb3d54 Update dependency io.javalin:javalin to v6 (#1152)
* Update dependency io.javalin:javalin to v6

* Simple compile fixes

* Simple compile fixes pass 2

* Add results to futures

* Setup jetty server and api routes

* Setup Cors

* Setup basic auth

* Documentation stubs

* Replace chapter mutex cache

* Fix compile

* Disable Jetty Logging

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-11-17 15:00:53 -05:00
renovate[bot] ba1c2845b6 chore(deps): update plugin buildconfig to v5 (#1135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 12:50:42 -05:00
renovate[bot] 065aa19e9e Update graphqlkotlin to v8 (major) (#1143)
* Update graphqlkotlin to v8

* Go back to JsonMapper

* Add context to data loaders

* Compile fixes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-11-17 12:50:33 -05:00
Mitchell Syer 4c2a05c3a6 Update Java to 21 (#1149)
* Update Java to 21

* Update Readme
2024-11-17 12:17:39 -05:00
Syer10 fd45c0740c [skip ci] Update Renovate config 2024-11-16 13:00:09 -05:00
Syer10 e44bf920fa [skip ci] Update Renovate config 2024-11-16 12:47:53 -05:00
renovate[bot] 8a327b2dff [skip ci] chore(config): migrate config renovate.json (#1144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-16 12:41:15 -05:00
Syer10 6ece7e2596 Update Renovate config 2024-11-16 12:37:02 -05:00
renovate[bot] 2e2ce98be3 fix(deps): update dependency com.pinterest.ktlint:ktlint-cli to v1.4.1 (#1126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:39:15 -05:00
renovate[bot] fe4c2392db chore(deps): update plugin ktlint to v12 (#1136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:31:04 -05:00
schroda 320a0971b4 Fix/gql download subscription (#1137)
* Properly set download update type on exceptions

* Always send FINISHED download update to client for deprecated subscription

By the time the status was sent to the client, the finished download item was already removed from the queue, causing the client to never get the latest status, thus, having an outdated cache

Regression introduced with 168b76cb0c
2024-11-15 21:30:09 -05:00
renovate[bot] bfb70b6a05 fix(deps): update dependency com.ibm.icu:icu4j to v76 (#1140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:29:28 -05:00
renovate[bot] a68af62748 fix(deps): update twelvemonkeys to v3.12.0 (#1133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:29:11 -05:00
renovate[bot] 52bd5ce5cc fix(deps): update kotlinx-coroutines monorepo to v1.9.0 (#1132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:28:44 -05:00
renovate[bot] b93d486348 fix(deps): update dependency org.bouncycastle:bcprov-jdk18on to v1.79 (#1127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:28:30 -05:00
renovate[bot] d193c58e5f fix(deps): update dependency androidx.annotation:annotation to v1.9.1 (#1121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 21:28:15 -05:00
renovate[bot] 6ac2a61793 Update serialization to v1.7.3 (#1119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:09:02 -05:00
renovate[bot] a45c6f2197 Update kotlin monorepo to v2.0.21 (#1117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:08:53 -05:00
renovate[bot] 71d639bf19 Update dependency io.mockk:mockk to v1.13.13 (#1116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:08:41 -05:00
Mitchell Syer 9a51472726 Update Titles from the Source (#1115)
* Update Titles from the source

* Properly keep null fields
2024-11-14 18:08:31 -05:00
Mitchell Syer 0670f298cd Switch from Kodein to Koin (#1112)
* Switch from Kodein to Koin

* Ktlint
2024-11-14 18:08:19 -05:00
schroda aa1e98544b Fix/invalid server settings gql mutation request (#1092)
* Validate setting values on mutation

* Handle invalid negative setting values

* Ensure at least one source is downloading at all times

* Prevent possible IllegalArgumentException

The "serverConfig.maxSourcesInParallel" value could have changed after the if-condition
2024-11-14 18:08:07 -05:00
renovate[bot] fa4607e232 Update dependency gradle to v8.11 (#1091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:07:55 -05:00
renovate[bot] cb46420c09 Update dependency com.android.tools.build:apksig to v8 (#1076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:07:42 -05:00
renovate[bot] d88014fa90 Update xmlserialization (#1075)
* Update xmlserialization

* Fix XML update

* Use Jvm Serialization

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-11-14 18:07:29 -05:00
schroda 168b76cb0c Feature/graphql download queue subscription send only updates (#1011)
* Emit only download changes instead of full status

The download subscription emitted the full download status, which, depending on how big the queue was, took forever because the graphql subscription does not support data loader batching, causing it to run into the n+1 problem

* Rename "DownloadManager#status" to "DownloadManager#updates"

* Add initial queue to download subscription type

Adds the current queue at the time of sending the initial message.
This field is null for all following messages after the initial one

* Optionally limit and omit download updates

To prevent the n+1 dataloader issue, the max number of updates included in the download subscription can be limited.
This way, the problem will be circumvented and instead, the latest download status should be (re-)fetched via the download status query, which does not run into this problem.

* Formatting
2024-11-14 18:07:14 -05:00
schroda f5680c6d69 Switch to Koin from Injekt (#1109)
replace "com.github.inorichi.injekt" with "com.github.null2264:injekt-koin"
2024-11-09 11:32:51 -05:00
Mitchell Syer 654a3cc7ed Use Backup.serializer() (#1088) 2024-09-16 21:18:01 -04:00
Mitchell Syer 841cdc474f Remove Broken Sources and Broken History (#1084) 2024-09-15 00:19:47 -04:00
schroda 0adbea3a43 Remove manga artist, author length limit (#1080) 2024-09-15 00:10:23 -04:00
schroda e12bada052 Use correct sync id (#1079) 2024-09-15 00:10:08 -04:00
renovate[bot] 68dbefc46f Update dependency gradle to v8.10.1 (#1072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:15:15 -04:00
renovate[bot] 9d71e9b177 Update twelvemonkeys to v3.11.0 (#1069)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:15:06 -04:00
renovate[bot] 5b5801c2cf Update dependency com.squareup.okio:okio to v3.9.1 (#1071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:07:53 -04:00
Antoine Aflalo df1cc2b8e9 fix(flaresolverr): fix cookie expiry for flaresolverr (#1070)
Cookie expiry is returned in seconds, the persistent cache expect it in miliseconds. Cookie is always considered as expired
2024-09-14 09:04:49 -04:00
renovate[bot] 18d399b3f7 Update settings to v1.2.0 (#1068)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:04:22 -04:00
renovate[bot] e9687fd182 Update serialization to v1.7.2 (#1067)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:04:13 -04:00
renovate[bot] 79137a074c Update plugin download to v5.6.0 (#1066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:04:04 -04:00
renovate[bot] dae55ca386 Update graphqlkotlin to v6.8.5 (#1064)
* Update graphqlkotlin to v6.8.5

* Replace Jackson with Kotlinx.Serialization where possible

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-09-14 09:03:53 -04:00
renovate[bot] b7f040d89a Update dependency org.jsoup:jsoup to v1.18.1 (#1060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 09:03:44 -04:00
renovate[bot] dc69df9f4f Update dependency com.squareup.okio:okio to v3.9.0 (#1056)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:37:36 -04:00
Syer10 6c1fbfa63b [skip ci] Formatting 2024-09-03 21:37:18 -04:00
renovate[bot] e968a2195a [skip ci] Update dependency com.pinterest.ktlint:ktlint-cli to v1.3.1 (#1055)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:36:34 -04:00
Syer10 000bcea181 [skip ci] Update SystemTray 2024-09-03 21:27:02 -04:00
renovate[bot] 8edf508453 [skip ci] Update dependency org.apache.commons:commons-compress to v1.27.1 (#1058)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:23:45 -04:00
renovate[bot] bc9cc50130 [skip ci] Update dependency org.bouncycastle:bcprov-jdk18on to v1.78.1 (#1059)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:23:26 -04:00
renovate[bot] 71091d88fc [skip ci] Update dependency com.android.tools.build:apksig to v7.4.2 (#1049)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:17:59 -04:00
renovate[bot] 70c1d7e21f [skip ci] Update dependency io.github.config4k:config4k to v0.7.0 (#1057)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 21:17:35 -04:00
renovate[bot] c07920978e Update tachiyomiorg/issue-moderator-action action to v2 (#1032)
* Update tachiyomiorg/issue-moderator-action action to v2

* Update issue_moderator.yml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2024-09-02 21:47:42 -04:00
renovate[bot] 954b2919ac [skip ci] Update plugin ktlint to v11.6.1 (#1043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:44:01 -04:00
renovate[bot] c630f731ed [skip ci] Update dependency androidx.annotation:annotation to v1.8.2 (#1046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:43:47 -04:00
renovate[bot] aaefa7f74e [skip ci] Update coroutines to v1.8.1 (#1045)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:43:19 -04:00
renovate[bot] 2ec6b471f1 [skip ci] Update dependency gradle to v8.10 (#1047)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:43:02 -04:00
Syer10 a5c5ab68d2 [skip ci] Ktlint 2024-09-02 21:28:59 -04:00
Syer10 fb045c501a Update Kotlin to 2.0.20 2024-09-02 21:25:01 -04:00
renovate[bot] 6a6e411492 [skip ci] Update dependency net.harawata:appdirs to v1.2.2 (#1033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:15:22 -04:00
renovate[bot] 76aac330fc [skip ci] Update dependency org.slf4j:slf4j-api to v2.0.16 (#1034)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:14:59 -04:00
renovate[bot] 07bdf31f66 [skip ci] Update okhttp monorepo to v5.0.0-alpha.14 (#1039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:14:43 -04:00
renovate[bot] fe14928af6 [skip ci] Update rhino to v1.7.15 (#1044)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 21:13:38 -04:00
renovate[bot] ad0c1033a4 [skip ci] Update GitHub Artifact Actions to v4 (#1040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 22:06:41 -04:00
Mitchell Syer 0c2448fb99 Update gradle build action (#1035) 2024-09-01 21:57:02 -04:00
renovate[bot] cedda145a5 Update gradle/gradle-build-action action to v3 (#1029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 20:02:46 -04:00
renovate[bot] ee73187f1a [skip ci] Update gradle/wrapper-validation-action action to v3 (#1030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 20:02:17 -04:00
renovate[bot] 89f91d6800 [skip ci] Update actions/checkout action to v4 (#1028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 20:02:05 -04:00
renovate[bot] 6714827694 [skip ci] Update softprops/action-gh-release action to v2 (#1031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 20:01:54 -04:00
renovate[bot] aad73f7d19 [skip ci] Update dependency io.mockk:mockk to v1.13.12 (#1026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 19:29:54 -04:00
renovate[bot] c5985de1c3 [skip ci] Update dependency com.typesafe:config to v1.4.3 (#1025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 19:29:41 -04:00
renovate[bot] 86a5b0879a Add renovate.json (#1024)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 19:24:12 -04:00
schroda 414972d545 Feature/update log file rotation (#1023)
* Keep up to 31 log files

On average one log file per day gets created, thus, increasing to 31 files will store log files for one month

* Decrease total log files size to 100mb

* Make log appender settings configurable
2024-08-31 18:55:26 -04:00
Antoine Aflalo 9a74ae5844 feat(comicinfo): add date fields to comic info (#1021)
* feat(comicinfo): add date fields to comic info

This will be parsed by Komga, Kavita etc ... and any other library management to also have the date of the chapter.

* refactor: improve code readability

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2024-08-31 18:55:13 -04:00
Antoine Aflalo 301980ab14 fix(flaresolverr): support possible rewrite flaresolverr (#1020)
This PR:
https://github.com/FlareSolverr/FlareSolverr/pull/1300

Solve a lot of issue with not solving challenge, however, the cookie don't have path, httpOnly, secure and sameSite.

By making them optional that should work for both version of flaresolverr.
2024-08-31 18:55:02 -04:00
schroda 5dced82e5a Fix/missed automated task execution failure crashes server on startup (#1019)
* Catch automated backup task errors

* Catch automated udpate task errors

* Catch automated webui update task errors
2024-08-31 18:54:51 -04:00
schroda 9a1e4df408 Fix/server startup blocked by synchronous tasks (#1018)
* Launch missed auto backup task in background

* Launch missed auto global update task in background

* Launch missed auto webui update check task in background
2024-08-31 18:54:41 -04:00
schroda 5b08b81239 Initialize manga on add to library (#1016)
In case a manga gets added to the library which has not been initialized yet, it should be tried to initialize it.
Since it's not an error to have uninitialized manga in the library, this can be done in the background via the updater and the client receives the updated data via the update subscription.
2024-08-31 18:54:30 -04:00
schroda 7fac538ba3 Initialize uninitialized manga during global update (#1015)
They were only initialized in case the setting to refresh manga metadata during an update was enabled.
However, this should always be done for uninitialized manga, regardless of the setting.

06bfc33e72 prevents uninitialized manga from getting filtered out, however, it did not ensure to initialize the manga
2024-08-31 18:54:18 -04:00
schroda ef6be74ec2 Fix/chapter downloaded check (#1012)
* Properly check for first page in cbz files

The download check for cbz files only checked if the archive existed but didn't check for the first page

* Streamline getImageImpl of ChapterDownloadProviders

* Exclude comic info file from page list

In case the download folder did not contain any page files, only the comic info file existed, which caused the download check to incorrectly detect the first page

* Add logging to ChapterForDownload#asDownloadReady
2024-08-31 18:54:06 -04:00
schroda 9f49587245 [skip ci] Update readme (#1008)
* Move JUI and Sorayomi to inactive clients

* Update client descriptions

* Remove outdated mihon sync info

* Update feature list

* Move Docker installation info to top of list

* Move feature list higher in readme

* Rename feature list section to "Features"

* Separate inactive/abonded clients
2024-08-18 17:17:44 -04:00
schroda b7b733f351 [skip ci] Feature/update readme (#1005)
* Replace tachiyomi with mihon

* Update "syncing with mihon" readme section

* Update graphql section in CONTRIBUTING.md
2024-08-17 20:10:47 -04:00
schroda 06bfc33e72 Always update uninitialized manga in global update (#997)
Manga can be added to the library while they have not been initialized yet.
In this case, depending on the manga exclusion setting, they will never be updated automatically unless they get refreshed once manually.
2024-07-28 15:58:23 -04:00
schroda fbcd55d6c5 Add "hasDuplicatedChapters" field to gql MangaType (#995) 2024-07-28 15:58:12 -04:00
schroda 2484b5f14b Fix/downloaded chapters with lost page count (#994)
* Persist page count during chapter list update

In case a downloaded chapter gets deleted during a chapter list update, the download status was tried to be preserved.
However, in case the status could be preserved, the page count was lost and thus, the chapter now was marked as downloaded with a page count of -1.

* Mark downloaded chapters without page count as not downloaded
2024-07-28 15:58:01 -04:00
schroda 6982659658 Fix/inserting duplicated chapters into database (#991)
* Prevent adding duplicated chapters into the db

it's possible that the source returns a list containing chapters with the same url
once such duplicated chapters have been added, they aren't being removed anymore as long as there is
a chapter with the same url in the fetched chapter list, even if the duplicated chapter itself
does not exist anymore on the source

* Drop duplicated chapters from database table

* Add unique constraint to chapter table

This is to completely prevent duplicated chapters from being added to the database.
Since once a duplicated chapter has been added to the database, it does not get removed anymore as long as a chapter with the same url is included in the requested source chapter list
2024-07-28 15:57:50 -04:00
AeonLucid 9e006166a8 Add setting to use the flaresolverr response (#990) 2024-07-28 15:57:40 -04:00
AeonLucid 25a62e33a1 Fix PersistentCookieStore for domains with an underscore (#989)
* Fix PersistentCookieStore for domains with an underscore

* Missed one uri
2024-07-28 15:57:30 -04:00
AeonLucid d05ed0a56c Fix SOCKS5 authentication by setting a default Authenticator (#988)
* Fix SOCKS5 authentication by setting a default Authenticator

* Fix lint
2024-07-28 15:57:17 -04:00
schroda eaffb2755c Cleanup only created auto backup files (#984)
The automated backup cleanup just deleted every file (recursively in subfolders as well) in the set folder in case it was older than the set backup ttl.
This made it impossible to save the automated backups into a folder with different files.
2024-06-29 11:44:03 -04:00
schroda e0fcae2ae3 Handle deprecated gql sort again (#983) 2024-06-28 09:21:32 -04:00
schroda af9ad61174 Feature/gql simpilify filtering for multiple values (#960)
* Remove code duplication

* Remove unnecessary functions

* Simplify filtering for multiple values in queries

Makes it easier to filter for multiple values at ones without having to nest filters with multiple "and".

e.g.

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: "action"}, and: {genre: {includesInsensitive: "adventure"}, and: { ... }}}
 ) {
  nodes {
   id
  }
 }
}
```

can be simplified to

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: ["action", "adventure", ...]}}
 ) {
  nodes {
   id
  }
 }
}
```

* Add filter for matching "any" value in list

Makes it easier to filter for entries that match any value without having to nest filters with multiple "or".

e.g.

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitiveAny: ["action", "adventure", ...]}}
 ) {
  nodes {
   id
  }
 }
}
```

instead of

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: "action", or: {genre: includesInsensitive: "adventure", or: {...}}}}
 ) {
  nodes {
   id
  }
 }
}
```

* Add util function to apply "andWhere/All/Any"
2024-06-27 20:34:29 -04:00
schroda 7c54ad54fc Sort gql queries by multiple columns (#963) 2024-06-27 20:34:21 -04:00
schroda aee9f1032c Exit early in case of empty chapter id list (#980)
In case the list was empty, the batch update failed with a NoSuchElement exception
2024-06-27 20:34:12 -04:00
robo f7d0605e0a [skip ci] Remove docker commands (#972)
* [skip ci] Remove docker commands

* mention compose file
2024-06-16 15:44:01 -04:00
Syer10 f40dcafb43 Release v1.1.1
CI Publish / Validate Gradle Wrapper (push) Successful in 11s
CI Publish / Build Jar (push) Failing after 8s
CI Publish / Make debian-all release (push) Has been skipped
CI Publish / Make linux-assets release (push) Has been skipped
CI Publish / Make linux-x64 release (push) Has been skipped
CI Publish / Make macOS-arm64 release (push) Has been skipped
CI Publish / Make macOS-x64 release (push) Has been skipped
CI Publish / Make windows-x64 release (push) Has been skipped
CI Publish / Make windows-x86 release (push) Has been skipped
CI Publish / release (push) Has been skipped
2024-06-15 13:15:00 -04:00
schroda d9cb54b285 Compare webUI version with bundled webUI version (#969)
* Compare webUI version with bundled webUI version

The bundled webUI version was incorrectly compared with the minimum server version.
This worked until the latest release, because the bundled webUI version had a lower revision number than the server revision, however, with the latest release, it is now higher, resulting in no compatible webUI version to be found

* Consider bundled webUI version only for default flavor

* Add "isDefault" util function to WebUIFlavor
2024-06-15 13:05:39 -04:00
schroda f738a162d3 Support for "STABLEPREVIEW" webUI version (#970)
Makes it possible to release new stable webUI versions without having to update the mapping file.
2024-06-15 13:05:28 -04:00
Syer10 fda4cd6783 Release v1.1.0
CI Publish / Validate Gradle Wrapper (push) Successful in 13s
CI Publish / Build Jar (push) Failing after 7s
CI Publish / Make debian-all release (push) Has been skipped
CI Publish / Make linux-assets release (push) Has been skipped
CI Publish / Make linux-x64 release (push) Has been skipped
CI Publish / Make macOS-arm64 release (push) Has been skipped
CI Publish / Make macOS-x64 release (push) Has been skipped
CI Publish / Make windows-x64 release (push) Has been skipped
CI Publish / Make windows-x86 release (push) Has been skipped
CI Publish / release (push) Has been skipped
2024-06-14 21:41:17 -04:00
schroda ecd1604e25 Update metadata in source browse only if new data is not null (#962)
Browsing a source loads only a minimal representation of a manga which does not include some metadata.
This metadata is only loaded when the specific manga gets fetched.

Thus, when the extra metadata of a manga was already loaded, it got removed when browsing the source and a page response included this manga
2024-06-09 12:08:21 -04:00
Mitchell Syer 0f061900af Fix browse source (#961) 2024-06-09 11:23:54 -04:00
Rat Cornu c47f5ea85e [skip ci] doc: add NixOS installation (#959) 2024-06-08 10:48:43 -04:00
Mitchell Syer 306eb0e3c7 Update manga info when browsing if not in library (#958)
* Update manga info when browsing if not in library

* Cleanup
2024-06-06 21:17:17 -04:00
schroda e64025ded8 Correctly set name of logger (#956) 2024-06-02 20:33:32 -04:00
schroda c1fe2da636 Fix/failing thumbnail requests with http 410 (#955)
* Refresh thumbnail url on 410 error

* Refresh thumbnail url on 301 error
2024-06-02 20:33:25 -04:00
schroda ff23f58a4f Support partial mutation responses (#954)
In case e.g. a mutation was made which looked like this

myMutation {
  mutationA { ... }
  mutationB { ... }
  mutationC { ... }
}

and mutation A and B succeeded while mutation C failed, the response only included the error of C and the successful mutation data response of A and B was missing
2024-06-02 20:33:17 -04:00
schroda fc2f5ffdf9 Fix/failing track progress update for logged out trackers (#953)
* Refresh track record only when logged in

In case one tracker was logged out, the refresh failed with an unauthenticated error and caused the other trackers to not get updated

* Prevent chapter track update from failing due to failure of other tracker

* Change level of log to "info"
2024-06-01 12:22:25 -04:00
schroda 6dd9ed7fb0 Fix/prevent importing unsupported trackers from backup II (#945)
* Properly prevent importing unsupported trackers from backup

Missed the early return in case no tracker record exists in the database in 2f362abb91be875e943b1364eb86d70a4144dd6f...

* Remove incorrect non null assertion

Prevented unbinding track records of unsupported trackers
2024-05-07 09:30:45 -04:00
schroda 2f362abb91 Prevent importing unsupported tracker from backup (#944)
* Prevent importing unsupported tracker from backup

This will lead to graphql field validation errors (non null declared field is null) once the track records get used, since they will point to trackers that do not exist

* Delete track records of unsupporter trackers

* Always return all track records of manga

Was already partially changed in 7df5f1c4c4 but this occurrence was missed
2024-05-06 09:29:34 -04:00
FumoVite 96807a64cf [skip ci] Update README.md (#941)
fixed "inctive"
2024-05-05 13:24:31 -04:00
schroda 7df5f1c4c4 Feature/backup tracking (#940)
* Include tracking in validation of backup

* Always return track records

Not clear why an empty list should be returned in case no trackers are logged in

* Include tracking in backup creation

* Restore tracking from backup
2024-05-05 13:24:16 -04:00
schroda cf1ede9cf7 Update lastPageRead on chapter update (#939)
Broken with 729385588a3d8e06ec8be38865a12c47e88f6bcb...
2024-04-28 10:35:33 -04:00
schroda 729385588a Prevent greater last page read than page count (#938)
In case multiple chapters are getting updated, the last page read might be higher than the available pages of a chapter
2024-04-28 00:34:40 -04:00
schroda 668d5cf8f0 Prevent IndexOutOfBoundsException when removing duplicated chapters (#935)
In case the "new" chapters consisted only of re-uploads an out of bound exception was thrown
2024-04-27 20:33:30 -04:00
schroda 72b1b5b0f9 Exit track progress update early in case new chapter is same as current local (#937)
Prevents unnecessary requests
2024-04-27 20:33:19 -04:00
schroda fbf726c174 Use "AsyncExecutionStrategy" for mutations (#932)
Batching only works with "AsyncExecutionStrategy" and by default mutations use "SerialExecutionStrategy"
2024-04-15 17:49:33 -04:00
schroda c441eed847 Exclude duplicated chapters from auto download limit (#923)
In case the new chapters include duplicates from different scanlators, they would be included in the limit causing the auto download to potentially only download duplicated chapters while there might be more non duplicated chapters to download.

Instead, the limit should only consider unique chapters and then should include all duplicates of the chapters that should get downloaded
2024-04-06 23:07:55 -04:00
schroda e8e83ed49c Remove duplicated mangas from gql "mangas" query (#924) 2024-04-06 22:53:56 -04:00
schroda cdc21b067c Fix/recognition of already downloaded chapters (#922)
* Remove overrides of "ChapterFilesProvider::downloadImpl"

* Check final download folder for existing page on download

Downloads were changed to get downloaded to the system temp folder instead to directly into the final download folder.

This broke the check for existing pages, because now only the temp folder was checked instead of both the temp and the final download folder.

Regression introduced with 1c9a139006

* Properly check for already existing downloaded pages

The previous check was always false because the file ending of the page file is unknown and thus, missing from the created file path

* Cleanup cache download folder
2024-04-06 22:53:49 -04:00
schroda 48e19f7914 Feature/auto download of new chapters improve handling of unhandable reuploads (#921)
* Update test/server-reference file

* Properly handle re-uploaded chapters in auto download of new chapters

In case of unhandable re-uploaded chapters (different chapter numbers) they potentially would have prevented auto downloads due being considered as unread.

Additionally, they would not have been considered to get downloaded due to not having a higher chapter number than the previous latest existing chapter before the chapter list fetch.

* Add option to ignore re-uploads for auto downloads

* Extract check for manga category download inclusion

* Extract logic to get new chapter ids to download

* Simplify manga category download inclusion check

In case the DEFAULT category does not exist, someone messed with the database and it is basically corrupted
2024-04-06 22:53:36 -04:00
schroda 89dd570b30 Add mutation to fetch the latest track data from the tracker (#920)
* Add mutation to fetch the latest track data from the tracker

* Update Track.kt

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2024-03-31 13:25:58 -04:00
schroda 16474d4328 Feature/tracking gql add option to delete remote binding on tracker (#919)
* Extract unbinding track into function

* Introduce new unbind mutation

* Add option to delete track binding on track service

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2024-03-31 13:22:13 -04:00
schroda 9db612bf03 Move trigger for track progress update to client (#918)
Triggering the progress update on server side does not work because the client needs to get the mutation result, otherwise, the clients cache will get outdated
2024-03-31 13:20:37 -04:00
schroda 7d92dbc5c0 Fix/tracking progress update in case local chapter is smaller than remote (#917)
* Update lastReadChapter on bind in case it's greater than remote

* Update lastReadChapter on chapter read in case it's greater than remote

* [Logging] Improve logs
2024-03-31 13:20:27 -04:00
schroda a9efca8687 Add chapter bookmark count field to MangaType (#912) 2024-03-31 13:20:19 -04:00
schroda dbfea5d02b Update inLibraryAt timestamp when adding manga to library (#911) 2024-03-31 13:20:08 -04:00
schroda a6b05c4a27 Feature/refresh outdated thumbnail url on fetch failure (#910)
* Extract thumbnail url fresh into function

* Remove incorrect non-null assertion

According to the typing there is no guarantee that fetching a manga from the source provides a thumbnail url

* Refresh manga thumbnail url on 404 error

* Refresh manga thumbnail url on unreachable origin cloudflare errors
2024-03-31 13:19:58 -04:00
schroda 6d539d3404 Fix/update subscription clear data loader cache (#908)
* Set updater running flag to false only at the end of the update

For clearing the data loader cache properly, the update status subscription requires the update to be running.

For the last completed manga update the flag was immediately set to false which prevented the dataloader cache from getting cleared, returning outdated data for the last updated manga

* Correctly clear the "MangaForIdsDataLoader" cache

The cache keys for this dataloader are lists of manga ids.
Thus, it is not possible to clear only the cached data of the provided manga id and instead each cache entry that includes the manga id has to be cleared

* Ensure that manga dataloader caches gets cleared during global update

The "StateFlow" drops value updates in case the collector is too slow, which was the case for the "UpdateSubscription".

This caused the dataloader cache to not get properly cleared because the running state of the update was already set to false.
2024-03-31 13:19:49 -04:00
Mitchell Syer b2aff1efc9 Fix MAL after restarting the server (#903)
* Fix MAL after restarting the server

* Cleanup MAL interceptor

* Fix

* Cleanup Anilist interceptor

* Use IOException

* Make Anilist private

* Lint
2024-03-16 23:36:45 -04:00
schroda 8a20a1ef50 Add first unread chapter field to MangaType (#900) 2024-03-10 19:01:03 -04:00
schroda 33cbfa9751 Fix/electron launch error not logged (#895)
* Log "Browser::openInBrowser" errors

The error was never written to the log file.
It was only visible in the console

* Remove "printStackTrace" usage with logs
2024-03-10 19:00:54 -04:00
schroda b95a8d44d4 Always fetch thumbnail of manga from local source (#898)
The local manga thumbnail got "downloaded" to thumbnail download folder of in library manga.
Since the "thumbnail url" of a local source manga never changes, the "downloaded" manga thumbnail never got updated

Regression introduced with f2dd67d87f
2024-03-10 19:00:44 -04:00
Syer10 54df9d634a Fix release
CI Publish / Validate Gradle Wrapper (push) Successful in 17s
CI Publish / Build Jar (push) Failing after 16s
CI Publish / Make debian-all release (push) Has been skipped
CI Publish / Make linux-assets release (push) Has been skipped
CI Publish / Make linux-x64 release (push) Has been skipped
CI Publish / Make macOS-arm64 release (push) Has been skipped
CI Publish / Make macOS-x64 release (push) Has been skipped
CI Publish / Make windows-x64 release (push) Has been skipped
CI Publish / Make windows-x86 release (push) Has been skipped
CI Publish / release (push) Has been skipped
2024-02-23 13:35:08 -05:00
Syer10 4eb9a696ff Fix release 2024-02-23 13:29:57 -05:00
Syer10 7b4fb4682b Add ChangeLog 2024-02-23 12:56:46 -05:00
Syer10 da4275530d v1.0.0 2024-02-23 12:13:42 -05:00
Mitchell Syer 1c417e909a Support Comic Info creation on download (#887)
* Support Comic Info creation on download

* Update Json and add Protobuf
2024-02-22 14:29:30 -05:00
Aria Moradi fc53d69f82 Add auth and version support to socks proxy (#883)
* Add auth support to socsk proxy

* better logging

* fix lint issue

* implement fixes and version

* add to test reference too
2024-02-19 11:06:39 -05:00
Mitchell Syer dda86cdb93 Seperate out migrations to allow run-once migrations (#882)
* Seperate out migrations to allow run-once migrations

* Previous

* Move migrations to a new file
2024-02-19 11:06:31 -05:00
Mitchell Syer 525a974e3a Start Server after routes are defined (#881)
* Start Server after routes are defined

* Separate events
2024-02-19 11:06:20 -05:00
Mitchell Syer b18c155e22 Fix Downloader Memory Leak (#880) 2024-02-19 11:06:13 -05:00
Mitchell Syer 07e011092a Support Token Expiry properly (#878)
* Support token expiry properly

* Small fix

* Lint

* Use newer fixes for expiry

* Lint
2024-02-19 11:06:00 -05:00
Aria Moradi 6803ac0611 move qtui to inactive list as it hasen't had commits in 2 years 2024-02-19 15:21:51 +03:30
Mitchell Syer af0dde5ae8 Add Source Meta (#875) 2024-02-17 11:24:01 -05:00
Mitchell Syer ea6edaecc4 Fix local source being accidentally removed (#874) 2024-02-17 11:23:53 -05:00
schroda eb2054bd5e Add VUI as a webUI flavor (#873) 2024-02-17 11:23:45 -05:00
schroda b277b3e3af Add thumbnail fetch timestamp to the gql manga type (#872) 2024-02-17 11:23:35 -05:00
schroda 9dc3a4e6ee Use correct name for scores data loader (#870) 2024-02-17 11:23:25 -05:00
schroda 6fbd2f1079 Feature/remove download ahead logic (#867)
* Remove download ahead logic

Unnecessary on server side, should just be done by the client

* Rename "autoDownloadAheadLimit" to "autoDownloadNewChaptersLimit"

* Deprecate the old field

* Update Stable WebUI

* Update Stable WebUI

---------

Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-02-17 11:23:13 -05:00
schroda 9edbc7f1d7 Feature/support different webui flavors (#863)
* Run functions for specific webui flavor

* Set default flavor of WebUIFlavor enum

* Consider flavor of served webUI when checking for update

In case the flavor was changed and the served webui files are still for the previous flavor, the update check could incorrectly detect no update

* Skip validation during initial setup

In case initial setup is triggered because of an invalid local webUI, doing a validation again is unnecessary

* Handle changed flavor on startup
2024-02-17 11:23:01 -05:00
schroda 8aa75be0d3 Cleanup gql subscription session state correctly (#859)
In case a socket got disconnected, the session state of the subscriptions did not get correctly cleaned up.
The active operations did get closed but not removed and thus, when the client tried to reconnect, the server incorrectly detected an active subscription for an operation and immediately terminated the subscription.
2024-02-01 21:34:11 -05:00
Chance Zibolski dc124fb15c Make flaresolverr session options configurable (#854)
Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
2024-01-24 21:11:53 -05:00
Chance Zibolski 9109d1ca3e Use a session with flaresolverr (#853)
Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
2024-01-24 20:44:45 -05:00
schroda 02296f1d1c Change flaresolverr settings to be non optional (#852)
The settings are not optional in the ServerConfig, thus, they should also not be optional in the returned settings type
2024-01-24 20:02:36 -05:00
Mitchell Syer 63e1082b97 Minor fixes for FlareSolverr (#851)
* Minor fixes for FlareSolverr

* Weird crash but ok
2024-01-24 17:49:51 -05:00
schroda 285f228660 Gracefully shutdown server in case webUI can't be setup (#850) 2024-01-24 17:49:42 -05:00
schroda c18cf069b1 Prevent invalid webUI from stopping the server (#849)
In case there is no internet connection, it is not possible to verify the webUI files, leading to the server to fail from starting up.
Instead, the existing webUI should just be used
2024-01-24 17:49:28 -05:00
schroda fc64f47589 Fix/excessive logging (#848)
* Remove log of mangas to update

This logged the full manga data objects in the list with information that is not needed (e.g. description of a manga).
Once a manga gets updated via the updater, it gets logged, which should be enough

* Include manga id in updater log

* Use "toString" to log mangas

* Change "HttpLoggingInterceptor" level to "BASIC"

Was unintentionally merged with d658e07583
2024-01-24 17:49:16 -05:00
Mitchell Syer 562b940d91 Remove dot before cookie (#845) 2024-01-23 19:21:33 -05:00
Mitchell Syer d658e07583 Implement FlareSolverr (#844)
* Implement FlareSolverr

* Oops
2024-01-23 18:48:55 -05:00
Mitchell Syer 9121a6341c Fix Tracker Status and Scores (#843) 2024-01-23 18:48:47 -05:00
Mitchell Syer 4bec027f11 Change Track.bind to use trackerId + remoteId (#842) 2024-01-22 21:35:56 -05:00
Mitchell Syer b9053e3057 Fix graphql tracking (#840) 2024-01-21 20:04:24 -05:00
Mitchell Syer 0621138478 Improve Tracker Icons Implementation (#836)
* Improve tracker icons implementation

* Fix description
2024-01-21 14:37:51 -05:00
Mitchell Syer ce42e89e25 Add MangaUpdates (#834) 2024-01-21 11:45:34 -05:00
Mitchell Syer 46e1e4c043 Table for Track Searches (#833)
* Table for Track Searches

* Lint
2024-01-20 23:12:18 -05:00
Andrei Paunescu 621468a183 Apply natural sort to local manga pages in Directory format (#826) 2024-01-20 19:42:08 -05:00
schroda d8876cf96a Add mutex to "updateExtensionDatabase" (#829)
If called in quick succession it is possible that duplicated extensions get inserted to the database, because it has not yet been updated by the first call
2024-01-20 19:42:01 -05:00
Chance Zibolski 57d5bc6480 Add support for configuring which categories are downloaded automatically (#832)
* Rename IncludeInUpdate class to IncludeOrExclude

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>

* Add support for configuring which categories are downloaded automatically

If a manga has no configured categories, behavior remains the same and
the automatic download functionality will download new chapters without
consulting the category includeInDownload configuration.

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>

---------

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
2024-01-20 19:41:47 -05:00
Mitchell Syer f224918f33 Create bin folder (#822) 2024-01-13 11:48:15 -05:00
Mitchell Syer 7b290dc465 Update User Agent (#821) 2024-01-12 23:48:33 -05:00
Mitchell Syer b1412dda34 Update Java 8 (#820) 2024-01-12 23:46:57 -05:00
Mitchell Syer 28e4ac8dcb Remove Playwright (#643) 2024-01-12 23:46:47 -05:00
robo 79eeb6d703 [skip ci] add VUI to README.md (#819) 2024-01-12 23:46:37 -05:00
Mitchell Syer 0d0e735d0e Fix brotli (#818) 2024-01-11 23:09:31 -05:00
Mitchell Syer d994502d06 Update Electron (#817) 2024-01-11 23:09:22 -05:00
schroda dfbd7a65ae [skip ci] Correct wrong tracker oauth example url (#814) 2024-01-10 21:06:06 -05:00
schroda f99f94c8d7 Enable tracking (#813)
* Enable tracking

* Add documentation
2024-01-10 20:31:56 -05:00
schroda 41c643496a Add more chapter fields to MangaType (#812)
- last read
- latest read (latest fetched chapter that has been read)
- latest fetched
- latest uploaded
2024-01-10 20:31:47 -05:00
Mitchell Syer e5476f8a01 Extension repo fixes and improvements (#811) 2024-01-09 19:42:53 -05:00
Mitchell Syer 188fb188ce Set Mac Launcher Executable (#810) 2024-01-09 19:40:42 -05:00
schroda c852592b34 Prevent adding duplicated extensions to the db table (#808)
* Prevent adding duplicated extensions to the db table

There is a possibility that multiple extension repos have been added which contain the same extensions.
In case these extensions have not been added to the db table yet, they would all get added to the db, which would create duplicated extensions

* Use the extension with the latest version from all repos

In case multiple repos have the same extension, use the extension with the latest version
2024-01-09 19:40:31 -05:00
schroda 3a1e0c5a63 Remove extension obsolete flag when updating db after extension list fetch (#807)
In case an extension got marked as obsolete and was found again in a set repo, the obsolete flag has to be removed
2024-01-09 19:40:16 -05:00
schroda 6376972130 Remove caching of extensions for gql mutation (#806)
The client should use the extension query to get "cached" extensions and the mutation to update the extensions
2024-01-09 19:38:21 -05:00
Mitchell Syer c70c860a82 Create Client IDs (#804)
* Create Client IDs

* Cleanup imports
2024-01-07 15:36:23 -05:00
Tachimanga 5a178ada74 add trackers support (#720)
* add trackers support

* Cleanup Tracker Code

* Add GraphQL support for Tracking

* Fix lint and deprecation errors

* remove password from logs

* Fixes after merge

* Disable tracking for now

* More disabled

---------

Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2024-01-07 15:07:41 -05:00
Mitchell Syer 230427e758 Support Custom Repos (#803)
* Support custom repos

* Fix migration

* Make extension after update optional
2024-01-05 19:14:09 -05:00
Mitchell Syer abf1af41a3 Update bundled webui (#802) 2024-01-05 15:22:04 -05:00
Mitchell Syer 61e2548bb7 Deb fixes (#801) 2024-01-05 14:51:23 -05:00
Mitchell Syer f739c54292 Rename the project (#795) 2024-01-05 14:12:35 -05:00
brianmakesthings 3ed84de320 [skip ci] Add API info (#798) 2023-12-25 23:51:53 -05:00
schroda 621b4c0946 Correctly calculate the first chapter to download index (#796)
Subtracting 1 from the first chapter to download index caused an additional chapter to get downloaded (e.g. limit 4 would download 5 chapters)

Regression was introduced with 05bf4f5525
2023-12-23 16:23:06 -05:00
schroda 11be969101 Fix/download subscription returning outdated data for finished downloads (#794)
* Return latest data for finished downloads

In case a download has finished, the cache of the data loader has to be cleared to be able to get the latest data, otherwise, the returned chapter will still be marked as not downloaded

* Correctly clear manga data loader caches
2023-12-16 12:59:17 -05:00
schroda ea958cd8f7 Correctly emit the current status immediately (#792)
For finished downloads the immediate emission did not work because the emission was done async and by the time the state got updated with the new status, the finished download was already removed from the queue.
Thus, the new state was missing the finished download.
2023-12-16 11:26:48 -05:00
Mitchell Syer 56048dcdb0 Update Github Actions (#788)
* Update github actions

* Replace set-output
2023-12-08 19:50:12 -05:00
schroda fb545947ec Feature/gql improve webui update status (#783)
* Remove "updateAvailable" from webui update info

Doesn't add anything

* Extract status creation into function

* Optionally emit status immediately

Otherwise, some emissions can get lost due to the 1s sample period

* Rename "STOPPED" state to "IDLE"

* Reset webui update status

Currently, the update status never gets reset.
Thus, it will be "FINISHED" or "ERROR" until the next update gets triggered.
Due to this, the client won't know that the update result was already handled and will handle it again the next time it gets the update status.

To prevent this, the client has to be able to tell the server that it has handled the update result and that it can be resetted
2023-12-08 19:17:25 -05:00
schroda df57070b70 Make sure to always send finished chapter downloads with the download status (#782)
Due to not immediately sending the status, the finished chapters were already removed from the queue by the time the status was actually send to the client.
This caused the client to never receive a status with the chapters downloaded flag to be true, resulting in the client to not know that a chapter is downloaded
2023-12-08 19:16:52 -05:00
Mitchell Syer 9b27d7ee23 Improve Http Client Configuration (#786)
* Improve Http Client Configuration

* Lint
2023-12-08 19:16:38 -05:00
Mitchell Syer a2d3fa6e1d Use new Tachiyomi backup filename format (#787)
* Use new Tachiyomi backup filename format

* Lint

* Get Backup Filename in more places

* Delete BackupFull
2023-12-08 19:16:25 -05:00
schroda 94b670eb81 Fix/gql about webui query same response type as webui update info (#781)
* Use a new type for the webui about info query

Using the same type for this and the webui update queries/mutations caused apollo to save it as the same data in the cache, overwriting the "about info"

* Use a new type for the webui about check query

To prevent similar issues (cc3bf5f34a8afebadd306d037db1a10088ef9334) with the "update check" and the "update progress" payloads

* Throw update check error when calling it via the query

Otherwise, the error is never raised to the frontend

* Set "ERROR" state in case the update check failed on WebUI update trigger
2023-11-26 17:17:28 -05:00
Mitchell Syer d65ed6ced7 Fix Bundler Script (#780) 2023-11-25 21:42:25 -05:00
schroda db50eb7526 Disable download ahead limit by default (#778)
Currently, it causes the download ahead while reading logic in the WebUI to be enabled by default, which should be disabled by default
2023-11-25 11:53:37 -05:00
schroda d21b2018cb Add mutation to clear the cached images (#775) 2023-11-25 11:53:29 -05:00
schroda 9110c07ed9 Correctly select enum webui flavor via "ui name" (#772)
The selection always returned the default value fallback due to incorrectly using the enums value name instead of the "uiName" of the enum value
2023-11-19 19:22:26 -05:00
schroda 2298e71279 Feature/gql about webui query (#773)
* Rename "about" query to "aboutServer"

* Add "about" query for webUI
2023-11-19 19:22:16 -05:00
schroda 909bd76e08 Cleanup parent folders when deleting downloaded chapters (#776)
Currently, the download folder never gets cleaned up which leads to having a lot of empty folders (sources, mangas folders)
2023-11-19 19:22:06 -05:00
Mitchell Syer 50cd0c4e10 Fix Queries Containing % (#766) 2023-11-07 18:11:38 -05:00
Mitchell Syer 460fc235e3 Add Cache-Control to Extension Icons (#765) 2023-11-07 18:11:30 -05:00
schroda c38a3d9eba Update served webUI after update (#764)
The served file gets cached and thus, it won't reflect the latest version of the file.
This was a problem after the webUI got updated, since now the served "index.html" was outdated and pointed to files that didn't exist anymore.
2023-11-07 18:11:21 -05:00
schroda b303291e94 Always get the latest commit count for jar name (#763)
When adding commits or switching between branches the "shadowJar" gradle task always used the same (outdated) commit count which created jars with confusing names
2023-11-05 21:16:55 -05:00
schroda 7993da038e Fix/initial auto backup never triggered in case server was not running (#762)
* Trigger initial auto backup in case server was not running

In case the server was not started (stopped, system shutdown - not in hibernation) during the scheduled auto backup time, the auto backup never got triggered.

* Update server util preferences
2023-11-05 21:16:48 -05:00
schroda 05bf4f5525 Fix/auto download new chapters initial fetch (#761)
* Fix automatic chapter download for initial chapter list fetch

The initial fetch wasn't correctly detected which caused chapters to get downloaded.
Using index based numbers also caused the first chapter to not get downloaded due to it being omitted in the "subList" call which excludes the "toIndex".

* [Logging] Update logs
2023-11-05 21:16:40 -05:00
Mitchell Syer db36896f92 Fix chapter duplicates if its a different url but same chapter list size (#759) 2023-11-05 10:52:10 -05:00
Mitchell Syer 16dbad8bdf Fix path to Preference file if it contains a invalid path character (#750)
* Fix path to shared preference files if it contains a invalid character

* Lint
2023-11-04 18:10:58 -04:00
schroda 8a4c717d24 Check for all downloaded pages during a chapter download (#752)
In case a chapter is marked as not downloaded, but the download folder exists already, the chapter did not get downloaded again.
 This could cause issues in case the previous download failed or has missing pages.
Instead of only checking if the folder exists, each page should be checked individually

This was previously done and was incorrectly changed with 1c9a139006.
2023-11-04 18:10:06 -04:00
Mitchell Syer 442a290966 Improve Extensions List (#753)
* Use new extension icon path

* Improve Extension list performance
2023-11-04 18:09:55 -04:00
Mitchell Syer 0785f4d0f5 Chapter Fetch Improvements (#754)
* Chapter fetch improvements

* Update previous date uploads

* Lint

* Fix backup inserts

* Remove extra maxSeenUploadDate

* Port downloaded over

* Make sure to set isDownloaded on all inserts
2023-11-04 18:09:40 -04:00
schroda 21e325af9c Correctly handle download of new chapters of not started entries (#755)
The function incorrectly exited early in case no latest read chapter was found.
This rendered disabling the setting "excludeEntryWithUnreadChapters" useless.
2023-11-04 18:09:32 -04:00
schroda 3e9d29ea7f Remove username and password from config log (#756) 2023-11-04 18:09:24 -04:00
schroda 4324373e61 Fix/chapter list fetch updating and inserting chapters into database (#749)
* Keep initial fetch date of existing chapters on a list fetch

The fetch at date should not get updated for existing chapters.
Updating this field makes it impossible to detect which chapters were actually newly fetched.
To get the last fetched timestamp of the chapters, the "chaptersLastFetchedAt" field of the manga should be used

got changed in 6d33d72663

* Get real chapter url safely

In case this causes an exception, it should not cause the whole list fetch to fail

was removed in 6d33d72663
2023-11-01 20:24:34 -04:00
schroda 673053d291 Migrate preferences only if necessary (#748)
Currently, the server tries to migrate the preference on every startup, even if the migration was already done.
This can lead to an unhandled exception, if the write permission to the system preference was revoked.
In case the migration has already happened, these permissions should not be required
2023-11-01 09:19:57 -04:00
schroda 5b3975f886 Only batch update in case list is not empty (#747)
Apparently "BatchUpdateStatement" can't handle an empty data set
2023-11-01 09:19:40 -04:00
schroda 7ed8f43859 Fix/backup import failure not resetting status (#746)
* Reset backup status to idle in case of an exception

* Rename "performRestore" function

* Set backup status to failure on exception

Makes it possible to detect if the restore failed or not after the first status was received

* Set backup status to success on completion

Since the status is not provided over a subscription, but over a query that should be pulled, it is not really easily detectable if a restore finished or not, since both states will be indicated by "idle"

* Correctly wait for first new status when triggering backup import

The status is only "Idle" in case no backup import has ever run.
Once the first backup process finished it is either "Failure" or "Success"

* Rename "ProtoBackupImport::restore" function

* Add id to restore process

Makes it possible to differentiate between backup restore processes.
2023-10-31 21:21:11 -04:00
schroda dcbb1c0dd1 Handle backups with categories having default category name (#745)
We do not allow any category to have the default categories name ("default" case-insensitive).
In case the backup includes a category with this name, it won't be matched to any category in our database and also won't insert a new category.
This will then lead to an exception, causing the backup to fail.
2023-10-30 19:47:33 -04:00
schroda 5d4d417f3e Extract downloaded webUI zip in temp folder for validation (#744)
Could cause issues due to not having permission to create the folder to extract the zip into
2023-10-30 19:47:23 -04:00
schroda 5943c6a2c6 Feature/improve browsing source performance (#743)
* Improve browse source database operations

* Reuse "insertOrGet" for "processEntries"
2023-10-30 19:47:13 -04:00
Alexandre Journet 6d33d72663 #733: Improve perfs on getChapterList with onlineFetch (Less databases calls) (#737)
* improve(#733): less databases calls on getChapterList with onlineFetch

* improve(#733): fixes (delete with ids), tried batch update but not successfull

* improve(#733): fixes (batch update)

* improve(#733): clean imports

* improve(#733): fixes SChapter to ChapterDataClass,

* improve(#733): re-added recognize chap number

---------

Co-authored-by: Alexandre JOURNET <alexandre.journet@axopen.com>
2023-10-30 19:47:03 -04:00
schroda 9d2b098837 Fix/updater update stuck in running status after failure (#731)
* Move running check to update function

* Move updating update status to process function

* Fail all source updates in case of update channel failure

In case the channel failed due to an exception, the update for the source failed completely.
This however was never handled and the pending updates for the source were never set to failed.
Due to this, the global updates running state was always true

* Remove completed update channel from available channels

* Always log specific update job failure
2023-10-30 19:46:54 -04:00
schroda 17bc2d2331 Fetch mangas during the update (#729)
* Optionally fetch mangas during the update

The update only fetched the chapter list of a manga but never the manga itself.
Thus, e.g. unless the manga got online fetched via the ui, it would never get recognized if it is completed or not.
This would e.g. prevent the update setting, to not update completed mangas, from working as intended

* Make settings required
2023-10-30 19:46:43 -04:00
Mitchell Syer 1c192b8db6 Fix Updater (#742) 2023-10-29 12:17:20 -04:00
schroda 76595233fc Prevent mangas from being added to the default category (#741)
Mangas are not supposed to be mapped to the default category in the database.
In case this happens, the category query won't be able to correctly select mangas in the default category
2023-10-29 11:02:31 -04:00
schroda 6531b80998 Delete outdated thumbnails when inserting mangas into database (#739)
In case the database got deleted without deleting cached/downloaded thumbnails, the next time a manga gets inserted, it's possible that a thumbnail was already downloaded for its id.
This then causes mangas to be displayed with incorrect thumbnails due to using the outdated cached/downloaded thumbnails
2023-10-29 11:02:23 -04:00
schroda 05707e29d7 Add missing settings to gql (#738)
* Add missing settings to gql

* Cleanup updating settings
2023-10-29 11:02:13 -04:00
schroda 616ed4637d Handle disabled download ahead limit for new chapters auto download (#734)
In case download ahead is disabled, all new chapters should get downloaded.
Due to incorrectly calculating the index of the first new chapter to download, no new chapter was downloaded at all
2023-10-29 11:02:02 -04:00
schroda 912c340a01 Fix update subscription returning stale data (#727)
In case a manga was already loaded via the data loader, the cached data will get used.
Due to this, the update status did not return the updated manga data, but instead, stale data
2023-10-29 11:01:55 -04:00
Mitchell Syer 583a2f0fad Migrate to XML Settings from Preferences (#722)
* Migrate to XML Settings from Preferences

* Lint
2023-10-29 11:01:46 -04:00
schroda 60015bc041 Return source for preference mutation (#728) 2023-10-26 18:15:29 -04:00
Mitchell Syer 029f445d0a Revert Dex2Jar (#721)
* Revert attempts to fix Dex2Jar

* Revert to v64 of Dex2Jar
2023-10-16 20:13:06 -04:00
schroda 150416b578 Fix/default log level (#719)
* Set default log level to INFO

Default log level was accidentally changed to ERROR for the base logger in 56deea9fb3

* Reduce graphql log level to WARN

Otherwise, thrown exceptions are swallowed by graphql and never logged besides a very brief error in the graphql response
2023-10-16 09:02:56 -04:00
schroda 6684576de1 Fix more missing functions (#718)
2cf9a407e8
2023-10-16 09:02:01 -04:00
Mitchell Syer 289acc9296 Fix Kavita (#716) 2023-10-15 21:58:38 -04:00
Mitchell Syer 2cf9a407e8 Fix MangaDex and Other Sources (#715) 2023-10-15 21:50:30 -04:00
Mitchell Syer 682c364647 Address Build Warnings and Cleanup (#707)
* Address build warnings and cleanup

* Actual name of who defined the protocol

* Remove uneeded detekt supression

* GraphQL Before-After cleanup

* Lint

* Cleanup unused functions

* Fix some discrepancies with the 1.5 source api and fix lang exception
2023-10-15 20:16:30 -04:00
schroda e70730e9a8 Query for mangas in specific categories (#712) 2023-10-15 20:14:59 -04:00
schroda 0ba6c88d69 Fix/graphql mangas query genre based filtering (#713)
* Filter mangas based on each genre of the genre condition

Genres are stored as a comma separated string in the database.
Thus, unless the correct string was passed in the condition, no manga would match the condition.

* Query mangas based on genre filter
2023-10-15 20:14:20 -04:00
schroda c56bdea1e2 Do not log ping messages (#709) 2023-10-15 20:14:10 -04:00
schroda a449a01a24 Fix/web interface manager get latest compatible version (#706)
* Correctly check for none PREVIEW channel latest compatible version

The only working channel was the PREVIEW channel, since any other channel would have fetched the actual version of the preview and used this as the potential latest compatible version.
This was caused due to incorrectly checking if the preview version should be ignored.

* Remove PREVIEW version constant

* Consider versions of different channels

In case the current server version isn't compatible with the latest version of the selected webUI channel, versions of other channel should be considered depending on the selected channel.

E.g. PREVIEW is the latest available version and thus, any version of another channel is also compatible with the PREVIEW channel

* Restrict min compatible version to the bundled version

The oldest compatible version for a server is the bundled version, thus, any version that is older than the bundled one should not be considered compatible
2023-10-07 20:20:09 -04:00
Mitchell Syer 849acfca3d Switch to a new Ktlint Formatter (#705)
* Switch to new Ktlint plugin

* Add ktlintCheck to PR builds

* Run formatter

* Put ktlint version in libs toml

* Fix lint

* Use Zip4Java from libs.toml
2023-10-06 23:38:39 -04:00
schroda 3cd3cb0186 Fix/graphql subscriptions logging (#704)
* Only log operationMessage in case gql logging is enabled

* Always log message type and operation name
2023-10-04 22:02:46 -04:00
Mitchell Syer feead100f2 Update dependencies (#701) 2023-10-04 22:02:28 -04:00
Mitchell Syer a9987e6ab0 Support more image types (#700) 2023-10-04 22:02:20 -04:00
schroda ef0a6f54b8 Feature/auto download ahead (#681)
* Add "download ahead" mutation

Checks if the specified number of unread chapters, that should be downloaded, are available.
In case not enough chapters are downloaded, the number of missing unread chapters will get downloaded

* Optionally pass the latest read chapter id of a manga

In case a chapter will get marked as read, which also triggered the download ahead call, it's possible, that by the time the download ahead logic gets triggered, the chapter hasn't been marked as read yet.
This could then cause this chapter to be included in the chapters to get downloaded.
By providing the chapter id, this chapter will be used as the latest read chapter instead, and thus, not be included inn the chapters to download.
2023-10-04 22:02:10 -04:00
Mitchell Syer c8865ad185 Implement Non-Final 1.5 Extensions API (#699)
* Implement non-final 1.5 extensions API

* Bump lib version max

* Add visibility to preferences

* Add preference visibility
2023-10-04 22:01:45 -04:00
schroda 354968fba7 Update version "name" and "code" when installing external extension (#698)
In case a newer version of the extension is installed  and the extension gets manually downgraded, the version in db is still the one of the newer version.
This will prevent detection of available updates, since it won't get recognized, that an older version is currently installed.
2023-10-02 17:46:46 -04:00
schroda f985ed2131 Order chapters to download by manga and source order (#697)
Chapters were added to the queue by database index order.
In case a chapters of different mangas got added to the queue, downloads got mingled instead of being group inserted per manga.
Also sort manga chapters by source order, to make sure, that, in case chapters of a manga are, for some reason, not in the correct order in the database, they will still get downloaded in the order of the source.
2023-10-02 17:46:38 -04:00
schroda be2628875f Correctly select results using cursors while sorting (#696)
When using cursors for pagination while sorting, the sort order was inverted (desc -> asc, asc -> desc).
However, this was then not considered when selecting results based on the cursor.
For before/after results where always selected via greater/less.
Due to inverting the sort order, this also needs to be inverted depending on the sort order (desc or asc).
2023-10-02 17:46:26 -04:00
Alessandro Schwaiger 9430c8c580 [skip ci] Added new Tachidesk-VaadinUI Client (#695) 2023-10-01 17:16:35 -04:00
Mitchell Syer ea2cf5d4ff Fix File Upload (#694)
* Fix File Upload

* Use operations instead of operation

* Fix tests
2023-09-30 21:55:42 -04:00
He Zhu 3b36974d84 Fixed Bitmap missing method when using Baozi Manhua extensions. (#687) 2023-09-23 16:26:20 -04:00
MangaCrushTeam 41fea1d2a0 remove @Synchronized in CloudflareInterceptor.kt for performance (#688) 2023-09-23 16:26:07 -04:00
schroda d81fafc9f6 Correctly detect initial fetch of chapters (#689)
Since the number of chapters gets converted to be index based, 1 available chapter would result in 0.

Due to this, in case a manga had exactly one chapter before updating the chapters, it was incorrectly detected as the initial fetch and the new chapters did not get automatically downloaded.
2023-09-23 16:25:59 -04:00
schroda 0a73177996 Update graphqlkotlin to v6.5.6 (#685) 2023-09-16 13:07:50 -04:00
schroda c9423ef425 Send every download status change to the subscriber (#684)
Flow::stateIn has "Strong equality-based conflation" (see documentation).
Thus, it omits every value in case it's equal to the previous one.
Since the DownloadManger::getStatus function returns a status with a queue, that contains all current "DownloadChapters" by reference, the equality check was always true.
Thus, progress changes of downloads were never sent to subscribers.
Subscriber were only notified about finished downloads (size of queue changed) or downloader status changes
2023-09-16 13:07:43 -04:00
schroda 7086055ec3 Handle finished downloads that weren't removed from the queue (#683)
In case a download was finished, but the downloader got stopped before it was able to remove the finished download from the queue, the downloader got stuck in an endless loop of starting and pausing downloads.

This was caused by selecting the next chapter to download and then recognizing in "Downloader::step", that there is another chapter to download before the current one in the queue.
However, since this recognized chapter is already downloaded, the downloader selected the next queued chapter again.
It was then stuck in this loop until the finished chapter was manually removed from the queue.
2023-09-16 13:07:35 -04:00
schroda 553b35d218 Feature/improve automatic chapter downloads (#680)
* Rename "newChapters" to "updatedChapterList"

* Do not auto download new chapters of entries with unread chapters

Makes it possible to prevent unnecessary chapter downloads in case the entry hasn't yet been caught up

* Optionally limit auto new chapter downloads

* Prevent downloading new chapters for mangas not in the library
2023-09-16 13:07:24 -04:00
schroda c910026308 Do not reset already loaded config when updating config file (#679)
In case the user config file has to be updated, the file needs to get reset.
While doing the reset, the already loaded internal state of the config got also reset, but was never updated again.
Due to this, the internal state of the config was the default config reference until the next server startup

Regression introduced with a31446557d.
2023-09-05 20:57:59 -04:00
schroda 35be9f14e4 Return correct latest compatible webUI version (#677)
The function always returned the PREVIEW version as the latest compatible version.
This was caused by incorrectly selecting the version from the json object, which resulted in the version to be wrapped in '"'.
2023-09-03 18:11:06 -04:00
schroda abcbec9c2a Fix/downloader not creating folder or cbz file (#676)
* Create manga download dir in case it's missing for cbz downloads

The directory, in which the cbz file should have been saved in, was never created.

* Correctly copy chapter download to final download location

"renameTo" does not include the content of a directory.
Thus, it just created an empty chapter folder int the final download directory
2023-09-03 18:10:54 -04:00
schroda ff6f5d7e89 Add more fields to the manga graphql type (#675)
These are information that are necessary for nearly all manga requests.
They could be selected via the categories mutation, but this only works for a single manga.
It is not possible to select this information for lists of mangas without having to request all chapters for every manga in the list.
2023-09-03 18:10:49 -04:00
schroda 56deea9fb3 Feature/graphql logging (#674)
* Set graphql logs to error level

Set log level for loggers with names
 - ExecutionStrategy (spams logs with "... completing field ...")
 - notprivacysafe (logs every received request up to 4 times (received, parse, validate, execute))

* Extract logic to get logger for name into function

* Add function to set log level for a logger

* Add settings to enable graphql debug logging
2023-09-03 18:10:43 -04:00
schroda 1c9a139006 Always return "ArchiveProvider" in case "downloadAsCbz" is enabled (#671)
* Move chapter download logic to base class

* Do not reuse "FolderProvider" in "ArchiveProviders" download function

Due to reusing the "FolderProvider" to download a chapter as a cbz file, a normal chapter download folder was created.
In case the download was aborted before the cbz file got created and the folder deleted, the next time the chapter got downloaded, the wrong "FileProvider" was selected, causing the chapter not to get downloaded as a cbz file.
2023-08-28 19:25:50 -04:00
Mitchell Syer 4d89c324b9 Fix Oracle JRE Extension Install (#670)
* Minor cleanup

* Fix Oracle JRE Write issue
2023-08-27 22:39:05 -04:00
schroda a76ce03911 Throw error instead of returning null (#666)
In case e.g. no manga exists for the passed id, the query returned null.
This makes it harder to have a "streamlined" error handling in the client, since these types of queries need a special handling.
2023-08-27 22:38:52 -04:00
schroda 9ee3f46ff0 Feature/graphql chapter pages mutation handle downloaded chapters (#665)
* Update chapter page refresh logic with logic from "ChapterMutation"

* Rename function to "getChapterDownloadReadyByIndex"

* Update "ChapterForDownload" to work with only "chapterId" being passed

* Return database chapter page list in case chapter is downloaded

In case the chapter is downloaded, fetching the chapter pages info should not be needed.
It should also currently break reading downloaded chapters while being offline, since the page request will always fail, since there is no internet connection
2023-08-27 22:38:33 -04:00
schroda 3343007cf8 Add mutation to install external extension (#667) 2023-08-26 22:19:51 -04:00
schroda c42d314b76 Move source download dirs to new download subfolder (#660)
Should have been added with f2dd67d87f
2023-08-20 14:32:53 -04:00
Mitchell Syer 8db6c2153e Fix some settings not being applied properly (#661)
* Fix some settings not being applied properly

* Update ProtoBackupExport.kt

* Update Updater.kt

* Revert "Update ProtoBackupExport.kt"

This reverts commit 41deaee2449ff28bb4ba4eb90959b68d4e82764d.

* Revert "Update Updater.kt"

This reverts commit 2678792cf6a354de8de4a19b2b74fbad72c1379a.
2023-08-20 14:32:25 -04:00
schroda 5baf54335b Feature/updater provide more info about update (#657)
* Provide last global update timestamp

* Provide skipped mangas in update status

* Extract update status logic into function

* Rename update "statusMap" to "mangaStatusMap"

* Provide info about categories in update status
2023-08-15 17:51:21 -04:00
schroda d9019b8f46 Correctly emit changed values (#656)
"SharedFlow::emit" blocked the flow due not being called in a new coroutine.
2023-08-12 14:06:38 -04:00
schroda a31446557d Feature/graphql server settings (#629)
* Add "uiName" to WebUI enum

* Add "Custom" WebUI to enum

* Rename "WebUI" enum to "WebUIFlavor"

* Add "WebUIInterface" enum

* Add query for server settings

* Add mutation for server settings

* Add mutation to reset the server settings

* Only update the config in case the value changed

In case the value of the config is already the same as the new value of the state flow, it is not necessary to update the config file
2023-08-12 12:03:25 -04:00
schroda 321fbe22dd Feature/listen to server config value changes (#617)
* Make server config value changes subscribable

* Make server config value changes subscribable - Update usage

* Add util functions to listen to server config value changes

* Listen to server config value changes - Auto backups

* Listen to server config value changes - Auto global update

* Listen to server config value changes - WebUI auto updates

* Listen to server config value changes - Javalin update ip and port

* Listen to server config value changes - Update socks proxy

* Listen to server config value changes - Update debug log level

* Listen to server config value changes - Update system tray icon

* Update config values one at a time

In case settings are changed in quick succession it's possible that each setting update reverts the change of the previous changed setting because the internal config hasn't been updated yet.

E.g.
1. settingA changed
2. settingB changed
3. settingA updates config file
4. settingB updates config file (internal config hasn't been updated yet with change from settingA)
5. settingA updates internal config (settingA updated)
6. settingB updates internal config (settingB updated, settingA outdated)

now settingA is unchanged because settingB reverted its change while updating the config with its new value

* Always add log interceptor to OkHttpClient

In case debug logs are disabled then the KotlinLogging log level will be set to level > debug and thus, these logs won't get logged

* Rename "maxParallelUpdateRequests" to "maxSourcesInParallel"

* Use server setting "maxSourcesInParallel" for downloads

* Listen to server config value changes - downloads

* Always use latest server settings - Browser

* Always use latest server settings - folders

* [Test] Fix type error
2023-08-12 11:47:41 -04:00
schroda 01ab912bd9 Remove unnecessary "downloadNewChapters" call in "fetchChapters" mutation (#652)
Gets already called by "Chapter::fetchChapterList", thus, this is unnecessary.
Additionally, "chapters.toList()" and "chapters.map()" have to be called in a transaction block, which they are not, and thus, cause an unhandled exception, breaking the mutation
2023-08-12 11:15:06 -04:00
schroda 557bad60bc Prevent last page read to be greater than max page count (#655)
There were cases where the last page read was greater than the max page count of a chapter.
This is not possible and is just invalid data, that is saved in the database, possible leading to other errors down the line.

This could happen in case the chapter was loaded at some point with e.g. 18 pages and after some time got fetched again from the source, now with fewer pages than before e.g. 15.
If the chapters last page was already read by that time, the last read page would have been 18, while the chapter now has only 15 pages.
2023-08-12 11:14:58 -04:00
schroda f2dd67d87f Feature/decouple thumbnail downloads and cache (#581)
* Rename "DownloadedFilesProvider" to "ChaptersFilesProvider"

* Move files into sub packages

* Further abstract "DownloadedFilesProvider"

* Rename "getCachedImageResponse" to "getImageResponse"

* Extract getting cached image response into new function

* Decouple thumbnail cache and download

* Download and delete permanent thumbnails

When adding/removing manga from/to the library make sure the permanent thumbnail files will get handled properly

* Move thumbnail cache to actual temp folder

* Rename "mangaDownloadsRoot" to "downloadRoot"

* Move manga downloads into "mangas" subfolder

* Clear downloaded thumbnail
2023-08-12 11:14:43 -04:00
Mitchell Syer b8b92c8d69 Suspend setupBundledWebUI() (#650) 2023-08-09 20:52:53 -04:00
schroda 74ff112e7a Feature/graphql web UI (#649)
* Add "server" to "checkForUpdate" logic names

* Use "webUIRoot" as default path for "getLocalVersion"

* Use local version as default version for "isUpdateAvailable"

* Return the version with the webUI update check

* Update WebinterfaceManager to be async

* Add query, mutation and subscription for webUI update

* Catch error and return default error value for missing local WebUI version
2023-08-09 20:46:48 -04:00
schroda 684bb1875c Fix/webinterfacemanager update to bundled webui (#648)
* Catch error when updating to bundled webUI

In case the bundled webUI is missing, the webUI setup threw an error and made the server startup fail.
Since a local webUI exists the error should be ignored, since it's only a try to update to a newer webUI version.

* Extract logic to setup bundled webUI version

* Update to bundled webUI try to download missing bundled webUI
2023-08-09 20:46:33 -04:00
schroda f6fec2424c Fix/extracting assets from apks (#644)
* Get rid of multiple static "assets/" usage

* Correctly add new zip entry

The name of the entry has to be a "/" separated path, otherwise, the files can't be found.
2023-08-06 23:21:31 -04:00
schroda 2889029b70 Fix/downloader manager persisting queue (#639)
* Extract reorder logic into function

* Save download queue everytime a download was finished

The download queue was never saved after a download was finished.
This caused finished download to be restored on a server start, which caused unnecessary "downloads" which most of the time would just finish immediately since the pages were still in the cache

* Wait for download queue save process to be finished

Since multiple downloaders could be finished at the same time, the download queue should be saved synchronously

* Remove unnecessary download queue save trigger

This gets called everytime a downloader finished downloading all chapters of its source.
Since the queue is now saved everytime a download is finished, this is trigger is not needed anymore
2023-08-06 23:21:21 -04:00
Mitchell Syer b56b4fa813 Update Local Source to latest Tachiyomi (#637)
* Update Local Source to latest Tachiyomi

* More formatting

* Enable zip64
2023-08-06 23:20:55 -04:00
schroda 00bc055d69 Fix/load extension log load failure (#641)
* Log extension load failure

In case the extension couldn't be loaded the error was never logged, making it impossible to analyse what was going on

* Log exception in "GetCatalogueSource:: getCatalogueSourceOrNull"

In case "GetCatalogueSource::getCatalogueSource" threw an error, this was never logged here
2023-08-05 20:10:12 -04:00
schroda 6fd291c7e3 Fetch downloaded chapters page again in case the stored file can't be retrieved (#640)
In case the file could not be retrieved, the page retrieve just failed and wasn't triggered again.
In case of the downloader, the chapter download just kept failing 3 times and was aborted
2023-08-05 20:10:02 -04:00
schroda dbdb787076 Restore download queue async (#638)
The download queue was blocking the main thread, thus, slowing down the startup.
In case the stored queue was huge, this could take multiple seconds
2023-08-05 20:09:38 -04:00
Mitchell Syer fc788a718d Add 128 px icon (#636) 2023-08-05 20:09:27 -04:00
Mitchell Syer e093fe6a06 Add CookieManager implementation (#635)
* Add CookieManager implementation

* Remove Syncronized

* Rename CookieStore
2023-08-05 20:09:12 -04:00
Mitchell Syer cdce368042 Fix Graphql-WS errors and Improve Downloader Subscription (#634)
* Fix errors in graphql-ws

* Send download messages more often
2023-08-04 22:48:41 -04:00
Mitchell Syer 689847d864 Update dependencies (#611) 2023-08-04 22:48:24 -04:00
Mitchell Syer 3675580d87 Add Subscriptions to GraphiQL and Update (#631)
* Add subscription url

* Update Graphiql and dependencies
2023-08-03 22:28:37 -04:00
Mitchell Syer 92f494d0fe Implement Graphql-WS Subscriptions (#630)
* Implement graphql-ws subscriptions

* Fix subscription payload issue

* Close session directly

* Improve id handling
2023-08-03 22:28:28 -04:00
Mitchell Syer 06d7a6d892 Info Queries (#627) 2023-08-03 18:09:11 -04:00
Mitchell Syer e2754200af Use Tachidesk-Launcher (#618)
* Use the Launcher

* Test launcher

* a

* Revert "a"

This reverts commit eb8667e4397c20dae7a7dfdf26058f5aff76fff8.

* Move launcher

* Test launcher 2

* Update dex2jar

* Fixes

* Use regular java with deb install

* Improve linux installs

* Revert "Test launcher 2"

This reverts commit 265825808fd82616223e4a919718ea87a7eeff43.

* Revert "Test launcher"

This reverts commit 7ff83c7ab954bcab6c0b039701041b639a489382.
2023-08-03 18:09:04 -04:00
Mitchell Syer cdb083ff48 Downloader Queries and Mutations (#610)
* Add downloader GraphQL endpoints

* Fix names

* DeleteDownloadedChapter(s)

* DequeueChapterDownload(s)
2023-08-03 18:08:47 -04:00
Mitchell Syer c3fb08d634 Library Update Queries and Mutations (#609)
* Add library update GraphQL endpoints

* No need for data classes

* UpdateLibraryManga
2023-08-03 18:08:35 -04:00
schroda 78a167aacf Fix/webui setup failure in case bundled webui is missing (#625)
* Rename functions

* Require version to be passed to "downloadVersion"

Makes it possible to download different versions than the latest compatible one with retry functionality

* Fallback to downloading bundled webUI in case it's missing

In case no download was possible and the fallback to the bundled version also failed due to it not existing, try to download the version of the bundled version as a last resort.

* Handle exception of "getLatestCompatibleVersion"

* Move validation of download to actual download function

* Extract retry logic into function

* Retry every fetch up to 3 times

* Log full exception and change log level
2023-07-30 10:29:40 -04:00
schroda 5a913fdfbb Make path to local source changeable (#626) 2023-07-30 10:29:09 -04:00
schroda f0a190e8d2 Update to bundled webUI version if necessary (#619)
In case on the startup no webUI update was available but the bundled version of the server is newer than the current used version, then the bundled version should be used.

This could be the case in case a new server version was installed and no compatible webUI version is available
2023-07-29 20:26:04 -04:00
schroda a2715fb851 Feature/webui update download failure do not immediately fallback to bundled version (#620)
* Return actual version for "PREVIEW" in "getLatestCompatibleVersion"

In case "PREVIEW" is the latest available version, the function should immediately fetch the actual webUI version that is currently the latest released version.

Thus, the function always returns a valid version and the preview version has not to be considered anymore at other places in the code

* Ignore download failure in case local webUI version is valid

In case the download failed e.g. due to internet connection issues, the server should only fall back to another version in case the local version is invalid or missing
2023-07-29 20:25:47 -04:00
Mitchell Syer 47e5b03f45 Fix some manga filters (#624) 2023-07-29 20:25:27 -04:00
schroda 251141a5c3 Fix/downloader (#622)
* Change log level of download error

* Change type of sourceId in Downloader

Unclear why it was converted to Long since it just got converted back to String anyway when it was used in the Downloader

* Only stop downloads from source of the Downloader

The downloader just changed the state of all downloads, ignoring if they are from the source the Downloader is for or not

* Remove unnecessary DownloadManager::start calls

In case chapters were added to the queue the DownloadManager will start itself

* Extract download filtering into property

* Improve Downloader logging

* Notify clients only in case Downloader was started

In case nothing was done there is nothing to notify about

* Do not start Downloaders for failed downloads

In case there were failed chapter downloads in the queue the DownloadManager still created a Downloader and started it.
This Downloader would than immediately call "onComplete", since there is no available download, which then would refresh the Downloaders again which created an infinite loop until the failed download got removed from the queue

* Retry download in case it failed it gets re-added to the queue

In case a failed downloaded that was still in the queue was tried to get added to the queue again, nothing happened.
Instead of doing nothing, the download should get retried.
Thus, it also provides the logic to easily retry a failed download by just "adding" the chapter to the queue again.
Currently, to retry a failed download, the download has to be removed from the queue and then get re-added.

* Rename function "unqueue" to "dequeue"

* Move "dequeue" function

* Extract dequeue logic into function

* Improve DownloadManager logging

* Override "toString" of DownloadChapter
2023-07-29 20:25:12 -04:00
schroda 6ac8f4c45d Use mathematical modulo implementation for calculations (#616)
See documentation (%/rem, mod) for differences.

Example for "issue" that occurred:
mathematical: -4 % 6 = 2 (expected)
kotlin: -4 % 6 = -4 (unexpected)
2023-07-26 19:28:13 -04:00
schroda 7ebefa7c42 Fix/updater scheduling auto updates (#615)
* Trigger missed auto global update immediately on server start

In case the last execution was missed, it was never immediately scheduled.
Thus, it had to be waited for the next scheduled execution to be executed.

* Schedule auto global updates at a later point during the startup

In case a global update was triggered immediately, the server setup wasn't far enough causing an error due to trying to use things (e.g. database) that weren't initialized yet
2023-07-25 20:33:57 -04:00
schroda 9e4c90f220 Always update the last webUI update check timestamp (#614) 2023-07-25 20:33:46 -04:00
schroda 50f988641b Fix/ha scheduler rescheduling ha tasks (#613)
* Correctly set the "firstExecutionTime" of a "HATask"

In case an initial delay is used for "Timer::scheduleAtFixedRate" (e.g. when rescheduling) then the "firstExecutionTime" of the "HATask" was incorrect, since it considered the first execution to be based on the actual interval.
This caused
 - calculations for execution times (e.g. "timeToNextExecution", "nextExecutionTime") to be incorrect
 - the ordering of the "scheduledTasks" queue to be incorrect

* Add logging

* Do not modify queue during forEach loop

Caused a "ConcurrentModificationException" and broke the system suspension detection due to the unhandled exception canceling the task

* Log all uncaught exceptions

In case an exception is uncaught/unhandled, it only gets logged in the console, but is not included in the log file.

E.g. the "HAScheduler::scheduleHibernateCheckerTask" task caused an unhandled "ConcurrentModificationException" which caused the task to get dropped.
In the log files this error could not be seen and thus, analysing the issue of the suspension detection to stop working was not possible via the logs

* Schedule "HATask" immediately when its last execution was missed

The missed execution was never triggered

* Calculate the "HATask" "last execution time" correctly

When scheduling a task for the first time, the "first execution time" is in the future.
This time is used for by all functions calculating times for this task (e.g. next/last execution time).

In case the first execution didn't happen yet and the current time, would have been an "execution time" based on the interval, the "hibernation detection" would trigger for this task, since it would think that the last execution was missed, due to the "last execution" being in the future.
To prevent this, it has to be made sure, that the "last execution time" is in the past.
2023-07-25 20:33:36 -04:00
schroda e53b9d4790 Fix/ha scheduler not triggering missed executions due to not meeting the threshold (#612)
* Check correctly if task threshold was met

It was incorrectly considered to be met in case the remaining time till the next execution was less than the threshold.
Instead, it has to be greater, since that would mean, that the next execution is taking long enough to not be triggering a double execution

Thus, the current logic is not, as intended, preventing possible double executions and instead is making sure to only execute missed tasks in case it will lead to double executions...

* Always trigger missed executions

The idea to have a threshold to prevent double executions in case the next scheduled execution isn't too far in the future doesn't really work with big intervals (e.g. in the days range).
For such cases, multiple days left for the next executions could be considered to cause double executions.

Decreasing the threshold doesn't really work since then it wouldn't really work for low intervals.
Instead, it makes more sense to just allow possible double executions and to just live with it.
In case it would be a problem for a specific task, the task should handle this issue itself.
2023-07-23 12:40:22 -04:00
schroda 027805c4d5 Preserve download queue through server restarts (#599) 2023-07-22 11:42:48 -04:00
schroda c02496c4f0 Fix/updater automated update max interval of 23 hours (#606)
* Rename schedule functions

* Introduce Base task for "HATask"

* Support kotlin Timer repeated interval in HAScheduler

It's not possible to schedule a task via cron expression to run every x hours in case the set hours are greater than 23.
To be able to do this and still keep the functionality provided by the "HAScheduler" it has to also support repeated tasks scheduled via the default Timer

* Support global update interval greater 23 hours

* Use "globalUpdateInterval" to disable auto updates

Gets rid of an unnecessary setting
2023-07-22 11:41:52 -04:00
schroda 2a83f290a5 Use "backupInterval" to disable auto backups (#608)
Gets rid of unnecessary setting
2023-07-22 11:41:21 -04:00
schroda d4f9b0b1bc Feature/log to file (#607)
* Setup "logback" to write to file

To be able to dynamically set the log file save location, logback has to be setup via code instead of a config file

* Log OkHttp via logback

Otherwise, the logs would only get written to the console and thus, not be included in the log file

* Init logback

Has to be done after the config was loaded, otherwise, the root directory would be unknown.
Moved the log of the loaded config to the "applicationSetup" since otherwise, the log would not be included in the log file
2023-07-21 19:53:41 -04:00
schroda 2452b03a49 Schedule automated update only once per hour (#605)
The update was scheduled to run every minute of the set hour.
But it should only run once in the set hour.
2023-07-21 19:52:12 -04:00
schroda 2ce423b6cb Correctly check if a new version is available for the preview channel (#604)
The actual version of the preview was never loaded and compared to the local version.
Instead, for the preview channel it was incorrectly decided that a new version is available on every update check
2023-07-21 19:51:51 -04:00
schroda e9206158b8 Feature/move server frontend mapping to the frontend (#591)
* Convert "WebInterfaceManager" to singleton

* Move server webUI mapping to the webUI

* Extract logic into functions

* Retry failed download

* Validate downloaded webUI

* Automatically check for webUI updates

* Add logic to support different webUIs

* Update logs

* Close ZipFile after extracting it
2023-07-20 20:48:27 -04:00
schroda 8690e918dd Feature/automatically download new chapters (#596)
* Automatically download new chapters

* Log queued downloads

* Add function to get number of manga chapters
2023-07-20 17:47:46 -04:00
schroda c1d702a51c Feature/improve automated backup (#597)
* Add option to disable cleanup of backups

* Ensure the minimum TTL of backups to 1 day

* Schedule the automated backup on a specific time of the day

* Introduce scheduler that takes system hibernation time into account

In case the system was hibernating/suspended scheduled task that should have been executed during that time would not get triggered and thus, miss an execution.

To prevent this, this new scheduler periodically checks if the system was suspended and in case it was, triggers any task that missed its last execution

* Use new scheduler
2023-07-20 17:47:30 -04:00
schroda 0338ac3810 Extract assets from apk file (#602)
Some extension require some assets to work properly.
Currently, the extracted jar file does not contain these assets, thus, these extensions wouldn't work
2023-07-20 17:47:08 -04:00
schroda 526fef85e4 Feature/global update trigger automatically (#593)
* Move "addCategoriesToUpdateQueue" to "Updater"

* Automatically trigger the global update
2023-07-10 13:14:14 +03:30
schroda 49f2d8588a Feature/automated backups (#595)
* Automatically create backups

* Cleanup automated backups

* Extract backup filename creation into function
2023-07-10 13:13:53 +03:30
schroda 9a80992aec Correctly read resource in build jar and dev mode (#594)
The server reference config file was only able to be read while in dev mode.
Using the build jar, the content of the file was empty, since in the build jar resources aren't actual files anymore, instead they are streams.
This caused the user config content to be replaced with an empty string.
2023-07-04 04:07:49 +03:30
Mitchell Syer 32d0890dba Proxy thumbnail urls (#589) 2023-07-02 19:18:44 +03:30
schroda b4d37f9ba2 Make sure "UserConfig" is up-to-date (#590)
Currently, the "UserConfig" was created in case it was missing.
But in case settings changed (added/removed), an already existing "UserConfig" never reflected these changes and thus, was out of date
2023-07-02 19:18:08 +03:30
Mitchell Syer 5372ef8f0c Manga for Source data loader (#588) 2023-07-02 19:16:04 +03:30
Mitchell Syer a11b654c3d Backup creation and restore gql endpoints (#587) 2023-07-02 19:15:44 +03:30
schroda 1a9a0b3394 Exclude "default" category from reordering (#586)
* Exclude "default" category from reordering

Due to the "default" category having been added to the database, the index based approach to reorder the categories didn't work anymore.
In case one tried to move a category to or from pos 1, the default category was selected due to being at index 0

* Normalize categories after reordering

Makes sure that the ordering is correct.
E.g. "default" category should always be at position 0
2023-07-01 21:23:10 +03:30
schroda 890920a57b Freeze graphql playground scripts to working versions (#585)
The latest versions might include breaking changes
2023-07-01 21:17:41 +03:30
Mitchell Syer 7fe7de5fdf Fix fetchSourceManga filtering 2023-07-01 13:28:15 -04:00
Mitchell Syer b9b115d0ea Rewrite filter and preference mutations (#577) 2023-06-24 19:58:11 +03:30
schroda 08af195f11 Fix graphql/plugin-explorer urls (#584) 2023-06-24 19:56:29 +03:30
schroda 71cde729fc Delete tmp files on request failure (#582)
There is a possibility that a partially downloaded file remains in case of an error.
In that case, the next time the image gets requested the existing file would be handled as a successfully cached image.
2023-06-21 17:32:58 +03:30
schroda 077f0a03f6 Update "dex2jar" to v61 (#583) 2023-06-21 17:21:16 +03:30
Mitchell Syer 812eb8001b Add fetch chapter pages (#576) 2023-06-10 21:42:42 +03:30
schroda b59af683ac Do not count mangas as part of categories that aren't in the library (#574)
Otherwise, the returned "size" of a property doesn't match the actual manga list, since that list only includes mangas from the library.
2023-06-09 18:27:16 +03:30
schroda 561d680e78 Exclude mangas with specific state from global update (#537) 2023-06-09 16:03:10 +03:30
Mitchell Syer 7c3eff2ba7 Complete source mutations (#567) 2023-06-05 16:49:03 +03:30
Mitchell Syer 300c0a8f35 Category Mutations (#566)
* Complete Category mutations

* Remove TODO
2023-06-05 16:48:57 +03:30
schroda 51bfdc0947 Feature/make config settings changeable during runtime (#545)
* Add logic to update config during runtime

* Update ConfigModule to always use the latest config

* Make ServerConfig settings re-assignable
2023-06-05 16:48:18 +03:30
Aria Moradi a64566c0f3 fill in the cover according to spec (#571) 2023-06-05 16:18:03 +03:30
Aria Moradi dbb9a80ea6 use commons-compress everywhere (#570) 2023-06-05 16:16:27 +03:30
Aria Moradi e930c54246 improve zip parsing (#569) 2023-06-05 14:31:14 +03:30
Mitchell Syer dfff047cbf Fix cascade migration (#565) 2023-05-29 17:29:54 -04:00
Mitchell Syer 44fb2b02bc Fix global meta delete (#564) 2023-05-29 23:41:39 +03:30
Mitchell Syer 6a7efafd9f Improve database column references and default category handling (#563)
* Improve default category handling and add cascade to references where possible

* Minor fix for default category

* Make the default category always first in the normalization
2023-05-28 04:11:27 +03:30
Mitchell Syer 241abc3956 Add items that are related to the deleted meta (#562) 2023-05-27 21:39:47 +03:30
Mitchell Syer 1e82c879bf Add default category to the database (#561)
* Add default category to the database

* Fix getCategorySize
2023-05-27 03:20:21 +03:30
Mitchell Syer a81d01d2e3 Don't use data fetchers in mutations (#559) 2023-05-27 03:09:31 +03:30
Mitchell Syer 2230796504 Extension mutations (#560) 2023-05-27 03:09:17 +03:30
Mitchell Syer 458ca7c7cf Fix update chapters (#557) 2023-05-26 13:45:25 +03:30
Mitchell Syer 3f91663ecf Rewrite meta and add meta mutations (#556) 2023-05-26 13:45:16 +03:30
Mitchell Syer 04a671382a Improve GQL Playground (#558) 2023-05-26 13:44:18 +03:30
Mitchell Syer 945ec818e5 Remove category filter (#551) 2023-05-24 14:01:21 +03:30
Mitchell Syer ff7ac8a785 Fetch Manga and Chapters in GQL (#555) 2023-05-24 14:01:07 +03:30
Mitchell Syer 603105e2ea Fix StringFilter (#554) 2023-05-24 14:00:54 +03:30
Mitchell Syer 5475567b48 Cleanup download type (#553) 2023-05-24 14:00:29 +03:30
Mitchell Syer 2aec0adb08 Category mangas (#552) 2023-05-24 14:00:06 +03:30
Mitchell Syer 54fc3761bf Put graphql under api (#549) 2023-05-17 03:58:00 +03:30
Mitchell Syer 99e1912bfe Fix manga/source and manga/chapters for graphql (#548) 2023-05-17 03:57:20 +03:30
Aria Moradi ecc1cabafd Merge pull request #547 from Suwayomi/graphql
add graphql
2023-05-13 23:00:13 +03:30
Aria Moradi 1a5b847b23 Update README.md 2023-05-01 19:16:32 +03:30
Aria Moradi d3409e7133 Update README.md 2023-05-01 19:09:59 +03:30
Aria Moradi 4e553e3eb3 better description about the Tachiyomi extension 2023-05-01 18:44:28 +03:30
Syer10 4577bbc572 More mutations 2023-04-28 21:56:25 -04:00
Syer10 da8ca23496 Start working on mutations 2023-04-28 21:29:06 -04:00
Syer10 988853be63 Seems like this should return null if it errors 2023-04-28 21:28:18 -04:00
schroda cde5dc5bfa Update "dex2jar" to v60 (#538) 2023-04-10 11:44:22 +03:30
Syer10 b617250eff Delete updates query since the chapters query can now mimic it 2023-04-08 22:55:56 -04:00
Syer10 313da99536 Add in library filter for chapters 2023-04-08 22:54:56 -04:00
Syer10 442e245216 Update TODO 2023-04-08 22:37:51 -04:00
Syer10 050ab17019 Complete SourceQuery 2023-04-08 21:05:05 -04:00
Syer10 c80f488a13 Complete ChapterQuery 2023-04-08 20:40:18 -04:00
Syer10 cf73804c71 Complete ExtensionQuery 2023-04-08 20:39:38 -04:00
Syer10 a90e5d13ea Complete MetaQuery 2023-04-08 15:47:10 -04:00
Syer10 891fb0b479 Simplify keyset pagination 2023-04-08 15:27:18 -04:00
Syer10 58a623d44d Fix keyset pagination for non-unique order by modes 2023-04-08 13:37:09 -04:00
Syer10 0e84b8a154 Lint 2023-04-08 13:36:28 -04:00
Syer10 a4dfcf80e4 Implement manga status filter 2023-04-07 21:30:20 -04:00
Syer10 d8567eadb2 Simplify queries 2023-04-07 21:10:38 -04:00
Syer10 0b88207ad5 Fix empty results errors 2023-04-07 00:02:00 -04:00
Syer10 671466a737 Complete CategoryQuery 2023-04-06 21:53:30 -04:00
Syer10 84881a0d52 Complete MangaQuery 2023-04-04 21:02:29 -04:00
Syer10 a589049cc7 Move things around and introduce Cursor type 2023-04-04 21:02:07 -04:00
Syer10 17877e0f17 Fix case insensitive 2023-04-03 22:12:38 -04:00
Syer10 1ed9bef2a1 Fix the playground explorer and add a updated default query 2023-04-03 22:07:10 -04:00
Syer10 a6dddf311c Basically finish MangaQuery, only paging left 2023-04-03 22:04:46 -04:00
Syer10 e8c2bad187 Handle missing objects in graphql 2023-04-02 20:39:56 -04:00
Syer10 52bda2c080 Start working on graphql paging 2023-04-02 20:15:09 -04:00
Syer10 607919f40f Implement more query parameters 2023-04-02 17:33:19 -04:00
Syer10 d830638ee6 Use actual MangaStatus enum 2023-04-02 16:47:00 -04:00
Syer10 106bda2097 Proper conversion Scalar for Long to String and back 2023-04-02 16:30:03 -04:00
Syer10 7debb27374 Might not need a updates query 2023-03-31 23:11:18 -04:00
Syer10 05b5a7f598 Add updates 2023-03-31 23:03:26 -04:00
Syer10 3bbda7ba54 More todos 2023-03-31 22:36:01 -04:00
Syer10 9312f5fd14 Add global meta 2023-03-31 22:30:14 -04:00
Syer10 399eb07e35 Fix imports 2023-03-31 22:21:09 -04:00
Syer10 eb197ebcee Switch database logger to SLF4J 2023-03-31 22:19:13 -04:00
Syer10 4c30d8ab05 Some TODOs with ideas 2023-03-31 22:00:01 -04:00
Syer10 3a67ddf0f6 Add Extensions to Graphql 2023-03-31 20:58:18 -04:00
Syer10 6541c7b5b7 Serialize Long as String in graphql 2023-03-31 20:30:24 -04:00
Syer10 37f41ade43 Directly use the database for sources in graphql 2023-03-31 20:29:55 -04:00
Syer10 007d20d417 Add Sources to Graphql 2023-03-31 19:44:21 -04:00
Syer10 00370a81fa Minor cleanup 2023-03-31 19:10:04 -04:00
Syer10 d4599c3331 Use Graphiql with the Explorer plugin for the query builder 2023-03-31 19:09:32 -04:00
Syer10 bce76bbcf3 Use Kotlin Coroutines Flow instead of Project reactor 2023-03-30 18:28:56 -04:00
Valter Martinek 847a5fe71b Subscriptions! 2023-03-30 17:05:41 -04:00
Valter Martinek e2fa003239 Rewrite graphql controller execute as function without docs 2023-03-30 17:04:01 -04:00
Valter Martinek 0c555e88d3 Update graphql-playground endpoint 2023-03-30 17:04:01 -04:00
Valter Martinek bf7f1a04b3 Add categories to graphql 2023-03-30 17:04:01 -04:00
Valter Martinek 623172af6d Add mutation for updating chapters 2023-03-30 17:04:01 -04:00
Valter Martinek 4fb689d9e4 Add chapter and manga meta field 2023-03-30 17:04:01 -04:00
Valter Martinek 6054c489c6 Add graphql playground 2023-03-30 17:04:01 -04:00
Valter Martinek 21719f4408 Add basic graphql implementation with manga and chapters loading with data loaders 2023-03-30 17:03:56 -04:00
Aria Moradi f2a650ba02 fix typo 2023-03-27 21:27:10 +03:30
Aria Moradi 871c28b1ea cleanup notes 2023-03-27 21:26:37 +03:30
schroda d3aa32147a Add logic to only update specific categories (#520)
Makes it possible to only update specific categories.

In case a manga is in an excluded category it will be excluded even if it is also in an included category.
2023-03-27 20:15:46 +03:30
schroda 9a50f2e408 Notify clients even if no manga gets updated (#531)
In case no manga gets updated and no update job was running before, the client would never receive an info about its update request
2023-03-27 17:11:19 +03:30
schroda dcde4947e8 Emit update to clients after adding all mangas to the queue (#521)
Emitting updates before all the mangas were added to the queue could lead to e.g. wrong progress calculation.
2023-03-25 22:08:42 +03:30
schroda 5b61bdc3a8 add size field to Category data class (#519)
Makes it possible to display the size of a category to the user
2023-03-25 22:07:50 +03:30
schroda ec1d65f4c3 update library grouped by source (#511)
* Update mangas grouped by source

* Limit parallel update requests
2023-03-10 11:03:09 +03:30
akabhirav a0081dec07 fix manga unread and download count (#509) 2023-02-23 22:04:03 +03:30
akabhirav 783787e514 Send last read chapter in Mangas in Category API (#507)
* Send last read chapter with manga

* optimize query

* introduce new field for better performance
2023-02-21 02:47:45 +03:30
akabhirav ac99dd55a2 Fix random page sent when manga is downloaded (#508) 2023-02-21 02:40:38 +03:30
Mitchell Syer c56f984952 Fix SharedPreferences.Editor.clear and SharedPreferences.Editor.remove (#505)
* Fix SharedPreferences.Editor.clear and SharedPreferences.Editor.remove

* UNCHECKED_CAST

* Support removing Set<String>

* Typo

* Remove unneeded OptIn
2023-02-19 07:54:24 +03:30
Aria Moradi 9269ca726e It's not us, I swear ;;; 2023-02-16 10:57:26 +03:30
DattatreyaReddy Panta eca3205dcf Update winget.yml (#500) 2023-02-14 15:02:41 +03:30
akabhirav 13f5486d0b Fix CBZ download bug for newly added mangas in Library (#499) 2023-02-13 19:17:14 +03:30
Aria Moradi d4e71274f9 update changelog 2023-02-12 23:33:06 +03:30
487 changed files with 32498 additions and 8326 deletions
+11
View File
@@ -0,0 +1,11 @@
[*.{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
+4 -1
View File
@@ -25,4 +25,7 @@
*.pyc binary
*.swp binary
*.pdf binary
*.exe binary
*.exe binary
*.avif binary
*.heif binary
*.jxl binary
-44
View File
@@ -1,44 +0,0 @@
---
name: "🐞 Bug report"
title: "[Bug] <short description>"
about: "Report a bug"
labels: "bug"
---
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app.
- 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 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.
- 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
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## Device information
- Tachidesk version: (Example: v0.2.3-r255-win32)
- Server Operating System: (Example: Ubuntu 20.04)
- Server Desktop Environment: N/A or (Example: Gnome 40)
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
- Client Operating System: <usually the same as above Server Operating System>
- Client Web Browser: (Example: Google Chrome 89.0.4389.82)
## Steps to reproduce
1. First Step
2. Second Step
### Expected behavior
Describe what should have happened. Remove this line after you are done.
### Actual behavior
Describe what happens instead. Remove this line after you are done.
## Other details
Describe additional details If necessary. Remove this line after you are done.
+144
View File
@@ -0,0 +1,144 @@
name: 🐞 Bug report
description: Report a bug in Suwayomi-Server
labels: [bug]
body:
- type: textarea
id: reproduce-steps
attributes:
label: Steps to reproduce
description: Provide an example of the issue.
placeholder: |
Example:
1. First step
2. Second step
3. Issue here
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Explain what you should expect to happen.
placeholder: |
Example: "This should happen..."
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: Explain what actually happens.
placeholder: |
Example: "This happened instead..."
validations:
required: true
- type: input
id: suwayomi-server-version
attributes:
label: Suwayomi-Server version
description: You can find your Suwayomi-Server version in **More → About**.
placeholder: |
Example: "v2.0.1727"
validations:
required: true
- type: input
id: server-os
attributes:
label: Server operating system
description: The operating system on which Suwayomi-Server is running on
placeholder: |
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
validations:
required: true
- type: input
id: server-desktop-environment
attributes:
label: Server Desktop Environment
description:
placeholder: |
Example: "Gnome 40"
validations:
required: false
- type: input
id: server-jvm-version
attributes:
label: Server JVM version
description: The java version used to run Suwayomi-Server
placeholder: |
Example: "openjdk 21.0.5 2024-10-15 LTS"
validations:
required: true
- type: input
id: client-name
attributes:
label: Used client name
description:
placeholder: |
Example: "Suwayomi-WebUI"
validations:
required: true
- type: input
id: client-version
attributes:
label: Client version
description:
placeholder: |
Example: "v1.2.3"
validations:
required: true
- type: input
id: client-browser
attributes:
label: Used web browser
description: The browser which is used to open Suwayomi-WebUI
placeholder: |
Example: "Chrome 134.0.6998.118 (64-Bit) | FireFox 136.0.2 (64-Bit) | Electron v35.0.2"
validations:
required: true
- type: input
id: client-os
attributes:
label: Client operating system
description: The system on which the Suwayomi-WebUI is running on
placeholder: |
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
description: The more information that gets provided the better, especially via videos and images
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title (ideally less than ~100 characters).
required: true
- label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support)
required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
- label: I understand that **Suwayomi does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
required: true
+4
View File
@@ -1 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: ☎️ Support
url: https://discord.gg/DDZdqZWaHA
about: Join our discord to get help for anything that is not a bug or a feature request
-29
View File
@@ -1,29 +0,0 @@
---
name: "🌟 Feature request"
title: "[Feature Request] <short description>"
about: "Suggest a feature to improve the project"
labels: "enhancement"
---
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app.
- 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 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.
- 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
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## 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.
## Why/Project's Benefit/Existing Problem
Explain why this should be added. Remove this line after you are done.
@@ -0,0 +1,37 @@
name: 🌟 Feature request
description: Suggest a feature to improve Suwayomi-Server
labels: [enhancement]
body:
- type: textarea
id: feature-description
attributes:
label: Describe your suggested feature
description: How can Suwayomi-Server be improved?
placeholder: |
Example:
"It should work like this..."
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title (ideally less than ~100 characters).
required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
+16 -16
View File
@@ -3,6 +3,10 @@ name: CI Pull Request
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
@@ -10,34 +14,32 @@ jobs:
steps:
- name: Clone repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/wrapper-validation-action@v3
build:
name: Build pull request
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout pull request
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 1.8
java-version: 21
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
@@ -46,8 +48,6 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build Jar
uses: gradle/gradle-build-action@v2
with:
build-root-directory: master
arguments: :server:shadowJar --stacktrace
working-directory: master
run: ./gradlew ktlintCheck :server:shadowJar --stacktrace
+76 -42
View File
@@ -5,39 +5,41 @@ on:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/wrapper-validation-action@v3
build:
name: Build Jar
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout master branch
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: master
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 1.8
java-version: 21
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
@@ -46,22 +48,20 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build Jar
uses: gradle/gradle-build-action@v2
env:
ProductBuildType: "Preview"
with:
build-root-directory: master
arguments: :server:shadowJar --stacktrace
working-directory: master
run: ./gradlew :server:shadowJar --stacktrace
- name: Upload Jar
uses: actions/upload-artifact@v3
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@v3
uses: actions/upload-artifact@v4
with:
name: icon
path: master/server/src/main/resources/icon
@@ -69,13 +69,45 @@ jobs:
- 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
uses: actions/upload-artifact@v4
with:
name: scripts
path: scripts.tar.gz
if-no-files-found: error
jlink:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
name: linux-x64
- os: windows-latest
name: windows-x64
- os: macos-14
name: macOS-arm64
- os: macos-13
name: macOS-x64
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
steps:
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'temurin'
- name: Package JDK
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JRE package
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}-jre
path: suwa
bundle:
strategy:
fail-fast: false
@@ -87,26 +119,32 @@ jobs:
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: build
needs: [build,jlink]
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: jar
path: server/build
- name: Download JRE
uses: actions/download-artifact@v4
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
with:
name: ${{ matrix.os }}-jre
path: jre
- name: Download icons
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: icon
path: server/src/main/resources/icon
- name: Download scripts.tar.gz
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: scripts
@@ -117,7 +155,7 @@ jobs:
scripts/bundler.sh -o upload/ ${{ matrix.os }}
- name: Upload ${{ matrix.os }} release
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
path: upload/*
@@ -127,43 +165,39 @@ jobs:
needs: bundle
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: jar
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: debian-all
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: linux-assets
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: linux-x64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: macOS-x64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: macOS-arm64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
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
uses: actions/checkout@v4
with:
repository: "Suwayomi/Tachidesk-Server-preview"
repository: "Suwayomi/Suwayomi-Server-preview"
ref: main
path: preview
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
@@ -172,9 +206,9 @@ jobs:
id: GenTagName
run: |
cd release
genTag=$(ls *.jar | sed -e's/Tachidesk-Server-\|.jar//g')
genTag=$(ls *.jar | sed -e's/Suwayomi-Server-\|.jar//g')
echo "$genTag"
echo "::set-output name=value::$genTag"
echo "value=$genTag" >> $GITHUB_OUTPUT
- name: Create Tag
run: |
@@ -193,10 +227,10 @@ jobs:
git push origin $TAG
- name: Upload Preview Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
repository: "Suwayomi/Tachidesk-Server-preview"
repository: "Suwayomi/Suwayomi-Server-preview"
tag_name: ${{ steps.GenTagName.outputs.value }}
files: release/*
-48
View File
@@ -1,48 +0,0 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ github.token }}
duplicate-check-enabled: true
duplicate-check-label: Source request
existing-check-enabled: true
existing-check-label: Source request
auto-close-rules: |
[
{
"type": "title",
"regex": ".*<short description>.*",
"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",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed"
},
{
"type": "body",
"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"
},
{
"type": "body",
"regex": ".*Remove this line after you are done.*",
"message": "The lines requesting to be removed were not removed."
}
]
+72 -37
View File
@@ -6,38 +6,41 @@ on:
tags:
- "v*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/wrapper-validation-action@v3
build:
name: Build Jar
needs: check_wrapper
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout ${{ github.ref }}
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 1.8
java-version: 21
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
@@ -47,22 +50,20 @@ jobs:
~/.gradle/gradle.properties
- name: Build and copy webUI, Build Jar
uses: gradle/gradle-build-action@v2
env:
ProductBuildType: "Stable"
with:
build-root-directory: master
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
working-directory: master
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
- name: Upload Jar
uses: actions/upload-artifact@v3
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@v3
uses: actions/upload-artifact@v4
with:
name: icon
path: master/server/src/main/resources/icon
@@ -70,13 +71,45 @@ jobs:
- 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
uses: actions/upload-artifact@v4
with:
name: scripts
path: scripts.tar.gz
if-no-files-found: error
jlink:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
name: linux-x64
- os: windows-latest
name: windows-x64
- os: macos-14
name: macOS-arm64
- os: macos-13
name: macOS-x64
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
steps:
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'temurin'
- name: Package JDK
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JDK package
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}-jre
path: suwa
bundle:
strategy:
fail-fast: false
@@ -88,26 +121,32 @@ jobs:
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: build
needs: [build, jlink]
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: jar
path: server/build
- name: Download JRE
uses: actions/download-artifact@v4
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
with:
name: ${{ matrix.os }}-jre
path: jre
- name: Download icons
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: icon
path: server/src/main/resources/icon
- name: Download scripts.tar.gz
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: scripts
@@ -118,7 +157,7 @@ jobs:
scripts/bundler.sh -o upload/ ${{ matrix.os }}
- name: Upload ${{ matrix.os }} files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
path: upload/*
@@ -129,45 +168,41 @@ jobs:
needs: bundle
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: jar
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: debian-all
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: linux-assets
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: linux-x64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: macOS-x64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: macOS-arm64
path: release
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: windows-x64
path: release
- uses: actions/download-artifact@v3
with:
name: windows-x86
path: release
- name: Generate checksums
run: cd release && sha256sum * > Checksums.sha256
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.WINGET_PUBLISH_PAT }}
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
draft: true
files: release/*
+5 -3
View File
@@ -1,12 +1,14 @@
name: Publish to WinGet
on:
release:
types: [released]
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@v1
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: Suwayomi.Tachidesk-Server
installers-regex: '.*x64.msi$'
+1
View File
@@ -5,6 +5,7 @@ gradle.properties
.fleet
# But we need these
!.idea/runConfigurations
.kotlin
# Ignore Gradle build output directory
build
+16 -5
View File
@@ -1,12 +1,23 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.kotlinter.get().pluginId)
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)
}
}
@@ -15,6 +15,6 @@ val ApplicationRootDir: String
get(): String {
return System.getProperty(
"$CONFIG_PREFIX.server.rootDir",
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null),
)
}
@@ -1,12 +0,0 @@
package xyz.nulldev.ts.config
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.singleton
class ConfigKodeinModule {
fun create() = DI.Module("ConfigManager") {
// Config module
bind<ConfigManager>() with singleton { GlobalConfigManager }
}
}
@@ -10,22 +10,31 @@ package xyz.nulldev.ts.config
import ch.qos.logback.classic.Level
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions
import mu.KotlinLogging
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.parser.ConfigDocument
import com.typesafe.config.parser.ConfigDocumentFactory
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
/**
* Manages app config.
*/
open class ConfigManager {
val logger = KotlinLogging.logger {}
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
val config by lazy { loadConfigs() }
private val userConfigFile = File(ApplicationRootDir, "server.conf")
private var internalConfig = loadConfigs()
val config: Config
get() = internalConfig
// Public read-only view of modules
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
get() = generatedModules
val logger = KotlinLogging.logger {}
private val mutex = Mutex()
/**
* Get a config module
@@ -38,6 +47,11 @@ open class ConfigManager {
@Suppress("UNCHECKED_CAST")
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
private fun getUserConfig(): Config =
userConfigFile.let {
ConfigFactory.parseFile(it)
}
/**
* Load configs
*/
@@ -48,30 +62,26 @@ open class ConfigManager {
val baseConfig =
ConfigFactory.parseMap(
mapOf(
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
)
// override AndroidCompat's rootDir
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat",
),
)
// Load user config
val userConfig =
File(ApplicationRootDir, "server.conf").let {
ConfigFactory.parseFile(it)
}
val userConfig = getUserConfig()
val config = ConfigFactory.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
.withFallback(compatConfig)
.withFallback(serverConfig)
.resolve()
val config =
ConfigFactory
.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
.withFallback(compatConfig)
.withFallback(serverConfig)
.resolve()
// set log level early
if (debugLogsEnabled(config)) {
setLogLevel(Level.DEBUG)
}
logger.debug {
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
setLogLevelFor(BASE_LOGGER_NAME, Level.DEBUG)
}
return config
@@ -86,6 +96,73 @@ open class ConfigManager {
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()
@@ -0,0 +1,9 @@
package xyz.nulldev.ts.config
import org.koin.core.module.Module
import org.koin.dsl.module
fun configManagerModule(): Module =
module {
single<ConfigManager> { GlobalConfigManager }
}
@@ -8,6 +8,8 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import io.github.config4k.getValue
import kotlin.reflect.KProperty
@@ -15,28 +17,47 @@ import kotlin.reflect.KProperty
* Abstract config module.
*/
@Suppress("UNUSED_PARAMETER")
abstract class ConfigModule(config: Config)
abstract class ConfigModule(
getConfig: () -> Config,
)
/**
* Abstract jvm-commandline-argument-overridable config module.
*/
abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String) : ConfigModule(config) {
val overridableConfig = SystemPropertyOverrideDelegate(config, moduleName)
abstract class SystemPropertyOverridableConfigModule(
getConfig: () -> Config,
moduleName: String,
) : ConfigModule(getConfig) {
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
}
/** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */
class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String) {
inline operator fun <R, reified T> getValue(thisRef: R, property: KProperty<*>): T {
class SystemPropertyOverrideDelegate(
val getConfig: () -> Config,
val moduleName: String,
) {
inline operator fun <R, reified T> getValue(
thisRef: R,
property: KProperty<*>,
): T {
val config = getConfig()
val configValue: T = config.getValue(thisRef, property)
val combined = System.getProperty(
"$CONFIG_PREFIX.$moduleName.${property.name}",
configValue.toString()
)
val combined =
System.getProperty(
"$CONFIG_PREFIX.$moduleName.${property.name}",
if (T::class.simpleName == "List") {
ConfigValueFactory.fromAnyRef(configValue).render()
} else {
configValue.toString()
},
)
return when (T::class.simpleName) {
"Int" -> combined.toInt()
"Boolean" -> combined.toBoolean()
"Double" -> combined.toDouble()
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
// add more types as needed
else -> combined // covers String
} as T
@@ -8,12 +8,131 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
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 mu.KotlinLogging
import io.github.oshai.kotlinlogging.DelegatingKLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.slf4j.Logger
import org.slf4j.LoggerFactory
fun setLogLevel(level: Level) {
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
private fun fileSizeValueOfOrDefault(
fileSizeStr: String,
default: String,
): FileSize =
try {
FileSize.valueOf(fileSizeStr)
} catch (e: IllegalArgumentException) {
FileSize.valueOf(default)
}
private const val FILE_APPENDER_NAME = "SuwayomiDefaultAppender"
private fun createRollingFileAppender(
logContext: LoggerContext,
logDirPath: String,
maxFiles: Int,
maxFileSize: String,
maxTotalSize: 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_APPENDER_NAME
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"
maxHistory = maxFiles.coerceAtLeast(0)
setMaxFileSize(fileSizeValueOfOrDefault(maxFileSize, "10mb"))
setTotalSizeCap(fileSizeValueOfOrDefault(maxTotalSize, "100mb"))
start()
}
appender.rollingPolicy = rollingPolicy
appender.start()
return appender
}
private fun getBaseLogger(): ch.qos.logback.classic.Logger =
((KotlinLogging.logger(Logger.ROOT_LOGGER_NAME) as DelegatingKLogger<*>).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,
maxFiles: Int,
maxFileSize: String,
maxTotalSize: 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", maxFiles, maxFileSize, maxTotalSize))
// set "kotlin exposed" log level
setLogLevelFor("Exposed", Level.ERROR)
}
fun updateFileAppender(
maxFiles: Int,
maxFileSize: String,
maxTotalSize: String,
) {
val logger = getBaseLogger()
val appender = logger.getAppender(FILE_APPENDER_NAME) as RollingFileAppender<*>? ?: return
val rollingPolicy = appender.rollingPolicy as SizeAndTimeBasedRollingPolicy<*>
rollingPolicy.apply {
maxHistory = maxFiles
setMaxFileSize(FileSize.valueOf(maxFileSize))
setTotalSizeCap(FileSize.valueOf(maxTotalSize))
rollingPolicy.stop()
appender.stop()
rollingPolicy.start()
appender.start()
}
}
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) =
@@ -2,5 +2,6 @@ package xyz.nulldev.ts.config.util
import com.typesafe.config.Config
operator fun Config.get(key: String) = getString(key)
?: throw IllegalStateException("Could not find value for config entry: $key!")
operator fun Config.get(key: String) =
getString(key)
?: throw IllegalStateException("Could not find value for config entry: $key!")
+17 -6
View File
@@ -1,8 +1,19 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.kotlinter.get().pluginId)
id(
libs.plugins.kotlin.jvm
.get()
.pluginId,
)
id(
libs.plugins.kotlin.serialization
.get()
.pluginId,
)
id(
libs.plugins.ktlint
.get()
.pluginId,
)
}
dependencies {
@@ -25,8 +36,8 @@ dependencies {
// AndroidX annotations
compileOnly(libs.android.annotations)
// substitute for duktape-android
implementation(libs.bundles.rhino)
// substitute for duktape-android/quickjs
implementation(libs.bundles.polyglot)
// Kotlin wrapper around Java Preferences, makes certain things easier
implementation(libs.bundles.settings)
@@ -25,7 +25,7 @@ import android.os.IBinder;
import android.util.Log;
import kotlin.NotImplementedError;
import xyz.nulldev.androidcompat.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -299,7 +299,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
private static final ServiceSupport serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class);
private static final ServiceSupport serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class);
private static final String TAG = "Service";
/**
@@ -328,7 +328,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
public Service() {
//==================[THIS LINE MODIFIED FROM ANDROID SOURCE!]==================
//Service must be initialized with a base context!
super(KodeinGlobalHelper.instance(Context.class));
super(KoinGlobalHelper.instance(Context.class));
}
/** Return the application that owns this service. */
public final Application getApplication() {
@@ -1,20 +1,23 @@
package android.graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
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 int width;
private int height;
private BufferedImage image;
private final int width;
private final int height;
private final BufferedImage image;
public Bitmap(BufferedImage image) {
this.image = image;
@@ -59,8 +62,8 @@ public final class Bitmap {
final int nativeInt;
private static Config sConfigs[] = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
private static final Config[] sConfigs = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
};
Config(int ni) {
@@ -72,11 +75,60 @@ public final class Bitmap {
}
}
/**
* 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();
@@ -87,7 +139,7 @@ public final class Bitmap {
}
float qualityFloat = ((float) quality) / 100;
String formatString = "";
String formatString;
if (format == Bitmap.CompressFormat.PNG) {
formatString = "png";
} else if (format == Bitmap.CompressFormat.JPEG) {
@@ -100,7 +152,7 @@ public final class Bitmap {
if (!writers.hasNext()) {
throw new IllegalStateException("no image writers found for this format!");
}
ImageWriter writer = (ImageWriter) writers.next();
ImageWriter writer = writers.next();
ImageOutputStream ios;
try {
@@ -111,19 +163,73 @@ public final class Bitmap {
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
if (formatString == "jpg") {
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();
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);
}
}
}
@@ -1,7 +1,7 @@
package android.os;
import xyz.nulldev.androidcompat.io.AndroidFiles;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.io.File;
@@ -9,7 +9,7 @@ import java.io.File;
* Android compatibility layer for files
*/
public class Environment {
private static AndroidFiles androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class);
private static AndroidFiles androidFiles = KoinGlobalHelper.instance(AndroidFiles.class);
public static String DIRECTORY_ALARMS = getHomeDirectory("Alarms").getAbsolutePath();
public static String DIRECTORY_DCIM = getHomeDirectory("DCIM").getAbsolutePath();
@@ -12,7 +12,7 @@ class PreferenceManager {
fun getDefaultSharedPreferences(context: Context) =
context.getSharedPreferences(
context.applicationInfo.packageName,
Context.MODE_PRIVATE
Context.MODE_PRIVATE,
)!!
}
}
@@ -0,0 +1,247 @@
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);
}
@@ -0,0 +1,45 @@
/*
* 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" })
}
@@ -22,6 +22,7 @@ public class Preference {
@JsonIgnore
protected Context context;
private boolean isVisible;
private String key;
private CharSequence title;
private CharSequence summary;
@@ -100,6 +101,14 @@ public class Preference {
return sharedPreferences;
}
public void setVisible(boolean visible) {
isVisible = visible;
}
public boolean getVisible() {
return isVisible;
}
/** Tachidesk specific API */
public void setSharedPreferences(SharedPreferences sharedPreferences) {
this.sharedPreferences = sharedPreferences;
@@ -1,69 +1,99 @@
package app.cash.quickjs;
import org.mozilla.javascript.ConsString;
import org.mozilla.javascript.NativeArray;
import org.graalvm.polyglot.*;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.Closeable;
import java.math.BigInteger;
import java.util.Arrays;
public final class QuickJs implements Closeable {
private ScriptEngine engine;
private Context context;
public static QuickJs create() {
return new QuickJs(new ScriptEngineManager());
return new QuickJs();
}
public QuickJs(ScriptEngineManager manager) {
this.engine = manager.getEngineByName("rhino");
public QuickJs() {
this.context = Context
.newBuilder("js")
.allowHostAccess(HostAccess.ALL)
.allowPolyglotAccess(PolyglotAccess.NONE)
.allowHostClassLoading(false)
.build();
context.enter();
}
public Object evaluate(String script, String fileName) {
public Object evaluate(String script, String ignoredFileName) {
return this.evaluate(script);
}
public Object evaluate(String script) {
try {
Object value = engine.eval(script);
Value value = context.eval("js", 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));
private Object translateType(Value obj) {
if (obj.isBoolean()) {
return obj.asBoolean();
} else if (obj.hasArrayElements()) {
if (obj.getArraySize() == 0) {
return new int[0];
} else {
Value element = obj.getArrayElement(0);
if (element.isBoolean()) {
return obj.as(boolean[].class);
} else if (element.isNumber()) {
if (element.fitsInInt()) {
return obj.as(int[].class);
} else if (element.fitsInBigInteger()) {
return Arrays.stream(obj.as(BigInteger[].class)).map(BigInteger::longValue).toArray();
} else {
return obj.as(double[].class);
}
} else if (element.isHostObject()) {
return obj.as(Object[].class);
} else if (element.isString()) {
return obj.as(String[].class);
}
}
return objects;
}
if (obj instanceof ConsString) {
ConsString consString = (ConsString) obj;
return consString.toString();
}
if (obj instanceof Long) {
Long value = (Long) obj;
return value.intValue();
} else if (obj.isNumber()) {
if (obj.fitsInInt()) {
return obj.asInt();
} else if (obj.fitsInBigInteger()) {
return obj.asBigInteger().longValue();
} else {
return obj.asDouble();
}
} else if (obj.isHostObject()) {
return obj.asHostObject();
} else if (obj.isString()) {
return obj.asString();
}
return obj;
}
public byte[] compile(String sourceCode, String fileName) {
public byte[] compile(String sourceCode, String ignoredFileName) {
return sourceCode.getBytes();
}
public Object execute(byte[] bytecode) {
return this.evaluate(new String(bytecode));
}
public <T> void set(String name, Class<T> ignoredType, T object) {
context.getBindings("js").putMember(name, object);
}
@Override
public void close() {
this.engine = null;
if (this.context != null) {
this.context.leave();
this.context.close();
this.context = null;
}
}
}
@@ -17,7 +17,7 @@ package dalvik.system;
import org.jetbrains.annotations.Nullable;
import xyz.nulldev.androidcompat.pm.PackageController;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.io.File;
import java.io.IOException;
@@ -33,7 +33,7 @@ import java.util.Enumeration;
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
private final URLClassLoader realClassloader;
@@ -1,14 +1,11 @@
package xyz.nulldev.androidcompat
import android.app.Application
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.koin.mp.KoinPlatformTools
import xyz.nulldev.androidcompat.androidimpl.CustomContext
class AndroidCompat {
val context: CustomContext by DI.global.instance()
val context: CustomContext by KoinPlatformTools.defaultContext().get().inject()
fun startApp(application: Application) {
application.attach(context)
@@ -1,7 +1,5 @@
package xyz.nulldev.androidcompat
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.androidcompat.config.FilesConfigModule
import xyz.nulldev.androidcompat.config.SystemConfigModule
@@ -12,16 +10,17 @@ import xyz.nulldev.ts.config.GlobalConfigManager
*/
class AndroidCompatInitializer {
fun init() {
DI.global.addImport(AndroidCompatModule().create())
// Register config modules
GlobalConfigManager.registerModules(
FilesConfigModule.register(GlobalConfigManager.config),
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config)
SystemConfigModule.register(GlobalConfigManager.config),
)
// Set some properties extensions use
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")
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",
)
}
}
@@ -1,11 +1,8 @@
package xyz.nulldev.androidcompat
import android.content.Context
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.kodein.di.singleton
import org.koin.core.module.Module
import org.koin.dsl.module
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
@@ -17,23 +14,19 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
* AndroidCompatModule
*/
class AndroidCompatModule {
fun create() = DI.Module("AndroidCompat") {
bind<AndroidFiles>() with singleton { AndroidFiles() }
fun androidCompatModule(): Module =
module {
single { AndroidFiles() }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
single { ApplicationInfoImpl(get()) }
bind<ServiceSupport>() with singleton { ServiceSupport() }
single { ServiceSupport() }
bind<FakePackageManager>() with singleton { FakePackageManager() }
single { FakePackageManager() }
bind<PackageController>() with singleton { PackageController() }
single { PackageController() }
// Context
bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with singleton {
val context: Context by DI.global.instance<CustomContext>()
context
}
single { CustomContext() }
single<Context> { get<CustomContext>() }
}
}
@@ -32,15 +32,14 @@ import android.os.*;
import android.view.Display;
import android.view.DisplayAdjustments;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kodein.di.*;
import org.koin.core.Koin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.io.AndroidFiles;
import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences;
import xyz.nulldev.androidcompat.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.io.*;
import java.util.HashMap;
@@ -51,26 +50,25 @@ import java.util.Map;
* Custom context implementation.
*
*/
public class CustomContext extends Context implements DIAware {
private final DI kodein;
public class CustomContext extends Context {
private final Koin koin;
public CustomContext() {
this(KodeinGlobalHelper.kodein());
this(KoinGlobalHelper.koin());
}
public CustomContext(DI kodein) {
this.kodein = kodein;
public CustomContext(Koin koin) {
this.koin = koin;
//Init configs
androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class, getDi());
applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class, getDi());
fakePackageManager = KodeinGlobalHelper.instance(FakePackageManager.class, getDi());
androidFiles = KoinGlobalHelper.instance(AndroidFiles.class, getDi());
applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class, getDi());
fakePackageManager = KoinGlobalHelper.instance(FakePackageManager.class, getDi());
}
@NotNull
@Override
public DI getDi() {
return kodein;
public Koin getDi() {
return koin;
}
private AndroidFiles androidFiles;
@@ -719,17 +717,5 @@ public class CustomContext extends Context implements DIAware {
public boolean isCredentialProtectedStorage() {
return false;
}
@NotNull
@Override
public DIContext<?> getDiContext() {
return getDi().getDiContext();
}
@Nullable
@Override
public DITrigger getDiTrigger() {
return null;
}
}
@@ -16,14 +16,14 @@ import android.os.UserHandle;
import kotlin.NotImplementedError;
import xyz.nulldev.androidcompat.pm.InstalledPackage;
import xyz.nulldev.androidcompat.pm.PackageController;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class FakePackageManager extends PackageManager {
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
@Override
public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
@@ -8,12 +8,13 @@ import xyz.nulldev.ts.config.ConfigModule
* Application info config.
*/
class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
val packageName: String by config
val debug: Boolean by config
class ApplicationInfoConfigModule(
getConfig: () -> Config,
) : ConfigModule(getConfig) {
val packageName: String by getConfig()
val debug: Boolean by getConfig()
companion object {
fun register(config: Config) =
ApplicationInfoConfigModule(config.getConfig("android.app"))
fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") }
}
}
@@ -8,27 +8,28 @@ import xyz.nulldev.ts.config.ConfigModule
* Files configuration modules. Specifies where to store the Android files.
*/
class FilesConfigModule(config: Config) : ConfigModule(config) {
val dataDir: String by config
val filesDir: String by config
val noBackupFilesDir: String by config
val externalFilesDirs: MutableList<String> by config
val obbDirs: MutableList<String> by config
val cacheDir: String by config
val codeCacheDir: String by config
val externalCacheDirs: MutableList<String> by config
val externalMediaDirs: MutableList<String> by config
val rootDir: String by config
val externalStorageDir: String by config
val downloadCacheDir: String by config
val databasesDir: String by config
class FilesConfigModule(
getConfig: () -> Config,
) : ConfigModule(getConfig) {
val dataDir: String by getConfig()
val filesDir: String by getConfig()
val noBackupFilesDir: String by getConfig()
val externalFilesDirs: MutableList<String> by getConfig()
val obbDirs: MutableList<String> by getConfig()
val cacheDir: String by getConfig()
val codeCacheDir: String by getConfig()
val externalCacheDirs: MutableList<String> by getConfig()
val externalMediaDirs: MutableList<String> by getConfig()
val rootDir: String by getConfig()
val externalStorageDir: String by getConfig()
val downloadCacheDir: String by getConfig()
val databasesDir: String by getConfig()
val prefsDir: String by config
val prefsDir: String by getConfig()
val packageDir: String by config
val packageDir: String by getConfig()
companion object {
fun register(config: Config) =
FilesConfigModule(config.getConfig("android.files"))
fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") }
}
}
@@ -4,19 +4,24 @@ import com.typesafe.config.Config
import io.github.config4k.getValue
import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(val config: Config) : ConfigModule(config) {
val isDebuggable: Boolean by config
class SystemConfigModule(
val getConfig: () -> Config,
) : ConfigModule(getConfig) {
val isDebuggable: Boolean by getConfig()
val propertyPrefix = "properties."
fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property")
fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
fun getStringProperty(property: String) = getConfig().getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = getConfig().getInt("$propertyPrefix$property")
fun getLongProperty(property: String) = getConfig().getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = getConfig().getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = getConfig().hasPath("$propertyPrefix$property")
companion object {
fun register(config: Config) =
SystemConfigModule(config.getConfig("android.system"))
fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") }
}
}
File diff suppressed because it is too large Load Diff
@@ -1,16 +1,12 @@
package xyz.nulldev.androidcompat.info
import android.content.pm.ApplicationInfo
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.ts.config.ConfigManager
class ApplicationInfoImpl(override val di: DI = DI.global) : ApplicationInfo(), DIAware {
val configManager: ConfigManager by di.instance()
class ApplicationInfoImpl(
private val configManager: ConfigManager,
) : ApplicationInfo() {
val appInfoConfig: ApplicationInfoConfigModule
get() = configManager.module()
@@ -8,7 +8,9 @@ import java.io.File
/**
* Android file constants.
*/
class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
class AndroidFiles(
val configManager: ConfigManager = GlobalConfigManager,
) {
val filesConfig: FilesConfigModule
get() = configManager.module()
@@ -30,9 +32,8 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
val packagesDir: File get() = registerFile(filesConfig.packageDir)
fun registerFile(file: String): File {
return File(file).apply {
fun registerFile(file: String): File =
File(file).apply {
mkdirs()
}
}
}
@@ -9,38 +9,87 @@ package xyz.nulldev.androidcompat.io.sharedprefs
import android.content.SharedPreferences
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.ExperimentalSettingsImplementation
import com.russhwolf.settings.PreferencesSettings
import com.russhwolf.settings.PropertiesSettings
import com.russhwolf.settings.Settings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.builtins.serializer
import java.util.prefs.PreferenceChangeListener
import java.util.prefs.Preferences
import xyz.nulldev.androidcompat.util.SafePath
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(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(key: String) : SharedPreferences {
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
private val preferences = PreferencesSettings(javaPreferences)
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
override fun getAll(): MutableMap<String, *> {
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(
key: String,
) : SharedPreferences {
companion object {
private val logger = KotlinLogging.logger {}
}
override fun getString(key: String, defValue: String?): String? {
return if (defValue != null) {
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
override fun getAll(): MutableMap<String, *> = preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
override fun getString(
key: String,
defValue: String?,
): String? =
if (defValue != null) {
preferences.getString(key, defValue)
} else {
preferences.getStringOrNull(key)
}
}
override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
override fun getStringSet(
key: String,
defValues: Set<String>?,
): Set<String>? {
try {
return if (defValues != null) {
preferences.decodeValue(SetSerializer(String.serializer()), key, defValues)
@@ -52,81 +101,117 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
}
override fun getInt(key: String, defValue: Int): Int {
return preferences.getInt(key, defValue)
}
override fun getInt(
key: String,
defValue: Int,
): Int = preferences.getInt(key, defValue)
override fun getLong(key: String, defValue: Long): Long {
return preferences.getLong(key, defValue)
}
override fun getLong(
key: String,
defValue: Long,
): Long = preferences.getLong(key, defValue)
override fun getFloat(key: String, defValue: Float): Float {
return preferences.getFloat(key, defValue)
}
override fun getFloat(
key: String,
defValue: Float,
): Float = preferences.getFloat(key, defValue)
override fun getBoolean(key: String, defValue: Boolean): Boolean {
return preferences.getBoolean(key, defValue)
}
override fun getBoolean(
key: String,
defValue: Boolean,
): Boolean = preferences.getBoolean(key, defValue)
override fun contains(key: String): Boolean {
return key in preferences.keys
}
override fun contains(key: String): Boolean = key in preferences.keys
override fun edit(): SharedPreferences.Editor {
return Editor(preferences)
}
override fun edit(): SharedPreferences.Editor =
Editor(preferences) { key ->
listeners.forEach { (_, listener) ->
listener(key)
}
}
class Editor(private val preferences: PreferencesSettings) : SharedPreferences.Editor {
val itemsToAdd = mutableMapOf<String, Any>()
class Editor(
private val preferences: Settings,
private val notify: (String) -> Unit,
) : SharedPreferences.Editor {
private val actions = mutableListOf<Action>()
override fun putString(key: String, value: String?): SharedPreferences.Editor {
private sealed class Action {
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) {
itemsToAdd[key] = value
actions += Action.Add(key, value)
} else {
remove(key)
actions += Action.Remove(key)
}
return this
}
override fun putStringSet(
key: String,
values: MutableSet<String>?
values: MutableSet<String>?,
): SharedPreferences.Editor {
if (values != null) {
itemsToAdd[key] = values
actions += Action.Add(key, values)
} else {
remove(key)
actions += Action.Remove(key)
}
return this
}
override fun putInt(key: String, value: Int): SharedPreferences.Editor {
itemsToAdd[key] = value
override fun putInt(
key: String,
value: Int,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putLong(key: String, value: Long): SharedPreferences.Editor {
itemsToAdd[key] = value
override fun putLong(
key: String,
value: Long,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
itemsToAdd[key] = value
override fun putFloat(
key: String,
value: Float,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
itemsToAdd[key] = value
override fun putBoolean(
key: String,
value: Boolean,
): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun remove(key: String): SharedPreferences.Editor {
itemsToAdd.remove(key)
actions += Action.Remove(key)
return this
}
override fun clear(): SharedPreferences.Editor {
itemsToAdd.clear()
actions.add(Action.Clear)
return this
}
@@ -140,38 +225,56 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
private fun addToPreferences() {
itemsToAdd.forEach { (key, value) ->
actions.forEach {
@Suppress("UNCHECKED_CAST")
when (value) {
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set<String>)
is String -> preferences.putString(key, value)
is Int -> preferences.putInt(key, value)
is Long -> preferences.putLong(key, value)
is Float -> preferences.putFloat(key, value)
is Double -> preferences.putDouble(key, value)
is Boolean -> preferences.putBoolean(key, value)
when (it) {
is Action.Add -> {
when (val value = it.value) {
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>)
is String -> preferences.putString(it.key, value)
is Int -> preferences.putInt(it.key, value)
is Long -> preferences.putLong(it.key, value)
is Float -> preferences.putFloat(it.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) {
val javaListener = PreferenceChangeListener {
listener.onSharedPreferenceChanged(this, it.key)
val javaListener: (String) -> Unit = {
listener.onSharedPreferenceChanged(this, it)
}
listeners[listener] = javaListener
javaPreferences.addPreferenceChangeListener(javaListener)
}
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
val registeredListener = listeners.remove(listener)
if (registeredListener != null) {
javaPreferences.removePreferenceChangeListener(registeredListener)
}
listeners.remove(listener)
}
fun deleteAll(): Boolean {
javaPreferences.removeNode()
preferences.clear()
return true
}
}
@@ -14,48 +14,60 @@ import java.io.File
import javax.imageio.ImageIO
import javax.xml.parsers.DocumentBuilderFactory
data class InstalledPackage(val root: File) {
data class InstalledPackage(
val root: File,
) {
val apk = File(root, "package.apk")
val jar = File(root, "translated.jar")
val icon = File(root, "icon.png")
val info: PackageInfo
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
val parsed = ApkFile(apk)
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc = parsed.manifestXml.byteInputStream().use {
dBuilder.parse(it)
get() =
ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
val parsed = ApkFile(apk)
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc =
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 {
val res = ApkVerifier.Builder(apk)
.build()
.verify()
val res =
ApkVerifier
.Builder(apk)
.build()
.verify()
return res.isVerified
}
@@ -64,11 +76,15 @@ data class InstalledPackage(val root: File) {
try {
val icons = ApkFile(apk).allIcons
val read = icons.filter { it.isFile }.map {
it.data.inputStream().use {
ImageIO.read(it)
}
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
val read =
icons
.filter { it.isFile }
.map {
it.data.inputStream().use {
ImageIO.read(it)
}
}.sortedByDescending { it.width * it.height }
.firstOrNull() ?: return
ImageIO.write(read, "png", icon)
} catch (e: Exception) {
@@ -88,8 +104,9 @@ data class InstalledPackage(val root: File) {
fun NodeList.toList(): List<Node> {
val out = mutableListOf<Node>()
for (i in 0 until length)
for (i in 0 until length) {
out += item(i)
}
return out
}
@@ -1,14 +1,12 @@
package xyz.nulldev.androidcompat.pm
import net.dongliu.apk.parser.ApkParsers
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.koin.mp.KoinPlatformTools
import xyz.nulldev.androidcompat.io.AndroidFiles
import java.io.File
class PackageController {
private val androidFiles by DI.global.instance<AndroidFiles>()
private val androidFiles: AndroidFiles by KoinPlatformTools.defaultContext().get().inject()
private val uninstallListeners = mutableListOf<(String) -> Unit>()
fun registerUninstallListener(listener: (String) -> Unit) {
@@ -25,7 +23,10 @@ class PackageController {
return File(androidFiles.packagesDir, pn)
}
fun installPackage(apk: File, allowReinstall: Boolean) {
fun installPackage(
apk: File,
allowReinstall: Boolean,
) {
val root = findRoot(apk)
if (root.exists()) {
@@ -54,13 +55,15 @@ class PackageController {
}
}
fun listInstalled(): List<InstalledPackage> {
return androidFiles.packagesDir.listFiles().orEmpty().filter {
it.isDirectory
}.map {
InstalledPackage(it)
}
}
fun listInstalled(): List<InstalledPackage> =
androidFiles.packagesDir
.listFiles()
.orEmpty()
.filter {
it.isDirectory
}.map {
InstalledPackage(it)
}
fun deletePackage(pack: InstalledPackage) {
if (!pack.root.exists()) error("Package was never installed!")
@@ -6,22 +6,24 @@ import android.content.pm.PackageInfo
import net.dongliu.apk.parser.bean.ApkMeta
import java.io.File
fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
return PackageInfo().also {
fun ApkMeta.toPackageInfo(apk: File): PackageInfo =
PackageInfo().also {
it.packageName = packageName
it.versionCode = versionCode.toInt()
it.versionName = versionName
it.reqFeatures = usesFeatures.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.reqFeatures =
usesFeatures
.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.applicationInfo = ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
}
it.applicationInfo =
ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
}
}
}
@@ -1,7 +1,7 @@
package xyz.nulldev.androidcompat.res;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -10,7 +10,7 @@ import java.util.Calendar;
* BuildConfig compat class.
*/
public class BuildConfigCompat {
private static ApplicationInfoImpl applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class);
private static ApplicationInfoImpl applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class);
public static final boolean DEBUG = applicationInfo.getDebug();
@@ -1,6 +1,8 @@
package xyz.nulldev.androidcompat.res
class DrawableResource(val location: String) : Resource {
class DrawableResource(
val location: String,
) : Resource {
override fun getType() = DrawableResource::class.java
override fun getValue() = javaClass.getResourceAsStream(location)
@@ -19,7 +19,9 @@ package xyz.nulldev.androidcompat.res
/**
* String resource.
*/
class StringResource(val string: String) : Resource {
class StringResource(
val string: String,
) : Resource {
override fun getValue() = string
override fun getType() = StringResource::class.java
@@ -3,7 +3,7 @@ package xyz.nulldev.androidcompat.service
import android.app.Service
import android.content.Context
import android.content.Intent
import mu.KotlinLogging
import io.github.oshai.kotlinlogging.KotlinLogging
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.thread
@@ -18,7 +18,10 @@ class ServiceSupport {
private val logger = KotlinLogging.logger {}
fun startService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
fun startService(
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
val name = intentToClassName(intent)
logger.debug { "Starting service: $name" }
@@ -35,7 +38,10 @@ class ServiceSupport {
}
}
fun stopService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
fun stopService(
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
val name = intentToClassName(intent)
stopService(name)
}
@@ -1,67 +0,0 @@
package xyz.nulldev.androidcompat.util
import android.content.Context
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
import xyz.nulldev.androidcompat.io.AndroidFiles
import xyz.nulldev.androidcompat.pm.PackageController
import xyz.nulldev.androidcompat.service.ServiceSupport
/**
* Helper class to allow access to Kodein from Java
*/
object KodeinGlobalHelper {
/**
* Get the Kodein object
*/
@JvmStatic
fun kodein() = DI.global
/**
* Get a dependency
*/
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
return when (type) {
AndroidFiles::class.java -> {
val instance: AndroidFiles by (kodein ?: kodein()).instance()
instance as T
}
ApplicationInfoImpl::class.java -> {
val instance: ApplicationInfoImpl by (kodein ?: kodein()).instance()
instance as T
}
ServiceSupport::class.java -> {
val instance: ServiceSupport by (kodein ?: kodein()).instance()
instance as T
}
FakePackageManager::class.java -> {
val instance: FakePackageManager by (kodein ?: kodein()).instance()
instance as T
}
PackageController::class.java -> {
val instance: PackageController by (kodein ?: kodein()).instance()
instance as T
}
CustomContext::class.java -> {
val instance: CustomContext by (kodein ?: kodein()).instance()
instance as T
}
Context::class.java -> {
val instance: Context by (kodein ?: kodein()).instance()
instance as T
}
else -> throw IllegalArgumentException("Kodein instance not found")
}
}
@JvmStatic
fun <T : Any> instance(type: Class<T>): T {
return instance(type, null)
}
}
@@ -0,0 +1,27 @@
package xyz.nulldev.androidcompat.util
import org.koin.core.Koin
import org.koin.mp.KoinPlatformTools
/**
* Helper class to allow access to Kodein from Java
*/
object KoinGlobalHelper {
/**
* Get the Kodein object
*/
@JvmStatic
fun koin() = KoinPlatformTools.defaultContext().get()
/**
* Get a dependency
*/
@JvmStatic
fun <T : Any> instance(
type: Class<T>,
koin: Koin? = null,
): T = (koin ?: koin()).get(type.kotlin)
@JvmStatic
fun <T : Any> instance(type: Class<T>): T = instance(type, null)
}
@@ -1,4 +1,4 @@
package suwayomi.tachidesk.manga.impl.util.storage
package xyz.nulldev.androidcompat.util
/*
* Copyright (C) Contributors to the Suwayomi project
@@ -24,5 +24,7 @@ import java.net.URI
* Utilites to convert between Java and Android Uris.
*/
fun Uri.java() = URI(this.toString())
fun Uri.file() = File(this.path)
fun URI.android() = Uri.parse(this.toString())!!
@@ -0,0 +1,96 @@
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 = acceptCookie
override fun setAcceptThirdPartyCookies(
webview: WebView?,
accept: Boolean,
) {
acceptThirdPartyCookies = accept
}
override fun acceptThirdPartyCookies(webview: WebView?): Boolean = 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 = cookieHandler.cookieStore.cookies.isNotEmpty()
override fun flush() {}
override fun allowFileSchemeCookiesImpl(): Boolean = allowFileSchemeCookies
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
allowFileSchemeCookies = acceptCookie
}
}
+2 -2
View File
@@ -2,10 +2,10 @@
## TL;DR
- N/A
## Tachidesk-Server Changelog
## Suwayomi-Server Changelog
- N/A
## Tachidesk-WebUI Changelog
## Suwayomi-WebUI Changelog
- N/A
+1335 -255
View File
File diff suppressed because it is too large Load Diff
+47 -17
View File
@@ -1,31 +1,61 @@
# Contributing
## Where should I start?
Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
Checkout [This Kanban Board](https://github.com/Suwayomi/Suwayomi-Server/projects/1) to see the rough development roadmap.
**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.
**Note 2:** Your pull request will be squashed into a single commit.
### Important notes
- 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.
- 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
- Porting Tachiyomi and covering it's features
- Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159)
- Generally rejecting features that Tachiyomi(main app) doesn't have,
- Porting Mihon (Tachiyomi) and covering its features
- Syncing with Mihon (Tachiyomi), [main issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159)
- Generally rejecting features that Mihon (Tachiyomi) (main app) doesn't have,
- 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
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) should
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) should
- be responsive
- support both desktop and mobile form factors well
## How does Tachidesk-Server work?
## How does Suwayomi-Server work?
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 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/Tachidesk-WebUI
1. **Server:** contains the implementation of [Mihon (Tachiyomi)'s source library](https://github.com/mihonapp/mihon/tree/main/source-api) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a GraphQL 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
### API
#### GraphQL
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?
This structure is chosen to
- Achieve the maximum multi-platform-ness
- 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 Tachidesk
- Gives the ability to access Suwayomi-Server from a remote client e.g., your phone, tablet or smart TV
- Ease development of user interfaces for Suwayomi
## Building from source
### Prerequisites
@@ -33,14 +63,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)
### 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/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle)
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
### 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/Tachidesk-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/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
### 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/Tachidesk-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/Suwayomi-Server-vX.Y.Z-rxxx-winXX.zip`.
## Running in development mode
run `./gradlew :server:run --stacktrace` to run the server
+96 -80
View File
@@ -1,99 +1,104 @@
| Build | Stable | Preview | Support Server |
|-------|----------|---------|---------|
| ![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) |
| 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) |
## Table of Content
- [What is Tachidesk?](#what-is-tachidesk)
- [Tachidesk client projects](#tachidesk-client-projects)
* [Is this application usable? Should I test it?](#is-this-application-usable-should-i-test-it)
- [What is Suwayomi?](#what-is-suwayomi)
- [Features](#Features)
- [Suwayomi client projects](#Suwayomi-client-projects)
- [Downloading and Running the app](#downloading-and-running-the-app)
* [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
- [Launcher Scripts](#launcher-scripts)
+ [Windows](#windows)
+ [macOS](#macos)
+ [GNU/Linux](#gnulinux)
* [Other methods of getting Tachidesk](#other-methods-of-getting-tachidesk)
* [Other methods of getting Suwayomi](#other-methods-of-getting-suwayomi)
+ [Arch Linux](#arch-linux)
+ [Ubuntu-based distributions](#ubuntu-based-distributions)
+ [Docker](#docker)
* [Advanced Methods](#advanced-methods)
+ [Running the jar release directly](#running-the-jar-release-directly)
+ [Using Tachidesk Remotely](#using-tachidesk-remotely)
- [Syncing With Tachiyomi](#syncing-with-tachiyomi)
+ [Using Suwayomi Remotely](#using-suwayomi-remotely)
- [Syncing With Mihon (Tachiyomi)](#syncing-with-mihon-tachiyomi)
- [Troubleshooting and Support](#troubleshooting-and-support)
- [Contributing and Technical info](#contributing-and-technical-info)
- [Credit](#credit)
- [License](#license)
<!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ -->
# What is Tachidesk?
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
# What is Suwayomi?
<img src="https://github.com/Suwayomi/Suwayomi-Server/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 [Mihon (Tachiyomi)](https://mihon.app/).
Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
Suwayomi is an independent Mihon (Tachiyomi) compatible software and is **not a Fork of** Mihon (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-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.
Ability to sync with Tachiyomi is a planned feature, for more info look [here](#syncing-with-tachiyomi).
You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look [here](#syncing-with-mihon-tachiyomi).
# Tachidesk client projects
**You need a client/user interface app as a front-end for Tachidesk-Server, if you [Directly Download Tachidesk-Server](https://github.com/Suwayomi/Tachidesk-Server/releases/latest) you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.**
## Features
> [!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.
Here's a list of known clients/user interfaces for Tachidesk-Server:
##### Actively Developed Cients
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server ships with by default.
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Tachidesk-Server. Currently the most advanced.
- [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 with a User Inerface inspired by Tachiyomi.
##### Inctive/Abandoned Cients
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
## Is this application usable? Should I test it?
Here is a list of current features:
- Installing and executing Tachiyomi's Extensions, So you'll get the same sources
- A library to save your mangas and categories to put them into
- Installing and executing Mihon (Tachiyomi)'s Extensions, So you'll get the same sources
- Searching and browsing installed sources
- A library to save your mangas and categories to put them into
- Automated library updates to check for new chapters
- Automated download of new chapters
- Viewing latest updated chapters
- Ability to download Manga for offline read
- Backup and restore support powered by Tachiyomi-compatible Backups
- Viewing latest updated chapters.
- Backup and restore support powered by Mihon (Tachiyomi)-compatible Backups
- Automated backup creations
- Tracking via [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [MangaUpdates](https://www.mangaupdates.com/)
- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) support to bypass Cloudflare protection
- Automated WebUI updates (supports the default WebUI and VUI)
**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.
# Suwayomi client projects
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
Here's a list of known clients/user interfaces for Suwayomi-Server (checkout the respective GitHub repository for their features):
##### Actively Developed Clients
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web front-end that Suwayomi-Server ships with by default.
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A Suwayomi-Server preview focused web frontend built with svelte
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin.
##### Inactive Clients (functional but outdated)
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-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 Mihon (Tachiyomi).
##### Abandoned Clients (functionality unknown)
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
# Downloading and Running the app
## Using Operating System Specific Bundles
To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
To facilitate the use of Suwayomi we provide bundle releases that include The Java Runtime Environment, ElectronJS and the Suwayomi-Launcher.
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.
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
### Windows
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).
Download the latest `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).
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
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1 and newer) 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).
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).
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
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).
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).
`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
### 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), an example compose file can also be found there. By default, the server will be running on http://localhost:4567 open this url in your browser.
## Other methods of getting Tachidesk
### Arch Linux
You can install Tachidesk from the AUR:
You can install Suwayomi from the AUR:
```
yay -S tachidesk
```
@@ -101,60 +106,67 @@ yay -S tachidesk
### Debian/Ubuntu
Download the latest deb package from the release section or Install from the MPR
```
git clone https://mpr.makedeb.org/tachidesk-server.git
cd tachidesk-server
git clone https://mpr.makedeb.org/suwayomi-server.git
cd suwayomi-server
makedeb -si
```
### Ubuntu
```
sudo add-apt-repository ppa:suwayomi/tachidesk-server
sudo add-apt-repository ppa:suwayomi/suwayomi-server
sudo apt update
sudo apt install tachidesk-server
sudo apt install suwayomi-server
```
### Docker
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.
### NixOS
You can deploy Suwayomi on NixOS using the module `services.suwayomi-server` in your configuration:
Install from the command line:
```
$ docker pull ghcr.io/suwayomi/tachidesk
```
Run Container from the command line:
```
$ docker run -p 4567:4567 ghcr.io/suwayomi/tachidesk
{
services.suwayomi-server = {
enable = true;
};
}
```
For more information, see [the NixOS manual](https://nixos.org/manual/nixos/stable/#module-services-suwayomi-server).
You can also directly use the package from [nixpkgs](https://search.nixos.org/packages?channel=unstable&type=packages&query=suwayomi-server).
## Advanced Methods
### Running the jar release directly
In order to run the app you need the following:
- The jar release of Tachidesk-Server
- The Java Runtime Environment(JRE) 8 or newer
- The jar release of Suwayomi-Server
- The Java Runtime Environment(JRE) 21 or newer
- A Browser like Google Chrome, Firefox, Edge, etc.
- ElectronJS (optional)
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
Download the latest `.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).
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.
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.
### Using Tachidesk Remotely
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.
### Using Suwayomi 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.
Check out [this wiki page](https://github.com/Suwayomi/Tachidesk-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Tachidesk-Server.
Check out [this wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Suwayomi-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
### The Tachidesk extension
- You can install the `Tachidesk` extension inside tachiyomi.
- The extension will load Tachidesk library.
- By manipulating filters you can browse your categories.
## Syncing With Mihon (Tachiyomi)
### The Suwayomi extension and tracker
- You can install the `Suwayomi` extension inside Mihon (Tachiyomi).
- The extension will load your Suwayomi library.
- By manipulating extension search filters you can browse your categories.
- You can enable the Suwayomi tracker to track reading progress with your Suwayomi server.
- Note: to sync from
- Mihon (Tachiyomi) to Suwayomi: Mihon (Tachiyomi) automatically updates the chapters read status when it's updating the tracker (e.g. while reading)
- Suwayomi to Mihon (Tachiyomi): To sync Mihon (Tachiyomi) with Suwayomi, you have to open the manga's track information, then, Mihon (Tachiyomi) will automatically update its chapter list with the state from Suwayomi
### Other methods
Checkout [this issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159) for tracking progress.
Checkout [this issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159) for tracking progress.
## Troubleshooting and Support
See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
See [this troubleshooting wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Troubleshooting).
## Contributing and Technical info
See [CONTRIBUTING.md](./CONTRIBUTING.md).
@@ -164,7 +176,7 @@ This project is a spiritual successor of [TachiWeb-Server](https://github.com/Ta
The `AndroidCompat` module was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0` and `Copyright 2019 Andy Bao and contributors`.
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0` and `Copyright 2015 Javier Tomás`.
Parts of [Mihon (Tachiyomi)](https://github.com/mihonapp/mihon) is adopted into this codebase, also licensed under `Apache License Version 2.0` and `Copyright 2015 Javier Tomás`.
You can obtain a copy of `Apache License Version 2.0` from http://www.apache.org/licenses/LICENSE-2.0
@@ -177,3 +189,7 @@ 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
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/.
## Disclaimer
The developer of this application does not have any affiliation with the content providers available.
+21 -19
View File
@@ -1,12 +1,12 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jmailen.gradle.kotlinter.tasks.LintTask
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlinter)
alias(libs.plugins.ktlint)
alias(libs.plugins.buildconfig) apply false
alias(libs.plugins.download)
}
@@ -19,7 +19,7 @@ allprojects {
repositories {
mavenCentral()
google()
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
maven("https://github.com/Suwayomi/Suwayomi-Server/raw/android-jar/")
maven("https://jitpack.io")
}
}
@@ -27,25 +27,27 @@ allprojects {
subprojects {
plugins.withType<JavaPlugin> {
extensions.configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}
plugins.withType<KtlintPlugin> {
extensions.configure<KtlintExtension>("ktlint") {
version.set(libs.versions.ktlint.get())
filter {
exclude("**/generated/**")
}
}
}
tasks {
withType<KotlinJvmCompile> {
dependsOn("formatKotlin")
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
dependsOn("ktlintFormat")
compilerOptions {
jvmTarget = JvmTarget.JVM_21
freeCompilerArgs.add("-Xcontext-receivers")
}
}
withType<LintTask> {
source(files("src/kotlin"))
}
withType<FormatTask> {
source(files("src/kotlin"))
}
}
}
}
+2 -2
View File
@@ -7,5 +7,5 @@ repositories {
}
dependencies {
implementation("net.lingala.zip4j:zip4j:2.9.0")
}
implementation(libs.zip4j)
}
+9
View File
@@ -0,0 +1,9 @@
rootProject.name = "buildSrc"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
+19 -15
View File
@@ -10,22 +10,26 @@ import java.io.BufferedReader
const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.7.0"
val getTachideskVersion = { "v2.0.${getCommitCount()}" }
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r983"
val webUIRevisionTag = "r2467"
// counts commits on the master branch
val tachideskRevision = runCatching {
System.getenv("ProductRevision") ?: ProcessBuilder()
.command("git", "rev-list", "HEAD", "--count")
.start()
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
private val getCommitCount = {
runCatching {
ProcessBuilder()
.command("git", "rev-list", "HEAD", "--count")
.start()
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
output.trim()
}
process.destroy()
"r" + output.trim()
}
}.getOrDefault("r0")
}.getOrDefault("0")
}
// counts commits on the current checked out branch
val getTachideskRevision = { "r${getCommitCount()}" }
+71 -48
View File
@@ -1,16 +1,19 @@
[versions]
kotlin = "1.8.0"
coroutines = "1.6.4"
serialization = "1.4.1"
okhttp = "5.0.0-alpha.11" # Major version is locked by Tachiyomi extensions
javalin = "4.6.6" # 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 = "v59"
rhino = "1.7.14"
settings = "1.0.0-RC"
twelvemonkeys = "3.9.4"
playwright = "1.28.0"
kotlin = "2.1.20"
coroutines = "1.10.1"
serialization = "1.8.0"
okhttp = "5.0.0-alpha.14" # Major version is locked by Tachiyomi extensions
javalin = "6.5.0"
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.59.0"
dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed
polyglot = "24.2.0"
settings = "1.3.0"
twelvemonkeys = "3.12.0"
graphqlkotlin = "8.4.0"
xmlserialization = "0.90.3"
ktlint = "1.5.0"
koin = "4.0.2"
[libraries]
# Kotlin
@@ -25,18 +28,22 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
# 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", version.ref = "xmlserialization" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging
slf4japi = "org.slf4j:slf4j-api:2.0.6"
logback = "ch.qos.logback:logback-classic:1.3.5"
kotlinlogging = "io.github.microutils:kotlin-logging:3.0.5"
slf4japi = "org.slf4j:slf4j-api:2.0.17"
logback = "ch.qos.logback:logback-classic:1.5.18"
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.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" }
okio = "com.squareup.okio:okio:3.3.0"
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.10.2"
# Javalin api
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
@@ -45,6 +52,12 @@ jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", ver
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-java-core = "com.graphql-java:graphql-java:22.3" # Major version locked by graphql-kotlin
graphql-java-scalars = "com.graphql-java:graphql-java-extended-scalars:22.0"
# Exposed ORM
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
@@ -53,24 +66,24 @@ exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version
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"
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.7.0"
# Dependency Injection
kodein = "org.kodein.di:kodein-di-conf-jvm:7.15.0"
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
# 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"
systemtray-core = "com.dorkbox:SystemTray:4.4"
systemtray-utils = "com.dorkbox:Utilities:1.46" # version locked by SystemTray
systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
# dependencies of Tachiyomi extensions
injekt = "com.github.inorichi.injekt:injekt-core:65b0440"
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
rxjava = "io.reactivex:rxjava:1.3.8"
jsoup = "org.jsoup:jsoup:1.15.3"
jsoup = "org.jsoup:jsoup:1.19.1"
# Config
config = "com.typesafe:config:1.4.2"
config4k = "io.github.config4k:config4k:0.5.0"
config = "com.typesafe:config:1.4.3"
config4k = "io.github.config4k:config4k:0.7.0"
# Sort
sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
@@ -79,41 +92,40 @@ sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
android-stubs = "com.github.Suwayomi:android-jar:1.0.0"
# Asm modificiation
asm = "org.ow2.asm:asm:9.4" # version locked by Dex2Jar
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"
apksig = "com.android.tools.build:apksig:8.9.0"
# Xml
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
# Disk & File
appdirs = "net.harawata:appdirs:1.2.1"
zip4j = "net.lingala.zip4j:zip4j:2.11.2"
junrar = "com.github.junrar:junrar:7.5.3"
# CloudflareInterceptor
playwright = { module = "com.microsoft.playwright:playwright", version.ref = "playwright" }
appdirs = "net.harawata:appdirs:1.4.0"
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
commonscompress = "org.apache.commons:commons-compress:1.27.1"
junrar = "com.github.junrar:junrar:7.5.5"
# AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.72"
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80"
# AndroidX annotations
android-annotations = "androidx.annotation:annotation:1.5.0"
android-annotations = "androidx.annotation:annotation:1.9.1"
# 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
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
polyglot-graaljs = { module = "org.graalvm.polyglot:js-community", version.ref = "polyglot" }
# 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:72.1"
icu4j = "com.ibm.icu:icu4j:77.1"
# Image Decoding implementation provider
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
@@ -125,7 +137,16 @@ twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg"
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
# Testing
mockk = "io.mockk:mockk:1.13.2"
mockk = "io.mockk:mockk:1.13.17"
# cron scheduler
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
# cron-utils
cronUtils = "com.cronutils:cron-utils:9.2.1"
# lint - used for renovate to update ktlint version
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
[plugins]
# Kotlin
@@ -133,16 +154,16 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
# Linter
kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0"}
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.2.0"}
# Build config
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"}
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.5.4"}
# Download
download = { id = "de.undercouch.download", version = "5.3.0"}
download = { id = "de.undercouch.download", version = "5.6.0"}
# ShadowJar
shadowjar = { id = "com.github.johnrengelman.shadow", version = "7.1.2"}
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
[bundles]
shared = [
@@ -151,8 +172,9 @@ shared = [
"coroutines-core",
"coroutines-jdk8",
"serialization-json",
"serialization-json-okio",
"serialization-protobuf",
"kodein",
"koin-core",
"slf4japi",
"logback",
"kotlinlogging",
@@ -176,10 +198,11 @@ okhttp = [
"okhttp-core",
"okhttp-logging",
"okhttp-dnsoverhttps",
"okhttp-brotli",
]
javalin = [
"javalin-core",
"javalin-openapi",
#"javalin-openapi",
]
jackson = [
"jackson-databind",
@@ -197,9 +220,9 @@ systemtray = [
"systemtray-utils",
"systemtray-desktop"
]
rhino = [
"rhino-runtime",
"rhino-engine",
polyglot = [
"polyglot-core",
"polyglot-graaljs",
]
settings = [
"settings-core",
Binary file not shown.
+3 -1
View File
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+24 -13
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
Vendored
+13 -10
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"semanticCommits": "disabled",
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"scripts/bundler.sh"
],
"matchStrings": [
"JRE_RELEASE=[\"'](?<currentValue>.+?)[\"']\\s+"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "adoptium/temurin21-binaries",
"versioningTemplate": "regex:^jdk-?(?<major>\\d+).(?<minor>\\d+).+?(?<patch>[\\d+]+)$"
}
]
}
+92 -97
View File
@@ -26,18 +26,18 @@ main() {
set -- "${POSITIONAL_ARGS[@]}"
OS="$1"
PLAYWRIGHT_VERSION="$(cat gradle/libs.versions.toml | grep -oP "playwright = \"\K([0-9\.]*)(?=\")")"
PLAYWRIGHT_REVISION="$(curl --silent "https://raw.githubusercontent.com/microsoft/playwright/v$PLAYWRIGHT_VERSION/packages/playwright-core/browsers.json" 2>&1 | grep -ozP "\"name\": \"chromium\",\n *\"revision\": \"\K[0-9]*")"
JAR="$(ls server/build/*.jar | tail -n1)"
RELEASE_NAME="$(echo "${JAR%.*}" | xargs basename)-$OS"
RELEASE_VERSION="$(tmp="${JAR%-*}"; echo "${tmp##*-}" | tr -d v)"
RELEASE_VERSION=$(echo "$JAR" | grep -oP "v\K[0-9]+\.[0-9]+\.[0-9]+")
#RELEASE_REVISION_NUMBER="$(tmp="${JAR%.*}" && echo "${tmp##*-}" | tr -d r)"
local electron_version="v14.0.0"
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"
@@ -51,84 +51,64 @@ main() {
move_release_to_output_dir
;;
linux-x64)
JRE="OpenJDK8U-jre_x64_linux_hotspot_8u302b08.tar.gz"
JRE_RELEASE="jdk8u302-b08"
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin21-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
PLAYWRIGHT_PLATFORM="linux"
setup_playwright
download_electron
setup_jre
tree "$RELEASE_NAME"
RELEASE="$RELEASE_NAME.tar.gz"
make_linux_bundle
move_release_to_output_dir
;;
macOS-x64)
JRE="OpenJDK8U-jre_x64_mac_hotspot_8u302b08.tar.gz"
JRE_RELEASE="jdk8u302-b08"
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin21-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
download_electron
setup_jre
tree "$RELEASE_NAME"
PLAYWRIGHT_PLATFORM="mac"
setup_playwright
RELEASE="$RELEASE_NAME.zip"
RELEASE="$RELEASE_NAME.tar.gz"
make_macos_bundle
move_release_to_output_dir
;;
macOS-arm64)
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_DIR="$JRE_RELEASE/zulu-8.jre"
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_aarch64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-darwin-arm64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
download_electron
setup_jre
tree "$RELEASE_NAME"
PLAYWRIGHT_PLATFORM="mac-arm64"
setup_playwright
RELEASE="$RELEASE_NAME.zip"
RELEASE="$RELEASE_NAME.tar.gz"
make_macos_bundle
move_release_to_output_dir
;;
windows-x86)
JRE="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
JRE_RELEASE="jdk8u292-b10"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/AdoptOpenJDK/openjdk8-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
PLAYWRIGHT_PLATFORM="win64"
setup_playwright
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)
JRE="OpenJDK8U-jre_x64_windows_hotspot_8u302b08.zip"
JRE_RELEASE="jdk8u302-b08"
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').zip"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin21-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
PLAYWRIGHT_PLATFORM="win64"
setup_playwright
download_electron
setup_jre
tree "$RELEASE_NAME"
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
@@ -152,66 +132,81 @@ move_release_to_output_dir() {
mv "$RELEASE" "$OUTPUT_DIR/"
}
download_jre_and_electron() {
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
fi
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_electron() {
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/"
tree
}
setup_jre() {
if [ -d "jre" ]; then
chmod +x ./jre/bin/java
chmod +x ./jre/lib/jspawnhelper
mv "jre" "$RELEASE_NAME/jre"
else
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
fi
local ext="${JRE##*.}"
if [ "$ext" = "zip" ]; then
unzip "$JRE"
else
tar xvf "$JRE"
fi
mv "$JRE_DIR" "$RELEASE_NAME/jre"
fi
}
copy_linux_package_assets_to() {
local output_dir
output_dir="$(readlink -e "$1" || exit 1)"
cp "scripts/resources/pkg/tachidesk-server-browser-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server-debug-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server-electron-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server.desktop" "$output_dir/"
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.png" \
"$output_dir/tachidesk-server.png"
cp "server/src/main/resources/icon/faviconlogo-128.png" \
"$output_dir/suwayomi-server.png"
}
make_linux_bundle() {
cp "$JAR" "$RELEASE_NAME/Tachidesk-Server.jar"
cp "scripts/resources/tachidesk-server-browser-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/tachidesk-server-debug-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/tachidesk-server-electron-launcher.sh" "$RELEASE_NAME/"
mkdir "$RELEASE_NAME/bin"
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/Tachidesk-Server.jar"
cp "scripts/resources/Tachidesk Browser Launcher.command" "$RELEASE_NAME/"
cp "scripts/resources/Tachidesk Debug Launcher.command" "$RELEASE_NAME/"
cp "scripts/resources/Tachidesk Electron Launcher.command" "$RELEASE_NAME/"
mkdir "$RELEASE_NAME/bin"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.command" "$RELEASE_NAME/"
zip -9 -r "$RELEASE" "$RELEASE_NAME/"
tar -I "gzip -9" -cvf "$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="tachidesk-server-$RELEASE_VERSION"
local source_dir="suwayomi-server-$RELEASE_VERSION"
#behind $RELEASE_VERSION is underscore "_"
local upstream_source="tachidesk-server_$RELEASE_VERSION.orig.tar.gz"
local upstream_source="suwayomi-server_$RELEASE_VERSION.orig.tar.gz"
mkdir "$RELEASE_NAME/$source_dir/"
cp "$JAR" "$RELEASE_NAME/$source_dir/Tachidesk-Server.jar"
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"
@@ -219,12 +214,13 @@ make_deb_package() {
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="tachidesk-server_$RELEASE_VERSION-1_all.deb"
local deb="suwayomi-server_$RELEASE_VERSION-1_all.deb"
mv "$RELEASE_NAME/$deb" "$RELEASE"
}
@@ -257,16 +253,16 @@ make_windows_bundle() {
#WINEARCH=win32 wine "$rcedit" "$RELEASE_NAME/electron/electron.exe" \
# --set-icon "$icon"
cp "$JAR" "$RELEASE_NAME/Tachidesk-Server.jar"
cp "scripts/resources/Tachidesk Browser Launcher.bat" "$RELEASE_NAME"
cp "scripts/resources/Tachidesk Debug Launcher.bat" "$RELEASE_NAME"
cp "scripts/resources/Tachidesk Electron Launcher.bat" "$RELEASE_NAME"
mkdir "$RELEASE_NAME/bin"
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
@@ -277,17 +273,16 @@ make_windows_package() {
| 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/tachidesk-server-$arch.wxs" \
"$RELEASE_NAME/jre.wxs" "$RELEASE_NAME/electron.wxs" -o "$RELEASE"
}
setup_playwright() {
mkdir "$RELEASE_NAME/bin"
curl -L "https://playwright.azureedge.net/builds/chromium/$PLAYWRIGHT_REVISION/chromium-$PLAYWRIGHT_PLATFORM.zip" -o "$RELEASE_NAME/bin/chromium.zip"
-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
+1
View File
@@ -0,0 +1 @@
start "" jre/bin/javaw -jar Suwayomi-Launcher.jar
+3
View File
@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/bin/java -jar Suwayomi-Launcher.jar
@@ -1 +0,0 @@
start "" jre/bin/javaw -jar Tachidesk-Server.jar
@@ -1,3 +0,0 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -jar Tachidesk-Server.jar
@@ -1,7 +0,0 @@
:: cleaner output
@echo off
jre\bin\java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk-Server.jar
:: Prevent cmd from closing when Tachidesk crashes
pause
@@ -1,3 +0,0 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk-Server.jar
@@ -1 +0,0 @@
jre\bin\javaw "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron.exe" -jar Tachidesk-Server.jar
@@ -1,3 +0,0 @@
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-Server.jar
+2 -2
View File
@@ -1,5 +1,5 @@
tachidesk-server ($pkgver-$pkgrel) unstable; urgency=medium
suwayomi-server ($pkgver-$pkgrel) unstable; urgency=medium
* See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server
* See CHANGELOG.md on https://github.com/Suwayomi/Suwayomi-Server
-- Mahor1221 <mahor1221@pm.me> Fri, 14 Jan 2022 00:00:00 +0000
+5 -5
View File
@@ -1,14 +1,14 @@
Source: tachidesk-server
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/Tachidesk-Server
Homepage: https://github.com/Suwayomi/Suwayomi-Server
Package: tachidesk-server
Package: suwayomi-server
Architecture: all
Depends: ${misc:Depends}, java8-runtime-headless, libc++-dev
Depends: ${misc:Depends}, openjdk-21-jre, libc++-dev
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.
Suwayomi is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.
+2 -2
View File
@@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: tachidesk-server
Upstream-Name: suwayomi-server
Upstream-Contact: https://discord.gg/DDZdqZWaHA
Source: https://github.com/Suwayomi/Tachidesk-Server
Source: https://github.com/Suwayomi/Suwayomi-Server
Files: *
Copyright: Contributors to the Suwayomi project
+11 -10
View File
@@ -1,12 +1,13 @@
#!/usr/bin/dh-exec
Tachidesk-Server.jar usr/share/java/tachidesk-server/
tachidesk-server.png usr/share/pixmaps/
tachidesk-server.desktop usr/share/applications/
tachidesk-server.service usr/lib/systemd/system/
tachidesk-server.sysusers => usr/lib/sysusers.d/tachidesk-server.conf
tachidesk-server.tmpfiles => usr/lib/tmpfiles.d/tachidesk-server.conf
tachidesk-server.conf => etc/tachidesk/server.conf
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
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
@@ -1 +1 @@
tachidesk-server.png
suwayomi-server.png
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="174c8f36-0bec-4585-9ddd-469c3d889dc1"
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>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<!-- 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>
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallValidate" />
</InstallExecuteSequence>
<!-- 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>
@@ -1,86 +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="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-Server.jar" Source="$(var.SourceDir)/Tachidesk-Server.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>
@@ -1,82 +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="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-Server.jar" Source="$(var.SourceDir)/Tachidesk-Server.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>
@@ -0,0 +1,8 @@
[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;
@@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/Suwayomi-Launcher.jar
@@ -0,0 +1,8 @@
[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;
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
@@ -1,4 +1,4 @@
TACHIDESK_ROOT_DIR="/var/lib/tachidesk"
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.
@@ -5,12 +5,12 @@ After=network-online.target
[Service]
Type=simple
User=tachidesk
Group=tachidesk
SyslogIdentifier=tachidesk
User=suwayomi-server
Group=suwayomi-server
SyslogIdentifier=suwayomi-server
EnvironmentFile=/etc/tachidesk/server.conf
ExecStart=/usr/bin/java $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}" -jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar
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
@@ -0,0 +1,2 @@
#Type Name ID GECOS Home directory Shell
u suwayomi-server - "Suwayomi Manga Server" /var/lib/suwayomi
@@ -0,0 +1,2 @@
#Type Path Mode User Group Age Argument
d /var/lib/suwayomi 0755 suwayomi-server suwayomi-server
@@ -1,2 +0,0 @@
#Type Name ID GECOS Home directory Shell
u tachidesk - "Tachidesk Manga Server" /var/lib/tachidesk
@@ -1,2 +0,0 @@
#Type Path Mode User Group Age Argument
d /var/lib/tachidesk 0755 tachidesk tachidesk
@@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar
@@ -1,5 +0,0 @@
#!/bin/sh
exec /usr/bin/java \
-Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true \
-jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar
@@ -1,12 +0,0 @@
#!/bin/sh
if [ ! -f /usr/bin/electron ]; then
echo "Electron executable was not found!
In order to run this launcher, you need Electron installed."
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
@@ -1,8 +0,0 @@
[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;

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