Compare commits
602 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70402a6d3a | |||
| 5c4143224a | |||
| f943e924f7 | |||
| 7d2f542f8a | |||
| fb1f88e971 | |||
| f566f13423 | |||
| 0f88baf1c1 | |||
| a04cbcd814 | |||
| a17d6a2ea4 | |||
| 08f49e0ac4 | |||
| 7787dd1ecc | |||
| 3d0765d4ab | |||
| b51651ace4 | |||
| 568fa56d59 | |||
| 50dee9251c | |||
| e575aaf4fb | |||
| bdd5caae1a | |||
| 3af7de3460 | |||
| caa219f8d6 | |||
| afabaccf1d | |||
| 53cc73701c | |||
| c69b954ffd | |||
| d05c447fe4 | |||
| 5810a24cb0 | |||
| e04c6a9f4d | |||
| e3a9f7af42 | |||
| 5eb58a73ee | |||
| 68ad1f72ce | |||
| 704a52d943 | |||
| 67ec9ccc4e | |||
| c7112ec67f | |||
| 1e1e2034eb | |||
| 5f316c6f44 | |||
| 0c39718f82 | |||
| 8a5ac4a0af | |||
| 492d5f5e84 | |||
| 5a3621fe39 | |||
| fb862e23e5 | |||
| 3d69348301 | |||
| 30787846a2 | |||
| 345ca27f85 | |||
| b7a6d6cae8 | |||
| dadb686514 | |||
| 75f635a28b | |||
| f2bd5b8149 | |||
| 6ed4c79ca4 | |||
| 333f954919 | |||
| 2494d0821d | |||
| e349d0cef3 | |||
| eddad2ba89 | |||
| bfaf88afd6 | |||
| cd59aed8c7 | |||
| f18ca5811f | |||
| 1ed9bcf7c8 | |||
| 29a79ab079 | |||
| 7c03c73419 | |||
| 74f3b9b609 | |||
| a3953d530e | |||
| 2280e8c725 | |||
| b327df732c | |||
| 21d7cf5d6a | |||
| 5b64bdc5b7 | |||
| a3a25b6263 | |||
| c7611c8024 | |||
| f08170504c | |||
| 863dccb5ea | |||
| fc8bb10ca3 | |||
| d06c3586fd | |||
| 395989b528 | |||
| a325440f24 | |||
| 14072bb5a0 | |||
| 7fc33ba8db | |||
| 47e51b6615 | |||
| 857562eaff | |||
| bace854b50 | |||
| e4a404472d | |||
| 7bfa215b4c | |||
| ab7af4b80b | |||
| 2c7ebd8ece | |||
| c96da79058 | |||
| 8f09ebacf5 | |||
| e21f3b9c75 | |||
| 37eeef06e2 | |||
| b7fe56687c | |||
| 60565729ca | |||
| 36f4e1c340 | |||
| abc2a5214b | |||
| a29010e0d7 | |||
| db99ab66ae | |||
| 84cc73c149 | |||
| 10a29cab33 | |||
| 849e2f103a | |||
| 6c22fe193a | |||
| e69dbbf418 | |||
| dfa59a1691 | |||
| 5023e96301 | |||
| 224c24ee9f | |||
| e3b154cf9e | |||
| d249867c4c | |||
| b56045e984 | |||
| 3777cc646e | |||
| aa5a1083d0 | |||
| 2ae5e0742e | |||
| e5e875c54a | |||
| 1a99ec76e4 | |||
| 1b122d1157 | |||
| 77f2f8cc18 | |||
| f0a99980b6 | |||
| b0d43ffe69 | |||
| 16cb0184a4 | |||
| f211a33ea3 | |||
| 440c815189 | |||
| 25829aacfd | |||
| 700a739f95 | |||
| d9620bec05 | |||
| 4b6c51b1f8 | |||
| bd02edf0b1 | |||
| 5c7123a997 | |||
| c17e3bd04f | |||
| 994ae97256 | |||
| 781428a690 | |||
| c23ac5faa8 | |||
| e8d41f83c2 | |||
| 921a0a3361 | |||
| dda5a2df93 | |||
| 155f9f107d | |||
| 24f68b8f1a | |||
| 0ffbe194fa | |||
| 0b41e2b72b | |||
| ef07b9b4ce | |||
| f3999cf2d9 | |||
| 1729847937 | |||
| 37bff6c76c | |||
| 4ef32d8037 | |||
| d2f6a33f0a | |||
| 31d9903251 | |||
| e97642d92a | |||
| c49fc0ff5f | |||
| deb2ab1ff4 | |||
| 23466cf853 | |||
| 16b34f874d | |||
| 0e0d08ae5a | |||
| 986b4c2c27 | |||
| 0bf9ccfcbd | |||
| 5e8c47928d | |||
| ffae7f911f | |||
| e37fdf6d79 | |||
| b359116745 | |||
| 60073aace3 | |||
| 874b13fa14 | |||
| b146d1024b | |||
| 332e95c021 | |||
| 1f68141df5 | |||
| dd731cd306 | |||
| 38d8d03cae | |||
| ec7d840f37 | |||
| 2813dbb897 | |||
| 77d1402b8a | |||
| 08e8a9d105 | |||
| 71661f70b6 | |||
| ac1e79ba83 | |||
| d082809776 | |||
| a458a696db | |||
| 75786a91b0 | |||
| 6ddb5db57b | |||
| 4f70cc9283 | |||
| 23b643d637 | |||
| fdfc256c4d | |||
| fba56c1b75 | |||
| 4743bfacf7 | |||
| 2356537f7c | |||
| fa071aee84 | |||
| c00ca23a8b | |||
| 733b017936 | |||
| 4147f2e368 | |||
| 154b9992eb | |||
| 88b881b043 | |||
| 5d1491fb8c | |||
| 3a33196cf1 | |||
| fa8e0478da | |||
| 7e7e069244 | |||
| 18e0d34af0 | |||
| 3fe3f35483 | |||
| cf8e274883 | |||
| 10dee8b345 | |||
| ae8d30593f | |||
| 9cde46b5da | |||
| 8e61632155 | |||
| e2c4b4cb57 | |||
| 326da504ea | |||
| c5874a3f10 | |||
| 02802fab97 | |||
| 29dea10be2 | |||
| 6bc36193dc | |||
| 81e123388e | |||
| 8ebd7869a5 | |||
| 7a2f5f13f1 | |||
| 25d7dad39f | |||
| c8f8795920 | |||
| 84206a7074 | |||
| 6fd8b36dca | |||
| d1500baae1 | |||
| 045801dd1a | |||
| 14a2cbc793 | |||
| fd385017df | |||
| 9b05954cf2 | |||
| 6aaf636069 | |||
| d30e89e5ec | |||
| 7acc745478 | |||
| 5a9a2d816e | |||
| 105f11ed02 | |||
| 3021437a05 | |||
| 439602fc03 | |||
| 34e13b9589 | |||
| 2aab4ae918 | |||
| 7ef67671a4 | |||
| e8df84416c | |||
| be930bb68b | |||
| db52948865 | |||
| d2a72526f6 | |||
| 0a9f57b32b | |||
| 180f210536 | |||
| c1baa31eed | |||
| cacc97cec7 | |||
| d5691fd81c | |||
| 49dc9fe5f6 | |||
| c0b49c7428 | |||
| fa345af42d | |||
| db3cc786a1 | |||
| fe879ae51d | |||
| 2f55460ffb | |||
| fbc5bd4642 | |||
| 5e0c7d3c9d | |||
| 083996a48d | |||
| 9d38f478e3 | |||
| 57274a0a01 | |||
| b3b56b7fc8 | |||
| 0b690577da | |||
| e9683a3a37 | |||
| f8f67b3eba | |||
| 7b16b082d8 | |||
| 2a783f0d8e | |||
| 42ae32de33 | |||
| cec7ddc486 | |||
| 9c55fc3868 | |||
| 104c5a8d83 | |||
| 7450b16742 | |||
| 3ecd0931a1 | |||
| 2f2a52ae2f | |||
| f464087c30 | |||
| 2364960388 | |||
| 76be4d64cd | |||
| 7d98e8ce47 | |||
| 40831fc681 | |||
| e38e7ccf26 | |||
| 98b9e2f2cf | |||
| 4bf3c12f76 | |||
| bab25f9ad9 | |||
| a62ee8f8c3 | |||
| 5f23691e20 | |||
| 3de9ccc62f | |||
| 1896f7f37b | |||
| 490643dc02 | |||
| 9808976088 | |||
| 5a73068a10 | |||
| 01d5c2540d | |||
| 866b01f865 | |||
| da6a953099 | |||
| bce8d58845 | |||
| 3cfce2db04 | |||
| 327aae5dd9 | |||
| 1bdfde7032 | |||
| 295a0817b0 | |||
| a02dc02d52 | |||
| dc012edf7d | |||
| 1e2eb11c13 | |||
| 3a825f4f25 | |||
| b9ea8c5f74 | |||
| 320d7e2536 | |||
| c200785479 | |||
| 8abb132ad6 | |||
| 8bb2269f36 | |||
| 9d17b26283 | |||
| 5909f15db7 | |||
| 11672ca576 | |||
| e09773def3 | |||
| f6d4432e6f | |||
| 45a6abc5c2 | |||
| dc5e677a38 | |||
| a82549dc17 | |||
| a002e19d9d | |||
| cdf1f98d28 | |||
| 0ff1ebdeb7 | |||
| 17f4a396f8 | |||
| 8aa3cf4368 | |||
| 0136c5e493 | |||
| 8b94b9ee80 | |||
| bed63f19f2 | |||
| e2a6545a84 | |||
| e3d3ec6895 | |||
| 7ba476bd79 | |||
| 2dd41ebd27 | |||
| 038df78171 | |||
| 6e5ff2b508 | |||
| ec8d1e8680 | |||
| 1f0f0c33b7 | |||
| 825940fcac | |||
| 4618834af2 | |||
| 55d968df5e | |||
| 63a078cf7d | |||
| 5304917e53 | |||
| 831b74d2ec | |||
| 1bad9dcd69 | |||
| dd43716851 | |||
| f2e55e95a2 | |||
| 14658a0c4d | |||
| 4195e7056b | |||
| 1d29e8b248 | |||
| b718c718df | |||
| a3601cf1b5 | |||
| 0236a9639b | |||
| 5f4c7454ee | |||
| 773120c96a | |||
| 4b273c6bf9 | |||
| b626aa66ba | |||
| 1dd029559e | |||
| 59cbe5d5bc | |||
| 40d1173653 | |||
| bf6a0aba5d | |||
| 34d8feacdd | |||
| 1ea821584c | |||
| 3d2fee19bb | |||
| 449d12779a | |||
| 6fb6a251e7 | |||
| 4d6220f894 | |||
| fe747bfc52 | |||
| 0c2d038870 | |||
| 4e3f73af75 | |||
| 63e5e1b45f | |||
| 2e1558bd96 | |||
| 0671dee8b2 | |||
| 8f91b8089a | |||
| 009b45f676 | |||
| 8f7d5eb311 | |||
| f3de835ef3 | |||
| fd6662f428 | |||
| fde137b3ed | |||
| a1349aa0e3 | |||
| c9ef5f9b9d | |||
| 8fbf564177 | |||
| ae0b1a818c | |||
| c04cc780b7 | |||
| 71ad1bb6e3 | |||
| c1be77ee9b | |||
| d1fa857ffb | |||
| 93fd81b38b | |||
| 2f116b40b2 | |||
| b884d34bdf | |||
| 309803368b | |||
| 19fc5be8f3 | |||
| c28fac14c0 | |||
| 66e38de29f | |||
| 282cb1d3be | |||
| b741ded595 | |||
| 6b290695fc | |||
| 4e43c554c0 | |||
| 090a72b35f | |||
| 3fcc269df3 | |||
| 9958e0eb34 | |||
| c5269002a2 | |||
| 455a35f8ae | |||
| 0c79f207c3 | |||
| cd16d32a35 | |||
| 1989c1eb48 | |||
| f56856529f | |||
| 52e27a3e39 | |||
| 177c971b52 | |||
| 7a52e19235 | |||
| 5171e509a5 | |||
| 975a3b1828 | |||
| c11887fada | |||
| e043cb5690 | |||
| b2d5354798 | |||
| a211a4143b | |||
| c0df7d314b | |||
| c8a8ce07e2 | |||
| e0e474dfce | |||
| 7591748811 | |||
| 884308690f | |||
| 15bd5b4b7a | |||
| abc3a16ee3 | |||
| bb09ccf3c0 | |||
| ad2ea8095b | |||
| 760d1116a1 | |||
| 47fcf7eb97 | |||
| b0e90c2f63 | |||
| f502884fdd | |||
| 5ed79523d2 | |||
| da5dd70969 | |||
| 68e69085df | |||
| 640ce8f5d7 | |||
| c960cc1ee5 | |||
| 2b2601aa4a | |||
| 99a10ec7db | |||
| 035105adf0 | |||
| f983f0e359 | |||
| 769472b24c | |||
| 8c80ad7575 | |||
| 63db2e6695 | |||
| d6d5e97fbd | |||
| 1ae0a8326e | |||
| 57693fef7b | |||
| 5656016700 | |||
| 90ae180b3e | |||
| 2a3c78d43e | |||
| 11000af718 | |||
| b808121f1d | |||
| addadefeb1 | |||
| 838cd20e57 | |||
| 5b9219522d | |||
| caeb4d273d | |||
| 77cf87c989 | |||
| 50c2dbed5d | |||
| 71a9396952 | |||
| bc3ad75328 | |||
| 077bbc3c38 | |||
| b1b1abad1d | |||
| e6ba2a0066 | |||
| 9b56ef7d82 | |||
| dd442c6653 | |||
| 3044317b09 | |||
| 3a1e1e01dc | |||
| a567701639 | |||
| 1802271358 | |||
| 9e649eef79 | |||
| 1eb4a9c216 | |||
| e3f65d2192 | |||
| bb09cfddb3 | |||
| d383939c9f | |||
| 32dd543562 | |||
| 5a75f26791 | |||
| 95c437efd5 | |||
| ec877f632f | |||
| 8666cbf8bc | |||
| 84b0c26450 | |||
| 64e5bbabb3 | |||
| cc1a15e5ba | |||
| d29e942a72 | |||
| 8d86c88c38 | |||
| c7dc7421aa | |||
| 34ed3e5c68 | |||
| 1a4a8af384 | |||
| 62b1e99bbf | |||
| 1aa3b76934 | |||
| 3e53c50f64 | |||
| 430386bc84 | |||
| 30049e8152 | |||
| 34d9a7a233 | |||
| 183972475b | |||
| fd46727f8e | |||
| f6ce010aa2 | |||
| d0ff30df9f | |||
| 8e449abd67 | |||
| 2986130268 | |||
| 1c0c09f2f2 | |||
| 44100cb5b6 | |||
| cfc6e5cd2a | |||
| c067d14c2c | |||
| aded854a2b | |||
| e79d0b9dd2 | |||
| a0115d88b0 | |||
| 85ec2ed367 | |||
| bf908c4d17 | |||
| f41c5c9428 | |||
| 04837983fa | |||
| 5d484b012c | |||
| 436a8d0585 | |||
| 28cc0a6f84 | |||
| 26cc2f2c96 | |||
| 149107e749 | |||
| a74936c5f5 | |||
| ff8c8913d4 | |||
| 83426e1302 | |||
| 9cd93d467c | |||
| 257f8a5a27 | |||
| 79bab08cae | |||
| 4e699e4f5a | |||
| 1128f40bac | |||
| 53ef836326 | |||
| b8df0e89e5 | |||
| 472bfec6bf | |||
| c1b86cedd2 | |||
| 428c65f075 | |||
| 92ed48f7f6 | |||
| 13e84bc492 | |||
| 0ef86c34b7 | |||
| 7e1a4259d7 | |||
| c842c51fb6 | |||
| 6f2f228e08 | |||
| c78eaa8b96 | |||
| f9606526d2 | |||
| fe4cc9ea2c | |||
| 54d0c05fcc | |||
| 2f7df73a37 | |||
| cf19f3626b | |||
| ff2da5e59b | |||
| e03922e518 | |||
| 893fba5b8c | |||
| c1786f8e24 | |||
| a59f974537 | |||
| 7157e07328 | |||
| 954084bd82 | |||
| 0915ba40f6 | |||
| de30d55bcf | |||
| af1c34fba5 | |||
| 7b7d93786f | |||
| 7c1c504482 | |||
| 33b22fcab6 | |||
| ab0566dcba | |||
| c4f2cc7189 | |||
| 4626d99590 | |||
| 6465ca8a19 | |||
| 15b9d151df | |||
| dd1b6c86cd | |||
| 9613cda79a | |||
| 648b8e5960 | |||
| ce545b1fd5 | |||
| 9151034fbc | |||
| 312a8baa13 | |||
| 18b6168cd1 | |||
| 9a282c3bf4 | |||
| 2bbebe4c30 | |||
| 162961b560 | |||
| f1cc37d0db | |||
| 5a9d216fb7 | |||
| bf37d3be7c | |||
| 7fd57aaed8 | |||
| d996c44b24 | |||
| 6f3052dd1b | |||
| d2b1bfdcdd | |||
| 945fb99594 | |||
| 09d624a4e2 | |||
| eb90db7ce6 | |||
| b56f9391b8 | |||
| c181478909 | |||
| 76b31e734c | |||
| ed8bd76d95 | |||
| 3051a72d7f | |||
| 3a33bf3a5d | |||
| 7959ba2664 | |||
| fe6568b82c | |||
| c228648bb6 | |||
| fdaeb6d1fa | |||
| ba45e18399 | |||
| 3e2bf877d4 | |||
| c80d344046 | |||
| 2364f10d8d | |||
| 2602275c20 | |||
| d113311f4e | |||
| 8d95701e8e | |||
| 0d2c54a5ed | |||
| 6506c84b85 | |||
| 69bb38b487 | |||
| 95e17f2b50 | |||
| 9625da9221 | |||
| c1659f1cf2 | |||
| c46ee764ac | |||
| 7aada85f76 | |||
| 145cbe3e4f | |||
| cb8dd8259d | |||
| b8e721fd27 | |||
| 7917b5384c | |||
| 087b7554bf | |||
| fb5f851a2a | |||
| 7ac51f8c2a | |||
| e5e40a986c | |||
| 7a27436868 | |||
| a5bab7425d | |||
| 93d5ab3739 | |||
| 3146fefb55 | |||
| 1ea51bb9df | |||
| 98bd664ab6 | |||
| 61aee2e784 | |||
| 22bf49078f | |||
| 7284e0d4ae | |||
| d39d075b1a | |||
| 0f6749b0c1 | |||
| 771030b911 | |||
| 8d5744a2cf | |||
| a58aab9004 | |||
| 61bd32f7f0 | |||
| 63a444bd81 | |||
| 8f28c3b74b | |||
| d766206343 | |||
| 172f83f5b3 | |||
| 9e308025c3 | |||
| aaa6a16778 | |||
| 2a21da2210 | |||
| d1cd2cfc8c | |||
| 832c224ed4 | |||
| 99316f4bd5 | |||
| 9caae5f1e5 |
+27
-5
@@ -1,6 +1,28 @@
|
|||||||
#
|
* text=auto
|
||||||
# https://help.github.com/articles/dealing-with-line-endings/
|
* text eol=lf
|
||||||
#
|
|
||||||
# These are explicitly windows files and should use crlf
|
|
||||||
*.bat text eol=crlf
|
|
||||||
|
|
||||||
|
# Windows forced line-endings
|
||||||
|
/.idea/* text eol=crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.ps1 text eol=crlf
|
||||||
|
|
||||||
|
# Gradle wrapper
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
|
# Binary files types
|
||||||
|
*.webp binary
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.swp binary
|
||||||
|
*.pdf binary
|
||||||
|
*.exe binary
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cp ../master/repo/* .
|
|
||||||
new_build=$(ls | tail -1)
|
|
||||||
echo "New build file name: $new_build"
|
|
||||||
|
|
||||||
cp -f $new_build Tachidesk-latest.jar
|
|
||||||
|
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
git status
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
git add .
|
|
||||||
git commit -m "Update repo"
|
|
||||||
git push
|
|
||||||
else
|
|
||||||
echo "No changes to commit"
|
|
||||||
fi
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Get last commit message
|
|
||||||
last_commit_log=$(git log -1 --pretty=format:"%s")
|
|
||||||
echo "last commit log: $last_commit_log"
|
|
||||||
|
|
||||||
filter_count=$(echo "$last_commit_log" | grep -c '\[RELEASE CI\]' )
|
|
||||||
echo "count is: $filter_count"
|
|
||||||
|
|
||||||
if [ "$filter_count" -gt 0 ]; then
|
|
||||||
mkdir -p repo/
|
|
||||||
cp server/build/Tachidesk-*.jar repo/
|
|
||||||
fi
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
name: CI
|
name: CI Pull Request
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,21 +16,21 @@ jobs:
|
|||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build FatJar
|
name: Build pull request
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous runs
|
- name: Cancel previous runs
|
||||||
uses: styfle/cancel-workflow-action@0.5.0
|
uses: styfle/cancel-workflow-action@0.9.0
|
||||||
with:
|
with:
|
||||||
access_token: ${{ github.token }}
|
access_token: ${{ github.token }}
|
||||||
|
|
||||||
- name: Checkout master branch
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -48,13 +45,12 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Download and process android.jar
|
- name: Download android.jar
|
||||||
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
|
|
||||||
run: |
|
run: |
|
||||||
cd master
|
cd master
|
||||||
./scripts/getAndroid.sh
|
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||||
|
|
||||||
- name: Build the Jar
|
- name: Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
uses: eskatos/gradle-command-action@v1
|
||||||
with:
|
with:
|
||||||
build-root-directory: master
|
build-root-directory: master
|
||||||
@@ -63,22 +59,3 @@ jobs:
|
|||||||
wrapper-cache-enabled: true
|
wrapper-cache-enabled: true
|
||||||
dependencies-cache-enabled: true
|
dependencies-cache-enabled: true
|
||||||
configuration-cache-enabled: true
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
- name: Create repo artifacts
|
|
||||||
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
|
|
||||||
run: |
|
|
||||||
cd master
|
|
||||||
./.github/scripts/create-repo.sh
|
|
||||||
|
|
||||||
- name: Checkout repo branch
|
|
||||||
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: repo
|
|
||||||
path: repo
|
|
||||||
|
|
||||||
- name: Deploy repo
|
|
||||||
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
|
|
||||||
run: |
|
|
||||||
cd repo
|
|
||||||
../master/.github/scripts/commit-repo.sh
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
name: CI build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_wrapper:
|
||||||
|
name: Validate Gradle Wrapper
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build artifacts and deploy preview
|
||||||
|
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.9.0
|
||||||
|
with:
|
||||||
|
access_token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Checkout master branch
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
path: master
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
|
||||||
|
- name: Copy CI gradle.properties
|
||||||
|
run: |
|
||||||
|
cd master
|
||||||
|
mkdir -p ~/.gradle
|
||||||
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
|
- name: Download android.jar
|
||||||
|
run: |
|
||||||
|
cd master
|
||||||
|
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||||
|
|
||||||
|
- name: Build Jar
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
env:
|
||||||
|
ProductBuildType: "Preview"
|
||||||
|
with:
|
||||||
|
build-root-directory: master
|
||||||
|
wrapper-directory: master
|
||||||
|
arguments: :server:shadowJar --stacktrace
|
||||||
|
wrapper-cache-enabled: true
|
||||||
|
dependencies-cache-enabled: true
|
||||||
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
|
# - name: Mock Build and copy webUI, Build Jar
|
||||||
|
# run: |
|
||||||
|
# mkdir -p master/server/build
|
||||||
|
# cd master/server/build
|
||||||
|
# echo "test" > Tachidesk-v0.3.8-r583.jar
|
||||||
|
|
||||||
|
- name: Generate Tag Name
|
||||||
|
id: GenTagName
|
||||||
|
run: |
|
||||||
|
cd master/server/build
|
||||||
|
genTag=$(ls *.jar | sed -e's/Tachidesk-\|.jar//g')
|
||||||
|
echo "$genTag"
|
||||||
|
echo "::set-output name=value::$genTag"
|
||||||
|
|
||||||
|
- name: make windows packages
|
||||||
|
run: |
|
||||||
|
cd master/scripts
|
||||||
|
./windows-bundler.sh win32
|
||||||
|
./windows-bundler.sh win64
|
||||||
|
|
||||||
|
# - name: Mock make windows packages
|
||||||
|
# run: |
|
||||||
|
# cd master/server/build
|
||||||
|
# echo test > Tachidesk-v0.3.8-r580-win32.zip
|
||||||
|
|
||||||
|
- name: Checkout preview branch
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: 'Suwayomi/Tachidesk-Server-preview'
|
||||||
|
ref: main
|
||||||
|
path: preview
|
||||||
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Tag
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.GenTagName.outputs.value }}"
|
||||||
|
echo "tag: $TAG"
|
||||||
|
cd preview
|
||||||
|
echo "{ \"latest\": \"$TAG\" }" > index.json
|
||||||
|
git add index.json
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git commit -m "Updated to $TAG"
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
git tag $TAG
|
||||||
|
git push origin $TAG
|
||||||
|
|
||||||
|
- name: Upload Preview Release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
|
artifacts: "master/server/build/*.jar,master/server/build/*.zip"
|
||||||
|
owner: "Suwayomi"
|
||||||
|
repo: "Tachidesk-Server-preview"
|
||||||
|
tag: ${{ steps.GenTagName.outputs.value }}
|
||||||
|
|
||||||
|
- name: Run Docker build workflow
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.DEPLOY_PREVIEW_TOKEN }}" -d '{"ref":"main", "inputs":{"tachidesk_release_type": "preview"}}' https://api.github.com/repos/suwayomi/docker-tachidesk/actions/workflows/build_container_images.yml/dispatches
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Issue closer
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
autoclose:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Autoclose issues
|
||||||
|
uses: arkon/issue-closer-action@v3.0
|
||||||
|
with:
|
||||||
|
repo-token: ${{ github.token }}
|
||||||
|
rules: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "title",
|
||||||
|
"regex": ".*<short description>*",
|
||||||
|
"message": "You did not fill out the description in 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."
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
name: CI Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_wrapper:
|
||||||
|
name: Validate Gradle Wrapper
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build artifacts and release
|
||||||
|
needs: check_wrapper
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Cancel previous runs
|
||||||
|
uses: styfle/cancel-workflow-action@0.9.0
|
||||||
|
with:
|
||||||
|
access_token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Checkout ${{ github.ref }}
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
path: master
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
|
||||||
|
- name: Copy CI gradle.properties
|
||||||
|
run: |
|
||||||
|
cd master
|
||||||
|
mkdir -p ~/.gradle
|
||||||
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
|
- name: Download android.jar
|
||||||
|
run: |
|
||||||
|
cd master
|
||||||
|
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||||
|
|
||||||
|
- name: Cache node_modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
**/webUI/node_modules
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/webUI/yarn.lock') }}
|
||||||
|
|
||||||
|
- name: Build and copy webUI, Build Jar
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
env:
|
||||||
|
ProductBuildType: "Stable"
|
||||||
|
with:
|
||||||
|
build-root-directory: master
|
||||||
|
wrapper-directory: master
|
||||||
|
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
|
||||||
|
wrapper-cache-enabled: true
|
||||||
|
dependencies-cache-enabled: true
|
||||||
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
|
- name: make windows packages
|
||||||
|
run: |
|
||||||
|
cd master/scripts
|
||||||
|
./windows-bundler.sh win32
|
||||||
|
./windows-bundler.sh win64
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: xresloader/upload-to-github-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
file: "master/server/build/*.jar;master/server/build/*.zip"
|
||||||
|
tags: true
|
||||||
|
draft: true
|
||||||
|
verbose: true
|
||||||
|
|
||||||
|
- name: Run Docker build workflow
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.DEPLOY_PREVIEW_TOKEN }}" -d '{"ref":"main", "inputs":{"tachidesk_release_type": "stable"}}' https://api.github.com/repos/suwayomi/docker-tachidesk/actions/workflows/build_container_images.yml/dispatches
|
||||||
|
|
||||||
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
# Ignore Gradle project-specific cache directory
|
# Ignore Gradle project-specific cache directory
|
||||||
.gradle
|
.gradle
|
||||||
.idea
|
.idea
|
||||||
|
gradle.properties
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Ignore Gradle build output directory
|
||||||
build
|
build
|
||||||
|
|
||||||
server/src/main/resources/react
|
server/src/main/resources/WebUI.zip
|
||||||
|
server/tmp/
|
||||||
|
server/tachiserver-data/
|
||||||
|
|
||||||
|
# bundle asset downlaods
|
||||||
|
OpenJDK*.zip
|
||||||
|
electron-*.zip
|
||||||
|
rcedit-*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
// Config API
|
// Config API, moved to the global build.gradle
|
||||||
// implementation("com.typesafe:config:1.4.0")
|
// implementation("com.typesafe:config:1.4.0")
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package xyz.nulldev.ts.config
|
||||||
|
|
||||||
|
import net.harawata.appdirs.AppDirsFactory
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
val ApplicationRootDir: String
|
||||||
|
get(): String {
|
||||||
|
return System.getProperty(
|
||||||
|
"suwayomi.tachidesk.server.rootDir",
|
||||||
|
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
package xyz.nulldev.ts.config
|
package xyz.nulldev.ts.config
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
@@ -10,49 +18,58 @@ import java.io.File
|
|||||||
* Manages app config.
|
* Manages app config.
|
||||||
*/
|
*/
|
||||||
open class ConfigManager {
|
open class ConfigManager {
|
||||||
private val generatedModules
|
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
||||||
= mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
|
||||||
val config by lazy { loadConfigs() }
|
val config by lazy { loadConfigs() }
|
||||||
|
|
||||||
//Public read-only view of modules
|
//Public read-only view of modules
|
||||||
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
||||||
get() = generatedModules
|
get() = generatedModules
|
||||||
|
|
||||||
open val configFolder: String
|
|
||||||
get() = System.getProperty("compat-configdirs") ?: "tachiserver-data/config"
|
|
||||||
|
|
||||||
val logger = KotlinLogging.logger {}
|
val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a config module
|
* Get a config module
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : ConfigModule> module(): T
|
inline fun <reified T : ConfigModule> module(): T = loadedModules[T::class.java] as T
|
||||||
= loadedModules[T::class.java] as T
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a config module (Java API)
|
* Get a config module (Java API)
|
||||||
*/
|
*/
|
||||||
fun <T : ConfigModule> module(type: Class<T>): T
|
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
|
||||||
= loadedModules[type] as T
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configs
|
* Load configs
|
||||||
*/
|
*/
|
||||||
fun loadConfigs(): Config {
|
fun loadConfigs(): Config {
|
||||||
val configs = mutableListOf<Config>()
|
//Load reference configs
|
||||||
|
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
||||||
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
|
val baseConfig =
|
||||||
|
ConfigFactory.parseMap(
|
||||||
|
mapOf(
|
||||||
|
"ts.server.rootDir" to ApplicationRootDir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
//Load reference config
|
//Load user config
|
||||||
configs += ConfigFactory.parseResources("reference.conf")
|
val userConfig =
|
||||||
|
File(ApplicationRootDir, "server.conf").let {
|
||||||
|
ConfigFactory.parseFile(it)
|
||||||
|
}
|
||||||
|
|
||||||
//Load custom configs from dir
|
|
||||||
File(configFolder).listFiles()?.map {
|
val config = ConfigFactory.empty()
|
||||||
ConfigFactory.parseFile(it)
|
.withFallback(baseConfig)
|
||||||
}?.filterNotNull()?.forEach {
|
.withFallback(userConfig)
|
||||||
configs += it.withFallback(configs.last())
|
.withFallback(compatConfig)
|
||||||
|
.withFallback(serverConfig)
|
||||||
|
.resolve()
|
||||||
|
|
||||||
|
// set log level early
|
||||||
|
if (debugLogsEnabled(config)) {
|
||||||
|
setLogLevel(Level.DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
val config = configs.last().resolve()
|
|
||||||
|
|
||||||
logger.debug {
|
logger.debug {
|
||||||
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
|
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
|
||||||
}
|
}
|
||||||
@@ -61,7 +78,7 @@ open class ConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun registerModule(module: ConfigModule) {
|
fun registerModule(module: ConfigModule) {
|
||||||
generatedModules.put(module.javaClass, module)
|
generatedModules[module.javaClass] = module
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerModules(vararg modules: ConfigModule) {
|
fun registerModules(vararg modules: ConfigModule) {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xyz.nulldev.ts.config
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.slf4j.Logger
|
||||||
|
|
||||||
|
fun setLogLevel(level: Level) {
|
||||||
|
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
fun debugLogsEnabled(config: Config)
|
||||||
|
= System.getProperty("suwayomi.tachidesk.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package xyz.nulldev.ts.config
|
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class ServerConfig(config: Config) : ConfigModule(config) {
|
|
||||||
val ip = config.getString("ip")
|
|
||||||
val port = config.getInt("port")
|
|
||||||
|
|
||||||
val allowConfigChanges = config.getBoolean("allowConfigChanges")
|
|
||||||
val enableWebUi = config.getBoolean("enableWebUi")
|
|
||||||
val useOldWebUi = config.getBoolean("useOldWebUi")
|
|
||||||
val prettyPrintApi = config.getBoolean("prettyPrintApi")
|
|
||||||
// TODO Apply to operation IDs
|
|
||||||
val disabledApiEndpoints = config.getStringList("disabledApiEndpoints").map(String::toLowerCase)
|
|
||||||
val enabledApiEndpoints = config.getStringList("enabledApiEndpoints").map(String::toLowerCase)
|
|
||||||
val httpInitializedPrintMessage = config.getString("httpInitializedPrintMessage")
|
|
||||||
|
|
||||||
val useExternalStaticFiles = config.getBoolean("useExternalStaticFiles")
|
|
||||||
val externalStaticFilesFolder = config.getString("externalStaticFilesFolder")
|
|
||||||
|
|
||||||
val rootDir = registerFile(config.getString("rootDir"))
|
|
||||||
val patchesDir = registerFile(config.getString("patchesDir"))
|
|
||||||
|
|
||||||
fun registerFile(file: String): File {
|
|
||||||
return File(file).apply {
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun register(config: Config)
|
|
||||||
= ServerConfig(config.getConfig("ts.server"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
|
kotlin("plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://jitpack.io")
|
url = uri("https://jitpack.io")
|
||||||
}
|
}
|
||||||
@@ -18,44 +18,49 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Android stub library
|
// Android stub library
|
||||||
// compileOnly( fileTree(File(rootProject.rootDir, "libs/android"), include: "*.jar")
|
|
||||||
implementation(fileTree("lib/"))
|
implementation(fileTree("lib/"))
|
||||||
implementation(fileTree("${rootProject.rootDir}/server/lib/dex2jar/"))
|
|
||||||
|
|
||||||
|
|
||||||
// Android JAR libs
|
// Android JAR libs
|
||||||
// compileOnly( fileTree(dir: new File(rootProject.rootDir, "libs/other"), include: "*.jar")
|
// compileOnly( fileTree(dir: new File(rootProject.rootDir, "libs/other"), include: "*.jar")
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
compileOnly( "com.google.code.gson:gson:2.8.6")
|
compileOnly("com.google.code.gson:gson:2.8.6")
|
||||||
|
|
||||||
// Javassist
|
// Javassist
|
||||||
compileOnly( "org.javassist:javassist:3.27.0-GA")
|
compileOnly("org.javassist:javassist:3.27.0-GA")
|
||||||
|
|
||||||
// Coroutines
|
|
||||||
val kotlinx_coroutines_version = "1.4.2"
|
|
||||||
compileOnly( "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version")
|
|
||||||
compileOnly( "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlinx_coroutines_version")
|
|
||||||
|
|
||||||
// XML
|
// XML
|
||||||
compileOnly( group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
implementation( project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
// dex2jar
|
|
||||||
// compileOnly( "dex2jar:dex-translator")
|
|
||||||
|
|
||||||
// APK parser
|
|
||||||
compileOnly("net.dongliu:apk-parser:2.6.10")
|
|
||||||
|
|
||||||
// APK sig verifier
|
// APK sig verifier
|
||||||
compileOnly("com.android.tools.build:apksig:4.2.0-alpha13")
|
compileOnly("com.android.tools.build:apksig:4.2.0-alpha13")
|
||||||
|
|
||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly( "androidx.annotation:annotation:1.2.0-alpha01")
|
compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
|
||||||
|
|
||||||
// compileOnly("io.reactivex:rxjava:1.3.8")
|
// substitute for duktape-android
|
||||||
|
// 'org.mozilla:rhino' includes some code that we don't need so use 'org.mozilla:rhino-runtime' instead
|
||||||
|
implementation("org.mozilla:rhino-runtime:1.7.13")
|
||||||
|
// 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn
|
||||||
|
implementation("org.mozilla:rhino-engine:1.7.13")
|
||||||
|
|
||||||
|
// Kotlin wrapper around Java Preferences, makes certain things easier
|
||||||
|
val multiplatformSettingsVersion = "0.7.7"
|
||||||
|
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
||||||
|
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
||||||
|
|
||||||
|
// Android version of SimpleDateFormat
|
||||||
|
implementation("com.ibm.icu:icu4j:69.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||||
|
kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Copyright (C) Contributors to the Suwayomi project
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# This is a windows only PowerShell script to create android.jar stubs
|
||||||
|
|
||||||
|
# foolproof against running from AndroidCompat dir instead of running from project root
|
||||||
|
if ($(Split-Path -Path (Get-Location) -Leaf) -eq "AndroidCompat" ) {
|
||||||
|
Set-Location ..
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Getting required Android.jar..."
|
||||||
|
Remove-Item -Recurse -Force "tmp" -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
New-Item -ItemType Directory -Force -Path "tmp" | Out-Null
|
||||||
|
|
||||||
|
$androidEncoded = (Invoke-WebRequest -Uri "https://android.googlesource.com/platform/prebuilts/sdk/+/6cd31be5e4e25901aadf838120d71a79b46d9add/30/public/android.jar?format=TEXT" -UseBasicParsing).content
|
||||||
|
|
||||||
|
$android_jar = (Get-Location).Path + "\tmp\android.jar"
|
||||||
|
|
||||||
|
[IO.File]::WriteAllBytes($android_jar, [Convert]::FromBase64String($androidEncoded))
|
||||||
|
|
||||||
|
# We need to remove any stub classes that we have implementations for
|
||||||
|
Write-Output "Patching JAR..."
|
||||||
|
|
||||||
|
function Remove-Files-Zip($zipfile, $paths)
|
||||||
|
{
|
||||||
|
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null
|
||||||
|
|
||||||
|
$stream = New-Object IO.FileStream($zipfile, [IO.FileMode]::Open)
|
||||||
|
$mode = [IO.Compression.ZipArchiveMode]::Update
|
||||||
|
$zip = New-Object IO.Compression.ZipArchive($stream, $mode)
|
||||||
|
|
||||||
|
if ($paths.getType().Name -eq "Object[]")
|
||||||
|
{
|
||||||
|
$paths | ForEach-Object {
|
||||||
|
$path = $_
|
||||||
|
($zip.Entries | Where-Object { $_.FullName -like $path }) | ForEach-Object { Write-Output "Deleting: $($_.FullName)"; $_.Delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
($zip.Entries | Where-Object { $_.FullName -like $paths }) | ForEach-Object { Write-Output "Deleting: $($_.FullName)"; $_.Delete() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$zip.Dispose()
|
||||||
|
$stream.Close()
|
||||||
|
$stream.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Removing org.json..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/json/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.apache..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/apache/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.w3c..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/w3c/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.xml..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/xml/*'
|
||||||
|
|
||||||
|
Write-Output "Removing org.xmlpull..."
|
||||||
|
Remove-Files-Zip $android_jar 'org/xmlpull/*'
|
||||||
|
|
||||||
|
Write-Output "Removing junit..."
|
||||||
|
Remove-Files-Zip $android_jar 'junit/*'
|
||||||
|
|
||||||
|
Write-Output "Removing javax..."
|
||||||
|
Remove-Files-Zip $android_jar 'javax/*'
|
||||||
|
|
||||||
|
Write-Output "Removing java..."
|
||||||
|
Remove-Files-Zip $android_jar 'java/*'
|
||||||
|
|
||||||
|
Write-Output "Removing overriden classes..."
|
||||||
|
Remove-Files-Zip $android_jar 'android/app/Application.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/app/Service.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/net/Uri.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/net/Uri$Builder.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/os/Environment.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/text/format/Formatter.class'
|
||||||
|
Remove-Files-Zip $android_jar 'android/text/Html.class'
|
||||||
|
|
||||||
|
function Dedupe($path)
|
||||||
|
{
|
||||||
|
Push-Location $path
|
||||||
|
$classes = Get-ChildItem . *.* -Recurse | Where-Object { !$_.PSIsContainer }
|
||||||
|
$classes | ForEach-Object {
|
||||||
|
"Processing class: $($_.FullName)"
|
||||||
|
Remove-Files-Zip $android_jar ("$($_.Name).class","$($_.Name)$*.class","$($_.Name)Kt.class","$($_.Name)Kt$*.class") | Out-Null
|
||||||
|
}
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Dedupe "AndroidCompat/src/main/java"
|
||||||
|
Dedupe "server/src/main/kotlin"
|
||||||
|
|
||||||
|
Write-Output "Copying Android.jar to library folder..."
|
||||||
|
Move-Item -Force $android_jar "AndroidCompat/lib/android.jar"
|
||||||
|
|
||||||
|
Write-Output "Cleaning up..."
|
||||||
|
Remove-Item -Recurse -Force "tmp"
|
||||||
|
|
||||||
|
Write-Output "Done!"
|
||||||
@@ -1,12 +1,38 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (C) Contributors to the Suwayomi project
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# This is a bash script to create android.jar stubs
|
||||||
|
|
||||||
|
for dep in "curl" "base64" "zip"
|
||||||
|
do
|
||||||
|
which $dep >/dev/null 2>&1 || { echo >&2 "Error: This script needs $dep installed."; abort=yes; }
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$abort" = yes ]; then
|
||||||
|
echo "Some of the dependencies didn't exist. Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# foolproof against running from AndroidCompat dir instead of running from project root
|
||||||
|
if [ "$(basename "$(pwd)")" = "AndroidCompat" ]; then
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
echo "Getting required Android.jar..."
|
echo "Getting required Android.jar..."
|
||||||
rm -rf "tmp"
|
rm -rf "tmp"
|
||||||
mkdir -p "tmp"
|
mkdir -p "tmp"
|
||||||
pushd "tmp"
|
pushd "tmp"
|
||||||
|
|
||||||
curl "https://android.googlesource.com/platform/prebuilts/sdk/+/3b8a524d25fa6c3d795afb1eece3f24870c60988/27/public/android.jar?format=TEXT" | base64 --decode > android.jar
|
curl "https://android.googlesource.com/platform/prebuilts/sdk/+/6cd31be5e4e25901aadf838120d71a79b46d9add/30/public/android.jar?format=TEXT" | base64 --decode > android.jar
|
||||||
|
|
||||||
# We need to remove any stub classes that we might use
|
# We need to remove any stub classes that we have implementations for
|
||||||
echo "Patching JAR..."
|
echo "Patching JAR..."
|
||||||
|
|
||||||
echo "Removing org.json..."
|
echo "Removing org.json..."
|
||||||
@@ -33,7 +59,7 @@ zip --delete android.jar javax/*
|
|||||||
echo "Removing java..."
|
echo "Removing java..."
|
||||||
zip --delete android.jar java/*
|
zip --delete android.jar java/*
|
||||||
|
|
||||||
echo "Removing overriden classes..."
|
echo "Removing overridden classes..."
|
||||||
zip --delete android.jar android/app/Application.class
|
zip --delete android.jar android/app/Application.class
|
||||||
zip --delete android.jar android/app/Service.class
|
zip --delete android.jar android/app/Service.class
|
||||||
zip --delete android.jar android/net/Uri.class
|
zip --delete android.jar android/net/Uri.class
|
||||||
@@ -42,12 +68,12 @@ zip --delete android.jar android/os/Environment.class
|
|||||||
zip --delete android.jar android/text/format/Formatter.class
|
zip --delete android.jar android/text/format/Formatter.class
|
||||||
zip --delete android.jar android/text/Html.class
|
zip --delete android.jar android/text/Html.class
|
||||||
|
|
||||||
# Dedup overriden Android classes
|
# Dedup overridden Android classes
|
||||||
ABS_JAR="$(realpath android.jar)"
|
ABS_JAR="$(realpath android.jar)"
|
||||||
function dedup() {
|
function dedup() {
|
||||||
pushd "$1"
|
pushd "$1"
|
||||||
CLASSES="$(find * -type f)"
|
CLASSES="$(find ./* -type f)"
|
||||||
echo "$CLASSES" | while read class
|
echo "$CLASSES" | while read -r class
|
||||||
do
|
do
|
||||||
NAME="${class%.*}"
|
NAME="${class%.*}"
|
||||||
echo "Processing class: $NAME"
|
echo "Processing class: $NAME"
|
||||||
@@ -56,13 +82,10 @@ function dedup() {
|
|||||||
popd
|
popd
|
||||||
}
|
}
|
||||||
|
|
||||||
pushd ..
|
popd
|
||||||
dedup AndroidCompat/src/main/java
|
dedup AndroidCompat/src/main/java
|
||||||
dedup server/src/main/java
|
|
||||||
dedup server/src/main/kotlin
|
dedup server/src/main/kotlin
|
||||||
popd
|
|
||||||
|
|
||||||
popd
|
|
||||||
echo "Copying Android.jar to library folder..."
|
echo "Copying Android.jar to library folder..."
|
||||||
mv tmp/android.jar AndroidCompat/lib
|
mv tmp/android.jar AndroidCompat/lib
|
||||||
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.support.v4.content;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.StatFs;
|
|
||||||
import android.support.v4.os.EnvironmentCompat;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for accessing features in {@link android.content.Context}
|
|
||||||
* introduced after API level 4 in a backwards compatible fashion.
|
|
||||||
*/
|
|
||||||
public class ContextCompat {
|
|
||||||
/**
|
|
||||||
* Start a set of activities as a synthesized task stack, if able.
|
|
||||||
*
|
|
||||||
* <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
|
|
||||||
* app navigation using the back key changed. The back key's behavior is local
|
|
||||||
* to the current task and does not capture navigation across different tasks.
|
|
||||||
* Navigating across tasks and easily reaching the previous task is accomplished
|
|
||||||
* through the "recents" UI, accessible through the software-provided Recents key
|
|
||||||
* on the navigation or system bar. On devices with the older hardware button configuration
|
|
||||||
* the recents UI can be accessed with a long press on the Home key.</p>
|
|
||||||
*
|
|
||||||
* <p>When crossing from one task stack to another post-Android 3.0,
|
|
||||||
* the application should synthesize a back stack/history for the new task so that
|
|
||||||
* the user may navigate out of the new task and back to the Launcher by repeated
|
|
||||||
* presses of the back key. Back key presses should not navigate across task stacks.</p>
|
|
||||||
*
|
|
||||||
* <p>startActivities provides a mechanism for constructing a synthetic task stack of
|
|
||||||
* multiple activities. If the underlying API is not available on the system this method
|
|
||||||
* will return false.</p>
|
|
||||||
*
|
|
||||||
* @param context Start activities using this activity as the starting context
|
|
||||||
* @param intents Array of intents defining the activities that will be started. The element
|
|
||||||
* length-1 will correspond to the top activity on the resulting task stack.
|
|
||||||
* @return true if the underlying API was available and the call was successful, false otherwise
|
|
||||||
*/
|
|
||||||
public static boolean startActivities(Context context, Intent[] intents) {
|
|
||||||
return startActivities(context, intents, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a set of activities as a synthesized task stack, if able.
|
|
||||||
*
|
|
||||||
* <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
|
|
||||||
* app navigation using the back key changed. The back key's behavior is local
|
|
||||||
* to the current task and does not capture navigation across different tasks.
|
|
||||||
* Navigating across tasks and easily reaching the previous task is accomplished
|
|
||||||
* through the "recents" UI, accessible through the software-provided Recents key
|
|
||||||
* on the navigation or system bar. On devices with the older hardware button configuration
|
|
||||||
* the recents UI can be accessed with a long press on the Home key.</p>
|
|
||||||
*
|
|
||||||
* <p>When crossing from one task stack to another post-Android 3.0,
|
|
||||||
* the application should synthesize a back stack/history for the new task so that
|
|
||||||
* the user may navigate out of the new task and back to the Launcher by repeated
|
|
||||||
* presses of the back key. Back key presses should not navigate across task stacks.</p>
|
|
||||||
*
|
|
||||||
* <p>startActivities provides a mechanism for constructing a synthetic task stack of
|
|
||||||
* multiple activities. If the underlying API is not available on the system this method
|
|
||||||
* will return false.</p>
|
|
||||||
*
|
|
||||||
* @param context Start activities using this activity as the starting context
|
|
||||||
* @param intents Array of intents defining the activities that will be started. The element
|
|
||||||
* length-1 will correspond to the top activity on the resulting task stack.
|
|
||||||
* @param options Additional options for how the Activity should be started.
|
|
||||||
* See {@link android.content.Context#startActivity(Intent, Bundle)
|
|
||||||
* @return true if the underlying API was available and the call was successful, false otherwise
|
|
||||||
*/
|
|
||||||
public static boolean startActivities(Context context, Intent[] intents,
|
|
||||||
Bundle options) {
|
|
||||||
context.startActivities(intents, options);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns absolute paths to application-specific directories on all
|
|
||||||
* external storage devices where the application's OBB files (if there are
|
|
||||||
* any) can be found. Note if the application does not have any OBB files,
|
|
||||||
* these directories may not exist.
|
|
||||||
* <p>
|
|
||||||
* This is like {@link Context#getFilesDir()} in that these files will be
|
|
||||||
* deleted when the application is uninstalled, however there are some
|
|
||||||
* important differences:
|
|
||||||
* <ul>
|
|
||||||
* <li>External files are not always available: they will disappear if the
|
|
||||||
* user mounts the external storage on a computer or removes it.
|
|
||||||
* <li>There is no security enforced with these files.
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* External storage devices returned here are considered a permanent part of
|
|
||||||
* the device, including both emulated external storage and physical media
|
|
||||||
* slots, such as SD cards in a battery compartment. The returned paths do
|
|
||||||
* not include transient devices, such as USB flash drives.
|
|
||||||
* <p>
|
|
||||||
* An application may store data on any or all of the returned devices. For
|
|
||||||
* example, an app may choose to store large files on the device with the
|
|
||||||
* most available space, as measured by {@link StatFs}.
|
|
||||||
* <p>
|
|
||||||
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
|
|
||||||
* are required to write to the returned paths; they're always accessible to
|
|
||||||
* the calling app. Before then,
|
|
||||||
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
|
|
||||||
* write. Write access outside of these paths on secondary external storage
|
|
||||||
* devices is not available. To request external storage access in a
|
|
||||||
* backwards compatible way, consider using {@code android:maxSdkVersion}
|
|
||||||
* like this:
|
|
||||||
*
|
|
||||||
* <pre class="prettyprint"><uses-permission
|
|
||||||
* android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
* android:maxSdkVersion="18" /></pre>
|
|
||||||
* <p>
|
|
||||||
* The first path returned is the same as {@link Context#getObbDir()}.
|
|
||||||
* Returned paths may be {@code null} if a storage device is unavailable.
|
|
||||||
*
|
|
||||||
* @see Context#getObbDir()
|
|
||||||
* @see EnvironmentCompat#getStorageState(File)
|
|
||||||
*/
|
|
||||||
public static File[] getObbDirs(Context context) {
|
|
||||||
return context.getObbDirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns absolute paths to application-specific directories on all
|
|
||||||
* external storage devices where the application can place persistent files
|
|
||||||
* it owns. These files are internal to the application, and not typically
|
|
||||||
* visible to the user as media.
|
|
||||||
* <p>
|
|
||||||
* This is like {@link Context#getFilesDir()} in that these files will be
|
|
||||||
* deleted when the application is uninstalled, however there are some
|
|
||||||
* important differences:
|
|
||||||
* <ul>
|
|
||||||
* <li>External files are not always available: they will disappear if the
|
|
||||||
* user mounts the external storage on a computer or removes it.
|
|
||||||
* <li>There is no security enforced with these files.
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* External storage devices returned here are considered a permanent part of
|
|
||||||
* the device, including both emulated external storage and physical media
|
|
||||||
* slots, such as SD cards in a battery compartment. The returned paths do
|
|
||||||
* not include transient devices, such as USB flash drives.
|
|
||||||
* <p>
|
|
||||||
* An application may store data on any or all of the returned devices. For
|
|
||||||
* example, an app may choose to store large files on the device with the
|
|
||||||
* most available space, as measured by {@link StatFs}.
|
|
||||||
* <p>
|
|
||||||
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
|
|
||||||
* are required to write to the returned paths; they're always accessible to
|
|
||||||
* the calling app. Before then,
|
|
||||||
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
|
|
||||||
* write. Write access outside of these paths on secondary external storage
|
|
||||||
* devices is not available. To request external storage access in a
|
|
||||||
* backwards compatible way, consider using {@code android:maxSdkVersion}
|
|
||||||
* like this:
|
|
||||||
*
|
|
||||||
* <pre class="prettyprint"><uses-permission
|
|
||||||
* android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
* android:maxSdkVersion="18" /></pre>
|
|
||||||
* <p>
|
|
||||||
* The first path returned is the same as
|
|
||||||
* {@link Context#getExternalFilesDir(String)}. Returned paths may be
|
|
||||||
* {@code null} if a storage device is unavailable.
|
|
||||||
*
|
|
||||||
* @see Context#getExternalFilesDir(String)
|
|
||||||
* @see EnvironmentCompat#getStorageState(File)
|
|
||||||
*/
|
|
||||||
public static File[] getExternalFilesDirs(Context context, String type) {
|
|
||||||
return context.getExternalFilesDirs(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns absolute paths to application-specific directories on all
|
|
||||||
* external storage devices where the application can place cache files it
|
|
||||||
* owns. These files are internal to the application, and not typically
|
|
||||||
* visible to the user as media.
|
|
||||||
* <p>
|
|
||||||
* This is like {@link Context#getCacheDir()} in that these files will be
|
|
||||||
* deleted when the application is uninstalled, however there are some
|
|
||||||
* important differences:
|
|
||||||
* <ul>
|
|
||||||
* <li>External files are not always available: they will disappear if the
|
|
||||||
* user mounts the external storage on a computer or removes it.
|
|
||||||
* <li>There is no security enforced with these files.
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* External storage devices returned here are considered a permanent part of
|
|
||||||
* the device, including both emulated external storage and physical media
|
|
||||||
* slots, such as SD cards in a battery compartment. The returned paths do
|
|
||||||
* not include transient devices, such as USB flash drives.
|
|
||||||
* <p>
|
|
||||||
* An application may store data on any or all of the returned devices. For
|
|
||||||
* example, an app may choose to store large files on the device with the
|
|
||||||
* most available space, as measured by {@link StatFs}.
|
|
||||||
* <p>
|
|
||||||
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
|
|
||||||
* are required to write to the returned paths; they're always accessible to
|
|
||||||
* the calling app. Before then,
|
|
||||||
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
|
|
||||||
* write. Write access outside of these paths on secondary external storage
|
|
||||||
* devices is not available. To request external storage access in a
|
|
||||||
* backwards compatible way, consider using {@code android:maxSdkVersion}
|
|
||||||
* like this:
|
|
||||||
*
|
|
||||||
* <pre class="prettyprint"><uses-permission
|
|
||||||
* android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
* android:maxSdkVersion="18" /></pre>
|
|
||||||
* <p>
|
|
||||||
* The first path returned is the same as
|
|
||||||
* {@link Context#getExternalCacheDir()}. Returned paths may be {@code null}
|
|
||||||
* if a storage device is unavailable.
|
|
||||||
*
|
|
||||||
* @see Context#getExternalCacheDir()
|
|
||||||
* @see EnvironmentCompat#getStorageState(File)
|
|
||||||
*/
|
|
||||||
public static File[] getExternalCacheDirs(Context context) {
|
|
||||||
return context.getExternalCacheDirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a drawable object associated with a particular resource ID.
|
|
||||||
* <p>
|
|
||||||
* Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned
|
|
||||||
* drawable will be styled for the specified Context's theme.
|
|
||||||
*
|
|
||||||
* @param id The desired resource identifier, as generated by the aapt tool.
|
|
||||||
* This integer encodes the package, type, and resource entry.
|
|
||||||
* The value 0 is an invalid identifier.
|
|
||||||
* @return Drawable An object that can be used to draw this resource.
|
|
||||||
*/
|
|
||||||
public static final Drawable getDrawable(Context context, int id) {
|
|
||||||
return context.getDrawable(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute path to the directory on the filesystem similar to
|
|
||||||
* {@link Context#getFilesDir()}. The difference is that files placed under this
|
|
||||||
* directory will be excluded from automatic backup to remote storage on
|
|
||||||
* devices running {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later. See
|
|
||||||
* {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
|
|
||||||
* of the automatic backup mechanism in Android.
|
|
||||||
*
|
|
||||||
* <p>No permissions are required to read or write to the returned path, since this
|
|
||||||
* path is internal storage.
|
|
||||||
*
|
|
||||||
* @return The path of the directory holding application files that will not be
|
|
||||||
* automatically backed up to remote storage.
|
|
||||||
*
|
|
||||||
* @see android.content.Context.getFilesDir
|
|
||||||
*/
|
|
||||||
public final File getNoBackupFilesDir(Context context) {
|
|
||||||
return context.getNoBackupFilesDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute path to the application specific cache directory on
|
|
||||||
* the filesystem designed for storing cached code. On devices running
|
|
||||||
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, the system will delete
|
|
||||||
* any files stored in this location both when your specific application is
|
|
||||||
* upgraded, and when the entire platform is upgraded.
|
|
||||||
* <p>
|
|
||||||
* This location is optimal for storing compiled or optimized code generated
|
|
||||||
* by your application at runtime.
|
|
||||||
* <p>
|
|
||||||
* Apps require no extra permissions to read or write to the returned path,
|
|
||||||
* since this path lives in their private storage.
|
|
||||||
*
|
|
||||||
* @return The path of the directory holding application code cache files.
|
|
||||||
*/
|
|
||||||
public final File getCodeCacheDir(Context context) {
|
|
||||||
return context.getCodeCacheDir();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.support.v4.os;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for accessing features in {@link Environment} introduced after API
|
|
||||||
* level 4 in a backwards compatible fashion.
|
|
||||||
*/
|
|
||||||
public class EnvironmentCompat {
|
|
||||||
/**
|
|
||||||
* Unknown storage state, such as when a path isn't backed by known storage
|
|
||||||
* media.
|
|
||||||
*
|
|
||||||
* @see #getStorageState(File)
|
|
||||||
*/
|
|
||||||
public static final String MEDIA_UNKNOWN = "unknown";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current state of the storage device that provides the given
|
|
||||||
* path.
|
|
||||||
*
|
|
||||||
* @return one of {@link #MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
|
|
||||||
* {@link Environment#MEDIA_UNMOUNTED},
|
|
||||||
* {@link Environment#MEDIA_CHECKING},
|
|
||||||
* {@link Environment#MEDIA_NOFS},
|
|
||||||
* {@link Environment#MEDIA_MOUNTED},
|
|
||||||
* {@link Environment#MEDIA_MOUNTED_READ_ONLY},
|
|
||||||
* {@link Environment#MEDIA_SHARED},
|
|
||||||
* {@link Environment#MEDIA_BAD_REMOVAL}, or
|
|
||||||
* {@link Environment#MEDIA_UNMOUNTABLE}.
|
|
||||||
*/
|
|
||||||
public static String getStorageState(File path) {
|
|
||||||
return Environment.getStorageState(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.support.v7.preference;
|
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A data store interface to be implemented and provided to the Preferences framework. This can be
|
|
||||||
* used to replace the default {@link android.content.SharedPreferences}, if needed.
|
|
||||||
*
|
|
||||||
* <p>In most cases you want to use {@link android.content.SharedPreferences} as it is automatically
|
|
||||||
* backed up and migrated to new devices. However, providing custom data store to preferences can be
|
|
||||||
* useful if your app stores its preferences in a local db, cloud or they are device specific like
|
|
||||||
* "Developer settings". It might be also useful when you want to use the preferences UI but
|
|
||||||
* the data are not supposed to be stored at all because they are valid per session only.
|
|
||||||
*
|
|
||||||
* <p>Once a put method is called it is full responsibility of the data store implementation to
|
|
||||||
* safely store the given values. Time expensive operations need to be done in the background to
|
|
||||||
* prevent from blocking the UI. You also need to have a plan on how to serialize the data in case
|
|
||||||
* the activity holding this object gets destroyed.
|
|
||||||
*
|
|
||||||
* <p>By default, all "put" methods throw {@link UnsupportedOperationException}.
|
|
||||||
*/
|
|
||||||
public abstract class PreferenceDataStore {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a {@link String} value to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param value the new value for the preference
|
|
||||||
* @see #getString(String, String)
|
|
||||||
*/
|
|
||||||
public void putString(String key, @Nullable String value) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a set of Strings to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param values the set of new values for the preference
|
|
||||||
* @see #getStringSet(String, Set<String>)
|
|
||||||
*/
|
|
||||||
public void putStringSet(String key, @Nullable Set<String> values) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an {@link Integer} value to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param value the new value for the preference
|
|
||||||
* @see #getInt(String, int)
|
|
||||||
*/
|
|
||||||
public void putInt(String key, int value) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a {@link Long} value to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param value the new value for the preference
|
|
||||||
* @see #getLong(String, long)
|
|
||||||
*/
|
|
||||||
public void putLong(String key, long value) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a {@link Float} value to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param value the new value for the preference
|
|
||||||
* @see #getFloat(String, float)
|
|
||||||
*/
|
|
||||||
public void putFloat(String key, float value) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a {@link Boolean} value to the data store.
|
|
||||||
*
|
|
||||||
* <p>Once the value is set the data store is responsible for holding it.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to modify
|
|
||||||
* @param value the new value for the preference
|
|
||||||
* @see #getBoolean(String, boolean)
|
|
||||||
*/
|
|
||||||
public void putBoolean(String key, boolean value) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented on this data store");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a {@link String} value from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValue value to return if this preference does not exist in the storage
|
|
||||||
* @return the value from the data store or the default return value
|
|
||||||
* @see #putString(String, String)
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String getString(String key, @Nullable String defValue) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a set of Strings from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValues values to return if this preference does not exist in the storage
|
|
||||||
* @return the values from the data store or the default return values
|
|
||||||
* @see #putStringSet(String, Set<String>)
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
|
|
||||||
return defValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves an {@link Integer} value from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValue value to return if this preference does not exist in the storage
|
|
||||||
* @return the value from the data store or the default return value
|
|
||||||
* @see #putInt(String, int)
|
|
||||||
*/
|
|
||||||
public int getInt(String key, int defValue) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a {@link Long} value from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValue value to return if this preference does not exist in the storage
|
|
||||||
* @return the value from the data store or the default return value
|
|
||||||
* @see #putLong(String, long)
|
|
||||||
*/
|
|
||||||
public long getLong(String key, long defValue) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a {@link Float} value from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValue value to return if this preference does not exist in the storage
|
|
||||||
* @return the value from the data store or the default return value
|
|
||||||
* @see #putFloat(String, float)
|
|
||||||
*/
|
|
||||||
public float getFloat(String key, float defValue) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a {@link Boolean} value from the data store.
|
|
||||||
*
|
|
||||||
* @param key the name of the preference to retrieve
|
|
||||||
* @param defValue value to return if this preference does not exist in the storage
|
|
||||||
* @return the value from the data store or the default return value
|
|
||||||
* @see #getBoolean(String, boolean)
|
|
||||||
*/
|
|
||||||
public boolean getBoolean(String key, boolean defValue) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package android.support.v7.preference;
|
|
||||||
|
|
||||||
public class PreferenceScreen {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package android.widget;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
public class EditText {
|
||||||
|
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public EditText(android.content.Context context, android.util.AttributeSet attrs) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
protected boolean getDefaultEditable() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
protected android.text.method.MovementMethod getDefaultMovementMethod() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public android.text.Editable getText() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void setText(java.lang.CharSequence text, android.widget.TextView.BufferType type) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void setSelection(int start, int stop) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void setSelection(int index) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void selectAll() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void extendSelection(int index) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public void setEllipsize(android.text.TextUtils.TruncateAt ellipsis) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public java.lang.CharSequence getAccessibilityClassName() { throw new RuntimeException("Stub!"); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package android.widget;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
public class Toast {
|
||||||
|
public static final int LENGTH_LONG = 1;
|
||||||
|
public static final int LENGTH_SHORT = 0;
|
||||||
|
|
||||||
|
private CharSequence text;
|
||||||
|
|
||||||
|
private Toast(CharSequence text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Toast(android.content.Context context) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
System.out.printf("made a Toast: \"%s\"\n", text.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setView(android.view.View view) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public android.view.View getView() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(int duration) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMargin(float horizontalMargin, float verticalMargin) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHorizontalMargin() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getVerticalMargin() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGravity(int gravity, int xOffset, int yOffset) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGravity() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getXOffset() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getYOffset() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Toast makeText(android.content.Context context, java.lang.CharSequence text, int duration) {
|
||||||
|
return new Toast(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static android.widget.Toast makeText(android.content.Context context, int resId, int duration) throws android.content.res.Resources.NotFoundException {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(int resId) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(java.lang.CharSequence s) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class CheckBoxPreference extends TwoStatePreference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/CheckBoxPreference.java
|
||||||
|
|
||||||
|
public CheckBoxPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public abstract class DialogPreference extends Preference {
|
||||||
|
private CharSequence dialogTitle;
|
||||||
|
private CharSequence dialogMessage;
|
||||||
|
|
||||||
|
public DialogPreference(Context context) { super(context); }
|
||||||
|
|
||||||
|
public CharSequence getDialogTitle() {
|
||||||
|
return dialogTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDialogTitle(CharSequence dialogTitle) {
|
||||||
|
this.dialogTitle = dialogTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getDialogMessage() {
|
||||||
|
return dialogMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDialogMessage(CharSequence dialogMessage) {
|
||||||
|
this.dialogMessage = dialogMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public class EditTextPreference extends DialogPreference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/EditTextPreference.java
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private OnBindEditTextListener onBindEditTextListener;
|
||||||
|
|
||||||
|
public EditTextPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnBindEditTextListener getOnBindEditTextListener() {
|
||||||
|
return onBindEditTextListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnBindEditTextListener(@Nullable OnBindEditTextListener onBindEditTextListener) {
|
||||||
|
this.onBindEditTextListener = onBindEditTextListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnBindEditTextListener {
|
||||||
|
void onBindEditText(@NonNull EditText editText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
@Override
|
||||||
|
public String getDefaultValueType() {
|
||||||
|
return "String";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public class ListPreference extends Preference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/ListPreference.java
|
||||||
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
|
private CharSequence[] entries;
|
||||||
|
private CharSequence[] entryValues;
|
||||||
|
|
||||||
|
public ListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntries(CharSequence[] entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findIndexOfValue(String value) {
|
||||||
|
if (value != null && entryValues != null) {
|
||||||
|
for (int i = entryValues.length - 1; i >= 0; i--) {
|
||||||
|
if (TextUtils.equals(entryValues[i].toString(), value)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntryValues() {
|
||||||
|
return entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntryValues(CharSequence[] entryValues) {
|
||||||
|
this.entryValues = entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setValueIndex(int index) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getValue() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setValue(String value) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
@Override
|
||||||
|
public String getDefaultValueType() {
|
||||||
|
return "String";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MultiSelectListPreference extends DialogPreference {
|
||||||
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
|
public MultiSelectListPreference(Context context) { super(context); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setEntries(CharSequence[] entries) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public CharSequence[] getEntries() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setEntryValues(CharSequence[] entryValues) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public CharSequence[] getEntryValues() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setValues(Set<String> values) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Set<String> getValues() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
public int findIndexOfValue(String value) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
@Override
|
||||||
|
public String getDefaultValueType() {
|
||||||
|
return "Set";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimal implementation of androidx.preference.Preference
|
||||||
|
*/
|
||||||
|
public class Preference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/Preference.java
|
||||||
|
// Note: `Preference` doesn't actually hold or persist the value, `OnPreferenceChangeListener` is called and it's up to the extension to persist it.
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
protected Context context;
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
private CharSequence title;
|
||||||
|
private CharSequence summary;
|
||||||
|
private Object defaultValue;
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
@JsonIgnore
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public OnPreferenceChangeListener onChangeListener;
|
||||||
|
|
||||||
|
public Preference(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
|
||||||
|
this.onChangeListener = onPreferenceChangeListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(CharSequence title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(CharSequence summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultValue(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean callChangeListener(Object newValue) {
|
||||||
|
return onChangeListener == null || onChangeListener.onPreferenceChange(this, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public String getDefaultValueType() {
|
||||||
|
return defaultValue.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public SharedPreferences getSharedPreferences() {
|
||||||
|
return sharedPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public void setSharedPreferences(SharedPreferences sharedPreferences) {
|
||||||
|
this.sharedPreferences = sharedPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnPreferenceChangeListener {
|
||||||
|
boolean onPreferenceChange(Preference preference, Object newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnPreferenceClickListener {
|
||||||
|
boolean onPreferenceClick(Preference preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public Object getCurrentValue() {
|
||||||
|
switch (getDefaultValueType()) {
|
||||||
|
case "String":
|
||||||
|
return sharedPreferences.getString(key, (String)defaultValue);
|
||||||
|
case "Boolean":
|
||||||
|
return sharedPreferences.getBoolean(key, (Boolean)defaultValue);
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unsupported type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public void saveNewValue(Object value) {
|
||||||
|
switch (getDefaultValueType()) {
|
||||||
|
case "String":
|
||||||
|
sharedPreferences.edit().putString(key, (String)value).apply();
|
||||||
|
break;
|
||||||
|
case "Boolean":
|
||||||
|
sharedPreferences.edit().putBoolean(key, (Boolean)value).apply();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unsupported type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PreferenceScreen extends Preference {
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
private List<Preference> preferences = new LinkedList<>();
|
||||||
|
|
||||||
|
public PreferenceScreen(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addPreference(Preference preference) {
|
||||||
|
// propagate own shared preferences
|
||||||
|
preference.setSharedPreferences(getSharedPreferences());
|
||||||
|
|
||||||
|
preferences.add(preference);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
public List<Preference> getPreferences(){
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class SwitchPreferenceCompat extends TwoStatePreference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/CheckBoxPreference.java
|
||||||
|
|
||||||
|
public SwitchPreferenceCompat(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package androidx.preference;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public class TwoStatePreference extends Preference {
|
||||||
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
|
public TwoStatePreference(Context context) { super(context); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isChecked() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setChecked(boolean checked) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public CharSequence getSummaryOn() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setSummaryOn(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public CharSequence getSummaryOff() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setSummaryOff(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean getDisableDependentsState() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setDisableDependentsState(boolean disableDependentsState) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
/** Tachidesk specific API */
|
||||||
|
@Override
|
||||||
|
public String getDefaultValueType() {
|
||||||
|
return "Boolean";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.squareup.duktape;
|
package com.squareup.duktape;
|
||||||
|
|
||||||
import kotlin.NotImplementedError;
|
import kotlin.NotImplementedError;
|
||||||
@@ -22,11 +23,18 @@ import javax.script.ScriptEngineManager;
|
|||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
|
||||||
/** A simple EMCAScript (Javascript) interpreter. */
|
/* Note (March 2021):
|
||||||
|
* The old implementation for duktape-android used the nashorn engine which is deprecated.
|
||||||
|
* This new implementation uses Mozilla's Rhino: https://github.com/mozilla/rhino
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple EMCAScript (Javascript) interpreter.
|
||||||
|
*/
|
||||||
public final class Duktape implements Closeable, AutoCloseable {
|
public final class Duktape implements Closeable, AutoCloseable {
|
||||||
|
|
||||||
private ScriptEngineManager factory = new ScriptEngineManager();
|
private ScriptEngineManager factory = new ScriptEngineManager();
|
||||||
private ScriptEngine engine = factory.getEngineByName("JavaScript");
|
private ScriptEngine engine = factory.getEngineByName("rhino");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new interpreter instance. Calls to this method <strong>must</strong> matched with
|
* Create a new interpreter instance. Calls to this method <strong>must</strong> matched with
|
||||||
@@ -38,17 +46,6 @@ public final class Duktape implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private Duktape() {}
|
private Duktape() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluate {@code script} and return a result. {@code fileName} will be used in error
|
|
||||||
* reporting. Note that the result must be one of the supported Java types or the call will
|
|
||||||
* return null.
|
|
||||||
*
|
|
||||||
* @throws DuktapeException if there is an error evaluating the script.
|
|
||||||
*/
|
|
||||||
public synchronized Object evaluate(String script, String fileName) {
|
|
||||||
throw new NotImplementedError("Not implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate {@code script} and return a result. Note that the result must be one of the
|
* Evaluate {@code script} and return a result. Note that the result must be one of the
|
||||||
* supported Java types or the call will return null.
|
* supported Java types or the call will return null.
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.squareup.duktape;
|
||||||
|
|
||||||
|
/* part of tachiyomi-extensions which is licensed under Apache License Version 2.0 */
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** This is the reference Duktape stub that tachiyomi's extensions depend on.
|
||||||
|
* Intended to be used as a reference.
|
||||||
|
*/
|
||||||
|
public class DuktapeStub implements Closeable {
|
||||||
|
|
||||||
|
public static Duktape create() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Object evaluate(String script) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized <T> void set(String name, Class<T> type, T object) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package rx.android.schedulers
|
package rx.android.schedulers
|
||||||
|
|
||||||
|
import rx.Scheduler
|
||||||
import rx.internal.schedulers.ImmediateScheduler
|
import rx.internal.schedulers.ImmediateScheduler
|
||||||
|
|
||||||
class AndroidSchedulers {
|
class AndroidSchedulers {
|
||||||
@@ -11,6 +12,7 @@ class AndroidSchedulers {
|
|||||||
/**
|
/**
|
||||||
* Simulated main thread scheduler
|
* Simulated main thread scheduler
|
||||||
*/
|
*/
|
||||||
fun mainThread() = mainThreadScheduler
|
@JvmStatic
|
||||||
|
fun mainThread(): Scheduler = mainThreadScheduler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,8 @@ class AndroidCompatInitializer {
|
|||||||
ApplicationInfoConfigModule.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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-13
@@ -38,7 +38,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
||||||
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
||||||
import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences;
|
import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences;
|
||||||
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
||||||
|
|
||||||
@@ -50,10 +50,9 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Custom context implementation.
|
* Custom context implementation.
|
||||||
*
|
*
|
||||||
* TODO Deal with packagemanager for extension sources
|
|
||||||
*/
|
*/
|
||||||
public class CustomContext extends Context implements DIAware {
|
public class CustomContext extends Context implements DIAware {
|
||||||
private DI kodein;
|
private final DI kodein;
|
||||||
public CustomContext() {
|
public CustomContext() {
|
||||||
this(KodeinGlobalHelper.kodein());
|
this(KodeinGlobalHelper.kodein());
|
||||||
}
|
}
|
||||||
@@ -165,23 +164,22 @@ public class CustomContext extends Context implements DIAware {
|
|||||||
/** Fake shared prefs! **/
|
/** Fake shared prefs! **/
|
||||||
private Map<String, SharedPreferences> prefs = new HashMap<>(); //Cache
|
private Map<String, SharedPreferences> prefs = new HashMap<>(); //Cache
|
||||||
|
|
||||||
private File sharedPrefsFileFromString(String s) {
|
|
||||||
return new File(androidFiles.getPrefsDir(), s + ".json");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized SharedPreferences getSharedPreferences(String s, int i) {
|
public synchronized SharedPreferences getSharedPreferences(String s, int i) {
|
||||||
SharedPreferences preferences = prefs.get(s);
|
SharedPreferences preferences = prefs.get(s);
|
||||||
//Create new shared preferences if one does not exist
|
//Create new shared preferences if one does not exist
|
||||||
if(preferences == null) {
|
if(preferences == null) {
|
||||||
preferences = getSharedPreferences(sharedPrefsFileFromString(s), i);
|
preferences = new JavaSharedPreferences(s);
|
||||||
prefs.put(s, preferences);
|
prefs.put(s, preferences);
|
||||||
}
|
}
|
||||||
return preferences;
|
return preferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPreferences getSharedPreferences(File file, int mode) {
|
@Override
|
||||||
return new JsonSharedPreferences(file);
|
public SharedPreferences getSharedPreferences(@NotNull File file, int mode) {
|
||||||
|
String path = file.getAbsolutePath().replace('\\', '/');
|
||||||
|
int firstSlash = path.indexOf("/");
|
||||||
|
return new JavaSharedPreferences(path.substring(firstSlash));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,8 +189,8 @@ public class CustomContext extends Context implements DIAware {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteSharedPreferences(String name) {
|
public boolean deleteSharedPreferences(String name) {
|
||||||
prefs.remove(name);
|
JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name);
|
||||||
return sharedPrefsFileFromString(name).delete();
|
return item.deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -735,4 +733,3 @@ public class CustomContext extends Context implements DIAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
package xyz.nulldev.androidcompat.io.sharedprefs
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import com.russhwolf.settings.ExperimentalSettingsApi
|
||||||
|
import com.russhwolf.settings.ExperimentalSettingsImplementation
|
||||||
|
import com.russhwolf.settings.JvmPreferencesSettings
|
||||||
|
import com.russhwolf.settings.serialization.decodeValue
|
||||||
|
import com.russhwolf.settings.serialization.encodeValue
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.builtins.SetSerializer
|
||||||
|
import kotlinx.serialization.builtins.nullable
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import java.util.prefs.PreferenceChangeListener
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
||||||
|
class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||||
|
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
|
||||||
|
private val preferences = JvmPreferencesSettings(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()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(key: String, defValue: String?): String? {
|
||||||
|
return if (defValue != null) {
|
||||||
|
preferences.getString(key, defValue)
|
||||||
|
} else {
|
||||||
|
preferences.getStringOrNull(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||||
|
try {
|
||||||
|
return if (defValues != null) {
|
||||||
|
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, defValues)
|
||||||
|
} else {
|
||||||
|
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, null)
|
||||||
|
}?.toMutableSet()
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
throw ClassCastException("$key was not a StringSet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(key: String, defValue: Int): Int {
|
||||||
|
return preferences.getInt(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(key: String, defValue: Long): Long {
|
||||||
|
return preferences.getLong(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFloat(key: String, defValue: Float): Float {
|
||||||
|
return preferences.getFloat(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBoolean(key: String, defValue: Boolean): Boolean {
|
||||||
|
return preferences.getBoolean(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contains(key: String): Boolean {
|
||||||
|
return key in preferences.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun edit(): SharedPreferences.Editor {
|
||||||
|
return Editor(preferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Editor(private val preferences: JvmPreferencesSettings) : SharedPreferences.Editor {
|
||||||
|
val itemsToAdd = mutableMapOf<String, Any>()
|
||||||
|
|
||||||
|
override fun putString(key: String, value: String?): SharedPreferences.Editor {
|
||||||
|
if (value != null) {
|
||||||
|
itemsToAdd[key] = value
|
||||||
|
} else {
|
||||||
|
remove(key)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putStringSet(
|
||||||
|
key: String,
|
||||||
|
values: MutableSet<String>?
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
if (values != null) {
|
||||||
|
itemsToAdd[key] = values
|
||||||
|
} else {
|
||||||
|
remove(key)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putInt(key: String, value: Int): SharedPreferences.Editor {
|
||||||
|
itemsToAdd[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putLong(key: String, value: Long): SharedPreferences.Editor {
|
||||||
|
itemsToAdd[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
|
||||||
|
itemsToAdd[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
|
||||||
|
itemsToAdd[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(key: String): SharedPreferences.Editor {
|
||||||
|
itemsToAdd.remove(key)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear(): SharedPreferences.Editor {
|
||||||
|
itemsToAdd.clear()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun commit(): Boolean {
|
||||||
|
addToPreferences()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply() {
|
||||||
|
addToPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addToPreferences() {
|
||||||
|
itemsToAdd.forEach { (key, value) ->
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||||
|
val javaListener = PreferenceChangeListener {
|
||||||
|
listener.onSharedPreferenceChanged(this, it.key)
|
||||||
|
}
|
||||||
|
listeners[listener] = javaListener
|
||||||
|
javaPreferences.addPreferenceChangeListener(javaListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||||
|
val registeredListener = listeners.remove(listener)
|
||||||
|
if (registeredListener != null) {
|
||||||
|
javaPreferences.removePreferenceChangeListener(registeredListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAll(): Boolean {
|
||||||
|
javaPreferences.removeNode()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -233,7 +233,7 @@ public class JsonSharedPreferences implements SharedPreferences {
|
|||||||
private JsonSharedPreferencesEditor() {
|
private JsonSharedPreferencesEditor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordChange(String key) {
|
private void recordChange(String key) {
|
||||||
if (!affectedKeys.contains(key)) {
|
if (!affectedKeys.contains(key)) {
|
||||||
affectedKeys.add(key);
|
affectedKeys.add(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ data class InstalledPackage(val root: File) {
|
|||||||
val icon = File(root, "icon.png")
|
val icon = File(root, "icon.png")
|
||||||
|
|
||||||
val info: PackageInfo
|
val info: PackageInfo
|
||||||
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(root, apk).also {
|
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
|
||||||
val parsed = ApkFile(apk)
|
val parsed = ApkFile(apk)
|
||||||
val dbFactory = DocumentBuilderFactory.newInstance()
|
val dbFactory = DocumentBuilderFactory.newInstance()
|
||||||
val dBuilder = dbFactory.newDocumentBuilder()
|
val dBuilder = dbFactory.newDocumentBuilder()
|
||||||
@@ -82,12 +82,14 @@ data class InstalledPackage(val root: File) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NodeList.toList(): List<Node> {
|
companion object {
|
||||||
val out = mutableListOf<Node>()
|
fun NodeList.toList(): List<Node> {
|
||||||
|
val out = mutableListOf<Node>()
|
||||||
|
|
||||||
for(i in 0 until length)
|
for (i in 0 until length)
|
||||||
out += item(i)
|
out += item(i)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import android.content.pm.PackageInfo
|
|||||||
import net.dongliu.apk.parser.bean.ApkMeta
|
import net.dongliu.apk.parser.bean.ApkMeta
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun ApkMeta.toPackageInfo(root: File, apk: File): PackageInfo {
|
fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
||||||
return PackageInfo().also {
|
return PackageInfo().also {
|
||||||
it.packageName = packageName
|
it.packageName = packageName
|
||||||
it.versionCode = versionCode.toInt()
|
it.versionCode = versionCode.toInt()
|
||||||
|
|||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
package xyz.nulldev.androidcompat.replace.java.text;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import com.ibm.icu.text.DisplayContext;
|
||||||
|
import com.ibm.icu.util.Currency;
|
||||||
|
import com.ibm.icu.util.CurrencyAmount;
|
||||||
|
import com.ibm.icu.util.ULocale;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.text.AttributedCharacterIterator;
|
||||||
|
import java.text.FieldPosition;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class NumberFormat extends java.text.NumberFormat {
|
||||||
|
private com.ibm.icu.text.NumberFormat delegate;
|
||||||
|
|
||||||
|
public NumberFormat(com.ibm.icu.text.NumberFormat delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(BigInteger number) {
|
||||||
|
return delegate.format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(BigDecimal number) {
|
||||||
|
return delegate.format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(com.ibm.icu.math.BigDecimal number) {
|
||||||
|
return delegate.format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(CurrencyAmount currAmt) {
|
||||||
|
return delegate.format(currAmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(number, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(currAmt, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number parse(String text, ParsePosition parsePosition) {
|
||||||
|
return delegate.parse(text, parsePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number parse(String text) throws ParseException {
|
||||||
|
return delegate.parse(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
|
||||||
|
return delegate.parseCurrency(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isParseIntegerOnly() {
|
||||||
|
return delegate.isParseIntegerOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParseIntegerOnly(boolean value) {
|
||||||
|
delegate.setParseIntegerOnly(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParseStrict(boolean value) {
|
||||||
|
delegate.setParseStrict(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isParseStrict() {
|
||||||
|
return delegate.isParseStrict();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContext(DisplayContext context) {
|
||||||
|
delegate.setContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayContext getContext(DisplayContext.Type type) {
|
||||||
|
return delegate.getContext(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.text.NumberFormat getInstance(Locale inLocale) {
|
||||||
|
return new NumberFormat(com.ibm.icu.text.NumberFormat.getInstance(inLocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getInstance(int style) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getInstance(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getInstance(Locale inLocale, int style) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getInstance(inLocale, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getNumberInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getNumberInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getIntegerInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getIntegerInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getCurrencyInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getCurrencyInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getPercentInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getPercentInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getScientificInstance(ULocale inLocale) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getScientificInstance(inLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Locale[] getAvailableLocales() {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getAvailableLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ULocale[] getAvailableULocales() {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getAvailableULocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object registerFactory(com.ibm.icu.text.NumberFormat.NumberFormatFactory factory) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.registerFactory(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean unregister(Object registryKey) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.unregister(registryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return delegate.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
return delegate.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGroupingUsed() {
|
||||||
|
return delegate.isGroupingUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupingUsed(boolean newValue) {
|
||||||
|
delegate.setGroupingUsed(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximumIntegerDigits() {
|
||||||
|
return delegate.getMaximumIntegerDigits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximumIntegerDigits(int newValue) {
|
||||||
|
delegate.setMaximumIntegerDigits(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinimumIntegerDigits() {
|
||||||
|
return delegate.getMinimumIntegerDigits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimumIntegerDigits(int newValue) {
|
||||||
|
delegate.setMinimumIntegerDigits(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximumFractionDigits() {
|
||||||
|
return delegate.getMaximumFractionDigits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximumFractionDigits(int newValue) {
|
||||||
|
delegate.setMaximumFractionDigits(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinimumFractionDigits() {
|
||||||
|
return delegate.getMinimumFractionDigits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimumFractionDigits(int newValue) {
|
||||||
|
delegate.setMinimumFractionDigits(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(Currency theCurrency) {
|
||||||
|
delegate.setCurrency(theCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.Currency getCurrency() {
|
||||||
|
return java.util.Currency.getInstance(delegate.getCurrency().getCurrencyCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingMode(int roundingMode) {
|
||||||
|
delegate.setRoundingMode(roundingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.NumberFormat getInstance(ULocale desiredLocale, int choice) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getInstance(desiredLocale, choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static String getPatternForStyle(ULocale forLocale, int choice) {
|
||||||
|
return com.ibm.icu.text.NumberFormat.getPatternForStyle(forLocale, choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ULocale getLocale(ULocale.Type type) {
|
||||||
|
return delegate.getLocale(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
|
||||||
|
return delegate.formatToCharacterIterator(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object parseObject(String source) throws ParseException {
|
||||||
|
return delegate.parseObject(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
+346
@@ -0,0 +1,346 @@
|
|||||||
|
package xyz.nulldev.androidcompat.replace.java.text;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import com.ibm.icu.text.DateFormatSymbols;
|
||||||
|
import com.ibm.icu.text.DisplayContext;
|
||||||
|
import com.ibm.icu.text.TimeZoneFormat;
|
||||||
|
import com.ibm.icu.util.ULocale;
|
||||||
|
import xyz.nulldev.androidcompat.replace.java.util.Calendar;
|
||||||
|
import xyz.nulldev.androidcompat.replace.java.util.TimeZone;
|
||||||
|
|
||||||
|
import java.text.*;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to switch to Android implementation
|
||||||
|
*/
|
||||||
|
public class SimpleDateFormat extends java.text.DateFormat {
|
||||||
|
private com.ibm.icu.text.SimpleDateFormat delegate;
|
||||||
|
|
||||||
|
public SimpleDateFormat() {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimpleDateFormat(com.ibm.icu.text.SimpleDateFormat delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern, Locale loc) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern, ULocale loc) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern, String override, ULocale loc) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, override, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern, DateFormatSymbols formatData) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, formatData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc) {
|
||||||
|
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, formatData, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static SimpleDateFormat getInstance(com.ibm.icu.util.Calendar.FormatConfiguration formatConfig) {
|
||||||
|
return new SimpleDateFormat(com.ibm.icu.text.SimpleDateFormat.getInstance(formatConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set2DigitYearStart(Date startDate) {
|
||||||
|
delegate.set2DigitYearStart(startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date get2DigitYearStart() {
|
||||||
|
return delegate.get2DigitYearStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContext(DisplayContext context) {
|
||||||
|
delegate.setContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer format(com.ibm.icu.util.Calendar cal, StringBuffer toAppendTo, FieldPosition pos) {
|
||||||
|
return delegate.format(cal, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberFormat(com.ibm.icu.text.NumberFormat newNumberFormat) {
|
||||||
|
delegate.setNumberFormat(newNumberFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(String text, com.ibm.icu.util.Calendar cal, ParsePosition parsePos) {
|
||||||
|
delegate.parse(text, cal, parsePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toPattern() {
|
||||||
|
return delegate.toPattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toLocalizedPattern() {
|
||||||
|
return delegate.toLocalizedPattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyPattern(String pat) {
|
||||||
|
delegate.applyPattern(pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyLocalizedPattern(String pat) {
|
||||||
|
delegate.applyLocalizedPattern(pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateFormatSymbols getDateFormatSymbols() {
|
||||||
|
return delegate.getDateFormatSymbols();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
|
||||||
|
delegate.setDateFormatSymbols(newFormatSymbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeZoneFormat getTimeZoneFormat() {
|
||||||
|
return delegate.getTimeZoneFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
|
||||||
|
delegate.setTimeZoneFormat(tzfmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
return delegate.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return delegate.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
|
||||||
|
return delegate.formatToCharacterIterator(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StringBuffer intervalFormatByAlgorithm(com.ibm.icu.util.Calendar fromCalendar, com.ibm.icu.util.Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) throws IllegalArgumentException {
|
||||||
|
return delegate.intervalFormatByAlgorithm(fromCalendar, toCalendar, appendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberFormat(String fields, com.ibm.icu.text.NumberFormat overrideNF) {
|
||||||
|
delegate.setNumberFormat(fields, overrideNF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.text.NumberFormat getNumberFormat(char field) {
|
||||||
|
return delegate.getNumberFormat(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
|
||||||
|
return delegate.format(date, toAppendTo, fieldPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date parse(String text) throws ParseException {
|
||||||
|
return delegate.parse(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date parse(String text, ParsePosition pos) {
|
||||||
|
return delegate.parse(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseObject(String source, ParsePosition pos) {
|
||||||
|
return delegate.parseObject(source, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getTimeInstance(int style, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getTimeInstance(style, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateInstance(int style, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateInstance(style, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateTimeInstance(int dateStyle, int timeStyle, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Locale[] getAvailableLocales() {
|
||||||
|
return com.ibm.icu.text.DateFormat.getAvailableLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ULocale[] getAvailableULocales() {
|
||||||
|
return com.ibm.icu.text.DateFormat.getAvailableULocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCalendar(java.util.Calendar newCalendar) {
|
||||||
|
com.ibm.icu.util.Calendar cal = com.ibm.icu.util.Calendar.getInstance(com.ibm.icu.util.TimeZone.getTimeZone(newCalendar.getTimeZone().getID()));
|
||||||
|
cal.setTimeInMillis(newCalendar.getTimeInMillis());
|
||||||
|
delegate.setCalendar(cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.util.Calendar getCalendar() {
|
||||||
|
return new Calendar(delegate.getCalendar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.text.NumberFormat getNumberFormat() {
|
||||||
|
return new NumberFormat(delegate.getNumberFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimeZone(java.util.TimeZone zone) {
|
||||||
|
delegate.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(zone.getID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.util.TimeZone getTimeZone() {
|
||||||
|
return new TimeZone(delegate.getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLenient(boolean lenient) {
|
||||||
|
delegate.setLenient(lenient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLenient() {
|
||||||
|
return delegate.isLenient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCalendarLenient(boolean lenient) {
|
||||||
|
delegate.setCalendarLenient(lenient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCalendarLenient() {
|
||||||
|
return delegate.isCalendarLenient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.text.DateFormat setBooleanAttribute(com.ibm.icu.text.DateFormat.BooleanAttribute key, boolean value) {
|
||||||
|
return delegate.setBooleanAttribute(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBooleanAttribute(com.ibm.icu.text.DateFormat.BooleanAttribute key) {
|
||||||
|
return delegate.getBooleanAttribute(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayContext getContext(DisplayContext.Type type) {
|
||||||
|
return delegate.getContext(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateInstance(com.ibm.icu.util.Calendar cal, int dateStyle, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateInstance(com.ibm.icu.util.Calendar cal, int dateStyle, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getTimeInstance(com.ibm.icu.util.Calendar cal, int timeStyle, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getTimeInstance(com.ibm.icu.util.Calendar cal, int timeStyle, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateTimeInstance(com.ibm.icu.util.Calendar cal, int dateStyle, int timeStyle, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateTimeInstance(com.ibm.icu.util.Calendar cal, int dateStyle, int timeStyle, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstance(com.ibm.icu.util.Calendar cal, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstance(cal, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstance(com.ibm.icu.util.Calendar cal, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstance(cal, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstance(com.ibm.icu.util.Calendar cal) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstance(cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateInstance(com.ibm.icu.util.Calendar cal, int dateStyle) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getTimeInstance(com.ibm.icu.util.Calendar cal, int timeStyle) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getDateTimeInstance(com.ibm.icu.util.Calendar cal, int dateStyle, int timeStyle) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(com.ibm.icu.util.Calendar cal, String skeleton, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(cal, skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(com.ibm.icu.util.Calendar cal, String skeleton, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(cal, skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getPatternInstance(com.ibm.icu.util.Calendar cal, String skeleton, Locale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getPatternInstance(cal, skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.text.DateFormat getPatternInstance(com.ibm.icu.util.Calendar cal, String skeleton, ULocale locale) {
|
||||||
|
return com.ibm.icu.text.DateFormat.getPatternInstance(cal, skeleton, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ULocale getLocale(ULocale.Type type) {
|
||||||
|
return delegate.getLocale(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseObject(String source) throws ParseException {
|
||||||
|
return delegate.parseObject(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
+294
@@ -0,0 +1,294 @@
|
|||||||
|
package xyz.nulldev.androidcompat.replace.java.util;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import com.ibm.icu.text.DateFormat;
|
||||||
|
import com.ibm.icu.util.ULocale;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class Calendar extends java.util.Calendar {
|
||||||
|
private com.ibm.icu.util.Calendar delegate;
|
||||||
|
|
||||||
|
public Calendar(com.ibm.icu.util.Calendar delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.Calendar getInstance() {
|
||||||
|
return new Calendar(com.ibm.icu.util.Calendar.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.Calendar getInstance(com.ibm.icu.util.TimeZone zone) {
|
||||||
|
return com.ibm.icu.util.Calendar.getInstance(zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.Calendar getInstance(Locale aLocale) {
|
||||||
|
return new Calendar(com.ibm.icu.util.Calendar.getInstance(aLocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.Calendar getInstance(ULocale locale) {
|
||||||
|
return com.ibm.icu.util.Calendar.getInstance(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.Calendar getInstance(com.ibm.icu.util.TimeZone zone, Locale aLocale) {
|
||||||
|
return com.ibm.icu.util.Calendar.getInstance(zone, aLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.Calendar getInstance(com.ibm.icu.util.TimeZone zone, ULocale locale) {
|
||||||
|
return com.ibm.icu.util.Calendar.getInstance(zone, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Locale[] getAvailableLocales() {
|
||||||
|
return com.ibm.icu.util.Calendar.getAvailableLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void computeTime() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void computeFields() {}
|
||||||
|
|
||||||
|
public static ULocale[] getAvailableULocales() {
|
||||||
|
return com.ibm.icu.util.Calendar.getAvailableULocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getKeywordValuesForLocale(String key, ULocale locale, boolean commonlyUsed) {
|
||||||
|
return com.ibm.icu.util.Calendar.getKeywordValuesForLocale(key, locale, commonlyUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeInMillis() {
|
||||||
|
return delegate.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimeInMillis(long millis) {
|
||||||
|
delegate.setTimeInMillis(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public int getRelatedYear() {
|
||||||
|
return delegate.getRelatedYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void setRelatedYear(int year) {
|
||||||
|
delegate.setRelatedYear(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return delegate.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEquivalentTo(com.ibm.icu.util.Calendar other) {
|
||||||
|
return delegate.isEquivalentTo(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean before(Object when) {
|
||||||
|
return delegate.before(when);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean after(Object when) {
|
||||||
|
return delegate.after(when);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActualMaximum(int field) {
|
||||||
|
return delegate.getActualMaximum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActualMinimum(int field) {
|
||||||
|
return delegate.getActualMinimum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void roll(int field, int amount) {
|
||||||
|
delegate.roll(field, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int field, int amount) {
|
||||||
|
delegate.add(field, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void roll(int field, boolean up) {
|
||||||
|
roll(field, up ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName(Locale loc) {
|
||||||
|
return delegate.getDisplayName(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName(ULocale loc) {
|
||||||
|
return delegate.getDisplayName(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(com.ibm.icu.util.Calendar that) {
|
||||||
|
return delegate.compareTo(that);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, Locale loc) {
|
||||||
|
return delegate.getDateTimeFormat(dateStyle, timeStyle, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, ULocale loc) {
|
||||||
|
return delegate.getDateTimeFormat(dateStyle, timeStyle, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static String getDateTimePattern(com.ibm.icu.util.Calendar cal, ULocale uLocale, int dateStyle) {
|
||||||
|
return com.ibm.icu.util.Calendar.getDateTimePattern(cal, uLocale, dateStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int fieldDifference(Date when, int field) {
|
||||||
|
return delegate.fieldDifference(when, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone(com.ibm.icu.util.TimeZone value) {
|
||||||
|
delegate.setTimeZone(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.util.TimeZone getTimeZone() {
|
||||||
|
return new TimeZone(delegate.getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLenient(boolean lenient) {
|
||||||
|
delegate.setLenient(lenient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLenient() {
|
||||||
|
return delegate.isLenient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepeatedWallTimeOption(int option) {
|
||||||
|
delegate.setRepeatedWallTimeOption(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRepeatedWallTimeOption() {
|
||||||
|
return delegate.getRepeatedWallTimeOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkippedWallTimeOption(int option) {
|
||||||
|
delegate.setSkippedWallTimeOption(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSkippedWallTimeOption() {
|
||||||
|
return delegate.getSkippedWallTimeOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirstDayOfWeek(int value) {
|
||||||
|
delegate.setFirstDayOfWeek(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFirstDayOfWeek() {
|
||||||
|
return delegate.getFirstDayOfWeek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMinimalDaysInFirstWeek(int value) {
|
||||||
|
delegate.setMinimalDaysInFirstWeek(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinimalDaysInFirstWeek() {
|
||||||
|
return delegate.getMinimalDaysInFirstWeek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinimum(int field) {
|
||||||
|
return delegate.getMinimum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximum(int field) {
|
||||||
|
return delegate.getMaximum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getGreatestMinimum(int field) {
|
||||||
|
return delegate.getGreatestMinimum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeastMaximum(int field) {
|
||||||
|
return delegate.getLeastMaximum(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public int getDayOfWeekType(int dayOfWeek) {
|
||||||
|
return delegate.getDayOfWeekType(dayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public int getWeekendTransition(int dayOfWeek) {
|
||||||
|
return delegate.getWeekendTransition(dayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWeekend(Date date) {
|
||||||
|
return delegate.isWeekend(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWeekend() {
|
||||||
|
return delegate.isWeekend();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
return delegate.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return delegate.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.Calendar.WeekData getWeekDataForRegion(String region) {
|
||||||
|
return com.ibm.icu.util.Calendar.getWeekDataForRegion(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.util.Calendar.WeekData getWeekData() {
|
||||||
|
return delegate.getWeekData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.util.Calendar setWeekData(com.ibm.icu.util.Calendar.WeekData wdata) {
|
||||||
|
return delegate.setWeekData(wdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFieldCount() {
|
||||||
|
return delegate.getFieldCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return delegate.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public boolean haveDefaultCentury() {
|
||||||
|
return delegate.haveDefaultCentury();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ULocale getLocale(ULocale.Type type) {
|
||||||
|
return delegate.getLocale(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
package xyz.nulldev.androidcompat.replace.java.util;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import com.ibm.icu.util.ULocale;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TimeZone extends java.util.TimeZone {
|
||||||
|
private com.ibm.icu.util.TimeZone delegate;
|
||||||
|
|
||||||
|
public TimeZone(com.ibm.icu.util.TimeZone delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
|
||||||
|
return delegate.getOffset(era, year, month, day, dayOfWeek, milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset(long date) {
|
||||||
|
return delegate.getOffset(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getOffset(long date, boolean local, int[] offsets) {
|
||||||
|
delegate.getOffset(date, local, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRawOffset(int offsetMillis) {
|
||||||
|
delegate.setRawOffset(offsetMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawOffset() {
|
||||||
|
return delegate.getRawOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getID() {
|
||||||
|
return delegate.getID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setID(String ID) {
|
||||||
|
delegate.setID(ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName(ULocale locale) {
|
||||||
|
return delegate.getDisplayName(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName(boolean daylight, int style, Locale locale) {
|
||||||
|
return delegate.getDisplayName(daylight, style, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName(boolean daylight, int style, ULocale locale) {
|
||||||
|
return delegate.getDisplayName(daylight, style, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDSTSavings() {
|
||||||
|
return delegate.getDSTSavings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useDaylightTime() {
|
||||||
|
return delegate.useDaylightTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean observesDaylightTime() {
|
||||||
|
return delegate.observesDaylightTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inDaylightTime(Date date) {
|
||||||
|
return delegate.inDaylightTime(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.TimeZone getTimeZone(String ID) {
|
||||||
|
return new TimeZone(com.ibm.icu.util.TimeZone.getTimeZone(ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.TimeZone getFrozenTimeZone(String ID) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getFrozenTimeZone(ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.ibm.icu.util.TimeZone getTimeZone(String ID, int type) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getTimeZone(ID, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultTimeZoneType(int type) {
|
||||||
|
com.ibm.icu.util.TimeZone.setDefaultTimeZoneType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDefaultTimeZoneType() {
|
||||||
|
return com.ibm.icu.util.TimeZone.getDefaultTimeZoneType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> getAvailableIDs(com.ibm.icu.util.TimeZone.SystemTimeZoneType zoneType, String region, Integer rawOffset) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getAvailableIDs(zoneType, region, rawOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getAvailableIDs(int rawOffset) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getAvailableIDs(rawOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getAvailableIDs(String country) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getAvailableIDs(country);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getAvailableIDs() {
|
||||||
|
return com.ibm.icu.util.TimeZone.getAvailableIDs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int countEquivalentIDs(String id) {
|
||||||
|
return com.ibm.icu.util.TimeZone.countEquivalentIDs(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getEquivalentID(String id, int index) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getEquivalentID(id, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.TimeZone getDefault() {
|
||||||
|
return new TimeZone(com.ibm.icu.util.TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefault(com.ibm.icu.util.TimeZone tz) {
|
||||||
|
com.ibm.icu.util.TimeZone.setDefault(tz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSameRules(com.ibm.icu.util.TimeZone other) {
|
||||||
|
return delegate.hasSameRules(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
return delegate.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return delegate.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTZDataVersion() {
|
||||||
|
return com.ibm.icu.util.TimeZone.getTZDataVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCanonicalID(String id) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getCanonicalID(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCanonicalID(String id, boolean[] isSystemID) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getCanonicalID(id, isSystemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRegion(String id) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getRegion(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getWindowsID(String id) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getWindowsID(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIDForWindowsID(String winid, String region) {
|
||||||
|
return com.ibm.icu.util.TimeZone.getIDForWindowsID(winid, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFrozen() {
|
||||||
|
return delegate.isFrozen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.util.TimeZone freeze() {
|
||||||
|
return delegate.freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.ibm.icu.util.TimeZone cloneAsThawed() {
|
||||||
|
return delegate.cloneAsThawed();
|
||||||
|
}
|
||||||
|
}
|
||||||
-3
@@ -1,6 +1,3 @@
|
|||||||
# Server ip and port bindings
|
|
||||||
ts.server.ip = 0.0.0.0
|
|
||||||
ts.server.port = 4567
|
|
||||||
|
|
||||||
# Allow/disallow preference changes (useful for demos)
|
# Allow/disallow preference changes (useful for demos)
|
||||||
ts.server.allowConfigChanges = true
|
ts.server.allowConfigChanges = true
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Code Of Conduct
|
||||||
|
- Don't be a dick.
|
||||||
|
|
||||||
|
# expanding the code of conduct!
|
||||||
|
The contents of this document is up for debate and improvement! Discussions on discord.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Contributing
|
||||||
|
## Where should I start?
|
||||||
|
Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
|
||||||
|
|
||||||
|
**Note to potential contributors:** Notify the developers on Suwayomi discord (#programming channel) 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.
|
||||||
|
|
||||||
|
## How does Tachidesk 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 apk extensions. All this concludes to serving a REST API to `webUI`.
|
||||||
|
2. **webUI:** A react SPA(`create-react-app`) project that works with the server to do the presentation.
|
||||||
|
|
||||||
|
## Why a web app?
|
||||||
|
This structure is chosen to
|
||||||
|
- Achieve the maximum multi-platform-ness
|
||||||
|
- Gives the ability to acces Tachidesk from a remote web browser e.g. your phone, tablet or smart TV
|
||||||
|
- Eaise development of alternative user intefaces for Tachidesk
|
||||||
|
|
||||||
|
## User Interfaces for Tachidesk server
|
||||||
|
Currently, there are three known interfaces for Tachidesk:
|
||||||
|
1. [webUI](https://github.com/Suwayomi/Tachidesk/tree/master/webUI/react): The react SPA that Tachidesk is traditionally shipped with.
|
||||||
|
2. [TachideskJUI](https://github.com/Suwayomi/TachideskJUI): A Jetbrains Compose Native app, re-uses components made for the upcoming Tachiyomi 1.x
|
||||||
|
3. [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stages of development.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
### Prerequisites
|
||||||
|
You need these software packages installed in order to build the project
|
||||||
|
### Server
|
||||||
|
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||||
|
- Android stubs jar
|
||||||
|
- Manual download: Download [android.jar](https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
||||||
|
- Automated download: Run `AndroidCompat/getAndroid.sh`(MacOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows) from project's root directory to download and rebuild the jar file from Google's repository.
|
||||||
|
### webUI
|
||||||
|
- Nodejs LTS or latest
|
||||||
|
- Yarn
|
||||||
|
- Git
|
||||||
|
### building the full-blown jar
|
||||||
|
Run `./gradlew :webUI:copyBuild server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
||||||
|
### building without `webUI` bundled(server only)
|
||||||
|
Delete the `server/src/main/resources/react` directory if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
||||||
|
### building the Windows package
|
||||||
|
First Build the jar, then cd into the `scripts` directory and run `./windows<bits>-bundler.sh` (or `./windows<bits>-bundler.ps1` if you are on windows), the resulting built zip package file will be `server/build/Tachidesk-vX.Y.Z-rxxx-win64.zip`.
|
||||||
|
## Running in development mode
|
||||||
|
First satisfy [the prerequisites](#prerequisites)
|
||||||
|
### server
|
||||||
|
run `./gradlew :server:run --stacktrace` to run the server
|
||||||
|
### webUI
|
||||||
|
How to do it is described in `webUI/react/README.md` but for short,
|
||||||
|
first cd into `webUI/react` then run `yarn` to install the node modules(do this only once)
|
||||||
|
then `yarn start` to start the development server, if a new browser window doesn't get opened automatically,
|
||||||
|
then open `http://127.0.0.1:3000` in a modern browser. This is a `create-react-app` project
|
||||||
|
and supports HMR and all the other goodies you'll need.
|
||||||
|
|
||||||
@@ -1,66 +1,91 @@
|
|||||||
|
|
||||||
|
| Build | Stable | Preview | Support Server |
|
||||||
|
|-------|----------|---------|---------|
|
||||||
|
|  | [](https://github.com/Suwayomi/Tachidesk/releases) | [](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [](https://discord.gg/DDZdqZWaHA) |
|
||||||
|
|
||||||
# Tachidesk
|
# Tachidesk
|
||||||
|
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
|
||||||
|
|
||||||
A free and open source manga reader that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
|
A free and open source manga reader that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
|
||||||
|
|
||||||
Tachidesk is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it.
|
Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
|
||||||
|
|
||||||
|
Tachidesk 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 read and write Tachiyomi compatible backups and syncing is a planned feature.
|
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
|
||||||
|
|
||||||
## How do I run the thing?
|
**Tachidesk needs serious front-end dev help for it's reader and other parts, if you like the app and want to see it become better please don't hesitate to contribute some code!**
|
||||||
#### Prerequisites
|
|
||||||
You should have java 8 or newer and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
|
|
||||||
|
|
||||||
#### Running pre-built jar packages
|
|
||||||
Download the latest (or a working more stable) release from [the repo branch](https://github.com/AriaMoradi/Tachidesk/tree/repo) or obtain it from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
|
|
||||||
|
|
||||||
Double click on the jar file or run `java -jar Tachidesk-latest.jar` or `java -jar Tachidesk-vX.Y.Z-rxxx.jar`
|
|
||||||
|
|
||||||
The server will be running on `http://localhost:4567` open this url in your browser.
|
|
||||||
|
|
||||||
#### Running on Docker
|
|
||||||
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
|
|
||||||
|
|
||||||
## Building from source
|
|
||||||
### Get Android stubs jar
|
|
||||||
#### Manual download
|
|
||||||
Download [android.jar](https://raw.githubusercontent.com/AriaMoradi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
|
||||||
#### Building from source(needs `bash`, `curl`, `base64`, `zip` to work)
|
|
||||||
Run `scripts/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
|
|
||||||
### building the jar
|
|
||||||
Run `./gradlew shadowJar` the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
|
|
||||||
## Running for development purposes
|
|
||||||
### `server` module
|
|
||||||
Run `./gradlew :server:run -x :webUI:copyBuild --stacktrace` to run the server
|
|
||||||
### `webUI` module
|
|
||||||
How to do it is described in `webUI/react/README.md` but for short,
|
|
||||||
first cd into `webUI/react` then run `yarn` to install the node modules(do this only once)
|
|
||||||
then `yarn start` to start the client if a new browser window doesn't start automatically,
|
|
||||||
then open `http://127.0.0.1:3000` in a modern browser. This is a `create-react-app` project
|
|
||||||
and supports HMR and all the other goodies you'll need.
|
|
||||||
|
|
||||||
## Is this application usable? Should I test it?
|
## Is this application usable? Should I test it?
|
||||||
If you'd ask me, I'd tell you If you want to read your manga **online** from tachiyomi or in one place and bypass all the ads, you can use Tachidesk.
|
Here is a list of current features:
|
||||||
|
|
||||||
There are almost no quality of life features, including no library, no downloading for offline enjoyment and sadly no MangaDex search.
|
- 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.
|
||||||
|
- Searching and browsing installed sources.
|
||||||
|
- A decent chapter reader.
|
||||||
|
- Ability to download Manga for offline read
|
||||||
|
- Backup and restore support powered by Tachiyomi Legacy Backups
|
||||||
|
|
||||||
Anyways, for more info checkout [finished milestone #1](https://github.com/AriaMoradi/Tachidesk/issues/2) and [milestone #2](https://github.com/AriaMoradi/Tachidesk/projects/1) to see what's implemented.
|
**Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting) if it happens.
|
||||||
|
|
||||||
## How does it work?
|
## Downloading and Running the app
|
||||||
This project has two components:
|
### All Operating Systems
|
||||||
1. **server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run apk extensions. All this concludes to serving a REST API to `webUI`.
|
You should have The Java Runtime Environment(JRE) 8 or newer and a modern browser installed(Google is your friend for seeking assitance). Also an internet connection is required as almost everything this app does is downloading stuff.
|
||||||
2. **webUI:** A react SPA project that works with the server to do the presentation.
|
|
||||||
|
Download the latest "Stable" jar release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||||
|
|
||||||
|
Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxx.jar` (or `java -jar Tachidesk-latest.jar` if you have the latest preview) from a Terminal/Command Prompt window to run the app which will open a new browser window automatically. Also the System Tray Icon is your friend if you need to open the browser window again or close Tachidesk.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
Download the latest "Stable" win32 or win64 (depending on your system, usually you want win64) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||||
|
|
||||||
|
The Windows specific build has java bundled inside, so you don't have to install java to use it. Unzip `Tachidesk-vX.Y.Z-rxxx-win64.zip` and run one of the Launcher files depending on what you want(see bellow). The rest works like the previous section.
|
||||||
|
#### Windows Launchers
|
||||||
|
- `Tachidesk Electron Launcher.bat`: Launches Tachidesk inside Electron as a desktop applicaton
|
||||||
|
- `Tachidesk Browser Launcher.bat`: Launches Tachidesk in a browser window
|
||||||
|
- `Tachidesk Debug Launcher.bat`: Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
You can install Tachidesk from the AUR
|
||||||
|
```
|
||||||
|
yay -S tachidesk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
Check our Offical Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) or use [arbuilder's](https://github.com/arbuilder/Tachidesk-docker) tachidesk docker repo for installation. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default the server will be running on http://localhost:4567 open this url in your browser.
|
||||||
|
|
||||||
|
Install from the command line:
|
||||||
|
```
|
||||||
|
$ docker pull ghcr.io/suwayomi/tachidesk
|
||||||
|
```
|
||||||
|
Run Container from the command line:
|
||||||
|
```
|
||||||
|
$ docker run -p 4567:4567 ghcr.io/suwayomi/tachidesk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Tachidesk Remotely
|
||||||
|
You can run Tachidesk on your computer or a server and connect to it remotely through the web interface with a web browser on any device including a mobile or tablet or even your smart TV!, this method of using Tachidesk is only recommended if you are a power user and know what you are doing.
|
||||||
|
|
||||||
|
## Troubleshooting and Support
|
||||||
|
See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
|
||||||
|
|
||||||
|
## Contributing and Technical info
|
||||||
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
The `AndroidCompat` module and `scripts/getAndroid.sh` 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`.
|
This project is a spiritual successor of [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server), Many of the ideas and the groundwork adopted in this project comes from TachiWeb.
|
||||||
|
|
||||||
|
The `AndroidCompat` module was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
|
||||||
|
|
||||||
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
||||||
|
|
||||||
Changes to both codebases is licensed under `MPL v. 2.0` as the rest of this project.
|
You can obtain a copy of `Apache License Version 2.0` from http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
You can obtain a copy of the license from http://www.apache.org/licenses/LICENSE-2.0
|
Changes to both codebases is licensed under `MPL v. 2.0` as the rest of this project.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2020-2021 Aria Moradi and contributors
|
Copyright (C) Contributors to the Suwayomi project
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
|||||||
+41
-30
@@ -1,33 +1,31 @@
|
|||||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.jetbrains.kotlin.jvm") version "1.4.21" apply false // Also in buildSrc Config.kt
|
kotlin("jvm") version "1.4.32"
|
||||||
id("java")
|
kotlin("plugin.serialization") version "1.4.32" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "xyz.nulldev.ts"
|
group = "suwayomi"
|
||||||
|
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven("https://maven.google.com/")
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||||
maven("https://dl.bintray.com/inorichi/maven")
|
|
||||||
maven("https://dl.google.com/dl/android/maven2/")
|
maven("https://dl.google.com/dl/android/maven2/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val javaProjects = listOf(
|
val projects = listOf(
|
||||||
project(":AndroidCompat"),
|
project(":AndroidCompat"),
|
||||||
project(":AndroidCompat:Config"),
|
project(":AndroidCompat:Config"),
|
||||||
project(":server")
|
project(":server")
|
||||||
)
|
)
|
||||||
|
|
||||||
configure(javaProjects) {
|
configure(projects) {
|
||||||
apply(plugin = "java")
|
|
||||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -35,46 +33,59 @@ configure(javaProjects) {
|
|||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION))
|
implementation(kotlin("reflect"))
|
||||||
testImplementation(kotlin("test", version = "1.4.21"))
|
testImplementation(kotlin("test-junit5"))
|
||||||
}
|
|
||||||
}
|
// coroutines
|
||||||
|
val coroutinesVersion = "1.4.3"
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||||
|
|
||||||
|
val kotlinSerializationVersion = "1.1.0"
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
||||||
|
|
||||||
configure(listOf(
|
|
||||||
project(":AndroidCompat"),
|
|
||||||
project(":server"),
|
|
||||||
project(":AndroidCompat:Config")
|
|
||||||
|
|
||||||
)) {
|
|
||||||
dependencies {
|
|
||||||
// Dependency Injection
|
// Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di-conf-jvm:7.1.0")
|
implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0")
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||||
implementation("org.slf4j:slf4j-simple:1.7.30")
|
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
implementation("io.github.microutils:kotlin-logging:2.0.3")
|
implementation("io.github.microutils:kotlin-logging:2.0.6")
|
||||||
|
|
||||||
// RxJava
|
// ReactiveX
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
implementation("io.reactivex:rxkotlin:1.0.0")
|
implementation("io.reactivex:rxkotlin:1.0.0")
|
||||||
|
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||||
|
|
||||||
// JSoup
|
// JSoup
|
||||||
implementation("org.jsoup:jsoup:1.13.1")
|
implementation("org.jsoup:jsoup:1.13.1")
|
||||||
|
|
||||||
// Kotlin
|
|
||||||
implementation(kotlin("reflect", version = "1.4.21"))
|
|
||||||
|
|
||||||
// dependency of :AndroidCompat:Config
|
// dependency of :AndroidCompat:Config
|
||||||
implementation("com.typesafe:config:1.4.0")
|
implementation("com.typesafe:config:1.4.1")
|
||||||
|
implementation("io.github.config4k:config4k:0.4.2")
|
||||||
|
|
||||||
|
// to get application content root
|
||||||
|
implementation("net.harawata:appdirs:1.2.1")
|
||||||
|
|
||||||
|
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
|
||||||
|
implementation("com.github.DexPatcher.dex2jar:dex-tools:v2.1-20190905-lanchon")
|
||||||
|
|
||||||
|
// APK parser
|
||||||
|
implementation("net.dongliu:apk-parser:2.6.10")
|
||||||
|
|
||||||
|
// Jackson
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-annotations:2.10.3")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
start "" jre/bin/javaw -jar Tachidesk.jar
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jre\bin\java -Dsuwayomi.tachidesk.server.debugLogsEnabled=true -jar Tachidesk.jar
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jre\bin\javaw "-Dsuwayomi.tachidesk.server.webInterface=electron" "-Dsuwayomi.tachidesk.server.electronPath=electron/electron.exe" -jar Tachidesk.jar
|
||||||
Executable
+82
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright (C) Contributors to the Suwayomi project
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
electron_version="v12.0.9"
|
||||||
|
|
||||||
|
if [ $1 = "win32" ]; then
|
||||||
|
jre="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
|
||||||
|
arch="win32"
|
||||||
|
electron="electron-$electron_version-win32-ia32.zip"
|
||||||
|
else
|
||||||
|
jre="OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip"
|
||||||
|
arch="win64"
|
||||||
|
electron="electron-$electron_version-win32-x64.zip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
jre_dir="jdk8u292-b10-jre"
|
||||||
|
|
||||||
|
echo "creating windows bundle"
|
||||||
|
|
||||||
|
jar=$(ls ../server/build/*.jar | tail -n1)
|
||||||
|
jar_name=$(echo $jar | cut -d'/' -f4)
|
||||||
|
release_name=$(echo $jar_name | sed 's/.jar//')-$arch
|
||||||
|
|
||||||
|
|
||||||
|
# make release dir
|
||||||
|
mkdir $release_name
|
||||||
|
|
||||||
|
|
||||||
|
echo "Dealing with jre..."
|
||||||
|
if [ ! -f $jre ]; then
|
||||||
|
curl -L "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/$jre" -o $jre
|
||||||
|
fi
|
||||||
|
unzip $jre
|
||||||
|
mv $jre_dir $release_name/jre
|
||||||
|
|
||||||
|
echo "Dealing with electron"
|
||||||
|
if [ ! -f $electron ]; then
|
||||||
|
curl -L "https://github.com/electron/electron/releases/download/$electron_version/$electron" -o $electron
|
||||||
|
fi
|
||||||
|
unzip $electron -d $release_name/electron
|
||||||
|
|
||||||
|
# change electron's icon
|
||||||
|
rcedit="rcedit-x86.exe"
|
||||||
|
if [ ! -f $rcedit ]; then
|
||||||
|
curl -L "https://github.com/electron/rcedit/releases/download/v1.1.1/$rcedit" -o $rcedit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if running under github actions
|
||||||
|
if [ $CI = true ]; then
|
||||||
|
# change electron executable's icon
|
||||||
|
sudo dpkg --add-architecture i386
|
||||||
|
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
|
||||||
|
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
|
||||||
|
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
|
||||||
|
sudo apt install --install-recommends winehq-stable
|
||||||
|
fi
|
||||||
|
# this script assumes that wine is installed here on out
|
||||||
|
|
||||||
|
WINEARCH=win32 wine $rcedit $release_name/electron/electron.exe --set-icon ../server/src/main/resources/icon/faviconlogo.ico
|
||||||
|
|
||||||
|
# copy artifacts
|
||||||
|
cp $jar $release_name/Tachidesk.jar
|
||||||
|
cp "resources/Tachidesk Browser Launcher.bat" $release_name
|
||||||
|
cp "resources/Tachidesk Debug Launcher.bat" $release_name
|
||||||
|
cp "resources/Tachidesk Electron Launcher.bat" $release_name
|
||||||
|
|
||||||
|
zip_name=$release_name.zip
|
||||||
|
zip -9 -r $zip_name $release_name
|
||||||
|
|
||||||
|
rm -rf $release_name
|
||||||
|
|
||||||
|
# clean up from possible previous runs
|
||||||
|
if [ -f ../server/build/$zip_name ]; then
|
||||||
|
rm ../server/build/$zip_name
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv $zip_name ../server/build/
|
||||||
+144
-100
@@ -1,101 +1,95 @@
|
|||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.jmailen.gradle.kotlinter.tasks.FormatTask
|
||||||
|
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id("org.jetbrains.kotlin.jvm") version "1.4.21"
|
|
||||||
application
|
application
|
||||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
id("com.github.johnrengelman.shadow") version "7.0.0"
|
||||||
id("org.jmailen.kotlinter") version "3.3.0"
|
id("org.jmailen.kotlinter") version "3.4.3"
|
||||||
|
id("de.fuerstenau.buildconfig") version "1.1.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
val TachideskVersion = "v0.1.2"
|
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
maven {
|
||||||
jcenter()
|
url = uri("https://repo1.maven.org/maven2/")
|
||||||
|
}
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://jitpack.io")
|
url = uri("https://jitpack.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
// okhttp
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
val okhttpVersion = "4.9.1" // version is locked by Tachiyomi extensions
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okio:okio:2.10.0")
|
||||||
|
|
||||||
|
// Javalin api
|
||||||
|
implementation("io.javalin:javalin:3.13.6")
|
||||||
|
// jackson version is tied to javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
|
||||||
|
|
||||||
|
// Exposed ORM
|
||||||
|
val exposedVersion = "0.31.1"
|
||||||
|
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
|
||||||
|
// current database driver
|
||||||
|
implementation("com.h2database:h2:1.4.200")
|
||||||
|
|
||||||
|
// Exposed Migrations
|
||||||
|
val exposedMigrationsVersion = "3.1.0"
|
||||||
|
implementation("com.github.Suwayomi:exposed-migrations:$exposedMigrationsVersion")
|
||||||
|
|
||||||
|
// tray icon
|
||||||
|
implementation("com.dorkbox:SystemTray:4.1")
|
||||||
|
implementation("com.dorkbox:Utilities:1.9")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
|
||||||
|
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.9.1")
|
||||||
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
|
implementation("org.jsoup:jsoup:1.13.1")
|
||||||
|
implementation("com.google.code.gson:gson:2.8.6")
|
||||||
|
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||||
|
|
||||||
|
|
||||||
|
// asm for fixing SimpleDateFormat (must match Dex2Jar version)
|
||||||
|
implementation("org.ow2.asm:asm-debug-all:5.0.3")
|
||||||
|
|
||||||
|
// extracting zip files
|
||||||
|
implementation("net.lingala.zip4j:zip4j:2.9.0")
|
||||||
|
|
||||||
// Source models and interfaces from Tachiyomi 1.x
|
// Source models and interfaces from Tachiyomi 1.x
|
||||||
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
|
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
|
||||||
// implementation("tachiyomi.sourceapi:source-api:1.1")
|
// implementation("tachiyomi.sourceapi:source-api:1.1")
|
||||||
|
|
||||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
|
||||||
|
|
||||||
val okhttp_version = "4.10.0-RC1"
|
|
||||||
implementation("com.squareup.okhttp3:okhttp:$okhttp_version")
|
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:$okhttp_version")
|
|
||||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version")
|
|
||||||
implementation("com.squareup.okio:okio:2.9.0")
|
|
||||||
|
|
||||||
|
|
||||||
// retrofit
|
|
||||||
val retrofit_version = "2.9.0"
|
|
||||||
implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
|
|
||||||
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0")
|
|
||||||
implementation("com.squareup.retrofit2:converter-gson:$retrofit_version")
|
|
||||||
implementation("com.squareup.retrofit2:adapter-rxjava:$retrofit_version")
|
|
||||||
|
|
||||||
|
|
||||||
// reactivex
|
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
|
||||||
// implementation("io.reactivex:rxandroid:1.2.1")
|
|
||||||
// implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
|
||||||
// implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
|
|
||||||
implementation("com.google.code.gson:gson:2.8.6")
|
|
||||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
|
||||||
|
|
||||||
implementation("org.jsoup:jsoup:1.13.1")
|
|
||||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
|
||||||
implementation("com.squareup.duktape:duktape-android:1.3.0")
|
|
||||||
|
|
||||||
|
|
||||||
val coroutinesVersion = "1.3.9"
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
|
||||||
|
|
||||||
// dex2jar
|
|
||||||
implementation(fileTree("lib/dex2jar/"))
|
|
||||||
|
|
||||||
// api
|
|
||||||
implementation("io.javalin:javalin:3.12.0")
|
|
||||||
implementation("org.slf4j:slf4j-simple:1.8.0-beta4")
|
|
||||||
implementation("org.slf4j:slf4j-api:1.8.0-beta4")
|
|
||||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
|
|
||||||
|
|
||||||
// to get application content root
|
|
||||||
implementation("net.harawata:appdirs:1.2.0")
|
|
||||||
|
|
||||||
// Exposed ORM
|
|
||||||
val exposed_version = "0.28.1"
|
|
||||||
implementation ("org.jetbrains.exposed:exposed-core:$exposed_version")
|
|
||||||
implementation ("org.jetbrains.exposed:exposed-dao:$exposed_version")
|
|
||||||
implementation ("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
|
||||||
implementation ("org.xerial:sqlite-jdbc:3.30.1")
|
|
||||||
|
|
||||||
// AndroidCompat
|
// AndroidCompat
|
||||||
implementation(project(":AndroidCompat"))
|
implementation(project(":AndroidCompat"))
|
||||||
implementation(project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
|
// uncomment to test extensions directly
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
// implementation(fileTree("lib/"))
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MainClass = "suwayomi.tachidesk.MainKt"
|
||||||
application {
|
application {
|
||||||
val name = "ir.armor.tachidesk.Main"
|
mainClass.set(MainClass)
|
||||||
mainClass.set(name)
|
|
||||||
|
|
||||||
// Required by ShadowJar.
|
// for testing electron
|
||||||
mainClassName = name
|
// applicationDefaultJvmArgs = listOf(
|
||||||
|
// "-Dsuwayomi.tachidesk.webInterface=electron",
|
||||||
|
// "-Dsuwayomi.tachidesk.electronPath=/usr/bin/electron"
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -106,50 +100,100 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val TachideskRevision = Runtime
|
// should be bumped with each stable release
|
||||||
|
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.4"
|
||||||
|
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r20"
|
||||||
|
|
||||||
|
// counts commit count on master
|
||||||
|
val tachideskRevision = runCatching {
|
||||||
|
System.getenv("ProductRevision") ?: Runtime
|
||||||
.getRuntime()
|
.getRuntime()
|
||||||
.exec("git rev-list master --count")
|
.exec("git rev-list HEAD --count")
|
||||||
.let { process ->
|
.let { process ->
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
val output = process.inputStream.use {
|
val output = process.inputStream.use {
|
||||||
it.bufferedReader().use(BufferedReader::readText)
|
it.bufferedReader().use(BufferedReader::readText)
|
||||||
}
|
}
|
||||||
process.destroy()
|
process.destroy()
|
||||||
"r"+output.trim()
|
"r" + output.trim()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}.getOrDefault("r0")
|
||||||
|
|
||||||
|
buildConfig {
|
||||||
|
clsName = "BuildConfig"
|
||||||
|
packageName = "suwayomi.tachidesk.server"
|
||||||
|
|
||||||
|
|
||||||
|
buildConfigField("String", "NAME", rootProject.name)
|
||||||
|
buildConfigField("String", "VERSION", tachideskVersion)
|
||||||
|
buildConfigField("String", "REVISION", tachideskRevision)
|
||||||
|
buildConfigField("String", "BUILD_TYPE", if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview")
|
||||||
|
buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
|
||||||
|
|
||||||
|
|
||||||
|
buildConfigField("String", "WEBUI_REPO", "https://github.com/Suwayomi/Tachidesk-WebUI-preview")
|
||||||
|
buildConfigField("String", "WEBUI_TAG", webUIRevisionTag)
|
||||||
|
|
||||||
|
|
||||||
|
buildConfigField("String", "GITHUB", "https://github.com/Suwayomi/Tachidesk")
|
||||||
|
buildConfigField("String", "DISCORD", "https://discord.gg/DDZdqZWaHA")
|
||||||
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
jar {
|
shadowJar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
mapOf(
|
mapOf(
|
||||||
"Main-Class" to "com.example.MainKt", //will make your jar (produced by jar task) runnable
|
"Main-Class" to MainClass,
|
||||||
"ImplementationTitle" to project.name,
|
"Implementation-Title" to rootProject.name,
|
||||||
"Implementation-Version" to project.version)
|
"Implementation-Vendor" to "The Suwayomi Project",
|
||||||
|
"Specification-Version" to tachideskVersion,
|
||||||
|
"Implementation-Version" to tachideskRevision
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
archiveBaseName.set(rootProject.name)
|
||||||
|
archiveVersion.set(tachideskVersion)
|
||||||
|
archiveClassifier.set(tachideskRevision)
|
||||||
|
}
|
||||||
|
withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
freeCompilerArgs = listOf(
|
||||||
|
"-Xopt-in=kotlin.RequiresOptIn",
|
||||||
|
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shadowJar {
|
|
||||||
manifest.inheritFrom(jar.get().manifest) //will make your shadowJar (produced by jar task) runnable
|
test {
|
||||||
archiveBaseName.set("Tachidesk")
|
useJUnit()
|
||||||
archiveVersion.set(TachideskVersion)
|
}
|
||||||
archiveClassifier.set(TachideskRevision)
|
|
||||||
|
withType<ShadowJar> {
|
||||||
|
destinationDirectory.set(File("$rootDir/server/build"))
|
||||||
|
dependsOn("formatKotlin", "lintKotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
named("run") {
|
||||||
|
dependsOn("formatKotlin", "lintKotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
named<Copy>("processResources") {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
mustRunAfter("downloadWebUI")
|
||||||
|
}
|
||||||
|
|
||||||
|
register<de.undercouch.gradle.tasks.download.Download>("downloadWebUI") {
|
||||||
|
src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip")
|
||||||
|
dest("src/main/resources/WebUI.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
withType<LintTask> {
|
||||||
|
source(files("src/kotlin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
withType<FormatTask> {
|
||||||
|
source(files("src/kotlin"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<ShadowJar> {
|
|
||||||
destinationDir = File("$rootDir/server/build")
|
|
||||||
dependsOn("lintKotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named("processResources") {
|
|
||||||
dependsOn(":webUI:copyBuild")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named("run") {
|
|
||||||
dependsOn("formatKotlin", "lintKotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,252 +0,0 @@
|
|||||||
package ir.armor.tachidesk;
|
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.NamedNodeMap;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
public class APKExtractor {
|
|
||||||
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
|
|
||||||
// such as for AndroidManifest.xml in .apk files
|
|
||||||
public static int endDocTag = 0x00100101;
|
|
||||||
public static int startTag = 0x00100102;
|
|
||||||
public static int endTag = 0x00100103;
|
|
||||||
|
|
||||||
static void prt(String str) {
|
|
||||||
//System.err.print(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String decompressXML(byte[] xml) {
|
|
||||||
|
|
||||||
StringBuilder finalXML = new StringBuilder();
|
|
||||||
|
|
||||||
// Compressed XML file/bytes starts with 24x bytes of data,
|
|
||||||
// 9 32 bit words in little endian order (LSB first):
|
|
||||||
// 0th word is 03 00 08 00
|
|
||||||
// 3rd word SEEMS TO BE: Offset at then of StringTable
|
|
||||||
// 4th word is: Number of strings in string table
|
|
||||||
// WARNING: Sometime I indiscriminently display or refer to word in
|
|
||||||
// little endian storage format, or in integer format (ie MSB first).
|
|
||||||
int numbStrings = LEW(xml, 4 * 4);
|
|
||||||
|
|
||||||
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
|
|
||||||
// of the length/string data in the StringTable.
|
|
||||||
int sitOff = 0x24; // Offset of start of StringIndexTable
|
|
||||||
|
|
||||||
// StringTable, each string is represented with a 16 bit little endian
|
|
||||||
// character count, followed by that number of 16 bit (LE) (Unicode)
|
|
||||||
// chars.
|
|
||||||
int stOff = sitOff + numbStrings * 4; // StringTable follows
|
|
||||||
// StrIndexTable
|
|
||||||
|
|
||||||
// XMLTags, The XML tag tree starts after some unknown content after the
|
|
||||||
// StringTable. There is some unknown data after the StringTable, scan
|
|
||||||
// forward from this point to the flag for the start of an XML start
|
|
||||||
// tag.
|
|
||||||
int xmlTagOff = LEW(xml, 3 * 4); // Start from the offset in the 3rd
|
|
||||||
// word.
|
|
||||||
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal
|
|
||||||
// int)
|
|
||||||
for (int ii = xmlTagOff; ii < xml.length - 4; ii += 4) {
|
|
||||||
if (LEW(xml, ii) == startTag) {
|
|
||||||
xmlTagOff = ii;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} // end of hack, scanning for start of first start tag
|
|
||||||
|
|
||||||
// XML tags and attributes:
|
|
||||||
// Every XML start and end tag consists of 6 32 bit words:
|
|
||||||
// 0th word: 02011000 for startTag and 03011000 for endTag
|
|
||||||
// 1st word: a flag?, like 38000000
|
|
||||||
// 2nd word: Line of where this tag appeared in the original source file
|
|
||||||
// 3rd word: FFFFFFFF ??
|
|
||||||
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
|
|
||||||
// 5th word: StringIndex of Element Name
|
|
||||||
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
|
|
||||||
|
|
||||||
// Start tags (not end tags) contain 3 more words:
|
|
||||||
// 6th word: 14001400 meaning??
|
|
||||||
// 7th word: Number of Attributes that follow this tag(follow word 8th)
|
|
||||||
// 8th word: 00000000 meaning??
|
|
||||||
|
|
||||||
// Attributes consist of 5 words:
|
|
||||||
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
|
|
||||||
// 1st word: StringIndex of Attribute Name
|
|
||||||
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId
|
|
||||||
// used
|
|
||||||
// 3rd word: Flags?
|
|
||||||
// 4th word: str ind of attr value again, or ResourceId of value
|
|
||||||
|
|
||||||
// TMP, dump string table to tr for debugging
|
|
||||||
// tr.addSelect("strings", null);
|
|
||||||
// for (int ii=0; ii<numbStrings; ii++) {
|
|
||||||
// // Length of string starts at StringTable plus offset in StrIndTable
|
|
||||||
// String str = compXmlString(xml, sitOff, stOff, ii);
|
|
||||||
// tr.add(String.valueOf(ii), str);
|
|
||||||
// }
|
|
||||||
// tr.parent();
|
|
||||||
|
|
||||||
// Step through the XML tree element tags and attributes
|
|
||||||
int off = xmlTagOff;
|
|
||||||
int indent = 0;
|
|
||||||
int startTagLineNo = -2;
|
|
||||||
while (off < xml.length) {
|
|
||||||
int tag0 = LEW(xml, off);
|
|
||||||
// int tag1 = LEW(xml, off+1*4);
|
|
||||||
int lineNo = LEW(xml, off + 2 * 4);
|
|
||||||
// int tag3 = LEW(xml, off+3*4);
|
|
||||||
int nameNsSi = LEW(xml, off + 4 * 4);
|
|
||||||
int nameSi = LEW(xml, off + 5 * 4);
|
|
||||||
|
|
||||||
if (tag0 == startTag) { // XML START TAG
|
|
||||||
int tag6 = LEW(xml, off + 6 * 4); // Expected to be 14001400
|
|
||||||
int numbAttrs = LEW(xml, off + 7 * 4); // Number of Attributes
|
|
||||||
// to follow
|
|
||||||
// int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
|
|
||||||
off += 9 * 4; // Skip over 6+3 words of startTag data
|
|
||||||
String name = compXmlString(xml, sitOff, stOff, nameSi);
|
|
||||||
// tr.addSelect(name, null);
|
|
||||||
startTagLineNo = lineNo;
|
|
||||||
|
|
||||||
// Look for the Attributes
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int ii = 0; ii < numbAttrs; ii++) {
|
|
||||||
int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str
|
|
||||||
// Ind, or FFFFFFFF
|
|
||||||
int attrNameSi = LEW(xml, off + 1 * 4); // AttrName String
|
|
||||||
// Index
|
|
||||||
int attrValueSi = LEW(xml, off + 2 * 4); // AttrValue Str
|
|
||||||
// Ind, or
|
|
||||||
// FFFFFFFF
|
|
||||||
int attrFlags = LEW(xml, off + 3 * 4);
|
|
||||||
int attrResId = LEW(xml, off + 4 * 4); // AttrValue
|
|
||||||
// ResourceId or dup
|
|
||||||
// AttrValue StrInd
|
|
||||||
off += 5 * 4; // Skip over the 5 words of an attribute
|
|
||||||
|
|
||||||
String attrName = compXmlString(xml, sitOff, stOff,
|
|
||||||
attrNameSi);
|
|
||||||
String attrValue = attrValueSi != -1 ? compXmlString(xml,
|
|
||||||
sitOff, stOff, attrValueSi) : "resourceID 0x"
|
|
||||||
+ Integer.toHexString(attrResId);
|
|
||||||
sb.append(" " + attrName + "=\"" + attrValue + "\"");
|
|
||||||
// tr.add(attrName, attrValue);
|
|
||||||
}
|
|
||||||
finalXML.append("<" + name + sb + ">");
|
|
||||||
prtIndent(indent, "<" + name + sb + ">");
|
|
||||||
indent++;
|
|
||||||
|
|
||||||
} else if (tag0 == endTag) { // XML END TAG
|
|
||||||
indent--;
|
|
||||||
off += 6 * 4; // Skip over 6 words of endTag data
|
|
||||||
String name = compXmlString(xml, sitOff, stOff, nameSi);
|
|
||||||
finalXML.append("</" + name + ">");
|
|
||||||
prtIndent(indent, "</" + name + "> (line " + startTagLineNo
|
|
||||||
+ "-" + lineNo + ")");
|
|
||||||
// tr.parent(); // Step back up the NobTree
|
|
||||||
|
|
||||||
} else if (tag0 == endDocTag) { // END OF XML DOC TAG
|
|
||||||
break;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
prt(" Unrecognized tag code '" + Integer.toHexString(tag0)
|
|
||||||
+ "' at offset " + off);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} // end of while loop scanning tags and attributes of XML tree
|
|
||||||
//prt(" end at offset " + off);
|
|
||||||
return finalXML.toString();
|
|
||||||
} // end of decompressXML
|
|
||||||
|
|
||||||
public static String compXmlString(byte[] xml, int sitOff, int stOff, int strInd) {
|
|
||||||
if (strInd < 0)
|
|
||||||
return null;
|
|
||||||
int strOff = stOff + LEW(xml, sitOff + strInd * 4);
|
|
||||||
return compXmlStringAt(xml, strOff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String spaces = " ";
|
|
||||||
|
|
||||||
public static void prtIndent(int indent, String str) {
|
|
||||||
prt(spaces.substring(0, Math.min(indent * 2, spaces.length())) + str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// compXmlStringAt -- Return the string stored in StringTable format at
|
|
||||||
// offset strOff. This offset points to the 16 bit string length, which
|
|
||||||
// is followed by that number of 16 bit (Unicode) chars.
|
|
||||||
public static String compXmlStringAt(byte[] arr, int strOff) {
|
|
||||||
int strLen = arr[strOff + 1] << 8 & 0xff00 | arr[strOff] & 0xff;
|
|
||||||
byte[] chars = new byte[strLen];
|
|
||||||
for (int ii = 0; ii < strLen; ii++) {
|
|
||||||
chars[ii] = arr[strOff + 2 + ii * 2];
|
|
||||||
}
|
|
||||||
return new String(chars); // Hack, just use 8 byte chars
|
|
||||||
} // end of compXmlStringAt
|
|
||||||
|
|
||||||
// LEW -- Return value of a Little Endian 32 bit word from the byte array
|
|
||||||
// at offset off.
|
|
||||||
public static int LEW(byte[] arr, int off) {
|
|
||||||
return arr[off + 3] << 24 & 0xff000000 | arr[off + 2] << 16 & 0xff0000
|
|
||||||
| arr[off + 1] << 8 & 0xff00 | arr[off] & 0xFF;
|
|
||||||
} // end of LEW
|
|
||||||
|
|
||||||
public static Document loadXMLFromString(String xml) throws Exception {
|
|
||||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
|
||||||
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
|
|
||||||
return docBuilder.parse(new InputSource(new StringReader(xml)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String extract_dex_and_read_className(String filePath, String dexPath) throws IOException {
|
|
||||||
ZipFile zip = null;
|
|
||||||
|
|
||||||
zip = new ZipFile(filePath);
|
|
||||||
ZipEntry androidManifest = zip.getEntry("AndroidManifest.xml");
|
|
||||||
ZipEntry classesDex = zip.getEntry("classes.dex");
|
|
||||||
|
|
||||||
// write dex file
|
|
||||||
InputStream dexStream = zip.getInputStream(classesDex);
|
|
||||||
try (OutputStream os = Files.newOutputStream(Paths.get(dexPath))) {
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int len;
|
|
||||||
while ((len = dexStream.read(buffer)) > 0) {
|
|
||||||
os.write(buffer, 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read xml file
|
|
||||||
InputStream is = zip.getInputStream(androidManifest);
|
|
||||||
byte[] buf = new byte[1024000]; // 100 kb
|
|
||||||
is.read(buf);
|
|
||||||
is.close();
|
|
||||||
zip.close();
|
|
||||||
|
|
||||||
String xml = APKExtractor.decompressXML(buf);
|
|
||||||
try {
|
|
||||||
Document xmlDoc = loadXMLFromString(xml);
|
|
||||||
String pkg = xmlDoc.getDocumentElement().getAttribute("package");
|
|
||||||
NodeList nodes = xmlDoc.getElementsByTagName("meta-data");
|
|
||||||
for (int i = 0; i < nodes.getLength(); i++) {
|
|
||||||
NamedNodeMap attributes = nodes.item(i).getAttributes();
|
|
||||||
System.out.println(attributes.getNamedItem("name").getNodeValue());
|
|
||||||
if (attributes.getNamedItem("name").getNodeValue().equals("tachiyomi.extension.class"))
|
|
||||||
return pkg + attributes.getNamedItem("value").getNodeValue();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
// import android.content.res.Configuration
|
// import android.content.res.Configuration
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
// import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
@@ -11,6 +18,7 @@ import com.google.gson.Gson
|
|||||||
// import eu.kanade.tachiyomi.data.track.TrackManager
|
// import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
@@ -47,6 +55,8 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { Gson() }
|
addSingletonFactory { Gson() }
|
||||||
|
|
||||||
|
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||||
|
|
||||||
// Asynchronously init expensive components for a faster cold start
|
// Asynchronously init expensive components for a faster cold start
|
||||||
|
|
||||||
// rxAsync { get<PreferencesHelper>() }
|
// rxAsync { get<PreferencesHelper>() }
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
interface AnimeCatalogueSource : AnimeSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||||
|
*/
|
||||||
|
val lang: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the source has support for latest updates.
|
||||||
|
*/
|
||||||
|
val supportsLatest: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of anime.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
fun fetchPopularAnime(page: Int): Observable<AnimesPage>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of anime.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
* @param query the search query.
|
||||||
|
* @param filters the list of filters to apply.
|
||||||
|
*/
|
||||||
|
fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of latest anime updates.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
fun fetchLatestUpdates(page: Int): Observable<AnimesPage>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of filters for the source.
|
||||||
|
*/
|
||||||
|
fun getFilterList(): AnimeFilterList
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic interface for creating a source. It could be an online source, a local source, etc...
|
||||||
|
*/
|
||||||
|
interface AnimeSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id for the source. Must be unique.
|
||||||
|
*/
|
||||||
|
val id: Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the source.
|
||||||
|
*/
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with the updated details for a anime.
|
||||||
|
*
|
||||||
|
* @param anime the anime to update.
|
||||||
|
*/
|
||||||
|
// @Deprecated("Use getAnimeDetails instead")
|
||||||
|
fun fetchAnimeDetails(anime: SAnime): Observable<SAnime>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with all the available episodes for an anime.
|
||||||
|
*
|
||||||
|
* @param anime the anime to update.
|
||||||
|
*/
|
||||||
|
// @Deprecated("Use getEpisodeList instead")
|
||||||
|
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with a link for the episode of an anime.
|
||||||
|
*
|
||||||
|
* @param episode the episode to get the link for.
|
||||||
|
*/
|
||||||
|
// @Deprecated("Use getEpisodeList instead")
|
||||||
|
fun fetchEpisodeLink(episode: SEpisode): Observable<String>
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * [1.x API] Get the updated details for a anime.
|
||||||
|
// */
|
||||||
|
// @Suppress("DEPRECATION")
|
||||||
|
// override suspend fun getAnimeDetails(anime: AnimeInfo): AnimeInfo {
|
||||||
|
// val sAnime = anime.toSAnime()
|
||||||
|
// val networkAnime = fetchAnimeDetails(sAnime).awaitSingle()
|
||||||
|
// sAnime.copyFrom(networkAnime)
|
||||||
|
// return sAnime.toAnimeInfo()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * [1.x API] Get all the available episodes for a anime.
|
||||||
|
// */
|
||||||
|
// @Suppress("DEPRECATION")
|
||||||
|
// override suspend fun getEpisodeList(anime: AnimeInfo): List<EpisodeInfo> {
|
||||||
|
// return fetchEpisodeList(anime.toSAnime()).awaitSingle()
|
||||||
|
// .map { it.toEpisodeInfo() }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * [1.x API] Get a link for the episode of an anime.
|
||||||
|
// */
|
||||||
|
// @Suppress("DEPRECATION")
|
||||||
|
// override suspend fun getEpisodeLink(episode: EpisodeInfo): String {
|
||||||
|
// return fetchEpisodeLink(episode.toSEpisode()).awaitSingle()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this)
|
||||||
|
|
||||||
|
// fun AnimeSource.getPreferenceKey(): String = "source_$id"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for creating sources at runtime.
|
||||||
|
*/
|
||||||
|
interface AnimeSourceFactory {
|
||||||
|
/**
|
||||||
|
* Create a new copy of the sources
|
||||||
|
* @return The created sources
|
||||||
|
*/
|
||||||
|
fun createSources(): List<AnimeSource>
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
open class AnimeSourceManager(private val context: Context) {
|
||||||
|
|
||||||
|
private val sourcesMap = mutableMapOf<Long, AnimeSource>()
|
||||||
|
|
||||||
|
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
createInternalSources().forEach { registerSource(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun get(sourceKey: Long): AnimeSource? {
|
||||||
|
return sourcesMap[sourceKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOrStub(sourceKey: Long): AnimeSource {
|
||||||
|
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
||||||
|
StubSource(sourceKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnlineSources() = sourcesMap.values.filterIsInstance<AnimeHttpSource>()
|
||||||
|
|
||||||
|
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<AnimeCatalogueSource>()
|
||||||
|
|
||||||
|
internal fun registerSource(source: AnimeSource) {
|
||||||
|
if (!sourcesMap.containsKey(source.id)) {
|
||||||
|
sourcesMap[source.id] = source
|
||||||
|
}
|
||||||
|
if (!stubSourcesMap.containsKey(source.id)) {
|
||||||
|
stubSourcesMap[source.id] = StubSource(source.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun unregisterSource(source: AnimeSource) {
|
||||||
|
sourcesMap.remove(source.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createInternalSources(): List<AnimeSource> = listOf(
|
||||||
|
// LocalAnimeSource(context)
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class StubSource(override val id: Long) : AnimeSource {
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = id.toString()
|
||||||
|
|
||||||
|
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||||
|
return Observable.error(getSourceNotInstalledException())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
|
return Observable.error(getSourceNotInstalledException())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchEpisodeLink(episode: SEpisode): Observable<String> {
|
||||||
|
return Observable.error(getSourceNotInstalledException())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSourceNotInstalledException(): Exception {
|
||||||
|
// return Exception(context.getString(R.string.source_not_installed, id.toString()))
|
||||||
|
return Exception("source not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
|
||||||
|
interface ConfigurableAnimeSource : AnimeSource {
|
||||||
|
|
||||||
|
fun setupPreferenceScreen(screen: PreferenceScreen)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
sealed class AnimeFilter<T>(val name: String, var state: T) {
|
||||||
|
open class Header(name: String) : AnimeFilter<Any>(name, 0)
|
||||||
|
open class Separator(name: String = "") : AnimeFilter<Any>(name, 0)
|
||||||
|
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : AnimeFilter<Int>(name, state)
|
||||||
|
abstract class Text(name: String, state: String = "") : AnimeFilter<String>(name, state)
|
||||||
|
abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter<Boolean>(name, state)
|
||||||
|
abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter<Int>(name, state) {
|
||||||
|
fun isIgnored() = state == STATE_IGNORE
|
||||||
|
fun isIncluded() = state == STATE_INCLUDE
|
||||||
|
fun isExcluded() = state == STATE_EXCLUDE
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val STATE_IGNORE = 0
|
||||||
|
const val STATE_INCLUDE = 1
|
||||||
|
const val STATE_EXCLUDE = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Group<V>(name: String, state: List<V>) : AnimeFilter<List<V>>(name, state)
|
||||||
|
|
||||||
|
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) :
|
||||||
|
AnimeFilter<Sort.Selection?>(name, state) {
|
||||||
|
data class Selection(val index: Int, val ascending: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is AnimeFilter<*>) return false
|
||||||
|
|
||||||
|
return name == other.name && state == other.state
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = name.hashCode()
|
||||||
|
result = 31 * result + (state?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
data class AnimeFilterList(val list: List<AnimeFilter<*>>) : List<AnimeFilter<*>> by list {
|
||||||
|
|
||||||
|
constructor(vararg fs: AnimeFilter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
data class AnimesPage(val animes: List<SAnime>, val hasNextPage: Boolean)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface SAnime : Serializable {
|
||||||
|
|
||||||
|
var url: String
|
||||||
|
|
||||||
|
var title: String
|
||||||
|
|
||||||
|
var artist: String?
|
||||||
|
|
||||||
|
var author: String?
|
||||||
|
|
||||||
|
var description: String?
|
||||||
|
|
||||||
|
var genre: String?
|
||||||
|
|
||||||
|
var status: Int
|
||||||
|
|
||||||
|
var thumbnail_url: String?
|
||||||
|
|
||||||
|
var initialized: Boolean
|
||||||
|
|
||||||
|
fun copyFrom(other: SAnime) {
|
||||||
|
if (other.title != null) {
|
||||||
|
title = other.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.author != null) {
|
||||||
|
author = other.author
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.artist != null) {
|
||||||
|
artist = other.artist
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.description != null) {
|
||||||
|
description = other.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.genre != null) {
|
||||||
|
genre = other.genre
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.thumbnail_url != null) {
|
||||||
|
thumbnail_url = other.thumbnail_url
|
||||||
|
}
|
||||||
|
|
||||||
|
status = other.status
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
initialized = other.initialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val UNKNOWN = 0
|
||||||
|
const val ONGOING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val LICENSED = 3
|
||||||
|
|
||||||
|
fun create(): SAnime {
|
||||||
|
return SAnimeImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
class SAnimeImpl : SAnime {
|
||||||
|
|
||||||
|
override lateinit var url: String
|
||||||
|
|
||||||
|
override lateinit var title: String
|
||||||
|
|
||||||
|
override var artist: String? = null
|
||||||
|
|
||||||
|
override var author: String? = null
|
||||||
|
|
||||||
|
override var description: String? = null
|
||||||
|
|
||||||
|
override var genre: String? = null
|
||||||
|
|
||||||
|
override var status: Int = 0
|
||||||
|
|
||||||
|
override var thumbnail_url: String? = null
|
||||||
|
|
||||||
|
override var initialized: Boolean = false
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface SEpisode : Serializable {
|
||||||
|
|
||||||
|
var url: String
|
||||||
|
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
var date_upload: Long
|
||||||
|
|
||||||
|
var episode_number: Float
|
||||||
|
|
||||||
|
var scanlator: String?
|
||||||
|
|
||||||
|
fun copyFrom(other: SEpisode) {
|
||||||
|
name = other.name
|
||||||
|
url = other.url
|
||||||
|
date_upload = other.date_upload
|
||||||
|
episode_number = other.episode_number
|
||||||
|
scanlator = other.scanlator
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(): SEpisode {
|
||||||
|
return SEpisodeImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
class SEpisodeImpl : SEpisode {
|
||||||
|
|
||||||
|
override lateinit var url: String
|
||||||
|
|
||||||
|
override lateinit var name: String
|
||||||
|
|
||||||
|
override var date_upload: Long = 0
|
||||||
|
|
||||||
|
override var episode_number: Float = -1f
|
||||||
|
|
||||||
|
override var scanlator: String? = null
|
||||||
|
}
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.online
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.newCallWithProgress
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation for sources from a website.
|
||||||
|
*/
|
||||||
|
abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network service.
|
||||||
|
*/
|
||||||
|
protected val network: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Preferences that a source may need.
|
||||||
|
// */
|
||||||
|
// val preferences: SharedPreferences by lazy {
|
||||||
|
// Injekt.get<Application>().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||||
|
*/
|
||||||
|
abstract val baseUrl: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version id used to generate the source id. If the site completely changes and urls are
|
||||||
|
* incompatible, you may increase this value and it'll be considered as a new source.
|
||||||
|
*/
|
||||||
|
open val versionId = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
||||||
|
* of the MD5 of the string: sourcename/language/versionId
|
||||||
|
* Note the generated id sets the sign bit to 0.
|
||||||
|
*/
|
||||||
|
override val id by lazy {
|
||||||
|
val key = "${name.toLowerCase()}/$lang/$versionId"
|
||||||
|
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||||
|
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headers used for requests.
|
||||||
|
*/
|
||||||
|
val headers: Headers by lazy { headersBuilder().build() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default network client for doing requests.
|
||||||
|
*/
|
||||||
|
open val client: OkHttpClient
|
||||||
|
get() = network.client
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||||
|
*/
|
||||||
|
protected open fun headersBuilder() = Headers.Builder().apply {
|
||||||
|
add("User-Agent", DEFAULT_USER_AGENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visible name of the source.
|
||||||
|
*/
|
||||||
|
override fun toString() = "$name (${lang.toUpperCase()})"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of anime. Normally it's not needed to
|
||||||
|
* override this method.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
|
||||||
|
return client.newCall(popularAnimeRequest(page))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
popularAnimeParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for the popular anime given the page.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
protected abstract fun popularAnimeRequest(page: Int): Request
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun popularAnimeParse(response: Response): AnimesPage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of anime. Normally it's not needed to
|
||||||
|
* override this method.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
* @param query the search query.
|
||||||
|
* @param filters the list of filters to apply.
|
||||||
|
*/
|
||||||
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
|
return client.newCall(searchAnimeRequest(page, query, filters))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
searchAnimeParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for the search anime given the page.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
* @param query the search query.
|
||||||
|
* @param filters the list of filters to apply.
|
||||||
|
*/
|
||||||
|
protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun searchAnimeParse(response: Response): AnimesPage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable containing a page with a list of latest anime updates.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
|
||||||
|
return client.newCall(latestUpdatesRequest(page))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
latestUpdatesParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for latest anime given the page.
|
||||||
|
*
|
||||||
|
* @param page the page number to retrieve.
|
||||||
|
*/
|
||||||
|
protected abstract fun latestUpdatesRequest(page: Int): Request
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun latestUpdatesParse(response: Response): AnimesPage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with the updated details for a anime. Normally it's not needed to
|
||||||
|
* override this method.
|
||||||
|
*
|
||||||
|
* @param anime the anime to be updated.
|
||||||
|
*/
|
||||||
|
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||||
|
return client.newCall(animeDetailsRequest(anime))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
animeDetailsParse(response).apply { initialized = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for the details of a anime. Override only if it's needed to change the
|
||||||
|
* url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param anime the anime to be updated.
|
||||||
|
*/
|
||||||
|
open fun animeDetailsRequest(anime: SAnime): Request {
|
||||||
|
return GET(baseUrl + anime.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns the details of a anime.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun animeDetailsParse(response: Response): SAnime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with the updated episode list for a anime. Normally it's not needed to
|
||||||
|
* override this method. If a anime is licensed an empty episode list observable is returned
|
||||||
|
*
|
||||||
|
* @param anime the anime to look for episodes.
|
||||||
|
*/
|
||||||
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
|
return if (anime.status != SAnime.LICENSED) {
|
||||||
|
client.newCall(episodeListRequest(anime))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
episodeListParse(response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Observable.error(Exception("Licensed - No episodes to show"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchEpisodeLink(episode: SEpisode): Observable<String> {
|
||||||
|
return client.newCall(episodeLinkRequest(episode))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
episodeLinkParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for updating the episode list. Override only if it's needed to override
|
||||||
|
* the url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param anime the anime to look for episodes.
|
||||||
|
*/
|
||||||
|
protected open fun episodeListRequest(anime: SAnime): Request {
|
||||||
|
return GET(baseUrl + anime.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for getting the episode link. Override only if it's needed to override
|
||||||
|
* the url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param episode the episode to look for links.
|
||||||
|
*/
|
||||||
|
protected open fun episodeLinkRequest(episode: SEpisode): Request {
|
||||||
|
return GET(baseUrl + episode.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a list of episodes.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a list of episodes.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun episodeLinkParse(response: Response): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for getting the page list. Override only if it's needed to override the
|
||||||
|
* url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param episode the episode whose page list has to be fetched.
|
||||||
|
*/
|
||||||
|
protected open fun pageListRequest(episode: SEpisode): Request {
|
||||||
|
return GET(baseUrl + episode.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a list of pages.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun pageListParse(response: Response): List<Page>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with the page containing the source url of the image. If there's any
|
||||||
|
* error, it will return null instead of throwing an exception.
|
||||||
|
*
|
||||||
|
* @param page the page whose source image has to be fetched.
|
||||||
|
*/
|
||||||
|
open fun fetchImageUrl(page: Page): Observable<String> {
|
||||||
|
return client.newCall(imageUrlRequest(page))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { imageUrlParse(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for getting the url to the source image. Override only if it's needed to
|
||||||
|
* override the url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param page the episode whose page list has to be fetched
|
||||||
|
*/
|
||||||
|
protected open fun imageUrlRequest(page: Page): Request {
|
||||||
|
return GET(page.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns the absolute url to the source image.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
protected abstract fun imageUrlParse(response: Response): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable with the response of the source image.
|
||||||
|
*
|
||||||
|
* @param page the page whose source image has to be downloaded.
|
||||||
|
*/
|
||||||
|
fun fetchImage(page: Page): Observable<Response> {
|
||||||
|
return client.newCallWithProgress(imageRequest(page), page)
|
||||||
|
.asObservableSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request for getting the source image. Override only if it's needed to override
|
||||||
|
* the url, send different headers or request method like POST.
|
||||||
|
*
|
||||||
|
* @param page the episode whose page list has to be fetched
|
||||||
|
*/
|
||||||
|
protected open fun imageRequest(page: Page): Request {
|
||||||
|
return GET(page.imageUrl!!, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the url of the episode without the scheme and domain. It saves some redundancy from
|
||||||
|
* database and the urls could still work after a domain change.
|
||||||
|
*
|
||||||
|
* @param url the full url to the episode.
|
||||||
|
*/
|
||||||
|
fun SEpisode.setUrlWithoutDomain(url: String) {
|
||||||
|
this.url = getUrlWithoutDomain(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the url of the anime without the scheme and domain. It saves some redundancy from
|
||||||
|
* database and the urls could still work after a domain change.
|
||||||
|
*
|
||||||
|
* @param url the full url to the anime.
|
||||||
|
*/
|
||||||
|
fun SAnime.setUrlWithoutDomain(url: String) {
|
||||||
|
this.url = getUrlWithoutDomain(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the url of the given string without the scheme and domain.
|
||||||
|
*
|
||||||
|
* @param orig the full url.
|
||||||
|
*/
|
||||||
|
private fun getUrlWithoutDomain(orig: String): String {
|
||||||
|
return try {
|
||||||
|
val uri = URI(orig)
|
||||||
|
var out = uri.path
|
||||||
|
if (uri.query != null) {
|
||||||
|
out += "?" + uri.query
|
||||||
|
}
|
||||||
|
if (uri.fragment != null) {
|
||||||
|
out += "#" + uri.fragment
|
||||||
|
}
|
||||||
|
out
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
orig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before inserting a new episode into database. Use it if you need to override episode
|
||||||
|
* fields, like the title or the episode number. Do not change anything to [anime].
|
||||||
|
*
|
||||||
|
* @param episode the episode to be added.
|
||||||
|
* @param anime the anime of the episode.
|
||||||
|
*/
|
||||||
|
open fun prepareNewEpisode(episode: SEpisode, anime: SAnime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of filters for the source.
|
||||||
|
*/
|
||||||
|
override fun getFilterList() = AnimeFilterList()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
fun AnimeHttpSource.getImageUrl(page: Page): Observable<Page> {
|
||||||
|
page.status = Page.LOAD_PAGE
|
||||||
|
return fetchImageUrl(page)
|
||||||
|
.doOnError { page.status = Page.ERROR }
|
||||||
|
.onErrorReturn { null }
|
||||||
|
.doOnNext { page.imageUrl = it }
|
||||||
|
.map { page }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AnimeHttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
||||||
|
return Observable.from(pages)
|
||||||
|
.filter { !it.imageUrl.isNullOrEmpty() }
|
||||||
|
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AnimeHttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
||||||
|
return Observable.from(pages)
|
||||||
|
.filter { it.imageUrl.isNullOrEmpty() }
|
||||||
|
.concatMap { getImageUrl(it) }
|
||||||
|
}
|
||||||
+222
@@ -0,0 +1,222 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.online
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation for sources from a website using Jsoup, an HTML parser.
|
||||||
|
*/
|
||||||
|
abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val animes = document.select(popularAnimeSelector()).map { element ->
|
||||||
|
popularAnimeFromElement(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNextPage = popularAnimeNextPageSelector()?.let { selector ->
|
||||||
|
document.select(selector).first()
|
||||||
|
} != null
|
||||||
|
|
||||||
|
return AnimesPage(animes, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
||||||
|
*/
|
||||||
|
protected abstract fun popularAnimeSelector(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
||||||
|
* totally fine to fill only those two values.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [popularAnimeSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun popularAnimeFromElement(element: Element): SAnime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
||||||
|
* there's no next page.
|
||||||
|
*/
|
||||||
|
protected abstract fun popularAnimeNextPageSelector(): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val animes = document.select(searchAnimeSelector()).map { element ->
|
||||||
|
searchAnimeFromElement(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNextPage = searchAnimeNextPageSelector()?.let { selector ->
|
||||||
|
document.select(selector).first()
|
||||||
|
} != null
|
||||||
|
|
||||||
|
return AnimesPage(animes, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
||||||
|
*/
|
||||||
|
protected abstract fun searchAnimeSelector(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
||||||
|
* totally fine to fill only those two values.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [searchAnimeSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun searchAnimeFromElement(element: Element): SAnime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
||||||
|
* there's no next page.
|
||||||
|
*/
|
||||||
|
protected abstract fun searchAnimeNextPageSelector(): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val animes = document.select(latestUpdatesSelector()).map { element ->
|
||||||
|
latestUpdatesFromElement(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
|
||||||
|
document.select(selector).first()
|
||||||
|
} != null
|
||||||
|
|
||||||
|
return AnimesPage(animes, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each anime.
|
||||||
|
*/
|
||||||
|
protected abstract fun latestUpdatesSelector(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a anime from the given [element]. Most sites only show the title and the url, it's
|
||||||
|
* totally fine to fill only those two values.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [latestUpdatesSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun latestUpdatesFromElement(element: Element): SAnime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
|
||||||
|
* there's no next page.
|
||||||
|
*/
|
||||||
|
protected abstract fun latestUpdatesNextPageSelector(): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns the details of a anime.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun animeDetailsParse(response: Response): SAnime {
|
||||||
|
return animeDetailsParse(response.asJsoup())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the details of the anime from the given [document].
|
||||||
|
*
|
||||||
|
* @param document the parsed document.
|
||||||
|
*/
|
||||||
|
protected abstract fun animeDetailsParse(document: Document): SAnime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a list of episodes.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return document.select(episodeListSelector()).map { episodeFromElement(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each episode.
|
||||||
|
*/
|
||||||
|
protected abstract fun episodeListSelector(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns a list of episodes.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun episodeLinkParse(response: Response): String {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return linkFromElement(document.select(episodeLinkSelector()).first())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each episode.
|
||||||
|
*/
|
||||||
|
protected abstract fun episodeLinkSelector(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a episode from the given element.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [episodeListSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun episodeFromElement(element: Element): SEpisode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a episode from the given element.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [episodeListSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun linkFromElement(element: Element): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the response from the site and returns the page list.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
return pageListParse(response.asJsoup())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a page list from the given document.
|
||||||
|
*
|
||||||
|
* @param document the parsed document.
|
||||||
|
*/
|
||||||
|
protected abstract fun pageListParse(document: Document): List<Page>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the response from the site and returns the absolute url to the source image.
|
||||||
|
*
|
||||||
|
* @param response the response from the site.
|
||||||
|
*/
|
||||||
|
override fun imageUrlParse(response: Response): String {
|
||||||
|
return imageUrlParse(response.asJsoup())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the absolute url to the source image from the document.
|
||||||
|
*
|
||||||
|
* @param document the parsed document.
|
||||||
|
*/
|
||||||
|
protected abstract fun imageUrlParse(document: Document): String
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.api
|
|
||||||
|
|
||||||
// import android.content.Context
|
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
|
||||||
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass
|
|
||||||
// import kotlinx.coroutines.Dispatchers
|
|
||||||
// import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
// import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
internal class ExtensionGithubApi {
|
|
||||||
|
|
||||||
// private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
suspend fun findExtensions(): List<Extension.Available> {
|
|
||||||
val service: ExtensionGithubService = ExtensionGithubService.create()
|
|
||||||
|
|
||||||
val response = service.getRepo()
|
|
||||||
return parseResponse(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// suspend fun checkForUpdates(): List<Extension.Installed> {
|
|
||||||
// val extensions = fin dExtensions()
|
|
||||||
//
|
|
||||||
// // preferences.lastExtCheck().set(Date().time)
|
|
||||||
//
|
|
||||||
// val installedExtensions = ExtensionLoader.loadExtensions(context)
|
|
||||||
// .filterIsInstance<LoadResult.Success>()
|
|
||||||
// .map { it.extension }
|
|
||||||
//
|
|
||||||
// val extensionsWithUpdate = mutableListOf<Extension.Installed>()
|
|
||||||
// for (installedExt in installedExtensions) {
|
|
||||||
// val pkgName = installedExt.pkgName
|
|
||||||
// val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
|
|
||||||
//
|
|
||||||
// val hasUpdate = availableExt.versionCode > installedExt.versionCode
|
|
||||||
// if (hasUpdate) {
|
|
||||||
// extensionsWithUpdate.add(installedExt)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return extensionsWithUpdate
|
|
||||||
// }
|
|
||||||
|
|
||||||
private fun parseResponse(json: JsonArray): List<Extension.Available> {
|
|
||||||
return json
|
|
||||||
.filter { element ->
|
|
||||||
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
|
||||||
val libVersion = versionName.substringBeforeLast('.').toDouble()
|
|
||||||
libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX
|
|
||||||
}
|
|
||||||
.map { element ->
|
|
||||||
val name = element.jsonObject["name"]!!.jsonPrimitive.content.substringAfter("Tachiyomi: ")
|
|
||||||
val pkgName = element.jsonObject["pkg"]!!.jsonPrimitive.content
|
|
||||||
val apkName = element.jsonObject["apk"]!!.jsonPrimitive.content
|
|
||||||
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
|
||||||
val versionCode = element.jsonObject["code"]!!.jsonPrimitive.int
|
|
||||||
val lang = element.jsonObject["lang"]!!.jsonPrimitive.content
|
|
||||||
val nsfw = element.jsonObject["nsfw"]!!.jsonPrimitive.int == 1
|
|
||||||
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
|
|
||||||
|
|
||||||
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getApkUrl(extension: Extension.Available): String {
|
|
||||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getApkUrl(extension: ExtensionDataClass): String {
|
|
||||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val BASE_URL = "https://raw.githubusercontent.com/"
|
|
||||||
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.api
|
|
||||||
|
|
||||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
// import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get the extension repo listing from GitHub.
|
|
||||||
*/
|
|
||||||
interface ExtensionGithubService {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val client by lazy {
|
|
||||||
val network: NetworkHelper by injectLazy()
|
|
||||||
network.client.newBuilder()
|
|
||||||
.addNetworkInterceptor { chain ->
|
|
||||||
val originalResponse = chain.proceed(chain.request())
|
|
||||||
originalResponse.newBuilder()
|
|
||||||
.header("Content-Encoding", "gzip")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun create(): ExtensionGithubService {
|
|
||||||
val adapter = Retrofit.Builder()
|
|
||||||
.baseUrl(ExtensionGithubApi.BASE_URL)
|
|
||||||
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
|
|
||||||
.client(client)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return adapter.create(ExtensionGithubService::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json.gz")
|
|
||||||
suspend fun getRepo(): JsonArray
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
|
|
||||||
sealed class Extension {
|
|
||||||
|
|
||||||
abstract val name: String
|
|
||||||
abstract val pkgName: String
|
|
||||||
abstract val versionName: String
|
|
||||||
abstract val versionCode: Int
|
|
||||||
abstract val lang: String?
|
|
||||||
abstract val isNsfw: Boolean
|
|
||||||
|
|
||||||
data class Installed(
|
|
||||||
override val name: String,
|
|
||||||
override val pkgName: String,
|
|
||||||
override val versionName: String,
|
|
||||||
override val versionCode: Int,
|
|
||||||
override val lang: String,
|
|
||||||
override val isNsfw: Boolean,
|
|
||||||
val sources: List<Source>,
|
|
||||||
val hasUpdate: Boolean = false,
|
|
||||||
val isObsolete: Boolean = false,
|
|
||||||
val isUnofficial: Boolean = false
|
|
||||||
) : Extension()
|
|
||||||
|
|
||||||
data class Available(
|
|
||||||
override val name: String,
|
|
||||||
override val pkgName: String,
|
|
||||||
override val versionName: String,
|
|
||||||
override val versionCode: Int,
|
|
||||||
override val lang: String,
|
|
||||||
override val isNsfw: Boolean,
|
|
||||||
val apkName: String,
|
|
||||||
val iconUrl: String
|
|
||||||
) : Extension()
|
|
||||||
|
|
||||||
data class Untrusted(
|
|
||||||
override val name: String,
|
|
||||||
override val pkgName: String,
|
|
||||||
override val versionName: String,
|
|
||||||
override val versionCode: Int,
|
|
||||||
val signatureHash: String,
|
|
||||||
override val lang: String? = null,
|
|
||||||
override val isNsfw: Boolean = false
|
|
||||||
) : Extension()
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.model
|
|
||||||
|
|
||||||
enum class InstallStep {
|
|
||||||
Pending, Downloading, Installing, Installed, Error;
|
|
||||||
|
|
||||||
fun isCompleted(): Boolean {
|
|
||||||
return this == Installed || this == Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.model
|
|
||||||
|
|
||||||
sealed class LoadResult {
|
|
||||||
|
|
||||||
class Success(val extension: Extension.Installed) : LoadResult()
|
|
||||||
class Untrusted(val extension: Extension.Untrusted) : LoadResult()
|
|
||||||
class Error(val message: String? = null) : LoadResult() {
|
|
||||||
constructor(exception: Throwable) : this(exception.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.util
|
|
||||||
|
|
||||||
// import android.annotation.SuppressLint
|
|
||||||
// import android.content.Context
|
|
||||||
// import android.content.pm.PackageInfo
|
|
||||||
// import android.content.pm.PackageManager
|
|
||||||
// import dalvik.system.PathClassLoader
|
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
// import eu.kanade.tachiyomi.util.lang.Hash
|
|
||||||
// import kotlinx.coroutines.async
|
|
||||||
// import kotlinx.coroutines.runBlocking
|
|
||||||
// import timber.log.Timber
|
|
||||||
// import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that handles the loading of the extensions installed in the system.
|
|
||||||
*/
|
|
||||||
// @SuppressLint("PackageManagerGetSignatures")
|
|
||||||
internal object ExtensionLoader {
|
|
||||||
|
|
||||||
// private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
// private val allowNsfwSource by lazy {
|
|
||||||
// preferences.allowNsfwSource().get()
|
|
||||||
// }
|
|
||||||
|
|
||||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
|
||||||
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
|
||||||
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
|
||||||
const val LIB_VERSION_MIN = 1.2
|
|
||||||
const val LIB_VERSION_MAX = 1.2
|
|
||||||
|
|
||||||
// private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
|
||||||
|
|
||||||
// inorichi's key
|
|
||||||
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
|
||||||
/**
|
|
||||||
* List of the trusted signatures.
|
|
||||||
*/
|
|
||||||
// var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all the installed extensions initialized concurrently.
|
|
||||||
*
|
|
||||||
* @param context The application context.
|
|
||||||
*/
|
|
||||||
// fun loadExtensions(context: Context): List<LoadResult> {
|
|
||||||
// val pkgManager = context.packageManager
|
|
||||||
// val installedPkgs = pkgManager.getInstalledPackages(PACKAGE_FLAGS)
|
|
||||||
// val extPkgs = installedPkgs.filter { isPackageAnExtension(it) }
|
|
||||||
//
|
|
||||||
// if (extPkgs.isEmpty()) return emptyList()
|
|
||||||
//
|
|
||||||
// // Load each extension concurrently and wait for completion
|
|
||||||
// return runBlocking {
|
|
||||||
// val deferred = extPkgs.map {
|
|
||||||
// async { loadExtension(context, it.packageName, it) }
|
|
||||||
// }
|
|
||||||
// deferred.map { it.await() }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to load an extension from the given package name. It checks if the extension
|
|
||||||
* contains the required feature flag before trying to load it.
|
|
||||||
*/
|
|
||||||
// fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult {
|
|
||||||
// val pkgInfo = try {
|
|
||||||
// context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
|
|
||||||
// } catch (error: PackageManager.NameNotFoundException) {
|
|
||||||
// // Unlikely, but the package may have been uninstalled at this point
|
|
||||||
// return LoadResult.Error(error)
|
|
||||||
// }
|
|
||||||
// if (!isPackageAnExtension(pkgInfo)) {
|
|
||||||
// return LoadResult.Error("Tried to load a package that wasn't a extension")
|
|
||||||
// }
|
|
||||||
// return loadExtension(context, pkgName, pkgInfo)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads an extension given its package name.
|
|
||||||
*
|
|
||||||
* @param context The application context.
|
|
||||||
* @param pkgName The package name of the extension to load.
|
|
||||||
* @param pkgInfo The package info of the extension.
|
|
||||||
*/
|
|
||||||
// private fun loadExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): LoadResult {
|
|
||||||
// val pkgManager = context.packageManager
|
|
||||||
//
|
|
||||||
// val appInfo = try {
|
|
||||||
// pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
|
|
||||||
// } catch (error: PackageManager.NameNotFoundException) {
|
|
||||||
// // Unlikely, but the package may have been uninstalled at this point
|
|
||||||
// return LoadResult.Error(error)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
|
|
||||||
// val versionName = pkgInfo.versionName
|
|
||||||
// val versionCode = pkgInfo.versionCode
|
|
||||||
//
|
|
||||||
// if (versionName.isNullOrEmpty()) {
|
|
||||||
// val exception = Exception("Missing versionName for extension $extName")
|
|
||||||
// Timber.w(exception)
|
|
||||||
// return LoadResult.Error(exception)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Validate lib version
|
|
||||||
// val libVersion = versionName.substringBeforeLast('.').toDouble()
|
|
||||||
// if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
|
||||||
// val exception = Exception(
|
|
||||||
// "Lib version is $libVersion, while only versions " +
|
|
||||||
// "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
|
||||||
// )
|
|
||||||
// Timber.w(exception)
|
|
||||||
// return LoadResult.Error(exception)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val signatureHash = getSignatureHash(pkgInfo)
|
|
||||||
//
|
|
||||||
// if (signatureHash == null) {
|
|
||||||
// return LoadResult.Error("Package $pkgName isn't signed")
|
|
||||||
// } else if (signatureHash !in trustedSignatures) {
|
|
||||||
// val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash)
|
|
||||||
// Timber.w("Extension $pkgName isn't trusted")
|
|
||||||
// return LoadResult.Untrusted(extension)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
|
||||||
// if (allowNsfwSource == PreferenceValues.NsfwAllowance.BLOCKED && isNsfw) {
|
|
||||||
// return LoadResult.Error("NSFW extension $pkgName not allowed")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
|
||||||
//
|
|
||||||
// val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
|
||||||
// .split(";")
|
|
||||||
// .map {
|
|
||||||
// val sourceClass = it.trim()
|
|
||||||
// if (sourceClass.startsWith(".")) {
|
|
||||||
// pkgInfo.packageName + sourceClass
|
|
||||||
// } else {
|
|
||||||
// sourceClass
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .flatMap {
|
|
||||||
// try {
|
|
||||||
// when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
|
||||||
// is Source -> listOf(obj)
|
|
||||||
// is SourceFactory -> {
|
|
||||||
// if (isSourceNsfw(obj)) {
|
|
||||||
// emptyList()
|
|
||||||
// } else {
|
|
||||||
// obj.createSources()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
|
||||||
// }
|
|
||||||
// } catch (e: Throwable) {
|
|
||||||
// Timber.e(e, "Extension load error: $extName.")
|
|
||||||
// return LoadResult.Error(e)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .filter { !isSourceNsfw(it) }
|
|
||||||
//
|
|
||||||
// val langs = sources.filterIsInstance<CatalogueSource>()
|
|
||||||
// .map { it.lang }
|
|
||||||
// .toSet()
|
|
||||||
// val lang = when (langs.size) {
|
|
||||||
// 0 -> ""
|
|
||||||
// 1 -> langs.first()
|
|
||||||
// else -> "all"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val extension = Extension.Installed(
|
|
||||||
// extName,
|
|
||||||
// pkgName,
|
|
||||||
// versionName,
|
|
||||||
// versionCode,
|
|
||||||
// lang,
|
|
||||||
// isNsfw,
|
|
||||||
// sources,
|
|
||||||
// isUnofficial = signatureHash != officialSignature
|
|
||||||
// )
|
|
||||||
// return LoadResult.Success(extension)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given package is an extension.
|
|
||||||
*
|
|
||||||
* @param pkgInfo The package info of the application.
|
|
||||||
*/
|
|
||||||
// private fun isPackageAnExtension(pkgInfo: PackageInfo): Boolean {
|
|
||||||
// return pkgInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the signature hash of the package or null if it's not signed.
|
|
||||||
*
|
|
||||||
* @param pkgInfo The package info of the application.
|
|
||||||
*/
|
|
||||||
// private fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
|
||||||
// val signatures = pkgInfo.signatures
|
|
||||||
// return if (signatures != null && signatures.isNotEmpty()) {
|
|
||||||
// Hash.sha256(signatures.first().toByteArray())
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
|
||||||
*/
|
|
||||||
// private fun isSourceNsfw(clazz: Any): Boolean {
|
|
||||||
// if (allowNsfwSource == PreferenceValues.NsfwAllowance.ALLOWED) {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (clazz !is Source && clazz !is SourceFactory) {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Annotations are proxied, hence this janky way of checking for them
|
|
||||||
// return clazz.javaClass.annotations
|
|
||||||
// .flatMap { it.javaClass.interfaces.map { it.simpleName } }
|
|
||||||
// .firstOrNull { it == Nsfw::class.java.simpleName } != null
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.annotation.SuppressLint
|
// import android.annotation.SuppressLint
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import android.os.Build
|
// import android.os.Build
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.CookieJar
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
class MemoryCookieJar : CookieJar {
|
||||||
|
private val cache = mutableSetOf<WrappedCookie>()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||||
|
val cookiesToRemove = mutableSetOf<WrappedCookie>()
|
||||||
|
val validCookies = mutableSetOf<WrappedCookie>()
|
||||||
|
|
||||||
|
cache.forEach { cookie ->
|
||||||
|
if (cookie.isExpired()) {
|
||||||
|
cookiesToRemove.add(cookie)
|
||||||
|
} else if (cookie.matches(url)) {
|
||||||
|
validCookies.add(cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.removeAll(cookiesToRemove)
|
||||||
|
|
||||||
|
return validCookies.toList().map(WrappedCookie::unwrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
|
val cookiesToAdd = cookies.map { WrappedCookie.wrap(it) }
|
||||||
|
|
||||||
|
cache.removeAll(cookiesToAdd)
|
||||||
|
cache.addAll(cookiesToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun clear() {
|
||||||
|
cache.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WrappedCookie private constructor(val cookie: Cookie) {
|
||||||
|
fun unwrap() = cookie
|
||||||
|
|
||||||
|
fun isExpired() = cookie.expiresAt < System.currentTimeMillis()
|
||||||
|
|
||||||
|
fun matches(url: HttpUrl) = cookie.matches(url)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is WrappedCookie) return false
|
||||||
|
|
||||||
|
return other.cookie.name == cookie.name &&
|
||||||
|
other.cookie.domain == cookie.domain &&
|
||||||
|
other.cookie.path == cookie.path &&
|
||||||
|
other.cookie.secure == cookie.secure &&
|
||||||
|
other.cookie.hostOnly == cookie.hostOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var hash = 17
|
||||||
|
hash = 31 * hash + cookie.name.hashCode()
|
||||||
|
hash = 31 * hash + cookie.domain.hashCode()
|
||||||
|
hash = 31 * hash + cookie.path.hashCode()
|
||||||
|
hash = 31 * hash + if (cookie.secure) 0 else 1
|
||||||
|
hash = 31 * hash + if (cookie.hostOnly) 0 else 1
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun wrap(cookie: Cookie) = WrappedCookie(cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// import android.content.Context
|
// import android.content.Context
|
||||||
// import eu.kanade.tachiyomi.BuildConfig
|
// import eu.kanade.tachiyomi.BuildConfig
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@@ -19,14 +26,17 @@ class NetworkHelper(context: Context) {
|
|||||||
|
|
||||||
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
||||||
|
|
||||||
// val cookieManager = AndroidCookieJar()
|
val cookieManager = MemoryCookieJar()
|
||||||
|
|
||||||
val client by lazy {
|
val client by lazy {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
// .cookieJar(cookieManager)
|
.cookieJar(cookieManager)
|
||||||
// .cache(Cache(cacheDir, cacheSize))
|
// .cache(Cache(cacheDir, cacheSize))
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(5, TimeUnit.MINUTES)
|
||||||
|
.writeTimeout(5, TimeUnit.MINUTES)
|
||||||
|
// .dispatcher(Dispatcher(Executors.newFixedThreadPool(1)))
|
||||||
|
|
||||||
// .addInterceptor(UserAgentInterceptor())
|
// .addInterceptor(UserAgentInterceptor())
|
||||||
|
|
||||||
// if (BuildConfig.DEBUG) {
|
// if (BuildConfig.DEBUG) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ fun Call.asObservable(): Observable<Response> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
call.cancel()
|
// call.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isUnsubscribed(): Boolean {
|
override fun isUnsubscribed(): Boolean {
|
||||||
@@ -80,17 +80,18 @@ fun Call.asObservable(): Observable<Response> {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
fun Call.asObservableSuccess(): Observable<Response> {
|
fun Call.asObservableSuccess(): Observable<Response> {
|
||||||
return asObservable().doOnNext { response ->
|
return asObservable()
|
||||||
if (!response.isSuccessful) {
|
.doOnNext { response ->
|
||||||
response.close()
|
if (!response.isSuccessful) {
|
||||||
throw Exception("HTTP error ${response.code}")
|
response.close()
|
||||||
|
throw Exception("HTTP error ${response.code}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
// fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
||||||
// val progressClient = newBuilder()
|
// val progressClient = newBuilder()
|
||||||
// .cache(null)
|
// .cache(nasObservableSuccessull)
|
||||||
// .addNetworkInterceptor { chain ->
|
// .addNetworkInterceptor { chain ->
|
||||||
// val originalResponse = chain.proceed(chain.request())
|
// val originalResponse = chain.proceed(chain.request())
|
||||||
// originalResponse.newBuilder()
|
// originalResponse.newBuilder()
|
||||||
@@ -104,7 +105,7 @@ fun Call.asObservableSuccess(): Observable<Response> {
|
|||||||
|
|
||||||
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
||||||
val progressClient = newBuilder()
|
val progressClient = newBuilder()
|
||||||
.cache(null)
|
// .cache(null)
|
||||||
// .addNetworkInterceptor { chain ->
|
// .addNetworkInterceptor { chain ->
|
||||||
// val originalResponse = chain.proceed(chain.request())
|
// val originalResponse = chain.proceed(chain.request())
|
||||||
// originalResponse.newBuilder()
|
// originalResponse.newBuilder()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.source
|
package eu.kanade.tachiyomi.source
|
||||||
|
|
||||||
// import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
|
||||||
interface ConfigurableSource : Source {
|
interface ConfigurableSource : Source {
|
||||||
|
|
||||||
// fun setupPreferenceScreen(screen: PreferenceScreen)
|
fun setupPreferenceScreen(screen: PreferenceScreen)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,358 @@
|
|||||||
|
package eu.kanade.tachiyomi.source
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
// import com.github.junrar.Archive
|
||||||
|
// import com.google.gson.JsonParser
|
||||||
|
// import eu.kanade.tachiyomi.R
|
||||||
|
// import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
// import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
// import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
// import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
// import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
// import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
// import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||||
|
// import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||||
|
// import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
// import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||||
|
// import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
|
// import rx.Observable
|
||||||
|
// import timber.log.Timber
|
||||||
|
// import java.io.File
|
||||||
|
// import java.io.FileInputStream
|
||||||
|
// import java.io.InputStream
|
||||||
|
// import java.util.Locale
|
||||||
|
// import java.util.concurrent.TimeUnit
|
||||||
|
// import java.util.zip.ZipFile
|
||||||
|
|
||||||
|
class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
|
companion object {
|
||||||
|
const val ID = 0L
|
||||||
|
// const val HELP_URL = "https://tachiyomi.org/help/guides/reading-local-manga/"
|
||||||
|
//
|
||||||
|
// private const val COVER_NAME = "cover.jpg"
|
||||||
|
// private val SUPPORTED_ARCHIVE_TYPES = setOf("zip", "rar", "cbr", "cbz", "epub")
|
||||||
|
//
|
||||||
|
// private val POPULAR_FILTERS = FilterList(OrderBy())
|
||||||
|
// private val LATEST_FILTERS = FilterList(OrderBy().apply { state = Filter.Sort.Selection(1, false) })
|
||||||
|
// private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)
|
||||||
|
//
|
||||||
|
// fun updateCover(context: Context, manga: SManga, input: InputStream): File? {
|
||||||
|
// val dir = getBaseDirectories(context).firstOrNull()
|
||||||
|
// if (dir == null) {
|
||||||
|
// input.close()
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
// val cover = File("${dir.absolutePath}/${manga.url}", COVER_NAME)
|
||||||
|
//
|
||||||
|
// // It might not exist if using the external SD card
|
||||||
|
// cover.parentFile?.mkdirs()
|
||||||
|
// input.use {
|
||||||
|
// cover.outputStream().use {
|
||||||
|
// input.copyTo(it)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return cover
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun getBaseDirectories(context: Context): List<File> {
|
||||||
|
// val c = context.getString(R.string.app_name) + File.separator + "local"
|
||||||
|
// return DiskUtil.getExternalStorages(context).map { File(it.absolutePath, c) }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val id = ID
|
||||||
|
override val name = "Local source"
|
||||||
|
override val lang = ""
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// override fun toString() = context.getString(R.string.local_source)
|
||||||
|
//
|
||||||
|
// override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
|
||||||
|
//
|
||||||
|
// override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
// val baseDirs = getBaseDirectories(context)
|
||||||
|
//
|
||||||
|
// val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L
|
||||||
|
// var mangaDirs = baseDirs
|
||||||
|
// .asSequence()
|
||||||
|
// .mapNotNull { it.listFiles()?.toList() }
|
||||||
|
// .flatten()
|
||||||
|
// .filter { it.isDirectory }
|
||||||
|
// .filterNot { it.name.startsWith('.') }
|
||||||
|
// .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
|
||||||
|
// .distinctBy { it.name }
|
||||||
|
//
|
||||||
|
// val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state
|
||||||
|
// when (state?.index) {
|
||||||
|
// 0 -> {
|
||||||
|
// mangaDirs = if (state.ascending) {
|
||||||
|
// mangaDirs.sortedBy { it.name.toLowerCase(Locale.ENGLISH) }
|
||||||
|
// } else {
|
||||||
|
// mangaDirs.sortedByDescending { it.name.toLowerCase(Locale.ENGLISH) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 1 -> {
|
||||||
|
// mangaDirs = if (state.ascending) {
|
||||||
|
// mangaDirs.sortedBy(File::lastModified)
|
||||||
|
// } else {
|
||||||
|
// mangaDirs.sortedByDescending(File::lastModified)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val mangas = mangaDirs.map { mangaDir ->
|
||||||
|
// SManga.create().apply {
|
||||||
|
// title = mangaDir.name
|
||||||
|
// url = mangaDir.name
|
||||||
|
//
|
||||||
|
// // Try to find the cover
|
||||||
|
// for (dir in baseDirs) {
|
||||||
|
// val cover = File("${dir.absolutePath}/$url", COVER_NAME)
|
||||||
|
// if (cover.exists()) {
|
||||||
|
// thumbnail_url = cover.absolutePath
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val chapters = fetchChapterList(this).toBlocking().first()
|
||||||
|
// if (chapters.isNotEmpty()) {
|
||||||
|
// val chapter = chapters.last()
|
||||||
|
// val format = getFormat(chapter)
|
||||||
|
// if (format is Format.Epub) {
|
||||||
|
// EpubFile(format.file).use { epub ->
|
||||||
|
// epub.fillMangaMetadata(this)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Copy the cover from the first chapter found.
|
||||||
|
// if (thumbnail_url == null) {
|
||||||
|
// try {
|
||||||
|
// val dest = updateCover(chapter, this)
|
||||||
|
// thumbnail_url = dest?.absolutePath
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// Timber.e(e)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return Observable.just(MangasPage(mangas.toList(), false))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
|
||||||
|
//
|
||||||
|
// override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
|
// getBaseDirectories(context)
|
||||||
|
// .asSequence()
|
||||||
|
// .mapNotNull { File(it, manga.url).listFiles()?.toList() }
|
||||||
|
// .flatten()
|
||||||
|
// .firstOrNull { it.extension == "json" }
|
||||||
|
// ?.apply {
|
||||||
|
// val reader = this.inputStream().bufferedReader()
|
||||||
|
// val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
//
|
||||||
|
// manga.title = json["title"]?.asString ?: manga.title
|
||||||
|
// manga.author = json["author"]?.asString ?: manga.author
|
||||||
|
// manga.artist = json["artist"]?.asString ?: manga.artist
|
||||||
|
// manga.description = json["description"]?.asString ?: manga.description
|
||||||
|
// manga.genre = json["genre"]?.asJsonArray?.joinToString(", ") { it.asString }
|
||||||
|
// ?: manga.genre
|
||||||
|
// manga.status = json["status"]?.asInt ?: manga.status
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return Observable.just(manga)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
// val chapters = getBaseDirectories(context)
|
||||||
|
// .asSequence()
|
||||||
|
// .mapNotNull { File(it, manga.url).listFiles()?.toList() }
|
||||||
|
// .flatten()
|
||||||
|
// .filter { it.isDirectory || isSupportedFile(it.extension) }
|
||||||
|
// .map { chapterFile ->
|
||||||
|
// SChapter.create().apply {
|
||||||
|
// url = "${manga.url}/${chapterFile.name}"
|
||||||
|
// name = if (chapterFile.isDirectory) {
|
||||||
|
// chapterFile.name
|
||||||
|
// } else {
|
||||||
|
// chapterFile.nameWithoutExtension
|
||||||
|
// }
|
||||||
|
// date_upload = chapterFile.lastModified()
|
||||||
|
//
|
||||||
|
// val format = getFormat(this)
|
||||||
|
// if (format is Format.Epub) {
|
||||||
|
// EpubFile(format.file).use { epub ->
|
||||||
|
// epub.fillChapterMetadata(this)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val chapNameCut = stripMangaTitle(name, manga.title)
|
||||||
|
// if (chapNameCut.isNotEmpty()) name = chapNameCut
|
||||||
|
// ChapterRecognition.parseChapterNumber(this, manga)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .sortedWith(
|
||||||
|
// Comparator { c1, c2 ->
|
||||||
|
// val c = c2.chapter_number.compareTo(c1.chapter_number)
|
||||||
|
// if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// .toList()
|
||||||
|
//
|
||||||
|
// return Observable.just(chapters)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Strips the manga title from a chapter name, matching only based on alphanumeric and whitespace
|
||||||
|
// * characters.
|
||||||
|
// */
|
||||||
|
// private fun stripMangaTitle(chapterName: String, mangaTitle: String): String {
|
||||||
|
// var chapterNameIndex = 0
|
||||||
|
// var mangaTitleIndex = 0
|
||||||
|
// while (chapterNameIndex < chapterName.length && mangaTitleIndex < mangaTitle.length) {
|
||||||
|
// val chapterChar = chapterName[chapterNameIndex]
|
||||||
|
// val mangaChar = mangaTitle[mangaTitleIndex]
|
||||||
|
// if (!chapterChar.equals(mangaChar, true)) {
|
||||||
|
// val invalidChapterChar = !chapterChar.isLetterOrDigit() && !chapterChar.isWhitespace()
|
||||||
|
// val invalidMangaChar = !mangaChar.isLetterOrDigit() && !mangaChar.isWhitespace()
|
||||||
|
//
|
||||||
|
// if (!invalidChapterChar && !invalidMangaChar) {
|
||||||
|
// return chapterName
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (invalidChapterChar) {
|
||||||
|
// chapterNameIndex++
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (invalidMangaChar) {
|
||||||
|
// mangaTitleIndex++
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// chapterNameIndex++
|
||||||
|
// mangaTitleIndex++
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return chapterName.substring(chapterNameIndex).trimStart(' ', '-', '_', ',', ':')
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
|
// return Observable.error(Exception("Unused"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun isSupportedFile(extension: String): Boolean {
|
||||||
|
// return extension.toLowerCase() in SUPPORTED_ARCHIVE_TYPES
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun getFormat(chapter: SChapter): Format {
|
||||||
|
// val baseDirs = getBaseDirectories(context)
|
||||||
|
//
|
||||||
|
// for (dir in baseDirs) {
|
||||||
|
// val chapFile = File(dir, chapter.url)
|
||||||
|
// if (!chapFile.exists()) continue
|
||||||
|
//
|
||||||
|
// return getFormat(chapFile)
|
||||||
|
// }
|
||||||
|
// throw Exception("Chapter not found")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun getFormat(file: File): Format {
|
||||||
|
// val extension = file.extension
|
||||||
|
// return if (file.isDirectory) {
|
||||||
|
// Format.Directory(file)
|
||||||
|
// } else if (extension.equals("zip", true) || extension.equals("cbz", true)) {
|
||||||
|
// Format.Zip(file)
|
||||||
|
// } else if (extension.equals("rar", true) || extension.equals("cbr", true)) {
|
||||||
|
// Format.Rar(file)
|
||||||
|
// } else if (extension.equals("epub", true)) {
|
||||||
|
// Format.Epub(file)
|
||||||
|
// } else {
|
||||||
|
// throw Exception("Invalid chapter format")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun updateCover(chapter: SChapter, manga: SManga): File? {
|
||||||
|
// return when (val format = getFormat(chapter)) {
|
||||||
|
// is Format.Directory -> {
|
||||||
|
// val entry = format.file.listFiles()
|
||||||
|
// ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||||
|
// ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||||
|
//
|
||||||
|
// entry?.let { updateCover(context, manga, it.inputStream()) }
|
||||||
|
// }
|
||||||
|
// is Format.Zip -> {
|
||||||
|
// ZipFile(format.file).use { zip ->
|
||||||
|
// val entry = zip.entries().toList()
|
||||||
|
// .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||||
|
// .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||||
|
//
|
||||||
|
// entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// is Format.Rar -> {
|
||||||
|
// Archive(format.file).use { archive ->
|
||||||
|
// val entry = archive.fileHeaders
|
||||||
|
// .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||||
|
// .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
|
||||||
|
//
|
||||||
|
// entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// is Format.Epub -> {
|
||||||
|
// EpubFile(format.file).use { epub ->
|
||||||
|
// val entry = epub.getImagesFromPages()
|
||||||
|
// .firstOrNull()
|
||||||
|
// ?.let { epub.getEntry(it) }
|
||||||
|
//
|
||||||
|
// entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private class OrderBy : Filter.Sort("Order by", arrayOf("Title", "Date"), Selection(0, true))
|
||||||
|
//
|
||||||
|
// override fun getFilterList() = FilterList(OrderBy())
|
||||||
|
//
|
||||||
|
// sealed class Format {
|
||||||
|
// data class Directory(val file: File) : Format()
|
||||||
|
// data class Zip(val file: File) : Format()
|
||||||
|
// data class Rar(val file: File) : Format()
|
||||||
|
// data class Epub(val file: File) : Format()
|
||||||
|
// }
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user