Compare commits

..
302 Commits
Author SHA1 Message Date
tarsin d7fcda1f83 Release 1.8.5.3
Releases / Build (push) Has been cancelled
2023-01-23 09:26:25 +08:00
FooIbar b832c24185 Remove app:navGraph from xml
Since we set it through setGraph()
2023-01-23 08:16:20 +08:00
FooIbar 3b04f8c695 Try to bypass VPN when domain fronting is enabled
Note VPN may disallow bypass
2023-01-22 21:09:44 +08:00
tarsin 0ef11b58d2 Move route out of object 2023-01-22 16:27:36 +08:00
tarsin bf13bbf506 Tweak directory structure 2023-01-22 16:24:02 +08:00
tarsin 92c94aa22a Fix typo 2023-01-22 14:04:28 +08:00
tarsin 1c0785432b Avoid unnecessary domain fronting, #711 2023-01-22 13:47:12 +08:00
Allan Nordhøy dcb9a50061 Translated using Weblate (Norwegian Bokmål)
Currently translated at 46.0% (282 of 613 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/nb_NO/
2023-01-22 09:09:19 +08:00
FooIbar de7f4bdb6e Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (612 of 613 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/zh_Hans/
2023-01-22 09:09:19 +08:00
Allan Nordhøy 73d636dba6 Translated using Weblate (English)
Currently translated at 99.8% (612 of 613 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/en/
2023-01-22 09:09:19 +08:00
FooIbar 3f1c453ff6 Update strings.xml 2023-01-21 22:48:47 +08:00
tarsin ecb6baaebb Update arrays.xml 2023-01-21 10:03:59 +08:00
tarsin e1cba45486 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.3% (609 of 613 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/zh_Hans/
2023-01-21 09:50:49 +08:00
Allan Nordhøy ea4181b243 Translated using Weblate (English)
Currently translated at 99.8% (612 of 613 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/en/
2023-01-21 09:50:49 +08:00
tarsin 6c5b9f8851 Add nb-rNO to supported languages 2023-01-21 09:10:57 +08:00
c1fccaf240 Translations update from Hosted Weblate (#701)
* Translated using Weblate (English)

Currently translated at 99.8% (610 of 611 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/en/

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (English)

Currently translated at 99.8% (611 of 612 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/en/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 19.7% (121 of 612 strings)

Translation: Ehviewer-Overhauled/Android strings
Translate-URL: https://hosted.weblate.org/projects/ehviewer-overhauled/android-strings/nb_NO/

Co-authored-by: tarsin <yuanqingxiang233@163.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
2023-01-21 08:32:21 +08:00
FooIbar e7c223da7d fixup! Resolve #604 2023-01-21 08:12:22 +08:00
FooIbar 6bfe1ba876 Resolve #703 2023-01-20 23:11:23 +08:00
FooIbar 574e5dab18 Remove minimum rating checkbox in advanced search 2023-01-20 21:23:30 +08:00
tarsin 5dc9b83831 Update README.md 2023-01-20 19:22:21 +08:00
FooIbar 57a214a9a4 Fix thumb url for Downloads/History/LocalFavorite 2023-01-20 18:58:29 +08:00
FooIbar cf48eb94af Convert EhUtils to kotlin 2023-01-20 18:58:29 +08:00
FooIbar 683f1e488a Rename .java to .kt 2023-01-20 18:58:29 +08:00
FooIbar e888ae4da7 Use different app name for debug variant 2023-01-20 18:58:29 +08:00
tarsin 4512f2a375 Bump Versio
Releases / Build (push) Has been cancelled
2023-01-20 18:07:19 +08:00
tarsin 68442afa0f Update coil 2023-01-20 12:31:46 +08:00
tarsin ddf4d0cbc3 Use our modified coil temporarily 2023-01-20 10:59:38 +08:00
FooIbar 4be185e428 Resolve #696 2023-01-19 21:30:24 +08:00
tarsin f75e66ac19 Upgrade compose compiler 2023-01-18 21:21:40 +08:00
tarsin abda781f02 Cleanup code 2023-01-18 18:50:38 +08:00
tarsin c1533ec66d Avoid unnecessary lambda object create 2023-01-18 18:23:46 +08:00
tarsin c8319446b0 Bump Version
Releases / Build (push) Has been cancelled
2023-01-18 17:44:45 +08:00
tarsin 7ae452cf13 Fixup! Use FileChannel transferFrom 2023-01-18 12:29:19 +08:00
tarsin 4c024c5faa Fixup! Use FileChannel transferFrom 2023-01-18 12:24:10 +08:00
tarsin 9d16e9d98c Use FileChannel transferFrom, Resolve #682 2023-01-18 12:18:43 +08:00
tarsin 48357d08f7 Add TODOs 2023-01-18 10:47:15 +08:00
tarsin 8e0bcf40db Rewrite the GIF frame delay if it's below a threshold, reland, Resolve #641 2023-01-18 10:46:21 +08:00
renovate[bot] 85ef5f04dd Update plugin com.android.application to v8.0.0-beta01 2023-01-18 09:08:12 +08:00
tarsin 18e0748d14 Revert "Update dependency gradle to v8.0-rc-2"
This reverts commit cdde5b098e.
2023-01-18 09:08:01 +08:00
renovate[bot] cdde5b098e Update dependency gradle to v8.0-rc-2 2023-01-17 21:19:03 +08:00
FooIbar f83964752d Set gallery site to eh when sign out 2023-01-17 16:49:16 +08:00
tarsin 0c3e34bbf9 Fix type warning 2023-01-17 10:50:56 +08:00
tarsin 6dea7faac8 Introduce gif rewriter stub 2023-01-17 10:50:11 +08:00
tarsin 726f482c33 Introduce new file read path 2023-01-17 10:40:20 +08:00
tarsin cb242f664a Convert Archive load to ByteBufferSource path 2023-01-17 09:29:44 +08:00
tarsin edc0345970 Introduce out image source type 2023-01-17 09:15:48 +08:00
tarsin a50535dd76 Fix crash 2023-01-17 09:00:10 +08:00
tarsin 505942aa25 Explicitly depend latest okio 2023-01-17 08:56:05 +08:00
tarsin 38a593a9de Resolve #686 2023-01-17 08:51:51 +08:00
tarsin da87fb2551 Fix image save 2023-01-17 08:20:57 +08:00
tarsin d4dc65a4ba Drop old disklrucache (#685)
* Drop Old DiskLruCache

* Avoid being interrupted when completing editor

* Protect all journal operations

* Revert "Protect all journal operations"

This reverts commit 5b7c7e3b33a515ee3e92826a6b76c2dfb3040c5d.

* Revert "Avoid being interrupted when completing editor"

This reverts commit cf4fbed1281eff766c6e65b6f685dfc355208a0b.

* Shutdown Thread gracefully

Test: m, Check no thread leaks

* Fix download extensions
2023-01-16 21:44:25 +08:00
Edward Wu 53463caedd Ehviewer: Translate: Update zh-rTW Translate (01/06)
* Update strings translate
* Reformat to be consistent with Simplified Chinese xml

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>
2023-01-16 18:34:37 +08:00
tarsin 0707a0fad8 Report DiskLruCache corruption if met 2023-01-16 17:42:55 +08:00
tarsin dd1024af27 Use Tonal Button for register 2023-01-16 17:38:24 +08:00
FooIbar 7edc58a6da Get funds from GP Exchange when unavailable from the archive page 2023-01-16 17:08:03 +08:00
FooIbar 5a5da0cace Convert EhUrl to kotlin 2023-01-16 17:08:03 +08:00
FooIbar 53f02ed366 Rename .java to .kt 2023-01-16 17:08:03 +08:00
tarsin 27cfa885e7 Resolve #681 2023-01-16 12:11:47 +08:00
tarsin beb132d604 Revert "Rewrite the GIF frame delay if it's below a threshold (coil-kt/coil#783) (#672)"
This reverts commit 0ebb55551c.
2023-01-16 11:18:06 +08:00
tarsin b1b10298b4 Revert "Avoid unnecessary overhead"
This reverts commit eada1bc023.
2023-01-16 11:17:53 +08:00
tarsin b1993ff7d2 Fix Gif LruCache size 2023-01-16 10:56:02 +08:00
FooIbar 2afe5fe5b2 Show current funds in archive dialog 2023-01-15 22:19:41 +08:00
FooIbar 52fa8f64cc Convert ArchiveParser to kotlin 2023-01-15 22:19:41 +08:00
FooIbar 4f7978070f Rename .java to .kt 2023-01-15 22:19:41 +08:00
tarsin a909983822 Don't exclude compose metadata 2023-01-15 16:45:43 +08:00
小白-白 5267cad896 Fix wrong extension when downloading 2023-01-15 14:34:03 +08:00
小白-白 ce274cf65b Hide unavailable archive 2023-01-15 14:34:03 +08:00
tarsin 55b6b2e34d Cleanup res 2023-01-15 13:29:18 +08:00
tarsin 5e4e2971b5 Fix code style 2023-01-15 13:24:09 +08:00
tarsin a933bcfb1e Resolve #677 2023-01-15 13:23:05 +08:00
tarsin b4b38e27f3 Resolve #675 2023-01-15 13:17:13 +08:00
tarsin bc70824b57 Fixup! Fix stuck when loading preview 2023-01-15 12:55:05 +08:00
tarsin 87ecaaa6a4 Fixup! Use viewLifecycleOwner 2023-01-15 12:52:21 +08:00
tarsin c4cd9cd802 Fix stuck when loading preview 2023-01-15 12:49:18 +08:00
tarsin bbe0e8c5a9 Remove preview limit
At least it works well on my MT6765
2023-01-15 12:42:04 +08:00
tarsin 3f8984d2dc Resolve #668 2023-01-15 10:50:55 +08:00
tarsin f695c4598a Bump Version
Releases / Build (push) Has been cancelled
2023-01-15 09:57:00 +08:00
tarsin eada1bc023 Avoid unnecessary overhead 2023-01-14 19:56:55 +08:00
FooIbar 0ebb55551c Rewrite the GIF frame delay if it's below a threshold (coil-kt/coil#783) (#672)
* Rewrite the GIF frame delay if it's below a threshold (coil-kt/coil#783)

Resolve #641

* fixup! Rewrite the GIF frame delay if it's below a threshold (coil-kt/coil#783)
2023-01-14 17:19:48 +08:00
tarsin 20bce419c9 Fix GalleryDetailScene bindViewFirst 2023-01-14 12:19:58 +08:00
tarsin ca3fc370c3 Minor refactor 2023-01-14 12:11:27 +08:00
tarsin 94a8b78d1a Use typesafe request 2023-01-14 12:03:55 +08:00
tarsin 06e9ff6fcf Add comments for some dependencies 2023-01-14 11:49:29 +08:00
FooIbar 699e74656d Disable drag and swipe on "All" or "Default" label 2023-01-14 11:41:51 +08:00
tarsin b3ad6ef8b8 Move out DirectionalViewPager
We rewrite it with ViewPager2 later
2023-01-14 11:33:26 +08:00
tarsin 673c8e25f5 Cleanup dependencies 2023-01-14 11:27:05 +08:00
tarsin 333c8e812d Remove lifecycle annotation
We turned to viewbinding
2023-01-14 10:34:44 +08:00
tarsin f0f7a88cfb Kotlinize EhFilter 2023-01-14 10:28:47 +08:00
tarsin 0661d78bce Rename .java to .kt 2023-01-14 10:28:47 +08:00
tarsin dc4aece272 Inline EhCookieStore supertype 2023-01-14 10:21:59 +08:00
tarsin 57a312b0b4 Singletonize EhCookieStore 2023-01-14 10:11:49 +08:00
tarsin b9897ed6ac Rename .java to .kt 2023-01-14 10:11:49 +08:00
tarsin 733230f037 Convert EhCallback to kotlin 2023-01-14 10:04:55 +08:00
tarsin fc6ba3e6ad Rename .java to .kt 2023-01-14 10:04:55 +08:00
tarsin 2d7c539c5f Convert FavouriteScene to kotlin 2023-01-14 09:55:11 +08:00
tarsin 7f34965026 Rename .java to .kt 2023-01-14 09:55:11 +08:00
tarsin de8b915cff Convert GalleryAdapter to kotlin 2023-01-14 09:32:15 +08:00
tarsin d56040581d Rename .java to .kt 2023-01-14 09:32:15 +08:00
tarsin 9869b7bbb5 Convert GalleryCommentsScene to kotlin 2023-01-14 09:26:56 +08:00
tarsin 7df97c28c0 Rename .java to .kt 2023-01-14 09:26:56 +08:00
tarsin 018a559f78 Fix crash in grid mode 2023-01-13 16:49:52 +08:00
tarsin 3b32ad5fed Don't refresh fragment when reselected 2023-01-13 10:26:41 +08:00
tarsin f796d2a842 Tweak NavigationUI to disable multiple backstack and use custom anim 2023-01-13 10:13:10 +08:00
FooIbar 2f7e84e126 Refresh the item as well if the confirmation dialog is canceled 2023-01-12 17:31:28 +08:00
FooIbar 996ddbf3db fixup! Support drag reordering for downloads 2023-01-12 16:48:30 +08:00
FooIbar 7779ba2ac5 Add "All" in download labels
Resolve #412
2023-01-12 15:23:12 +08:00
tarsin 4dadeab762 Avoid querying database on Main Thread, GalleryDetailScene, #629 2023-01-12 12:06:10 +08:00
tarsin f4d3131ef3 Put history in IO coroutines, #629 2023-01-12 11:53:41 +08:00
renovate[bot] efa2ac803e Update dependency androidx.compose:compose-bom to v2023 2023-01-12 11:50:46 +08:00
renovate[bot] e0be9d46bd Update room_version to v2.5.0 2023-01-12 11:50:32 +08:00
tarsin 106b27dbec Finalize ViewBindingize GalleryDetailScene 2023-01-12 11:46:56 +08:00
tarsin f764567523 ViewBindingize GalleryDetailScene, step 6 2023-01-12 11:40:56 +08:00
tarsin 245f8669bc ViewBindingize GalleryDetailScene, step 5 2023-01-12 11:34:51 +08:00
tarsin b265d9bba6 ViewBindingize GalleryDetailScene, step 4 2023-01-12 11:28:12 +08:00
tarsin 72228574f9 ViewBindingize GalleryDetailScene, step 3 2023-01-12 11:16:32 +08:00
tarsin fba1ac3d90 ViewBindingize GalleryDetailScene, step 2 2023-01-12 11:11:32 +08:00
tarsin 4a73fb565e ViewBindingize GalleryDetailScene, step 1 2023-01-12 11:05:41 +08:00
tarsin 13011ed4af Kotlinize GalleryDetailScene 2023-01-12 10:32:49 +08:00
tarsin b2eccdefd3 Rename .java to .kt 2023-01-12 10:32:49 +08:00
tarsin dd2d6a977b Kotlinize GalleryHolder 2023-01-12 10:01:07 +08:00
tarsin 38cd1b6040 Rename .java to .kt 2023-01-12 10:01:07 +08:00
tarsin 1a28bbcde8 ViewBindinglize GalleryPreviewsScene 2023-01-12 09:57:54 +08:00
tarsin 91013daadc Kotlinize GalleryPreviewsScene 2023-01-12 09:50:09 +08:00
tarsin 1ad435e79d Rename .java to .kt 2023-01-12 09:50:09 +08:00
tarsin fd5c300c53 Remove unused navigateToTop 2023-01-12 09:39:57 +08:00
tarsin aa7d9cf29c Remove unused TransitionNameFactory 2023-01-12 09:38:14 +08:00
tarsin 8db1c3400e Kotlinize ProgressScene 2023-01-12 09:34:48 +08:00
tarsin e723336706 Rename .java to .kt 2023-01-12 09:34:48 +08:00
tarsin b5cd4c36ab Singletonize ItemTouchHelper 2023-01-12 09:22:35 +08:00
tarsin d1928f2647 Fixup! Support drag reordering for downloads, fix drag ripple 2023-01-12 09:12:37 +08:00
tarsin 61621ae00b Use compatible compose compiler 2023-01-12 08:56:46 +08:00
tarsin ac20755c45 Bump Version
Releases / Build (push) Has been cancelled
2023-01-12 08:48:14 +08:00
renovate[bot] 610202bbbd Update dependency androidx.startup:startup-runtime to v1.2.0-alpha02 2023-01-12 07:23:13 +08:00
renovate[bot] 58ce7134e1 Update lifecycle_version to v2.6.0-alpha04 2023-01-12 07:22:59 +08:00
FooIbar 71f91cd79e Get updated positions in click listeners
Resolve #655
2023-01-12 07:22:32 +08:00
tarsin 8c821ecad3 Make EhEngine obtain its okhttpClient 2023-01-11 21:46:17 +08:00
FooIbar c704940462 Support drag reordering for downloads
Resolve #54
2023-01-11 21:31:18 +08:00
tarsin 6a7cd1b89a Add some comments and TODO 2023-01-11 14:35:43 +08:00
tarsin e29da0ba13 Cleanup res 2023-01-11 14:28:53 +08:00
tarsin 7791fa10f5 Getprofile in SelectSiteScreen 2023-01-11 14:11:36 +08:00
tarsin 48ed3c8f31 Avoid webview reentry 2023-01-11 14:07:44 +08:00
tarsin 7ad7a9b94e Add WebviewSignInScreen 2023-01-11 13:59:28 +08:00
tarsin 54b929ba24 Use LocalClipboardManager to better support multiplatform 2023-01-11 13:28:00 +08:00
FooIbar 7b655c6260 Show dialog before removing quick search 2023-01-11 13:24:33 +08:00
FooIbar 4e8d41b145 Show dialog before removing download label 2023-01-11 13:24:33 +08:00
tarsin f036774e07 Remove origin signin scene 2023-01-11 13:22:56 +08:00
tarsin 97d1bd7685 Start moving to new compose login activity 2023-01-11 13:12:16 +08:00
tarsin 8e39449856 Restore finish from SelectSiteScreen 2023-01-11 13:06:27 +08:00
tarsin 30f1a5a6eb Add SelectSiteScreen Impl 2023-01-11 13:01:46 +08:00
tarsin 627abb047c Compose dependency tweaks 2023-01-11 12:07:33 +08:00
tarsin 9284008afe Refine CookieSignInScreen 2023-01-11 11:44:13 +08:00
tarsin 9159f4616f Refine SignInScreen KeyBoard options 2023-01-11 10:41:42 +08:00
tarsin 73296b1430 Refine SignInScreen TextInput 2023-01-11 10:25:43 +08:00
tarsin c74e622d69 Add snackbar to CookieSignInScreen layout 2023-01-11 09:55:41 +08:00
tarsin f48eb55516 Basic CookieSignInScreen layout 2023-01-11 09:23:10 +08:00
tarsin b01f6ef8d3 Fixup! use local functions 2023-01-11 08:27:46 +08:00
tarsin 167185ef45 Basic signin refine 2023-01-11 07:58:40 +08:00
tarsin 777a8ffeaa Refine SignInScreen 2023-01-10 20:58:28 +08:00
FooIbar b2b3e6c485 Add tips for torrent download 2023-01-10 20:30:26 +08:00
tarsin 613d502fc1 Use Mdc3Theme 2023-01-10 20:26:39 +08:00
tarsin 79da457db4 Basic signin Compose layout 2023-01-10 20:21:35 +08:00
tarsin b1a431ed82 Use mca component to judge is dynamic color available 2023-01-10 18:17:10 +08:00
tarsin b0f2776a23 Cleanup compose theme and navigation skeleton 2023-01-10 18:10:24 +08:00
tarsin 900c346ee8 Fixup! Convert login to compose, step 1, skeleton 2023-01-10 17:57:04 +08:00
tarsin 7541a87b21 Convert login to compose, step 1, skeleton 2023-01-10 17:54:55 +08:00
tarsin 2547c67ebd Fixup migrate to androidx navigation 2023-01-09 19:05:37 +08:00
tarsin 5ae6a0b287 Fixup migrate to androidx navigation 2023-01-09 19:00:04 +08:00
tarsin f718fc0c89 Reduce one more layout depth 2023-01-09 18:07:05 +08:00
tarsin 57e69ba7be Layout tweaks 2023-01-09 17:47:44 +08:00
tarsin 6168b3e047 Fixup! Use viewLifecycleOwner 2023-01-09 14:17:49 +08:00
tarsin 3e500500e0 Rewrite SelectSiteScene 2023-01-09 13:41:45 +08:00
tarsin 42f06ff84a Rename .java to .kt 2023-01-09 13:41:45 +08:00
tarsin a7824fc473 Convert WebViewSignInScene to kotlin 2023-01-09 13:35:53 +08:00
tarsin 67e7fd6928 Rename .java to .kt 2023-01-09 13:35:53 +08:00
tarsin 450e8badba GetProfile when signin with cookies 2023-01-09 13:21:48 +08:00
tarsin 704c211e11 Rewrite CookieSignInScene with modern viewBinding 2023-01-09 13:14:30 +08:00
tarsin 0ad871cb5b Rename .java to .kt 2023-01-09 13:14:30 +08:00
tarsin 9920605570 Checkout xz to 5.4.0 2023-01-09 12:56:10 +08:00
tarsin b1e8678dbf Fixup! Rewrite SignInScene with modern coroutines 2023-01-08 22:30:35 +08:00
tarsin e2cd2ddc0d Rewrite SignInScene with modern coroutines 2023-01-08 22:22:34 +08:00
tarsin a3be0dfddf Rewrite SignInScene 2023-01-08 21:33:50 +08:00
tarsin 4b615ffc0b Rename .java to .kt 2023-01-08 21:33:50 +08:00
tarsin 64cbd189ad Convert to kotlin 2023-01-08 21:15:26 +08:00
tarsin 0fae5a128f Rename .java to .kt 2023-01-08 21:15:26 +08:00
tarsin 518b44216f Navchecked item now handled internally 2023-01-08 20:57:59 +08:00
tarsin 9ef4512ef9 Reformat code 2023-01-08 20:42:23 +08:00
tarsin 8bb5f1e955 Initial migrate to androidx navigation (#647)
* Neutralize scene firstly

* Remove StageLayout

* Add nav graph

* Rename .java to .kt

* Convert BaseScene to kotlin

* Add GalleryDetailScene to navgraph

No need to check topfragment is callback target fragment, since these callback must be cancelled in fragment's lifecycle when DestroyView

* Restore funcs

* Restore preview

* Revert "Convert BaseScene to kotlin"

Should not do it here
This reverts commit 5d2e3af6ad85fef7604d8df19ee58a957390cdb5.

* Revert "Rename .java to .kt"

This reverts commit 128c655bc98fc54528d58534651d61ff47b2284d.

* Restore drawer

* Remove all getScene usage

* Remove all getStageActivity usage

* Remove all getStageActivity usage

* setupWithNavController

* Refactor MainActivity with viewBinding

* Add navdrawer corresponding navdestination

* Migrate All normal fragment

* Add anims

* Fix statusbar flicker

* Remove SceneApplication

* Remove SceneFragment

* Remove StageActivity

* Drop scene totally

* Dynamically set navgraph

* Restore signin

* Restore login via cookies

* Restore login via webview

* Add some TODOs

* Fixup

* Restore onProvideAssistContent

* Restore checkClipboard

* Restore intent handle

* Fix handle intent

* Remove unused EhUrlOpener

* Let system handle that intent

* Restore handle download intent

* Restore AboveSnackerLayout

* Fix the last navigation
2023-01-08 20:37:11 +08:00
FooIbar 8f8932f617 Fix potential integer overflow 2023-01-08 18:06:49 +08:00
tarsin 23fd1d75c4 Fix supertype corresponding parameter warning 2023-01-08 09:15:22 +08:00
stevenyomi 593dcef550 Fix floating-point error in navigate pan (#8856) 2023-01-08 09:04:53 +08:00
o0kam1 d769fe9074 fix #642 2023-01-07 22:13:16 +08:00
tarsin 77a8fec790 Bump Version
Releases / Build (push) Has been cancelled
2023-01-07 09:10:50 +08:00
renovate[bot] 34440edfaa Update dependency com.google.android.material:material to v1.8.0-rc01 2023-01-06 21:54:07 +00:00
tarsin 3c3d8784ad Fixup cleanup Mainthread db query 2023-01-06 18:48:06 +08:00
tarsin 1591abc92c It's impossible no activity receive that Intent, remove workaround 2023-01-06 18:38:38 +08:00
tarsin 0ffa0c34f0 Remove some NotifyDataSetChanged usage 2023-01-06 18:27:54 +08:00
tarsin c7dc94b61b Singletonize GalleryListScene OnBackPressedCallback 2023-01-06 18:11:41 +08:00
tarsin a8b83871a4 Convert GalleryListScene drawer to ViewBinding 2023-01-06 18:07:57 +08:00
tarsin 6f547984ff Convert GalleryListScene to ViewBinding 2023-01-06 17:59:15 +08:00
tarsin 3979c11317 Refine GalleryListScene nullability 2023-01-06 17:36:55 +08:00
tarsin 2a1ec985f4 GalleryListScene urlBuilder cleanup 2023-01-06 17:27:30 +08:00
tarsin 2a1d49e945 Fix DownloadsScene delete label, #629 2023-01-06 13:02:12 +08:00
FooIbar 7b7e6140d2 Improve domain fronting description 2023-01-06 12:56:25 +08:00
tarsin 907cfd9694 Fix DownloadsScene add label, #629 2023-01-06 12:42:13 +08:00
tarsin b51b1d1ec6 Convert DownloadsScene to kotlin, #629 2023-01-06 12:37:24 +08:00
tarsin 6a026300e4 Rename .java to .kt 2023-01-06 12:37:24 +08:00
tarsin 8511907390 Fix HistoryScene history clearall, #629 2023-01-06 12:22:57 +08:00
tarsin 05a5be336e Fix HistoryScene history delete, #629 2023-01-06 12:21:18 +08:00
tarsin 5434b1ce19 Fix GalleryListScene quicksearch delete, #629 2023-01-06 12:19:20 +08:00
tarsin c2d4a4367c Fix GalleryListScene quicksearch insert, #629 2023-01-06 12:16:37 +08:00
tarsin de2f277513 Convert GalleryListScene to kotlin, #629 2023-01-06 12:11:20 +08:00
tarsin 283e522037 Rename .java to .kt 2023-01-06 12:11:20 +08:00
tarsin caebe4c23b Initialize database earlier, #629 2023-01-06 11:54:26 +08:00
tarsin b195277c9a Minor Refactor 2023-01-06 11:32:53 +08:00
tarsin 6b99cb6253 Make GalleryDetail well typed and implement Parcelable 2023-01-06 11:07:45 +08:00
txyyh a822bb670a Change bug-report issue template 2023-01-06 11:05:24 +08:00
tarsin af7cc11647 Extract properties to a standalone AbstractGalleryInfo 2023-01-06 11:04:18 +08:00
tarsin 0afe331d3d Rename GalleryInfoImpl to BaseGalleryInfo 2023-01-06 11:02:44 +08:00
tarsin 7af40f0cd7 Revert "Not good solution :("
This reverts commit 3283851dc4.
2023-01-06 11:00:28 +08:00
tarsin 52dc699ee2 Revert "Cleanup primary constructors"
This reverts commit ec28e21897.
2023-01-06 11:00:17 +08:00
tarsin ec28e21897 Cleanup primary constructors 2023-01-06 10:41:33 +08:00
tarsin 3283851dc4 Not good solution :( 2023-01-06 10:30:31 +08:00
tarsin ec5e0c7b25 Fuck you Jetbrains! 2023-01-06 09:55:09 +08:00
tarsin 13660cb70a Cleanup JvmFields in GalleryInfo 2023-01-06 09:33:24 +08:00
tarsin ce008f5576 Bump Version
Releases / Build (push) Has been cancelled
2023-01-06 08:49:47 +08:00
tarsin 25f7e8b35c Always show toast in BottomSheet mode, Resolve #633 2023-01-06 08:47:37 +08:00
tarsin 109398d955 Fix enter Reader from previews 2023-01-06 08:38:57 +08:00
tarsin 4a8551087b Fix crash when open history 2023-01-06 08:15:42 +08:00
tarsin 34febe3545 Fix crash when open download 2023-01-06 08:09:11 +08:00
tarsin 53a0307a8e Bump Versions
Releases / Build (push) Has been cancelled
2023-01-05 21:42:34 +08:00
tarsin 1c6f624fd1 Kotlinize EhExceptions 2023-01-05 21:21:47 +08:00
tarsin 780e5f08f9 Rename .java to .kt 2023-01-05 21:21:47 +08:00
tarsin 1bb73b1147 Kotlinize 2023-01-05 21:15:57 +08:00
tarsin 09b0d4d6c4 Rename .java to .kt 2023-01-05 21:15:57 +08:00
tarsin 7e3856fa05 Refine dao nullability 2023-01-05 21:10:14 +08:00
tarsin fbcb3f5436 Get download manager earlier, #629 2023-01-05 19:44:56 +08:00
tarsin 324b4ced06 Convert DAO to kotlin, step 3, and use ksp 2023-01-05 19:15:19 +08:00
tarsin 79c2abe790 Rename .java to .kt 2023-01-05 19:15:19 +08:00
tarsin cdab9168fc Convert DAO to kotlin, step 2 2023-01-05 19:01:35 +08:00
tarsin ce272724de Rename .java to .kt 2023-01-05 19:01:35 +08:00
tarsin 2fa0a19d3b Convert DAO to kotlin, step 1 2023-01-05 18:45:45 +08:00
tarsin 38b9a6e04a Rename .java to .kt 2023-01-05 18:45:45 +08:00
tarsin 22d3c2b6bf Migrate to Parcelize step 2 2023-01-05 17:42:07 +08:00
tarsin 218a8397c4 Rename .java to .kt 2023-01-05 17:42:07 +08:00
tarsin fd990da71e Fixup! Execute request in fragment's lifecycle coroutine scope, Resolve #628 2023-01-05 15:52:23 +08:00
小白-白 4a70c89a4d Restore download dir to avoid redownload
Resolve #547
2023-01-05 15:19:01 +08:00
小白-白 7e3143678f Drop clear download path cache
Useless and will cause redownload
2023-01-05 15:19:01 +08:00
renovate[bot] d80e09dfba Update plugin com.android.application to v8.0.0-alpha11 2023-01-04 23:13:36 +00:00
tarsin 3b3b251c21 Reformat code 2023-01-04 17:04:12 +08:00
tarsin 1e848bd70f Refine request api 2023-01-04 16:52:47 +08:00
tarsin fab2295445 Reorganize request code and fix bugs 2023-01-04 16:24:59 +08:00
tarsin a913bccc9e Rename .java to .kt 2023-01-04 16:24:59 +08:00
tarsin 57e7f89814 Execute request in fragment's lifecycle coroutine scope 2023-01-04 15:02:16 +08:00
tarsin f98fd5e5f1 Handle Call cancellation internally 2023-01-04 14:31:40 +08:00
tarsin e6af2e5c6b Make EhEngine task well cancelled in coroutine 2023-01-04 13:43:51 +08:00
tarsin d219989ec7 Rename .java to .kt 2023-01-04 13:43:51 +08:00
小白-白 b9daf608ff Add more info for torrent download 2023-01-04 12:26:46 +08:00
tarsin 869da61fd0 Use coroutine CancellationException 2023-01-04 12:26:09 +08:00
tarsin 9f79e2505c Coroutinize EhClient (#622)
* Rename .java to .kt

* Coroutinize EhClient to improve performance and clarity

* Nullability Compatibility

* Refine control flow

* Fix nullability
2023-01-04 11:51:31 +08:00
tarsin 3000b36dbd Exclude unused dependency 2023-01-03 16:23:03 +08:00
tarsin 1d21b0e5c4 Use ktx dependency 2023-01-03 15:56:59 +08:00
tarsin 8d6b0926d6 Convert to kotlin-parcelize 2023-01-03 15:33:04 +08:00
tarsin 4c39d13c80 Rename .java to .kt 2023-01-03 15:33:04 +08:00
tarsin 327dd1c73f Minor Refactor 2023-01-03 12:29:39 +08:00
tarsin b36b8f3234 Rename .java to .kt 2023-01-03 12:29:39 +08:00
FooIbar 5f6da284fb fixup! Create download dir only in download mode 2023-01-03 11:12:28 +08:00
tarsin 83d27e18cb Migrate to kotlin-parcelize 2023-01-03 09:56:37 +08:00
FooIbar e291bd58a8 Check if ehTags is initialized 2023-01-03 08:56:30 +08:00
FooIbar 23acdb6492 Support non-H@H archive download 2023-01-03 08:53:25 +08:00
FooIbar 3b5e56b049 Create download dir only in download mode
Do not try to find download dir, should use restore download instead
2023-01-02 22:10:27 +08:00
tarsin 57d4967f2a Implement #612 2023-01-02 21:39:46 +08:00
tarsin 80fcda149b Resolve #615 2023-01-02 21:03:47 +08:00
o0kam1 1a86eef52c add copy image 2023-01-02 20:45:24 +08:00
FooIbar 98d401f9c3 Fix callback order 2023-01-02 15:31:25 +08:00
FooIbar 0f2fae886e fixup! Set toolbar text when exiting search view 2023-01-02 15:31:25 +08:00
tarsin f30a9093af Remove databinding runtime
It is introduced when we migrated to tachiyomi reader
Seems tachiyomi's other parts uses databinding but not its reader
2023-01-02 13:26:03 +08:00
tarsin b4334da202 Fixup! Convert all kotlin fragment to viewbinding 2023-01-02 13:17:47 +08:00
tarsin 416fb3bda9 Fixup! Convert all kotlin fragment to databinding 2023-01-02 12:52:54 +08:00
tarsin 7e0fc2e364 Convert all kotlin fragment to databinding 2023-01-02 12:46:48 +08:00
tarsin 540d290be2 Convert HistoryScene to databinding 2023-01-02 11:32:55 +08:00
小白-白 53974ad88d Don't cancel task if mData is empty
Resolve #587
2023-01-02 11:18:58 +08:00
tarsin cd9b5e5a44 Simplify onNavigationClick 2023-01-02 10:56:20 +08:00
tarsin d9959bbcb2 Remove onBackPressed support on scene 2023-01-02 10:47:41 +08:00
tarsin d7dfd53244 Handle GalleryListScene SearchMode OnBackPressedCallback 2023-01-02 10:29:01 +08:00
tarsin 84489bf61b Handle GalleryCommentsScene EditPanel OnBackPressedCallback 2023-01-02 10:16:20 +08:00
tarsin 3fdb56390e Use colorAccent for Filled heart button 2023-01-02 09:44:22 +08:00
tarsin a34e766f70 Fix drawer OnBackPressedCallback 2023-01-02 09:35:53 +08:00
FooIbar d74023d4fa Resolve #605 2023-01-02 08:44:31 +08:00
FooIbar 41572e98ba Resolve #604 2023-01-02 08:44:31 +08:00
FooIbar 063087c87e Add horizontal clip spacing to avoid layout change 2023-01-02 08:44:31 +08:00
FooIbar 4a5d53d713 Set toolbar text when exiting search view 2023-01-02 08:44:31 +08:00
tarsin 9a262525ec Revert "Exit activity directly when back-pressed"
This reverts commit e327b4e84b.
2023-01-01 17:53:25 +08:00
tarsin e327b4e84b Exit activity directly when back-pressed 2023-01-01 17:26:39 +08:00
tarsin c128a2b518 Fix preference flow being cancelled 2023-01-01 17:12:05 +08:00
tarsin a3263ab4af Avoid unnecessary coroutine context switch 2023-01-01 16:58:34 +08:00
tarsin 4a4efe4725 Convert All activity to kotlin 2023-01-01 16:11:28 +08:00
tarsin 89d2433369 Rename .java to .kt 2023-01-01 16:11:28 +08:00
tarsin 023175ce6d Fixup convert to kotlin 2023-01-01 15:34:06 +08:00
FooIbar 21c8d24d57 Add size and cost for archive download 2023-01-01 15:27:22 +08:00
tarsin e49f5c95f4 Handle fablayout expand backpress on its callback 2023-01-01 15:26:32 +08:00
tarsin 6912e578ca Convert fabLayout to kotlin and refine nullability 2023-01-01 14:36:17 +08:00
tarsin 8680cff2b2 Rename .java to .kt 2023-01-01 14:36:17 +08:00
248 changed files with 15901 additions and 25634 deletions

No files matched your search

+4 -11
View File
@@ -17,12 +17,14 @@ body:
attributes:
label: 复现步骤 / Step to reproduce
description: |
在此处写下复现的方式
Describe how to reproduce here
请从全新安装应用开始,在此处写下复现的方式。请尽量附带录屏
Describe how to reproduce here with fresh installation, with screen recording if possible.
placeholder: |
0. 重新安装应用 / Fresh install
1.
2.
3.
[录屏] / [Screen recording]
validations:
required: true
@@ -111,15 +113,6 @@ body:
validations:
required: true
- type: textarea
id: log
attributes:
label: 应用日志 / App logs
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
placeholder: 06-15 17:44:53.704 23382 23382 E ...
validations:
required: true
- type: checkboxes
id: check
attributes:
+6
View File
@@ -73,6 +73,12 @@ with Material Design 3 and Dynamic Color Support
![screenshot-01](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-01.png)
![screenshot-02](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-02.png)
# Localization
Thank you to each of the **Ehviewer Overhauled** translators, and if you would like to contribute, please submit a translation via [Weblate](https://hosted.weblate.org/engage/ehviewer-overhauled/).
[<img src="https://hosted.weblate.org/widgets/ehviewer-overhauled/-/horizontal-auto.svg" alt="" />](https://hosted.weblate.org/engage/ehviewer-overhauled/)
# Thanks
Here is the libraries
+41 -77
View File
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
}
@@ -31,9 +32,9 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180030
versionName "1.8.5.2"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
versionCode 180031
versionName "1.8.5.3"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th', 'nb-rNO'
}
externalNativeBuild {
@@ -51,9 +52,7 @@ android {
jvmTarget = '19'
freeCompilerArgs += [
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
"-progressive"
]
}
@@ -65,7 +64,8 @@ android {
packagingOptions {
resources {
excludes += "/META-INF/**"
excludes += "/META-INF/CHANGES"
excludes += "/META-INF/README.md"
excludes += "/kotlin/**"
excludes += "**.txt"
excludes += "**.bin"
@@ -90,116 +90,80 @@ android {
buildFeatures {
buildConfig true
compose true
dataBinding true
viewBinding true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
kotlinCompilerExtensionVersion = "1.4.0"
}
namespace 'com.hippo.ehviewer'
}
dependencies {
def activity_version = "1.6.1"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
implementation "androidx.appcompat:appcompat:1.7.0-alpha01"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
implementation "androidx.arch.core:core-runtime:$arch_core_version"
// https://developer.android.com/jetpack/androidx/releases/activity
implementation "androidx.activity:activity-compose:1.6.1"
implementation "androidx.appcompat:appcompat:1.6.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha02"
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
implementation platform("androidx.compose:compose-bom:2022.12.00")
implementation "androidx.compose.animation:animation"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
// https://developer.android.com/jetpack/androidx/releases/compose-material3
implementation platform("androidx.compose:compose-bom:2023.01.00")
implementation "androidx.compose.material:material-icons-extended"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.runtime:runtime-saveable"
implementation "androidx.compose.ui:ui"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
// https://developer.android.com/jetpack/androidx/releases/lifecycle
implementation "androidx.lifecycle:lifecycle-process:2.5.1"
def core_version = "1.9.0"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.customview:customview:1.2.0-alpha02"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.6.0-alpha04"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha03"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.loader:loader:1.1.0"
// https://developer.android.com/jetpack/androidx/releases/navigation
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-compose:$nav_version"
implementation "androidx.paging:paging-runtime:3.2.0-alpha03"
implementation 'androidx.preference:preference:1.2.0'
// https://developer.android.com/jetpack/androidx/releases/paging
implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03"
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
def room_version = "2.5.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// https://developer.android.com/jetpack/androidx/releases/room
def room_version = "2.5.0"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation "androidx.transition:transition-ktx:1.4.1"
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-beta01'
implementation 'com.github.chrisbanes:PhotoView:2.3.0' // Dead Dependency
implementation 'com.github.tachiyomiorg:DirectionalViewPager:1.0.0' // Dead Dependency
// https://github.com/google/accompanist/releases
implementation "com.google.accompanist:accompanist-themeadapter-material3:0.28.0"
implementation 'com.google.android.material:material:1.8.0-rc01'
// https://square.github.io/okhttp/changelogs/changelog/
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:okhttp-coroutines'
implementation 'com.squareup.okio:okio-jvm:3.3.0'
implementation 'dev.chrisbanes.insetter:insetter:0.6.1' // Dead Dependency
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'io.coil-kt:coil:2.2.2'
// https://coil-kt.github.io/coil/changelog/
// implementation 'io.coil-kt:coil:2.2.2'
implementation 'com.github.Ehviewer-Overhauled.coil:coil:d65881d'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.3'
// Tachiyomi Reader
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
}
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
exclude group: 'androidx.compose.material'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-android-extensions-runtime'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,14 +15,8 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:tint="@color/brown_500"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="@string/pd_cookie" />
<resources>
</vector>
<string name="app_name" translatable="false">EhViewer Debug</string>
</resources>
+21 -38
View File
@@ -1,19 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ 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.
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
@@ -28,13 +13,12 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:name="com.hippo.ehviewer.EhApplication"
android:name=".EhApplication"
android:allowBackup="true"
android:appCategory="image"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -42,29 +26,33 @@
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:name=".ui.login.LoginActivity"
android:exported="false"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
@@ -85,26 +73,26 @@
<data android:host="g.e-hentai.org" />
<data android:host="lofi.e-hentai.org" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/image_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -138,21 +126,18 @@
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
<activity
android:name="com.hippo.ehviewer.ui.SettingsActivity"
android:name=".ui.SettingsActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.ui.SecurityActivity"
android:name=".ui.SecurityActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
android:name=".shortcuts.ShortcutsActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
@@ -166,9 +151,8 @@
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name="com.hippo.ehviewer.download.DownloadService"
android:name=".download.DownloadService"
android:foregroundServiceType="dataSync"
android:label="@string/download_service_label" />
@@ -177,11 +161,10 @@
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
</manifest>
+1 -1
View File
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c gifutils.c)
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} archive_static log -Wl,--gc-sections)
+28 -1
View File
@@ -1,4 +1,13 @@
#include <jni.h>
#include <sys/mman.h>
#include <android/log.h>
#include <string.h>
#include <errno.h>
#define LOG_TAG "mmap_utils"
#include "ehviewer.h"
JNIEXPORT jint JNICALL
Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
@@ -13,4 +22,22 @@ Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
}
return fd;
}
}
JNIEXPORT jobject JNICALL
Java_com_hippo_Native_mapFd(JNIEnv *env, jclass clazz, jint fd, jlong capability) {
void *addr = mmap(0, capability, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (addr != MAP_FAILED) {
return (*env)->NewDirectByteBuffer(env, addr, capability);
} else {
LOGE("%s%s", "mmap failed with error ", strerror(errno));
return NULL;
}
}
JNIEXPORT void JNICALL
Java_com_hippo_Native_unmapDirectByteBuffer(JNIEnv *env, jclass clazz, jobject buffer) {
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
if (munmap(addr, size)) LOGE("munmap addr:%p, size%zu failed!", addr, size);
}
+44
View File
@@ -0,0 +1,44 @@
#include <jni.h>
#include <string.h>
#include <stdbool.h>
#include <android/log.h>
#define LOG_TAG "gifUtils"
#include "ehviewer.h"
#define GIF_HEADER_87A "GIF87a"
#define GIF_HEADER_89A "GIF89a"
static int FRAME_DELAY_START_MARKER = 0x0021F904;
typedef signed char byte;
#define FRAME_DELAY_START_MARKER ((byte*)(&FRAME_DELAY_START_MARKER))
#define MINIMUM_FRAME_DELAY 2
#define DEFAULT_FRAME_DELAY 10
static inline bool isGif(void *addr) {
return !memcmp(addr, GIF_HEADER_87A, 6) || !memcmp(addr, GIF_HEADER_89A, 6);
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
if (size < 7 || !isGif(addr)) return;
for (size_t i = 0; i < size - 8; i++) {
// TODO: Optimize this hex find with SIMD?
if (addr[i] == FRAME_DELAY_START_MARKER[3] && addr[i + 1] == FRAME_DELAY_START_MARKER[2] &&
addr[i + 2] == FRAME_DELAY_START_MARKER[1] &&
addr[i + 3] == FRAME_DELAY_START_MARKER[0]) {
byte *end = addr + i + 4;
if (end[4] != 0) continue;
int frameDelay = end[2] << 8 | end[1];
if (frameDelay > MINIMUM_FRAME_DELAY)
break; // Quit if the first block looks normal, for performance
end[1] = DEFAULT_FRAME_DELAY;
end[2] = 0;
}
}
}
@@ -0,0 +1,49 @@
package androidx.navigation.ui
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.IdRes
import androidx.navigation.ActivityNavigator
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavOptions
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
if (navController.currentDestination?.matchDestination(item.itemId) == true) return false
val builder = NavOptions.Builder().setLaunchSingleTop(true)
if (
navController.currentDestination!!.parent!!.findNode(item.itemId)
is ActivityNavigator.Destination
) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
} else {
builder.setEnterAnim(com.hippo.ehviewer.R.anim.scene_open_enter)
.setExitAnim(com.hippo.ehviewer.R.anim.scene_open_exit)
.setPopEnterAnim(com.hippo.ehviewer.R.anim.scene_close_enter)
.setPopExitAnim(com.hippo.ehviewer.R.anim.scene_close_exit)
}
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
builder.setPopUpTo(
navController.graph.findStartDestination().id,
inclusive = false,
saveState = false
)
}
val options = builder.build()
return try {
// TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.itemId, null, options)
// Return true only if the destination we've navigated to matches the MenuItem
navController.currentDestination?.matchDestination(item.itemId) == true
} catch (e: IllegalArgumentException) {
false
}
}
fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
hierarchy.any { it.id == destId }
File diff suppressed because it is too large. Load diff
@@ -476,10 +476,8 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
private fun setGestureDetector(context: Context) {
detector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
@Suppress("SENSELESS_COMPARISON")
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
// The e1 can be null, that's weird and cause crash
if (panEnabled && isReady && vTranslate != null && e1 != null && e2 != null && (abs(e1.x - e2.x) > 50 || abs(e1.y - e2.y) > 50) && (abs(velocityX) > 500 || abs(velocityY) > 500) && !isZooming) {
if (panEnabled && isReady && vTranslate != null && (abs(e1.x - e2.x) > 50 || abs(e1.y - e2.y) > 50) && (abs(velocityX) > 500 || abs(velocityY) > 500) && !isZooming) {
val vTranslateEnd = PointF(vTranslate!!.x + velocityX * 0.25F, vTranslate!!.y + velocityY * 0.25F)
val sCenterXEnd = (width / 2 - vTranslateEnd.x) / scale
val sCenterYEnd = (height / 2 - vTranslateEnd.y) / scale
@@ -607,12 +605,16 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
return true
}
// Detect flings, taps and double taps
if (!isQuickScaling && detector?.onTouchEvent(event) != false) {
isZooming = false
isPanning = false
maxTouchCount = 0
return true
// May throw NPE
runCatching {
if (!isQuickScaling && detector?.onTouchEvent(event) != false) {
isZooming = false
isPanning = false
maxTouchCount = 0
return true
}
}
if (vTranslateStart == null) {
vTranslateStart = PointF(0F, 0F)
}
+8 -1
View File
@@ -18,6 +18,7 @@
package com.hippo
import java.io.FileDescriptor
import java.nio.ByteBuffer
object Native {
fun initialize() {
@@ -26,4 +27,10 @@ object Native {
@JvmStatic
external fun getFd(fd: FileDescriptor?): Int
}
@JvmStatic
external fun mapFd(fd: Int, capability: Long): ByteBuffer?
@JvmStatic
external fun unmapDirectByteBuffer(buffer: ByteBuffer)
}
@@ -19,6 +19,7 @@ package com.hippo
import android.content.Context
import android.net.Uri
import com.hippo.image.Image
import java.nio.ByteBuffer
class UriArchiveAccessor(ctx: Context, uri: Uri) {
@@ -28,7 +29,7 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
}
private external fun openArchive(fd: Int, size: Long): Int
external fun extractToByteBuffer(index: Int): ByteBuffer?
private external fun extractToByteBuffer(index: Int): ByteBuffer?
external fun extractToFd(index: Int, fd: Int)
external fun getFilename(index: Int): String
external fun needPassword(): Boolean
@@ -39,8 +40,23 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
pfd.close()
}
fun getImageSource(index: Int): Image.ByteBufferSource? {
val buffer = extractToByteBuffer(index)
buffer ?: return null
check(buffer.isDirect)
return object : Image.ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return buffer
}
override fun close() {
releaseByteBuffer(buffer)
}
}
}
companion object {
@JvmStatic
external fun releaseByteBuffer(buffer: ByteBuffer)
private external fun releaseByteBuffer(buffer: ByteBuffer)
}
}
@@ -1,985 +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 com.hippo.beerbelly;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Each key must match
* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
* accessible as streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public final class DiskLruCache implements Closeable {
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override
public void write(int b) {
// Eat all writes silently. Nom nom.
}
};
/**
* This cache uses a single background thread to evict entries.
*/
final ThreadPoolExecutor executorService =
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private final int valueCount;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<>(0, 0.75f, true);
private long maxSize;
private long size = 0;
private Writer journalWriter;
private int redundantOpCount;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
try (Writer writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII))) {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
}
}
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
/**
* Return {@code true} if it contains the key
*/
public synchronized boolean contain(String key) {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
return entry != null && entry.readable;
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
Util.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ').append(key).append(String.valueOf('\n'));
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/**
* Returns the directory where this cache stores its data.
*/
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ').append(key).append(String.valueOf('\n'));
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/**
* Returns true if this cache has been closed.
*/
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/**
* Force buffered operations to the filesystem.
*/
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/**
* Closes this cache. Stored values will remain on the filesystem.
*/
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException("keys must match regex "
+ STRING_KEY_PATTERN + ": \"" + key + "\"");
}
}
/**
* A snapshot of the values for an entry.
*/
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private final long[] lengths;
private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.ins = ins;
this.lengths = lengths;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/**
* Returns the unbuffered stream with the value for {@code index}.
*/
public InputStream getInputStream(int index) {
return ins[index];
}
/**
* Returns the string value for {@code index}.
*/
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
/**
* Returns the byte length of the value for {@code index}.
*/
public long getLength(int index) {
return lengths[index];
}
public void close() {
for (InputStream in : ins) {
Util.closeQuietly(in);
}
}
}
/**
* Edits the values for an entry.
*/
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
if (index < 0 || index >= valueCount) {
throw new IllegalArgumentException("Expected index " + index + " to "
+ "be greater than 0 and less than the maximum value count "
+ "of " + valueCount);
}
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
/**
* Sets the value at {@code index} to {@code value}.
*/
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
writer.write(value);
} finally {
Util.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/**
* Lengths of this entry's files.
*/
private final long[] lengths;
/**
* True if this entry has ever been published.
*/
private boolean readable;
/**
* The ongoing edit or null if this entry is not being edited.
*/
private Editor currentEditor;
/**
* The sequence number of the most recently committed edit to this entry.
*/
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/**
* Set lengths using decimal numbers like "10123".
*/
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "." + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "." + i + ".tmp");
}
}
}
@@ -1,504 +0,0 @@
/*
* Copyright 2015-2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.beerbelly;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
import com.hippo.util.HashCodeUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SimpleDiskCache {
private static final String TAG = SimpleDiskCache.class.getSimpleName();
/**
* Disk cache is free
*/
private static final int STATE_DISK_CACHE_NONE = 0;
/**
* Disk cache is flushing or clearing
*/
private static final int STATE_DISK_CACHE_IN_USE = 1;
/**
* Disk cache is reading or writing
*/
private static final int STATE_DISK_CACHE_BUSY = 2;
private final File mCacheDir;
private final int mSize;
@NonNull
private final Map<String, CounterLock> mLockMap = new HashMap<>();
@NonNull
private final LockPool mLockPool = new LockPool();
private int mDiskCacheState;
@Nullable
private DiskLruCache mDiskLruCache;
public SimpleDiskCache(File cacheDir, int size) {
mCacheDir = cacheDir;
mSize = size;
try {
init();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable
* for using as a disk filename.
*
* @param key The key used to store the file
*/
private static String hashKeyForDisk(final String key) {
String cacheKey;
try {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes());
cacheKey = HashCodeUtils.bytesToHexString(digest.digest());
} catch (final NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
public synchronized boolean isValid() {
return mDiskLruCache != null;
}
private synchronized void init() throws IOException {
if (!isValid()) {
mDiskLruCache = DiskLruCache.open(mCacheDir, 1, 1, mSize);
}
}
public synchronized long size() {
if (null != mDiskLruCache) {
return mDiskLruCache.size();
} else {
return -1L;
}
}
public synchronized void flush() {
// Wait for cache available
while (mDiskCacheState != STATE_DISK_CACHE_NONE) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mDiskCacheState = STATE_DISK_CACHE_BUSY;
if (null != mDiskLruCache) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
Log.e(TAG, "SimpleDiskCache flush", e);
}
}
mDiskCacheState = STATE_DISK_CACHE_NONE;
notifyAll();
}
public synchronized boolean clear() {
boolean result = true;
if (null == mDiskLruCache) {
return false;
}
// Wait for cache available
while (mDiskCacheState != STATE_DISK_CACHE_NONE) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mDiskCacheState = STATE_DISK_CACHE_BUSY;
try {
mDiskLruCache.delete();
} catch (IOException e) {
Log.e(TAG, "SimpleDiskCache clearCache", e);
}
mDiskLruCache = null;
try {
init();
} catch (IOException e) {
Log.e(TAG, "SimpleDiskCache init", e);
result = false;
}
mDiskCacheState = STATE_DISK_CACHE_NONE;
notifyAll();
return result;
}
private synchronized CounterLock obtainLock(String key) {
// Wait for clear over
while (mDiskCacheState == STATE_DISK_CACHE_BUSY) {
try {
wait();
} catch (InterruptedException e) {
// Empty
}
}
mDiskCacheState = STATE_DISK_CACHE_IN_USE;
// Get lock from key
CounterLock lock;
lock = mLockMap.get(key);
if (lock == null) {
lock = mLockPool.get();
mLockMap.put(key, lock);
}
lock.obtain();
return lock;
}
private synchronized void releaseLock(String key, CounterLock lock) {
lock.release();
if (lock.isFree()) {
mLockMap.remove(key);
mLockPool.push(lock);
}
if (mLockMap.isEmpty()) {
mDiskCacheState = STATE_DISK_CACHE_NONE;
notifyAll();
}
}
public boolean contain(@NonNull String key) {
String diskKey = hashKeyForDisk(key);
CounterLock lock = obtainLock(diskKey);
if (null == mDiskLruCache) {
releaseLock(diskKey, lock);
return false;
}
lock.readLock().lock();
boolean result = mDiskLruCache.contain(diskKey);
lock.readLock().unlock();
releaseLock(diskKey, lock);
return result;
}
public boolean remove(@NonNull String key) {
String diskKey = hashKeyForDisk(key);
CounterLock lock = obtainLock(diskKey);
if (null == mDiskLruCache) {
releaseLock(diskKey, lock);
return false;
}
lock.writeLock().lock();
boolean result;
try {
result = mDiskLruCache.remove(diskKey);
} catch (IOException e) {
e.printStackTrace();
result = false;
}
lock.writeLock().unlock();
releaseLock(diskKey, lock);
return result;
}
public boolean put(@NonNull String key, @NonNull InputStream is) {
String diskKey = hashKeyForDisk(key);
CounterLock lock = obtainLock(diskKey);
if (null == mDiskLruCache) {
releaseLock(diskKey, lock);
return false;
}
lock.writeLock().lock();
boolean result = false;
if (isValid()) {
result = putToDisk(diskKey, is);
}
lock.writeLock().unlock();
releaseLock(diskKey, lock);
return result;
}
private boolean putToDisk(String key, InputStream is) {
DiskLruCache.Editor editor = null;
OutputStream os = null;
boolean completeEdit = false;
try {
//noinspection ConstantConditions
editor = mDiskLruCache.edit(key);
if (editor == null) {
// The editor is in progress
return false;
}
os = editor.newOutputStream(0);
if (os != null) {
Util.copy(is, os);
completeEdit = true;
editor.commit();
return true;
} else {
// Can't get OutputStream
completeEdit = true;
editor.abort();
return false;
}
} catch (IOException e) {
Util.closeQuietly(os);
try {
if (!completeEdit && editor != null) {
editor.abort();
}
} catch (IOException ignored) {
}
return false;
}
}
/**
* @param key the key of the target
* @return the InputStreamPipe, <code>null</code> for missing
*/
@Nullable
public InputStreamPipe getInputStreamPipe(@NonNull String key) {
if (contain(key)) {
String diskKey = hashKeyForDisk(key);
return new CacheInputStreamPipe(diskKey);
} else {
return null;
}
}
@NonNull
public OutputStreamPipe getOutputStreamPipe(@NonNull String key) {
String diskKey = hashKeyForDisk(key);
return new CacheOutputStreamPipe(diskKey);
}
private class CacheInputStreamPipe implements InputStreamPipe {
private final String mKey;
private CounterLock mLock;
private DiskLruCache.Snapshot mCurrentSnapshot;
private CacheInputStreamPipe(String key) {
mKey = key;
}
@Override
public void obtain() {
if (mLock == null) {
mLock = obtainLock(mKey);
mLock.readLock().lock();
}
}
@Override
public void release() {
if (mCurrentSnapshot != null) {
throw new IllegalStateException("Please close it first");
}
if (mLock != null) {
mLock.readLock().unlock();
releaseLock(mKey, mLock);
mLock = null;
}
}
@Override
public @NonNull
InputStream open() throws IOException {
if (mLock == null) {
throw new IllegalStateException("Please obtain it first");
}
if (mCurrentSnapshot != null) {
throw new IllegalStateException("Please close it before reopen");
}
if (null == mDiskLruCache) {
throw new IOException("Can't find disk lru cache");
}
DiskLruCache.Snapshot snapshot;
snapshot = mDiskLruCache.get(mKey);
if (snapshot == null) {
throw new IOException("Miss the key " + mKey);
}
mCurrentSnapshot = snapshot;
return snapshot.getInputStream(0);
}
@Override
public void close() {
Util.closeQuietly(mCurrentSnapshot);
mCurrentSnapshot = null;
}
}
private class CacheOutputStreamPipe implements OutputStreamPipe {
private final String mKey;
private CounterLock mLock;
private DiskLruCache.Editor mCurrentEditor;
private CacheOutputStreamPipe(String key) {
mKey = key;
}
@Override
public void obtain() {
if (mLock == null) {
mLock = obtainLock(mKey);
mLock.writeLock().lock();
}
}
@Override
public void release() {
if (mCurrentEditor != null) {
throw new IllegalStateException("Please close it first");
}
if (mLock != null) {
mLock.writeLock().unlock();
releaseLock(mKey, mLock);
mLock = null;
}
}
@Override
public @NonNull
OutputStream open() throws IOException {
if (mLock == null) {
throw new IllegalStateException("Please obtain it first");
}
if (mCurrentEditor != null) {
throw new IllegalStateException("Please close it before reopen");
}
if (null == mDiskLruCache) {
throw new IOException("Can't find disk lru cache");
}
DiskLruCache.Editor editor = mDiskLruCache.edit(mKey);
if (editor == null) {
throw new IOException("Miss the key " + mKey);
}
OutputStream os;
try {
os = editor.newOutputStream(0);
mCurrentEditor = editor;
return os;
} catch (IOException e) {
// Get trouble
try {
editor.abort();
} catch (IOException ex) {
// Ignore
}
throw e;
}
}
@Override
public void close() {
if (mCurrentEditor != null) {
try {
mCurrentEditor.commit();
} catch (IOException e) {
// Empty
}
mCurrentEditor = null;
}
}
}
private class CounterLock extends ReentrantReadWriteLock {
private int mCount;
public boolean isFree() {
return mCount == 0;
}
public void release() {
if (--mCount < 0) {
throw new IllegalStateException("Release time is more than occupy time");
}
}
public void obtain() {
++mCount;
}
}
private class LockPool {
private static final int MAX_SIZE = 10;
private final CounterLock[] mArray = new CounterLock[MAX_SIZE];
private final int mMaxSize = MAX_SIZE;
private int mSize;
public void push(CounterLock lock) {
if (lock != null && mSize < mMaxSize) {
mArray[mSize++] = lock;
}
}
public CounterLock get() {
if (mSize > 0) {
CounterLock lock = mArray[--mSize];
mArray[mSize] = null;
return lock;
} else {
return new CounterLock();
}
}
}
}
@@ -1,193 +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 com.hippo.beerbelly;
import androidx.annotation.NonNull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* Buffers input from an {@link InputStream} for reading lines.
*
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
* line at end of input is invalid and will be ignored, the caller may use {@code
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
*
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
class StrictLineReader implements Closeable {
private static final byte CR = (byte) '\r';
private static final byte LF = (byte) '\n';
private final InputStream in;
private final Charset charset;
/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;
/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}
/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException("capacity <= 0");
}
if (!(charset.equals(Util.US_ASCII))) {
throw new IllegalArgumentException("Unsupported encoding");
}
this.in = in;
this.charset = charset;
buf = new byte[capacity];
}
/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset);
pos = i + 1;
return res;
}
}
// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@NonNull
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
return new String(buf, 0, length, charset);
}
};
while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}
public boolean hasUnterminatedLine() {
return end == -1;
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}
@@ -1,102 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.beerbelly;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Junk drawer of utility methods.
*/
final class Util {
static final Charset US_ASCII = StandardCharsets.US_ASCII;
static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private Util() {
}
static String readFully(Reader reader) throws IOException {
try (reader) {
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1) {
writer.write(buffer, 0, count);
}
return writer.toString();
}
}
/**
* Deletes the contents of {@code dir}. Throws an IOException if any file
* could not be deleted, or if {@code dir} is not a readable directory.
*/
static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IOException("not a readable directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
static void closeQuietly(/*Auto*/Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
/**
* Copy bytes from an <code>InputStream</code> to an
* <code>OutputStream</code>.
*
* @param input the InputStream
* @param output the OutputStream
* @return the number of bytes copied
*/
public static long copy(InputStream input, OutputStream output)
throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long count = 0;
int n;
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -25,7 +24,6 @@ import android.os.Debug;
import androidx.annotation.NonNull;
import com.hippo.scene.StageActivity;
import com.hippo.util.PackageUtils;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
@@ -78,27 +76,10 @@ public final class Crash {
// Runtime
String topActivityClazzName = "null";
String topSceneClazzName = "null";
try {
Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity();
if (null != topActivity) {
topActivityClazzName = topActivity.getClass().getName();
if (topActivity instanceof StageActivity) {
Class<?> clazz = ((StageActivity) topActivity).getTopSceneClass();
if (clazz != null) {
topSceneClazzName = clazz.getName();
}
}
}
} catch (Throwable e) {
// Ignore
}
fw.write("======== Runtime ========\r\n");
fw.write("TopActivity=");
fw.write(avoidNull(topActivityClazzName));
fw.write("\r\n");
fw.write("TopScene=");
fw.write(avoidNull(topSceneClazzName));
fw.write("\r\n");
fw.write("\r\n");
@@ -19,6 +19,7 @@ package com.hippo.ehviewer
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.content.ComponentCallbacks2
import android.text.Html
import android.text.method.LinkMovementMethod
@@ -35,10 +36,8 @@ import coil.ImageLoaderFactory
import coil.disk.DiskCache
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhDns
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhRequestBuilder
import com.hippo.ehviewer.client.EhSSLSocketFactory
import com.hippo.ehviewer.client.EhTagDatabase
@@ -46,11 +45,11 @@ import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhX509TrustManager
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.EventPaneParser
import com.hippo.ehviewer.dao.buildMainDB
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.scene.SceneApplication
import com.hippo.util.BitmapUtils
import com.hippo.util.ExceptionUtils
import com.hippo.util.ReadableTime
@@ -58,12 +57,10 @@ import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
@@ -74,7 +71,7 @@ import java.util.Arrays
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val mIdGenerator = IntIdGenerator()
private val mGlobalStuffMap = HashMap<Int, Any>()
private val mActivityList = ArrayList<Activity>()
@@ -104,50 +101,56 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
}
handler?.uncaughtException(t, e)
}
super<SceneApplication>.onCreate()
super<Application>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
EhDB.initialize(this)
EhEngine.initialize()
BitmapUtils.initialize(this)
EhTagDatabase.update()
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
GlobalScope.launch {
withContext(Dispatchers.IO) {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
launchIO {
launchIO {
ehDatabase
}
launchIO {
downloadManager
}
launchIO {
cleanupDownload()
}
launchIO {
theDawnOfNewDay()
}
}
GlobalScope.launch {
theDawnOfNewDay()
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
}
private suspend fun theDawnOfNewDay() {
private fun cleanupDownload() {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
}
private fun theDawnOfNewDay() {
if (!Settings.getRequestNews()) {
return
}
@@ -159,22 +162,20 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
EhCookieStore.KEY_IPD_PASS_HASH
)
) {
withContext(Dispatchers.IO) {
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
call.execute().use { response ->
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
}
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
call.execute().use { response ->
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
}
} catch (e: Throwable) {
e.printStackTrace()
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
@@ -280,10 +281,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
private set
@JvmStatic
val ehCookieStore by lazy { EhCookieStore(application) }
@JvmStatic
val ehClient by lazy { EhClient() }
val ehCookieStore by lazy { EhCookieStore }
@JvmStatic
val ehProxySelector by lazy { EhProxySelector() }
@@ -347,5 +345,8 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
@JvmStatic
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
@JvmStatic
val ehDatabase by lazy { buildMainDB(application) }
}
}
+35 -15
View File
@@ -58,7 +58,7 @@ import java.util.List;
public class EhDB {
private static final int CUR_DB_VER = 4;
private static EhDatabase db;
private static final EhDatabase db = EhApplication.getEhDatabase();
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
switch (oldVersion) {
@@ -99,10 +99,6 @@ public class EhDB {
}
}
public static void initialize(Context context) {
db = Room.databaseBuilder(context, EhDatabase.class, "eh.db").allowMainThreadQueries().build();
}
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
DownloadsDao dao = db.downloadsDao();
List<DownloadInfo> list = dao.list();
@@ -115,10 +111,34 @@ public class EhDB {
return list;
}
public static synchronized void moveDownloadInfo(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
boolean reverse = fromPosition > toPosition;
int offset = reverse ? toPosition : fromPosition;
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
DownloadsDao dao = db.downloadsDao();
List<DownloadInfo> list = dao.list(offset, limit);
int step = reverse ? 1 : -1;
int start = reverse ? limit - 1 : 0;
int end = reverse ? 0 : limit - 1;
long toTime = list.get(end).time;
for (int i = end; reverse ? i < start : i > start; i += step) {
list.get(i).time = list.get(i + step).time;
}
list.get(start).time = toTime;
dao.update(list);
}
// Insert or update
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
DownloadsDao dao = db.downloadsDao();
if (null != dao.load(downloadInfo.gid)) {
if (null != dao.load(downloadInfo.getGid())) {
// Update
dao.update(downloadInfo);
} else {
@@ -256,7 +276,7 @@ public class EhDB {
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
LocalFavoritesDao dao = db.localFavoritesDao();
if (null == dao.load(galleryInfo.gid)) {
if (null == dao.load(galleryInfo.getGid())) {
LocalFavoriteInfo info;
if (galleryInfo instanceof LocalFavoriteInfo) {
info = (LocalFavoriteInfo) galleryInfo;
@@ -281,9 +301,9 @@ public class EhDB {
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
quickSearch.id = null;
quickSearch.time = System.currentTimeMillis();
quickSearch.id = dao.insert(quickSearch);
quickSearch.setId(null);
quickSearch.setTime(System.currentTimeMillis());
quickSearch.setId(dao.insert(quickSearch));
}
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
@@ -335,7 +355,7 @@ public class EhDB {
info = new HistoryInfo(galleryInfo);
}
info.time = System.currentTimeMillis();
if (null != dao.load(info.gid)) {
if (null != dao.load(info.getGid())) {
dao.update(info);
} else {
dao.insert(info);
@@ -345,7 +365,7 @@ public class EhDB {
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
HistoryDao dao = db.historyDao();
for (HistoryInfo info : historyInfoList) {
if (null == dao.load(info.gid)) {
if (null == dao.load(info.getGid())) {
dao.insert(info);
}
}
@@ -386,7 +406,7 @@ public class EhDB {
}
public static synchronized void triggerFilter(Filter filter) {
filter.setEnable(!filter.enable);
filter.enable = !filter.enable;
db.filterDao().update(filter);
}
@@ -521,9 +541,9 @@ public class EhDB {
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
List<QuickSearch> importList = new ArrayList<>();
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
String name = quickSearch.getName();
for (QuickSearch q : currentQuickSearchList) {
if (ObjectUtils.equal(q.name, name)) {
if (ObjectUtils.equal(q.getName(), name)) {
// The same name
name = null;
break;
@@ -51,7 +51,7 @@ public class FavouriteStatusRouter {
for (Map<Long, GalleryInfo> map : maps.values()) {
GalleryInfo info = map.get(gid);
if (info != null) {
info.favoriteSlot = slot;
info.setFavoriteSlot(slot);
}
}
@@ -291,7 +291,7 @@ public class Hosts {
}
}
private boolean contains(String host) {
public boolean contains(String host) {
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host})) {
return cursor.moveToNext();
}
@@ -85,13 +85,12 @@ public class Settings {
public static final int DEFAULT_READ_CACHE_SIZE = 320;
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
public static final String KEY_BYPASS_VPN = "bypass_vpn";
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
private static final boolean DEFAULT_SHOW_COMMENTS = true;
private static final String KEY_PREVIEW_NUM = "preview_num";
private static final int DEFAULT_PREVIEW_NUM = 60;
private static final String TAG = Settings.class.getSimpleName();
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
@@ -181,6 +180,7 @@ public class Settings {
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
private static final boolean DEFAULT_FRONTING = false;
private static final boolean DEFAULT_BYPASS_VPN = true;
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
private static final String KEY_PROXY_IP = "proxy_ip";
@@ -423,10 +423,6 @@ public class Settings {
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
}
public static int getPreviewNum() {
return getInt(KEY_PREVIEW_NUM, DEFAULT_PREVIEW_NUM);
}
public static int getDefaultCategories() {
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
}
@@ -679,6 +675,10 @@ public class Settings {
putBoolean(KEY_DOMAIN_FRONTING, value);
}
public static boolean getBypassVPN() {
return getBoolean(KEY_BYPASS_VPN, DEFAULT_BYPASS_VPN);
}
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -26,12 +26,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
@@ -56,7 +52,7 @@ public final class UrlOpener {
if (galleryDetail != null) {
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
if (result != null) {
if (result.gid == galleryDetail.gid) {
if (result.gid == galleryDetail.getGid()) {
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
@@ -77,16 +73,6 @@ public final class UrlOpener {
}
}
}
Announcer announcer = EhUrlOpener.parseUrl(url);
if (null != announcer) {
intent = new Intent(context, MainActivity.class);
intent.setAction(StageActivity.ACTION_START_SCENE);
intent.putExtra(StageActivity.KEY_SCENE_NAME, announcer.getClazz().getName());
intent.putExtra(StageActivity.KEY_SCENE_ARGS, announcer.getArgs());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
}
CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder();
@@ -1,202 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.exception.CancelledException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class EhClient {
public static final String TAG = EhClient.class.getSimpleName();
public static final int METHOD_SIGN_IN = 0;
public static final int METHOD_GET_GALLERY_LIST = 1;
public static final int METHOD_GET_GALLERY_DETAIL = 3;
public static final int METHOD_GET_PREVIEW_SET = 4;
public static final int METHOD_GET_RATE_GALLERY = 5;
public static final int METHOD_GET_COMMENT_GALLERY = 6;
public static final int METHOD_GET_GALLERY_TOKEN = 7;
public static final int METHOD_GET_FAVORITES = 8;
public static final int METHOD_ADD_FAVORITES = 9;
public static final int METHOD_ADD_FAVORITES_RANGE = 10;
public static final int METHOD_MODIFY_FAVORITES = 11;
public static final int METHOD_GET_TORRENT_LIST = 12;
public static final int METHOD_GET_PROFILE = 14;
public static final int METHOD_VOTE_COMMENT = 15;
public static final int METHOD_IMAGE_SEARCH = 16;
public static final int METHOD_ARCHIVE_LIST = 17;
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
public static final int METHOD_VOTE_TAG = 19;
public static final int METHOD_GET_UCONFIG = 20;
private static final OkHttpClient mOkHttpClient = EhApplication.getOkHttpClient();
private final ThreadPoolExecutor mRequestThreadPool;
public EhClient() {
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
}
@SuppressWarnings("unchecked")
public void execute(EhRequest request) {
if (!request.isCancelled()) {
Task task = new Task(request.getMethod(), request.getCallback());
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
request.task = task;
} else {
request.getCallback().onCancel();
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
@SuppressWarnings("deprecation")
public static class Task extends AsyncTask<Object, Void, Object> {
private final int mMethod;
private final AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback<Object> mCallback;
public Task(int method, Callback<Object> callback) {
mMethod = method;
mCallback = callback;
}
// Called in Job thread
public void setCall(Call call) throws CancelledException {
if (mStop.get()) {
// Stopped Job thread
throw new CancelledException();
} else {
mCall.lazySet(call);
}
}
public void stop() {
if (!mStop.get()) {
mStop.lazySet(true);
if (mCallback != null) {
// TODO Avoid new runnable
SimpleHandler.getInstance().post(mCallback::onCancel);
}
Status status = getStatus();
if (status == Status.PENDING) {
cancel(false);
} else if (status == Status.RUNNING) {
// It is running, cancel call if it is created
Call call = mCall.get();
if (call != null) {
call.cancel();
}
}
// Clear
mCallback = null;
mCall.lazySet(null);
}
}
@Override
protected Object doInBackground(Object... params) {
try {
return switch (mMethod) {
case METHOD_SIGN_IN ->
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST ->
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL ->
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET ->
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY ->
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY ->
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN ->
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES ->
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
case METHOD_ADD_FAVORITES ->
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE ->
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES ->
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
case METHOD_GET_TORRENT_LIST ->
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT ->
EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
case METHOD_IMAGE_SEARCH ->
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST ->
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE ->
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
case METHOD_VOTE_TAG ->
EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
default -> new IllegalStateException("Can't detect method " + mMethod);
};
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return e;
}
}
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
//noinspection StatementWithEmptyBody
if (!(result instanceof CancelledException)) {
if (result instanceof Exception) {
mCallback.onFailure((Exception) result);
} else {
mCallback.onSuccess(result);
}
} else {
// onCancel is called in stop
}
}
// Clear
mCallback = null;
mCall.lazySet(null);
}
}
}
@@ -0,0 +1,184 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import java.io.File
object EhClient {
internal fun enqueue(request: EhRequest, scope: CoroutineScope) {
check(!request.isActive) // Abort if attempt to execute an active request
request.job = scope.launchIO {
val callback: Callback<Any?>? = request.callback
try {
val result: Any? = request.run {
if (args == null)
execute(method)
else
execute(method, *args!!)
}
withUIContext { callback?.onSuccess(result) }
} catch (e: Exception) {
if (e is CancellationException)
throw e // Don't catch coroutine CancellationException
e.printStackTrace()
withUIContext { callback?.onFailure(e) }
}
}
}
suspend fun execute(method: Int, vararg params: Any?): Any? {
return when (method) {
METHOD_SIGN_IN -> EhEngine.signIn(
params[0] as String,
params[1] as String
)
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
params[0] as String
)
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
params[0] as String?
)
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
params[0] as String?
)
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Float
)
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
params[0] as String?,
params[1] as String,
params[2] as String?
)
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
params[0] as Long,
params[1] as String?,
params[2] as Int
)
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
params[0] as String
)
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
params[0] as Long,
params[1] as String?,
params[2] as Int,
params[3] as String?
)
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
params[0] as LongArray,
params[1] as Array<String?>,
params[2] as Int
)
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
params[0] as String,
params[1] as LongArray,
params[2] as Int
)
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
params[0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_GET_PROFILE -> EhEngine.getProfile()
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Long,
params[5] as Int
)
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
params[0] as File,
params[1] as Boolean,
params[2] as Boolean,
params[3] as Boolean
)
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
params[0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
params[0] as Long,
params[1] as String?,
params[2] as String?,
params[3] as String?,
params[4] as Boolean
)
METHOD_VOTE_TAG -> EhEngine.voteTag(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as String?,
params[5] as Int
)
METHOD_GET_UCONFIG -> EhEngine.getUConfig()
else -> throw IllegalStateException("Can't detect method $method")
}
}
interface Callback<E> {
fun onSuccess(result: E)
fun onFailure(e: Exception)
fun onCancel()
}
const val METHOD_SIGN_IN = 0
const val METHOD_GET_GALLERY_LIST = 1
const val METHOD_GET_GALLERY_DETAIL = 3
const val METHOD_GET_PREVIEW_SET = 4
const val METHOD_GET_RATE_GALLERY = 5
const val METHOD_GET_COMMENT_GALLERY = 6
const val METHOD_GET_GALLERY_TOKEN = 7
const val METHOD_GET_FAVORITES = 8
const val METHOD_ADD_FAVORITES = 9
const val METHOD_ADD_FAVORITES_RANGE = 10
const val METHOD_MODIFY_FAVORITES = 11
const val METHOD_GET_TORRENT_LIST = 12
const val METHOD_GET_PROFILE = 14
const val METHOD_VOTE_COMMENT = 15
const val METHOD_IMAGE_SEARCH = 16
const val METHOD_ARCHIVE_LIST = 17
const val METHOD_DOWNLOAD_ARCHIVE = 18
const val METHOD_VOTE_TAG = 19
const val METHOD_GET_UCONFIG = 20
}
@@ -1,116 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.content.Context;
import androidx.annotation.NonNull;
import com.hippo.network.CookieRepository;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
public class EhCookieStore extends CookieRepository {
public static final String KEY_IPD_MEMBER_ID = "ipb_member_id";
public static final String KEY_IPD_PASS_HASH = "ipb_pass_hash";
public static final String KEY_IGNEOUS = "igneous";
public static final Cookie sTipsCookie =
new Cookie.Builder()
.name(EhConfig.KEY_CONTENT_WARNING)
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
.domain(EhUrl.DOMAIN_E)
.path("/")
.expiresAt(Long.MAX_VALUE)
.build();
public EhCookieStore(Context context) {
super(context, "okhttp3-cookie.db");
}
public static Cookie newCookie(Cookie cookie, String newDomain, boolean forcePersistent,
boolean forceLongLive, boolean forceNotHostOnly) {
Cookie.Builder builder = new Cookie.Builder();
builder.name(cookie.name());
builder.value(cookie.value());
if (forceLongLive) {
builder.expiresAt(Long.MAX_VALUE);
} else if (cookie.persistent()) {
builder.expiresAt(cookie.expiresAt());
} else if (forcePersistent) {
builder.expiresAt(Long.MAX_VALUE);
}
if (cookie.hostOnly() && !forceNotHostOnly) {
builder.hostOnlyDomain(newDomain);
} else {
builder.domain(newDomain);
}
builder.path(cookie.path());
if (cookie.secure()) {
builder.secure();
}
if (cookie.httpOnly()) {
builder.httpOnly();
}
return builder.build();
}
public void signOut() {
clear();
}
public boolean hasSignedIn() {
HttpUrl url = HttpUrl.parse(EhUrl.HOST_E);
return contains(url, KEY_IPD_MEMBER_ID) &&
contains(url, KEY_IPD_PASS_HASH);
}
@NonNull
@Override
public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
List<Cookie> cookies = super.loadForRequest(url);
boolean checkTips = domainMatch(url, EhUrl.DOMAIN_E);
if (checkTips) {
List<Cookie> result = new ArrayList<>(cookies.size() + 1);
// Add all but skip some
for (Cookie cookie : cookies) {
String name = cookie.name();
if (EhConfig.KEY_CONTENT_WARNING.equals(name)) {
continue;
}
if (EhConfig.KEY_UCONFIG.equals(name)) {
continue;
}
result.add(cookie);
}
// Add some
result.add(sTipsCookie);
return Collections.unmodifiableList(result);
} else {
return cookies;
}
}
}
@@ -0,0 +1,247 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import com.hippo.ehviewer.EhApplication
import com.hippo.network.CookieDatabase
import com.hippo.network.CookieSet
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.Collections
import java.util.regex.Pattern
object EhCookieStore : CookieJar {
private val db: CookieDatabase = CookieDatabase(EhApplication.application, "okhttp3-cookie.db")
private val map: MutableMap<String, CookieSet> = db.allCookies
fun signOut() {
clear()
}
fun hasSignedIn(): Boolean {
val url = EhUrl.HOST_E.toHttpUrl()
return contains(url, KEY_IPD_MEMBER_ID) &&
contains(url, KEY_IPD_PASS_HASH)
}
const val KEY_IPD_MEMBER_ID = "ipb_member_id"
const val KEY_IPD_PASS_HASH = "ipb_pass_hash"
const val KEY_IGNEOUS = "igneous"
private val sTipsCookie: Cookie = Cookie.Builder()
.name(EhConfig.KEY_CONTENT_WARNING)
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
.domain(EhUrl.DOMAIN_E)
.path("/")
.expiresAt(Long.MAX_VALUE)
.build()
fun newCookie(
cookie: Cookie, newDomain: String, forcePersistent: Boolean,
forceLongLive: Boolean, forceNotHostOnly: Boolean
): Cookie {
val builder = Cookie.Builder()
builder.name(cookie.name)
builder.value(cookie.value)
if (forceLongLive) {
builder.expiresAt(Long.MAX_VALUE)
} else if (cookie.persistent) {
builder.expiresAt(cookie.expiresAt)
} else if (forcePersistent) {
builder.expiresAt(Long.MAX_VALUE)
}
if (cookie.hostOnly && !forceNotHostOnly) {
builder.hostOnlyDomain(newDomain)
} else {
builder.domain(newDomain)
}
builder.path(cookie.path)
if (cookie.secure) {
builder.secure()
}
if (cookie.httpOnly) {
builder.httpOnly()
}
return builder.build()
}
@Synchronized
fun addCookie(cookie: Cookie) {
// For cookie database
var toAdd: Cookie? = null
var toUpdate: Cookie? = null
var toRemove: Cookie? = null
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
if (cookie.expiresAt <= System.currentTimeMillis()) {
toRemove = set.remove(cookie)
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent) {
toRemove = null
}
} else {
toAdd = cookie
toUpdate = set.add(cookie)
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent) toAdd = null
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate
toUpdate = null
}
}
if (toRemove != null) {
db.remove(toRemove)
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd)
} else {
db.add(toAdd)
}
}
}
fun getCookieHeader(url: HttpUrl): String {
val cookies = getCookies(url)
val cookieHeader = StringBuilder()
var i = 0
val size = cookies.size
while (i < size) {
if (i > 0) {
cookieHeader.append("; ")
}
val cookie = cookies[i]
cookieHeader.append(cookie.name).append('=').append(cookie.value)
i++
}
return cookieHeader.toString()
}
@Synchronized
fun getCookies(url: HttpUrl): List<Cookie> {
val accepted: MutableList<Cookie> = ArrayList()
val expired: MutableList<Cookie> = ArrayList()
for ((domain, cookieSet) in map) {
if (domainMatch(url, domain)) {
cookieSet[url, accepted, expired]
}
}
for (cookie in expired) {
if (cookie.persistent) {
db.remove(cookie)
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
return accepted
}
fun contains(url: HttpUrl, name: String?): Boolean {
for (cookie in getCookies(url)) {
if (cookie.name == name) {
return true
}
}
return false
}
/**
* Remove all cookies in this `CookieRepository`.
*/
@Synchronized
fun clear() {
map.clear()
db.clear()
}
@Synchronized
fun close() {
db.close()
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
for (cookie in cookies) {
addCookie(cookie)
}
}
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
*
*
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private val VERIFY_AS_IP_ADDRESS =
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private fun verifyAsIpAddress(host: String): Boolean {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
@JvmStatic
fun domainMatch(url: HttpUrl, domain: String?): Boolean {
val urlHost = url.host
return if (urlHost == domain) {
true // As in 'example.com' matching 'example.com'.
} else urlHost.endsWith(domain!!)
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
urlHost
)
// As in 'example.com' matching 'www.example.com'.
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val cookies = getCookies(url)
val checkTips = domainMatch(url, EhUrl.DOMAIN_E)
return if (checkTips) {
val result: MutableList<Cookie> = ArrayList(cookies.size + 1)
// Add all but skip some
for (cookie in cookies) {
val name = cookie.name
if (EhConfig.KEY_CONTENT_WARNING == name) {
continue
}
if (EhConfig.KEY_UCONFIG == name) {
continue
}
result.add(cookie)
}
// Add some
result.add(sTipsCookie)
Collections.unmodifiableList(result)
} else {
cookies
}
}
}
@@ -123,4 +123,8 @@ object EhDns : Dns {
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.getBuiltInHosts() }
?: Dns.SYSTEM.lookup(hostname)
}
}
fun isInHosts(hostname: String): Boolean {
return hosts.contains(hostname) || (builtInHosts.contains(hostname) && Settings.getBuiltInHosts())
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,313 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.util.Log;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class EhFilter {
public static final int MODE_TITLE = 0;
public static final int MODE_UPLOADER = 1;
public static final int MODE_TAG = 2;
public static final int MODE_TAG_NAMESPACE = 3;
public static final int MODE_COMMENTER = 4;
public static final int MODE_COMMENT = 5;
private static final String TAG = EhFilter.class.getSimpleName();
private static EhFilter sInstance;
private final List<Filter> mTitleFilterList = new ArrayList<>();
private final List<Filter> mUploaderFilterList = new ArrayList<>();
private final List<Filter> mTagFilterList = new ArrayList<>();
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
private final List<Filter> mCommenterFilterList = new ArrayList<>();
private final List<Filter> mCommentFilterList = new ArrayList<>();
private EhFilter() {
List<Filter> list = EhDB.getAllFilter();
for (int i = 0, n = list.size(); i < n; i++) {
Filter filter = list.get(i);
switch (filter.mode) {
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
}
public static EhFilter getInstance() {
if (sInstance == null) {
sInstance = new EhFilter();
}
return sInstance;
}
public List<Filter> getTitleFilterList() {
return mTitleFilterList;
}
public List<Filter> getUploaderFilterList() {
return mUploaderFilterList;
}
public List<Filter> getTagFilterList() {
return mTagFilterList;
}
public List<Filter> getTagNamespaceFilterList() {
return mTagNamespaceFilterList;
}
public List<Filter> getCommenterFilterList() {
return mCommenterFilterList;
}
public List<Filter> getCommentFilterList() {
return mCommentFilterList;
}
public synchronized boolean addFilter(Filter filter) {
// enable filter by default before it is added to database
filter.enable = true;
if (!EhDB.addFilter(filter)) return false;
switch (filter.mode) {
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
return true;
}
public synchronized void triggerFilter(Filter filter) {
EhDB.triggerFilter(filter);
}
public synchronized void deleteFilter(Filter filter) {
EhDB.deleteFilter(filter);
switch (filter.mode) {
case MODE_TITLE -> mTitleFilterList.remove(filter);
case MODE_TAG -> mTagFilterList.remove(filter);
case MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter);
case MODE_UPLOADER -> mUploaderFilterList.remove(filter);
case MODE_COMMENTER -> mCommenterFilterList.remove(filter);
case MODE_COMMENT -> mCommentFilterList.remove(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
public synchronized boolean needTags() {
return 0 != mTagFilterList.size() || 0 != mTagNamespaceFilterList.size();
}
public synchronized boolean filterTitle(GalleryInfo info) {
if (null == info) {
return false;
}
// Title
String title = info.title;
List<Filter> filters = mTitleFilterList;
if (null != title && filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && title.toLowerCase().contains(filters.get(i).text)) {
return false;
}
}
}
return true;
}
public synchronized boolean filterUploader(GalleryInfo info) {
if (null == info) {
return false;
}
// Uploader
String uploader = info.uploader;
List<Filter> filters = mUploaderFilterList;
if (null != uploader && filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && uploader.equals(filters.get(i).text)) {
return false;
}
}
}
return true;
}
private boolean matchTag(String tag, String filter) {
if (null == tag || null == filter) {
return false;
}
String tagNamespace;
String tagName;
String filterNamespace;
String filterName;
int index = tag.indexOf(':');
if (index < 0) {
tagNamespace = null;
tagName = tag;
} else {
tagNamespace = tag.substring(0, index);
tagName = tag.substring(index + 1);
}
index = filter.indexOf(':');
if (index < 0) {
filterNamespace = null;
filterName = filter;
} else {
filterNamespace = filter.substring(0, index);
filterName = filter.substring(index + 1);
}
if (null != tagNamespace && null != filterNamespace &&
!tagNamespace.equals(filterNamespace)) {
return false;
}
return tagName.equals(filterName);
}
public synchronized boolean filterTag(GalleryInfo info) {
if (null == info) {
return false;
}
// Tag
String[] tags = info.simpleTags;
List<Filter> filters = mTagFilterList;
if (null != tags && filters.size() > 0) {
for (String tag : tags) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && matchTag(tag, filters.get(i).text)) {
return false;
}
}
}
}
return true;
}
private boolean matchTagNamespace(String tag, String filter) {
if (null == tag || null == filter) {
return false;
}
String tagNamespace;
int index = tag.indexOf(':');
if (index >= 0) {
tagNamespace = tag.substring(0, index);
return tagNamespace.equals(filter);
} else {
return false;
}
}
public synchronized boolean filterTagNamespace(GalleryInfo info) {
if (null == info) {
return false;
}
String[] tags = info.simpleTags;
List<Filter> filters = mTagNamespaceFilterList;
if (null != tags && filters.size() > 0) {
for (String tag : tags) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && matchTagNamespace(tag, filters.get(i).text)) {
return false;
}
}
}
}
return true;
}
public synchronized boolean filterCommenter(String commenter) {
if (null == commenter) {
return false;
}
List<Filter> filters = mCommenterFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && commenter.equals(filters.get(i).text)) {
return false;
}
}
}
return true;
}
public synchronized boolean filterComment(String comment) {
if (null == comment) {
return false;
}
List<Filter> filters = mCommentFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable) {
Pattern p = Pattern.compile(filters.get(i).text);
Matcher m = p.matcher(comment);
if (m.find()) return false;
}
}
}
return true;
}
}
@@ -0,0 +1,315 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import android.util.Log
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.Filter
import java.util.Locale
import java.util.regex.Pattern
object EhFilter {
private val mTitleFilterList: MutableList<Filter> = ArrayList()
private val mUploaderFilterList: MutableList<Filter> = ArrayList()
private val mTagFilterList: MutableList<Filter> = ArrayList()
private val mTagNamespaceFilterList: MutableList<Filter> = ArrayList()
private val mCommenterFilterList: MutableList<Filter> = ArrayList()
private val mCommentFilterList: MutableList<Filter> = ArrayList()
const val MODE_TITLE = 0
const val MODE_UPLOADER = 1
const val MODE_TAG = 2
const val MODE_TAG_NAMESPACE = 3
const val MODE_COMMENTER = 4
const val MODE_COMMENT = 5
private val TAG = EhFilter::class.java.simpleName
init {
val list = EhDB.getAllFilter()
var i = 0
val n = list.size
while (i < n) {
val filter = list[i]
when (filter.mode) {
MODE_TITLE -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTitleFilterList.add(filter)
}
MODE_TAG -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTagFilterList.add(filter)
}
MODE_TAG_NAMESPACE -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTagNamespaceFilterList.add(filter)
}
MODE_UPLOADER -> mUploaderFilterList.add(filter)
MODE_COMMENTER -> mCommenterFilterList.add(filter)
MODE_COMMENT -> mCommentFilterList.add(filter)
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
}
i++
}
}
val titleFilterList: List<Filter>
get() = mTitleFilterList
val uploaderFilterList: List<Filter>
get() = mUploaderFilterList
val tagFilterList: List<Filter>
get() = mTagFilterList
val tagNamespaceFilterList: List<Filter>
get() = mTagNamespaceFilterList
val commenterFilterList: List<Filter>
get() = mCommenterFilterList
val commentFilterList: List<Filter>
get() = mCommentFilterList
@Synchronized
fun addFilter(filter: Filter): Boolean {
// enable filter by default before it is added to database
filter.enable = true
if (!EhDB.addFilter(filter)) return false
when (filter.mode) {
MODE_TITLE -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTitleFilterList.add(filter)
}
MODE_TAG -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTagFilterList.add(filter)
}
MODE_TAG_NAMESPACE -> {
filter.text = filter.text!!.lowercase(Locale.getDefault())
mTagNamespaceFilterList.add(filter)
}
MODE_UPLOADER -> mUploaderFilterList.add(filter)
MODE_COMMENTER -> mCommenterFilterList.add(filter)
MODE_COMMENT -> mCommentFilterList.add(filter)
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
}
return true
}
@Synchronized
fun triggerFilter(filter: Filter?) {
EhDB.triggerFilter(filter)
}
@Synchronized
fun deleteFilter(filter: Filter) {
EhDB.deleteFilter(filter)
when (filter.mode) {
MODE_TITLE -> mTitleFilterList.remove(filter)
MODE_TAG -> mTagFilterList.remove(filter)
MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter)
MODE_UPLOADER -> mUploaderFilterList.remove(filter)
MODE_COMMENTER -> mCommenterFilterList.remove(filter)
MODE_COMMENT -> mCommentFilterList.remove(filter)
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
}
}
@Synchronized
fun needTags(): Boolean {
return 0 != mTagFilterList.size || 0 != mTagNamespaceFilterList.size
}
@Synchronized
fun filterTitle(info: GalleryInfo?): Boolean {
if (null == info) {
return false
}
// Title
val title = info.title
val filters: List<Filter> = mTitleFilterList
if (null != title && filters.isNotEmpty()) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!! && title.lowercase(Locale.getDefault()).contains(
filters[i].text!!
)
) {
return false
}
i++
}
}
return true
}
@Synchronized
fun filterUploader(info: GalleryInfo?): Boolean {
if (null == info) {
return false
}
// Uploader
val uploader = info.uploader
val filters: List<Filter> = mUploaderFilterList
if (null != uploader && filters.isNotEmpty()) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!! && uploader == filters[i].text) {
return false
}
i++
}
}
return true
}
private fun matchTag(tag: String?, filter: String?): Boolean {
if (null == tag || null == filter) {
return false
}
val tagNamespace: String?
val tagName: String
val filterNamespace: String?
val filterName: String
var index = tag.indexOf(':')
if (index < 0) {
tagNamespace = null
tagName = tag
} else {
tagNamespace = tag.substring(0, index)
tagName = tag.substring(index + 1)
}
index = filter.indexOf(':')
if (index < 0) {
filterNamespace = null
filterName = filter
} else {
filterNamespace = filter.substring(0, index)
filterName = filter.substring(index + 1)
}
return if (null != tagNamespace && null != filterNamespace &&
tagNamespace != filterNamespace
) {
false
} else tagName == filterName
}
@Synchronized
fun filterTag(info: GalleryInfo?): Boolean {
if (null == info) {
return false
}
// Tag
val tags = info.simpleTags
val filters: List<Filter> = mTagFilterList
if (null != tags && filters.isNotEmpty()) {
for (tag in tags) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!! && matchTag(tag, filters[i].text)) {
return false
}
i++
}
}
}
return true
}
private fun matchTagNamespace(tag: String?, filter: String?): Boolean {
if (null == tag || null == filter) {
return false
}
val tagNamespace: String
val index = tag.indexOf(':')
return if (index >= 0) {
tagNamespace = tag.substring(0, index)
tagNamespace == filter
} else {
false
}
}
@Synchronized
fun filterTagNamespace(info: GalleryInfo?): Boolean {
if (null == info) {
return false
}
val tags = info.simpleTags
val filters: List<Filter> = mTagNamespaceFilterList
if (null != tags && filters.isNotEmpty()) {
for (tag in tags) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!! && matchTagNamespace(tag, filters[i].text)) {
return false
}
i++
}
}
}
return true
}
@Synchronized
fun filterCommenter(commenter: String?): Boolean {
if (null == commenter) {
return false
}
val filters: List<Filter> = mCommenterFilterList
if (filters.isNotEmpty()) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!! && commenter == filters[i].text) {
return false
}
i++
}
}
return true
}
@Synchronized
fun filterComment(comment: String?): Boolean {
if (null == comment) {
return false
}
val filters: List<Filter> = mCommentFilterList
if (filters.isNotEmpty()) {
var i = 0
val n = filters.size
while (i < n) {
if (filters[i].enable!!) {
val p = Pattern.compile(filters[i].text!!)
val m = p.matcher(comment)
if (m.find()) return false
}
i++
}
}
return true
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import com.hippo.ehviewer.Settings;
public class EhRequest {
EhClient.Task task;
private int mMethod;
private Object[] mArgs;
private EhClient.Callback mCallback;
private EhConfig mEhConfig;
private boolean mCancel = false;
public int getMethod() {
return mMethod;
}
public EhRequest setMethod(int method) {
mMethod = method;
return this;
}
public Object[] getArgs() {
return mArgs;
}
public EhRequest setArgs(Object... args) {
mArgs = args;
return this;
}
public EhClient.Callback getCallback() {
return mCallback;
}
public EhRequest setCallback(EhClient.Callback callback) {
mCallback = callback;
return this;
}
public EhConfig getEhConfig() {
return mEhConfig != null ? mEhConfig : Settings.getEhConfig();
}
public EhRequest setEhConfig(EhConfig ehConfig) {
mEhConfig = ehConfig;
return this;
}
public void cancel() {
if (!mCancel) {
mCancel = true;
if (task != null) {
task.stop();
task = null;
}
}
}
public boolean isCancelled() {
return mCancel;
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import android.app.Activity
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
class EhRequest {
internal var job: Job? = null
val isActive
get() = job?.isActive ?: false
var method = 0
private set
var args: Array<out Any?>? = null
private set
var callback: EhClient.Callback<Any?>? = null
private set
fun setMethod(method: Int): EhRequest {
this.method = method
return this
}
fun setArgs(vararg args: Any?): EhRequest {
this.args = args
return this
}
@Suppress("UNCHECKED_CAST")
fun setCallback(callback: EhClient.Callback<*>?): EhRequest {
this.callback = callback as EhClient.Callback<Any?>?
return this
}
fun enqueue(scope: CoroutineScope) {
EhClient.enqueue(this, scope)
}
@DelicateCoroutinesApi
fun enqueue() {
EhClient.enqueue(this, GlobalScope)
}
fun enqueue(fragment: Fragment) {
enqueue(fragment.viewLifecycleOwner.lifecycleScope)
}
fun enqueue(activity: Activity) {
check(activity is FragmentActivity)
enqueue(activity.lifecycleScope)
}
@MainThread
fun cancel() {
if (isActive) {
job?.cancel()
callback?.onCancel()
}
}
}
@@ -34,8 +34,8 @@ object EhSSLSocketFactory : SSLSocketFactory() {
}
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
val address = s.inetAddress.hostAddress
val socket = sslSocketFactory.createSocket(s, address, port, autoClose) as SSLSocket
val address = s.inetAddress.hostAddress.takeIf { EhDns.isInHosts(host) }
val socket = sslSocketFactory.createSocket(s, address ?: host, port, autoClose) as SSLSocket
val sslSession = socket.session
Log.d(
"EhSSLSocketFactory",
@@ -24,12 +24,10 @@ import com.hippo.util.ExceptionUtils
import com.hippo.util.HashCodeUtils
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IOUtils
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSource
@@ -193,7 +191,7 @@ object EhTagDatabase {
@OptIn(DelicateCoroutinesApi::class)
@JvmStatic
fun update() {
GlobalScope.launch(Dispatchers.IO) {
launchIO {
updateInternal()
}
}
@@ -1,196 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.Settings;
import com.hippo.network.UrlBuilder;
public class EhUrl {
public static final int SITE_E = 0;
public static final int SITE_EX = 1;
public static final String DOMAIN_EX = "exhentai.org";
public static final String DOMAIN_E = "e-hentai.org";
public static final String DOMAIN_LOFI = "lofi.e-hentai.org";
public static final String HOST_EX = "https://" + DOMAIN_EX + "/";
public static final String API_EX = HOST_EX + "api.php";
public static final String URL_FAVORITES_EX = HOST_EX + "favorites.php";
public static final String URL_UCONFIG_EX = HOST_EX + "uconfig.php";
public static final String URL_MY_TAGS_EX = HOST_EX + "mytags";
public static final String URL_WATCHED_EX = HOST_EX + "watched";
public static final String HOST_E = "https://" + DOMAIN_E + "/";
public static final String API_E = HOST_E + "api.php";
public static final String URL_FAVORITES_E = HOST_E + "favorites.php";
public static final String URL_UCONFIG_E = HOST_E + "uconfig.php";
public static final String URL_MY_TAGS_E = HOST_E + "mytags";
public static final String URL_WATCHED_E = HOST_E + "watched";
public static final String API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01";
public static final String URL_POPULAR_E = "https://e-hentai.org/popular";
public static final String URL_POPULAR_EX = "https://exhentai.org/popular";
public static final String URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php";
public static final String URL_IMAGE_SEARCH_EX = "https://exhentai.org/upload/image_lookup.php";
public static final String URL_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login";
public static final String URL_REGISTER = "https://forums.e-hentai.org/index.php?act=Reg&CODE=00";
public static final String URL_FORUMS = "https://forums.e-hentai.org/";
public static final String REFERER_EX = "https://" + DOMAIN_EX;
public static final String ORIGIN_EX = REFERER_EX;
public static final String REFERER_E = "https://" + DOMAIN_E;
public static final String ORIGIN_E = REFERER_E;
public static String getGalleryDetailUrl(long gid, String token) {
return getGalleryDetailUrl(gid, token, 0, false);
}
public static String getHost() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return HOST_E;
case SITE_EX:
return HOST_EX;
}
}
public static String getFavoritesUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_FAVORITES_E;
case SITE_EX:
return URL_FAVORITES_EX;
}
}
public static String getApiUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return API_E;
case SITE_EX:
return API_EX;
}
}
public static String getReferer() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return REFERER_E;
case SITE_EX:
return REFERER_EX;
}
}
public static String getOrigin() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return ORIGIN_E;
case SITE_EX:
return ORIGIN_EX;
}
}
public static String getUConfigUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_UCONFIG_E;
case SITE_EX:
return URL_UCONFIG_EX;
}
}
public static String getMyTagsUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_MY_TAGS_E;
case SITE_EX:
return URL_MY_TAGS_EX;
}
}
public static String getGalleryDetailUrl(long gid, String token, int index, boolean allComment) {
UrlBuilder builder = new UrlBuilder(getHost() + "g/" + gid + '/' + token + '/');
if (index != 0) {
builder.addQuery("p", index);
}
if (allComment) {
builder.addQuery("hc", 1);
}
return builder.build();
}
public static String getGalleryMultiPageViewerUrl(long gid, String token) {
UrlBuilder builder = new UrlBuilder(getHost() + "mpv/" + gid + '/' + token + '/');
return builder.build();
}
public static String getPageUrl(long gid, int index, String pToken) {
return getHost() + "s/" + pToken + '/' + gid + '-' + (index + 1);
}
public static String getAddFavorites(long gid, String token) {
return getHost() + "gallerypopups.php?gid=" + gid + "&t=" + token + "&act=addfav";
}
public static String getDownloadArchive(long gid, String token, String or) {
return getHost() + "archiver.php?gid=" + gid + "&token=" + token + "&or=" + or;
}
public static String getTagDefinitionUrl(String tag) {
return "https://ehwiki.org/wiki/" + tag.replace(' ', '_');
}
@NonNull
public static String getPopularUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_POPULAR_E;
case SITE_EX:
return URL_POPULAR_EX;
}
}
@NonNull
public static String getImageSearchUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_IMAGE_SEARCH_E;
case SITE_EX:
return URL_IMAGE_SEARCH_EX;
}
}
@NonNull
public static String getWatchedUrl() {
switch (Settings.getGallerySite()) {
default:
case SITE_E:
return URL_WATCHED_E;
case SITE_EX:
return URL_WATCHED_EX;
}
}
}
@@ -0,0 +1,174 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import com.hippo.ehviewer.Settings
import com.hippo.network.UrlBuilder
object EhUrl {
const val SITE_E = 0
const val SITE_EX = 1
const val DOMAIN_EX = "exhentai.org"
const val DOMAIN_E = "e-hentai.org"
const val DOMAIN_LOFI = "lofi.e-hentai.org"
const val HOST_EX = "https://$DOMAIN_EX/"
const val API_EX = HOST_EX + "api.php"
const val URL_FAVORITES_EX = HOST_EX + "favorites.php"
const val URL_UCONFIG_EX = HOST_EX + "uconfig.php"
const val URL_MY_TAGS_EX = HOST_EX + "mytags"
const val URL_WATCHED_EX = HOST_EX + "watched"
const val HOST_E = "https://$DOMAIN_E/"
const val API_E = HOST_E + "api.php"
const val URL_FAVORITES_E = HOST_E + "favorites.php"
const val URL_UCONFIG_E = HOST_E + "uconfig.php"
const val URL_MY_TAGS_E = HOST_E + "mytags"
const val URL_WATCHED_E = HOST_E + "watched"
const val API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01"
const val URL_POPULAR_E = "https://e-hentai.org/popular"
const val URL_POPULAR_EX = "https://exhentai.org/popular"
const val URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php"
const val URL_IMAGE_SEARCH_EX = "https://exhentai.org/upload/image_lookup.php"
const val URL_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login"
const val URL_REGISTER = "https://forums.e-hentai.org/index.php?act=Reg&CODE=00"
const val URL_FORUMS = "https://forums.e-hentai.org/"
const val URL_FUNDS = "https://e-hentai.org/exchange.php?t=gp"
const val REFERER_EX = "https://$DOMAIN_EX"
const val ORIGIN_EX = REFERER_EX
const val REFERER_E = "https://$DOMAIN_E"
const val ORIGIN_E = REFERER_E
const val URL_PREFIX_THUMB_E = "https://ehgt.org/"
const val URL_PREFIX_THUMB_EX = "https://exhentai.org/t/"
val host: String
get() = when (Settings.getGallerySite()) {
SITE_E -> HOST_E
SITE_EX -> HOST_EX
else -> HOST_E
}
val favoritesUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_FAVORITES_E
SITE_EX -> URL_FAVORITES_EX
else -> URL_FAVORITES_E
}
val apiUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> API_E
SITE_EX -> API_EX
else -> API_E
}
@JvmStatic
val referer: String
get() = when (Settings.getGallerySite()) {
SITE_E -> REFERER_E
SITE_EX -> REFERER_EX
else -> REFERER_E
}
val origin: String
get() = when (Settings.getGallerySite()) {
SITE_E -> ORIGIN_E
SITE_EX -> ORIGIN_EX
else -> ORIGIN_E
}
@JvmStatic
val uConfigUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
@JvmStatic
val myTagsUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_MY_TAGS_E
SITE_EX -> URL_MY_TAGS_EX
else -> URL_MY_TAGS_E
}
val popularUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_POPULAR_E
SITE_EX -> URL_POPULAR_EX
else -> URL_POPULAR_E
}
val imageSearchUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_IMAGE_SEARCH_E
SITE_EX -> URL_IMAGE_SEARCH_EX
else -> URL_IMAGE_SEARCH_E
}
val watchedUrl: String
get() = when (Settings.getGallerySite()) {
SITE_E -> URL_WATCHED_E
SITE_EX -> URL_WATCHED_EX
else -> URL_WATCHED_E
}
fun getGalleryDetailUrl(gid: Long, token: String?): String {
return getGalleryDetailUrl(gid, token, 0, false)
}
@JvmStatic
fun getGalleryDetailUrl(gid: Long, token: String?, index: Int, allComment: Boolean): String {
val builder = UrlBuilder(host + "g/" + gid + '/' + token + '/')
if (index != 0) {
builder.addQuery("p", index)
}
if (allComment) {
builder.addQuery("hc", 1)
}
return builder.build()
}
@JvmStatic
fun getGalleryMultiPageViewerUrl(gid: Long, token: String): String {
val builder = UrlBuilder(host + "mpv/" + gid + '/' + token + '/')
return builder.build()
}
@JvmStatic
fun getPageUrl(gid: Long, index: Int, pToken: String): String {
return host + "s/" + pToken + '/' + gid + '-' + (index + 1)
}
fun getAddFavorites(gid: Long, token: String?): String {
return host + "gallerypopups.php?gid=" + gid + "&t=" + token + "&act=addfav"
}
fun getDownloadArchive(gid: Long, token: String?, or: String): String {
return host + "archiver.php?gid=" + gid + "&token=" + token + "&or=" + or
}
fun getTagDefinitionUrl(tag: String): String {
return "https://ehwiki.org/wiki/" + tag.replace(' ', '_')
}
fun getThumbUrlPrefix(): String {
return when (Settings.getGallerySite()) {
SITE_E -> URL_PREFIX_THUMB_E
SITE_EX -> URL_PREFIX_THUMB_EX
else -> URL_PREFIX_THUMB_E
}
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser;
import com.hippo.ehviewer.client.parser.GalleryListUrlParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.scene.GalleryDetailScene;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.ehviewer.ui.scene.ProgressScene;
import com.hippo.scene.Announcer;
public class EhUrlOpener {
private static final String TAG = EhUrlOpener.class.getSimpleName();
@Nullable
public static Announcer parseUrl(String url) {
if (TextUtils.isEmpty(url)) {
return null;
}
ListUrlBuilder listUrlBuilder = GalleryListUrlParser.parse(url);
if (listUrlBuilder != null) {
Bundle args = new Bundle();
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_LIST_URL_BUILDER);
args.putParcelable(GalleryListScene.KEY_LIST_URL_BUILDER, listUrlBuilder);
return new Announcer(GalleryListScene.class).setArgs(args);
}
GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(url);
if (result1 != null) {
Bundle args = new Bundle();
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN);
args.putLong(GalleryDetailScene.KEY_GID, result1.gid);
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token);
return new Announcer(GalleryDetailScene.class).setArgs(args);
}
GalleryPageUrlParser.Result result2 = GalleryPageUrlParser.parse(url);
if (result2 != null) {
Bundle args = new Bundle();
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN);
args.putLong(ProgressScene.KEY_GID, result2.gid);
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken);
args.putInt(ProgressScene.KEY_PAGE, result2.page);
return new Announcer(ProgressScene.class).setArgs(args);
}
Log.i(TAG, "Can't parse url: " + url);
return null;
}
}
@@ -1,189 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.graphics.Color;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.data.GalleryInfo;
import java.util.regex.Pattern;
public class EhUtils {
public static final int NONE = -1; // Use it for homepage
public static final int PRIVATE = 0x400;
public static final int UNKNOWN = 0x800;
public static final int BG_COLOR_DOUJINSHI = 0xfff44336;
public static final int BG_COLOR_MANGA = 0xffff9800;
public static final int BG_COLOR_ARTIST_CG = 0xfffbc02d;
public static final int BG_COLOR_GAME_CG = 0xff4caf50;
public static final int BG_COLOR_WESTERN = 0xff8bc34a;
public static final int BG_COLOR_NON_H = 0xff2196f3;
public static final int BG_COLOR_IMAGE_SET = 0xff3f51b5;
public static final int BG_COLOR_COSPLAY = 0xff9c27b0;
public static final int BG_COLOR_ASIAN_PORN = 0xff9575cd;
public static final int BG_COLOR_MISC = 0xfff06292;
public static final int BG_COLOR_UNKNOWN = Color.BLACK;
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE);
private static final int[] CATEGORY_VALUES = {
EhConfig.MISC,
EhConfig.DOUJINSHI,
EhConfig.MANGA,
EhConfig.ARTIST_CG,
EhConfig.GAME_CG,
EhConfig.IMAGE_SET,
EhConfig.COSPLAY,
EhConfig.ASIAN_PORN,
EhConfig.NON_H,
EhConfig.WESTERN,
PRIVATE,
UNKNOWN};
private static final String[][] CATEGORY_STRINGS = {
new String[]{"misc"},
new String[]{"doujinshi"},
new String[]{"manga"},
new String[]{"artistcg", "Artist CG Sets", "Artist CG"},
new String[]{"gamecg", "Game CG Sets", "Game CG"},
new String[]{"imageset", "Image Sets", "Image Set"},
new String[]{"cosplay"},
new String[]{"asianporn", "Asian Porn"},
new String[]{"non-h"},
new String[]{"western"},
new String[]{"private"},
new String[]{"unknown"}
};
public static int getCategory(String type) {
int i;
for (i = 0; i < CATEGORY_STRINGS.length - 1; i++) {
for (String str : CATEGORY_STRINGS[i])
if (str.equalsIgnoreCase(type))
return CATEGORY_VALUES[i];
}
return CATEGORY_VALUES[i];
}
@NonNull
public static String getCategory(int type) {
int i;
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
if (CATEGORY_VALUES[i] == type)
break;
}
return CATEGORY_STRINGS[i][0];
}
public static int getCategoryColor(int category) {
return switch (category) {
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
case EhConfig.MANGA -> BG_COLOR_MANGA;
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
case EhConfig.NON_H -> BG_COLOR_NON_H;
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
case EhConfig.MISC -> BG_COLOR_MISC;
default -> BG_COLOR_UNKNOWN;
};
}
public static void signOut() {
EhApplication.getEhCookieStore().signOut();
Settings.putAvatar(null);
Settings.putDisplayName(null);
Settings.putNeedSignIn(true);
}
public static boolean needSignedIn() {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
}
public static String getSuitableTitle(GalleryInfo gi) {
if (Settings.getShowJpnTitle()) {
return TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn;
} else {
return TextUtils.isEmpty(gi.title) ? gi.titleJpn : gi.title;
}
}
@Nullable
public static String extractTitle(String title) {
if (null == title) {
return null;
}
title = PATTERN_TITLE_PREFIX.matcher(title).replaceFirst("");
title = PATTERN_TITLE_SUFFIX.matcher(title).replaceFirst("");
// Sometimes title is combined by romaji and english translation.
// Only need romaji.
// TODO But not sure every '|' means that
int index = title.indexOf('|');
if (index >= 0) {
title = title.substring(0, index);
}
if (title.isEmpty()) {
return null;
} else {
return title;
}
}
public static String handleThumbUrlResolution(String url) {
if (null == url) {
return null;
}
String resolution;
switch (Settings.getThumbResolution()) {
default:
case 0: // Auto
return url;
case 1: // 250
resolution = "250";
break;
case 2: // 300
resolution = "300";
break;
}
int index1 = url.lastIndexOf('_');
int index2 = url.lastIndexOf('.');
if (index1 >= 0 && index2 >= 0 && index1 < index2) {
return url.substring(0, index1 + 1) + resolution + url.substring(index2);
} else {
return url;
}
}
}
@@ -0,0 +1,158 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import android.text.TextUtils
import com.hippo.ehviewer.EhApplication.Companion.ehCookieStore
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.GalleryInfo
import java.util.regex.Pattern
object EhUtils {
const val NONE = -1 // Use it for homepage
const val PRIVATE = 0x400
const val UNKNOWN = 0x800
// https://youtrack.jetbrains.com/issue/KT-4749
private const val BG_COLOR_DOUJINSHI = 0xfff44336u
private const val BG_COLOR_MANGA = 0xffff9800u
private const val BG_COLOR_ARTIST_CG = 0xfffbc02du
private const val BG_COLOR_GAME_CG = 0xff4caf50u
private const val BG_COLOR_WESTERN = 0xff8bc34au
private const val BG_COLOR_NON_H = 0xff2196f3u
private const val BG_COLOR_IMAGE_SET = 0xff3f51b5u
private const val BG_COLOR_COSPLAY = 0xff9c27b0u
private const val BG_COLOR_ASIAN_PORN = 0xff9575cdu
private const val BG_COLOR_MISC = 0xfff06292u
private const val BG_COLOR_UNKNOWN = 0xff000000u
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
private val PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*"
)
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
private val PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE
)
private val CATEGORY_VALUES = hashMapOf(
EhConfig.MISC to arrayOf("misc"),
EhConfig.DOUJINSHI to arrayOf("doujinshi"),
EhConfig.MANGA to arrayOf("manga"),
EhConfig.ARTIST_CG to arrayOf("artistcg", "Artist CG Sets", "Artist CG"),
EhConfig.GAME_CG to arrayOf("gamecg", "Game CG Sets", "Game CG"),
EhConfig.IMAGE_SET to arrayOf("imageset", "Image Sets", "Image Set"),
EhConfig.COSPLAY to arrayOf("cosplay"),
EhConfig.ASIAN_PORN to arrayOf("asianporn", "Asian Porn"),
EhConfig.NON_H to arrayOf("non-h"),
EhConfig.WESTERN to arrayOf("western"),
PRIVATE to arrayOf("private"),
UNKNOWN to arrayOf("unknown")
)
private val CATEGORY_STRINGS = CATEGORY_VALUES.entries.map { (k, v) -> v to k }
@JvmStatic
fun getCategory(type: String?): Int {
for (entry in CATEGORY_STRINGS) {
for (str in entry.first)
if (str.equals(type, ignoreCase = true))
return entry.second
}
return UNKNOWN
}
fun getCategory(type: Int): String {
return CATEGORY_VALUES.getOrDefault(type, CATEGORY_VALUES[UNKNOWN])!![0]
}
fun getCategoryColor(category: Int): Int {
return when (category) {
EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI
EhConfig.MANGA -> BG_COLOR_MANGA
EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG
EhConfig.GAME_CG -> BG_COLOR_GAME_CG
EhConfig.WESTERN -> BG_COLOR_WESTERN
EhConfig.NON_H -> BG_COLOR_NON_H
EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET
EhConfig.COSPLAY -> BG_COLOR_COSPLAY
EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN
EhConfig.MISC -> BG_COLOR_MISC
else -> BG_COLOR_UNKNOWN
}.toInt()
}
fun signOut() {
ehCookieStore.signOut()
Settings.putAvatar(null)
Settings.putDisplayName(null)
Settings.putGallerySite(EhUrl.SITE_E)
Settings.putNeedSignIn(true)
}
fun needSignedIn(): Boolean {
return Settings.getNeedSignIn() && !ehCookieStore.hasSignedIn()
}
@JvmStatic
fun getSuitableTitle(gi: GalleryInfo): String? {
return if (Settings.getShowJpnTitle()) {
if (TextUtils.isEmpty(gi.titleJpn)) gi.title else gi.titleJpn
} else {
if (TextUtils.isEmpty(gi.title)) gi.titleJpn else gi.title
}
}
fun extractTitle(fullTitle: String?): String? {
var title: String = fullTitle ?: return null
title = PATTERN_TITLE_PREFIX.matcher(title).replaceFirst("")
title = PATTERN_TITLE_SUFFIX.matcher(title).replaceFirst("")
// Sometimes title is combined by romaji and english translation.
// Only need romaji.
// TODO But not sure every '|' means that
val index = title.indexOf('|')
if (index >= 0) {
title = title.substring(0, index)
}
return title.ifEmpty { null }
}
fun fixThumbUrl(url: String): String {
return EhUrl.getThumbUrlPrefix() +
url.removePrefix(EhUrl.URL_PREFIX_THUMB_E).removePrefix(EhUrl.URL_PREFIX_THUMB_EX)
}
@JvmStatic
fun handleThumbUrlResolution(url: String?): String? {
if (null == url) {
return null
}
val resolution = when (Settings.getThumbResolution()) {
0 -> return url
1 -> "250"
2 -> "300"
else -> return url
}
val index1 = url.lastIndexOf('_')
val index2 = url.lastIndexOf('.')
return if (index1 >= 0 && index2 >= 0 && index1 < index2) {
url.substring(0, index1 + 1) + resolution + url.substring(index2)
} else {
url
}
}
}
@@ -0,0 +1,25 @@
package com.hippo.ehviewer.client.data
interface AbstractGalleryInfo {
var gid: Long
var token: String?
var title: String?
var titleJpn: String?
var thumb: String?
var category: Int
var posted: String?
var uploader: String?
var disowned: Boolean
var rating: Float
var rated: Boolean
var simpleTags: Array<String>?
var pages: Int
var thumbWidth: Int
var thumbHeight: Int
var spanSize: Int
var spanIndex: Int
var spanGroupIndex: Int
var simpleLanguage: String?
var favoriteSlot: Int
var favoriteName: String?
}
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Parcelize
open class BaseGalleryInfo(
@PrimaryKey
@ColumnInfo(name = "GID")
override var gid: Long = 0,
@ColumnInfo(name = "TOKEN")
override var token: String? = null,
@ColumnInfo(name = "TITLE")
override var title: String? = null,
@ColumnInfo(name = "TITLE_JPN")
override var titleJpn: String? = null,
@ColumnInfo(name = "THUMB")
override var thumb: String? = null,
@ColumnInfo(name = "CATEGORY")
override var category: Int = 0,
@ColumnInfo(name = "POSTED")
override var posted: String? = null,
@ColumnInfo(name = "UPLOADER")
override var uploader: String? = null,
@Ignore
override var disowned: Boolean = false,
@ColumnInfo(name = "RATING")
override var rating: Float = 0f,
@Ignore
override var rated: Boolean = false,
@Ignore
override var simpleTags: Array<String>? = null,
@Ignore
override var pages: Int = 0,
@Ignore
override var thumbWidth: Int = 0,
@Ignore
override var thumbHeight: Int = 0,
@Ignore
override var spanSize: Int = 0,
@Ignore
override var spanIndex: Int = 0,
@Ignore
override var spanGroupIndex: Int = 0,
@ColumnInfo(name = "SIMPLE_LANGUAGE")
override var simpleLanguage: String? = null,
@Ignore
override var favoriteSlot: Int = -2,
@Ignore
override var favoriteName: String? = null
) : GalleryInfo
@@ -1,146 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.network.UrlBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class FavListUrlBuilder implements Parcelable {
public static final int FAV_CAT_ALL = -1;
public static final int FAV_CAT_LOCAL = -2;
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
return new FavListUrlBuilder(source);
}
@Override
public FavListUrlBuilder[] newArray(int size) {
return new FavListUrlBuilder[size];
}
};
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
private String mPrev;
private String mNext;
private String mJumpTo;
private String mKeyword;
private int mFavCat = FAV_CAT_ALL;
public FavListUrlBuilder() {
}
protected FavListUrlBuilder(Parcel in) {
this.mPrev = in.readString();
this.mNext = in.readString();
this.mJumpTo = in.readString();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
public static boolean isValidFavCat(int favCat) {
return favCat >= 0 && favCat <= 9;
}
public void setIndex(String index, boolean isNext) {
if (isNext) {
mNext = index;
mPrev = null;
} else {
mPrev = index;
mNext = null;
}
}
public void setJumpTo(String jumpTo) {
mJumpTo = jumpTo;
}
public String getKeyword() {
return mKeyword;
}
public void setKeyword(String keyword) {
mKeyword = keyword;
}
public int getFavCat() {
return mFavCat;
}
public void setFavCat(int favCat) {
mFavCat = favCat;
}
public boolean isLocalFavCat() {
return mFavCat == FAV_CAT_LOCAL;
}
public String build() {
UrlBuilder ub = new UrlBuilder(EhUrl.getFavoritesUrl());
if (isValidFavCat(mFavCat)) {
ub.addQuery("favcat", Integer.toString(mFavCat));
} else if (mFavCat == FAV_CAT_ALL) {
ub.addQuery("favcat", "all");
}
if (!TextUtils.isEmpty(mKeyword)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
} else {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8.displayName()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
if (mPrev != null && !mPrev.isEmpty()) {
ub.addQuery("prev", mPrev);
}
if (mNext != null && !mNext.isEmpty()) {
ub.addQuery("next", mNext);
}
if (mJumpTo != null && !mJumpTo.isEmpty()) {
ub.addQuery("seek", mJumpTo);
}
return ub.build();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mPrev);
dest.writeString(this.mNext);
dest.writeString(this.mJumpTo);
dest.writeString(this.mKeyword);
dest.writeInt(this.mFavCat);
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.ehviewer.client.EhUrl
import com.hippo.network.UrlBuilder
import com.hippo.util.encodeUTF8
import kotlinx.parcelize.Parcelize
@Parcelize
class FavListUrlBuilder(
private var mPrev: String? = null,
private var mNext: String? = null,
var jumpTo: String? = null,
var keyword: String? = null,
var favCat: Int = FAV_CAT_ALL,
) : Parcelable {
fun setIndex(index: String?, isNext: Boolean) {
mNext = index.takeIf { isNext }
mPrev = index.takeUnless { isNext }
}
fun build(): String {
val ub = UrlBuilder(EhUrl.favoritesUrl)
if (isValidFavCat(favCat)) {
ub.addQuery("favcat", favCat.toString())
} else if (favCat == FAV_CAT_ALL) {
ub.addQuery("favcat", "all")
}
keyword?.takeIf { it.isNotBlank() }?.let { ub.addQuery("f_search", encodeUTF8(it)) }
mPrev?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("prev", it) }
mNext?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("next", it) }
jumpTo?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("seek", it) }
return ub.build()
}
companion object {
const val FAV_CAT_ALL = -1
const val FAV_CAT_LOCAL = -2
fun isValidFavCat(favCat: Int): Boolean {
return favCat in 0..9
}
}
}
@@ -1,98 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
public class GalleryApiInfo implements Parcelable {
public static final Parcelable.Creator<GalleryApiInfo> CREATOR = new Parcelable.Creator<GalleryApiInfo>() {
@Override
public GalleryApiInfo createFromParcel(Parcel source) {
return new GalleryApiInfo(source);
}
@Override
public GalleryApiInfo[] newArray(int size) {
return new GalleryApiInfo[size];
}
};
public long gid;
public String token;
public String archiverKey;
public String title;
public String titleJpn;
public int category;
public String thumb;
public String uploader;
public long posted;
public int filecount;
public long filesize;
public boolean expunged;
public float rating;
public int torrentcount;
@Nullable
public String[] tags;
public GalleryApiInfo() {
}
protected GalleryApiInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.archiverKey = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.category = in.readInt();
this.thumb = in.readString();
this.uploader = in.readString();
this.posted = in.readLong();
this.filecount = in.readInt();
this.filesize = in.readLong();
this.expunged = in.readByte() != 0;
this.rating = in.readFloat();
this.torrentcount = in.readInt();
this.tags = in.createStringArray();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.gid);
dest.writeString(this.token);
dest.writeString(this.archiverKey);
dest.writeString(this.title);
dest.writeString(this.titleJpn);
dest.writeInt(this.category);
dest.writeString(this.thumb);
dest.writeString(this.uploader);
dest.writeLong(this.posted);
dest.writeInt(this.filecount);
dest.writeLong(this.filesize);
dest.writeByte(expunged ? (byte) 1 : (byte) 0);
dest.writeFloat(this.rating);
dest.writeInt(this.torrentcount);
dest.writeStringArray(this.tags);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryApiInfo(
var gid: Long = 0,
var token: String? = null,
var archiverKey: String? = null,
var title: String? = null,
var titleJpn: String? = null,
var category: Int = 0,
var thumb: String? = null,
var uploader: String? = null,
var posted: Long = 0,
var filecount: Int = 0,
var filesize: Long = 0,
var expunged: Boolean = false,
var rating: Float = 0f,
var torrentcount: Int = 0,
var tags: Array<String>?
) : Parcelable
@@ -1,90 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
public class GalleryComment implements Parcelable {
public static final Creator<GalleryComment> CREATOR = new Creator<GalleryComment>() {
@Override
public GalleryComment createFromParcel(Parcel source) {
return new GalleryComment(source);
}
@Override
public GalleryComment[] newArray(int size) {
return new GalleryComment[size];
}
};
// 0 for uploader comment. can't vote
public long id;
public int score;
public boolean editable;
public boolean voteUpAble;
public boolean voteUpEd;
public boolean voteDownAble;
public boolean voteDownEd;
public boolean uploader;
public String voteState;
public long time;
public String user;
public String comment;
public long lastEdited;
public GalleryComment() {
}
protected GalleryComment(Parcel in) {
this.id = in.readLong();
this.score = in.readInt();
this.editable = in.readByte() != 0;
this.voteUpAble = in.readByte() != 0;
this.voteUpEd = in.readByte() != 0;
this.voteDownAble = in.readByte() != 0;
this.voteDownEd = in.readByte() != 0;
this.uploader = in.readByte() != 0;
this.voteState = in.readString();
this.time = in.readLong();
this.user = in.readString();
this.comment = in.readString();
this.lastEdited = in.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeInt(this.score);
dest.writeByte(editable ? (byte) 1 : (byte) 0);
dest.writeByte(voteUpAble ? (byte) 1 : (byte) 0);
dest.writeByte(voteUpEd ? (byte) 1 : (byte) 0);
dest.writeByte(voteDownAble ? (byte) 1 : (byte) 0);
dest.writeByte(voteDownEd ? (byte) 1 : (byte) 0);
dest.writeByte(uploader ? (byte) 1 : (byte) 0);
dest.writeString(this.voteState);
dest.writeLong(this.time);
dest.writeString(this.user);
dest.writeString(this.comment);
dest.writeLong(this.lastEdited);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryComment(
// 0 for uploader comment. can't vote
@JvmField
var id: Long = 0,
@JvmField
var score: Int = 0,
@JvmField
var editable: Boolean = false,
@JvmField
var voteUpAble: Boolean = false,
@JvmField
var voteUpEd: Boolean = false,
@JvmField
var voteDownAble: Boolean = false,
@JvmField
var voteDownEd: Boolean = false,
@JvmField
var uploader: Boolean = false,
@JvmField
var voteState: String? = null,
@JvmField
var time: Long = 0,
@JvmField
var user: String? = null,
@JvmField
var comment: String? = null,
@JvmField
var lastEdited: Long = 0
) : Parcelable
@@ -1,65 +0,0 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
public class GalleryCommentList implements Parcelable {
public static final Creator<GalleryCommentList> CREATOR = new Creator<GalleryCommentList>() {
@Override
public GalleryCommentList createFromParcel(Parcel in) {
return new GalleryCommentList(in);
}
@Override
public GalleryCommentList[] newArray(int size) {
return new GalleryCommentList[size];
}
};
public GalleryComment[] comments;
public boolean hasMore;
public GalleryCommentList(GalleryComment[] comments, boolean hasMore) {
this.comments = comments;
this.hasMore = hasMore;
}
protected GalleryCommentList(Parcel in) {
Parcelable[] array = in.readParcelableArray(getClass().getClassLoader());
if (array != null) {
comments = Arrays.copyOf(array, array.length, GalleryComment[].class);
} else {
comments = null;
}
hasMore = in.readByte() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelableArray(comments, flags);
dest.writeByte(hasMore ? (byte) 1 : (byte) 0);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 Hippo Seven
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
package com.hippo.ehviewer.client.exception;
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
public class EhException extends Exception {
public EhException(String detailMessage) {
super(detailMessage);
}
public EhException(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
}
@Parcelize
class GalleryCommentList(
@JvmField
var comments: Array<GalleryComment>?,
@JvmField
var hasMore: Boolean
) : Parcelable
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Arrays;
public class GalleryDetail extends GalleryInfo {
public static final Creator<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
@Override
public GalleryDetail createFromParcel(Parcel source) {
return new GalleryDetail(source);
}
@Override
public GalleryDetail[] newArray(int size) {
return new GalleryDetail[size];
}
};
public long apiUid = -1L;
public String apiKey;
public int torrentCount;
public String torrentUrl;
public String archiveUrl;
public String parent;
public ArrayList<GalleryInfo> newerVersions = new ArrayList<>();
public String visible;
public String language;
public String size;
public int pages;
public int favoriteCount;
public boolean isFavorited;
public int ratingCount;
public GalleryTagGroup[] tags;
public GalleryCommentList comments;
public int previewPages;
public PreviewSet previewSet;
public GalleryDetail() {
}
protected GalleryDetail(Parcel in) {
super(in);
this.torrentCount = in.readInt();
this.torrentUrl = in.readString();
this.archiveUrl = in.readString();
this.parent = in.readString();
//noinspection unchecked
this.newerVersions = in.readArrayList(GalleryInfo.class.getClassLoader());
this.visible = in.readString();
this.language = in.readString();
this.size = in.readString();
this.pages = in.readInt();
this.favoriteCount = in.readInt();
this.isFavorited = in.readByte() != 0;
this.ratingCount = in.readInt();
Parcelable[] array = in.readParcelableArray(GalleryTagGroup.class.getClassLoader());
if (array != null) {
this.tags = Arrays.copyOf(array, array.length, GalleryTagGroup[].class);
} else {
this.tags = null;
}
this.comments = in.readParcelable(getClass().getClassLoader());
this.previewPages = in.readInt();
this.previewSet = in.readParcelable(PreviewSet.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.torrentCount);
dest.writeString(this.torrentUrl);
dest.writeString(this.archiveUrl);
dest.writeString(this.parent);
dest.writeList(this.newerVersions);
dest.writeString(this.visible);
dest.writeString(this.language);
dest.writeString(this.size);
dest.writeInt(this.pages);
dest.writeInt(this.favoriteCount);
dest.writeByte(isFavorited ? (byte) 1 : (byte) 0);
dest.writeInt(this.ratingCount);
dest.writeParcelableArray(this.tags, flags);
dest.writeParcelable(this.comments, flags);
dest.writeInt(this.previewPages);
dest.writeParcelable(previewSet, flags);
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryDetail(
@JvmField
val galleryInfo: GalleryInfo = BaseGalleryInfo(),
@JvmField
var apiUid: Long = -1L,
@JvmField
var apiKey: String? = null,
@JvmField
var torrentCount: Int = 0,
@JvmField
var torrentUrl: String? = null,
@JvmField
var archiveUrl: String? = null,
@JvmField
var parent: String? = null,
@JvmField
var newerVersions: ArrayList<GalleryInfo> = ArrayList(),
@JvmField
var visible: String? = null,
@JvmField
var language: String? = null,
@JvmField
var size: String? = null,
@JvmField
var favoriteCount: Int = 0,
@JvmField
var isFavorited: Boolean = false,
@JvmField
var ratingCount: Int = 0,
@JvmField
var tags: Array<GalleryTagGroup>? = null,
@JvmField
var comments: GalleryCommentList? = null,
@JvmField
var previewPages: Int = 0,
@JvmField
var previewSet: PreviewSet? = null,
) : AbstractGalleryInfo by galleryInfo, GalleryInfo
@@ -1,247 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import java.util.regex.Pattern;
public class GalleryInfo implements Parcelable {
/**
* ISO 639-1
*/
@SuppressWarnings("unused")
public static final String S_LANG_JA = "JA";
public static final String S_LANG_EN = "EN";
public static final String S_LANG_ZH = "ZH";
public static final String S_LANG_NL = "NL";
public static final String S_LANG_FR = "FR";
public static final String S_LANG_DE = "DE";
public static final String S_LANG_HU = "HU";
public static final String S_LANG_IT = "IT";
public static final String S_LANG_KO = "KO";
public static final String S_LANG_PL = "PL";
public static final String S_LANG_PT = "PT";
public static final String S_LANG_RU = "RU";
public static final String S_LANG_ES = "ES";
public static final String S_LANG_TH = "TH";
public static final String S_LANG_VI = "VI";
public static final String[] S_LANGS = {
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL,
};
public static final Pattern[] S_LANG_PATTERNS = {
Pattern.compile("[(\\[]eng(?:lish)?[)\\]]|英訳", Pattern.CASE_INSENSITIVE),
// [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
Pattern.compile("[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
};
public static final String[] S_LANG_TAGS = {
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch",
};
public static final Parcelable.Creator<GalleryInfo> CREATOR = new Parcelable.Creator<GalleryInfo>() {
@Override
public GalleryInfo createFromParcel(Parcel source) {
return new GalleryInfo(source);
}
@Override
public GalleryInfo[] newArray(int size) {
return new GalleryInfo[size];
}
};
@PrimaryKey
@ColumnInfo(name = "GID")
public long gid;
@ColumnInfo(name = "TOKEN")
public String token;
@ColumnInfo(name = "TITLE")
public String title;
@ColumnInfo(name = "TITLE_JPN")
public String titleJpn;
@ColumnInfo(name = "THUMB")
public String thumb;
@ColumnInfo(name = "CATEGORY")
public int category;
@ColumnInfo(name = "POSTED")
public String posted;
@ColumnInfo(name = "UPLOADER")
public String uploader;
@Ignore
public boolean disowned;
@ColumnInfo(name = "RATING")
public float rating;
@Ignore
public boolean rated;
@Nullable
@Ignore
public String[] simpleTags;
@Ignore
public int pages;
@Ignore
public int thumbWidth;
@Ignore
public int thumbHeight;
@Ignore
public int spanSize;
@Ignore
public int spanIndex;
@Ignore
public int spanGroupIndex;
/**
* language from title
*/
@ColumnInfo(name = "SIMPLE_LANGUAGE")
public String simpleLanguage;
@Ignore
public int favoriteSlot = -2;
@Ignore
public String favoriteName;
public GalleryInfo() {
}
protected GalleryInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.thumb = in.readString();
this.category = in.readInt();
this.posted = in.readString();
this.uploader = in.readString();
this.rating = in.readFloat();
this.rated = in.readByte() != 0;
this.simpleLanguage = in.readString();
this.simpleTags = in.createStringArray();
this.thumbWidth = in.readInt();
this.thumbHeight = in.readInt();
this.spanSize = in.readInt();
this.spanIndex = in.readInt();
this.spanGroupIndex = in.readInt();
this.favoriteSlot = in.readInt();
this.favoriteName = in.readString();
this.disowned = in.readByte() != 0;
}
public final void generateSLang() {
if (simpleTags != null) {
generateSLangFromTags();
}
if (simpleLanguage == null && title != null) {
generateSLangFromTitle();
}
}
private void generateSLangFromTags() {
assert simpleTags != null;
for (String tag : simpleTags) {
for (int i = 0; i < S_LANGS.length; i++) {
if (S_LANG_TAGS[i].equals(tag)) {
simpleLanguage = S_LANGS[i];
return;
}
}
}
}
private void generateSLangFromTitle() {
for (int i = 0; i < S_LANGS.length; i++) {
if (S_LANG_PATTERNS[i].matcher(title).find()) {
simpleLanguage = S_LANGS[i];
return;
}
}
simpleLanguage = null;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.gid);
dest.writeString(this.token);
dest.writeString(this.title);
dest.writeString(this.titleJpn);
dest.writeString(this.thumb);
dest.writeInt(this.category);
dest.writeString(this.posted);
dest.writeString(this.uploader);
dest.writeFloat(this.rating);
dest.writeByte(this.rated ? (byte) 1 : (byte) 0);
dest.writeString(this.simpleLanguage);
dest.writeStringArray(this.simpleTags);
dest.writeInt(this.thumbWidth);
dest.writeInt(this.thumbHeight);
dest.writeInt(this.spanSize);
dest.writeInt(this.spanIndex);
dest.writeInt(this.spanGroupIndex);
dest.writeInt(this.favoriteSlot);
dest.writeString(this.favoriteName);
dest.writeByte(this.disowned ? (byte) 1 : (byte) 0);
}
}
@@ -0,0 +1,131 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import java.util.regex.Pattern
interface GalleryInfo : AbstractGalleryInfo, Parcelable {
fun generateSLang() {
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) }
?: title?.let { generateSLangFromTitle(it) }
}
private fun generateSLangFromTags(simpleTags: Array<String>): String? {
for (tag in simpleTags) {
for (i in S_LANGS.indices) {
if (S_LANG_TAGS[i] == tag) {
return S_LANGS[i]
}
}
}
return null
}
private fun generateSLangFromTitle(title: String): String? {
for (i in S_LANGS.indices) {
if (S_LANG_PATTERNS[i].matcher(title).find()) {
return S_LANGS[i]
}
}
return null
}
companion object {
/**
* ISO 639-1
*/
const val S_LANG_JA = "JA"
const val S_LANG_EN = "EN"
const val S_LANG_ZH = "ZH"
const val S_LANG_NL = "NL"
const val S_LANG_FR = "FR"
const val S_LANG_DE = "DE"
const val S_LANG_HU = "HU"
const val S_LANG_IT = "IT"
const val S_LANG_KO = "KO"
const val S_LANG_PL = "PL"
const val S_LANG_PT = "PT"
const val S_LANG_RU = "RU"
const val S_LANG_ES = "ES"
const val S_LANG_TH = "TH"
const val S_LANG_VI = "VI"
val S_LANGS = arrayOf(
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL
)
val S_LANG_PATTERNS = arrayOf(
Pattern.compile(
"[(\\[]eng(?:lish)?[)\\]]|英訳",
Pattern.CASE_INSENSITIVE
), // [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
Pattern.compile(
"[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile(
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile(
"[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile(
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE)
)
val S_LANG_TAGS = arrayOf(
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch"
)
}
}
@@ -1,83 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import com.hippo.widget.LoadImageView;
public class GalleryPreview implements Parcelable {
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
@Override
public GalleryPreview createFromParcel(Parcel source) {
return new GalleryPreview(source);
}
@Override
public GalleryPreview[] newArray(int size) {
return new GalleryPreview[size];
}
};
String imageKey;
String imageUrl;
String pageUrl;
int position;
int offsetX = Integer.MIN_VALUE;
int offsetY = Integer.MIN_VALUE;
int clipWidth = Integer.MIN_VALUE;
int clipHeight = Integer.MIN_VALUE;
public GalleryPreview() {
}
protected GalleryPreview(Parcel in) {
this.imageUrl = in.readString();
this.pageUrl = in.readString();
this.position = in.readInt();
this.offsetX = in.readInt();
this.offsetY = in.readInt();
this.clipWidth = in.readInt();
this.clipHeight = in.readInt();
}
public int getPosition() {
return position;
}
public void load(LoadImageView view) {
view.setClip(offsetX, offsetY, clipWidth, clipHeight);
view.load(imageKey, imageUrl);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.imageUrl);
dest.writeString(this.pageUrl);
dest.writeInt(this.position);
dest.writeInt(this.offsetX);
dest.writeInt(this.offsetY);
dest.writeInt(this.clipWidth);
dest.writeInt(this.clipHeight);
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.widget.LoadImageView
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryPreview(
@JvmField
var imageKey: String? = null,
@JvmField
var imageUrl: String? = null,
@JvmField
var pageUrl: String? = null,
@JvmField
var position: Int = 0,
@JvmField
var offsetX: Int = Int.MIN_VALUE,
@JvmField
var offsetY: Int = Int.MIN_VALUE,
@JvmField
var clipWidth: Int = Int.MIN_VALUE,
@JvmField
var clipHeight: Int = Int.MIN_VALUE,
) : Parcelable {
fun load(view: LoadImageView) {
view.setClip(offsetX, offsetY, clipWidth, clipHeight)
view.load(imageKey!!, imageUrl!!)
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
// TODO Add url field?
public class GalleryTagGroup implements Parcelable {
public static final Parcelable.Creator<GalleryTagGroup> CREATOR = new Parcelable.Creator<GalleryTagGroup>() {
@Override
public GalleryTagGroup createFromParcel(Parcel source) {
return new GalleryTagGroup(source);
}
@Override
public GalleryTagGroup[] newArray(int size) {
return new GalleryTagGroup[size];
}
};
private final ArrayList<String> mTagList;
public String groupName;
public GalleryTagGroup() {
mTagList = new ArrayList<>();
}
protected GalleryTagGroup(Parcel in) {
this.groupName = in.readString();
this.mTagList = in.createStringArrayList();
}
public void addTag(String tag) {
mTagList.add(tag);
}
public int size() {
return mTagList.size();
}
public String getTagAt(int position) {
return mTagList.get(position);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.groupName);
dest.writeStringList(this.mTagList);
}
}
@@ -13,24 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
package com.hippo.ehviewer.client.exception;
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
public class ParseException extends EhException {
private final String mBody;
public ParseException(String detailMessage, String body) {
super(detailMessage);
mBody = body;
@Parcelize
class GalleryTagGroup(
private val mTagList: ArrayList<String> = arrayListOf(),
@JvmField
var groupName: String? = null
) : Parcelable {
fun addTag(tag: String) {
mTagList.add(tag)
}
public ParseException(String detailMessage, String body, Throwable cause) {
super(detailMessage, cause);
mBody = body;
fun size(): Int {
return mTagList.size
}
public String getBody() {
return mBody;
fun getTagAt(position: Int): String {
return mTagList[position]
}
}
}
@@ -1,106 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.collect.IntList;
import java.util.ArrayList;
public class LargePreviewSet extends PreviewSet {
public static final Parcelable.Creator<LargePreviewSet> CREATOR = new Parcelable.Creator<LargePreviewSet>() {
@Override
public LargePreviewSet createFromParcel(Parcel source) {
return new LargePreviewSet(source);
}
@Override
public LargePreviewSet[] newArray(int size) {
return new LargePreviewSet[size];
}
};
private final IntList mPositionList;
private final ArrayList<String> mImageUrlList;
private final ArrayList<String> mPageUrlList;
public LargePreviewSet() {
mPositionList = new IntList();
mImageUrlList = new ArrayList<>();
mPageUrlList = new ArrayList<>();
}
protected LargePreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageUrlList = in.createStringArrayList();
this.mPageUrlList = in.createStringArrayList();
}
public void addItem(int index, String imageUrl, String pageUrl) {
mPositionList.add(index);
mImageUrlList.add(imageUrl);
mPageUrlList.add(pageUrl);
}
@Override
public int size() {
return mImageUrlList.size();
}
@Override
public int getPosition(int index) {
return mPositionList.get(index);
}
@Override
public String getPageUrlAt(int index) {
return mPageUrlList.get(index);
}
@Override
public GalleryPreview getGalleryPreview(long gid, int index) {
GalleryPreview galleryPreview = new GalleryPreview();
galleryPreview.position = mPositionList.get(index);
galleryPreview.imageKey = EhCacheKeyFactory.getLargePreviewKey(gid, galleryPreview.position);
galleryPreview.imageUrl = mImageUrlList.get(index);
galleryPreview.pageUrl = mPageUrlList.get(index);
return galleryPreview;
}
@Override
public void load(LoadImageView view, long gid, int index) {
view.resetClip();
view.load(EhCacheKeyFactory.getLargePreviewKey(gid, mPositionList.get(index)),
mImageUrlList.get(index));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.mPositionList, flags);
dest.writeStringList(this.mImageUrlList);
dest.writeStringList(this.mPageUrlList);
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import com.hippo.ehviewer.client.EhCacheKeyFactory
import com.hippo.widget.LoadImageView
import com.hippo.yorozuya.collect.IntList
import kotlinx.parcelize.Parcelize
@Parcelize
class LargePreviewSet(
private val mPositionList: IntList = IntList(),
private val mImageUrlList: ArrayList<String> = ArrayList(),
private val mPageUrlList: ArrayList<String> = ArrayList()
) : PreviewSet() {
fun addItem(index: Int, imageUrl: String, pageUrl: String) {
mPositionList.add(index)
mImageUrlList.add(imageUrl)
mPageUrlList.add(pageUrl)
}
override fun size(): Int {
return mImageUrlList.size
}
override fun getPosition(index: Int): Int {
return mPositionList[index]
}
override fun getPageUrlAt(index: Int): String {
return mPageUrlList[index]
}
override fun getGalleryPreview(gid: Long, index: Int): GalleryPreview {
val galleryPreview = GalleryPreview()
galleryPreview.position = mPositionList[index]
galleryPreview.imageKey = EhCacheKeyFactory.getLargePreviewKey(gid, galleryPreview.position)
galleryPreview.imageUrl = mImageUrlList[index]
galleryPreview.pageUrl = mPageUrlList[index]
return galleryPreview
}
override fun load(view: LoadImageView, gid: Long, index: Int) {
view.resetClip()
view.load(
EhCacheKeyFactory.getLargePreviewKey(gid, mPositionList[index]),
mImageUrlList[index]
)
}
}
@@ -15,9 +15,7 @@
*/
package com.hippo.ehviewer.client.data
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import android.text.TextUtils
import androidx.annotation.IntDef
import com.hippo.ehviewer.client.EhConfig
@@ -26,49 +24,31 @@ import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.dao.QuickSearch
import com.hippo.ehviewer.widget.AdvanceSearchTable
import com.hippo.network.UrlBuilder
import com.hippo.util.encodeUTF8
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.StringUtils
import kotlinx.parcelize.Parcelize
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.net.URLEncoder
open class ListUrlBuilder : Cloneable, Parcelable {
@get:Mode
@Mode
var mode = MODE_NORMAL
private var mPrev: String? = null
private var mNext: String? = null
private var mJumpTo: String? = null
var category = EhUtils.NONE
private var mKeyword: String? = null
private var mSHash: String? = null
var advanceSearch = -1
var minRating = -1
var pageFrom = -1
var pageTo = -1
var imagePath: String? = null
var isUseSimilarityScan = false
var isOnlySearchCovers = false
var isShowExpunged = false
constructor()
protected constructor(parcel: Parcel) {
mode = parcel.readInt()
mPrev = parcel.readString()
mNext = parcel.readString()
mJumpTo = parcel.readString()
this.category = parcel.readInt()
mKeyword = parcel.readString()
advanceSearch = parcel.readInt()
minRating = parcel.readInt()
pageFrom = parcel.readInt()
pageTo = parcel.readInt()
imagePath = parcel.readString()
isUseSimilarityScan = parcel.readByte().toInt() != 0
isOnlySearchCovers = parcel.readByte().toInt() != 0
isShowExpunged = parcel.readByte().toInt() != 0
mSHash = parcel.readString()
}
@Parcelize
data class ListUrlBuilder(
@get:Mode @Mode var mode: Int = MODE_NORMAL,
private var mPrev: String? = null,
private var mNext: String? = null,
private var mJumpTo: String? = null,
var category: Int = EhUtils.NONE,
private var mKeyword: String? = null,
private var mSHash: String? = null,
var advanceSearch: Int = -1,
var minRating: Int = -1,
var pageFrom: Int = -1,
var pageTo: Int = -1,
var imagePath: String? = null,
var isUseSimilarityScan: Boolean = false,
var isOnlySearchCovers: Boolean = false,
var isShowExpunged: Boolean = false,
) : Parcelable {
fun reset() {
mode = MODE_NORMAL
@@ -88,22 +68,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
mSHash = null
}
public override fun clone(): ListUrlBuilder {
return try {
super.clone() as ListUrlBuilder
} catch (e: CloneNotSupportedException) {
throw IllegalStateException(e)
}
}
fun setIndex(index: String?, isNext: Boolean = true) {
if (isNext) {
mNext = index
mPrev = null
} else {
mPrev = index
mNext = null
}
mNext = index?.takeIf { isNext }
mPrev = index?.takeUnless { isNext }
}
fun setJumpTo(jumpTo: String?) {
@@ -116,29 +83,6 @@ open class ListUrlBuilder : Cloneable, Parcelable {
mKeyword = keyword
}
/**
* Make them the same
*
* @param lub The template
*/
fun set(lub: ListUrlBuilder) {
mode = lub.mode
mPrev = lub.mPrev
mNext = lub.mNext
mJumpTo = lub.mJumpTo
this.category = lub.category
mKeyword = lub.mKeyword
advanceSearch = lub.advanceSearch
minRating = lub.minRating
pageFrom = lub.pageFrom
pageTo = lub.pageTo
imagePath = lub.imagePath
isUseSimilarityScan = lub.isUseSimilarityScan
isOnlySearchCovers = lub.isOnlySearchCovers
isShowExpunged = lub.isShowExpunged
mSHash = lub.mSHash
}
fun set(q: QuickSearch) {
mode = q.mode
this.category = q.category
@@ -331,23 +275,19 @@ open class ListUrlBuilder : Cloneable, Parcelable {
return when (mode) {
MODE_NORMAL, MODE_SUBSCRIPTION -> {
val url: String = if (mode == MODE_NORMAL) {
EhUrl.getHost()
EhUrl.host
} else {
EhUrl.getWatchedUrl()
EhUrl.watchedUrl
}
val ub = UrlBuilder(url)
if (this.category != EhUtils.NONE) {
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
}
// Search key
if (mKeyword != null) {
val keyword = mKeyword!!.trim { it <= ' ' }
mKeyword?.run {
val keyword = trim { it <= ' ' }
if (keyword.isNotEmpty()) {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
}
ub.addQuery("f_search", encodeUTF8(this))
}
}
mSHash?.let {
@@ -386,12 +326,10 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
MODE_UPLOADER -> {
val sb = StringBuilder(EhUrl.getHost())
sb.append("uploader/")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
val sb = StringBuilder(EhUrl.host)
mKeyword?.let {
sb.append("uploader/")
sb.append(encodeUTF8(it))
}
mPrev?.let {
sb.append("?prev=").append(it)
@@ -406,12 +344,10 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
MODE_TAG -> {
val sb = StringBuilder(EhUrl.getHost())
sb.append("tag/")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
val sb = StringBuilder(EhUrl.host)
mKeyword?.let {
sb.append("tag/")
sb.append(encodeUTF8(it))
}
mPrev?.let {
sb.append("?prev=").append(it)
@@ -425,15 +361,13 @@ open class ListUrlBuilder : Cloneable, Parcelable {
sb.toString()
}
MODE_WHATS_HOT -> EhUrl.getPopularUrl()
MODE_IMAGE_SEARCH -> EhUrl.getImageSearchUrl()
MODE_WHATS_HOT -> EhUrl.popularUrl
MODE_IMAGE_SEARCH -> EhUrl.imageSearchUrl
MODE_TOPLIST -> {
val sb = StringBuilder(EhUrl.HOST_E)
sb.append("toplist.php?tl=")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
mKeyword.orEmpty().let {
sb.append(encodeUTF8(it))
}
mJumpTo?.let {
sb.append("&p=").append(it)
@@ -441,32 +375,10 @@ open class ListUrlBuilder : Cloneable, Parcelable {
sb.toString()
}
else -> throw java.lang.IllegalStateException("Unexpected value: $mode")
else -> throw IllegalStateException("Unexpected value: $mode")
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(mode)
dest.writeString(mPrev)
dest.writeString(mNext)
dest.writeString(mJumpTo)
dest.writeInt(this.category)
dest.writeString(mKeyword)
dest.writeInt(advanceSearch)
dest.writeInt(minRating)
dest.writeInt(pageFrom)
dest.writeInt(pageTo)
dest.writeString(imagePath)
dest.writeByte(if (isUseSimilarityScan) 1.toByte() else 0.toByte())
dest.writeByte(if (isOnlySearchCovers) 1.toByte() else 0.toByte())
dest.writeByte(if (isShowExpunged) 1.toByte() else 0.toByte())
dest.writeString(mSHash)
}
@IntDef(
MODE_NORMAL,
MODE_UPLOADER,
@@ -486,16 +398,5 @@ open class ListUrlBuilder : Cloneable, Parcelable {
const val MODE_IMAGE_SEARCH = 0x4
const val MODE_SUBSCRIPTION = 0x5
const val MODE_TOPLIST = 0x6
@JvmField
val CREATOR: Creator<ListUrlBuilder> = object : Creator<ListUrlBuilder> {
override fun createFromParcel(source: Parcel): ListUrlBuilder {
return ListUrlBuilder(source)
}
override fun newArray(size: Int): Array<ListUrlBuilder?> {
return arrayOfNulls(size)
}
}
}
}
@@ -1,135 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.collect.IntList;
import java.util.ArrayList;
public class NormalPreviewSet extends PreviewSet {
public static final Creator<NormalPreviewSet> CREATOR = new Creator<NormalPreviewSet>() {
@Override
public NormalPreviewSet createFromParcel(Parcel source) {
return new NormalPreviewSet(source);
}
@Override
public NormalPreviewSet[] newArray(int size) {
return new NormalPreviewSet[size];
}
};
private IntList mPositionList = new IntList();
private ArrayList<String> mImageKeyList = new ArrayList<>();
private ArrayList<String> mImageUrlList = new ArrayList<>();
private IntList mOffsetXList = new IntList();
private IntList mOffsetYList = new IntList();
private IntList mClipWidthList = new IntList();
private IntList mClipHeightList = new IntList();
private ArrayList<String> mPageUrlList = new ArrayList<>();
public NormalPreviewSet() {
}
protected NormalPreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageKeyList = in.createStringArrayList();
this.mImageUrlList = in.createStringArrayList();
this.mOffsetXList = in.readParcelable(IntList.class.getClassLoader());
this.mOffsetYList = in.readParcelable(IntList.class.getClassLoader());
this.mClipWidthList = in.readParcelable(IntList.class.getClassLoader());
this.mClipHeightList = in.readParcelable(IntList.class.getClassLoader());
this.mPageUrlList = in.createStringArrayList();
}
private String getImageKey(String imageUrl) {
int index = imageUrl.indexOf('/');
if (index >= 0) {
return imageUrl.substring(index + 1);
} else {
return imageUrl;
}
}
public void addItem(int position, String imageUrl, int xOffset, int yOffset, int width,
int height, String pageUrl) {
mPositionList.add(position);
mImageKeyList.add(getImageKey(imageUrl));
mImageUrlList.add(imageUrl);
mOffsetXList.add(xOffset);
mOffsetYList.add(yOffset);
mClipWidthList.add(width);
mClipHeightList.add(height);
mPageUrlList.add(pageUrl);
}
@Override
public int size() {
return mPositionList.size();
}
@Override
public int getPosition(int index) {
return mPositionList.get(index);
}
@Override
public String getPageUrlAt(int index) {
return mPageUrlList.get(index);
}
@Override
public GalleryPreview getGalleryPreview(long gid, int index) {
GalleryPreview galleryPreview = new GalleryPreview();
galleryPreview.position = mPositionList.get(index);
galleryPreview.imageKey = mImageKeyList.get(index);
galleryPreview.imageUrl = mImageUrlList.get(index);
galleryPreview.pageUrl = mPageUrlList.get(index);
galleryPreview.offsetX = mOffsetXList.get(index);
galleryPreview.offsetY = mOffsetYList.get(index);
galleryPreview.clipWidth = mClipWidthList.get(index);
galleryPreview.clipHeight = mClipHeightList.get(index);
return galleryPreview;
}
@Override
public void load(LoadImageView view, long gid, int index) {
view.setClip(mOffsetXList.get(index), mOffsetYList.get(index),
mClipWidthList.get(index), mClipHeightList.get(index));
view.load(mImageKeyList.get(index), mImageUrlList.get(index));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.mPositionList, flags);
dest.writeStringList(this.mImageKeyList);
dest.writeStringList(this.mImageUrlList);
dest.writeParcelable(this.mOffsetXList, flags);
dest.writeParcelable(this.mOffsetYList, flags);
dest.writeParcelable(this.mClipWidthList, flags);
dest.writeParcelable(this.mClipHeightList, flags);
dest.writeStringList(this.mPageUrlList);
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import com.hippo.widget.LoadImageView
import com.hippo.yorozuya.collect.IntList
import kotlinx.parcelize.Parcelize
@Parcelize
class NormalPreviewSet(
private var mPositionList: IntList = IntList(),
private var mImageKeyList: ArrayList<String> = ArrayList(),
private var mImageUrlList: ArrayList<String> = ArrayList(),
private var mOffsetXList: IntList = IntList(),
private var mOffsetYList: IntList = IntList(),
private var mClipWidthList: IntList = IntList(),
private var mClipHeightList: IntList = IntList(),
private var mPageUrlList: ArrayList<String> = ArrayList()
) : PreviewSet() {
private fun getImageKey(imageUrl: String): String {
val index = imageUrl.indexOf('/')
return if (index >= 0) {
imageUrl.substring(index + 1)
} else {
imageUrl
}
}
fun addItem(
position: Int, imageUrl: String, xOffset: Int, yOffset: Int, width: Int,
height: Int, pageUrl: String
) {
mPositionList.add(position)
mImageKeyList.add(getImageKey(imageUrl))
mImageUrlList.add(imageUrl)
mOffsetXList.add(xOffset)
mOffsetYList.add(yOffset)
mClipWidthList.add(width)
mClipHeightList.add(height)
mPageUrlList.add(pageUrl)
}
override fun size(): Int {
return mPositionList.size()
}
override fun getPosition(index: Int): Int {
return mPositionList[index]
}
override fun getPageUrlAt(index: Int): String {
return mPageUrlList[index]
}
override fun getGalleryPreview(gid: Long, index: Int): GalleryPreview {
val galleryPreview = GalleryPreview()
galleryPreview.position = mPositionList[index]
galleryPreview.imageKey = mImageKeyList[index]
galleryPreview.imageUrl = mImageUrlList[index]
galleryPreview.pageUrl = mPageUrlList[index]
galleryPreview.offsetX = mOffsetXList[index]
galleryPreview.offsetY = mOffsetYList[index]
galleryPreview.clipWidth = mClipWidthList[index]
galleryPreview.clipHeight = mClipHeightList[index]
return galleryPreview
}
override fun load(view: LoadImageView, gid: Long, index: Int) {
view.setClip(
mOffsetXList[index], mOffsetYList[index],
mClipWidthList[index], mClipHeightList[index]
)
view.load(mImageKeyList[index], mImageUrlList[index])
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcelable;
import com.hippo.widget.LoadImageView;
public abstract class PreviewSet implements Parcelable {
public abstract int size();
public abstract int getPosition(int index);
public abstract String getPageUrlAt(int index);
public abstract GalleryPreview getGalleryPreview(long gid, int index);
public abstract void load(LoadImageView view, long gid, int index);
}
@@ -0,0 +1,27 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.widget.LoadImageView
abstract class PreviewSet : Parcelable {
abstract fun size(): Int
abstract fun getPosition(index: Int): Int
abstract fun getPageUrlAt(index: Int): String
abstract fun getGalleryPreview(gid: Long, index: Int): GalleryPreview
abstract fun load(view: LoadImageView, gid: Long, index: Int)
}
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class NoHAtHClientException extends EhException {
public NoHAtHClientException(String detailMessage) {
super(detailMessage);
}
}
open class EhException : Exception {
constructor(detailMessage: String?) : super(detailMessage)
constructor(detailMessage: String?, cause: Throwable?) : super(detailMessage, cause)
}
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class Image509Exception extends EhException {
public Image509Exception() {
super("509");
}
}
class Image509Exception : EhException("509")
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class CancelledException extends Exception {
public CancelledException() {
super("CANCELED");
}
}
class NoHAtHClientException(detailMessage: String?) : EhException(detailMessage)
@@ -13,15 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception;
package com.hippo.ehviewer.client.exception
/**
* It is an exception for get offensive tip for g.e-hentai.org
*/
public class OffensiveException extends EhException {
public OffensiveException() {
super("OFFENSIVE");
}
}
class OffensiveException : EhException("OFFENSIVE")
@@ -0,0 +1,31 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
class ParseException : EhException {
val body: String
constructor(detailMessage: String?, body: String) : super(detailMessage) {
this.body = body
}
constructor(detailMessage: String?, body: String, cause: Throwable?) : super(
detailMessage,
cause
) {
this.body = body
}
}
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class PiningException extends EhException {
public PiningException() {
super("pining for the fjords");
}
}
class PiningException : EhException("pining for the fjords")
@@ -1,48 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.parser;
import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ArchiveParser {
private static final Pattern PATTERN_FORM = Pattern.compile("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">");
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a href=\"[^\"]*\" onclick=\"return do_hathdl\\('([0-9]+|org)'\\)\">([^<]+)</a>");
@SuppressWarnings("unchecked")
public static Pair<String, Pair<String, String>[]> parse(String body) {
Matcher m = PATTERN_FORM.matcher(body);
if (!m.find()) {
return new Pair<String, Pair<String, String>[]>("", new Pair[0]);
}
String paramOr = m.group(1);
List<Pair<String, String>> archiveList = new ArrayList<>();
m = PATTERN_ARCHIVE.matcher(body);
while (m.find()) {
String res = ParserUtils.trim(m.group(1));
String name = ParserUtils.trim(m.group(2));
Pair<String, String> item = new Pair<>(res, name);
archiveList.add(item);
}
return new Pair<>(paramOr, archiveList.toArray(new Pair[0]));
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.exception.NoHAtHClientException
import org.jsoup.Jsoup
import java.util.function.IntFunction
import java.util.regex.Pattern
object ArchiveParser {
private val PATTERN_ARCHIVE_URL =
Pattern.compile("<strong>(.*)</strong>.*<a href=\"([^\"]*)\">Click Here To Start Downloading</a>")
private val PATTERN_CURRENT_FUNDS =
Pattern.compile("<p>([\\d,]+) GP \\[[^]]*] &nbsp; ([\\d,]+) Credits \\[[^]]*]</p>")
private val PATTERN_FUNDS =
Pattern.compile("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", Pattern.DOTALL)
private val PATTERN_HATH_FORM =
Pattern.compile("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">")
private val PATTERN_HATH_ARCHIVE =
Pattern.compile("<p><a href=\"[^\"]*\" onclick=\"return do_hathdl\\('([0-9]+|org)'\\)\">([^<]+)</a></p>\\s*<p>([\\w. ]+)</p>\\s*<p>([\\w. ]+)</p>")
private val PATTERN_NEED_HATH_CLIENT =
Pattern.compile("You must have a H@H client assigned to your account to use this feature\\.")
fun parse(body: String): Result? {
var m = PATTERN_HATH_FORM.matcher(body)
if (!m.find()) {
return null
}
val paramOr = m.group(1)
val archiveList = ArrayList<Archive>()
val d = Jsoup.parse(body)
val es = d.select("#db>div>div")
for (e in es) {
if (e.childrenSize() > 0 && !e.attr("style").contains("color:#CCCCCC")) {
try {
val res = e.selectFirst("form>input")!!.attr("value")
val name = e.selectFirst("form>div>input")!!.attr("value")
val size = e.selectFirst("p>strong")!!.text()
val cost = e.selectFirst("div>strong")!!.text().replace(",", "")
val item = Archive(res, name, size, cost, false)
archiveList.add(item)
} catch (ex: NullPointerException) {
ex.printStackTrace()
}
}
}
m = PATTERN_HATH_ARCHIVE.matcher(body)
while (m.find()) {
val res = ParserUtils.trim(m.group(1))
val name = ParserUtils.trim(m.group(2))
val size = ParserUtils.trim(m.group(3))
val cost = ParserUtils.trim(m.group(4))
val item = Archive(res, name, size, cost, true)
archiveList.add(item)
}
var funds: Funds? = null
m = PATTERN_CURRENT_FUNDS.matcher(body)
if (m.find()) {
val fundsGP = ParserUtils.parseInt(m.group(1), 0)
val fundsC = ParserUtils.parseInt(m.group(2), 0)
funds = Funds(fundsGP, fundsC)
}
return Result(paramOr, archiveList, funds)
}
@Throws(NoHAtHClientException::class)
fun parseArchiveUrl(body: String): String? {
val m = PATTERN_NEED_HATH_CLIENT.matcher(body)
if (m.find()) {
throw NoHAtHClientException("No H@H client")
}
val d = Jsoup.parse(body)
val a = d.selectFirst("#continue>a[href]")
return if (a != null) {
a.attr("href") + "?start=1"
} else null
// TODO: Check more errors
}
fun parseFunds(body: String): Funds? {
val m = PATTERN_FUNDS.matcher(body)
return if (m.find()) {
val fundsC = ParserUtils.parseInt(m.group(1), 0)
val fundsGP = ParserUtils.parseInt(m.group(2), 0) * 1000
return Funds(fundsGP, fundsC)
} else null
}
class Archive(
val res: String,
val name: String,
val size: String,
val cost: String,
val isHAtH: Boolean
) {
fun format(getString: IntFunction<String?>): String {
return if (isHAtH) {
val costStr = if (cost == "Free") getString.apply(R.string.archive_free) else cost
"[H@H] $name [$size] [$costStr]"
} else {
val nameStr =
getString.apply(if (res == "org") R.string.archive_original else R.string.archive_resample)
val costStr = if (cost == "Free!") getString.apply(R.string.archive_free) else cost
"$nameStr [$size] [$costStr]"
}
}
}
class Funds(var fundsGP: Int, var fundsC: Int)
class Result(val paramOr: String?, val archiveList: List<Archive>, var funds: Funds?)
}
@@ -39,13 +39,13 @@ public class GalleryApiParser {
if (gi == null) {
continue;
}
gi.title = ParserUtils.trim(g.getString("title"));
gi.titleJpn = ParserUtils.trim(g.getString("title_jpn"));
gi.category = EhUtils.getCategory(g.getString("category"));
gi.thumb = EhUtils.handleThumbUrlResolution(g.getString("thumb"));
gi.uploader = g.getString("uploader");
gi.posted = ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000);
gi.rating = NumberUtils.parseFloatSafely(g.getString("rating"), 0.0f);
gi.setTitle(ParserUtils.trim(g.getString("title")));
gi.setTitleJpn(ParserUtils.trim(g.getString("title_jpn")));
gi.setCategory(EhUtils.getCategory(g.getString("category")));
gi.setThumb(EhUtils.handleThumbUrlResolution(g.getString("thumb")));
gi.setUploader(g.getString("uploader"));
gi.setPosted(ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000));
gi.setRating(NumberUtils.parseFloatSafely(g.getString("rating"), 0.0f));
// tags
JSONArray tagJa = g.getJSONArray("tags");
int tagLength = tagJa.length();
@@ -53,8 +53,8 @@ public class GalleryApiParser {
for (int j = 0; j < tagLength; j++) {
tags[j] = tagJa.getString(j);
}
gi.simpleTags = tags;
gi.pages = NumberUtils.parseIntSafely(g.getString("filecount"), 0);
gi.setSimpleTags(tags);
gi.setPages(NumberUtils.parseIntSafely(g.getString("filecount"), 0));
gi.generateSLang();
}
}
@@ -62,7 +62,7 @@ public class GalleryApiParser {
private static GalleryInfo getGalleryInfoByGid(List<GalleryInfo> galleryInfoList, long gid) {
for (int i = 0, size = galleryInfoList.size(); i < size; i++) {
GalleryInfo gi = galleryInfoList.get(i);
if (gi.gid == gid) {
if (gi.getGid() == gid) {
return gi;
}
}
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
import com.hippo.ehviewer.client.data.GalleryDetail;
@@ -124,7 +125,7 @@ public class GalleryDetailParser {
// Generate simpleLanguage for local favorites
for (int i = 0; i < LANGUAGES.length; i++) {
if (galleryDetail.language.equals(LANGUAGES[i])) {
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
galleryDetail.setSimpleLanguage(GalleryInfo.Companion.getS_LANGS()[i]);
break;
}
}
@@ -136,14 +137,14 @@ public class GalleryDetailParser {
private static void parseDetail(GalleryDetail gd, Document d, String body) throws ParseException {
Matcher matcher = PATTERN_DETAIL.matcher(body);
if (matcher.find()) {
gd.gid = NumberUtils.parseLongSafely(matcher.group(1), -1L);
gd.token = matcher.group(2);
gd.setGid(NumberUtils.parseLongSafely(matcher.group(1), -1L));
gd.setToken(matcher.group(2));
gd.apiUid = NumberUtils.parseLongSafely(matcher.group(3), -1L);
gd.apiKey = matcher.group(4);
} else {
throw new ParseException("Can't parse gallery detail", body);
}
if (gd.gid == -1L) {
if (gd.getGid() == -1L) {
throw new ParseException("Can't parse gallery detail", body);
}
@@ -169,26 +170,26 @@ public class GalleryDetailParser {
// Thumb url
Element gd1 = gm.getElementById("gd1");
try {
gd.thumb = parseCoverStyle(StringUtils.trim(gd1.child(0).attr("style")));
gd.setThumb(parseCoverStyle(StringUtils.trim(gd1.child(0).attr("style"))));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
gd.thumb = "";
gd.setThumb("");
}
// Title
Element gn = gm.getElementById("gn");
if (null != gn) {
gd.title = StringUtils.trim(gn.text());
gd.setTitle(StringUtils.trim(gn.text()));
} else {
gd.title = "";
gd.setTitle("");
}
// Jpn title
Element gj = gm.getElementById("gj");
if (null != gj) {
gd.titleJpn = StringUtils.trim(gj.text());
gd.setTitleJpn(StringUtils.trim(gj.text()));
} else {
gd.titleJpn = "";
gd.setTitleJpn("");
}
// Category
@@ -198,27 +199,27 @@ public class GalleryDetailParser {
if (ce == null) {
ce = JsoupUtils.getElementByClass(gdc, "cs");
}
gd.category = EhUtils.getCategory(ce.text());
gd.setCategory(EhUtils.getCategory(ce.text()));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
gd.category = EhUtils.UNKNOWN;
gd.setCategory(EhUtils.UNKNOWN);
}
// Uploader
Element gdn = gm.getElementById("gdn");
if (null != gdn) {
gd.disowned = gdn.attr("style").contains("opacity:0.5");
gd.uploader = StringUtils.trim(gdn.text());
gd.setDisowned(gdn.attr("style").contains("opacity:0.5"));
gd.setUploader(StringUtils.trim(gdn.text()));
} else {
gd.uploader = "";
gd.setUploader("");
}
Element gdd = gm.getElementById("gdd");
gd.posted = "";
gd.setPosted("");
gd.parent = "";
gd.visible = "";
gd.size = "";
gd.pages = 0;
gd.setPages(0);
gd.favoriteCount = 0;
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
@@ -239,17 +240,17 @@ public class GalleryDetailParser {
if (null != rating_label) {
String ratingStr = StringUtils.trim(rating_label.text());
if ("Not Yet Rated".equals(ratingStr)) {
gd.rating = -1.0f;
gd.setRating(-1.0f);
} else {
int index = ratingStr.indexOf(' ');
if (index == -1 || index >= ratingStr.length()) {
gd.rating = 0f;
gd.setRating(0f);
} else {
gd.rating = NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f);
gd.setRating(NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f));
}
}
} else {
gd.rating = -1.0f;
gd.setRating(-1.0f);
}
// isFavorited
@@ -258,19 +259,19 @@ public class GalleryDetailParser {
if (gdf != null) {
final String favoriteName = StringUtils.trim(gdf.text());
if (favoriteName.equals("Add to Favorites")) {
gd.favoriteName = null;
gd.setFavoriteName(null);
} else {
gd.isFavorited = true;
gd.favoriteName = StringUtils.trim(gdf.text());
gd.setFavoriteName(StringUtils.trim(gdf.text()));
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
if (matcher.find()) {
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
gd.setFavoriteSlot((NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19);
}
}
}
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
gd.favoriteSlot = -1;
if (gd.getFavoriteSlot() == -2 && EhDB.containLocalFavorites(gd.getGid())) {
gd.setFavoriteSlot(-1);
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
@@ -289,13 +290,13 @@ public class GalleryDetailParser {
Elements elements = gnd.select("a");
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
GalleryInfo gi = new GalleryInfo();
GalleryInfo gi = new BaseGalleryInfo();
GalleryDetailUrlParser.Result result = GalleryDetailUrlParser.parse(element.attr("href"));
if (result != null) {
gi.gid = result.gid;
gi.token = result.token;
gi.title = StringUtils.trim(element.text());
gi.posted = dates.get(i);
gi.setGid(result.gid);
gi.setToken(result.token);
gi.setTitle(StringUtils.trim(element.text()));
gi.setPosted(dates.get(i));
gd.newerVersions.add(gi);
}
}
@@ -324,7 +325,7 @@ public class GalleryDetailParser {
String key = StringUtils.trim(es.get(0).text());
String value = StringUtils.trim(es.get(1).ownText());
if (key.startsWith("Posted")) {
gd.posted = value;
gd.setPosted(value);
} else if (key.startsWith("Parent")) {
Element a = es.get(1).children().first();
if (a != null) {
@@ -339,9 +340,9 @@ public class GalleryDetailParser {
} else if (key.startsWith("Length")) {
int index = value.indexOf(' ');
if (index >= 0) {
gd.pages = NumberUtils.parseIntSafely(value.substring(0, index), 1);
gd.setPages(NumberUtils.parseIntSafely(value.substring(0, index), 1));
} else {
gd.pages = 1;
gd.setPages(1);
}
} else if (key.startsWith("Favorited")) {
switch (value) {
@@ -476,7 +477,7 @@ public class GalleryDetailParser {
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
// filter comment
if (!comment.uploader) {
EhFilter sEhFilter = EhFilter.getInstance();
EhFilter sEhFilter = EhFilter.INSTANCE;
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
return null;
}
@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
@@ -110,7 +111,7 @@ public class GalleryListParser {
}
private static GalleryInfo parseGalleryInfo(Element e) {
GalleryInfo gi = new GalleryInfo();
GalleryInfo gi = new BaseGalleryInfo();
// Title, gid, token (required), tags
Element glname = JsoupUtils.getElementByClass(e, "glname");
@@ -125,8 +126,8 @@ public class GalleryListParser {
if (a != null) {
GalleryDetailUrlParser.Result result = GalleryDetailUrlParser.parse(a.attr("href"));
if (result != null) {
gi.gid = result.gid;
gi.token = result.token;
gi.setGid(result.gid);
gi.setToken(result.token);
}
}
@@ -136,9 +137,9 @@ public class GalleryListParser {
child = children.get(0);
children = child.children();
}
gi.title = child.text().trim();
gi.setTitle(child.text().trim());
}
if (gi.title == null) {
if (gi.getTitle() == null) {
return null;
}
@@ -149,17 +150,17 @@ public class GalleryListParser {
for (Element gt : gts) {
tags.add(gt.attr("title"));
}
gi.simpleTags = tags.toArray(new String[0]);
gi.setSimpleTags(tags.toArray(new String[0]));
}
// Category
gi.category = EhUtils.UNKNOWN;
gi.setCategory(EhUtils.UNKNOWN);
Element ce = JsoupUtils.getElementByClass(e, "cn");
if (ce == null) {
ce = JsoupUtils.getElementByClass(e, "cs");
}
if (ce != null) {
gi.category = EhUtils.getCategory(ce.text());
gi.setCategory(EhUtils.getCategory(ce.text()));
}
// Thumb
@@ -170,12 +171,12 @@ public class GalleryListParser {
// Thumb size
Matcher m = PATTERN_THUMB_SIZE.matcher(img.attr("style"));
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0);
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0);
gi.setThumbWidth(NumberUtils.parseIntSafely(m.group(2), 0));
gi.setThumbHeight(NumberUtils.parseIntSafely(m.group(1), 0));
} else {
Log.w(TAG, "Can't parse gallery info thumb size");
gi.thumbWidth = 0;
gi.thumbHeight = 0;
gi.setThumbWidth(0);
gi.setThumbHeight(0);
}
// Thumb url
String url = img.attr("data-src");
@@ -185,7 +186,7 @@ public class GalleryListParser {
if (TextUtils.isEmpty(url)) {
url = null;
}
gi.thumb = EhUtils.handleThumbUrlResolution(url);
gi.setThumb(EhUtils.handleThumbUrlResolution(url));
}
// Pages
@@ -193,12 +194,12 @@ public class GalleryListParser {
if (div != null) {
Matcher matcher = PATTERN_PAGES.matcher(div.text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
// Try extended and thumbnail version
if (gi.thumb == null) {
if (gi.getThumb() == null) {
Element gl = JsoupUtils.getElementByClass(e, "gl1e");
if (gl == null) {
gl = JsoupUtils.getElementByClass(e, "gl3t");
@@ -209,35 +210,35 @@ public class GalleryListParser {
// Thumb size
Matcher m = PATTERN_THUMB_SIZE.matcher(img.attr("style"));
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0);
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0);
gi.setThumbWidth(NumberUtils.parseIntSafely(m.group(2), 0));
gi.setThumbHeight(NumberUtils.parseIntSafely(m.group(1), 0));
} else {
Log.w(TAG, "Can't parse gallery info thumb size");
gi.thumbWidth = 0;
gi.thumbHeight = 0;
gi.setThumbWidth(0);
gi.setThumbHeight(0);
}
gi.thumb = EhUtils.handleThumbUrlResolution(img.attr("src"));
gi.setThumb(EhUtils.handleThumbUrlResolution(img.attr("src")));
}
}
}
// Posted
gi.favoriteSlot = -2;
Element posted = e.getElementById("posted_" + gi.gid);
gi.setFavoriteSlot(-2);
Element posted = e.getElementById("posted_" + gi.getGid());
if (posted != null) {
gi.posted = posted.text().trim();
gi.favoriteSlot = parseFavoriteSlot(posted.attr("style"));
gi.setPosted(posted.text().trim());
gi.setFavoriteSlot(parseFavoriteSlot(posted.attr("style")));
}
if (gi.favoriteSlot == -2) {
gi.favoriteSlot = EhDB.containLocalFavorites(gi.gid) ? -1 : -2;
if (gi.getFavoriteSlot() == -2) {
gi.setFavoriteSlot(EhDB.containLocalFavorites(gi.getGid()) ? -1 : -2);
}
// Rating
Element ir = JsoupUtils.getElementByClass(e, "ir");
if (ir != null) {
gi.rating = NumberUtils.parseFloatSafely(parseRating(ir.attr("style")), -1.0f);
gi.setRating(NumberUtils.parseFloatSafely(parseRating(ir.attr("style")), -1.0f));
// TODO The gallery may be rated even if it doesn't has one of these classes
gi.rated = ir.hasClass("irr") || ir.hasClass("irg") || ir.hasClass("irb");
gi.setRated(ir.hasClass("irr") || ir.hasClass("irg") || ir.hasClass("irb"));
}
// Uploader and pages
@@ -255,15 +256,15 @@ public class GalleryListParser {
if (children.size() > uploaderIndex) {
Element div = children.get(uploaderIndex);
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5");
gi.setDisowned(div.attr("style").contains("opacity:0.5"));
Element a = div.children().first();
gi.uploader = a == null ? div.text().trim() : a.text().trim();
gi.setUploader(a == null ? div.text().trim() : a.text().trim());
}
}
if (children.size() > pagesIndex) {
Matcher matcher = PATTERN_PAGES.matcher(children.get(pagesIndex).text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
@@ -274,7 +275,7 @@ public class GalleryListParser {
if (div != null) {
Matcher matcher = PATTERN_PAGES.matcher(div.text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
@@ -16,27 +16,132 @@
package com.hippo.ehviewer.client.parser;
import android.util.Pair;
import androidx.annotation.NonNull;
import org.jsoup.Jsoup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TorrentParser {
private static final Pattern PATTERN_TORRENT = Pattern.compile("<td colspan=\"5\"> &nbsp; <a href=\".*\" onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a></td>");
private static final Pattern PATTERN_TORRENT = Pattern.compile("</span> ([0-9-]+) [0-9:]+</td>[\\s\\S]+</span> ([0-9\\.]+ [KMGT]B)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span>([^<]+)</td>[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a>");
@SuppressWarnings("unchecked")
public static Pair<String, String>[] parse(String body) {
List<Pair<String, String>> torrentList = new ArrayList<>();
Matcher m = PATTERN_TORRENT.matcher(body);
while (m.find()) {
String url = ParserUtils.trim(m.group(1));
String name = ParserUtils.trim(m.group(2));
Pair<String, String> item = new Pair<>(url, name);
torrentList.add(item);
public static List<Result> parse(String body) {
var torrentList = new ArrayList<Result>();
var d = Jsoup.parse(body);
var es = d.select("form>div>table");
for (var e : es) {
Matcher m = PATTERN_TORRENT.matcher(e.html());
if (m.find()) {
String posted = m.group(1);
String size = m.group(2);
int seeds = Integer.parseInt(m.group(3));
int peers = Integer.parseInt(m.group(4));
int downloads = Integer.parseInt(m.group(5));
String uploader = ParserUtils.trim(m.group(6));
String url = ParserUtils.trim(m.group(7));
String name = ParserUtils.trim(m.group(8));
var item = new Result(posted, size, seeds, peers, downloads, uploader, url, name);
torrentList.add(item);
}
}
return torrentList;
}
public static final class Result {
private final String posted;
private final String size;
private final int seeds;
private final int peers;
private final int downloads;
private final String uploader;
private final String url;
private final String name;
public Result(String posted, String size, int seeds, int peers, int downloads, String uploader, String url, String name) {
this.posted = posted;
this.size = size;
this.seeds = seeds;
this.peers = peers;
this.downloads = downloads;
this.uploader = uploader;
this.url = url;
this.name = name;
}
public String format(IntFunction<String> getString) {
return String.format("[%s] %s [%s] [↑%s ↓%s ✓%s]", posted, name, size, seeds, peers, downloads);
}
public String posted() {
return posted;
}
public String size() {
return size;
}
public int seeds() {
return seeds;
}
public int peers() {
return peers;
}
public int downloads() {
return downloads;
}
public String uploader() {
return uploader;
}
public String url() {
return url;
}
public String name() {
return name;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Result) obj;
return Objects.equals(this.posted, that.posted) &&
Objects.equals(this.size, that.size) &&
this.seeds == that.seeds &&
this.peers == that.peers &&
this.downloads == that.downloads &&
Objects.equals(this.uploader, that.uploader) &&
Objects.equals(this.url, that.url) &&
Objects.equals(this.name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(posted, size, seeds, peers, downloads, uploader, url, name);
}
@NonNull
@Override
public String toString() {
return "Result[" +
"posted=" + posted + ", " +
"size=" + size + ", " +
"seeds=" + seeds + ", " +
"peers=" + peers + ", " +
"downloads=" + downloads + ", " +
"uploader=" + uploader + ", " +
"url=" + url + ", " +
"name=" + name + ']';
}
return torrentList.toArray(new Pair[0]);
}
}
@@ -1,9 +0,0 @@
package com.hippo.ehviewer.dao;
import java.util.List;
public interface BasicDao<T> {
List<T> list();
long insert(T t);
}
@@ -0,0 +1,6 @@
package com.hippo.ehviewer.dao
interface BasicDao<T> {
fun list(): List<T>
fun insert(t: T): Long
}
@@ -1,160 +0,0 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "BOOKMARKS")
public class BookmarkInfo extends GalleryInfo {
public static final Creator<BookmarkInfo> CREATOR = new Creator<>() {
@Override
public BookmarkInfo createFromParcel(Parcel source) {
return new BookmarkInfo(source);
}
@Override
public BookmarkInfo[] newArray(int size) {
return new BookmarkInfo[size];
}
};
@ColumnInfo(name = "PAGE")
public int page;
@ColumnInfo(name = "TIME")
public long time;
protected BookmarkInfo(Parcel in) {
super(in);
this.page = in.readInt();
this.time = in.readLong();
}
public BookmarkInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public BookmarkInfo(int page, long time) {
this.page = page;
this.time = time;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.page);
dest.writeLong(this.time);
}
}
@@ -0,0 +1,16 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
@Entity(tableName = "BOOKMARKS")
class BookmarkInfo : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "PAGE")
var page = 0
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
}
@@ -1,20 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface BookmarksDao extends BasicDao<BookmarkInfo> {
@Insert
long insert(BookmarkInfo bookmark);
@Delete
void delete(BookmarkInfo bookmark);
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
List<BookmarkInfo> list();
}
@@ -0,0 +1,18 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface BookmarksDao : BasicDao<BookmarkInfo> {
@Insert
override fun insert(t: BookmarkInfo): Long
@Delete
fun delete(bookmark: BookmarkInfo)
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
override fun list(): List<BookmarkInfo>
}
@@ -1,30 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_DIRNAME")
public class DownloadDirname {
@PrimaryKey
@ColumnInfo(name = "GID")
private long gid;
@ColumnInfo(name = "DIRNAME")
private String dirname;
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getDirname() {
return dirname;
}
public void setDirname(String dirname) {
this.dirname = dirname;
}
}
@@ -0,0 +1,15 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "DOWNLOAD_DIRNAME")
data class DownloadDirname(
@PrimaryKey
@ColumnInfo(name = "GID")
var gid: Long = 0,
@ColumnInfo(name = "DIRNAME")
var dirname: String? = null
)
@@ -1,29 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadDirnameDao extends BasicDao<DownloadDirname> {
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
DownloadDirname load(long gid);
@Update
void update(DownloadDirname downloadDirname);
@Insert
long insert(DownloadDirname downloadDirname);
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
void deleteByKey(long gid);
@Query("DELETE FROM DOWNLOAD_DIRNAME")
void deleteAll();
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
List<DownloadDirname> list();
}
@@ -0,0 +1,27 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadDirnameDao : BasicDao<DownloadDirname> {
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
fun load(gid: Long): DownloadDirname?
@Update
fun update(downloadDirname: DownloadDirname)
@Insert
override fun insert(t: DownloadDirname): Long
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
fun deleteByKey(gid: Long)
@Query("DELETE FROM DOWNLOAD_DIRNAME")
fun deleteAll()
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
override fun list(): List<DownloadDirname>
}
@@ -1,252 +0,0 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "DOWNLOADS")
public class DownloadInfo extends GalleryInfo {
public static final Creator<DownloadInfo> CREATOR = new Creator<>() {
@Override
public DownloadInfo createFromParcel(Parcel source) {
return new DownloadInfo(source);
}
@Override
public DownloadInfo[] newArray(int size) {
return new DownloadInfo[size];
}
};
public static final int STATE_INVALID = -1;
public static final int STATE_NONE = 0;
public static final int STATE_WAIT = 1;
public static final int STATE_DOWNLOAD = 2;
public static final int STATE_FINISH = 3;
public static final int STATE_FAILED = 4;
@ColumnInfo(name = "STATE")
public int state;
@ColumnInfo(name = "LEGACY")
public int legacy;
@ColumnInfo(name = "TIME")
public long time;
@ColumnInfo(name = "LABEL")
public String label;
@Ignore
public long speed;
@Ignore
public long remaining;
@Ignore
public int finished;
@Ignore
public int downloaded;
@Ignore
public int total;
DownloadInfo() {
}
protected DownloadInfo(Parcel in) {
super(in);
this.state = in.readInt();
this.legacy = in.readInt();
this.time = in.readLong();
this.label = in.readString();
}
public DownloadInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public DownloadInfo(int state, int legacy, long time, String label, long speed, long remaining,
int finished, int downloaded, int total) {
this.state = state;
this.legacy = legacy;
this.time = time;
this.label = label;
this.speed = speed;
this.remaining = remaining;
this.finished = finished;
this.downloaded = downloaded;
this.total = total;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getLegacy() {
return legacy;
}
public void setLegacy(int legacy) {
this.legacy = legacy;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.state);
dest.writeInt(this.legacy);
dest.writeLong(this.time);
dest.writeString(this.label);
}
public long getSpeed() {
return this.speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public long getRemaining() {
return this.remaining;
}
public void setRemaining(long remaining) {
this.remaining = remaining;
}
public int getFinished() {
return this.finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public int getDownloaded() {
return this.downloaded;
}
public void setDownloaded(int downloaded) {
this.downloaded = downloaded;
}
public int getTotal() {
return this.total;
}
public void setTotal(int total) {
this.total = total;
}
}
@@ -0,0 +1,69 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "DOWNLOADS")
class DownloadInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "STATE")
var state = 0
@JvmField
@ColumnInfo(name = "LEGACY")
var legacy = 0
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
@JvmField
@ColumnInfo(name = "LABEL")
var label: String? = null
@JvmField
@Ignore
var speed: Long = 0
@JvmField
@Ignore
var remaining: Long = 0
@JvmField
@Ignore
var finished = 0
@JvmField
@Ignore
var downloaded = 0
@JvmField
@Ignore
var total = 0
constructor(galleryInfo: GalleryInfo) : this() {
gid = galleryInfo.gid
token = galleryInfo.token
title = galleryInfo.title
titleJpn = galleryInfo.titleJpn
thumb = galleryInfo.thumb
this.category = galleryInfo.category
posted = galleryInfo.posted
uploader = galleryInfo.uploader
rating = galleryInfo.rating
simpleTags = galleryInfo.simpleTags
simpleLanguage = galleryInfo.simpleLanguage
}
companion object {
const val STATE_INVALID = -1
const val STATE_NONE = 0
const val STATE_WAIT = 1
const val STATE_DOWNLOAD = 2
const val STATE_FINISH = 3
const val STATE_FAILED = 4
}
}
@@ -1,40 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_LABELS")
public class DownloadLabel {
@PrimaryKey
@ColumnInfo(name = "_id")
private Long id;
@ColumnInfo(name = "LABEL")
private String label;
@ColumnInfo(name = "TIME")
private long time;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
@@ -0,0 +1,18 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "DOWNLOAD_LABELS")
data class DownloadLabel(
@PrimaryKey
@ColumnInfo(name = "_id")
var id: Long? = null,
@ColumnInfo(name = "LABEL")
var label: String? = null,
@ColumnInfo(name = "TIME")
var time: Long = 0
)
@@ -1,30 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadLabelDao extends BasicDao<DownloadLabel> {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
List<DownloadLabel> list();
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
List<DownloadLabel> list(int offset, int limit);
@Update
void update(List<DownloadLabel> downloadLabels);
@Update
void update(DownloadLabel downloadLabel);
@Insert
long insert(DownloadLabel downloadLabel);
@Delete
void delete(DownloadLabel downloadLabel);
}
@@ -0,0 +1,28 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadLabelDao : BasicDao<DownloadLabel> {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
override fun list(): List<DownloadLabel>
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<DownloadLabel>
@Update
fun update(downloadLabels: List<DownloadLabel>)
@Update
fun update(downloadLabel: DownloadLabel)
@Insert
override fun insert(t: DownloadLabel): Long
@Delete
fun delete(downloadLabel: DownloadLabel)
}
@@ -1,27 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadsDao extends BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
List<DownloadInfo> list();
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
DownloadInfo load(long gid);
@Update
void update(DownloadInfo downloadInfo);
@Insert
long insert(DownloadInfo downloadInfo);
@Delete
void delete(DownloadInfo downloadInfo);
}
@@ -0,0 +1,31 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadsDao : BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
override fun list(): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
fun load(gid: Long): DownloadInfo?
@Update
fun update(downloadInfos: List<DownloadInfo>)
@Update
fun update(downloadInfo: DownloadInfo)
@Insert
override fun insert(t: DownloadInfo): Long
@Delete
fun delete(downloadInfo: DownloadInfo)
}
@@ -1,23 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {BookmarkInfo.class, DownloadInfo.class, DownloadLabel.class, DownloadDirname.class, Filter.class, HistoryInfo.class, LocalFavoriteInfo.class, QuickSearch.class}, version = 4, exportSchema = false)
public abstract class EhDatabase extends RoomDatabase {
public abstract BookmarksDao bookmarksBao();
public abstract DownloadDirnameDao downloadDirnameDao();
public abstract DownloadLabelDao downloadLabelDao();
public abstract DownloadsDao downloadsDao();
public abstract FilterDao filterDao();
public abstract HistoryDao historyDao();
public abstract LocalFavoritesDao localFavoritesDao();
public abstract QuickSearchDao quickSearchDao();
}
@@ -0,0 +1,28 @@
package com.hippo.ehviewer.dao
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [BookmarkInfo::class, DownloadInfo::class, DownloadLabel::class, DownloadDirname::class, Filter::class, HistoryInfo::class, LocalFavoriteInfo::class, QuickSearch::class],
version = 4,
exportSchema = false
)
abstract class EhDatabase : RoomDatabase() {
abstract fun bookmarksBao(): BookmarksDao
abstract fun downloadDirnameDao(): DownloadDirnameDao
abstract fun downloadLabelDao(): DownloadLabelDao
abstract fun downloadsDao(): DownloadsDao
abstract fun filterDao(): FilterDao
abstract fun historyDao(): HistoryDao
abstract fun localFavoritesDao(): LocalFavoritesDao
abstract fun quickSearchDao(): QuickSearchDao
}
fun buildMainDB(context: Context): EhDatabase {
// TODO: Remove allowMainThreadQueries
return Room.databaseBuilder(context, EhDatabase::class.java, "eh.db").allowMainThreadQueries()
.build()
}
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff. Show more