Compare commits
589
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebbf6e349 | ||
|
|
1b71f6a60a | ||
|
|
a3241e48df | ||
|
|
69396f3d59 | ||
|
|
9042d065ae | ||
|
|
300505e127 | ||
|
|
f7bf1d624b | ||
|
|
d7b08a0181 | ||
|
|
3941f59bbc | ||
|
|
802a30e16c | ||
|
|
032563d7a4 | ||
|
|
fe7ad2032b | ||
|
|
fcde4b3fc2 | ||
|
|
1073ed6c24 | ||
|
|
e5ce3450e8 | ||
|
|
a6f1e91747 | ||
|
|
8dcf5a3654 | ||
|
|
22f9cf7c4c | ||
|
|
38f2879d82 | ||
|
|
4060695db6 | ||
|
|
81fab695ff | ||
|
|
5259428106 | ||
|
|
9774bd858f | ||
|
|
d30379251d | ||
|
|
0e6156e51c | ||
|
|
d610c9727d | ||
|
|
adba04e2dd | ||
|
|
b2aa6552b1 | ||
|
|
8b4b060425 | ||
|
|
2f11853f0d | ||
|
|
388685b784 | ||
|
|
d6939b2913 | ||
|
|
90ee3b749f | ||
|
|
e51c297a7d | ||
|
|
283e8f20b3 | ||
|
|
850c3fb8cd | ||
|
|
7046e994d7 | ||
|
|
099d802a03 | ||
|
|
453df64f69 | ||
|
|
8a2cb42546 | ||
|
|
6413ed67c6 | ||
|
|
4dc7a97322 | ||
|
|
a2d8189c74 | ||
|
|
f806c82c33 | ||
|
|
f46598cd74 | ||
|
|
919d34c90d | ||
|
|
e230af8991 | ||
|
|
09842f5d72 | ||
|
|
911ff31f69 | ||
|
|
f1c9c4e231 | ||
|
|
fa04ce5465 | ||
|
|
45ff03abfd | ||
|
|
20cebb8a05 | ||
|
|
f79e3ff51f | ||
|
|
45a67a7f3d | ||
|
|
281469fe5c | ||
|
|
461ad7fa42 | ||
|
|
899d221b67 | ||
|
|
b246e163d2 | ||
|
|
d83bb0f964 | ||
|
|
a9626475e6 | ||
|
|
d1374eca15 | ||
|
|
6289162684 | ||
|
|
25b3187094 | ||
|
|
bd2c9b172a | ||
|
|
246f953b4d | ||
|
|
433098a388 | ||
|
|
f442d9d030 | ||
|
|
61d7ae03f6 | ||
|
|
22e144cb3b | ||
|
|
a3f0d7092d | ||
|
|
27efdba185 | ||
|
|
59a1fa9992 | ||
|
|
a5140ca293 | ||
|
|
a639b02890 | ||
|
|
ead64d3c73 | ||
|
|
be761071f8 | ||
|
|
e48f19269f | ||
|
|
82ebfb99c5 | ||
|
|
4d69bf2aba | ||
|
|
4e4f77de90 | ||
|
|
fe0c3176d8 | ||
|
|
8cfbdf46e0 | ||
|
|
2234d056fc | ||
|
|
4a4c447bd2 | ||
|
|
933a95023c | ||
|
|
1d8b5f81a1 | ||
|
|
81df632e48 | ||
|
|
b0df7cbc68 | ||
|
|
25d90a3c78 | ||
|
|
384637efdf | ||
|
|
ef3d3e6443 | ||
|
|
675e98fa1a | ||
|
|
0b059c71c1 | ||
|
|
8988f08243 | ||
|
|
adb54d9d7c | ||
|
|
fdf58388a3 | ||
|
|
a5244cdb41 | ||
|
|
6caec5dbce | ||
|
|
4419ab154b | ||
|
|
67c529ffe9 | ||
|
|
057ba0c6e1 | ||
|
|
6117ea755f | ||
|
|
a56c923f0e | ||
|
|
3686241bf5 | ||
|
|
0311c6f52b | ||
|
|
f59dccd5fd | ||
|
|
01d8dabef4 | ||
|
|
773993c6f2 | ||
|
|
2c5646b1c2 | ||
|
|
b8d71e24e7 | ||
|
|
4db3f7a742 | ||
|
|
298203f909 | ||
|
|
a1cafe62cf | ||
|
|
cab75ef0d6 | ||
|
|
33a8d86684 | ||
|
|
b4d329725b | ||
|
|
6efe8922b3 | ||
|
|
f5bd097584 | ||
|
|
7492841b5f | ||
|
|
1430bd9e88 | ||
|
|
e0400305ab | ||
|
|
65d5120272 | ||
|
|
cbb398e58c | ||
|
|
57d8735624 | ||
|
|
fd99bd2ed1 | ||
|
|
46c89534fd | ||
|
|
5650ee580f | ||
|
|
62f126b5d9 | ||
|
|
51cb354f35 | ||
|
|
d4f591354d | ||
|
|
0895af0ecc | ||
|
|
fafe25fee4 | ||
|
|
3db6cc1a1c | ||
|
|
408f5a05fc | ||
|
|
28ee23c5f3 | ||
|
|
37804e67c1 | ||
|
|
a7bce9c8e9 | ||
|
|
bcf2d16a10 | ||
|
|
28d2b92abb | ||
|
|
2f3b6df529 | ||
|
|
fa2cbbb0cc | ||
|
|
d1ab9e77de | ||
|
|
3484261b50 | ||
|
|
7a03fb53ea | ||
|
|
d7f050efc4 | ||
|
|
2d11a29a79 | ||
|
|
0b5aecfdb5 | ||
|
|
487925b1f2 | ||
|
|
54c8183666 | ||
|
|
c878952237 | ||
|
|
26d6428836 | ||
|
|
cf31a31430 | ||
|
|
e10e1c665f | ||
|
|
0834ff26be | ||
|
|
014b8db20d | ||
|
|
34445e6f92 | ||
|
|
7d00e01e00 | ||
|
|
300fabca21 | ||
|
|
e17dc78aeb | ||
|
|
1b9e00efe1 | ||
|
|
152b4d3765 | ||
|
|
e0b95ab2ae | ||
|
|
e0520edccc | ||
|
|
581b32e8a2 | ||
|
|
e71d61ef37 | ||
|
|
0a51d48703 | ||
|
|
30c7f9e020 | ||
|
|
71f7771a97 | ||
|
|
b36e5b7906 | ||
|
|
bb25449b35 | ||
|
|
dc8053dfcd | ||
|
|
20be8e23ba | ||
|
|
d83f476439 | ||
|
|
29352b8838 | ||
|
|
c0390c3c5d | ||
|
|
81dd2eeb47 | ||
|
|
3beedf4dfe | ||
|
|
f5be71ecbd | ||
|
|
58ba5dfedc | ||
|
|
040d265f3c | ||
|
|
fccf4db163 | ||
|
|
1122498b54 | ||
|
|
6a98aea209 | ||
|
|
ae4d589c8e | ||
|
|
6ccb59957d | ||
|
|
edd4e865b3 | ||
|
|
6ff7614e94 | ||
|
|
065a14952a | ||
|
|
1e620931a2 | ||
|
|
a46a531a89 | ||
|
|
06c95b8d36 | ||
|
|
39967a9211 | ||
|
|
a18e6f4b63 | ||
|
|
ef475000c3 | ||
|
|
f08a2444be | ||
|
|
c746d58210 | ||
|
|
ccb48505bf | ||
|
|
c9ddd25e99 | ||
|
|
42234d9163 | ||
|
|
e058885c7c | ||
|
|
1e3556cc8e | ||
|
|
b1cda242df | ||
|
|
b593fce745 | ||
|
|
282cdeaad0 | ||
|
|
6e54c7d672 | ||
|
|
51a12ee5cb | ||
|
|
fcea40685c | ||
|
|
07450a7a51 | ||
|
|
19fa8d15a6 | ||
|
|
db2980f598 | ||
|
|
b3e4fd65aa | ||
|
|
d46eca750c | ||
|
|
d441e92adc | ||
|
|
5f70d71203 | ||
|
|
59ffb2ea8e | ||
|
|
ce59b8037f | ||
|
|
56e67ba70a | ||
|
|
108e9e216e | ||
|
|
b219b4e832 | ||
|
|
ef4e0d76ec | ||
|
|
61c75c06a9 | ||
|
|
091705b3f2 | ||
|
|
6aa5b06619 | ||
|
|
df8fc0b229 | ||
|
|
d7fcda1f83 | ||
|
|
b832c24185 | ||
|
|
3b04f8c695 | ||
|
|
0ef11b58d2 | ||
|
|
bf13bbf506 | ||
|
|
92c94aa22a | ||
|
|
1c0785432b | ||
|
|
dcb9a50061 | ||
|
|
de7f4bdb6e | ||
|
|
73d636dba6 | ||
|
|
3f1c453ff6 | ||
|
|
ecb6baaebb | ||
|
|
e1cba45486 | ||
|
|
ea4181b243 | ||
|
|
6c5b9f8851 | ||
|
|
c1fccaf240 | ||
|
|
e7c223da7d | ||
|
|
6bfe1ba876 | ||
|
|
574e5dab18 | ||
|
|
5dc9b83831 | ||
|
|
57a214a9a4 | ||
|
|
cf48eb94af | ||
|
|
683f1e488a | ||
|
|
e888ae4da7 | ||
|
|
4512f2a375 | ||
|
|
68442afa0f | ||
|
|
ddf4d0cbc3 | ||
|
|
4be185e428 | ||
|
|
f75e66ac19 | ||
|
|
abda781f02 | ||
|
|
c1533ec66d | ||
|
|
c8319446b0 | ||
|
|
7ae452cf13 | ||
|
|
4c024c5faa | ||
|
|
9d16e9d98c | ||
|
|
48357d08f7 | ||
|
|
8e0bcf40db | ||
|
|
85ef5f04dd | ||
|
|
18e0748d14 | ||
|
|
cdde5b098e | ||
|
|
f83964752d | ||
|
|
0c3e34bbf9 | ||
|
|
6dea7faac8 | ||
|
|
726f482c33 | ||
|
|
cb242f664a | ||
|
|
edc0345970 | ||
|
|
a50535dd76 | ||
|
|
505942aa25 | ||
|
|
38a593a9de | ||
|
|
da87fb2551 | ||
|
|
d4dc65a4ba | ||
|
|
53463caedd | ||
|
|
0707a0fad8 | ||
|
|
dd1024af27 | ||
|
|
7edc58a6da | ||
|
|
5a5da0cace | ||
|
|
53f02ed366 | ||
|
|
27cfa885e7 | ||
|
|
beb132d604 | ||
|
|
b1b10298b4 | ||
|
|
b1993ff7d2 | ||
|
|
2afe5fe5b2 | ||
|
|
52fa8f64cc | ||
|
|
4f7978070f | ||
|
|
a909983822 | ||
|
|
5267cad896 | ||
|
|
ce274cf65b | ||
|
|
55b6b2e34d | ||
|
|
5e4e2971b5 | ||
|
|
a933bcfb1e | ||
|
|
b4b38e27f3 | ||
|
|
bc70824b57 | ||
|
|
87ecaaa6a4 | ||
|
|
c4cd9cd802 | ||
|
|
bbe0e8c5a9 | ||
|
|
3f8984d2dc | ||
|
|
f695c4598a | ||
|
|
eada1bc023 | ||
|
|
0ebb55551c | ||
|
|
20bce419c9 | ||
|
|
ca3fc370c3 | ||
|
|
94a8b78d1a | ||
|
|
06e9ff6fcf | ||
|
|
699e74656d | ||
|
|
b3ad6ef8b8 | ||
|
|
673c8e25f5 | ||
|
|
333c8e812d | ||
|
|
f0f7a88cfb | ||
|
|
0661d78bce | ||
|
|
dc4aece272 | ||
|
|
57a312b0b4 | ||
|
|
b9897ed6ac | ||
|
|
733230f037 | ||
|
|
fc6ba3e6ad | ||
|
|
2d7c539c5f | ||
|
|
7f34965026 | ||
|
|
de8b915cff | ||
|
|
d56040581d | ||
|
|
9869b7bbb5 | ||
|
|
7df97c28c0 | ||
|
|
018a559f78 | ||
|
|
3b32ad5fed | ||
|
|
f796d2a842 | ||
|
|
2f7e84e126 | ||
|
|
996ddbf3db | ||
|
|
7779ba2ac5 | ||
|
|
4dadeab762 | ||
|
|
f4d3131ef3 | ||
|
|
efa2ac803e | ||
|
|
e0be9d46bd | ||
|
|
106b27dbec | ||
|
|
f764567523 | ||
|
|
245f8669bc | ||
|
|
b265d9bba6 | ||
|
|
72228574f9 | ||
|
|
fba1ac3d90 | ||
|
|
4a73fb565e | ||
|
|
13011ed4af | ||
|
|
b2eccdefd3 | ||
|
|
dd2d6a977b | ||
|
|
38cd1b6040 | ||
|
|
1a28bbcde8 | ||
|
|
91013daadc | ||
|
|
1ad435e79d | ||
|
|
fd5c300c53 | ||
|
|
aa7d9cf29c | ||
|
|
8db1c3400e | ||
|
|
e723336706 | ||
|
|
b5cd4c36ab | ||
|
|
d1928f2647 | ||
|
|
61621ae00b | ||
|
|
ac20755c45 | ||
|
|
610202bbbd | ||
|
|
58ce7134e1 | ||
|
|
71f91cd79e | ||
|
|
8c821ecad3 | ||
|
|
c704940462 | ||
|
|
6a7cd1b89a | ||
|
|
e29da0ba13 | ||
|
|
7791fa10f5 | ||
|
|
48ed3c8f31 | ||
|
|
7ad7a9b94e | ||
|
|
54b929ba24 | ||
|
|
7b655c6260 | ||
|
|
4e8d41b145 | ||
|
|
f036774e07 | ||
|
|
97d1bd7685 | ||
|
|
8e39449856 | ||
|
|
30f1a5a6eb | ||
|
|
627abb047c | ||
|
|
9284008afe | ||
|
|
9159f4616f | ||
|
|
73296b1430 | ||
|
|
c74e622d69 | ||
|
|
f48eb55516 | ||
|
|
b01f6ef8d3 | ||
|
|
167185ef45 | ||
|
|
777a8ffeaa | ||
|
|
b2b3e6c485 | ||
|
|
613d502fc1 | ||
|
|
79da457db4 | ||
|
|
b1a431ed82 | ||
|
|
b0f2776a23 | ||
|
|
900c346ee8 | ||
|
|
7541a87b21 | ||
|
|
2547c67ebd | ||
|
|
5ae6a0b287 | ||
|
|
f718fc0c89 | ||
|
|
57e69ba7be | ||
|
|
6168b3e047 | ||
|
|
3e500500e0 | ||
|
|
42f06ff84a | ||
|
|
a7824fc473 | ||
|
|
67e7fd6928 | ||
|
|
450e8badba | ||
|
|
704c211e11 | ||
|
|
0ad871cb5b | ||
|
|
9920605570 | ||
|
|
b1e8678dbf | ||
|
|
e2cd2ddc0d | ||
|
|
a3be0dfddf | ||
|
|
4b615ffc0b | ||
|
|
64cbd189ad | ||
|
|
0fae5a128f | ||
|
|
518b44216f | ||
|
|
9ef4512ef9 | ||
|
|
8bb5f1e955 | ||
|
|
8f8932f617 | ||
|
|
23fd1d75c4 | ||
|
|
593dcef550 | ||
|
|
d769fe9074 | ||
|
|
77a8fec790 | ||
|
|
34440edfaa | ||
|
|
3c3d8784ad | ||
|
|
1591abc92c | ||
|
|
0ffa0c34f0 | ||
|
|
c7dc94b61b | ||
|
|
a8b83871a4 | ||
|
|
6f547984ff | ||
|
|
3979c11317 | ||
|
|
2a1ec985f4 | ||
|
|
2a1d49e945 | ||
|
|
7b7e6140d2 | ||
|
|
907cfd9694 | ||
|
|
b51b1d1ec6 | ||
|
|
6a026300e4 | ||
|
|
8511907390 | ||
|
|
05a5be336e | ||
|
|
5434b1ce19 | ||
|
|
c2d4a4367c | ||
|
|
de2f277513 | ||
|
|
283e522037 | ||
|
|
caebe4c23b | ||
|
|
b195277c9a | ||
|
|
6b99cb6253 | ||
|
|
a822bb670a | ||
|
|
af7cc11647 | ||
|
|
0afe331d3d | ||
|
|
7af40f0cd7 | ||
|
|
52dc699ee2 | ||
|
|
ec28e21897 | ||
|
|
3283851dc4 | ||
|
|
ec5e0c7b25 | ||
|
|
13660cb70a | ||
|
|
ce008f5576 | ||
|
|
25f7e8b35c | ||
|
|
109398d955 | ||
|
|
4a8551087b | ||
|
|
34febe3545 | ||
|
|
53a0307a8e | ||
|
|
1c6f624fd1 | ||
|
|
780e5f08f9 | ||
|
|
1bb73b1147 | ||
|
|
09b0d4d6c4 | ||
|
|
7e3856fa05 | ||
|
|
fbcb3f5436 | ||
|
|
324b4ced06 | ||
|
|
79c2abe790 | ||
|
|
cdab9168fc | ||
|
|
ce272724de | ||
|
|
2fa0a19d3b | ||
|
|
38b9a6e04a | ||
|
|
22d3c2b6bf | ||
|
|
218a8397c4 | ||
|
|
fd990da71e | ||
|
|
4a70c89a4d | ||
|
|
7e3143678f | ||
|
|
d80e09dfba | ||
|
|
3b3b251c21 | ||
|
|
1e848bd70f | ||
|
|
fab2295445 | ||
|
|
a913bccc9e | ||
|
|
57e7f89814 | ||
|
|
f98fd5e5f1 | ||
|
|
e6af2e5c6b | ||
|
|
d219989ec7 | ||
|
|
b9daf608ff | ||
|
|
869da61fd0 | ||
|
|
9f79e2505c | ||
|
|
3000b36dbd | ||
|
|
1d21b0e5c4 | ||
|
|
8d6b0926d6 | ||
|
|
4c39d13c80 | ||
|
|
327dd1c73f | ||
|
|
b36b8f3234 | ||
|
|
5f6da284fb | ||
|
|
83d27e18cb | ||
|
|
e291bd58a8 | ||
|
|
23acdb6492 | ||
|
|
3b5e56b049 | ||
|
|
57d4967f2a | ||
|
|
80fcda149b | ||
|
|
1a86eef52c | ||
|
|
98d401f9c3 | ||
|
|
0f2fae886e | ||
|
|
f30a9093af | ||
|
|
b4334da202 | ||
|
|
416fb3bda9 | ||
|
|
7e0fc2e364 | ||
|
|
540d290be2 | ||
|
|
53974ad88d | ||
|
|
cd9b5e5a44 | ||
|
|
d9959bbcb2 | ||
|
|
d7dfd53244 | ||
|
|
84489bf61b | ||
|
|
3fdb56390e | ||
|
|
a34e766f70 | ||
|
|
d74023d4fa | ||
|
|
41572e98ba | ||
|
|
063087c87e | ||
|
|
4a5d53d713 | ||
|
|
9a262525ec | ||
|
|
e327b4e84b | ||
|
|
c128a2b518 | ||
|
|
a3263ab4af | ||
|
|
4a4efe4725 | ||
|
|
89d2433369 | ||
|
|
023175ce6d | ||
|
|
21c8d24d57 | ||
|
|
e49f5c95f4 | ||
|
|
6912e578ca | ||
|
|
8680cff2b2 | ||
|
|
aea75d44e3 | ||
|
|
80481f1778 | ||
|
|
3ea7ff8202 | ||
|
|
b5979c0528 | ||
|
|
f0f79a52b8 | ||
|
|
b549acc1de | ||
|
|
2505cc5d00 | ||
|
|
d5af66b3fc | ||
|
|
f680dc7e46 | ||
|
|
ffd8bbb1ce | ||
|
|
216a043094 | ||
|
|
021eb5f828 | ||
|
|
f3bf8849e9 | ||
|
|
128f7100d7 | ||
|
|
2cf53dd00f | ||
|
|
4a674e46e6 | ||
|
|
f6b0134fdb | ||
|
|
675a6b863e | ||
|
|
b570eeef93 | ||
|
|
71a8b4e607 | ||
|
|
89b866b0fc | ||
|
|
bcce20d9ac | ||
|
|
420e760fb3 | ||
|
|
a168c95d79 | ||
|
|
d8ccec45f9 | ||
|
|
1316875602 | ||
|
|
a85387c7c8 | ||
|
|
d357e83d06 | ||
|
|
51546f6fea | ||
|
|
b4aa5ec593 | ||
|
|
909380f592 | ||
|
|
d5f26643bf | ||
|
|
a15dc4afdf | ||
|
|
b317bc334e | ||
|
|
d0a8d08b26 | ||
|
|
f73a922a8e | ||
|
|
c6682ce718 | ||
|
|
764d1137ae | ||
|
|
02fc972a79 | ||
|
|
7fc727cf2e | ||
|
|
d77e1e6820 | ||
|
|
241d9daae9 | ||
|
|
462e0257a9 | ||
|
|
66fbc7e821 | ||
|
|
3c37f22027 | ||
|
|
0d24bc6602 | ||
|
|
5a50df8179 | ||
|
|
f1251fd150 | ||
|
|
16202efc7b | ||
|
|
26c6052286 | ||
|
|
7ce386a7b8 | ||
|
|
e43cd0a039 | ||
|
|
70be405032 | ||
|
|
9f3ad8142a | ||
|
|
51d9ece30c | ||
|
|
d8e79f879f | ||
|
|
76d50b56ee | ||
|
|
c8d1e957a1 | ||
|
|
b287af4824 | ||
|
|
416ef8d6d7 | ||
|
|
7c71abb985 | ||
|
|
900f2462a7 |
No files matched your search
@@ -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:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
|
||||
|
||||
- name: Push Apks
|
||||
uses: cpina/github-action-push-to-another-repository@v1.5
|
||||
uses: Ehviewer-Overhauled/github-action-push-to-another-repository@main
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
@@ -68,5 +68,6 @@ jobs:
|
||||
user-name: 'github-actions[bot]'
|
||||
user-email: 'github-actions[bot]@users.noreply.github.com'
|
||||
target-branch: deploy
|
||||
create-target-branch: true
|
||||
commit-message: Add apks from $GITHUB_REF
|
||||
target-directory: fdroid/tmp-apks
|
||||
@@ -1,10 +1,10 @@
|
||||
<p align="right">
|
||||
<strong>English<strong>
|
||||
<strong>English</strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
@@ -66,7 +66,7 @@ with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Get it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
|
||||
# Screenshot
|
||||
|
||||
@@ -94,7 +94,7 @@ Here is the libraries
|
||||
|
||||
Copyright 2014-2019 Hippo Seven
|
||||
Copyright 2020-2022 NekoInverter
|
||||
Copyright 2022 Tarsin Norbin
|
||||
Copyright 2022-2023 Tarsin Norbin
|
||||
|
||||
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.1"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable = true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
universalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
sign {
|
||||
storeFile file(signStoreFile)
|
||||
storePassword signStorePassword
|
||||
keyAlias signkeyAlias
|
||||
keyPassword signkeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180029
|
||||
versionName "1.8.5.1"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_19
|
||||
targetCompatibility JavaVersion.VERSION_19
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '19'
|
||||
freeCompilerArgs += [
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive",
|
||||
// Generate smaller bytecode by not generating runtime not-null assertions.
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-receiver-assertions",
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||
]
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError true
|
||||
checkReleaseBuilds false
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/**"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo.includeInApk false
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.sign
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
dataBinding true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
|
||||
}
|
||||
|
||||
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.6.0-rc01"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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.paging:paging-runtime:3.2.0-alpha03"
|
||||
|
||||
implementation 'androidx.preference:preference: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"
|
||||
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.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
|
||||
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'
|
||||
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'
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
buildToolsVersion = "33.0.1"
|
||||
ndkVersion = "25.2.9519653"
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
val signConfig = signingConfigs.create("release") {
|
||||
storeFile = File(projectDir.path + "/keystore/androidkey.jks")
|
||||
storePassword = "000000"
|
||||
keyAlias = "key0"
|
||||
keyPassword = "000000"
|
||||
|
||||
enableV3Signing = true
|
||||
enableV4Signing = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
minSdk = 28
|
||||
targetSdk = 33
|
||||
versionCode = 180031
|
||||
versionName = "1.8.6.0-alpha03"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
"zh-rCN",
|
||||
"zh-rHK",
|
||||
"zh-rTW",
|
||||
"es",
|
||||
"ja",
|
||||
"ko",
|
||||
"fr",
|
||||
"de",
|
||||
"th",
|
||||
"tr",
|
||||
"nb-rNO"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = File("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_19
|
||||
targetCompatibility = JavaVersion.VERSION_19
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "19"
|
||||
freeCompilerArgs = listOf(
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive",
|
||||
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = true
|
||||
checkReleaseBuilds = false
|
||||
disable.add("MissingTranslation")
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/CHANGES"
|
||||
excludes += "/META-INF/README.md"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo.includeInApk = false
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
signingConfig = signConfig
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
compose = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.2"
|
||||
}
|
||||
|
||||
namespace = "com.hippo.ehviewer"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||
implementation("androidx.activity:activity-compose:1.6.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
|
||||
implementation("androidx.browser:browser:1.5.0")
|
||||
implementation("androidx.collection:collection-ktx:1.3.0-alpha02")
|
||||
|
||||
// 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.core:core-ktx:1.10.0-alpha02")
|
||||
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha07")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.5.1")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
val nav_version = "2.5.3"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
|
||||
implementation("androidx.navigation:navigation-compose:$nav_version")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||
implementation("androidx.paging:paging-runtime-ktx:3.2.0-alpha04")
|
||||
implementation("androidx.paging:paging-compose:1.0.0-alpha18")
|
||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0-rc01")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/room
|
||||
val room_version = "2.5.0"
|
||||
ksp("androidx.room:room-compiler:$room_version")
|
||||
implementation("androidx.room:room-paging:$room_version")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
implementation("com.drakeet.drawer:drawer:1.0.3")
|
||||
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")
|
||||
|
||||
// https://square.github.io/okhttp/changelogs/changelog/
|
||||
implementation("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11")
|
||||
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")
|
||||
// https://coil-kt.github.io/coil/changelog/
|
||||
// implementation 'io.coil-kt:coil:2.2.2'
|
||||
implementation("com.github.Ehviewer-Overhauled.coil:coil-compose:53302f2")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.5.0-RC")
|
||||
implementation("org.jsoup:jsoup:1.15.3")
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude("dev.rikka.rikkax.appcompat", "appcompat")
|
||||
exclude("dev.rikka.rikkax.material", "material")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-android-extensions-runtime")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||
}
|
||||
Vendored
+8
-7
@@ -2,22 +2,23 @@
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static void check*(...);
|
||||
public static void throw*(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
# https://issuetracker.google.com/issues/190382641
|
||||
-keepclassmembers class kotlin.SafePublicationLazyImpl {
|
||||
java.lang.Object _value;
|
||||
}
|
||||
|
||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||
|
||||
# Ktor logger
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
|
||||
-keepattributes LineNumberTable,SourceFile
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-overloadaggressively
|
||||
+6
-11
@@ -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>
|
||||
@@ -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,30 +26,33 @@
|
||||
android:fullBackupContent="@xml/backup_scheme"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
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>
|
||||
|
||||
@@ -86,27 +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:colorMode="wideColorGamut"
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -140,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=""
|
||||
@@ -168,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" />
|
||||
|
||||
@@ -179,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>
|
||||
@@ -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)
|
||||
@@ -1,5 +1,16 @@
|
||||
#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"
|
||||
|
||||
// TODO: Replace it with AFileDescriptor_getFd when minsdk 31
|
||||
// https://developer.android.com/ndk/reference/group/file-descriptor
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
@@ -13,4 +24,4 @@ Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule app/src/main/cpp/liblzma updated: b35079d589...fc9fb0ea24.
@@ -0,0 +1,40 @@
|
||||
package androidx.navigation.ui
|
||||
|
||||
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.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)
|
||||
}
|
||||
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 }
|
||||
@@ -0,0 +1,25 @@
|
||||
package androidx.swiperefreshlayout.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : SwipeRefreshLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
val surfaceColor =
|
||||
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
|
||||
val overlayColor =
|
||||
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
|
||||
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
|
||||
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
|
||||
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,66 @@
|
||||
@file:JvmName("GifDecodeUtils")
|
||||
|
||||
package coil.decode
|
||||
|
||||
import kotlin.experimental.and
|
||||
import okio.BufferedSource
|
||||
import okio.ByteString.Companion.encodeUtf8
|
||||
|
||||
// https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
private val GIF_HEADER_87A = "GIF87a".encodeUtf8()
|
||||
private val GIF_HEADER_89A = "GIF89a".encodeUtf8()
|
||||
|
||||
// https://developers.google.com/speed/webp/docs/riff_container
|
||||
private val WEBP_HEADER_RIFF = "RIFF".encodeUtf8()
|
||||
private val WEBP_HEADER_WEBP = "WEBP".encodeUtf8()
|
||||
private val WEBP_HEADER_VPX8 = "VP8X".encodeUtf8()
|
||||
|
||||
// https://nokiatech.github.io/heif/technical.html
|
||||
private val HEIF_HEADER_FTYP = "ftyp".encodeUtf8()
|
||||
private val HEIF_HEADER_MSF1 = "msf1".encodeUtf8()
|
||||
private val HEIF_HEADER_HEVC = "hevc".encodeUtf8()
|
||||
private val HEIF_HEADER_HEVX = "hevx".encodeUtf8()
|
||||
|
||||
/**
|
||||
* Return 'true' if the [source] contains a GIF image. The [source] is not consumed.
|
||||
*/
|
||||
fun DecodeUtils.isGif(source: BufferedSource): Boolean {
|
||||
return source.rangeEquals(0, GIF_HEADER_89A) ||
|
||||
source.rangeEquals(0, GIF_HEADER_87A)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 'true' if the [source] contains a WebP image. The [source] is not consumed.
|
||||
*/
|
||||
fun DecodeUtils.isWebP(source: BufferedSource): Boolean {
|
||||
return source.rangeEquals(0, WEBP_HEADER_RIFF) &&
|
||||
source.rangeEquals(8, WEBP_HEADER_WEBP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 'true' if the [source] contains an animated WebP image. The [source] is not consumed.
|
||||
*/
|
||||
fun DecodeUtils.isAnimatedWebP(source: BufferedSource): Boolean {
|
||||
return isWebP(source) &&
|
||||
source.rangeEquals(12, WEBP_HEADER_VPX8) &&
|
||||
source.request(17) &&
|
||||
(source.buffer[16] and 0b00000010) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 'true' if the [source] contains an HEIF image. The [source] is not consumed.
|
||||
*/
|
||||
fun DecodeUtils.isHeif(source: BufferedSource): Boolean {
|
||||
return source.rangeEquals(4, HEIF_HEADER_FTYP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 'true' if the [source] contains an animated HEIF image sequence. The [source] is not
|
||||
* consumed.
|
||||
*/
|
||||
fun DecodeUtils.isAnimatedHeif(source: BufferedSource): Boolean {
|
||||
return isHeif(source) &&
|
||||
(source.rangeEquals(8, HEIF_HEADER_MSF1) ||
|
||||
source.rangeEquals(8, HEIF_HEADER_HEVC) ||
|
||||
source.rangeEquals(8, HEIF_HEADER_HEVX))
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package coil.decode
|
||||
|
||||
import okio.Buffer
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.decodeHex
|
||||
import okio.ForwardingSource
|
||||
import okio.Source
|
||||
|
||||
/**
|
||||
* A [ForwardingSource] that rewrites the GIF frame delay in every graphics control block if it's
|
||||
* below a threshold.
|
||||
*/
|
||||
internal class FrameDelayRewritingSource(delegate: Source) : ForwardingSource(delegate) {
|
||||
|
||||
// An intermediary buffer so we can read and alter the data before it's written to the destination.
|
||||
private val buffer = Buffer()
|
||||
|
||||
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||
// Ensure our buffer has enough bytes to satisfy this read.
|
||||
request(byteCount)
|
||||
|
||||
// Short circuit if there are no bytes in the buffer.
|
||||
if (buffer.size == 0L) {
|
||||
return if (byteCount == 0L) 0L else -1L
|
||||
}
|
||||
|
||||
// Search through the buffer and rewrite any frame delays below the threshold.
|
||||
var bytesWritten = 0L
|
||||
while (true) {
|
||||
val index = indexOf(FRAME_DELAY_START_MARKER)
|
||||
if (index == -1L) break
|
||||
|
||||
// Write up until the end of the frame delay start marker.
|
||||
bytesWritten += write(sink, index + FRAME_DELAY_START_MARKER_SIZE_BYTES)
|
||||
|
||||
// Check for the end of the graphics control extension block.
|
||||
if (!request(5) || buffer[4] != 0.toByte()) continue
|
||||
|
||||
// Rewrite the frame delay if it is below the threshold.
|
||||
// The frame delay is stored as two unsigned bytes in reverse order
|
||||
// (i.e. the most significant bits are in the byte that's read second).
|
||||
val frameDelay = (buffer[2].toUByte().toInt() shl 8) or buffer[1].toUByte().toInt()
|
||||
if (frameDelay < MINIMUM_FRAME_DELAY) {
|
||||
sink.writeByte(buffer[0].toInt())
|
||||
sink.writeByte(DEFAULT_FRAME_DELAY)
|
||||
sink.writeByte(0)
|
||||
buffer.skip(3)
|
||||
}
|
||||
}
|
||||
|
||||
// Write anything left in the source.
|
||||
if (bytesWritten < byteCount) {
|
||||
bytesWritten += write(sink, byteCount - bytesWritten)
|
||||
}
|
||||
return if (bytesWritten == 0L) -1 else bytesWritten
|
||||
}
|
||||
|
||||
private fun indexOf(bytes: ByteString): Long {
|
||||
var index = -1L
|
||||
while (true) {
|
||||
index = buffer.indexOf(bytes[0], index + 1)
|
||||
if (index == -1L) break
|
||||
if (request(bytes.size.toLong()) && buffer.rangeEquals(index, bytes)) break
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun write(sink: Buffer, byteCount: Long): Long {
|
||||
return buffer.read(sink, byteCount).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
private fun request(byteCount: Long): Boolean {
|
||||
if (buffer.size >= byteCount) return true
|
||||
val toRead = byteCount - buffer.size
|
||||
return super.read(buffer, toRead) == toRead
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
// See: "Graphics Control Extension"
|
||||
private val FRAME_DELAY_START_MARKER = "0021F904".decodeHex()
|
||||
private const val FRAME_DELAY_START_MARKER_SIZE_BYTES = 4
|
||||
private const val MINIMUM_FRAME_DELAY = 2
|
||||
private const val DEFAULT_FRAME_DELAY = 10
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@ object Native {
|
||||
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
package com.hippo
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import com.hippo.image.Image
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
@@ -28,9 +30,7 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToByteBuffer(index: Int): ByteBuffer?
|
||||
|
||||
external fun releaseByteBuffer(buffer: 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
|
||||
@@ -40,4 +40,25 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
closeArchive()
|
||||
pfd.close()
|
||||
}
|
||||
|
||||
fun getImageSource(index: Int): Image.CloseableSource? {
|
||||
val buffer = extractToByteBuffer(index)
|
||||
buffer ?: return null
|
||||
check(buffer.isDirect)
|
||||
Image.rewriteGifSource(buffer)
|
||||
val source = ImageDecoder.createSource(buffer)
|
||||
return object : Image.CloseableSource {
|
||||
override val source: ImageDecoder.Source
|
||||
get() = source
|
||||
|
||||
override fun close() {
|
||||
releaseByteBuffer(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private external fun releaseByteBuffer(buffer: ByteBuffer)
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,12 @@ import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
class ListCheckBoxDialogBuilder constructor(
|
||||
context: Context, items: Array<CharSequence>,
|
||||
listener: OnItemClickListener?, checkText: String?, checked: Boolean
|
||||
class ListCheckBoxDialogBuilder(
|
||||
context: Context,
|
||||
items: List<CharSequence>,
|
||||
listener: (ListCheckBoxDialogBuilder?, AlertDialog?, Int) -> Unit,
|
||||
checkText: String?,
|
||||
checked: Boolean
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
) {
|
||||
@@ -42,10 +45,6 @@ class ListCheckBoxDialogBuilder constructor(
|
||||
return mDialog as AlertDialog
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(builder: ListCheckBoxDialogBuilder?, dialog: AlertDialog?, position: Int)
|
||||
}
|
||||
|
||||
init {
|
||||
val view =
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null)
|
||||
@@ -55,7 +54,7 @@ class ListCheckBoxDialogBuilder constructor(
|
||||
listView.adapter = ArrayAdapter(getContext(), R.layout.item_select_dialog, items)
|
||||
listView.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
|
||||
listener?.onItemClick(this@ListCheckBoxDialogBuilder, mDialog, position)
|
||||
listener(this@ListCheckBoxDialogBuilder, mDialog, position)
|
||||
mDialog?.dismiss()
|
||||
}
|
||||
mCheckBox.text = checkText
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.hippo.compat
|
||||
|
||||
import com.hippo.ehviewer.spider.SpiderInfo
|
||||
import com.hippo.yorozuya.IOUtils
|
||||
import java.io.InputStream
|
||||
|
||||
fun readLegacySpiderInfo(inputStream: InputStream): SpiderInfo {
|
||||
fun read(): String {
|
||||
return IOUtils.readAsciiLine(inputStream)
|
||||
}
|
||||
|
||||
fun readInt(): Int {
|
||||
return read().toInt()
|
||||
}
|
||||
|
||||
fun readLong(): Long {
|
||||
return read().toLong()
|
||||
}
|
||||
repeat(2) { read() } // We assert that only info v2
|
||||
val gid = readLong()
|
||||
val token = read()
|
||||
read()
|
||||
val previewPages = readInt()
|
||||
val previewPerPage = readInt()
|
||||
val pages = read().toInt()
|
||||
val pTokenMap = hashMapOf<Int, String>()
|
||||
val info = SpiderInfo(gid, pages, pTokenMap, 0, token, previewPages, previewPerPage)
|
||||
runCatching {
|
||||
val line = read()
|
||||
val pos = line.indexOf(" ")
|
||||
if (pos > 0) {
|
||||
val index = line.substring(0, pos).toInt()
|
||||
val pToken = line.substring(pos + 1)
|
||||
if (pToken.isNotEmpty()) {
|
||||
pTokenMap[index] = pToken
|
||||
}
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
@@ -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
|
||||
@@ -34,45 +35,35 @@ import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.hippo.Native
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.beerbelly.SimpleDiskCache
|
||||
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
|
||||
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
|
||||
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
|
||||
import java.io.File
|
||||
import java.net.Proxy
|
||||
import java.security.KeyStore
|
||||
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>()
|
||||
@@ -102,77 +93,73 @@ 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
|
||||
}
|
||||
val store = ehCookieStore
|
||||
val eh = EhUrl.HOST_E.toHttpUrl()
|
||||
|
||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(
|
||||
eh,
|
||||
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)
|
||||
}
|
||||
if (ehCookieStore.hasSignedIn()) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,6 +252,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
|
||||
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
|
||||
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
crossfade(true)
|
||||
}.build()
|
||||
}
|
||||
|
||||
@@ -278,10 +266,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() }
|
||||
@@ -290,29 +275,16 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
|
||||
val okHttpClient by lazy {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.cookieJar(ehCookieStore)
|
||||
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
|
||||
.dns(EhDns)
|
||||
.proxySelector(ehProxySelector)
|
||||
|
||||
if (Settings.getDF()) {
|
||||
var trustManager: X509TrustManager
|
||||
try {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm()
|
||||
)
|
||||
trustManagerFactory.init(null as KeyStore?)
|
||||
val trustManagers = trustManagerFactory.trustManagers
|
||||
check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
|
||||
"Unexpected default trust managers:" + Arrays.toString(
|
||||
trustManagers
|
||||
)
|
||||
}
|
||||
trustManager = trustManagers[0] as X509TrustManager
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
trustManager = EhX509TrustManager
|
||||
}
|
||||
val factory =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())!!
|
||||
factory.init(null as KeyStore?)
|
||||
val manager = factory.trustManagers!!
|
||||
|
||||
val trustManager = manager.filterIsInstance<X509TrustManager>().first()
|
||||
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
|
||||
builder.proxy(Proxy.NO_PROXY)
|
||||
}
|
||||
@@ -329,20 +301,18 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val spiderInfoCache by lazy {
|
||||
SimpleDiskCache(
|
||||
File(application.cacheDir, "spider_info"),
|
||||
20 * 1024 * 1024
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val downloadManager by lazy { DownloadManager(application) }
|
||||
val downloadManager by lazy { DownloadManager() }
|
||||
|
||||
@JvmStatic
|
||||
val hosts by lazy { Hosts(application, "hosts.db") }
|
||||
|
||||
@JvmStatic
|
||||
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
|
||||
|
||||
@JvmStatic
|
||||
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
|
||||
|
||||
@JvmStatic
|
||||
val ehDatabase by lazy { buildMainDB(application) }
|
||||
}
|
||||
}
|
||||
@@ -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,15 @@ public class EhDB {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static synchronized void updateDownloadInfo(List<DownloadInfo> downloadInfos) {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
dao.update(downloadInfos);
|
||||
}
|
||||
|
||||
// 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 +257,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 +282,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,17 +336,32 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfoNonRefresh(GalleryInfo info) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
var i = dao.load(info.getGid());
|
||||
if (null != i) {
|
||||
HistoryInfo historyInfo;
|
||||
if (info instanceof HistoryInfo) {
|
||||
historyInfo = (HistoryInfo) info;
|
||||
} else {
|
||||
historyInfo = new HistoryInfo(info);
|
||||
historyInfo.time = i.time;
|
||||
}
|
||||
dao.update(historyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
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 +402,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 +537,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();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ public class Settings {
|
||||
public static final String KEY_THEME = "theme";
|
||||
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_IMAGE_LIMITS = "image_limits";
|
||||
public static final String KEY_U_CONFIG = "uconfig";
|
||||
public static final String KEY_MY_TAGS = "mytags";
|
||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
||||
public static final int THEME_SYSTEM = -1;
|
||||
public static final String KEY_GALLERY_SITE = "gallery_site";
|
||||
@@ -52,11 +55,24 @@ public class Settings {
|
||||
public static final String KEY_DETAIL_SIZE = "detail_size";
|
||||
public static final String KEY_THUMB_SIZE = "thumb_size_";
|
||||
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
|
||||
public static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
||||
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
|
||||
public static final String KEY_TAG_TRANSLATIONS_SOURCE = "tag_translations_source";
|
||||
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
|
||||
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
||||
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
||||
public static final String KEY_REQUEST_NEWS = "request_news";
|
||||
public static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
|
||||
public static final String[] SIGN_IN_REQUIRED = {
|
||||
KEY_IMAGE_LIMITS,
|
||||
KEY_GALLERY_SITE,
|
||||
KEY_U_CONFIG,
|
||||
KEY_MY_TAGS,
|
||||
KEY_SHOW_JPN_TITLE,
|
||||
KEY_REQUEST_NEWS,
|
||||
KEY_HIDE_HV_EVENTS
|
||||
};
|
||||
/********************
|
||||
****** Privacy and Security
|
||||
********************/
|
||||
@@ -85,13 +101,11 @@ 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;
|
||||
@@ -111,7 +125,6 @@ public class Settings {
|
||||
private static final int DEFAULT_DETAIL_SIZE = 0;
|
||||
private static final int DEFAULT_THUMB_SIZE = 120;
|
||||
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
||||
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
||||
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
||||
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
|
||||
private static final boolean DEFAULT_SHOW_GALLERY_PAGES = false;
|
||||
@@ -181,6 +194,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";
|
||||
@@ -191,9 +205,10 @@ public class Settings {
|
||||
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
|
||||
private static final String KEY_DOWNLOAD_DELAY = "download_delay";
|
||||
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
||||
private static final String KEY_REQUEST_NEWS = "request_news";
|
||||
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
||||
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
||||
private static final String KEY_QS_SAVE_PROGRESS = "qs_save_progress";
|
||||
private static final boolean DEFAULT_QS_SAVE_PROGRESS = true;
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
public static SharedPreferences sSettingsPre;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
@@ -421,10 +436,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);
|
||||
}
|
||||
@@ -629,6 +640,14 @@ public class Settings {
|
||||
putInt(KEY_DEFAULT_FAV_SLOT, value);
|
||||
}
|
||||
|
||||
public static boolean getQSSaveProgress() {
|
||||
return getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS);
|
||||
}
|
||||
|
||||
public static void putQSSaveProgress(boolean value) {
|
||||
putBoolean(KEY_QS_SAVE_PROGRESS, value);
|
||||
}
|
||||
|
||||
public static boolean getSaveParseErrorBody() {
|
||||
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
|
||||
}
|
||||
@@ -669,6 +688,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,40 +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;
|
||||
|
||||
public class EhCacheKeyFactory {
|
||||
|
||||
public static String getThumbKey(long gid) {
|
||||
return "preview:large:" + gid + ":" + 0; // "thumb:" + gid;
|
||||
}
|
||||
|
||||
public static String getNormalPreviewKey(long gid, int index) {
|
||||
return "preview:normal:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getLargePreviewKey(long gid, int index) {
|
||||
return "preview:large:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getLargePreviewSetKey(long gid, int index) {
|
||||
return "large_preview_set:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getImageKey(long gid, int index) {
|
||||
return "image:" + gid + ":" + index;
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -13,12 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
package com.hippo.ehviewer.client.exception;
|
||||
|
||||
public class Image509Exception extends EhException {
|
||||
|
||||
public Image509Exception() {
|
||||
super("509");
|
||||
object EhCacheKeyFactory {
|
||||
fun getImageKey(gid: Long, index: Int): String {
|
||||
return "image:$gid:$index"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_IPB_MEMBER_ID) &&
|
||||
contains(url, KEY_IPB_PASS_HASH)
|
||||
}
|
||||
|
||||
const val KEY_IPB_MEMBER_ID = "ipb_member_id"
|
||||
const val KEY_IPB_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,275 +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;
|
||||
|
||||
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;
|
||||
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 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);
|
||||
break;
|
||||
case MODE_UPLOADER:
|
||||
mUploaderFilterList.add(filter);
|
||||
break;
|
||||
case MODE_TAG:
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagFilterList.add(filter);
|
||||
break;
|
||||
case MODE_TAG_NAMESPACE:
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagNamespaceFilterList.add(filter);
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
break;
|
||||
case MODE_UPLOADER:
|
||||
mUploaderFilterList.add(filter);
|
||||
break;
|
||||
case MODE_TAG:
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagFilterList.add(filter);
|
||||
break;
|
||||
case MODE_TAG_NAMESPACE:
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagNamespaceFilterList.add(filter);
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
break;
|
||||
case MODE_UPLOADER:
|
||||
mUploaderFilterList.remove(filter);
|
||||
break;
|
||||
case MODE_TAG:
|
||||
mTagFilterList.remove(filter);
|
||||
break;
|
||||
case MODE_TAG_NAMESPACE:
|
||||
mTagNamespaceFilterList.remove(filter);
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +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.okhttp.ChromeRequestBuilder;
|
||||
|
||||
public class EhRequestBuilder extends ChromeRequestBuilder {
|
||||
|
||||
public EhRequestBuilder(String url, String referer) {
|
||||
this(url, referer, null);
|
||||
}
|
||||
|
||||
public EhRequestBuilder(String url, String referer, String origin) {
|
||||
super(url);
|
||||
if (referer != null) {
|
||||
addHeader("Referer", referer);
|
||||
}
|
||||
if (origin != null) {
|
||||
addHeader("Origin", origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.okhttp.ChromeRequestBuilder
|
||||
|
||||
class EhRequestBuilder @JvmOverloads constructor(
|
||||
url: String,
|
||||
referer: String?,
|
||||
origin: String? = null
|
||||
) : ChromeRequestBuilder(url) {
|
||||
init {
|
||||
referer?.let { addHeader("Referer", it) }
|
||||
origin?.let { addHeader("Origin", it) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -45,35 +43,62 @@ import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
private typealias TagGroup = Map<String, String>
|
||||
private typealias TagGroups = Map<String, TagGroup>
|
||||
|
||||
object EhTagDatabase {
|
||||
private lateinit var tags: JSONObject
|
||||
private lateinit var tagList: ArrayList<Pair<String, String>>
|
||||
private const val NAMESPACE_PREFIX = "n"
|
||||
private lateinit var tagGroups: TagGroups
|
||||
|
||||
fun isInitialized(): Boolean {
|
||||
return this::tags.isInitialized && this::tagList.isInitialized
|
||||
return this::tagGroups.isInitialized
|
||||
}
|
||||
|
||||
private fun JSONObject.toTagGroups(): TagGroups =
|
||||
keys().asSequence().associateWith { getJSONObject(it).toTagGroup() }
|
||||
|
||||
private fun JSONObject.toTagGroup(): TagGroup =
|
||||
keys().asSequence().associateWith { getString(it) }
|
||||
|
||||
private fun updateData(source: BufferedSource) {
|
||||
try {
|
||||
val tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8))
|
||||
val tmpTagList = arrayListOf<Pair<String, String>>()
|
||||
tmpTags.keys().forEachRemaining { prefix: String ->
|
||||
val values = tmpTags.optJSONObject(prefix)
|
||||
values?.let {
|
||||
it.keys().forEachRemaining { tag: String ->
|
||||
tmpTagList.add(Pair("$prefix:$tag", values.optString(tag)))
|
||||
}
|
||||
}
|
||||
}
|
||||
tags = tmpTags
|
||||
tagList = tmpTagList
|
||||
tagGroups = JSONObject(source.readString(StandardCharsets.UTF_8)).toTagGroups()
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTranslation(prefix: String?, tag: String?): String? {
|
||||
return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? {
|
||||
return tagGroups[prefix]?.get(tag)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun internalSuggestFlow(
|
||||
tags: Map<String, String>,
|
||||
keyword: String,
|
||||
translate: Boolean,
|
||||
exactly: Boolean
|
||||
): Flow<Pair<String?, String>> = flow {
|
||||
if (exactly) {
|
||||
tags[keyword]?.let {
|
||||
emit(Pair(it.takeIf { translate }, keyword))
|
||||
}
|
||||
} else {
|
||||
if (translate) {
|
||||
tags.forEach { (tag, hint) ->
|
||||
if (tag != keyword &&
|
||||
(tag.containsIgnoreSpace(keyword) || hint.containsIgnoreSpace(keyword))
|
||||
) {
|
||||
emit(Pair(hint, tag))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tags.keys.forEach { tag ->
|
||||
if (tag != keyword && tag.containsIgnoreSpace(keyword)) {
|
||||
emit(Pair(null, tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Construct a cold flow for tag database suggestions */
|
||||
@@ -82,32 +107,32 @@ object EhTagDatabase {
|
||||
translate: Boolean,
|
||||
exactly: Boolean = false
|
||||
): Flow<Pair<String?, String>> = flow {
|
||||
tagList.forEach {
|
||||
val tag = it.first
|
||||
val hint = if (translate) it.second else null
|
||||
val index = tag.indexOf(':')
|
||||
if (tag.substring(index + 1).trim() == keyword) {
|
||||
if (exactly)
|
||||
emit(Pair(hint, tag))
|
||||
return@forEach
|
||||
val keywordPrefix = keyword.substringBefore(':')
|
||||
val keywordTag = keyword.drop(keywordPrefix.length + 1)
|
||||
val prefix = namespaceToPrefix(keywordPrefix) ?: keywordPrefix
|
||||
val tags = tagGroups[prefix.takeIf { keywordTag.isNotEmpty() && it != NAMESPACE_PREFIX }]
|
||||
tags?.let {
|
||||
internalSuggestFlow(it, keywordTag, translate, exactly).collect { (hint, tag) ->
|
||||
emit(Pair(hint, "$prefix:$tag"))
|
||||
}
|
||||
if (exactly)
|
||||
return@forEach
|
||||
val keywordMatches: Boolean =
|
||||
if (index == -1 || index >= tag.length - 1 || keyword.length > 2) {
|
||||
containsIgnoreSpace(tag, keyword)
|
||||
} else {
|
||||
containsIgnoreSpace(tag.substring(index + 1), keyword)
|
||||
} ?: tagGroups.forEach { (prefix, tags) ->
|
||||
if (prefix != NAMESPACE_PREFIX) {
|
||||
internalSuggestFlow(tags, keyword, translate, exactly).collect { (hint, tag) ->
|
||||
emit(Pair(hint, "$prefix:$tag"))
|
||||
}
|
||||
} else {
|
||||
internalSuggestFlow(tags, keyword, translate, exactly).collect { (hint, tag) ->
|
||||
emit(Pair(hint, "$tag:"))
|
||||
}
|
||||
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
|
||||
emit(Pair(hint, tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsIgnoreSpace(text: String?, key: String): Boolean {
|
||||
return text != null && text.replace(" ", "").contains(key.replace(" ", ""))
|
||||
}
|
||||
private fun String.removeSpace(): String = replace(" ", "")
|
||||
|
||||
private fun String.containsIgnoreSpace(other: String, ignoreCase: Boolean = true): Boolean =
|
||||
removeSpace().contains(other.removeSpace(), ignoreCase)
|
||||
|
||||
|
||||
private val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
|
||||
it["artist"] = "a"
|
||||
@@ -193,7 +218,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,175 @@
|
||||
/*
|
||||
* 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 = HOST_E + "exchange.php?t=gp"
|
||||
const val URL_HOME = HOST_E + "home.php"
|
||||
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,162 @@
|
||||
/*
|
||||
* 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 }
|
||||
|
||||
val isExHentai: Boolean
|
||||
get() = Settings.getGallerySite() == EhUrl.SITE_EX
|
||||
|
||||
@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)
|
||||
Settings.putSelectSite(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
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
object EhX509TrustManager : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate?> {
|
||||
return arrayOfNulls(0)
|
||||
}
|
||||
}
|
||||
@@ -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,150 +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<FavListUrlBuilder>() {
|
||||
|
||||
@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 setPrev(String prev) {
|
||||
mPrev = prev;
|
||||
}
|
||||
|
||||
public void setNext(String next) {
|
||||
mNext = next;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+10
-9
@@ -13,14 +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.util;
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
public class MutableBoolean {
|
||||
|
||||
public boolean value;
|
||||
|
||||
public MutableBoolean(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@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,104 @@
|
||||
/*
|
||||
* 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.data.GalleryInfo.Companion.S_LANGS
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
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,
|
||||
|
||||
@IgnoredOnParcel
|
||||
@JvmField
|
||||
var previewSet: PreviewSet? = null,
|
||||
) : AbstractGalleryInfo by galleryInfo, GalleryInfo {
|
||||
override fun generateSLang() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) {
|
||||
simpleLanguage = S_LANGS[index]
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LANGUAGES = arrayOf(
|
||||
"English",
|
||||
"Chinese",
|
||||
"Spanish",
|
||||
"Korean",
|
||||
"Russian",
|
||||
"French",
|
||||
"Portuguese",
|
||||
"Thai",
|
||||
"German",
|
||||
"Italian",
|
||||
"Vietnamese",
|
||||
"Polish",
|
||||
"Hungarian",
|
||||
"Dutch"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,43 @@
|
||||
/*
|
||||
* 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 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(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);
|
||||
}
|
||||
}
|
||||
+9
-11
@@ -13,16 +13,14 @@
|
||||
* 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 GalleryTagGroup(
|
||||
private val mTagList: ArrayList<String> = arrayListOf(),
|
||||
@JvmField
|
||||
var groupName: String? = null
|
||||
) : Parcelable, MutableList<String> by mTagList
|
||||
@@ -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,59 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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.imageUrl = mImageUrlList[index]
|
||||
galleryPreview.pageUrl = mPageUrlList[index]
|
||||
return galleryPreview
|
||||
}
|
||||
|
||||
override fun load(view: LoadImageView, gid: Long, index: Int) {
|
||||
view.resetClip()
|
||||
view.load(
|
||||
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,54 +24,36 @@ 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 mPrevGid = 0
|
||||
private var mNextGid = 0
|
||||
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()
|
||||
mPrevGid = parcel.readInt()
|
||||
mNextGid = parcel.readInt()
|
||||
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
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
mPrev = null
|
||||
mNext = null
|
||||
mJumpTo = null
|
||||
this.category = EhUtils.NONE
|
||||
mKeyword = null
|
||||
@@ -88,20 +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 setPrevGid(prevGid: Int) {
|
||||
mPrevGid = prevGid
|
||||
}
|
||||
|
||||
fun setNextGid(nextGid: Int) {
|
||||
mNextGid = nextGid
|
||||
fun setIndex(index: String?, isNext: Boolean = true) {
|
||||
mNext = index?.takeIf { isNext }
|
||||
mPrev = index?.takeUnless { isNext }
|
||||
}
|
||||
|
||||
fun setJumpTo(jumpTo: String?) {
|
||||
@@ -114,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
|
||||
mPrevGid = lub.mPrevGid
|
||||
mNextGid = lub.mNextGid
|
||||
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
|
||||
@@ -196,7 +142,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return
|
||||
}
|
||||
val querys = StringUtils.split(query, '&')
|
||||
val queries = StringUtils.split(query, '&')
|
||||
var category = 0
|
||||
var keyword: String? = null
|
||||
var enableAdvanceSearch = false
|
||||
@@ -206,7 +152,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
var enablePage = false
|
||||
var pageFrom = -1
|
||||
var pageTo = -1
|
||||
for (str in querys) {
|
||||
for (str in queries) {
|
||||
val index = str.indexOf('=')
|
||||
if (index < 0) {
|
||||
continue
|
||||
@@ -329,38 +275,38 @@ 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 <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
mSHash?.let {
|
||||
ub.addQuery("f_shash", it)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
ub.addQuery("seek", mJumpTo!!)
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
ub.addQuery("seek", it)
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
mPrev?.let {
|
||||
ub.addQuery("prev", it)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
mNext?.let {
|
||||
ub.addQuery("next", it)
|
||||
}
|
||||
// Search key
|
||||
// the settings of ub:UrlBuilder may be overwritten by following Advance search
|
||||
StringUtils.split(mKeyword, '|')?.forEachIndexed { idx, keyword ->
|
||||
val keyword = keyword.trim { it <= ' ' }
|
||||
when (idx) {
|
||||
0 -> keyword.takeIf { it.isNotEmpty() }?.let { ub.addQuery("f_search", encodeUTF8(it)) }
|
||||
else -> keyword.indexOf(':').takeIf { it >= 0 }?.run {
|
||||
val key = keyword.substring(0, this).trim { it <= ' ' }
|
||||
val value = keyword.substring(this + 1).trim { it <= ' ' }
|
||||
ub.addQuery(key, encodeUTF8(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Advance search
|
||||
if (advanceSearch != -1) {
|
||||
@@ -386,133 +332,59 @@ 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))
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
mPrev?.let {
|
||||
sb.append("?prev=").append(it)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
mNext?.let {
|
||||
sb.append("?next=").append(it)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
mJumpTo?.let {
|
||||
sb.append("&seek=").append(it)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
mPrev?.let {
|
||||
sb.append("?prev=").append(it)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
mNext?.let {
|
||||
sb.append("?next=").append(it)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
mJumpTo?.let {
|
||||
sb.append("&seek=").append(it)
|
||||
}
|
||||
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) {
|
||||
// Empty
|
||||
mKeyword.orEmpty().let {
|
||||
sb.append(encodeUTF8(it))
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("&p=").append(mNextGid)
|
||||
mJumpTo?.let {
|
||||
sb.append("&p=").append(it)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val url: String = if (mode == MODE_NORMAL) {
|
||||
EhUrl.getHost()
|
||||
} else {
|
||||
EhUrl.getWatchedUrl()
|
||||
}
|
||||
val ub = UrlBuilder(url)
|
||||
if (this.category != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
if (mKeyword != null) {
|
||||
val keyword = mKeyword!!.trim { it <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
}
|
||||
if (advanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1")
|
||||
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
|
||||
if (minRating != -1) {
|
||||
ub.addQuery("f_sr", "on")
|
||||
ub.addQuery("f_srdd", minRating)
|
||||
}
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
ub.addQuery("f_sp", "on")
|
||||
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
|
||||
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
|
||||
}
|
||||
}
|
||||
ub.build()
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected value: $mode")
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(mode)
|
||||
dest.writeInt(mPrevGid)
|
||||
dest.writeInt(mNextGid)
|
||||
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,
|
||||
@@ -532,16 +404,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,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 com.hippo.widget.LoadImageView
|
||||
import com.hippo.yorozuya.collect.IntList
|
||||
|
||||
class NormalPreviewSet(
|
||||
private var mPositionList: IntList = IntList(),
|
||||
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() {
|
||||
|
||||
fun addItem(
|
||||
position: Int, imageUrl: String, xOffset: Int, yOffset: Int, width: Int,
|
||||
height: Int, pageUrl: String
|
||||
) {
|
||||
mPositionList.add(position)
|
||||
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.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(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,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
abstract class PreviewSet {
|
||||
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)
|
||||
}
|
||||
+5
-9
@@ -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)
|
||||
}
|
||||
+2
-8
@@ -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)
|
||||
+2
-8
@@ -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")
|
||||
@@ -1,36 +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.exception;
|
||||
|
||||
public class ParseException extends EhException {
|
||||
|
||||
private final String mBody;
|
||||
|
||||
public ParseException(String detailMessage, String body) {
|
||||
super(detailMessage);
|
||||
mBody = body;
|
||||
}
|
||||
|
||||
public ParseException(String detailMessage, String body, Throwable cause) {
|
||||
super(detailMessage, cause);
|
||||
mBody = body;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return mBody;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -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,85 @@
|
||||
/*
|
||||
* 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.client.exception.NoHAtHClientException
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object ArchiveParser {
|
||||
private val PATTERN_ARCHIVE_URL =
|
||||
Regex("<strong>(.*)</strong>.*<a href=\"([^\"]*)\">Click Here To Start Downloading</a>")
|
||||
private val PATTERN_CURRENT_FUNDS =
|
||||
Regex("<p>([\\d,]+) GP \\[[^]]*] ([\\d,]+) Credits \\[[^]]*]</p>")
|
||||
private val PATTERN_HATH_FORM =
|
||||
Regex("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">")
|
||||
private val PATTERN_HATH_ARCHIVE =
|
||||
Regex("<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 =
|
||||
Regex("You must have a H@H client assigned to your account to use this feature\\.")
|
||||
|
||||
fun parse(body: String): Result? {
|
||||
val m = PATTERN_HATH_FORM.find(body) ?: return null
|
||||
val paramOr = m.groupValues[1]
|
||||
val archiveList = ArrayList<Archive>()
|
||||
Jsoup.parse(body).select("#db>div>div").forEach { element ->
|
||||
if (element.childrenSize() > 0 && !element.attr("style").contains("color:#CCCCCC")) {
|
||||
runCatching {
|
||||
val res = element.selectFirst("form>input")!!.attr("value")
|
||||
val name = element.selectFirst("form>div>input")!!.attr("value")
|
||||
val size = element.selectFirst("p>strong")!!.text()
|
||||
val cost = element.selectFirst("div>strong")!!.text().replace(",", "")
|
||||
Archive(res, name, size, cost, false)
|
||||
}.onSuccess {
|
||||
archiveList.add(it)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
PATTERN_HATH_ARCHIVE.findAll(body).forEach { matchResult ->
|
||||
val (res, name, size, cost) = matchResult.groupValues.slice(1..4)
|
||||
.map { ParserUtils.trim(it) }
|
||||
val item = Archive(res, name, size, cost, true)
|
||||
archiveList.add(item)
|
||||
}
|
||||
val result = Result(paramOr, archiveList, null)
|
||||
PATTERN_CURRENT_FUNDS.find(body)?.groupValues?.run {
|
||||
val fundsGP = ParserUtils.parseInt(get(1), 0)
|
||||
val fundsC = ParserUtils.parseInt(get(2), 0)
|
||||
val funds = HomeParser.Funds(fundsGP, fundsC)
|
||||
result.funds = funds
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(NoHAtHClientException::class)
|
||||
fun parseArchiveUrl(body: String): String? {
|
||||
if (PATTERN_NEED_HATH_CLIENT.containsMatchIn(body)) throw NoHAtHClientException("No H@H client")
|
||||
return Jsoup.parse(body).selectFirst("#continue>a[href]")
|
||||
?.let { it.attr("href") + "?start=1" }
|
||||
// TODO: Check more errors
|
||||
}
|
||||
|
||||
class Archive(
|
||||
val res: String,
|
||||
val name: String,
|
||||
val size: String,
|
||||
val cost: String,
|
||||
val isHAtH: Boolean
|
||||
)
|
||||
|
||||
class Result(val paramOr: String?, val archiveList: List<Archive>, var funds: HomeParser.Funds?)
|
||||
}
|
||||
@@ -31,22 +31,17 @@ import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FavoritesParser {
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
|
||||
|
||||
public static Result parse(String body) throws Exception {
|
||||
if (body.contains("This page requires you to log on.</p>")) {
|
||||
throw new EhException(GetText.getString(R.string.need_sign_in));
|
||||
}
|
||||
Document d = Jsoup.parse(body);
|
||||
|
||||
String[] catArray = new String[10];
|
||||
int[] countArray = new int[10];
|
||||
try {
|
||||
Document d = Jsoup.parse(body);
|
||||
Element ido = JsoupUtils.getElementByClass(d, "ido");
|
||||
//noinspection ConstantConditions
|
||||
Elements fps = ido.getElementsByClass("fp");
|
||||
@@ -64,24 +59,13 @@ public class FavoritesParser {
|
||||
throw new ParseException("Parse favorites error", body);
|
||||
}
|
||||
|
||||
Result re = new Result();
|
||||
try {
|
||||
Element prev = d.getElementById("uprev");
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
|
||||
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
GalleryListParser.Result result = GalleryListParser.parse(body);
|
||||
|
||||
Result re = new Result();
|
||||
re.catArray = catArray;
|
||||
re.countArray = countArray;
|
||||
re.prev = result.prev;
|
||||
re.next = result.next;
|
||||
re.galleryInfoList = result.galleryInfoList;
|
||||
|
||||
return re;
|
||||
@@ -90,8 +74,8 @@ public class FavoritesParser {
|
||||
public static class Result {
|
||||
public String[] catArray; // Size 10
|
||||
public int[] countArray; // Size 10
|
||||
public String prevPage;
|
||||
public String nextPage;
|
||||
public String prev;
|
||||
public String next;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.hippo.ehviewer.client.parser;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.exception.ParseException;
|
||||
import com.hippo.ehviewer.client.exception.EhException;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
@@ -26,7 +26,7 @@ import org.jsoup.nodes.Element;
|
||||
|
||||
public class ForumsParser {
|
||||
|
||||
public static String parse(String body) throws ParseException {
|
||||
public static String parse(String body) throws EhException {
|
||||
try {
|
||||
Document d = Jsoup.parse(body, EhUrl.URL_FORUMS);
|
||||
Element userlinks = d.getElementById("userlinks");
|
||||
@@ -34,7 +34,7 @@ public class ForumsParser {
|
||||
return child.attr("href");
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throw new ParseException("Parse forums error", body);
|
||||
throw new EhException("Not logged in");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,686 +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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryComment;
|
||||
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup;
|
||||
import com.hippo.ehviewer.client.data.LargePreviewSet;
|
||||
import com.hippo.ehviewer.client.data.NormalPreviewSet;
|
||||
import com.hippo.ehviewer.client.data.PreviewSet;
|
||||
import com.hippo.ehviewer.client.exception.EhException;
|
||||
import com.hippo.ehviewer.client.exception.OffensiveException;
|
||||
import com.hippo.ehviewer.client.exception.ParseException;
|
||||
import com.hippo.ehviewer.client.exception.PiningException;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.JsoupUtils;
|
||||
import com.hippo.util.MutableBoolean;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GalleryDetailParser {
|
||||
|
||||
private static final Pattern PATTERN_ERROR = Pattern.compile("<div class=\"d\">\n<p>([^<]+)</p>");
|
||||
private static final Pattern PATTERN_DETAIL = Pattern.compile("var gid = (\\d+);.+?var token = \"([a-f0-9]+)\";.+?var apiuid = ([\\-\\d]+);.+?var apikey = \"([a-f0-9]+)\";", Pattern.DOTALL);
|
||||
private static final Pattern PATTERN_TORRENT = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a");
|
||||
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>");
|
||||
private static final Pattern PATTERN_COVER = Pattern.compile("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
|
||||
private static final Pattern PATTERN_PREVIEW_PAGES = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
|
||||
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
|
||||
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
|
||||
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("/fav.png\\); background-position:0px -(\\d+)px");
|
||||
|
||||
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
|
||||
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
|
||||
|
||||
private static final DateTimeFormatter WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
|
||||
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||
private static final String OFFENSIVE_STRING =
|
||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
||||
private static final String PINING_STRING =
|
||||
"<p>This gallery is pining for the fjords.</p>";
|
||||
|
||||
private static final String[] LANGUAGES = {
|
||||
"English",
|
||||
"Chinese",
|
||||
"Spanish",
|
||||
"Korean",
|
||||
"Russian",
|
||||
"French",
|
||||
"Portuguese",
|
||||
"Thai",
|
||||
"German",
|
||||
"Italian",
|
||||
"Vietnamese",
|
||||
"Polish",
|
||||
"Hungarian",
|
||||
"Dutch",
|
||||
};
|
||||
|
||||
public static GalleryDetail parse(String body) throws EhException {
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
throw new OffensiveException();
|
||||
}
|
||||
|
||||
if (body.contains(PINING_STRING)) {
|
||||
throw new PiningException();
|
||||
}
|
||||
|
||||
// Error info
|
||||
Matcher m = PATTERN_ERROR.matcher(body);
|
||||
if (m.find()) {
|
||||
throw new EhException(m.group(1));
|
||||
}
|
||||
|
||||
GalleryDetail galleryDetail = new GalleryDetail();
|
||||
Document document = Jsoup.parse(body);
|
||||
parseDetail(galleryDetail, document, body);
|
||||
galleryDetail.tags = parseTagGroups(document);
|
||||
galleryDetail.comments = parseComments(document);
|
||||
galleryDetail.previewPages = parsePreviewPages(document, body);
|
||||
galleryDetail.previewSet = parsePreviewSet(document, body);
|
||||
|
||||
// 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];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return galleryDetail;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
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.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) {
|
||||
throw new ParseException("Can't parse gallery detail", body);
|
||||
}
|
||||
|
||||
matcher = PATTERN_TORRENT.matcher(body);
|
||||
if (matcher.find()) {
|
||||
gd.torrentUrl = StringUtils.unescapeXml(StringUtils.trim(matcher.group(1)));
|
||||
gd.torrentCount = NumberUtils.parseIntSafely(matcher.group(2), 0);
|
||||
} else {
|
||||
gd.torrentCount = 0;
|
||||
gd.torrentUrl = "";
|
||||
}
|
||||
|
||||
matcher = PATTERN_ARCHIVE.matcher(body);
|
||||
if (matcher.find()) {
|
||||
gd.archiveUrl = StringUtils.unescapeXml(StringUtils.trim(matcher.group(1)));
|
||||
} else {
|
||||
gd.archiveUrl = "";
|
||||
}
|
||||
|
||||
try {
|
||||
Element gm = JsoupUtils.getElementByClass(d, "gm");
|
||||
|
||||
// Thumb url
|
||||
Element gd1 = gm.getElementById("gd1");
|
||||
try {
|
||||
gd.thumb = parseCoverStyle(StringUtils.trim(gd1.child(0).attr("style")));
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
gd.thumb = "";
|
||||
}
|
||||
|
||||
// Title
|
||||
Element gn = gm.getElementById("gn");
|
||||
if (null != gn) {
|
||||
gd.title = StringUtils.trim(gn.text());
|
||||
} else {
|
||||
gd.title = "";
|
||||
}
|
||||
|
||||
// Jpn title
|
||||
Element gj = gm.getElementById("gj");
|
||||
if (null != gj) {
|
||||
gd.titleJpn = StringUtils.trim(gj.text());
|
||||
} else {
|
||||
gd.titleJpn = "";
|
||||
}
|
||||
|
||||
// Category
|
||||
Element gdc = gm.getElementById("gdc");
|
||||
try {
|
||||
Element ce = JsoupUtils.getElementByClass(gdc, "cn");
|
||||
if (ce == null) {
|
||||
ce = JsoupUtils.getElementByClass(gdc, "cs");
|
||||
}
|
||||
gd.category = EhUtils.getCategory(ce.text());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
gd.category = 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());
|
||||
} else {
|
||||
gd.uploader = "";
|
||||
}
|
||||
|
||||
Element gdd = gm.getElementById("gdd");
|
||||
gd.posted = "";
|
||||
gd.parent = "";
|
||||
gd.visible = "";
|
||||
gd.size = "";
|
||||
gd.pages = 0;
|
||||
gd.favoriteCount = 0;
|
||||
Elements es = gdd.child(0).child(0).children();
|
||||
for (int i = 0, n = es.size(); i < n; i++) {
|
||||
parseDetailInfo(gd, es.get(i));
|
||||
}
|
||||
|
||||
// Rating count
|
||||
Element rating_count = gm.getElementById("rating_count");
|
||||
if (null != rating_count) {
|
||||
gd.ratingCount = NumberUtils.parseIntSafely(
|
||||
StringUtils.trim(rating_count.text()), 0);
|
||||
} else {
|
||||
gd.ratingCount = 0;
|
||||
}
|
||||
|
||||
// Rating
|
||||
Element rating_label = gm.getElementById("rating_label");
|
||||
if (null != rating_label) {
|
||||
String ratingStr = StringUtils.trim(rating_label.text());
|
||||
if ("Not Yet Rated".equals(ratingStr)) {
|
||||
gd.rating = -1.0f;
|
||||
} else {
|
||||
int index = ratingStr.indexOf(' ');
|
||||
if (index == -1 || index >= ratingStr.length()) {
|
||||
gd.rating = 0f;
|
||||
} else {
|
||||
gd.rating = NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gd.rating = -1.0f;
|
||||
}
|
||||
|
||||
// isFavorited
|
||||
Element gdf = gm.getElementById("gdf");
|
||||
gd.isFavorited = false;
|
||||
if (gdf != null) {
|
||||
final String favoriteName = StringUtils.trim(gdf.text());
|
||||
if (favoriteName.equals("Add to Favorites")) {
|
||||
gd.favoriteName = null;
|
||||
} else {
|
||||
gd.isFavorited = true;
|
||||
gd.favoriteName = StringUtils.trim(gdf.text());
|
||||
|
||||
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
|
||||
if (matcher.find()) {
|
||||
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
|
||||
gd.favoriteSlot = -1;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throw new ParseException("Can't parse gallery detail", body);
|
||||
}
|
||||
|
||||
// newer version
|
||||
try {
|
||||
Element gnd = d.getElementById("gnd");
|
||||
if (gnd != null) {
|
||||
matcher = PATTERN_NEWER_DATE.matcher(body);
|
||||
ArrayList<String> dates = new ArrayList<>();
|
||||
while (matcher.find()) {
|
||||
dates.add(matcher.group(1));
|
||||
}
|
||||
Elements elements = gnd.select("a");
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
Element element = elements.get(i);
|
||||
GalleryInfo gi = new GalleryInfo();
|
||||
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);
|
||||
gd.newerVersions.add(gi);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// width:250px; height:356px; background:transparent url(https://exhentai.org/t/fe/1f/fe1fcfa9bf8fba2f03982eda0aa347cc9d6a6372-145921-1050-1492-jpg_250.jpg) 0 0 no-repeat
|
||||
private static String parseCoverStyle(String str) {
|
||||
Matcher matcher = PATTERN_COVER.matcher(str);
|
||||
if (matcher.find()) {
|
||||
return EhUtils.handleThumbUrlResolution(matcher.group(3));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDetailInfo(GalleryDetail gd, Element e) {
|
||||
Elements es = e.children();
|
||||
if (es.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = StringUtils.trim(es.get(0).text());
|
||||
String value = StringUtils.trim(es.get(1).ownText());
|
||||
if (key.startsWith("Posted")) {
|
||||
gd.posted = value;
|
||||
} else if (key.startsWith("Parent")) {
|
||||
Element a = es.get(1).children().first();
|
||||
if (a != null) {
|
||||
gd.parent = a.attr("href");
|
||||
}
|
||||
} else if (key.startsWith("Visible")) {
|
||||
gd.visible = value;
|
||||
} else if (key.startsWith("Language")) {
|
||||
gd.language = value;
|
||||
} else if (key.startsWith("File Size")) {
|
||||
gd.size = value;
|
||||
} else if (key.startsWith("Length")) {
|
||||
int index = value.indexOf(' ');
|
||||
if (index >= 0) {
|
||||
gd.pages = NumberUtils.parseIntSafely(value.substring(0, index), 1);
|
||||
} else {
|
||||
gd.pages = 1;
|
||||
}
|
||||
} else if (key.startsWith("Favorited")) {
|
||||
switch (value) {
|
||||
case "Never" -> gd.favoriteCount = 0;
|
||||
case "Once" -> gd.favoriteCount = 1;
|
||||
default -> {
|
||||
int index = value.indexOf(' ');
|
||||
if (index == -1) {
|
||||
gd.favoriteCount = 0;
|
||||
} else {
|
||||
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GalleryTagGroup parseTagGroup(Element element) {
|
||||
try {
|
||||
GalleryTagGroup group = new GalleryTagGroup();
|
||||
|
||||
String nameSpace = element.child(0).text();
|
||||
// Remove last ':'
|
||||
nameSpace = nameSpace.substring(0, nameSpace.length() - 1);
|
||||
group.groupName = nameSpace;
|
||||
|
||||
Elements tags = element.child(1).children();
|
||||
for (int i = 0, n = tags.size(); i < n; i++) {
|
||||
String tag = tags.get(i).text();
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
int index = tag.indexOf('|');
|
||||
if (index >= 0) {
|
||||
tag = tag.substring(0, index).trim();
|
||||
}
|
||||
group.addTag(tag);
|
||||
}
|
||||
|
||||
return group.size() > 0 ? group : null;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag groups with html parser
|
||||
*/
|
||||
@NonNull
|
||||
public static GalleryTagGroup[] parseTagGroups(Document document) {
|
||||
try {
|
||||
Element taglist = document.getElementById("taglist");
|
||||
assert taglist != null;
|
||||
Elements tagGroups = taglist.child(0).child(0).children();
|
||||
return parseTagGroups(tagGroups);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
return EMPTY_GALLERY_TAG_GROUP_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static GalleryTagGroup[] parseTagGroups(Elements trs) {
|
||||
try {
|
||||
List<GalleryTagGroup> list = new ArrayList<>(trs.size());
|
||||
for (int i = 0, n = trs.size(); i < n; i++) {
|
||||
GalleryTagGroup group = parseTagGroup(trs.get(i));
|
||||
if (null != group) {
|
||||
list.add(group);
|
||||
}
|
||||
}
|
||||
return list.toArray(new GalleryTagGroup[0]);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
return EMPTY_GALLERY_TAG_GROUP_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static GalleryComment parseComment(Element element) {
|
||||
try {
|
||||
GalleryComment comment = new GalleryComment();
|
||||
// Id
|
||||
Element a = element.previousElementSibling();
|
||||
String name = a.attr("name");
|
||||
comment.id = Integer.parseInt(StringUtils.trim(name).substring(1));
|
||||
// Editable, vote up and vote down
|
||||
Element c4 = JsoupUtils.getElementByClass(element, "c4");
|
||||
if (null != c4) {
|
||||
if ("Uploader Comment".equals(c4.text())) {
|
||||
comment.uploader = true;
|
||||
}
|
||||
for (Element e : c4.children()) {
|
||||
switch (e.text()) {
|
||||
case "Vote+" -> {
|
||||
comment.voteUpAble = true;
|
||||
comment.voteUpEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
}
|
||||
case "Vote-" -> {
|
||||
comment.voteDownAble = true;
|
||||
comment.voteDownEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
}
|
||||
case "Edit" -> comment.editable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vote state
|
||||
Element c7 = JsoupUtils.getElementByClass(element, "c7");
|
||||
if (null != c7) {
|
||||
comment.voteState = StringUtils.trim(c7.text());
|
||||
}
|
||||
// Score
|
||||
Element c5 = JsoupUtils.getElementByClass(element, "c5");
|
||||
if (null != c5) {
|
||||
Elements es = c5.children();
|
||||
if (!es.isEmpty()) {
|
||||
comment.score = NumberUtils.parseIntSafely(StringUtils.trim(es.get(0).text()), 0);
|
||||
}
|
||||
}
|
||||
// time
|
||||
Element c3 = JsoupUtils.getElementByClass(element, "c3");
|
||||
String temp = c3.ownText();
|
||||
temp = temp.substring("Posted on ".length(), temp.length() - " by:".length());
|
||||
comment.time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
// user
|
||||
comment.user = c3.child(0).text();
|
||||
// comment
|
||||
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
|
||||
// last edited
|
||||
Element c8 = JsoupUtils.getElementByClass(element, "c8");
|
||||
if (c8 != null) {
|
||||
Element e = c8.children().first();
|
||||
if (e != null) {
|
||||
comment.lastEdited = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comments with html parser
|
||||
*/
|
||||
@NonNull
|
||||
public static GalleryCommentList parseComments(Document document) {
|
||||
try {
|
||||
Element cdiv = document.getElementById("cdiv");
|
||||
assert cdiv != null;
|
||||
Elements c1s = cdiv.getElementsByClass("c1");
|
||||
|
||||
List<GalleryComment> list = new ArrayList<>(c1s.size());
|
||||
for (int i = 0, n = c1s.size(); i < n; i++) {
|
||||
GalleryComment comment = parseComment(c1s.get(i));
|
||||
if (null != comment) {
|
||||
list.add(comment);
|
||||
}
|
||||
}
|
||||
|
||||
Element chd = cdiv.getElementById("chd");
|
||||
MutableBoolean hasMore = new MutableBoolean(false);
|
||||
assert chd != null;
|
||||
NodeTraversor.traverse(new NodeVisitor() {
|
||||
@Override
|
||||
public void head(@NonNull Node node, int depth) {
|
||||
if (node instanceof Element && ((Element) node).text().equals("click to show all")) {
|
||||
hasMore.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(@NonNull Node node, int depth) {
|
||||
}
|
||||
}, chd);
|
||||
|
||||
return new GalleryCommentList(list.toArray(new GalleryComment[0]), hasMore.value);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
return EMPTY_GALLERY_COMMENT_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with html parser
|
||||
*/
|
||||
public static int parsePreviewPages(Document document, String body) throws ParseException {
|
||||
try {
|
||||
Element ptt = document.getElementsByClass("ptt").first();
|
||||
assert ptt != null;
|
||||
Elements elements = ptt.child(0).child(0).children();
|
||||
return Integer.parseInt(elements.get(elements.size() - 2).text());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
throw new ParseException("Can't parse preview pages", body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with regular expressions
|
||||
*/
|
||||
public static int parsePreviewPages(String body) throws ParseException {
|
||||
Matcher m = PATTERN_PREVIEW_PAGES.matcher(body);
|
||||
int previewPages = -1;
|
||||
if (m.find()) {
|
||||
previewPages = ParserUtils.parseInt(m.group(1), -1);
|
||||
}
|
||||
|
||||
if (previewPages <= 0) {
|
||||
throw new ParseException("Parse preview page count error", body);
|
||||
}
|
||||
|
||||
return previewPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse pages with regular expressions
|
||||
*/
|
||||
public static int parsePages(String body) throws ParseException {
|
||||
int pages = -1;
|
||||
|
||||
Matcher m = PATTERN_PAGES.matcher(body);
|
||||
if (m.find()) {
|
||||
pages = ParserUtils.parseInt(m.group(1), -1);
|
||||
}
|
||||
|
||||
if (pages < 0) {
|
||||
throw new ParseException("Parse pages error", body);
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
public static PreviewSet parsePreviewSet(Document d, String body) throws ParseException {
|
||||
try {
|
||||
return parseLargePreviewSet(d, body);
|
||||
} catch (ParseException e) {
|
||||
return parseNormalPreviewSet(body);
|
||||
}
|
||||
}
|
||||
|
||||
public static PreviewSet parsePreviewSet(String body) throws ParseException {
|
||||
try {
|
||||
return parseLargePreviewSet(body);
|
||||
} catch (ParseException e) {
|
||||
return parseNormalPreviewSet(body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse large previews with regular expressions
|
||||
*/
|
||||
private static LargePreviewSet parseLargePreviewSet(Document d, String body) throws ParseException {
|
||||
try {
|
||||
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
||||
Element gdt = d.getElementById("gdt");
|
||||
assert gdt != null;
|
||||
Elements gdtls = gdt.getElementsByClass("gdtl");
|
||||
int n = gdtls.size();
|
||||
if (n == 0) {
|
||||
throw new ParseException("Can't parse large preview", body);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
Element element = gdtls.get(i).child(0);
|
||||
String pageUrl = element.attr("href");
|
||||
element = element.child(0);
|
||||
String imageUrl = element.attr("src");
|
||||
int index = Integer.parseInt(element.attr("alt")) - 1;
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
return largePreviewSet;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
throw new ParseException("Can't parse large preview", body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse large previews with regular expressions
|
||||
*/
|
||||
private static LargePreviewSet parseLargePreviewSet(String body) throws ParseException {
|
||||
Matcher m = PATTERN_LARGE_PREVIEW.matcher(body);
|
||||
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
||||
|
||||
while (m.find()) {
|
||||
int index = ParserUtils.parseInt(m.group(2), 0) - 1;
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
String imageUrl = ParserUtils.trim(m.group(3));
|
||||
String pageUrl = ParserUtils.trim(m.group(1));
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
|
||||
if (largePreviewSet.size() == 0) {
|
||||
throw new ParseException("Can't parse large preview", body);
|
||||
}
|
||||
|
||||
return largePreviewSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse normal previews with regular expressions
|
||||
*/
|
||||
private static NormalPreviewSet parseNormalPreviewSet(String body) throws ParseException {
|
||||
Matcher m = PATTERN_NORMAL_PREVIEW.matcher(body);
|
||||
NormalPreviewSet normalPreviewSet = new NormalPreviewSet();
|
||||
while (m.find()) {
|
||||
int position = ParserUtils.parseInt(m.group(6), 0) - 1;
|
||||
if (position < 0) {
|
||||
continue;
|
||||
}
|
||||
String imageUrl = ParserUtils.trim(m.group(3));
|
||||
int xOffset = ParserUtils.parseInt(m.group(4), 0);
|
||||
int yOffset = 0;
|
||||
int width = ParserUtils.parseInt(m.group(1), 0);
|
||||
if (width <= 0) {
|
||||
continue;
|
||||
}
|
||||
int height = ParserUtils.parseInt(m.group(2), 0);
|
||||
if (height <= 0) {
|
||||
continue;
|
||||
}
|
||||
String pageUrl = ParserUtils.trim(m.group(5));
|
||||
normalPreviewSet.addItem(position, imageUrl, xOffset, yOffset, width, height, pageUrl);
|
||||
}
|
||||
|
||||
if (normalPreviewSet.size() == 0) {
|
||||
throw new ParseException("Can't parse normal preview", body);
|
||||
}
|
||||
|
||||
return normalPreviewSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
/*
|
||||
* 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.EhDB
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.EhUtils.getCategory
|
||||
import com.hippo.ehviewer.client.EhUtils.handleThumbUrlResolution
|
||||
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
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.LargePreviewSet
|
||||
import com.hippo.ehviewer.client.data.NormalPreviewSet
|
||||
import com.hippo.ehviewer.client.data.PreviewSet
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.OffensiveException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import com.hippo.ehviewer.client.exception.PiningException
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.JsoupUtils
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
import com.hippo.yorozuya.StringUtils
|
||||
import com.hippo.yorozuya.unescapeXml
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Node
|
||||
import org.jsoup.select.Elements
|
||||
import org.jsoup.select.NodeTraversor
|
||||
import org.jsoup.select.NodeVisitor
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
object GalleryDetailParser {
|
||||
private val PATTERN_ERROR = Regex("<div class=\"d\">\n<p>([^<]+)</p>")
|
||||
private val PATTERN_DETAIL = Regex(
|
||||
"var gid = (\\d+);.+?var token = \"([a-f0-9]+)\";.+?var apiuid = ([\\-\\d]+);.+?var apikey = \"([a-f0-9]+)\";",
|
||||
RegexOption.DOT_MATCHES_ALL
|
||||
)
|
||||
private val PATTERN_TORRENT =
|
||||
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a")
|
||||
private val PATTERN_ARCHIVE =
|
||||
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>")
|
||||
private val PATTERN_COVER = Regex("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)")
|
||||
private val PATTERN_PAGES =
|
||||
Regex("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>")
|
||||
private val PATTERN_PREVIEW_PAGES =
|
||||
Regex("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>")
|
||||
private val PATTERN_NORMAL_PREVIEW =
|
||||
Regex("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"")
|
||||
private val PATTERN_LARGE_PREVIEW =
|
||||
Regex("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"")
|
||||
private val PATTERN_NEWER_DATE = Regex(", added (.+?)<br />")
|
||||
private val PATTERN_FAVORITE_SLOT =
|
||||
Regex("/fav.png\\); background-position:0px -(\\d+)px")
|
||||
private val EMPTY_GALLERY_TAG_GROUP_ARRAY = arrayOf<GalleryTagGroup>()
|
||||
private val EMPTY_GALLERY_COMMENT_ARRAY = GalleryCommentList(arrayOf(), false)
|
||||
private val WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
|
||||
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC)
|
||||
private const val OFFENSIVE_STRING =
|
||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>"
|
||||
private const val PINING_STRING = "<p>This gallery is pining for the fjords.</p>"
|
||||
|
||||
@Throws(EhException::class)
|
||||
fun parse(body: String): GalleryDetail {
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
throw OffensiveException()
|
||||
}
|
||||
if (body.contains(PINING_STRING)) {
|
||||
throw PiningException()
|
||||
}
|
||||
|
||||
// Error info
|
||||
PATTERN_ERROR.find(body)?.run { throw EhException(groupValues[1]) }
|
||||
val galleryDetail = GalleryDetail()
|
||||
val document = Jsoup.parse(body)
|
||||
parseDetail(galleryDetail, document, body)
|
||||
galleryDetail.tags = parseTagGroups(document)
|
||||
galleryDetail.comments = parseComments(document)
|
||||
galleryDetail.previewPages = parsePreviewPages(document, body)
|
||||
galleryDetail.previewSet = parsePreviewSet(document, body)
|
||||
|
||||
// Generate simpleLanguage for local favorites
|
||||
galleryDetail.generateSLang()
|
||||
return galleryDetail
|
||||
}
|
||||
|
||||
@Throws(ParseException::class)
|
||||
private fun parseDetail(gd: GalleryDetail, d: Document, body: String) {
|
||||
PATTERN_DETAIL.find(body)?.apply {
|
||||
gd.gid = groupValues[1].toLongOrNull() ?: -1L
|
||||
gd.token = groupValues[2]
|
||||
gd.apiUid = groupValues[3].toLongOrNull() ?: -1L
|
||||
gd.apiKey = groupValues[4]
|
||||
} ?: throw ParseException("Can't parse gallery detail", body)
|
||||
if (gd.gid == -1L) {
|
||||
throw ParseException("Can't parse gallery detail", body)
|
||||
}
|
||||
PATTERN_TORRENT.find(body)?.run {
|
||||
gd.torrentUrl = groupValues[1].trim().unescapeXml()
|
||||
gd.torrentCount = groupValues[2].toIntOrNull() ?: 0
|
||||
}
|
||||
PATTERN_ARCHIVE.find(body)?.run {
|
||||
gd.archiveUrl = groupValues[1].trim().unescapeXml()
|
||||
}
|
||||
try {
|
||||
val gm = d.getElementsByClass("gm")[0]
|
||||
// Thumb url
|
||||
gm.getElementById("gd1")?.child(0)?.attr("style")?.trim()?.let {
|
||||
gd.thumb = PATTERN_COVER.find(it)?.run {
|
||||
handleThumbUrlResolution(groupValues[3])
|
||||
}
|
||||
}
|
||||
|
||||
gd.title = gm.getElementById("gn")?.text()?.trim()
|
||||
gd.titleJpn = gm.getElementById("gj")?.text()?.trim()
|
||||
|
||||
// Category
|
||||
val gdc = gm.getElementById("gdc")
|
||||
try {
|
||||
var ce = JsoupUtils.getElementByClass(gdc, "cn")
|
||||
if (ce == null) {
|
||||
ce = JsoupUtils.getElementByClass(gdc, "cs")
|
||||
}
|
||||
gd.category = getCategory(ce!!.text())
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
gd.category = EhUtils.UNKNOWN
|
||||
}
|
||||
|
||||
// Uploader
|
||||
val gdn = gm.getElementById("gdn")
|
||||
if (null != gdn) {
|
||||
gd.disowned = gdn.attr("style").contains("opacity:0.5")
|
||||
gd.uploader = StringUtils.trim(gdn.text())
|
||||
} else {
|
||||
gd.uploader = ""
|
||||
}
|
||||
val gdd = gm.getElementById("gdd")
|
||||
gd.posted = ""
|
||||
gd.parent = ""
|
||||
gd.visible = ""
|
||||
gd.size = ""
|
||||
gd.pages = 0
|
||||
gd.favoriteCount = 0
|
||||
val es = gdd!!.child(0).child(0).children()
|
||||
es.forEach {
|
||||
parseDetailInfo(gd, it)
|
||||
}
|
||||
|
||||
// Rating count
|
||||
val ratingCount = gm.getElementById("rating_count")
|
||||
if (null != ratingCount) {
|
||||
gd.ratingCount = NumberUtils.parseIntSafely(
|
||||
StringUtils.trim(ratingCount.text()), 0
|
||||
)
|
||||
} else {
|
||||
gd.ratingCount = 0
|
||||
}
|
||||
|
||||
// Rating
|
||||
val ratingLabel = gm.getElementById("rating_label")
|
||||
if (null != ratingLabel) {
|
||||
val ratingStr = StringUtils.trim(ratingLabel.text())
|
||||
if ("Not Yet Rated" == ratingStr) {
|
||||
gd.rating = -1.0f
|
||||
} else {
|
||||
val index = ratingStr.indexOf(' ')
|
||||
if (index == -1 || index >= ratingStr.length) {
|
||||
gd.rating = 0f
|
||||
} else {
|
||||
gd.rating = NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gd.rating = -1.0f
|
||||
}
|
||||
|
||||
// isFavorited
|
||||
val gdf = gm.getElementById("gdf")
|
||||
gd.isFavorited = false
|
||||
if (gdf != null) {
|
||||
val favoriteName = StringUtils.trim(gdf.text())
|
||||
if (favoriteName == "Add to Favorites") {
|
||||
gd.favoriteName = null
|
||||
} else {
|
||||
gd.isFavorited = true
|
||||
gd.favoriteName = StringUtils.trim(gdf.text())
|
||||
PATTERN_FAVORITE_SLOT.find(body)?.run {
|
||||
gd.favoriteSlot = ((groupValues[1].toIntOrNull() ?: 2) - 2) / 19
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
|
||||
gd.favoriteSlot = -1
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
throw ParseException("Can't parse gallery detail", body)
|
||||
}
|
||||
|
||||
// newer version
|
||||
d.getElementById("gnd")?.run {
|
||||
val dates = PATTERN_NEWER_DATE.findAll(body).map { it.groupValues[1] }.toList()
|
||||
select("a").forEachIndexed { index, element ->
|
||||
val gi = BaseGalleryInfo()
|
||||
val 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[index]
|
||||
gd.newerVersions.add(gi)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDetailInfo(gd: GalleryDetail, e: Element) {
|
||||
val es = e.children()
|
||||
if (es.size < 2) {
|
||||
return
|
||||
}
|
||||
val key = StringUtils.trim(es[0].text())
|
||||
val value = StringUtils.trim(es[1].ownText())
|
||||
if (key.startsWith("Posted")) {
|
||||
gd.posted = value
|
||||
} else if (key.startsWith("Parent")) {
|
||||
val a = es[1].children().first()
|
||||
if (a != null) {
|
||||
gd.parent = a.attr("href")
|
||||
}
|
||||
} else if (key.startsWith("Visible")) {
|
||||
gd.visible = value
|
||||
} else if (key.startsWith("Language")) {
|
||||
gd.language = value
|
||||
} else if (key.startsWith("File Size")) {
|
||||
gd.size = value
|
||||
} else if (key.startsWith("Length")) {
|
||||
val index = value.indexOf(' ')
|
||||
if (index >= 0) {
|
||||
gd.pages = NumberUtils.parseIntSafely(value.substring(0, index), 1)
|
||||
} else {
|
||||
gd.pages = 1
|
||||
}
|
||||
} else if (key.startsWith("Favorited")) {
|
||||
when (value) {
|
||||
"Never" -> gd.favoriteCount = 0
|
||||
"Once" -> gd.favoriteCount = 1
|
||||
else -> {
|
||||
val index = value.indexOf(' ')
|
||||
if (index == -1) {
|
||||
gd.favoriteCount = 0
|
||||
} else {
|
||||
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTagGroup(element: Element): GalleryTagGroup? {
|
||||
return try {
|
||||
val group = GalleryTagGroup()
|
||||
var nameSpace = element.child(0).text()
|
||||
// Remove last ':'
|
||||
nameSpace = nameSpace.substring(0, nameSpace.length - 1)
|
||||
group.groupName = nameSpace
|
||||
val tags = element.child(1).children()
|
||||
tags.forEach {
|
||||
var tag = it.text()
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
val index = tag.indexOf('|')
|
||||
if (index >= 0) {
|
||||
tag = tag.substring(0, index).trim()
|
||||
}
|
||||
if (it.className() == "gtw") {
|
||||
tag = "_$tag" // weak tag
|
||||
}
|
||||
group.add(tag)
|
||||
}
|
||||
if (group.size > 0) group else null
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag groups with html parser
|
||||
*/
|
||||
private fun parseTagGroups(document: Document): Array<GalleryTagGroup> {
|
||||
return try {
|
||||
val taglist = document.getElementById("taglist")!!
|
||||
val tagGroups = taglist.child(0).child(0).children()
|
||||
parseTagGroups(tagGroups)
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
EMPTY_GALLERY_TAG_GROUP_ARRAY
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTagGroups(trs: Elements): Array<GalleryTagGroup> {
|
||||
return try {
|
||||
trs.mapNotNull { parseTagGroup(it) }.toTypedArray()
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
EMPTY_GALLERY_TAG_GROUP_ARRAY
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseComment(element: Element): GalleryComment? {
|
||||
return try {
|
||||
val comment = GalleryComment()
|
||||
// Id
|
||||
val a = element.previousElementSibling()
|
||||
val name = a!!.attr("name")
|
||||
comment.id = StringUtils.trim(name).substring(1).toInt().toLong()
|
||||
// Editable, vote up and vote down
|
||||
val c4 = JsoupUtils.getElementByClass(element, "c4")
|
||||
if (null != c4) {
|
||||
if ("Uploader Comment" == c4.text()) {
|
||||
comment.uploader = true
|
||||
}
|
||||
for (e in c4.children()) {
|
||||
when (e.text()) {
|
||||
"Vote+" -> {
|
||||
comment.voteUpAble = true
|
||||
comment.voteUpEd = StringUtils.trim(e.attr("style")).isNotEmpty()
|
||||
}
|
||||
|
||||
"Vote-" -> {
|
||||
comment.voteDownAble = true
|
||||
comment.voteDownEd = StringUtils.trim(e.attr("style")).isNotEmpty()
|
||||
}
|
||||
|
||||
"Edit" -> comment.editable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vote state
|
||||
val c7 = JsoupUtils.getElementByClass(element, "c7")
|
||||
if (null != c7) {
|
||||
comment.voteState = StringUtils.trim(c7.text())
|
||||
}
|
||||
// Score
|
||||
val c5 = JsoupUtils.getElementByClass(element, "c5")
|
||||
if (null != c5) {
|
||||
val es = c5.children()
|
||||
if (!es.isEmpty()) {
|
||||
comment.score = NumberUtils.parseIntSafely(
|
||||
StringUtils.trim(es[0].text()), 0
|
||||
)
|
||||
}
|
||||
}
|
||||
// time
|
||||
val c3 = JsoupUtils.getElementByClass(element, "c3")
|
||||
var temp = c3!!.ownText()
|
||||
if (temp.endsWith(':')) {
|
||||
temp = temp.substring("Posted on ".length, temp.length - " by:".length)
|
||||
// user
|
||||
comment.user = c3.child(0).text()
|
||||
} else {
|
||||
temp = temp.substring("Posted on ".length)
|
||||
}
|
||||
comment.time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli()
|
||||
// comment
|
||||
val c6 = JsoupUtils.getElementByClass(element, "c6")
|
||||
// fix underline support
|
||||
for (e in c6!!.children()) {
|
||||
if ("span" == e.tagName() && "text-decoration:underline;" == e.attr("style")) {
|
||||
e.tagName("u")
|
||||
}
|
||||
}
|
||||
comment.comment = c6.html()
|
||||
// filter comment
|
||||
if (!comment.uploader) {
|
||||
val sEhFilter = EhFilter
|
||||
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
// last edited
|
||||
val c8 = JsoupUtils.getElementByClass(element, "c8")
|
||||
if (c8 != null) {
|
||||
val e = c8.children().first()
|
||||
if (e != null) {
|
||||
comment.lastEdited =
|
||||
Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli()
|
||||
}
|
||||
}
|
||||
comment
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comments with html parser
|
||||
*/
|
||||
fun parseComments(document: Document): GalleryCommentList {
|
||||
return try {
|
||||
val cdiv = document.getElementById("cdiv")!!
|
||||
val c1s = cdiv.getElementsByClass("c1")
|
||||
val list = c1s.mapNotNull { parseComment(it) }
|
||||
val chd = cdiv.getElementById("chd")
|
||||
var hasMore = false
|
||||
NodeTraversor.traverse(object : NodeVisitor {
|
||||
override fun head(node: Node, depth: Int) {
|
||||
if (node is Element && node.text() == "click to show all") {
|
||||
hasMore = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun tail(node: Node, depth: Int) {}
|
||||
}, chd!!)
|
||||
GalleryCommentList(list.toTypedArray(), hasMore)
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
EMPTY_GALLERY_COMMENT_ARRAY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with html parser
|
||||
*/
|
||||
@Throws(ParseException::class)
|
||||
fun parsePreviewPages(document: Document, body: String?): Int {
|
||||
return try {
|
||||
val ptt = document.getElementsByClass("ptt").first()!!
|
||||
val elements = ptt.child(0).child(0).children()
|
||||
elements[elements.size - 2].text().toInt()
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
e.printStackTrace()
|
||||
throw ParseException("Can't parse preview pages", body!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with regular expressions
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ParseException::class)
|
||||
fun parsePreviewPages(body: String): Int {
|
||||
return PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse preview page count error", body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse pages with regular expressions
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ParseException::class)
|
||||
fun parsePages(body: String): Int {
|
||||
return PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse pages error", body)
|
||||
}
|
||||
|
||||
@Throws(ParseException::class)
|
||||
fun parsePreviewSet(d: Document, body: String): PreviewSet {
|
||||
return try {
|
||||
parseLargePreviewSet(d, body)
|
||||
} catch (e: ParseException) {
|
||||
parseNormalPreviewSet(body)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(ParseException::class)
|
||||
fun parsePreviewSet(body: String): PreviewSet {
|
||||
return try {
|
||||
parseLargePreviewSet(body)
|
||||
} catch (e: ParseException) {
|
||||
parseNormalPreviewSet(body)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse large previews with regular expressions
|
||||
*/
|
||||
@Throws(ParseException::class)
|
||||
private fun parseLargePreviewSet(d: Document, body: String): LargePreviewSet {
|
||||
return try {
|
||||
val largePreviewSet = LargePreviewSet()
|
||||
val gdt = d.getElementById("gdt")!!
|
||||
val gdtls = gdt.getElementsByClass("gdtl")
|
||||
val n = gdtls.size
|
||||
if (n == 0) {
|
||||
throw ParseException("Can't parse large preview", body)
|
||||
}
|
||||
for (i in 0 until n) {
|
||||
var element = gdtls[i].child(0)
|
||||
val pageUrl = element.attr("href")
|
||||
element = element.child(0)
|
||||
val imageUrl = element.attr("src")
|
||||
val index = element.attr("alt").toInt() - 1
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl)
|
||||
}
|
||||
largePreviewSet
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
throw ParseException("Can't parse large preview", body)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse large previews with regular expressions
|
||||
*/
|
||||
@Throws(ParseException::class)
|
||||
private fun parseLargePreviewSet(body: String): LargePreviewSet {
|
||||
val largePreviewSet = LargePreviewSet()
|
||||
PATTERN_LARGE_PREVIEW.findAll(body).forEach {
|
||||
val index = (it.groupValues[2].toIntOrNull() ?: return@forEach) - 1
|
||||
val imageUrl = it.groupValues[3].trim()
|
||||
val pageUrl = it.groupValues[1].trim()
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl)
|
||||
}
|
||||
if (largePreviewSet.size() == 0) {
|
||||
throw ParseException("Can't parse large preview", body)
|
||||
}
|
||||
return largePreviewSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse normal previews with regular expressions
|
||||
*/
|
||||
@Throws(ParseException::class)
|
||||
private fun parseNormalPreviewSet(body: String): NormalPreviewSet {
|
||||
val normalPreviewSet = NormalPreviewSet()
|
||||
PATTERN_NORMAL_PREVIEW.findAll(body).forEach {
|
||||
val position = (it.groupValues[6].toIntOrNull() ?: return@forEach) - 1
|
||||
val imageUrl = it.groupValues[3].trim()
|
||||
val xOffset = it.groupValues[4].toIntOrNull() ?: 0
|
||||
val yOffset = 0
|
||||
val width = it.groupValues[1].toIntOrNull() ?: return@forEach
|
||||
val height = it.groupValues[2].toIntOrNull() ?: return@forEach
|
||||
val pageUrl = it.groupValues[5].trim()
|
||||
normalPreviewSet.addItem(position, imageUrl, xOffset, yOffset, width, height, pageUrl)
|
||||
}
|
||||
if (normalPreviewSet.size() == 0) {
|
||||
throw ParseException("Can't parse normal preview", body)
|
||||
}
|
||||
return normalPreviewSet
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -50,8 +51,9 @@ public class GalleryListParser {
|
||||
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
|
||||
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("p=(\\d+)");
|
||||
private static final Pattern PATTERN_PREV = Pattern.compile("prev=(\\d+(-\\d+)?)");
|
||||
private static final Pattern PATTERN_NEXT = Pattern.compile("next=(\\d+(-\\d+)?)");
|
||||
|
||||
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
|
||||
new String[]{"0", "0", "0"},
|
||||
@@ -109,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");
|
||||
@@ -124,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,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;
|
||||
}
|
||||
|
||||
@@ -148,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
|
||||
@@ -169,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");
|
||||
@@ -184,7 +186,7 @@ public class GalleryListParser {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
url = null;
|
||||
}
|
||||
gi.thumb = EhUtils.handleThumbUrlResolution(url);
|
||||
gi.setThumb(EhUtils.handleThumbUrlResolution(url));
|
||||
}
|
||||
|
||||
// Pages
|
||||
@@ -192,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");
|
||||
@@ -208,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
|
||||
@@ -254,19 +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();
|
||||
if (a != null) {
|
||||
gi.uploader = a.text().trim();
|
||||
} else {
|
||||
gi.uploader = div.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,28 +294,44 @@ public class GalleryListParser {
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
Matcher matcherPrev = PATTERN_PREV.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT.matcher(next.attr("href"));
|
||||
if (matcherPrev.find())
|
||||
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
result.prev = matcherPrev.group(1);
|
||||
if (matcherNext.find())
|
||||
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
result.next = matcherNext.group(1);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
||||
if (body.contains("No hits found</p>")) {
|
||||
result.founds = 0;
|
||||
//noinspection unchecked
|
||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||
return result;
|
||||
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
||||
result.founds = 1;
|
||||
} else {
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
try { // For toplists
|
||||
Element ptt = d.getElementsByClass("ptt").first();
|
||||
if (ptt != null) {
|
||||
Elements es = ptt.child(0).child(0).children();
|
||||
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
|
||||
|
||||
Element e = es.get(es.size() - 1);
|
||||
if (e != null) {
|
||||
e = e.children().first();
|
||||
if (e != null) {
|
||||
String href = e.attr("href");
|
||||
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
|
||||
if (matcher.find()) {
|
||||
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
Element itg = d.getElementsByClass("itg").first();
|
||||
Elements es;
|
||||
@@ -350,9 +364,10 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public int founds;
|
||||
public int prevGid;
|
||||
public int nextGid;
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public String prev;
|
||||
public String next;
|
||||
public boolean noWatchedTags;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object HomeParser {
|
||||
private val PATTERN_FUNDS =
|
||||
Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
|
||||
private const val RESET_SUCCEED = "Image limit was successfully reset."
|
||||
|
||||
fun parse(body: String): Limits {
|
||||
Jsoup.parse(body).selectFirst("div.homebox")?.let {
|
||||
val es = it.select("p > strong")
|
||||
if (es.size == 3) {
|
||||
val current = ParserUtils.parseInt(es[0].text(), 0)
|
||||
val maximum = ParserUtils.parseInt(es[1].text(), 0)
|
||||
val resetCost = ParserUtils.parseInt(es[2].text(), 0)
|
||||
return Limits(current, maximum, resetCost)
|
||||
}
|
||||
}
|
||||
throw ParseException("Parse image limits error", body)
|
||||
}
|
||||
|
||||
fun parseResetLimits(body: String): Limits? {
|
||||
return if (body.contains(RESET_SUCCEED)){
|
||||
null
|
||||
} else {
|
||||
parse(body)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseFunds(body: String): Funds {
|
||||
PATTERN_FUNDS.find(body)?.groupValues?.run {
|
||||
val fundsC = ParserUtils.parseInt(get(1), 0)
|
||||
val fundsGP = ParserUtils.parseInt(get(2), 0) * 1000
|
||||
return Funds(fundsGP, fundsC)
|
||||
}
|
||||
throw ParseException("Parse funds error", body)
|
||||
}
|
||||
|
||||
data class Limits(val current: Int = 0, val maximum: Int, val resetCost: Int = 0)
|
||||
data class Funds(val fundsGP: Int, val fundsC: Int)
|
||||
class Result(val limits: Limits, val funds: Funds)
|
||||
}
|
||||
@@ -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\"> <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);
|
||||
}
|
||||
Loaded 100 of 370 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user