Compare commits
734
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
dd982c6cb9 | ||
|
|
927a011c9d | ||
|
|
ad0ad47a40 | ||
|
|
71a424c15d | ||
|
|
145b7049a8 | ||
|
|
323d258bc2 | ||
|
|
56ba52d7b9 | ||
|
|
54bd6f6ed8 | ||
|
|
f06167ba4b | ||
|
|
60438ae1ed | ||
|
|
1b87750b87 | ||
|
|
40cdc566c8 | ||
|
|
428ae07e7f | ||
|
|
481ad22b53 | ||
|
|
65f58b93ed | ||
|
|
45411fa14d | ||
|
|
8ba1ba4fb1 | ||
|
|
8427e1b3a5 | ||
|
|
32f0f2a0cf | ||
|
|
8b94997cde | ||
|
|
bd2d54f774 | ||
|
|
34be285510 | ||
|
|
7b906f3c3e | ||
|
|
82ba22cda2 | ||
|
|
b1a9601ae9 | ||
|
|
7c1a297a99 | ||
|
|
a8195c0e7c | ||
|
|
08713e3922 | ||
|
|
0485d52e3e | ||
|
|
8b1723e19b | ||
|
|
9cb683dc55 | ||
|
|
dce1589c19 | ||
|
|
107cf16d36 | ||
|
|
2bab97c829 | ||
|
|
5242d825c2 | ||
|
|
11d3799b03 | ||
|
|
55c7f6b896 | ||
|
|
a09d607177 | ||
|
|
626a101208 | ||
|
|
f4ca3acbf6 | ||
|
|
86835a3bd7 | ||
|
|
0ef46e2e11 | ||
|
|
04878447b7 | ||
|
|
3669d5d90e | ||
|
|
ee5e222a13 | ||
|
|
9ea44ff59a | ||
|
|
0474e2c0f5 | ||
|
|
4a909d4b93 | ||
|
|
8dbe24ab55 | ||
|
|
25f7b005cb | ||
|
|
23f55ea208 | ||
|
|
5f19fa0f1f | ||
|
|
58fbdcfcdd | ||
|
|
e33cca9fd9 | ||
|
|
b25e3d5454 | ||
|
|
583f272daa | ||
|
|
2a83ad63be | ||
|
|
6fdc908ad1 | ||
|
|
5213caac49 | ||
|
|
ca0518c747 | ||
|
|
298cd29537 | ||
|
|
1a963a3239 | ||
|
|
683c8dc5c5 | ||
|
|
2087970de6 | ||
|
|
5bd270fe6a | ||
|
|
0992fcfcb0 | ||
|
|
fc12395f7e | ||
|
|
57b291a4e8 | ||
|
|
3adf391478 | ||
|
|
a859941845 | ||
|
|
8d1fc85638 | ||
|
|
51ece1c472 | ||
|
|
45f67ec37f | ||
|
|
85b559c989 | ||
|
|
1382050b5f | ||
|
|
fb970752fd | ||
|
|
3d256a402f | ||
|
|
a677d95d34 | ||
|
|
452c0f2846 | ||
|
|
e568e688f5 | ||
|
|
a201c930c2 | ||
|
|
7d927dde6b | ||
|
|
f76fece81d | ||
|
|
069d7e2382 | ||
|
|
e2e0562719 | ||
|
|
347aaadb43 | ||
|
|
6ddd64232f | ||
|
|
e532b3b05b | ||
|
|
d7c17d80e7 | ||
|
|
b35980cf60 | ||
|
|
263853d6a7 | ||
|
|
6a100b8250 | ||
|
|
893560e589 | ||
|
|
6b7ce7de5b | ||
|
|
6082d8cc1a | ||
|
|
ec37c9cab2 | ||
|
|
309e2a715e | ||
|
|
fbb4afa4f7 | ||
|
|
90863b95f4 | ||
|
|
3576eaf3ff | ||
|
|
f14c9741ea | ||
|
|
6d04e51e10 | ||
|
|
6ea1aa6c1a | ||
|
|
c86e1a9409 | ||
|
|
f1845f2d4e | ||
|
|
a20ca4ea2e | ||
|
|
0effc0d181 | ||
|
|
7ce273ec67 | ||
|
|
39e080cdca | ||
|
|
17584ae5fb | ||
|
|
c46c72d66d | ||
|
|
36f00ab568 | ||
|
|
dae7dacf3d | ||
|
|
b69b72cd1b | ||
|
|
68d1e39410 | ||
|
|
d4482dec23 | ||
|
|
8f36cecc7a | ||
|
|
a59fbcc9c8 | ||
|
|
25cddb2d61 | ||
|
|
9a7af235b8 | ||
|
|
c9751ab2a2 | ||
|
|
5e13986cc0 | ||
|
|
35e2cc535e | ||
|
|
81355a0248 | ||
|
|
3e98502e2d | ||
|
|
6973fa4538 | ||
|
|
20191c3f46 | ||
|
|
f084fec54e | ||
|
|
367e3b3a93 | ||
|
|
36383b6f47 | ||
|
|
9ecd14820e | ||
|
|
be552e92a1 | ||
|
|
1bf676fd56 | ||
|
|
154a96b5fd | ||
|
|
443c384f41 | ||
|
|
85c7829636 | ||
|
|
68e3bb8d66 | ||
|
|
63b2e52eaf | ||
|
|
c7492f3faf | ||
|
|
9052343ef7 | ||
|
|
00c09e4a1a | ||
|
|
086c0d9f28 | ||
|
|
3ca0189f28 | ||
|
|
8f5a2ba85d | ||
|
|
1ab9811d7a | ||
|
|
0b720848ba | ||
|
|
eebd95e700 | ||
|
|
913e02f0fa | ||
|
|
10fdab2473 | ||
|
|
26710761ed | ||
|
|
17bf6ac12b | ||
|
|
6a01b7729d | ||
|
|
039e817002 | ||
|
|
3ea1e1914a | ||
|
|
e6c5152123 | ||
|
|
7bdaf186f3 | ||
|
|
b6b4e826cb | ||
|
|
949f3cb246 | ||
|
|
501855a8c0 | ||
|
|
44634be13d | ||
|
|
7c718df1af | ||
|
|
16cca9cbbd | ||
|
|
b715320298 | ||
|
|
d31d9eb137 | ||
|
|
b53c29c035 | ||
|
|
d3bf20bee6 | ||
|
|
cff445ddf5 | ||
|
|
877d96a50a | ||
|
|
12863dfa21 | ||
|
|
959959d880 | ||
|
|
cd676eabfc | ||
|
|
0a3e95c8d4 | ||
|
|
35b483e655 | ||
|
|
7dae7b72f4 | ||
|
|
620f5df241 | ||
|
|
d5ddb6eb8c | ||
|
|
33c9b9d4e4 | ||
|
|
216cafbe54 | ||
|
|
72a644bd3b | ||
|
|
7008f0bc89 | ||
|
|
289d3ea287 | ||
|
|
92ae157f00 | ||
|
|
9800f7c01b | ||
|
|
8281af4590 | ||
|
|
d609e5e113 | ||
|
|
238d769f27 | ||
|
|
1e648cc6b4 | ||
|
|
7ac20d59fc | ||
|
|
1d8ab51a22 | ||
|
|
cdfb3b74b3 | ||
|
|
6f2868f7c3 | ||
|
|
f3061fa497 | ||
|
|
468cce5f3d | ||
|
|
bfaba80d3b | ||
|
|
2ca4a2af86 | ||
|
|
7d0b245d3d | ||
|
|
96fcf69bdd | ||
|
|
63d95b1265 | ||
|
|
d173e83e01 | ||
|
|
54d11dea95 | ||
|
|
fbdab389c4 | ||
|
|
3dd3bad362 | ||
|
|
65a9a4b4f2 | ||
|
|
5f6cf5550d | ||
|
|
673dad4032 | ||
|
|
5e45ec9a9c | ||
|
|
306eb9d23c | ||
|
|
158218e501 | ||
|
|
d773332b74 | ||
|
|
5dc2eb0062 | ||
|
|
bee7febafc | ||
|
|
f49cc2d498 | ||
|
|
3f4d2b75fc | ||
|
|
af8733c2d1 | ||
|
|
545eed98a7 | ||
|
|
2da436189a | ||
|
|
906ea11733 | ||
|
|
47c5629f7d | ||
|
|
a37c676d96 | ||
|
|
d9981f15f2 | ||
|
|
27b75746fd | ||
|
|
ee80a3a841 | ||
|
|
f8d639054f | ||
|
|
7f76e58e4e | ||
|
|
1b326a86a2 | ||
|
|
607bf4c67f | ||
|
|
3e4836c91e | ||
|
|
dae8f21388 | ||
|
|
84b328b60b | ||
|
|
b98d55b444 | ||
|
|
ee9413e54a | ||
|
|
76dedce5da | ||
|
|
37f99ae706 | ||
|
|
aee58d967d | ||
|
|
5ecabeb045 | ||
|
|
408c142aad | ||
|
|
a56d541380 | ||
|
|
ae79417845 | ||
|
|
010daa583e | ||
|
|
3034957c99 | ||
|
|
99134346ed | ||
|
|
9a70708300 | ||
|
|
50f0df5012 | ||
|
|
599bb80106 | ||
|
|
aa0db1f400 | ||
|
|
82b6fd22a9 | ||
|
|
b5c730d37a | ||
|
|
a2cb6444f4 | ||
|
|
8a144eabca | ||
|
|
75c2a3b0ec | ||
|
|
beec02a69b | ||
|
|
df78b45f69 | ||
|
|
e6a3c8f165 | ||
|
|
4d948e93a3 | ||
|
|
ece082727b | ||
|
|
5b5040f4af | ||
|
|
d9d88e690b | ||
|
|
bda123fc39 | ||
|
|
2200bb9b45 | ||
|
|
884d460b52 | ||
|
|
beb97270a0 | ||
|
|
52681bd386 | ||
|
|
d772a737fc | ||
|
|
638fcfea12 | ||
|
|
cae32cca5a | ||
|
|
c429e7054a | ||
|
|
f526f11c73 | ||
|
|
892016368f | ||
|
|
d9464ac278 | ||
|
|
a68a9995a1 | ||
|
|
1a0c2cb180 | ||
|
|
d7572f5a79 | ||
|
|
a208651e13 | ||
|
|
41c231b4f3 | ||
|
|
567b6f7659 | ||
|
|
6d1e0089e5 | ||
|
|
502f532fe7 | ||
|
|
ddc47f09ab | ||
|
|
5dbcdf2fce | ||
|
|
706588c39c | ||
|
|
3e3e64942e | ||
|
|
3700ac8a84 | ||
|
|
cc0dc0abf5 | ||
|
|
5861147d17 | ||
|
|
732035a0e2 | ||
|
|
643f71009d | ||
|
|
4643e37adb | ||
|
|
1bc8854685 | ||
|
|
66ebf6a391 | ||
|
|
1239277cec | ||
|
|
9cb8fb5527 | ||
|
|
819d67dca8 | ||
|
|
f895982fa0 | ||
|
|
505c097659 | ||
|
|
783fa10157 | ||
|
|
82ffbf2a1d | ||
|
|
3c3ae47511 | ||
|
|
858d7acf68 | ||
|
|
f8b7005690 | ||
|
|
33a8cb98e6 | ||
|
|
338a22f2ed | ||
|
|
f7cecaf709 | ||
|
|
c38b3c15e7 | ||
|
|
4665678e9f | ||
|
|
07416eb74d | ||
|
|
6f37dc53c7 | ||
|
|
55b963dcf9 | ||
|
|
1a433019b3 | ||
|
|
7b292cb1ab | ||
|
|
b2f5590c04 | ||
|
|
2cf2c87a8b | ||
|
|
01420f7e72 | ||
|
|
3f699973b9 | ||
|
|
3f27c9139c | ||
|
|
9eee6fc4af | ||
|
|
a343dbe8f8 | ||
|
|
bc08a09621 | ||
|
|
586ad66bff | ||
|
|
1a426e1f5f | ||
|
|
e9a9f1070c | ||
|
|
7d3dd61bc4 | ||
|
|
f37894af88 | ||
|
|
14129545b8 | ||
|
|
f06346b6c7 | ||
|
|
13e3e8c534 | ||
|
|
4be282813c | ||
|
|
74e66a407d | ||
|
|
b19da4c82e | ||
|
|
2f49764e1b | ||
|
|
08dfb0f72d | ||
|
|
9251ed238c | ||
|
|
3b96328765 | ||
|
|
108928e524 | ||
|
|
b1191bbf9b | ||
|
|
3424260cfd | ||
|
|
e1fe56160e | ||
|
|
5838ea3e3f | ||
|
|
0906b17ed1 | ||
|
|
49b9cb72d3 | ||
|
|
01508cc3c1 | ||
|
|
79eba83812 | ||
|
|
a455dee24c | ||
|
|
f58b4959e4 | ||
|
|
a6b4182ecd | ||
|
|
9010591ffc | ||
|
|
e0dde2c982 | ||
|
|
6109d97bf7 | ||
|
|
95936e01ce | ||
|
|
beadc7b185 | ||
|
|
0bc2e45137 | ||
|
|
b8e649e995 | ||
|
|
bdcd21a535 | ||
|
|
1b779a8b77 | ||
|
|
6c59a341ee | ||
|
|
1a6e03c8f8 | ||
|
|
653c0a2ac6 | ||
|
|
e4189f367a | ||
|
|
48b7355ba4 | ||
|
|
d7d3a903ff | ||
|
|
bab3067273 | ||
|
|
fdd70a6908 | ||
|
|
3699a8fe30 | ||
|
|
32f98f8a15 | ||
|
|
c5366760a2 | ||
|
|
c98ec2a7a4 | ||
|
|
e4c729e51b | ||
|
|
f36726d8fb | ||
|
|
d2fc2ee136 | ||
|
|
6edc2164af | ||
|
|
e34dc30f1f | ||
|
|
c03081d3fa | ||
|
|
97287e4538 | ||
|
|
fc44507a34 | ||
|
|
2597004750 | ||
|
|
d1bd9c5fae | ||
|
|
54542f9246 | ||
|
|
377b23458f | ||
|
|
b7973cdb76 | ||
|
|
5f1a70886c | ||
|
|
5fbc8b0b66 | ||
|
|
780b2b6e1d | ||
|
|
039ad2032c | ||
|
|
a24222b693 | ||
|
|
6047d91d7e | ||
|
|
f0158f6946 | ||
|
|
00ecdc58e6 | ||
|
|
0a075a97af | ||
|
|
34ee70ca7d | ||
|
|
338751ef2b | ||
|
|
d4465ba864 | ||
|
|
9a8fdd4edb | ||
|
|
338848f1eb | ||
|
|
9b918ec036 | ||
|
|
24439c7853 |
No files matched your search
@@ -1,19 +1,30 @@
|
||||
name: Bug 反馈 / Bug report
|
||||
description: 提交一个问题报告 / Create a bug report to help us improve
|
||||
description: 提交一个问题报告 / Create a bug report
|
||||
labels:
|
||||
- "bug"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
||||
提交问题报告前,还请首先完成文末的自查步骤
|
||||
|
||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
||||
Please finish verify steps which list in the end first before create bug report
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
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
|
||||
|
||||
@@ -44,12 +55,25 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 备注 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
placeholder: |
|
||||
其他有用的信息与附件
|
||||
Additional details and attachments
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
placeholder: 1.6.9.13
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -89,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:
|
||||
@@ -119,4 +134,4 @@ body:
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
|
||||
required: true
|
||||
required: true
|
||||
@@ -1,8 +1,17 @@
|
||||
name: 功能请求 / Feature request
|
||||
description: 提出一个功能建议 / Suggest an idea for this project
|
||||
description: 提出一个功能建议 / Suggest an idea
|
||||
labels:
|
||||
- "enhancement"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
||||
提交功能建议前,还请首先完成文末的自查步骤
|
||||
|
||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
||||
Please finish verify steps which list in the end first before suggest an idea
|
||||
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
@@ -33,7 +42,7 @@ body:
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 其他细节 / Addition details
|
||||
label: 备注 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
@@ -48,7 +57,7 @@ body:
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
placeholder: 1.6.9.13
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -82,4 +91,4 @@ body:
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
|
||||
required: true
|
||||
required: true
|
||||
@@ -17,19 +17,20 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
@@ -55,3 +56,10 @@ jobs:
|
||||
with:
|
||||
name: x86-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-x86-release.apk
|
||||
|
||||
- name: Update mapping
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release/mapping.txt
|
||||
retention-days: 5
|
||||
@@ -12,15 +12,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -15,15 +15,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
|
||||
- name: Rename Apks
|
||||
run: |
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
|
||||
- name: Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -44,11 +44,12 @@ jobs:
|
||||
body: Bump Version
|
||||
discussion_category_name: Announcements
|
||||
files: |
|
||||
app/build/outputs/apk/release/EhViewer-universal-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
app/build/outputs/mapping/release/mapping.txt
|
||||
|
||||
- name: Collect Apks
|
||||
run: |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.svg" width="200">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
@@ -66,20 +66,19 @@ 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="art/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="art/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
|
||||
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
|
||||
<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>
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Thanks
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [AOSP & AndroidX](http://source.android.com/)
|
||||
- [Coil](https://coil-kt.github.io/coil/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
@@ -95,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.
|
||||
|
||||
|
||||
+58
-88
@@ -1,18 +1,14 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-parcelize'
|
||||
id 'com.google.devtools.ksp'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.0.8775105"
|
||||
|
||||
Properties localProperties = new Properties()
|
||||
if (project.rootProject.file('local.properties').exists()) {
|
||||
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
}
|
||||
buildToolsVersion "33.0.1"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
abi {
|
||||
@@ -34,10 +30,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180009
|
||||
versionName "1.8.2.0"
|
||||
versionCode 180030
|
||||
versionName "1.8.5.3-rc01"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -48,12 +44,16 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_19
|
||||
targetCompatibility JavaVersion.VERSION_19
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
jvmTarget = '19'
|
||||
freeCompilerArgs += [
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive"
|
||||
]
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -64,7 +64,8 @@ android {
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/**"
|
||||
excludes += "/META-INF/CHANGES"
|
||||
excludes += "/META-INF/README.md"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
@@ -87,113 +88,82 @@ android {
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
kotlinCompilerExtensionVersion = "1.4.0"
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-beta01"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.5.0-alpha02"
|
||||
implementation "androidx.annotation:annotation-experimental:1.3.0-beta01"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-beta01"
|
||||
|
||||
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-alpha04"
|
||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||
implementation "androidx.activity:activity-compose:1.6.1"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0"
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
|
||||
def collection_version = "1.3.0-alpha02"
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
// 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"
|
||||
|
||||
def compose_version = "1.3.0-alpha03"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-alpha16"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-process:2.5.1"
|
||||
|
||||
def core_version = "1.9.0-beta01"
|
||||
implementation "androidx.core:core:$core_version"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
implementation "androidx.customview:customview:1.2.0-alpha01"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha01"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha01"
|
||||
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.1"
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
// https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
def 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"
|
||||
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
|
||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03"
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
|
||||
|
||||
def room_version = "2.5.0-alpha02"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
// https://developer.android.com/jetpack/androidx/releases/room
|
||||
def room_version = "2.5.0"
|
||||
ksp "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
|
||||
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation "androidx.transition:transition-ktx:1.4.1"
|
||||
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
|
||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0-rc01'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0' // Dead Dependency
|
||||
implementation 'com.github.tachiyomiorg:DirectionalViewPager:1.0.0' // Dead Dependency
|
||||
// https://github.com/google/accompanist/releases
|
||||
implementation "com.google.accompanist:accompanist-themeadapter-material3:0.28.0"
|
||||
implementation 'com.google.android.material:material:1.8.0-rc01'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||
// 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:d65881d'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
|
||||
exclude group: 'dev.rikka.rikkax.material', module: 'material'
|
||||
exclude group: 'androidx.compose.material'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-android-extensions-runtime'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
|
||||
}
|
||||
Vendored
+3
-12
@@ -2,24 +2,15 @@
|
||||
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 { }
|
||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
-keepattributes LineNumberTable,SourceFile
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-mergeinterfacesaggressively
|
||||
-overloadaggressively
|
||||
@@ -1,20 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
@@ -23,50 +9,50 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<!-- Required to maintain app compatibility. -->
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
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"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_scheme"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
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:theme="@style/AppTheme">
|
||||
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:targetApi="tiramisu">
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.ui.MainActivity"
|
||||
android:name=".ui.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>
|
||||
|
||||
@@ -87,26 +73,26 @@
|
||||
<data android:host="g.e-hentai.org" />
|
||||
<data android:host="lofi.e-hentai.org" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/keyword_search">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/image_search">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.ui.GalleryActivity"
|
||||
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -140,28 +126,33 @@
|
||||
<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=""
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<service
|
||||
android:name="com.hippo.ehviewer.download.DownloadService"
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".download.DownloadService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:label="@string/download_service_label" />
|
||||
|
||||
@@ -170,11 +161,10 @@
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
</manifest>
|
||||
@@ -1,7 +1,7 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -funroll-loops -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
@@ -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 main.c archive.c image.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 jnigraphics GLESv2 -Wl,--gc-sections)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log -Wl,--gc-sections)
|
||||
+128
-48
@@ -24,6 +24,7 @@
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
@@ -32,12 +33,8 @@
|
||||
#include <archive_entry.h>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
typedef struct {
|
||||
int using;
|
||||
@@ -46,6 +43,11 @@ typedef struct {
|
||||
struct archive_entry *entry;
|
||||
} archive_ctx;
|
||||
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
int index;
|
||||
} entry;
|
||||
|
||||
pthread_mutex_t ctx_lock;
|
||||
static archive_ctx **ctx_pool;
|
||||
#define CTX_POOL_SIZE 20
|
||||
@@ -53,7 +55,11 @@ static archive_ctx **ctx_pool;
|
||||
static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static jlong archiveSize = 0;
|
||||
static size_t archiveSize = 0;
|
||||
static entry *entries = NULL;
|
||||
static size_t entryCount = 0;
|
||||
|
||||
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
@@ -78,6 +84,30 @@ static inline int filename_is_playable_file(const char *name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int compare_entries(const void *a, const void *b) {
|
||||
const char *fa = ((entry *) a)->filename;
|
||||
const char *fb = ((entry *) b)->filename;
|
||||
int a_len = (int) strlen(fa);
|
||||
int b_len = (int) strlen(fb);
|
||||
return a_len == b_len ? strcmp(fa, fb) : a_len - b_len;
|
||||
}
|
||||
|
||||
static long archive_map_entries_index(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||
const char *name = archive_entry_pathname(ctx->entry);
|
||||
if (filename_is_playable_file(name)) {
|
||||
entries[count].filename = strdup(name);
|
||||
entries[count].index = count;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (!count)
|
||||
LOGE("%s", archive_error_string(ctx->arc));
|
||||
qsort(entries, entryCount, sizeof(entry), compare_entries);
|
||||
return count;
|
||||
}
|
||||
|
||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
||||
@@ -108,8 +138,12 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
||||
free(ctx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
archive_read_support_format_all(ctx->arc);
|
||||
archive_read_support_filter_all(ctx->arc);
|
||||
archive_read_support_format_tar(ctx->arc);
|
||||
archive_read_support_format_7zip(ctx->arc);
|
||||
archive_read_support_format_rar5(ctx->arc);
|
||||
archive_read_support_format_zip(ctx->arc);
|
||||
archive_read_support_filter_gzip(ctx->arc);
|
||||
archive_read_support_filter_xz(ctx->arc);
|
||||
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||
if (passwd)
|
||||
archive_read_add_passphrase(ctx->arc, passwd);
|
||||
@@ -195,15 +229,19 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
int mmap_flags = MAP_PRIVATE;
|
||||
if (size <= POPULATE_THRESHOLD)
|
||||
mmap_flags |= MAP_POPULATE;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
|
||||
if (archiveAddr == MAP_FAILED) {
|
||||
LOGE("%s%d", "mmap64 failed with errno ", errno);
|
||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
archiveSize = size;
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
||||
pthread_mutex_init(&ctx_lock, 0);
|
||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||
if (!ctx_pool) {
|
||||
@@ -214,10 +252,9 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
r = archive_list_all_entries(ctx);
|
||||
entryCount = archive_list_all_entries(ctx);
|
||||
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
|
||||
|
||||
// We must read through the file|vm then we can know whether it is encrypted
|
||||
@@ -231,66 +268,69 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
need_encrypt = false;
|
||||
}
|
||||
|
||||
// madvise for readahead optimization
|
||||
int format = archive_format(ctx->arc);
|
||||
switch (format) {
|
||||
case ARCHIVE_FORMAT_ZIP:
|
||||
case ARCHIVE_FORMAT_RAR:
|
||||
case ARCHIVE_FORMAT_RAR_V5:
|
||||
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
|
||||
break;
|
||||
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
|
||||
r = archive_alloc_ctx(&ctx);
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
entries = calloc(entryCount, sizeof(entry));
|
||||
r = archive_map_entries_index(ctx);
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *env, jobject thiz, jint index) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret)
|
||||
return 0;
|
||||
Memarea *memarea = malloc(sizeof(Memarea));
|
||||
if (!memarea) {
|
||||
long long size = archive_entry_size(ctx->entry);
|
||||
void *buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
memarea->size = (long) archive_entry_size(ctx->entry);
|
||||
memarea->buffer = malloc(memarea->size);
|
||||
if (!memarea->buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
free(memarea);
|
||||
return 0;
|
||||
}
|
||||
ret = archive_read_data(ctx->arc, memarea->buffer, memarea->size);
|
||||
ret = archive_read_data(ctx->arc, buffer, size);
|
||||
ctx->using = 0;
|
||||
if (ret == memarea->size)
|
||||
return (jlong) memarea;
|
||||
if (ret != memarea->size)
|
||||
if (ret == size)
|
||||
return (*env)->NewDirectByteBuffer(env, buffer, size);
|
||||
if (ret != size)
|
||||
LOGE("%s", "No enough data read, WTF?");
|
||||
if (ret < 0)
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
free(memarea->buffer);
|
||||
free(memarea);
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||
archive_release_ctx(ctx_pool[i]);
|
||||
free(passwd);
|
||||
@@ -299,18 +339,22 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
passwd = NULL;
|
||||
need_encrypt = false;
|
||||
munmap(archiveAddr, archiveSize);
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
free((void *) entries[i].filename);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(thiz);
|
||||
struct archive_entry *entry;
|
||||
archive_ctx *ctx;
|
||||
jboolean ret = true;
|
||||
@@ -337,3 +381,39 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jst
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret)
|
||||
return NULL;
|
||||
jstring str = (*env)->NewStringUTF(env, archive_entry_pathname(ctx->entry));
|
||||
ctx->using = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (!ret) {
|
||||
archive_read_data_into_fd(ctx->arc, fd);
|
||||
ctx->using = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||
EH_UNUSED(thiz);
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
free(addr);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef EHVIEWER_EHVIEWER_H
|
||||
#define EHVIEWER_EHVIEWER_H
|
||||
|
||||
#define EH_UNUSED(x) (void)x
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
#define madvise_log_if_error(addr, len, advice) \
|
||||
if (madvise(addr, len, advice)) \
|
||||
LOGE("%s%p%s%zu%s%d%s%s%s", "madvise addr:", addr, "len:", len, "with advice ", advice, " failed with error: ", strerror(errno), ", Ignored")
|
||||
|
||||
#endif /* EHVIEWER_EHVIEWER_H */
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <jni.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define LOG_TAG "mmap_utils"
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
|
||||
|
||||
if (fdClass != NULL) {
|
||||
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
|
||||
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
|
||||
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_Native_mapFd(JNIEnv *env, jclass clazz, jint fd, jlong capability) {
|
||||
void *addr = mmap(0, capability, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (addr != MAP_FAILED) {
|
||||
return (*env)->NewDirectByteBuffer(env, addr, capability);
|
||||
} else {
|
||||
LOGE("%s%s", "mmap failed with error ", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_Native_unmapDirectByteBuffer(JNIEnv *env, jclass clazz, jobject buffer) {
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||
if (munmap(addr, size)) LOGE("munmap addr:%p, size%zu failed!", addr, size);
|
||||
}
|
||||
@@ -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,380 +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/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/data_space.h>
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static void *tile_buffer = NULL;
|
||||
|
||||
bool image_onLoad() {
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
return tile_buffer != NULL;
|
||||
}
|
||||
|
||||
void image_onUnload() {
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
void *srcBuffer;
|
||||
size_t stride;
|
||||
int fd;
|
||||
} SOURCE;
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
void *buffer;
|
||||
int bufferLen;
|
||||
bool isAnimated;
|
||||
bool alpha;
|
||||
int delay;
|
||||
SOURCE *src;
|
||||
} IMAGE;
|
||||
|
||||
static void recycleSource(SOURCE *src) {
|
||||
if (!src)
|
||||
return;
|
||||
AImageDecoder_delete(src->decoder);
|
||||
free(src->srcBuffer);
|
||||
free(src);
|
||||
}
|
||||
|
||||
static void recycle(IMAGE *image) {
|
||||
if (!image)
|
||||
return;
|
||||
recycleSource(image->src);
|
||||
free(image->buffer);
|
||||
free(image);
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
int left;
|
||||
int line;
|
||||
size_t line_stride;
|
||||
int src_stride;
|
||||
int src_pos;
|
||||
int dst_pos;
|
||||
size_t dst_blank_length;
|
||||
|
||||
// Sanitize
|
||||
if (src_x < 0) {
|
||||
width -= src_x;
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
if (dst_x < 0) {
|
||||
width -= dst_x;
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (src_y < 0) {
|
||||
height -= src_y;
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
if (dst_y < 0) {
|
||||
height -= dst_y;
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_x + width - src_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
left = dst_x + width - dst_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_y + height - src_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
left = dst_y + height - dst_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init
|
||||
line_stride = (size_t) (width * 4);
|
||||
src_stride = src_w * 4;
|
||||
src_pos = src_y * src_stride + src_x * 4;
|
||||
dst_pos = 0;
|
||||
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
|
||||
// First line
|
||||
dst_pos += (int)dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
dst_pos += (int)dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeCommon(IMAGE *image, SOURCE *src) {
|
||||
image->src = src;
|
||||
AImageDecoder *decoder = src->decoder;
|
||||
image->isAnimated = AImageDecoder_isAnimated(decoder);
|
||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
||||
image->alpha =
|
||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
||||
src->stride = AImageDecoder_getMinimumStride(decoder);
|
||||
image->bufferLen = (int) (image->height * src->stride);
|
||||
image->buffer = malloc(image->bufferLen);
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
||||
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
|
||||
AImageDecoder_getFrameInfo(decoder, frameInfo);
|
||||
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
|
||||
AImageDecoderFrameInfo_delete(frameInfo);
|
||||
}
|
||||
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
|
||||
if (!image->isAnimated) {
|
||||
recycleSource(image->src);
|
||||
image->src = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
IMAGE *createFromFd(int fd) {
|
||||
AImageDecoder *decoder = NULL;
|
||||
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->fd = fd;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(void *addr, long size) {
|
||||
AImageDecoder *decoder;
|
||||
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->srcBuffer = addr;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *create(int32_t width, int32_t height, const void *data) {
|
||||
IMAGE *plain = calloc(1, sizeof(IMAGE));
|
||||
void *buffer = NULL;
|
||||
size_t length = width * height * 4;
|
||||
|
||||
buffer = malloc(length);
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
// plain->isAnimated = false; this is unnecessary since we use calloc
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
plain->bufferLen = (int) length;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
void render(IMAGE *image, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height);
|
||||
}
|
||||
|
||||
void advance(IMAGE *image) {
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_advanceFrame(image->src->decoder);
|
||||
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
||||
AImageDecoder_rewind(image->src->decoder);
|
||||
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int jniGetFDFromFileDescriptor(JNIEnv *env, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
|
||||
|
||||
if (fdClass != NULL) {
|
||||
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
|
||||
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
|
||||
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeFdInt(JNIEnv *env, jclass clazz, jint fd) {
|
||||
IMAGE *image = createFromFd(fd);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jobject fd) {
|
||||
return Java_com_hippo_image_Image_nativeDecodeFdInt(env, clazz,
|
||||
jniGetFDFromFileDescriptor(env, fd));
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
void *image = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
|
||||
image = create((int)info.width, (int)info.height, pixels);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
|
||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
||||
jint height) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, dst, &info);
|
||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
|
||||
jint src_x, jint src_y, jint width, jint height) {
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
||||
return;
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
advance((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->delay;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->alpha;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
recycle((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
||||
Memarea *memarea = (Memarea *) addr;
|
||||
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
|
||||
free(memarea);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->isAnimated;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->width;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->height;
|
||||
}
|
||||
@@ -1,29 +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/>.
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool image_onLoad();
|
||||
|
||||
void image_onUnload();
|
||||
|
||||
#endif /* IMAGE_IMAGE_H */
|
||||
Submodule app/src/main/cpp/libarchive updated: 37d2cf6063...c9298fd6dc.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 2327a461e1...fc9fb0ea24.
@@ -1,65 +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/>.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <archive.h>
|
||||
#include <lzma.h>
|
||||
#include <zlib.h>
|
||||
#include <nettle/config.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (!image_onLoad(vm)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
image_onUnload();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getlibarchiveVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, ARCHIVE_VERSION_STRING);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getliblzmaVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, lzma_version_string());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getzlibVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, ZLIB_VERSION);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getnettleVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, PACKAGE_STRING);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package androidx.navigation.ui
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavOptions
|
||||
|
||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
||||
if (navController.currentDestination?.matchDestination(item.itemId) == true) return false
|
||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
||||
if (
|
||||
navController.currentDestination!!.parent!!.findNode(item.itemId)
|
||||
is ActivityNavigator.Destination
|
||||
) {
|
||||
builder.setEnterAnim(R.anim.nav_default_enter_anim)
|
||||
.setExitAnim(R.anim.nav_default_exit_anim)
|
||||
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
|
||||
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
|
||||
} else {
|
||||
builder.setEnterAnim(com.hippo.ehviewer.R.anim.scene_open_enter)
|
||||
.setExitAnim(com.hippo.ehviewer.R.anim.scene_open_exit)
|
||||
.setPopEnterAnim(com.hippo.ehviewer.R.anim.scene_close_enter)
|
||||
.setPopExitAnim(com.hippo.ehviewer.R.anim.scene_close_exit)
|
||||
}
|
||||
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
|
||||
builder.setPopUpTo(
|
||||
navController.graph.findStartDestination().id,
|
||||
inclusive = false,
|
||||
saveState = false
|
||||
)
|
||||
}
|
||||
val options = builder.build()
|
||||
return try {
|
||||
// TODO provide proper API instead of using Exceptions as Control-Flow.
|
||||
navController.navigate(item.itemId, null, options)
|
||||
// Return true only if the destination we've navigated to matches the MenuItem
|
||||
navController.currentDestination?.matchDestination(item.itemId) == true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
|
||||
hierarchy.any { it.id == destId }
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
||||
* use of a bitmap, asset, resource, external file or any other URI.
|
||||
*
|
||||
*
|
||||
* When you are using a preview image, you must set the dimensions of the full size image on the
|
||||
* ImageSource object for the full size image using the [.dimensions] method.
|
||||
*/
|
||||
class ImageSource {
|
||||
val bitmap: Bitmap?
|
||||
val provider: InputProvider?
|
||||
|
||||
private var sWidth = 0
|
||||
private var sHeight = 0
|
||||
|
||||
var sRegion = Rect(0, 0, 0, 0)
|
||||
private set
|
||||
var isCached = false
|
||||
private set
|
||||
|
||||
private constructor(bitmap: Bitmap, cached: Boolean) {
|
||||
this.bitmap = bitmap
|
||||
provider = null
|
||||
sWidth = bitmap.width
|
||||
sHeight = bitmap.height
|
||||
isCached = cached
|
||||
}
|
||||
|
||||
private constructor(provider: InputProvider) {
|
||||
bitmap = null
|
||||
this.provider = provider
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
||||
* you are using one.
|
||||
*
|
||||
* @param sRegion the region of the source image to be displayed.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun region(sRegion: Rect): ImageSource {
|
||||
this.sRegion = sRegion
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
||||
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
||||
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
||||
*
|
||||
* @param sWidth width of the source image.
|
||||
* @param sHeight height of the source image.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
||||
if (bitmap == null) {
|
||||
this.sWidth = sWidth
|
||||
this.sHeight = sHeight
|
||||
}
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setInvariants() {
|
||||
if (!sRegion.isEmpty) {
|
||||
sWidth = sRegion.width()
|
||||
sHeight = sRegion.height()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
|
||||
*
|
||||
* @param resId resource ID.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resource(context: Context, resId: Int): ImageSource {
|
||||
return ImageSource(ResourceInputProvider(context, resId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an asset name.
|
||||
*
|
||||
* @param assetName asset name.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asset(context: Context, assetName: String): ImageSource {
|
||||
return ImageSource(AssetInputProvider(context, assetName))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a URI.
|
||||
*
|
||||
* @param uri image URI.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun uri(context: Context, uri: Uri): ImageSource {
|
||||
return ImageSource(UriInputProvider(context, uri))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input provider.
|
||||
*
|
||||
* @param provider input stream provider.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun provider(provider: InputProvider): ImageSource {
|
||||
return ImageSource(provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input stream.
|
||||
*
|
||||
* @param stream open input stream.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun inputStream(stream: InputStream): ImageSource {
|
||||
return ImageSource(OpenStreamProvider(stream))
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded bitmap for display.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun bitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
||||
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
||||
* or Volley.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.graphics.PointF
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
||||
*/
|
||||
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
||||
private val centerX: Float = center.x
|
||||
private val centerY: Float = center.y
|
||||
|
||||
val center: PointF
|
||||
get() = PointF(centerX, centerY)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes.
|
||||
*/
|
||||
interface Decoder {
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun init(context: Context, provider: InputProvider): Point
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
val isReady: Boolean
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
fun recycle()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import java.lang.Exception
|
||||
|
||||
class ImageDecoder @JvmOverloads constructor(
|
||||
private val cropBorders: Boolean,
|
||||
bitmapConfig: Bitmap.Config? = null
|
||||
) : Decoder {
|
||||
init {
|
||||
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun init(context: Context, provider: InputProvider): Point {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
override val isReady: Boolean = TODO()
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
override fun recycle() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
|
||||
private val assets = context.assets
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = assets.open(assetName)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
interface InputProvider {
|
||||
@Throws(IOException::class)
|
||||
fun openStream(): InputStream?
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
/**
|
||||
* Provider from an already open input stream.
|
||||
*
|
||||
*
|
||||
* Please keep in mind this provider is not reusable.
|
||||
*/
|
||||
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = stream
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
|
||||
private val resources = context.resources
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resources.openRawResource(resource)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
import kotlin.Throws
|
||||
|
||||
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
|
||||
private val resolver = context.contentResolver
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resolver.openInputStream(uri)
|
||||
}
|
||||
@@ -15,20 +15,22 @@
|
||||
* 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
|
||||
|
||||
package com.hippo;
|
||||
import java.io.FileDescriptor
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
public class Native {
|
||||
|
||||
public static void initialize() {
|
||||
System.loadLibrary("ehviewer");
|
||||
object Native {
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
public static native String getlibarchiveVersion();
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
|
||||
public static native String getliblzmaVersion();
|
||||
@JvmStatic
|
||||
external fun mapFd(fd: Int, capability: Long): ByteBuffer?
|
||||
|
||||
public static native String getzlibVersion();
|
||||
|
||||
public static native String getnettleVersion();
|
||||
@JvmStatic
|
||||
external fun unmapDirectByteBuffer(buffer: ByteBuffer)
|
||||
}
|
||||
@@ -1,54 +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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
public class UriArchiveAccessor {
|
||||
ParcelFileDescriptor pfd;
|
||||
Context ctx;
|
||||
Uri uri;
|
||||
|
||||
public UriArchiveAccessor(Context ctx, Uri uri) {
|
||||
this.ctx = ctx;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public int open() throws Exception {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
return openArchive(pfd.getFd(), pfd.getStatSize());
|
||||
}
|
||||
|
||||
private native int openArchive(int fd, long size);
|
||||
|
||||
public native long extracttoOutputStream(int index);
|
||||
|
||||
public native boolean needPassword();
|
||||
|
||||
public native boolean providePassword(String str);
|
||||
|
||||
private native void closeArchive();
|
||||
|
||||
public void close() throws Exception {
|
||||
closeArchive();
|
||||
pfd.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.hippo.image.Image
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
fun open(): Int {
|
||||
return openArchive(pfd.fd, pfd.statSize)
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
private external fun extractToByteBuffer(index: Int): ByteBuffer?
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
external fun providePassword(str: String): Boolean
|
||||
private external fun closeArchive()
|
||||
fun close() {
|
||||
closeArchive()
|
||||
pfd.close()
|
||||
}
|
||||
|
||||
fun getImageSource(index: Int): Image.ByteBufferSource? {
|
||||
val buffer = extractToByteBuffer(index)
|
||||
buffer ?: return null
|
||||
check(buffer.isDirect)
|
||||
return object : Image.ByteBufferSource {
|
||||
override fun getByteBuffer(): ByteBuffer {
|
||||
return buffer
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
releaseByteBuffer(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private external fun releaseByteBuffer(buffer: ByteBuffer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
open class BaseDialogBuilder constructor(
|
||||
context: Context
|
||||
) : MaterialAlertDialogBuilder(context)
|
||||
@@ -18,7 +18,6 @@ package com.hippo.app
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class CheckBoxDialogBuilder constructor(
|
||||
@@ -26,7 +25,7 @@ class CheckBoxDialogBuilder constructor(
|
||||
message: String?,
|
||||
checkText: String?,
|
||||
checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
@@ -31,7 +30,7 @@ class EditTextDialogBuilder constructor(
|
||||
context: Context,
|
||||
text: String?,
|
||||
hint: String?
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
), OnEditorActionListener {
|
||||
private val mTextInputLayout: TextInputLayout
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
class HapticSlider @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Slider(context, attrs) {
|
||||
init {
|
||||
addOnChangeListener { _, _, _ -> performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) }
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,13 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
class ListCheckBoxDialogBuilder constructor(
|
||||
context: Context, items: Array<CharSequence>,
|
||||
listener: OnItemClickListener?, checkText: String?, checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -1,493 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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 android.util.LruCache;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class BeerBelly<V> {
|
||||
|
||||
private static final String TAG = BeerBelly.class.getSimpleName();
|
||||
private final boolean mHasMemoryCache;
|
||||
private final boolean mHasDiskCache;
|
||||
@Nullable
|
||||
private MemoryCache<V> mMemoryCache;
|
||||
@Nullable
|
||||
private DiskCache<V> mDiskCache;
|
||||
|
||||
public BeerBelly(BeerBellyParams params) {
|
||||
params.isValid();
|
||||
mHasMemoryCache = params.hasMemoryCache;
|
||||
mHasDiskCache = params.hasDiskCache;
|
||||
|
||||
if (mHasMemoryCache) {
|
||||
initMemoryCache(params.memoryCacheMaxSize);
|
||||
}
|
||||
|
||||
if (mHasDiskCache) {
|
||||
initDiskCache(params.diskCacheDir, params.diskCacheMaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMemoryCache(int maxSize) {
|
||||
mMemoryCache = new MemoryCache<>(maxSize, this);
|
||||
}
|
||||
|
||||
private void initDiskCache(File cacheDir, int maxSize) {
|
||||
// Set up disk cache
|
||||
try {
|
||||
mDiskCache = new DiskCache<>(cacheDir, maxSize, this);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can't create disk cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int sizeOf(String key, V value);
|
||||
|
||||
protected abstract void memoryEntryAdded(V value);
|
||||
|
||||
protected abstract void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue);
|
||||
|
||||
protected abstract V read(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
protected abstract boolean write(OutputStream os, V value);
|
||||
|
||||
@Nullable
|
||||
public LruCache<String, V> getMemoryCache() {
|
||||
return mMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SimpleDiskCache getDiskCache() {
|
||||
if (mDiskCache != null) {
|
||||
return mDiskCache.mDiskCache;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have memory cache
|
||||
*
|
||||
* @return true if have memory cache
|
||||
*/
|
||||
public boolean hasMemoryCache() {
|
||||
return mHasMemoryCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have disk cache
|
||||
*
|
||||
* @return true if have disk cache
|
||||
*/
|
||||
public boolean hasDiskCache() {
|
||||
return mHasDiskCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache usage size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memorySize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.size();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache max size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memoryMaxSize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.maxSize();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache usage size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.size();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache max size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskMaxSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.getMaxSize();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache
|
||||
*/
|
||||
public V getFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from disk cache. Override {@link #read(InputStreamPipe)} to do it
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache or get error
|
||||
*/
|
||||
public V getFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache and disk cache. If miss in memory cache and
|
||||
* get in disk cache, it will put value from disk cache to memory cache.
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get
|
||||
*/
|
||||
public V get(@NonNull String key) {
|
||||
V value = getFromMemory(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in memory cache
|
||||
return value;
|
||||
}
|
||||
|
||||
value = getFromDisk(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in disk cache
|
||||
putToMemory(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no memory cache
|
||||
*/
|
||||
public boolean putToMemory(@NonNull String key, @NonNull V value) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
memoryEntryAdded(value);
|
||||
mMemoryCache.put(key, value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putToDisk(@NonNull String key, @NonNull V value) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.put(key, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache and disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
*/
|
||||
public void put(@NonNull String key, @NonNull V value) {
|
||||
putToMemory(key, value);
|
||||
putToDisk(key, value);
|
||||
}
|
||||
|
||||
public void removeFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(@NonNull String key) {
|
||||
removeFromMemory(key);
|
||||
removeFromDisk(key);
|
||||
}
|
||||
|
||||
public boolean pullFromDiskCache(@NonNull String key, @NonNull OutputStream os) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.pull(key, os);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the key
|
||||
* @param is the input stream to store
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.putRaw(key, is);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache
|
||||
*/
|
||||
public void clearMemory() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear disk cache
|
||||
*/
|
||||
public void clearDisk() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache and lets the system know
|
||||
* now would be a good time to garbage collect
|
||||
*/
|
||||
public void clear() {
|
||||
clearMemory();
|
||||
clearDisk();
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder class that contains cache parameters.
|
||||
*/
|
||||
public static class BeerBellyParams {
|
||||
|
||||
/**
|
||||
* is memory cache available
|
||||
*/
|
||||
public boolean hasMemoryCache = false;
|
||||
/**
|
||||
* the maximum number of bytes the memory cache should use to store
|
||||
*/
|
||||
public int memoryCacheMaxSize = 0;
|
||||
/**
|
||||
* is disk cache available
|
||||
*/
|
||||
public boolean hasDiskCache = false;
|
||||
/**
|
||||
* the dir to store disk cache
|
||||
*/
|
||||
public File diskCacheDir = null;
|
||||
/**
|
||||
* the maximum number of bytes the disk cache should use to store
|
||||
*/
|
||||
public int diskCacheMaxSize = 0;
|
||||
|
||||
/**
|
||||
* Check BeerBellyParams is valid
|
||||
*/
|
||||
public void isValid() throws IllegalStateException {
|
||||
if (!hasMemoryCache && !hasDiskCache) {
|
||||
throw new IllegalStateException("No memory cache and no disk cache. What can I do for you?");
|
||||
}
|
||||
|
||||
if (hasMemoryCache && memoryCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Memory cache max size must be bigger than 0.");
|
||||
}
|
||||
|
||||
if (hasDiskCache) {
|
||||
if (diskCacheDir == null) {
|
||||
throw new IllegalStateException("Disk cache dir can't be null.");
|
||||
}
|
||||
if (diskCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Disk cache max size must be bigger than 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiskCache<E> {
|
||||
|
||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
private final SimpleDiskCache mDiskCache;
|
||||
private final BeerBelly<E> mParent;
|
||||
|
||||
private final File mCacheDir;
|
||||
private final int mMaxSize;
|
||||
|
||||
public DiskCache(File cacheDir, int size, BeerBelly<E> parent) throws IOException {
|
||||
mDiskCache = new SimpleDiskCache(cacheDir, size);
|
||||
mParent = parent;
|
||||
|
||||
mCacheDir = cacheDir;
|
||||
mMaxSize = size;
|
||||
}
|
||||
|
||||
public File getCacheDir() {
|
||||
return mCacheDir;
|
||||
}
|
||||
|
||||
public int getMaxSize() {
|
||||
return mMaxSize;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return mDiskCache.size();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
|
||||
public E get(String key) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return null;
|
||||
} else {
|
||||
return mParent.read(isPipe);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean put(String key, E value) {
|
||||
OutputStreamPipe osPipe = mDiskCache.getOutputStreamPipe(key);
|
||||
BufferedOutputStream buffOut = null;
|
||||
try {
|
||||
osPipe.obtain();
|
||||
OutputStream os = osPipe.open();
|
||||
buffOut = new BufferedOutputStream(os, IO_BUFFER_SIZE);
|
||||
return mParent.write(os, value);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
Util.closeQuietly(buffOut);
|
||||
osPipe.close();
|
||||
osPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putRaw(String key, InputStream is) {
|
||||
return mDiskCache.put(key, is);
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
|
||||
public boolean pull(@NonNull String key, @NonNull OutputStream os) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
InputStream is = isPipe.open();
|
||||
Util.copy(is, os);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryCache<E> extends LruCache<String, E> {
|
||||
|
||||
public BeerBelly<E> mParent;
|
||||
|
||||
public MemoryCache(int maxSize, BeerBelly<E> parent) {
|
||||
super(maxSize);
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, E value) {
|
||||
return mParent.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) {
|
||||
mParent.memoryEntryRemoved(evicted, key, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 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;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,522 +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 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 = bytesToHexString(digest.digest());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
cacheKey = String.valueOf(key.hashCode());
|
||||
}
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/332079
|
||||
*
|
||||
* @param bytes The bytes to convert.
|
||||
* @return A {@link String} converted from the bytes of a hashable key used
|
||||
* to store a filename on the disk, to hex digits.
|
||||
*/
|
||||
private static String bytesToHexString(final byte[] bytes) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final byte b : bytes) {
|
||||
final String hex = Integer.toHexString(0xFF & b);
|
||||
if (hex.length() == 1) {
|
||||
builder.append('0');
|
||||
}
|
||||
builder.append(hex);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
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,198 +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.io.UnsupportedEncodingException;
|
||||
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.name());
|
||||
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;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,223 +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.conaco;
|
||||
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.thread.SerialThreadExecutor;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final class Conaco<V> {
|
||||
|
||||
public static final int SOURCE_MEMORY = 0;
|
||||
public static final int SOURCE_DISK = 1;
|
||||
public static final int SOURCE_NETWORK = 2;
|
||||
private static final String TAG = Conaco.class.getSimpleName();
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Register<V> mRegister;
|
||||
private final SerialThreadExecutor mDiskExecutor;
|
||||
private final ThreadPoolExecutor mNetworkExecutor;
|
||||
private final AtomicInteger mIdGenerator;
|
||||
private final boolean mDebug;
|
||||
|
||||
private Conaco(Builder<V> builder) {
|
||||
mHelper = builder.objectHelper;
|
||||
|
||||
BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams();
|
||||
beerBellyParams.hasMemoryCache = builder.hasMemoryCache;
|
||||
beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize;
|
||||
beerBellyParams.hasDiskCache = builder.hasDiskCache;
|
||||
beerBellyParams.diskCacheDir = builder.diskCacheDir;
|
||||
beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize;
|
||||
|
||||
mCache = new ValueCache<>(beerBellyParams, mHelper);
|
||||
mOkHttpClient = builder.okHttpClient;
|
||||
|
||||
mRegister = new Register<>();
|
||||
|
||||
mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(),
|
||||
new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
|
||||
mIdGenerator = new AtomicInteger();
|
||||
|
||||
mDebug = builder.debug;
|
||||
}
|
||||
|
||||
public BeerBelly getBeerBelly() {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
private void onUnregisterConacoTask(ConacoTask<V> task) {
|
||||
ConacoTask<V> next = mRegister.getByKey(task.getKey());
|
||||
if (next != null) {
|
||||
startConacoTask(next);
|
||||
}
|
||||
}
|
||||
|
||||
private void startConacoTask(ConacoTask<V> task) {
|
||||
Unikery<V> unikery = task.getUnikery();
|
||||
|
||||
// Can't unikery is gone, finish the task now
|
||||
if (unikery == null) {
|
||||
finishConacoTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
String key = task.getKey();
|
||||
V value = null;
|
||||
|
||||
// Get from memory
|
||||
if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) {
|
||||
value = mCache.getFromMemory(key);
|
||||
}
|
||||
|
||||
if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) {
|
||||
unikery.onMiss(SOURCE_MEMORY);
|
||||
task.start();
|
||||
} else {
|
||||
// Get the object, finish the task
|
||||
finishConacoTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void load(ConacoTask.Builder<V> builder) {
|
||||
builder.isValid();
|
||||
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "Key " + builder.getKey());
|
||||
Log.d(TAG, "Url " + builder.getUrl());
|
||||
}
|
||||
|
||||
Unikery<V> unikery = builder.getUnikery();
|
||||
|
||||
// Cancel first
|
||||
cancel(unikery);
|
||||
|
||||
// Build conaco task
|
||||
int id;
|
||||
//noinspection StatementWithEmptyBody
|
||||
while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ;
|
||||
unikery.setTaskId(id);
|
||||
builder.setId(id)
|
||||
.setHelper(mHelper)
|
||||
.setCache(mCache)
|
||||
.setOkHttpClient(mOkHttpClient)
|
||||
.setDiskExecutor(mDiskExecutor)
|
||||
.setNetworkExecutor(mNetworkExecutor)
|
||||
.setConaco(this);
|
||||
ConacoTask<V> task = builder.build();
|
||||
|
||||
if (!mRegister.register(id, task)) {
|
||||
startConacoTask(task);
|
||||
} else {
|
||||
// The key is repeated, wait
|
||||
unikery.onWait();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void cancel(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
if (id != Unikery.INVALID_ID) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
ConacoTask<V> task = mRegister.unregister(id);
|
||||
if (task != null) {
|
||||
task.stop();
|
||||
// Don't need unikery anymore
|
||||
task.clearUnikery();
|
||||
// Callback
|
||||
onUnregisterConacoTask(task);
|
||||
} else {
|
||||
Log.e(TAG, "Can't find conaco task by id " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoading(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
return id != Unikery.INVALID_ID && mRegister.contain(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister task, reset unikery id, clear unikery in task, call next
|
||||
*/
|
||||
void finishConacoTask(ConacoTask<V> task) {
|
||||
mRegister.unregister(task.getId());
|
||||
Unikery unikery = task.getUnikery();
|
||||
if (unikery != null) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
}
|
||||
task.clearUnikery();
|
||||
|
||||
onUnregisterConacoTask(task);
|
||||
}
|
||||
|
||||
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Source {
|
||||
}
|
||||
|
||||
public static class Builder<T> extends BeerBelly.BeerBellyParams {
|
||||
/**
|
||||
* The client to get image from internet
|
||||
*/
|
||||
public OkHttpClient okHttpClient = null;
|
||||
|
||||
/**
|
||||
* Decode, get size and others
|
||||
*/
|
||||
public ValueHelper<T> objectHelper = null;
|
||||
|
||||
public boolean debug = false;
|
||||
|
||||
@Override
|
||||
public void isValid() throws IllegalStateException {
|
||||
super.isValid();
|
||||
|
||||
if (okHttpClient == null) {
|
||||
throw new IllegalStateException("No http client? How can I load image via url?");
|
||||
}
|
||||
}
|
||||
|
||||
public Conaco<T> build() {
|
||||
isValid();
|
||||
return new Conaco<>(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,588 +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.conaco;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.okhttp.ChromeRequestBuilder;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public final class ConacoTask<V> {
|
||||
|
||||
private static final String TAG = ConacoTask.class.getSimpleName();
|
||||
|
||||
private final int mId;
|
||||
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
||||
private final String mKey;
|
||||
private final String mUrl;
|
||||
private final DataContainer mDataContainer;
|
||||
private final boolean mUseMemoryCache;
|
||||
private final boolean mUseDiskCache;
|
||||
private final boolean mUseNetwork;
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Executor mDiskExecutor;
|
||||
private final Executor mNetworkExecutor;
|
||||
private final Conaco<V> mConaco;
|
||||
|
||||
private DiskLoadTask mDiskLoadTask;
|
||||
private NetworkLoadTask mNetworkLoadTask;
|
||||
private Call mCall;
|
||||
private boolean mStart;
|
||||
private volatile boolean mStop;
|
||||
|
||||
private ConacoTask(Builder<V> builder) {
|
||||
mId = builder.mId;
|
||||
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
||||
mKey = builder.mKey;
|
||||
mUrl = builder.mUrl;
|
||||
mDataContainer = builder.mDataContainer;
|
||||
mUseMemoryCache = builder.mUseMemoryCache;
|
||||
mUseDiskCache = builder.mUseDiskCache;
|
||||
mUseNetwork = builder.mUseNetwork;
|
||||
mHelper = builder.mHelper;
|
||||
mCache = builder.mCache;
|
||||
mOkHttpClient = builder.mOkHttpClient;
|
||||
mDiskExecutor = builder.mDiskExecutor;
|
||||
mNetworkExecutor = builder.mNetworkExecutor;
|
||||
mConaco = builder.mConaco;
|
||||
}
|
||||
|
||||
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
|
||||
SimpleDiskCache diskCache = cache.getDiskCache();
|
||||
if (diskCache != null) {
|
||||
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
container.save(pipe.open(), -1L, null, null);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Can't save value from disk cache to data container");
|
||||
e.printStackTrace();
|
||||
container.remove();
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
|
||||
InputStreamPipe pipe = container.get();
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
cache.putRawToDisk(key, pipe.open());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Can't save value from data container to disk cache", e);
|
||||
cache.removeFromDisk(key);
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
boolean useMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Unikery<V> getUnikery() {
|
||||
return mUnikeryWeakReference.get();
|
||||
}
|
||||
|
||||
void clearUnikery() {
|
||||
mUnikeryWeakReference.clear();
|
||||
}
|
||||
|
||||
private void onFinish() {
|
||||
if (!mStop) {
|
||||
mConaco.finishConacoTask(this);
|
||||
}/* else {
|
||||
// It is done by Conaco
|
||||
}*/
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void start() {
|
||||
if (mStop || mStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStart = true;
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (mUseDiskCache) {
|
||||
mDiskLoadTask = new DiskLoadTask();
|
||||
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
|
||||
return;
|
||||
} else if (mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onMiss(Conaco.SOURCE_NETWORK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void stop() {
|
||||
if (mStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStop = true;
|
||||
|
||||
// Stop jobs
|
||||
if (mDiskLoadTask != null) { // Getting from disk
|
||||
mDiskLoadTask.cancel(false);
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall.cancel();
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null) {
|
||||
unikery.onCancel();
|
||||
}
|
||||
|
||||
// Conaco handle the clean up
|
||||
}
|
||||
|
||||
private boolean isNotNecessary(AsyncTask asyncTask) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
|
||||
public static class Builder<T> {
|
||||
|
||||
private int mId;
|
||||
private Unikery<T> mUnikery;
|
||||
private String mKey;
|
||||
private String mUrl;
|
||||
private DataContainer mDataContainer;
|
||||
private boolean mUseMemoryCache = true;
|
||||
private boolean mUseDiskCache = true;
|
||||
private boolean mUseNetwork = true;
|
||||
private ValueHelper<T> mHelper;
|
||||
private ValueCache<T> mCache;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Executor mDiskExecutor;
|
||||
private Executor mNetworkExecutor;
|
||||
private Conaco<T> mConaco;
|
||||
|
||||
public Builder<T> setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Unikery<T> getUnikery() {
|
||||
return mUnikery;
|
||||
}
|
||||
|
||||
public Builder<T> setUnikery(Unikery<T> unikery) {
|
||||
mUnikery = unikery;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public Builder<T> setKey(String key) {
|
||||
mKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
public Builder<T> setUrl(String url) {
|
||||
mUrl = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setDataContainer(DataContainer dataContainer) {
|
||||
mDataContainer = dataContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
|
||||
mUseMemoryCache = useMemoryCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseDiskCache() {
|
||||
return mUseDiskCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseDiskCache(boolean useDiskCache) {
|
||||
mUseDiskCache = useDiskCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseNetwork() {
|
||||
return mUseNetwork;
|
||||
}
|
||||
|
||||
public Builder<T> setUseNetwork(boolean useNetwork) {
|
||||
mUseNetwork = useNetwork;
|
||||
return this;
|
||||
}
|
||||
|
||||
ValueHelper<T> getHelper() {
|
||||
return mHelper;
|
||||
}
|
||||
|
||||
Builder<T> setHelper(ValueHelper<T> helper) {
|
||||
mHelper = helper;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setCache(ValueCache<T> cache) {
|
||||
mCache = cache;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
|
||||
mOkHttpClient = okHttpClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setDiskExecutor(Executor diskExecutor) {
|
||||
mDiskExecutor = diskExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setNetworkExecutor(Executor networkExecutor) {
|
||||
mNetworkExecutor = networkExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setConaco(Conaco<T> conaco) {
|
||||
mConaco = conaco;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void isValid() {
|
||||
if (mUnikery == null) {
|
||||
throw new IllegalStateException("Must set unikery");
|
||||
}
|
||||
if (mKey == null && mUrl == null && mDataContainer == null) {
|
||||
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
||||
}
|
||||
}
|
||||
|
||||
public ConacoTask<T> build() {
|
||||
return new ConacoTask<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
} else {
|
||||
V value = null;
|
||||
|
||||
// First check data container
|
||||
if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp != null) {
|
||||
value = mHelper.decode(isp);
|
||||
}
|
||||
}
|
||||
|
||||
// Then check disk cache
|
||||
if (mKey != null) {
|
||||
if (value == null && mUseDiskCache) {
|
||||
value = mCache.getFromDisk(mKey);
|
||||
// Put back to data container
|
||||
if (value != null && mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V value) {
|
||||
mDiskLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(value);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
boolean getValue = false;
|
||||
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else if (!getValue) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V holder) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
|
||||
|
||||
@Override
|
||||
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
if (!isNotNecessary(this)) {
|
||||
publishProgress(singleReceivedSize, receivedSize, totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDiskCache(InputStream is, long length) {
|
||||
SimpleDiskCache diskCache = mCache.getDiskCache();
|
||||
if (diskCache == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
|
||||
try {
|
||||
pipe.obtain();
|
||||
OutputStream os = pipe.open();
|
||||
|
||||
final byte[] buffer = new byte[1024 * 4];
|
||||
long receivedSize = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
receivedSize += bytesRead;
|
||||
notifyProgress(bytesRead, receivedSize, length);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDataContainer(InputStream is, ResponseBody body) {
|
||||
// Get media type
|
||||
String mediaType;
|
||||
MediaType mt = body.contentType();
|
||||
if (mt != null) {
|
||||
mediaType = mt.type() + '/' + mt.subtype();
|
||||
} else {
|
||||
mediaType = null;
|
||||
}
|
||||
return mDataContainer.save(is, body.contentLength(), mediaType, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
V value;
|
||||
InputStream is = null;
|
||||
try {
|
||||
Log.d("TAG", "Conaco " + mUrl);
|
||||
|
||||
// Load it from internet
|
||||
Request request = new ChromeRequestBuilder(mUrl).build();
|
||||
mCall = mOkHttpClient.newCall(request);
|
||||
|
||||
Response response = mCall.execute();
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
is = body.byteStream();
|
||||
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((mDataContainer == null || !mDataContainer.isEnabled()) && mKey != null) {
|
||||
if (putToDiskCache(is, body.contentLength())) {
|
||||
// Get object from disk cache
|
||||
value = mCache.getFromDisk(mKey);
|
||||
if (value == null) {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
return null;
|
||||
}
|
||||
} else if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
// Check url Moved
|
||||
HttpUrl requestHttpUrl = request.url();
|
||||
HttpUrl responseHttpUrl = response.request().url();
|
||||
if (!responseHttpUrl.equals(requestHttpUrl)) {
|
||||
mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString());
|
||||
}
|
||||
|
||||
// Put to data container
|
||||
if (!putToDataContainer(is, body)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get value from data container
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp == null) {
|
||||
return null;
|
||||
}
|
||||
value = mHelper.decode(isp);
|
||||
if (value == null) {
|
||||
mDataContainer.remove();
|
||||
} else if (mKey != null) {
|
||||
// Put to disk cache
|
||||
putFromDataContainerToDiskCache(mKey, mCache, mDataContainer);
|
||||
|
||||
if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
mCall = null;
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V holder) {
|
||||
mNetworkLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(holder);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V value) {
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Long... values) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
|
||||
unikery.onProgress(values[0], values[1], values[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.conaco;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface DataContainer {
|
||||
|
||||
/**
|
||||
* Returns the enabled status for this DataContainer.
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Get 304 or something like that
|
||||
*/
|
||||
void onUrlMoved(String requestUrl, String responseUrl);
|
||||
|
||||
/**
|
||||
* Save the {@code InputStream}
|
||||
*/
|
||||
boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify);
|
||||
|
||||
/**
|
||||
* Get {@code InputStreamPipe} for saved before
|
||||
*/
|
||||
@Nullable
|
||||
InputStreamPipe get();
|
||||
|
||||
/**
|
||||
* Remove saved stuff
|
||||
*/
|
||||
void remove();
|
||||
}
|
||||
@@ -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.conaco;
|
||||
|
||||
import com.hippo.yorozuya.collect.Multimap;
|
||||
import com.hippo.yorozuya.collect.SparseILArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Register<V> {
|
||||
|
||||
private final SparseILArray<ConacoTask<V>> mIdMap = new SparseILArray<>();
|
||||
private final Multimap<String, ConacoTask<V>> mKeyMap = Multimap.create();
|
||||
|
||||
/**
|
||||
* @return true for the key is already registered
|
||||
*/
|
||||
public synchronized boolean register(int id, ConacoTask<V> task) {
|
||||
boolean repeatedKey = false;
|
||||
String taskKey = task.getKey();
|
||||
|
||||
// For no-key task, there no need to check
|
||||
if (taskKey != null) {
|
||||
repeatedKey = mKeyMap.countElements(taskKey) != 0;
|
||||
}
|
||||
|
||||
// Append task
|
||||
mKeyMap.putElement(taskKey, task);
|
||||
mIdMap.append(id, task);
|
||||
|
||||
return repeatedKey;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> unregister(int id) {
|
||||
ConacoTask<V> task = mIdMap.remove(id);
|
||||
if (task != null) {
|
||||
mKeyMap.removeElement(task.getKey(), task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public synchronized boolean contain(int id) {
|
||||
return mIdMap.indexOfKey(id) >= 0;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> getByKey(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ConacoTask<V>> list = mKeyMap.get(key);
|
||||
if (list != null && list.size() > 0) {
|
||||
return list.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface Unikery<V> {
|
||||
|
||||
int INVALID_ID = -1;
|
||||
|
||||
/**
|
||||
* Get the conaco task id
|
||||
*
|
||||
* @return the conaco task id
|
||||
*/
|
||||
int getTaskId();
|
||||
|
||||
/**
|
||||
* Store the conaco task id
|
||||
*
|
||||
* @param id the conaco task id
|
||||
*/
|
||||
void setTaskId(int id);
|
||||
|
||||
/**
|
||||
* On miss in the source
|
||||
*/
|
||||
void onMiss(@Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On start http request
|
||||
*/
|
||||
void onRequest();
|
||||
|
||||
/**
|
||||
* On http request progress
|
||||
*/
|
||||
void onProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
|
||||
/**
|
||||
* On start to wait because repeated key
|
||||
*/
|
||||
void onWait();
|
||||
|
||||
/**
|
||||
* On get the value
|
||||
*
|
||||
* @return {@code true} for the {@code Unikery} accepts the value
|
||||
* and the task ends. {@code false} for the {@code Unikery}
|
||||
* reject the value and the task goes on or failed
|
||||
*/
|
||||
boolean onGetValue(@NonNull V value, @Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On failed to get value
|
||||
*/
|
||||
void onFailure();
|
||||
|
||||
/**
|
||||
* On user cancel the conaco task
|
||||
*/
|
||||
void onCancel();
|
||||
}
|
||||
@@ -1,61 +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.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
class ValueCache<V> extends BeerBelly<V> {
|
||||
|
||||
private final ValueHelper<V> mHelper;
|
||||
|
||||
public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper<V> helper) {
|
||||
super(params);
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, V value) {
|
||||
return mHelper.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryAdded(V value) {
|
||||
mHelper.onAddToMemoryCache(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) {
|
||||
if (oldValue != null) {
|
||||
mHelper.onRemoveFromMemoryCache(key, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V read(@NonNull InputStreamPipe isPipe) {
|
||||
return mHelper.decode(isPipe);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean write(OutputStream os, V value) {
|
||||
throw new UnsupportedOperationException("Not support write object");
|
||||
}
|
||||
}
|
||||
@@ -1,55 +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.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
public interface ValueHelper<V> {
|
||||
|
||||
/**
|
||||
* Decode object for the {@code InputStreamPipe}
|
||||
*
|
||||
* @param isPipe the {@code InputStreamPipe}
|
||||
* @return the decoded object
|
||||
*/
|
||||
@Nullable
|
||||
V decode(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
/**
|
||||
* Get the size of the object
|
||||
*/
|
||||
int sizeOf(@NonNull String key, @NonNull V value);
|
||||
|
||||
/**
|
||||
* Called when the object added to memory cache
|
||||
*/
|
||||
void onAddToMemoryCache(@NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Called when the object removed from memory cache
|
||||
*/
|
||||
void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Use cache memory or not. Sometimes large object
|
||||
* should not be stored in memory cache.
|
||||
*/
|
||||
boolean useMemoryCache(@NonNull String key, V holder);
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class BatteryDrawable extends Drawable {
|
||||
|
||||
public static final int WARN_LIMIT = 15;
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = BatteryDrawable.class.getSimpleName();
|
||||
private final Paint mPaint;
|
||||
private final Rect mTopRect;
|
||||
private final Rect mBottomRect;
|
||||
private final Rect mRightRect;
|
||||
private final Rect mHeadRect;
|
||||
private final Rect mElectRect;
|
||||
private int mColor = Color.WHITE;
|
||||
private int mWarningColor = Color.RED;
|
||||
private int mElect = -1;
|
||||
private int mStart;
|
||||
private int mStop;
|
||||
|
||||
public BatteryDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mTopRect = new Rect();
|
||||
mBottomRect = new Rect();
|
||||
mRightRect = new Rect();
|
||||
mHeadRect = new Rect();
|
||||
mElectRect = new Rect();
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
int width = bounds.width();
|
||||
int height = bounds.height();
|
||||
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
|
||||
int turn1 = width * 6 / 7;
|
||||
int turn2 = height / 3;
|
||||
int secBottom = height - strokeWidth;
|
||||
mStart = strokeWidth;
|
||||
mStop = turn1 - strokeWidth;
|
||||
|
||||
mTopRect.set(0, 0, turn1, strokeWidth);
|
||||
mBottomRect.set(0, secBottom, turn1, height);
|
||||
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
|
||||
mHeadRect.set(turn1, turn2, width, height - turn2);
|
||||
mElectRect.set(0, strokeWidth, mStop, secBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* How to draw:<br>
|
||||
* |------------------------------|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mElect == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
|
||||
|
||||
canvas.drawRect(mTopRect, mPaint);
|
||||
canvas.drawRect(mBottomRect, mPaint);
|
||||
canvas.drawRect(mRightRect, mPaint);
|
||||
canvas.drawRect(mHeadRect, mPaint);
|
||||
canvas.drawRect(mElectRect, mPaint);
|
||||
}
|
||||
|
||||
private boolean isWarn() {
|
||||
return mElect <= WARN_LIMIT;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mColor = color;
|
||||
if (!isWarn()) {
|
||||
mPaint.setColor(mColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setWarningColor(int color) {
|
||||
if (mWarningColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWarningColor = color;
|
||||
if (isWarn()) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setElect(int elect) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
public void setElect(int elect, boolean warn) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint(warn);
|
||||
}
|
||||
|
||||
private void updatePaint() {
|
||||
updatePaint(isWarn());
|
||||
}
|
||||
|
||||
private void updatePaint(boolean warn) {
|
||||
if (warn) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
} else {
|
||||
mPaint.setColor(mColor);
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -29,17 +30,25 @@ import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
public class DrawableWrapper extends Drawable implements Drawable.Callback {
|
||||
private Drawable mDrawable;
|
||||
private Rect mBounds;
|
||||
|
||||
public DrawableWrapper(Drawable drawable) {
|
||||
this.setWrappedDrawable(drawable);
|
||||
}
|
||||
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
this.mDrawable.draw(canvas);
|
||||
if (mDrawable instanceof BitmapDrawable bitmapDrawable) {
|
||||
canvas.drawBitmap(bitmapDrawable.getBitmap(), null, mBounds, bitmapDrawable.getPaint());
|
||||
} else {
|
||||
synchronized (mDrawable) {
|
||||
mDrawable.setBounds(mBounds);
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
this.mDrawable.setBounds(bounds);
|
||||
mBounds = bounds;
|
||||
}
|
||||
|
||||
public int getChangingConfigurations() {
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.drawable;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
/**
|
||||
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
|
||||
*/
|
||||
public class DrawerArrowDrawable extends Drawable {
|
||||
|
||||
// The angle in degrees that the arrow head is inclined at.
|
||||
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
|
||||
private final Paint mPaint = new Paint();
|
||||
private final float mBarThickness;
|
||||
// The length of top and bottom bars when they merge into an arrow
|
||||
private final float mTopBottomArrowSize;
|
||||
// The length of middle bar
|
||||
private final float mBarSize;
|
||||
// The length of the middle bar when arrow is shaped
|
||||
private final float mMiddleArrowSize;
|
||||
// The space between bars when they are parallel
|
||||
private final float mBarGap;
|
||||
// Whether bars should spin or not during progress
|
||||
private final boolean mSpin;
|
||||
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
|
||||
// wont look different
|
||||
private final Path mPath = new Path();
|
||||
// The reported intrinsic size of the drawable.
|
||||
private final int mSize;
|
||||
// the amount that overlaps w/ bar size when rotation is max
|
||||
private final float mMaxCutForBarSize;
|
||||
// Whether we should mirror animation when animation is reversed.
|
||||
private boolean mVerticalMirror = false;
|
||||
// The interpolated version of the original progress
|
||||
private float mProgress;
|
||||
|
||||
/**
|
||||
* @param context used to get the configuration for the drawable from
|
||||
*/
|
||||
public DrawerArrowDrawable(Context context, int color) {
|
||||
Resources resources = context.getResources();
|
||||
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(color);
|
||||
mSize = resources.getDimensionPixelSize(R.dimen.dad_drawable_size);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarSize = Math.round(resources.getDimension(R.dimen.dad_bar_size));
|
||||
// round this because having this floating may cause bad measurements
|
||||
mTopBottomArrowSize = Math.round(resources.getDimension(R.dimen.dad_top_bottom_bar_arrow_size));
|
||||
mBarThickness = resources.getDimension(R.dimen.dad_thickness);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarGap = Math.round(resources.getDimension(R.dimen.dad_gap_between_bars));
|
||||
mSpin = resources.getBoolean(R.bool.dad_spin_bars);
|
||||
mMiddleArrowSize = resources.getDimension(R.dimen.dad_middle_bar_arrow_size);
|
||||
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeJoin(Paint.Join.MITER);
|
||||
mPaint.setStrokeCap(Paint.Cap.BUTT);
|
||||
mPaint.setStrokeWidth(mBarThickness);
|
||||
|
||||
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
||||
*/
|
||||
protected void setVerticalMirror(boolean verticalMirror) {
|
||||
mVerticalMirror = verticalMirror;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = getBounds();
|
||||
// Interpolated widths of arrow bars
|
||||
final float arrowSize = MathUtils.lerp(mBarSize, mTopBottomArrowSize, mProgress);
|
||||
final float middleBarSize = MathUtils.lerp(mBarSize, mMiddleArrowSize, mProgress);
|
||||
// Interpolated size of middle bar
|
||||
final float middleBarCut = Math.round(MathUtils.lerp(0, mMaxCutForBarSize, mProgress));
|
||||
// The rotation of the top and bottom bars (that make the arrow head)
|
||||
final float rotation = MathUtils.lerp(0, ARROW_HEAD_ANGLE, mProgress);
|
||||
|
||||
// The whole canvas rotates as the transition happens
|
||||
final float canvasRotate = MathUtils.lerp(-180, 0, mProgress);
|
||||
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
|
||||
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
|
||||
|
||||
mPath.rewind();
|
||||
final float topBottomBarOffset = MathUtils.lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
|
||||
mProgress);
|
||||
|
||||
final float arrowEdge = -middleBarSize / 2;
|
||||
// draw middle bar
|
||||
mPath.moveTo(arrowEdge + middleBarCut, 0);
|
||||
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
|
||||
|
||||
// bottom bar
|
||||
mPath.moveTo(arrowEdge, topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, arrowHeight);
|
||||
|
||||
// top bar
|
||||
mPath.moveTo(arrowEdge, -topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, -arrowHeight);
|
||||
|
||||
mPath.close();
|
||||
|
||||
canvas.save();
|
||||
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
|
||||
// the arrow pointing the other way for RTL.
|
||||
canvas.translate(bounds.centerX(), bounds.centerY());
|
||||
if (mSpin) {
|
||||
canvas.rotate(canvasRotate * (mVerticalMirror ? -1 : 1));
|
||||
}
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void setColor(@ColorInt int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) {
|
||||
mPaint.setAlpha(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setProgress(float progress) {
|
||||
if (progress == 1f) {
|
||||
setVerticalMirror(true);
|
||||
} else if (progress == 0f) {
|
||||
setVerticalMirror(false);
|
||||
}
|
||||
mProgress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setMenu(long duration) {
|
||||
setShape(false, duration);
|
||||
}
|
||||
|
||||
public void setArrow(long duration) {
|
||||
setShape(true, duration);
|
||||
}
|
||||
|
||||
public void setShape(boolean arrow, long duration) {
|
||||
if (!((!arrow && mProgress == 0f) || (arrow && mProgress == 1f))) {
|
||||
float endProgress = arrow ? 1f : 0f;
|
||||
if (duration <= 0) {
|
||||
setProgress(endProgress);
|
||||
} else {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
|
||||
oa.setDuration(duration);
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,31 +16,25 @@
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.drawable.AnimatedImageDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
||||
ObservedTextView.OnWindowAttachListener {
|
||||
import coil.Coil;
|
||||
import coil.request.Disposable;
|
||||
import coil.request.ImageRequest;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements ObservedTextView.OnWindowAttachListener {
|
||||
|
||||
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
private String mUrl;
|
||||
private Disposable task;
|
||||
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
public UnikeryDrawable(ObservedTextView textView) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,22 +44,28 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
mConaco.cancel(this);
|
||||
if (task != null && !task.isDisposed())
|
||||
task.dispose();
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
public void load(String url) {
|
||||
if (url != null) {
|
||||
mUrl = url;
|
||||
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
|
||||
var imageLoader = Coil.imageLoader(mTextView.getContext());
|
||||
var request = new ImageRequest.Builder(mTextView.getContext()).data(url).memoryCacheKey(url).diskCacheKey(url).target(
|
||||
drawable -> null,
|
||||
drawable -> null,
|
||||
drawable -> {
|
||||
onGetValue(drawable);
|
||||
return null;
|
||||
}
|
||||
).build();
|
||||
task = imageLoader.enqueue(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
((ImageDrawable) drawable).recycle();
|
||||
}
|
||||
setDrawable(null);
|
||||
}
|
||||
|
||||
@@ -89,63 +89,16 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskId() {
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskId(int id) {
|
||||
mTaskId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateSelf() {
|
||||
CharSequence cs = mTextView.getText();
|
||||
mTextView.setText(cs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiss(int source) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWait() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
ImageDrawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onGetValue(Drawable drawable) {
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
drawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
// Empty
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
|
||||
animatedImageDrawable.start();
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,7 @@ public final class LayoutManagerUtils {
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||
int millisecondsPerInch) {
|
||||
SimpleSmoothScroller smoothScroller;
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
if (layoutManager instanceof final LinearLayoutManager linearLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
@@ -73,8 +72,7 @@ public final class LayoutManagerUtils {
|
||||
}
|
||||
};
|
||||
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
||||
} else if (layoutManager instanceof final StaggeredGridLayoutManager staggeredGridLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -1,597 +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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.collection.LruCache;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.Native;
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.conaco.Conaco;
|
||||
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.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.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.BitmapUtils;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class EhApplication extends SceneApplication {
|
||||
|
||||
public static final boolean BETA = false;
|
||||
private static final String TAG = EhApplication.class.getSimpleName();
|
||||
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
|
||||
private static final boolean DEBUG_CONACO = false;
|
||||
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
|
||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = true;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
|
||||
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
|
||||
private final List<Activity> mActivityList = new ArrayList<>();
|
||||
private EhCookieStore mEhCookieStore;
|
||||
private EhClient mEhClient;
|
||||
private EhProxySelector mEhProxySelector;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Cache mOkHttpCache;
|
||||
private ImageBitmapHelper mImageBitmapHelper;
|
||||
private Conaco<ImageBitmap> mConaco;
|
||||
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
|
||||
private SimpleDiskCache mSpiderInfoCache;
|
||||
private DownloadManager mDownloadManager;
|
||||
private Hosts mHosts;
|
||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
||||
private boolean initialized = false;
|
||||
|
||||
public static EhApplication getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhCookieStore == null) {
|
||||
application.mEhCookieStore = new EhCookieStore(context);
|
||||
}
|
||||
return application.mEhCookieStore;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhClient getEhClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhClient == null) {
|
||||
application.mEhClient = new EhClient(application);
|
||||
}
|
||||
return application.mEhClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhProxySelector getEhProxySelector(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhProxySelector == null) {
|
||||
application.mEhProxySelector = new EhProxySelector();
|
||||
}
|
||||
return application.mEhProxySelector;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpClient == null) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.callTimeout(10, TimeUnit.SECONDS)
|
||||
.cookieJar(getEhCookieStore(application))
|
||||
.cache(getOkHttpCache(application))
|
||||
.dns(new EhDns(application))
|
||||
.addNetworkInterceptor(chain -> {
|
||||
try {
|
||||
return chain.proceed(chain.request());
|
||||
} catch (NullPointerException e) {
|
||||
// crash on meizu devices due to old Android version
|
||||
// https://github.com/square/okhttp/issues/3301#issuecomment-348415095
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
})
|
||||
.proxySelector(getEhProxySelector(application));
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), new EhX509TrustManager());
|
||||
}
|
||||
application.mOkHttpClient = builder.build();
|
||||
}
|
||||
return application.mOkHttpClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Cache getOkHttpCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpCache == null) {
|
||||
application.mOkHttpCache = new Cache(new File(application.getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
|
||||
}
|
||||
return application.mOkHttpCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mImageBitmapHelper == null) {
|
||||
application.mImageBitmapHelper = new ImageBitmapHelper();
|
||||
}
|
||||
return application.mImageBitmapHelper;
|
||||
}
|
||||
|
||||
private static int getMemoryCacheMaxSize() {
|
||||
return Math.min(20 * 1024 * 1024, (int) OSUtils.getAppMaxMemory());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mConaco == null) {
|
||||
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
|
||||
builder.hasMemoryCache = true;
|
||||
builder.memoryCacheMaxSize = getMemoryCacheMaxSize();
|
||||
builder.hasDiskCache = true;
|
||||
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
|
||||
builder.diskCacheMaxSize = 320 * 1024 * 1024; // 320MB
|
||||
builder.okHttpClient = getOkHttpClient(context);
|
||||
builder.objectHelper = getImageBitmapHelper(context);
|
||||
builder.debug = DEBUG_CONACO;
|
||||
application.mConaco = builder.build();
|
||||
}
|
||||
return application.mConaco;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LruCache<Long, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mGalleryDetailCache == null) {
|
||||
// Max size 25, 3 min timeout
|
||||
application.mGalleryDetailCache = new LruCache<>(25);
|
||||
getFavouriteStatusRouter().addListener((gid, slot) -> {
|
||||
GalleryDetail gd = application.mGalleryDetailCache.get(gid);
|
||||
if (gd != null) {
|
||||
gd.favoriteSlot = slot;
|
||||
}
|
||||
});
|
||||
}
|
||||
return application.mGalleryDetailCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SimpleDiskCache getSpiderInfoCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (null == application.mSpiderInfoCache) {
|
||||
application.mSpiderInfoCache = new SimpleDiskCache(
|
||||
new File(context.getCacheDir(), "spider_info"), 20 * 1024 * 1024); // 20M
|
||||
}
|
||||
return application.mSpiderInfoCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager() {
|
||||
return getDownloadManager(instance);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mDownloadManager == null) {
|
||||
application.mDownloadManager = new DownloadManager(application);
|
||||
}
|
||||
return application.mDownloadManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Hosts getHosts(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mHosts == null) {
|
||||
application.mHosts = new Hosts(application, "hosts.db");
|
||||
}
|
||||
return application.mHosts;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter() {
|
||||
return getFavouriteStatusRouter(getInstance());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mFavouriteStatusRouter == null) {
|
||||
application.mFavouriteStatusRouter = new FavouriteStatusRouter();
|
||||
}
|
||||
return application.mFavouriteStatusRouter;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
|
||||
instance = this;
|
||||
|
||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
try {
|
||||
// Always save crash file if onCreate() is not done
|
||||
if (!initialized || Settings.getSaveCrashLog()) {
|
||||
Crash.saveCrashLog(instance, e);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
handler.uncaughtException(t, e);
|
||||
}
|
||||
});
|
||||
|
||||
super.onCreate();
|
||||
|
||||
Native.initialize();
|
||||
ClipboardUtil.initialize(this);
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
Settings.initialize(this);
|
||||
ReadableTime.initialize(this);
|
||||
AppConfig.initialize(this);
|
||||
SpiderDen.initialize(this);
|
||||
EhDB.initialize(this);
|
||||
EhEngine.initialize();
|
||||
BitmapUtils.initialize(this);
|
||||
|
||||
// Locales can be managed by system automatically above Snow Cone v2
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
||||
|
||||
// Do io tasks in new thread
|
||||
//noinspection deprecation
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
// Check no media file
|
||||
try {
|
||||
UniFile downloadLocation = Settings.getDownloadLocation();
|
||||
if (Settings.getMediaScan()) {
|
||||
CommonOperations.removeNoMediaFile(downloadLocation);
|
||||
} else {
|
||||
CommonOperations.ensureNoMediaFile(downloadLocation);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
// Clear temp files
|
||||
try {
|
||||
clearTempDir();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
|
||||
|
||||
// Check app update
|
||||
update();
|
||||
|
||||
// Update version code
|
||||
try {
|
||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
Settings.putVersionCode(pi.versionCode);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
|
||||
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
|
||||
debugPrint();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
theDawnOfNewDay();
|
||||
}
|
||||
|
||||
private void theDawnOfNewDay() {
|
||||
if (!Settings.getRequestNews()) {
|
||||
return;
|
||||
}
|
||||
EhCookieStore store = getEhCookieStore(this);
|
||||
HttpUrl eh = HttpUrl.get(EhUrl.HOST_E);
|
||||
|
||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(eh, EhCookieStore.KEY_IPD_PASS_HASH)) {
|
||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
||||
String referer = EhUrl.REFERER_E;
|
||||
Request request = new EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build();
|
||||
Call call = getOkHttpClient(this).newCall(request);
|
||||
try {
|
||||
Response response = call.execute();
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
return;
|
||||
}
|
||||
String body = responseBody.string();
|
||||
String html = EventPaneParser.parse(body);
|
||||
if (html != null) {
|
||||
showEventPane(html);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void showEventPane(String html) {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
dialog.setOnShowListener(d -> {
|
||||
final View messageView = dialog.findViewById(android.R.id.message);
|
||||
if (messageView instanceof TextView) {
|
||||
((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
});
|
||||
try {
|
||||
dialog.show();
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void clearTempDir() {
|
||||
File dir = AppConfig.getTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
dir = AppConfig.getExternalTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
int version = Settings.getVersionCode();
|
||||
if (version < 52) {
|
||||
Settings.putGuideGallery(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearMemoryCache() {
|
||||
if (null != mConaco) {
|
||||
mConaco.getBeerBelly().clearMemory();
|
||||
}
|
||||
if (null != mGalleryDetailCache) {
|
||||
mGalleryDetailCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
clearMemoryCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void debugPrint() {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY) {
|
||||
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
|
||||
Debug.getNativeHeapAllocatedSize(), false));
|
||||
}
|
||||
if (DEBUG_PRINT_IMAGE_COUNT) {
|
||||
Log.i(TAG, "Image count: " + Image.getImageCount());
|
||||
}
|
||||
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
|
||||
public int putGlobalStuff(@NonNull Object o) {
|
||||
int id = mIdGenerator.nextId();
|
||||
mGlobalStuffMap.put(id, o);
|
||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean containGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.containsKey(id);
|
||||
}
|
||||
|
||||
public Object getGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.get(id);
|
||||
}
|
||||
|
||||
public Object removeGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.remove(id);
|
||||
}
|
||||
|
||||
public void removeGlobalStuff(Object o) {
|
||||
mGlobalStuffMap.values().removeAll(Collections.singleton(o));
|
||||
}
|
||||
|
||||
public void registerActivity(Activity activity) {
|
||||
mActivityList.add(activity);
|
||||
}
|
||||
|
||||
public void unregisterActivity(Activity activity) {
|
||||
mActivityList.remove(activity);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EhActivity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startService(Intent service) {
|
||||
try {
|
||||
return super.startService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
||||
try {
|
||||
return super.bindService(service, conn, flags);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public void unbindService(ServiceConnection conn) {
|
||||
try {
|
||||
super.unbindService(conn);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startForegroundService(Intent service) {
|
||||
try {
|
||||
return super.startForegroundService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.LruCache
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import coil.disk.DiskCache
|
||||
import com.hippo.Native
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhDns
|
||||
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.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 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 : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
||||
private val mIdGenerator = IntIdGenerator()
|
||||
private val mGlobalStuffMap = HashMap<Int, Any>()
|
||||
private val mActivityList = ArrayList<Activity>()
|
||||
val topActivity: EhActivity?
|
||||
get() = if (mActivityList.isNotEmpty()) {
|
||||
mActivityList[mActivityList.size - 1] as EhActivity
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun recreateAllActivity() {
|
||||
mActivityList.forEach { it.recreate() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
override fun onCreate() {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
application = this
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
try {
|
||||
if (Settings.getSaveCrashLog()) {
|
||||
Crash.saveCrashLog(application, e)
|
||||
}
|
||||
} catch (ignored: Throwable) {
|
||||
}
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super<Application>.onCreate()
|
||||
Native.initialize()
|
||||
GetText.initialize(this)
|
||||
Settings.initialize()
|
||||
ReadableTime.initialize(this)
|
||||
AppConfig.initialize(this)
|
||||
SpiderDen.initialize(this)
|
||||
BitmapUtils.initialize(this)
|
||||
EhTagDatabase.update()
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
|
||||
launchIO {
|
||||
launchIO {
|
||||
ehDatabase
|
||||
}
|
||||
launchIO {
|
||||
downloadManager
|
||||
}
|
||||
launchIO {
|
||||
cleanupDownload()
|
||||
}
|
||||
launchIO {
|
||||
theDawnOfNewDay()
|
||||
}
|
||||
}
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showEventPane(html: String) {
|
||||
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
|
||||
return
|
||||
}
|
||||
val activity = topActivity
|
||||
activity?.runOnUiThread {
|
||||
val dialog = BaseDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
dialog.setOnShowListener {
|
||||
val messageView = dialog.findViewById<View>(android.R.id.message)
|
||||
if (messageView is TextView) {
|
||||
messageView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
try {
|
||||
dialog.show()
|
||||
} catch (t: Throwable) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearTempDir() {
|
||||
var dir = AppConfig.getTempDir()
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
dir = AppConfig.getExternalTempDir()
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
galleryDetailCache.evictAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun putGlobalStuff(o: Any): Int {
|
||||
val id = mIdGenerator.nextId()
|
||||
mGlobalStuffMap[id] = o
|
||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId())
|
||||
return id
|
||||
}
|
||||
|
||||
fun containGlobalStuff(id: Int): Boolean {
|
||||
return mGlobalStuffMap.containsKey(id)
|
||||
}
|
||||
|
||||
fun getGlobalStuff(id: Int): Any? {
|
||||
return mGlobalStuffMap[id]
|
||||
}
|
||||
|
||||
fun removeGlobalStuff(id: Int): Any? {
|
||||
return mGlobalStuffMap.remove(id)
|
||||
}
|
||||
|
||||
fun removeGlobalStuff(o: Any) {
|
||||
mGlobalStuffMap.values.removeAll(setOf(o))
|
||||
}
|
||||
|
||||
fun registerActivity(activity: Activity) {
|
||||
mActivityList.add(activity)
|
||||
}
|
||||
|
||||
fun unregisterActivity(activity: Activity) {
|
||||
mActivityList.remove(activity)
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000
|
||||
locked = true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
return ImageLoader.Builder(this).apply {
|
||||
okHttpClient(okHttpClient)
|
||||
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||
// Coil spawns a new thread for every image load by default
|
||||
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
|
||||
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
}.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id"
|
||||
var locked = true
|
||||
var locked_last_leave_time: Long = 0
|
||||
|
||||
@JvmStatic
|
||||
lateinit var application: EhApplication
|
||||
private set
|
||||
|
||||
@JvmStatic
|
||||
val ehCookieStore by lazy { EhCookieStore }
|
||||
|
||||
@JvmStatic
|
||||
val ehProxySelector by lazy { EhProxySelector() }
|
||||
|
||||
@JvmStatic
|
||||
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
|
||||
}
|
||||
|
||||
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
|
||||
builder.proxy(Proxy.NO_PROXY)
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val galleryDetailCache by lazy {
|
||||
LruCache<Long, GalleryDetail>(25).also {
|
||||
favouriteStatusRouter.addListener { gid, slot ->
|
||||
it[gid]?.favoriteSlot = slot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val spiderInfoCache by lazy {
|
||||
DiskCache.Builder().directory(File(application.cacheDir, "spider_info"))
|
||||
.maxSizeBytes(20 * 1024 * 1024).build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
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,34 @@ public class EhDB {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static synchronized void moveDownloadInfo(int fromPosition, int toPosition) {
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean reverse = fromPosition > toPosition;
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
List<DownloadInfo> list = dao.list(offset, limit);
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
int end = reverse ? 0 : limit - 1;
|
||||
long toTime = list.get(end).time;
|
||||
for (int i = end; reverse ? i < start : i > start; i += step) {
|
||||
list.get(i).time = list.get(i + step).time;
|
||||
}
|
||||
list.get(start).time = toTime;
|
||||
|
||||
dao.update(list);
|
||||
}
|
||||
|
||||
// Insert or update
|
||||
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
if (null != dao.load(downloadInfo.gid)) {
|
||||
if (null != dao.load(downloadInfo.getGid())) {
|
||||
// Update
|
||||
dao.update(downloadInfo);
|
||||
} else {
|
||||
@@ -234,7 +254,7 @@ public class EhDB {
|
||||
|
||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
|
||||
return new ArrayList<>(list);
|
||||
}
|
||||
|
||||
@@ -256,7 +276,7 @@ public class EhDB {
|
||||
|
||||
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
if (null == dao.load(galleryInfo.gid)) {
|
||||
if (null == dao.load(galleryInfo.getGid())) {
|
||||
LocalFavoriteInfo info;
|
||||
if (galleryInfo instanceof LocalFavoriteInfo) {
|
||||
info = (LocalFavoriteInfo) galleryInfo;
|
||||
@@ -281,9 +301,9 @@ public class EhDB {
|
||||
|
||||
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
quickSearch.id = null;
|
||||
quickSearch.time = System.currentTimeMillis();
|
||||
quickSearch.id = dao.insert(quickSearch);
|
||||
quickSearch.setId(null);
|
||||
quickSearch.setTime(System.currentTimeMillis());
|
||||
quickSearch.setId(dao.insert(quickSearch));
|
||||
}
|
||||
|
||||
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
|
||||
@@ -293,11 +313,6 @@ public class EhDB {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
dao.update(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
dao.delete(quickSearch);
|
||||
@@ -327,25 +342,22 @@ public class EhDB {
|
||||
dao.update(list);
|
||||
}
|
||||
|
||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
||||
return db.historyDao().list();
|
||||
}
|
||||
|
||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||
return db.historyDao().listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
if (null != info) {
|
||||
// Update time
|
||||
info.time = System.currentTimeMillis();
|
||||
HistoryInfo info;
|
||||
if (galleryInfo instanceof HistoryInfo) {
|
||||
info = (HistoryInfo) galleryInfo;
|
||||
} else {
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
}
|
||||
info.time = System.currentTimeMillis();
|
||||
if (null != dao.load(info.getGid())) {
|
||||
dao.update(info);
|
||||
} else {
|
||||
// New history
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
@@ -353,7 +365,7 @@ public class EhDB {
|
||||
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
for (HistoryInfo info : historyInfoList) {
|
||||
if (null == dao.load(info.gid)) {
|
||||
if (null == dao.load(info.getGid())) {
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
@@ -394,15 +406,14 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized void triggerFilter(Filter filter) {
|
||||
filter.setEnable(!filter.enable);
|
||||
filter.enable = !filter.enable;
|
||||
db.filterDao().update(filter);
|
||||
}
|
||||
|
||||
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
private static <T> void copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
List<T> list = from.list();
|
||||
for (T item : list)
|
||||
to.insert(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||
@@ -414,20 +425,18 @@ public class EhDB {
|
||||
|
||||
try {
|
||||
// Copy data to a export db
|
||||
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
|
||||
return false;
|
||||
if (copyDao(db.historyDao(), newDb.historyDao()))
|
||||
return false;
|
||||
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
|
||||
return false;
|
||||
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
|
||||
return false;
|
||||
if (copyDao(db.filterDao(), newDb.filterDao()))
|
||||
return false;
|
||||
copyDao(db.downloadsDao(), newDb.downloadsDao());
|
||||
copyDao(db.downloadLabelDao(), newDb.downloadLabelDao());
|
||||
copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao());
|
||||
copyDao(db.historyDao(), newDb.historyDao());
|
||||
copyDao(db.quickSearchDao(), newDb.quickSearchDao());
|
||||
copyDao(db.localFavoritesDao(), newDb.localFavoritesDao());
|
||||
copyDao(db.filterDao(), newDb.filterDao());
|
||||
|
||||
// Close export db so we can copy it
|
||||
if (newDb.isOpen()) {
|
||||
newDb.close();
|
||||
}
|
||||
|
||||
// Copy export db to data dir
|
||||
File dbFile = context.getDatabasePath(ehExportName);
|
||||
@@ -447,6 +456,7 @@ public class EhDB {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
|
||||
// Delete failed file
|
||||
return false;
|
||||
} finally {
|
||||
@@ -460,6 +470,8 @@ public class EhDB {
|
||||
public static synchronized String importDB(Context context, Uri uri) {
|
||||
try {
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
|
||||
// Copy file to cache dir
|
||||
File file = File.createTempFile("importDatabase", "");
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
byte[] buff = new byte[1024];
|
||||
@@ -474,6 +486,7 @@ public class EhDB {
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
// Check database version
|
||||
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
int newVersion = CUR_DB_VER;
|
||||
@@ -482,69 +495,96 @@ public class EhDB {
|
||||
upgradeDB(oldDB, oldVersion);
|
||||
oldDB.setVersion(newVersion);
|
||||
} else if (oldVersion > newVersion) {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
return context.getString(R.string.db_version_too_high);
|
||||
}
|
||||
|
||||
// Crete temp room database from cache file
|
||||
String tmpDBName = "tmp.db";
|
||||
context.deleteDatabase(tmpDBName);
|
||||
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
||||
.createFromFile(file).allowMainThreadQueries().build();
|
||||
|
||||
// Download label
|
||||
DownloadManager manager = EhApplication.getDownloadManager();
|
||||
try {
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Downloads
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
|
||||
// Download label
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Download dirname
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
try {
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// History
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// QuickSearch
|
||||
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
|
||||
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
try {
|
||||
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
|
||||
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.getName();
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.getName(), name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
|
||||
// LocalFavorites
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
try {
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Filter
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
try {
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Delete temp database
|
||||
if (oldRoomDatabase.isOpen()) {
|
||||
oldRoomDatabase.close();
|
||||
}
|
||||
context.deleteDatabase(tmpDBName);
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 512 * 512;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
|
||||
return value.getWidth() * value.getHeight() * 4 /* value.getByteCount() TODO Update Image */;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
|
||||
oldValue.obtain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
|
||||
oldValue.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
|
||||
if (value != null) {
|
||||
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE
|
||||
/* value.getByteCount() <= MAX_CACHE_BYTE_COUNT TODO Update Image */;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
@@ -27,19 +26,13 @@ import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.glgallery.GalleryView;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -50,19 +43,18 @@ public class Settings {
|
||||
********************/
|
||||
|
||||
public static final String KEY_THEME = "theme";
|
||||
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
||||
public static final int THEME_LIGHT = 1;
|
||||
public static final int THEME_SYSTEM = -1;
|
||||
public static final int THEME_BLACK = 2;
|
||||
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
|
||||
public static final String KEY_GALLERY_SITE = "gallery_site";
|
||||
public static final String KEY_LIST_MODE = "list_mode";
|
||||
public static final String KEY_DETAIL_SIZE = "detail_size";
|
||||
public static final String KEY_THUMB_SIZE = "thumb_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_TAG_TRANSLATIONS = "show_tag_translations";
|
||||
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
|
||||
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";
|
||||
/********************
|
||||
@@ -76,7 +68,6 @@ public class Settings {
|
||||
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
|
||||
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
|
||||
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
|
||||
|
||||
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
|
||||
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
|
||||
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
|
||||
@@ -84,49 +75,40 @@ public class Settings {
|
||||
public static final String KEY_IMAGE_RESOLUTION = "image_size";
|
||||
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
|
||||
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
|
||||
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
|
||||
/********************
|
||||
****** Advanced
|
||||
********************/
|
||||
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
|
||||
public static final String KEY_SECURITY = "require_unlock";
|
||||
public static final String DEFAULT_SECURITY = "";
|
||||
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
|
||||
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
|
||||
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
|
||||
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_APP_LANGUAGE = "app_language";
|
||||
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 TAG = Settings.class.getSimpleName();
|
||||
private static final String KEY_VERSION_CODE = "version_code";
|
||||
private static final int DEFAULT_VERSION_CODE = 0;
|
||||
private static final String KEY_DISPLAY_NAME = "display_name";
|
||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
||||
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
|
||||
private static final String KEY_AVATAR = "avatar";
|
||||
private static final String DEFAULT_AVATAR = null;
|
||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
||||
private static final boolean DEFAULT_SHOW_WARNING = true;
|
||||
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
|
||||
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
|
||||
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
|
||||
private static final boolean DEFAULT_NEED_SIGN_IN = true;
|
||||
private static final String KEY_SELECT_SITE = "select_site";
|
||||
private static final boolean DEFAULT_SELECT_SITE = true;
|
||||
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
|
||||
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
|
||||
private static final int DEFAULT_THEME = THEME_SYSTEM;
|
||||
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
|
||||
private static final int DEFAULT_GALLERY_SITE = 1;
|
||||
private static final String KEY_LAUNCH_PAGE = "launch_page";
|
||||
private static final int DEFAULT_LAUNCH_PAGE = 0;
|
||||
private static final int DEFAULT_LIST_MODE = 0;
|
||||
private static final int DEFAULT_DETAIL_SIZE = 0;
|
||||
private static final int DEFAULT_THUMB_SIZE = 1;
|
||||
private static final int DEFAULT_THUMB_SIZE = 120;
|
||||
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
||||
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
|
||||
private static final boolean DEFAULT_FIX_THUMB_URL = false;
|
||||
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";
|
||||
@@ -138,41 +120,6 @@ public class Settings {
|
||||
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
|
||||
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
|
||||
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
|
||||
private static final String KEY_NIGHT_MODE = "night_mode";
|
||||
private static final String DEFAULT_NIGHT_MODE = "-1";
|
||||
private static final String KEY_E_INK_MODE = "e_ink_mode_2";
|
||||
private static final boolean DEFAULT_E_INK_MODE = false;
|
||||
/********************
|
||||
****** Read
|
||||
********************/
|
||||
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
|
||||
private static final int DEFAULT_SCREEN_ROTATION = 0;
|
||||
private static final String KEY_READING_DIRECTION = "reading_direction";
|
||||
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
|
||||
private static final String KEY_PAGE_SCALING = "page_scaling";
|
||||
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
|
||||
private static final String KEY_START_POSITION = "start_position";
|
||||
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
|
||||
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
|
||||
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
|
||||
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
|
||||
private static final boolean DEFAULT_SHOW_CLOCK = true;
|
||||
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
|
||||
private static final boolean DEFAULT_SHOW_PROGRESS = true;
|
||||
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
|
||||
private static final boolean DEFAULT_SHOW_BATTERY = true;
|
||||
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
|
||||
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
|
||||
private static final String KEY_VOLUME_PAGE = "volume_page";
|
||||
private static final boolean DEFAULT_VOLUME_PAGE = false;
|
||||
private static final String KEY_REVERSE_VOLUME_PAGE = "reserve_volume_page";
|
||||
private static final boolean DEFAULT_REVERSE_VOLUME_PAGE = false;
|
||||
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
|
||||
private static final boolean VALUE_READING_FULLSCREEN = true;
|
||||
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
|
||||
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
|
||||
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
|
||||
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
|
||||
private static final boolean DEFAULT_MEDIA_SCAN = false;
|
||||
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
|
||||
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
|
||||
@@ -186,8 +133,6 @@ public class Settings {
|
||||
private static final int DEFAULT_PRELOAD_IMAGE = 5;
|
||||
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
|
||||
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
|
||||
private static final String KEY_READ_THEME = "read_theme";
|
||||
private static final int DEFAULT_READ_THEME = 1;
|
||||
/********************
|
||||
****** Favorites
|
||||
********************/
|
||||
@@ -229,47 +174,17 @@ public class Settings {
|
||||
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
|
||||
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
|
||||
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
|
||||
/********************
|
||||
****** Analytics
|
||||
********************/
|
||||
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
|
||||
private static final boolean DEFAULT_ASK_ANALYTICS = true;
|
||||
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
|
||||
private static final String KEY_USER_ID = "user_id";
|
||||
private static final String FILENAME_USER_ID = ".user_id";
|
||||
private static final int LENGTH_USER_ID = 32;
|
||||
/********************
|
||||
****** Update
|
||||
********************/
|
||||
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
|
||||
private static final boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
|
||||
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
|
||||
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
|
||||
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
|
||||
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
|
||||
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 String DEFAULT_APP_LANGUAGE = "system";
|
||||
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";
|
||||
private static final String DEFAULT_PROXY_IP = null;
|
||||
private static final String KEY_PROXY_PORT = "proxy_port";
|
||||
private static final int DEFAULT_PROXY_PORT = -1;
|
||||
/********************
|
||||
****** Guide
|
||||
********************/
|
||||
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
|
||||
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
|
||||
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
|
||||
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
|
||||
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
|
||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
|
||||
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
|
||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
|
||||
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
|
||||
private static final boolean DEFAULT_GUIDE_GALLERY = true;
|
||||
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
|
||||
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
|
||||
private static final String KEY_DOWNLOAD_DELAY = "download_delay";
|
||||
@@ -277,20 +192,20 @@ public class Settings {
|
||||
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;
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sContext = context.getApplicationContext();
|
||||
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
|
||||
public static void initialize() {
|
||||
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(EhApplication.getApplication());
|
||||
sEhConfig = loadEhConfig();
|
||||
fixDefaultValue(context);
|
||||
fixDefaultValue();
|
||||
}
|
||||
|
||||
private static void fixDefaultValue(Context context) {
|
||||
private static void fixDefaultValue() {
|
||||
if ("CN".equals(Locale.getDefault().getCountry())) {
|
||||
// Enable domain fronting if the country is CN
|
||||
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
|
||||
@@ -307,16 +222,6 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static Locale getLocale() {
|
||||
Locale locale = null;
|
||||
String language = Settings.getAppLanguage();
|
||||
if (language != null && !language.equals("system")) {
|
||||
return Locale.forLanguageTag(language);
|
||||
} else {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private static EhConfig loadEhConfig() {
|
||||
EhConfig ehConfig = new EhConfig();
|
||||
ehConfig.imageSize = getImageResolution();
|
||||
@@ -353,32 +258,6 @@ public class Settings {
|
||||
sSettingsPre.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
public static long getLong(String key, long defValue) {
|
||||
try {
|
||||
return sSettingsPre.getLong(key, defValue);
|
||||
} catch (ClassCastException e) {
|
||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void putLong(String key, long value) {
|
||||
sSettingsPre.edit().putLong(key, value).apply();
|
||||
}
|
||||
|
||||
public static float getFloat(String key, float defValue) {
|
||||
try {
|
||||
return sSettingsPre.getFloat(key, defValue);
|
||||
} catch (ClassCastException e) {
|
||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void putFloat(String key, float value) {
|
||||
sSettingsPre.edit().putFloat(key, value).apply();
|
||||
}
|
||||
|
||||
public static String getString(String key, String defValue) {
|
||||
try {
|
||||
return sSettingsPre.getString(key, defValue);
|
||||
@@ -420,14 +299,7 @@ public class Settings {
|
||||
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
|
||||
}
|
||||
|
||||
public static int getVersionCode() {
|
||||
return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
|
||||
}
|
||||
|
||||
public static void putVersionCode(int value) {
|
||||
putInt(KEY_VERSION_CODE, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getDisplayName() {
|
||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||
}
|
||||
@@ -436,22 +308,10 @@ public class Settings {
|
||||
putString(KEY_DISPLAY_NAME, value);
|
||||
}
|
||||
|
||||
public static String getAvatar() {
|
||||
return getString(KEY_AVATAR, DEFAULT_AVATAR);
|
||||
}
|
||||
|
||||
public static void putAvatar(String value) {
|
||||
putString(KEY_AVATAR, value);
|
||||
}
|
||||
|
||||
public static boolean getShowWarning() {
|
||||
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
|
||||
}
|
||||
|
||||
public static void putShowWarning(boolean value) {
|
||||
putBoolean(KEY_SHOW_WARNING, value);
|
||||
}
|
||||
|
||||
public static boolean getRemoveImageFiles() {
|
||||
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
|
||||
}
|
||||
@@ -484,14 +344,6 @@ public class Settings {
|
||||
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
|
||||
}
|
||||
|
||||
public static void putTheme(int theme) {
|
||||
putIntToStr(KEY_THEME, theme);
|
||||
}
|
||||
|
||||
public static boolean getApplyNavBarThemeColor() {
|
||||
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
|
||||
}
|
||||
|
||||
public static int getGallerySite() {
|
||||
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
|
||||
}
|
||||
@@ -502,17 +354,13 @@ public class Settings {
|
||||
|
||||
public static String getLaunchPageGalleryListSceneAction() {
|
||||
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
|
||||
switch (value) {
|
||||
default:
|
||||
case 0:
|
||||
return GalleryListScene.ACTION_HOMEPAGE;
|
||||
case 1:
|
||||
return GalleryListScene.ACTION_SUBSCRIPTION;
|
||||
case 2:
|
||||
return GalleryListScene.ACTION_WHATS_HOT;
|
||||
case 3:
|
||||
return GalleryListScene.ACTION_TOP_LIST;
|
||||
}
|
||||
return switch (value) {
|
||||
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
|
||||
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
|
||||
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
|
||||
case 3 -> GalleryListScene.ACTION_TOP_LIST;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + value);
|
||||
};
|
||||
}
|
||||
|
||||
public static int getListMode() {
|
||||
@@ -525,44 +373,38 @@ public class Settings {
|
||||
|
||||
@DimenRes
|
||||
public static int getDetailSizeResId() {
|
||||
switch (getDetailSize()) {
|
||||
default:
|
||||
case 0:
|
||||
return R.dimen.gallery_list_column_width_long;
|
||||
case 1:
|
||||
return R.dimen.gallery_list_column_width_short;
|
||||
}
|
||||
return switch (getDetailSize()) {
|
||||
case 0 -> R.dimen.gallery_list_column_width_long;
|
||||
case 1 -> R.dimen.gallery_list_column_width_short;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
|
||||
};
|
||||
}
|
||||
|
||||
public static int getThumbSize() {
|
||||
return getIntFromStr(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE);
|
||||
return dip2px(getInt(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE));
|
||||
}
|
||||
|
||||
@DimenRes
|
||||
public static int getThumbSizeResId() {
|
||||
switch (getThumbSize()) {
|
||||
case 0:
|
||||
return R.dimen.gallery_grid_column_width_large;
|
||||
default:
|
||||
case 1:
|
||||
return R.dimen.gallery_grid_column_width_middle;
|
||||
case 2:
|
||||
return R.dimen.gallery_grid_column_width_small;
|
||||
}
|
||||
public static int dip2px(int dpValue) {
|
||||
final float scale = EhApplication.getApplication().getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static int getThumbResolution() {
|
||||
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
||||
}
|
||||
|
||||
public static boolean getFixThumbUrl() {
|
||||
return getBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL);
|
||||
public static boolean getShowComments() {
|
||||
return getBoolean(KEY_SHOW_COMMENTS, DEFAULT_SHOW_COMMENTS);
|
||||
}
|
||||
|
||||
public static boolean getRequestNews() {
|
||||
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
||||
}
|
||||
|
||||
public static boolean getHideHvEvents() {
|
||||
return getBoolean(KEY_HIDE_HV_EVENTS, DEFAULT_HIDE_HV_EVENTS);
|
||||
}
|
||||
|
||||
public static boolean getShowJpnTitle() {
|
||||
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
|
||||
}
|
||||
@@ -583,32 +425,14 @@ public class Settings {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
}
|
||||
|
||||
public static void putDefaultCategories(int value) {
|
||||
sEhConfig.defaultCategories = value;
|
||||
sEhConfig.setDirty();
|
||||
putInt(KEY_DEFAULT_CATEGORIES, value);
|
||||
}
|
||||
|
||||
public static int getExcludedTagNamespaces() {
|
||||
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
|
||||
}
|
||||
|
||||
public static void putExcludedTagNamespaces(int value) {
|
||||
sEhConfig.excludedNamespaces = value;
|
||||
sEhConfig.setDirty();
|
||||
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
|
||||
}
|
||||
|
||||
public static String getExcludedLanguages() {
|
||||
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
|
||||
}
|
||||
|
||||
public static void putExcludedLanguages(String value) {
|
||||
sEhConfig.excludedLanguages = value;
|
||||
sEhConfig.setDirty();
|
||||
putString(KEY_EXCLUDED_LANGUAGES, value);
|
||||
}
|
||||
|
||||
public static boolean getMeteredNetworkWarning() {
|
||||
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
|
||||
}
|
||||
@@ -621,152 +445,20 @@ public class Settings {
|
||||
putBoolean(KEY_APP_LINK_VERIFY_TIP, value);
|
||||
}
|
||||
|
||||
public static int getScreenRotation() {
|
||||
return getIntFromStr(KEY_SCREEN_ROTATION, DEFAULT_SCREEN_ROTATION);
|
||||
}
|
||||
|
||||
public static void putScreenRotation(int value) {
|
||||
putIntToStr(KEY_SCREEN_ROTATION, value);
|
||||
}
|
||||
|
||||
@GalleryView.LayoutMode
|
||||
public static int getReadingDirection() {
|
||||
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
|
||||
}
|
||||
|
||||
public static void putReadingDirection(int value) {
|
||||
putIntToStr(KEY_READING_DIRECTION, value);
|
||||
}
|
||||
|
||||
@GalleryView.ScaleMode
|
||||
public static int getPageScaling() {
|
||||
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
|
||||
}
|
||||
|
||||
public static void putPageScaling(int value) {
|
||||
putIntToStr(KEY_PAGE_SCALING, value);
|
||||
}
|
||||
|
||||
@GalleryView.StartPosition
|
||||
public static int getStartPosition() {
|
||||
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
|
||||
}
|
||||
|
||||
public static void putStartPosition(int value) {
|
||||
putIntToStr(KEY_START_POSITION, value);
|
||||
}
|
||||
|
||||
public static boolean getKeepScreenOn() {
|
||||
return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
public static void putKeepScreenOn(boolean value) {
|
||||
putBoolean(KEY_KEEP_SCREEN_ON, value);
|
||||
}
|
||||
|
||||
public static boolean getShowClock() {
|
||||
return getBoolean(KEY_SHOW_CLOCK, DEFAULT_SHOW_CLOCK);
|
||||
}
|
||||
|
||||
public static void putShowClock(boolean value) {
|
||||
putBoolean(KEY_SHOW_CLOCK, value);
|
||||
}
|
||||
|
||||
public static boolean getShowProgress() {
|
||||
return getBoolean(KEY_SHOW_PROGRESS, DEFAULT_SHOW_PROGRESS);
|
||||
}
|
||||
|
||||
public static void putShowProgress(boolean value) {
|
||||
putBoolean(KEY_SHOW_PROGRESS, value);
|
||||
}
|
||||
|
||||
public static boolean getShowBattery() {
|
||||
return getBoolean(KEY_SHOW_BATTERY, DEFAULT_SHOW_BATTERY);
|
||||
}
|
||||
|
||||
public static void putShowBattery(boolean value) {
|
||||
putBoolean(KEY_SHOW_BATTERY, value);
|
||||
}
|
||||
|
||||
public static boolean getShowPageInterval() {
|
||||
return getBoolean(KEY_SHOW_PAGE_INTERVAL, DEFAULT_SHOW_PAGE_INTERVAL);
|
||||
}
|
||||
|
||||
public static void putShowPageInterval(boolean value) {
|
||||
putBoolean(KEY_SHOW_PAGE_INTERVAL, value);
|
||||
}
|
||||
|
||||
public static boolean getVolumePage() {
|
||||
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
|
||||
}
|
||||
|
||||
public static void putVolumePage(boolean value) {
|
||||
putBoolean(KEY_VOLUME_PAGE, value);
|
||||
}
|
||||
|
||||
public static boolean getReverseVolumePage() {
|
||||
return getBoolean(KEY_REVERSE_VOLUME_PAGE, DEFAULT_REVERSE_VOLUME_PAGE);
|
||||
}
|
||||
|
||||
public static void putReverseVolumePage(boolean value) {
|
||||
putBoolean(KEY_REVERSE_VOLUME_PAGE, value);
|
||||
}
|
||||
|
||||
public static boolean getReadingFullscreen() {
|
||||
return getBoolean(KEY_READING_FULLSCREEN, VALUE_READING_FULLSCREEN);
|
||||
}
|
||||
|
||||
public static void putReadingFullscreen(boolean value) {
|
||||
putBoolean(KEY_READING_FULLSCREEN, value);
|
||||
}
|
||||
|
||||
public static boolean getCustomScreenLightness() {
|
||||
return getBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, DEFAULT_CUSTOM_SCREEN_LIGHTNESS);
|
||||
}
|
||||
|
||||
public static void putCustomScreenLightness(boolean value) {
|
||||
putBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, value);
|
||||
}
|
||||
|
||||
public static int getScreenLightness() {
|
||||
return getInt(KEY_SCREEN_LIGHTNESS, DEFAULT_SCREEN_LIGHTNESS);
|
||||
}
|
||||
|
||||
public static void putScreenLightness(int value) {
|
||||
putInt(KEY_SCREEN_LIGHTNESS, value);
|
||||
}
|
||||
|
||||
public static int getReadTheme() {
|
||||
return getIntFromStr(KEY_READ_THEME, DEFAULT_READ_THEME);
|
||||
}
|
||||
|
||||
public static void putReadTheme(int value) {
|
||||
putIntToStr(KEY_READ_THEME, value);
|
||||
}
|
||||
|
||||
public static boolean getEnabledSecurity() {
|
||||
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
|
||||
}
|
||||
|
||||
public static void putEnabledSecurity(boolean value) {
|
||||
putBoolean(KEY_READING_FULLSCREEN, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static UniFile getDownloadLocation() {
|
||||
UniFile dir = null;
|
||||
try {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
||||
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
||||
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
||||
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
||||
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
||||
dir = UniFile.fromUri(sContext, builder.build());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
UniFile dir;
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
||||
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
||||
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
||||
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
||||
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
||||
dir = UniFile.fromUri(EhApplication.getApplication(), builder.build());
|
||||
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
|
||||
}
|
||||
|
||||
@@ -817,26 +509,14 @@ public class Settings {
|
||||
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
|
||||
}
|
||||
|
||||
public static void putMultiThreadDownload(int value) {
|
||||
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
|
||||
}
|
||||
|
||||
public static int getDownloadDelay() {
|
||||
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
|
||||
}
|
||||
|
||||
public static void putDownloadDelay(int value) {
|
||||
putIntToStr(KEY_DOWNLOAD_DELAY, value);
|
||||
}
|
||||
|
||||
public static int getPreloadImage() {
|
||||
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
|
||||
}
|
||||
|
||||
public static void putPreloadImage(int value) {
|
||||
putIntToStr(KEY_PRELOAD_IMAGE, value);
|
||||
}
|
||||
|
||||
public static String getImageResolution() {
|
||||
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
|
||||
}
|
||||
@@ -851,10 +531,6 @@ public class Settings {
|
||||
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
|
||||
}
|
||||
|
||||
public static void putDownloadOriginImage(boolean value) {
|
||||
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
|
||||
}
|
||||
|
||||
public static String[] getFavCat() {
|
||||
String[] favCat = new String[10];
|
||||
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
|
||||
@@ -949,104 +625,18 @@ public class Settings {
|
||||
putInt(KEY_DEFAULT_FAV_SLOT, value);
|
||||
}
|
||||
|
||||
public static boolean getAskAnalytics() {
|
||||
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
|
||||
public static boolean getQSSaveProgress() {
|
||||
return getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS);
|
||||
}
|
||||
|
||||
public static void putAskAnalytics(boolean value) {
|
||||
putBoolean(KEY_ASK_ANALYTICS, value);
|
||||
}
|
||||
|
||||
public static boolean getEnableAnalytics() {
|
||||
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
|
||||
}
|
||||
|
||||
public static void putEnableAnalytics(boolean value) {
|
||||
putBoolean(KEY_ENABLE_ANALYTICS, value);
|
||||
}
|
||||
|
||||
public static String getUserID() {
|
||||
boolean writeXml = false;
|
||||
boolean writeFile = false;
|
||||
String userID = getString(KEY_USER_ID, null);
|
||||
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
writeXml = true;
|
||||
// Get use ID from out sd card file
|
||||
userID = FileUtils.read(file);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
writeFile = true;
|
||||
userID = generateUserID();
|
||||
}
|
||||
} else {
|
||||
writeFile = true;
|
||||
}
|
||||
|
||||
if (writeXml) {
|
||||
putString(KEY_USER_ID, userID);
|
||||
}
|
||||
if (writeFile) {
|
||||
FileUtils.write(file, userID);
|
||||
}
|
||||
|
||||
return userID;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String generateUserID() {
|
||||
int length = LENGTH_USER_ID;
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (MathUtils.random(0, ('9' - '0' + 1) + ('z' - 'a' + 1)) <= '9' - '0') {
|
||||
sb.append((char) MathUtils.random('0', '9' + 1));
|
||||
} else {
|
||||
sb.append((char) MathUtils.random('a', 'z' + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isValidUserID(@Nullable String userID) {
|
||||
if (null == userID || LENGTH_USER_ID != userID.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < LENGTH_USER_ID; i++) {
|
||||
char ch = userID.charAt(i);
|
||||
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean getBetaUpdateChannel() {
|
||||
return getBoolean(KEY_BETA_UPDATE_CHANNEL, DEFAULT_BETA_UPDATE_CHANNEL);
|
||||
}
|
||||
|
||||
public static void putBetaUpdateChannel(boolean value) {
|
||||
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
|
||||
}
|
||||
|
||||
public static int getSkipUpdateVersion() {
|
||||
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
|
||||
}
|
||||
|
||||
public static void putSkipUpdateVersion(int value) {
|
||||
putInt(KEY_SKIP_UPDATE_VERSION, value);
|
||||
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);
|
||||
}
|
||||
|
||||
public static void putSaveParseErrorBody(boolean value) {
|
||||
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
|
||||
}
|
||||
|
||||
public static boolean getSaveCrashLog() {
|
||||
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
|
||||
}
|
||||
@@ -1059,8 +649,8 @@ public class Settings {
|
||||
putBoolean(KEY_SECURITY, value);
|
||||
}
|
||||
|
||||
public static boolean getEnableFingerprint() {
|
||||
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
|
||||
public static int getSecurityDelay() {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
public static int getReadCacheSize() {
|
||||
@@ -1083,14 +673,6 @@ public class Settings {
|
||||
putBoolean(KEY_DOMAIN_FRONTING, value);
|
||||
}
|
||||
|
||||
public static String getAppLanguage() {
|
||||
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
|
||||
}
|
||||
|
||||
public static void putAppLanguage(String value) {
|
||||
putString(KEY_APP_LANGUAGE, value);
|
||||
}
|
||||
|
||||
public static int getProxyType() {
|
||||
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
|
||||
}
|
||||
@@ -1115,46 +697,6 @@ public class Settings {
|
||||
putInt(KEY_PROXY_PORT, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideQuickSearch() {
|
||||
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
|
||||
}
|
||||
|
||||
public static void putGuideQuickSearch(boolean value) {
|
||||
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideCollections() {
|
||||
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
|
||||
}
|
||||
|
||||
public static void putGuideCollections(boolean value) {
|
||||
putBoolean(KEY_GUIDE_COLLECTIONS, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideDownloadThumb() {
|
||||
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
|
||||
}
|
||||
|
||||
public static void putGuideDownloadThumb(boolean value) {
|
||||
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideDownloadLabels() {
|
||||
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
|
||||
}
|
||||
|
||||
public static void puttGuideDownloadLabels(boolean value) {
|
||||
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideGallery() {
|
||||
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
|
||||
}
|
||||
|
||||
public static void putGuideGallery(boolean value) {
|
||||
putBoolean(KEY_GUIDE_GALLERY, value);
|
||||
}
|
||||
|
||||
public static int getClipboardTextHashCode() {
|
||||
return getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,10 @@ 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.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.StageActivity;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
|
||||
public final class UrlOpener {
|
||||
|
||||
@@ -55,20 +52,20 @@ public final class UrlOpener {
|
||||
if (galleryDetail != null) {
|
||||
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
||||
if (result != null) {
|
||||
if (result.gid == galleryDetail.gid) {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
|
||||
if (result.gid == galleryDetail.getGid()) {
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
} else if (url.startsWith("#c")) {
|
||||
try {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
@@ -76,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,212 +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 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;
|
||||
|
||||
private final ThreadPoolExecutor mRequestThreadPool;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
|
||||
public EhClient(Context context) {
|
||||
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
||||
mOkHttpClient = EhApplication.getOkHttpClient(context);
|
||||
}
|
||||
|
||||
public void execute(EhRequest request) {
|
||||
if (!request.isCancelled()) {
|
||||
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
|
||||
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();
|
||||
}
|
||||
|
||||
public 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 mCallback;
|
||||
private EhConfig mEhConfig;
|
||||
|
||||
public Task(int method, Callback callback, EhConfig ehConfig) {
|
||||
mMethod = method;
|
||||
mCallback = callback;
|
||||
mEhConfig = ehConfig;
|
||||
}
|
||||
|
||||
// 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 EhConfig getEhConfig() {
|
||||
return mEhConfig;
|
||||
}
|
||||
|
||||
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;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
try {
|
||||
switch (mMethod) {
|
||||
case METHOD_SIGN_IN:
|
||||
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
||||
case METHOD_GET_GALLERY_LIST:
|
||||
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_GALLERY_DETAIL:
|
||||
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_PREVIEW_SET:
|
||||
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_RATE_GALLERY:
|
||||
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
||||
case METHOD_GET_COMMENT_GALLERY:
|
||||
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
||||
case METHOD_GET_GALLERY_TOKEN:
|
||||
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
||||
case METHOD_GET_FAVORITES:
|
||||
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
|
||||
case METHOD_ADD_FAVORITES:
|
||||
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
||||
case METHOD_ADD_FAVORITES_RANGE:
|
||||
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
||||
case METHOD_MODIFY_FAVORITES:
|
||||
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
|
||||
case METHOD_GET_TORRENT_LIST:
|
||||
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_GET_PROFILE:
|
||||
return EhEngine.getProfile(this, mOkHttpClient);
|
||||
case METHOD_VOTE_COMMENT:
|
||||
return 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:
|
||||
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
||||
case METHOD_ARCHIVE_LIST:
|
||||
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_DOWNLOAD_ARCHIVE:
|
||||
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
||||
case METHOD_VOTE_TAG:
|
||||
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
||||
default:
|
||||
return new IllegalStateException("Can't detect method " + mMethod);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@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;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
|
||||
object EhClient {
|
||||
internal fun enqueue(request: EhRequest, scope: CoroutineScope) {
|
||||
check(!request.isActive) // Abort if attempt to execute an active request
|
||||
request.job = scope.launchIO {
|
||||
val callback: Callback<Any?>? = request.callback
|
||||
try {
|
||||
val result: Any? = request.run {
|
||||
if (args == null)
|
||||
execute(method)
|
||||
else
|
||||
execute(method, *args!!)
|
||||
}
|
||||
withUIContext { callback?.onSuccess(result) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e // Don't catch coroutine CancellationException
|
||||
e.printStackTrace()
|
||||
withUIContext { callback?.onFailure(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun execute(method: Int, vararg params: Any?): Any? {
|
||||
return when (method) {
|
||||
METHOD_SIGN_IN -> EhEngine.signIn(
|
||||
params[0] as String,
|
||||
params[1] as String
|
||||
)
|
||||
|
||||
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
|
||||
params[0] as String
|
||||
)
|
||||
|
||||
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
|
||||
params[0] as String?
|
||||
)
|
||||
|
||||
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
|
||||
params[0] as String?
|
||||
)
|
||||
|
||||
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as Long,
|
||||
params[3] as String?,
|
||||
params[4] as Float
|
||||
)
|
||||
|
||||
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
|
||||
params[0] as String?,
|
||||
params[1] as String,
|
||||
params[2] as String?
|
||||
)
|
||||
|
||||
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as Int
|
||||
)
|
||||
|
||||
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
|
||||
params[0] as String
|
||||
)
|
||||
|
||||
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as Int,
|
||||
params[3] as String?
|
||||
)
|
||||
|
||||
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
|
||||
params[0] as LongArray,
|
||||
params[1] as Array<String?>,
|
||||
params[2] as Int
|
||||
)
|
||||
|
||||
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
|
||||
params[0] as String,
|
||||
params[1] as LongArray,
|
||||
params[2] as Int
|
||||
)
|
||||
|
||||
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
|
||||
params[0] as String?,
|
||||
params[1] as Long,
|
||||
params[2] as String?
|
||||
)
|
||||
|
||||
METHOD_GET_PROFILE -> EhEngine.getProfile()
|
||||
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as Long,
|
||||
params[3] as String?,
|
||||
params[4] as Long,
|
||||
params[5] as Int
|
||||
)
|
||||
|
||||
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
|
||||
params[0] as File,
|
||||
params[1] as Boolean,
|
||||
params[2] as Boolean,
|
||||
params[3] as Boolean
|
||||
)
|
||||
|
||||
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
|
||||
params[0] as String?,
|
||||
params[1] as Long,
|
||||
params[2] as String?
|
||||
)
|
||||
|
||||
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as String?,
|
||||
params[3] as String?,
|
||||
params[4] as Boolean
|
||||
)
|
||||
|
||||
METHOD_VOTE_TAG -> EhEngine.voteTag(
|
||||
params[0] as Long,
|
||||
params[1] as String?,
|
||||
params[2] as Long,
|
||||
params[3] as String?,
|
||||
params[4] as String?,
|
||||
params[5] as Int
|
||||
)
|
||||
|
||||
METHOD_GET_UCONFIG -> EhEngine.getUConfig()
|
||||
else -> throw IllegalStateException("Can't detect method $method")
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback<E> {
|
||||
fun onSuccess(result: E)
|
||||
fun onFailure(e: Exception)
|
||||
fun onCancel()
|
||||
}
|
||||
|
||||
const val METHOD_SIGN_IN = 0
|
||||
const val METHOD_GET_GALLERY_LIST = 1
|
||||
const val METHOD_GET_GALLERY_DETAIL = 3
|
||||
const val METHOD_GET_PREVIEW_SET = 4
|
||||
const val METHOD_GET_RATE_GALLERY = 5
|
||||
const val METHOD_GET_COMMENT_GALLERY = 6
|
||||
const val METHOD_GET_GALLERY_TOKEN = 7
|
||||
const val METHOD_GET_FAVORITES = 8
|
||||
const val METHOD_ADD_FAVORITES = 9
|
||||
const val METHOD_ADD_FAVORITES_RANGE = 10
|
||||
const val METHOD_MODIFY_FAVORITES = 11
|
||||
const val METHOD_GET_TORRENT_LIST = 12
|
||||
const val METHOD_GET_PROFILE = 14
|
||||
const val METHOD_VOTE_COMMENT = 15
|
||||
const val METHOD_IMAGE_SEARCH = 16
|
||||
const val METHOD_ARCHIVE_LIST = 17
|
||||
const val METHOD_DOWNLOAD_ARCHIVE = 18
|
||||
const val METHOD_VOTE_TAG = 19
|
||||
const val METHOD_GET_UCONFIG = 20
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.network.CookieRepository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class EhCookieStore extends CookieRepository {
|
||||
|
||||
public static final String KEY_IPD_MEMBER_ID = "ipb_member_id";
|
||||
public static final String KEY_IPD_PASS_HASH = "ipb_pass_hash";
|
||||
public static final String KEY_IGNEOUS = "igneous";
|
||||
|
||||
public static final Cookie sTipsCookie =
|
||||
new Cookie.Builder()
|
||||
.name(EhConfig.KEY_CONTENT_WARNING)
|
||||
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
|
||||
.domain(EhUrl.DOMAIN_E)
|
||||
.path("/")
|
||||
.expiresAt(Long.MAX_VALUE)
|
||||
.build();
|
||||
|
||||
public EhCookieStore(Context context) {
|
||||
super(context, "okhttp3-cookie.db");
|
||||
}
|
||||
|
||||
public static Cookie newCookie(Cookie cookie, String newDomain, boolean forcePersistent,
|
||||
boolean forceLongLive, boolean forceNotHostOnly) {
|
||||
Cookie.Builder builder = new Cookie.Builder();
|
||||
builder.name(cookie.name());
|
||||
builder.value(cookie.value());
|
||||
|
||||
if (forceLongLive) {
|
||||
builder.expiresAt(Long.MAX_VALUE);
|
||||
} else if (cookie.persistent()) {
|
||||
builder.expiresAt(cookie.expiresAt());
|
||||
} else if (forcePersistent) {
|
||||
builder.expiresAt(Long.MAX_VALUE);
|
||||
}
|
||||
if (cookie.hostOnly() && !forceNotHostOnly) {
|
||||
builder.hostOnlyDomain(newDomain);
|
||||
} else {
|
||||
builder.domain(newDomain);
|
||||
}
|
||||
builder.path(cookie.path());
|
||||
if (cookie.secure()) {
|
||||
builder.secure();
|
||||
}
|
||||
if (cookie.httpOnly()) {
|
||||
builder.httpOnly();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void signOut() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public boolean hasSignedIn() {
|
||||
HttpUrl url = HttpUrl.parse(EhUrl.HOST_E);
|
||||
return contains(url, KEY_IPD_MEMBER_ID) &&
|
||||
contains(url, KEY_IPD_PASS_HASH);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
|
||||
List<Cookie> cookies = super.loadForRequest(url);
|
||||
|
||||
boolean checkTips = domainMatch(url, EhUrl.DOMAIN_E);
|
||||
|
||||
if (checkTips) {
|
||||
List<Cookie> result = new ArrayList<>(cookies.size() + 1);
|
||||
// Add all but skip some
|
||||
for (Cookie cookie : cookies) {
|
||||
String name = cookie.name();
|
||||
if (EhConfig.KEY_CONTENT_WARNING.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
if (EhConfig.KEY_UCONFIG.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
result.add(cookie);
|
||||
}
|
||||
// Add some
|
||||
result.add(sTipsCookie);
|
||||
return Collections.unmodifiableList(result);
|
||||
} else {
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.network.CookieDatabase
|
||||
import com.hippo.network.CookieSet
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import java.util.Collections
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object EhCookieStore : CookieJar {
|
||||
private val db: CookieDatabase = CookieDatabase(EhApplication.application, "okhttp3-cookie.db")
|
||||
private val map: MutableMap<String, CookieSet> = db.allCookies
|
||||
|
||||
fun signOut() {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun hasSignedIn(): Boolean {
|
||||
val url = EhUrl.HOST_E.toHttpUrl()
|
||||
return contains(url, KEY_IPD_MEMBER_ID) &&
|
||||
contains(url, KEY_IPD_PASS_HASH)
|
||||
}
|
||||
|
||||
const val KEY_IPD_MEMBER_ID = "ipb_member_id"
|
||||
const val KEY_IPD_PASS_HASH = "ipb_pass_hash"
|
||||
const val KEY_IGNEOUS = "igneous"
|
||||
private val sTipsCookie: Cookie = Cookie.Builder()
|
||||
.name(EhConfig.KEY_CONTENT_WARNING)
|
||||
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
|
||||
.domain(EhUrl.DOMAIN_E)
|
||||
.path("/")
|
||||
.expiresAt(Long.MAX_VALUE)
|
||||
.build()
|
||||
|
||||
fun newCookie(
|
||||
cookie: Cookie, newDomain: String, forcePersistent: Boolean,
|
||||
forceLongLive: Boolean, forceNotHostOnly: Boolean
|
||||
): Cookie {
|
||||
val builder = Cookie.Builder()
|
||||
builder.name(cookie.name)
|
||||
builder.value(cookie.value)
|
||||
if (forceLongLive) {
|
||||
builder.expiresAt(Long.MAX_VALUE)
|
||||
} else if (cookie.persistent) {
|
||||
builder.expiresAt(cookie.expiresAt)
|
||||
} else if (forcePersistent) {
|
||||
builder.expiresAt(Long.MAX_VALUE)
|
||||
}
|
||||
if (cookie.hostOnly && !forceNotHostOnly) {
|
||||
builder.hostOnlyDomain(newDomain)
|
||||
} else {
|
||||
builder.domain(newDomain)
|
||||
}
|
||||
builder.path(cookie.path)
|
||||
if (cookie.secure) {
|
||||
builder.secure()
|
||||
}
|
||||
if (cookie.httpOnly) {
|
||||
builder.httpOnly()
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addCookie(cookie: Cookie) {
|
||||
// For cookie database
|
||||
var toAdd: Cookie? = null
|
||||
var toUpdate: Cookie? = null
|
||||
var toRemove: Cookie? = null
|
||||
var set = map[cookie.domain]
|
||||
if (set == null) {
|
||||
set = CookieSet()
|
||||
map[cookie.domain] = set
|
||||
}
|
||||
if (cookie.expiresAt <= System.currentTimeMillis()) {
|
||||
toRemove = set.remove(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (toRemove != null && !toRemove.persistent) {
|
||||
toRemove = null
|
||||
}
|
||||
} else {
|
||||
toAdd = cookie
|
||||
toUpdate = set.add(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (!toAdd.persistent) toAdd = null
|
||||
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
|
||||
// Remove the cookie if it updates to null
|
||||
if (toAdd == null && toUpdate != null) {
|
||||
toRemove = toUpdate
|
||||
toUpdate = null
|
||||
}
|
||||
}
|
||||
if (toRemove != null) {
|
||||
db.remove(toRemove)
|
||||
}
|
||||
if (toAdd != null) {
|
||||
if (toUpdate != null) {
|
||||
db.update(toUpdate, toAdd)
|
||||
} else {
|
||||
db.add(toAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCookieHeader(url: HttpUrl): String {
|
||||
val cookies = getCookies(url)
|
||||
val cookieHeader = StringBuilder()
|
||||
var i = 0
|
||||
val size = cookies.size
|
||||
while (i < size) {
|
||||
if (i > 0) {
|
||||
cookieHeader.append("; ")
|
||||
}
|
||||
val cookie = cookies[i]
|
||||
cookieHeader.append(cookie.name).append('=').append(cookie.value)
|
||||
i++
|
||||
}
|
||||
return cookieHeader.toString()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getCookies(url: HttpUrl): List<Cookie> {
|
||||
val accepted: MutableList<Cookie> = ArrayList()
|
||||
val expired: MutableList<Cookie> = ArrayList()
|
||||
for ((domain, cookieSet) in map) {
|
||||
if (domainMatch(url, domain)) {
|
||||
cookieSet[url, accepted, expired]
|
||||
}
|
||||
}
|
||||
for (cookie in expired) {
|
||||
if (cookie.persistent) {
|
||||
db.remove(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 6265 Section-5.4 step 2, sort the cookie-list
|
||||
// Cookies with longer paths are listed before cookies with shorter paths.
|
||||
// Ignore creation-time, we don't store them.
|
||||
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
|
||||
return accepted
|
||||
}
|
||||
|
||||
fun contains(url: HttpUrl, name: String?): Boolean {
|
||||
for (cookie in getCookies(url)) {
|
||||
if (cookie.name == name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all cookies in this `CookieRepository`.
|
||||
*/
|
||||
@Synchronized
|
||||
fun clear() {
|
||||
map.clear()
|
||||
db.clear()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun close() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
for (cookie in cookies) {
|
||||
addCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
|
||||
* of Android's private InetAddress#isNumeric API.
|
||||
*
|
||||
*
|
||||
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
|
||||
* including dots after the first colon. It matches IPv4 addresses as strings containing only
|
||||
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
|
||||
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
|
||||
* verification).
|
||||
*/
|
||||
private val VERIFY_AS_IP_ADDRESS =
|
||||
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
|
||||
|
||||
/**
|
||||
* Returns true if `host` is not a host name and might be an IP address.
|
||||
*/
|
||||
private fun verifyAsIpAddress(host: String): Boolean {
|
||||
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
|
||||
}
|
||||
|
||||
// okhttp3.Cookie.domainMatch(HttpUrl, String)
|
||||
@JvmStatic
|
||||
fun domainMatch(url: HttpUrl, domain: String?): Boolean {
|
||||
val urlHost = url.host
|
||||
return if (urlHost == domain) {
|
||||
true // As in 'example.com' matching 'example.com'.
|
||||
} else urlHost.endsWith(domain!!)
|
||||
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
|
||||
urlHost
|
||||
)
|
||||
// As in 'example.com' matching 'www.example.com'.
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val cookies = getCookies(url)
|
||||
val checkTips = domainMatch(url, EhUrl.DOMAIN_E)
|
||||
return if (checkTips) {
|
||||
val result: MutableList<Cookie> = ArrayList(cookies.size + 1)
|
||||
// Add all but skip some
|
||||
for (cookie in cookies) {
|
||||
val name = cookie.name
|
||||
if (EhConfig.KEY_CONTENT_WARNING == name) {
|
||||
continue
|
||||
}
|
||||
if (EhConfig.KEY_UCONFIG == name) {
|
||||
continue
|
||||
}
|
||||
result.add(cookie)
|
||||
}
|
||||
// Add some
|
||||
result.add(sTipsCookie)
|
||||
Collections.unmodifiableList(result)
|
||||
} else {
|
||||
cookies
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
/*
|
||||
* Created by Hippo on 2018/3/23.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Dns;
|
||||
|
||||
public class EhDns implements Dns {
|
||||
|
||||
private static final Map<String, List<InetAddress>> builtInHosts;
|
||||
|
||||
static {
|
||||
Map<String, List<InetAddress>> map = new HashMap<>();
|
||||
put(map, "e-hentai.org", "104.20.134.21", "104.20.135.21", "172.67.0.127");
|
||||
put(map, "exhentai.org", "178.175.128.252", "178.175.129.252", "178.175.129.254", "178.175.128.254",
|
||||
"178.175.132.20", "178.175.132.22");
|
||||
put(map, "repo.e-hentai.org", "94.100.28.57", "94.100.29.73");
|
||||
put(map, "forums.e-hentai.org", "94.100.18.243");
|
||||
put(map, "ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt0.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt1.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt2.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt3.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "ul.ehgt.org", "94.100.24.82", "94.100.24.72");
|
||||
put(map, "raw.githubusercontent.com", "151.101.0.133", "151.101.64.133", "151.101.128.133", "151.101.192.133");
|
||||
builtInHosts = map;
|
||||
}
|
||||
|
||||
private final Hosts hosts;
|
||||
|
||||
public EhDns(Context context) {
|
||||
hosts = EhApplication.getHosts(context);
|
||||
}
|
||||
|
||||
private static void put(Map<String, List<InetAddress>> map, String host, String... ips) {
|
||||
List<InetAddress> addresses = new ArrayList<>();
|
||||
for (String ip : ips) {
|
||||
addresses.add(Hosts.toInetAddress(host, ip));
|
||||
}
|
||||
map.put(host, addresses);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
|
||||
List<InetAddress> inetAddresses = hosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
if (Settings.getBuiltInHosts()) {
|
||||
inetAddresses = builtInHosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
}
|
||||
return SYSTEM.lookup(hostname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2018 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.ehviewer.Hosts
|
||||
import com.hippo.ehviewer.Settings
|
||||
import okhttp3.Dns
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
object EhDns : Dns {
|
||||
private val hosts = EhApplication.hosts
|
||||
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
|
||||
|
||||
init {
|
||||
/* Pair(ip: String!, blockedByCCP: Boolean!) */
|
||||
val ehgtHosts = arrayOf(
|
||||
Pair("37.48.89.44", false),
|
||||
Pair("81.171.10.48", false),
|
||||
Pair("178.162.139.24", false),
|
||||
Pair("178.162.140.212", false),
|
||||
Pair("2001:1af8:4700:a062:8::47de", false),
|
||||
Pair("2001:1af8:4700:a062:9::47de", true),
|
||||
Pair("2001:1af8:4700:a0c9:4::47de", false),
|
||||
Pair("2001:1af8:4700:a0c9:3::47de", true),
|
||||
)
|
||||
|
||||
put(
|
||||
builtInHosts,
|
||||
"e-hentai.org",
|
||||
Pair("104.20.134.21", false),
|
||||
Pair("104.20.135.21", false),
|
||||
Pair("172.67.0.127", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"exhentai.org",
|
||||
Pair("178.175.128.252", false),
|
||||
Pair("178.175.129.252", false),
|
||||
Pair("178.175.129.254", false),
|
||||
Pair("178.175.128.254", false),
|
||||
Pair("178.175.132.20", false),
|
||||
Pair("178.175.132.22", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"repo.e-hentai.org",
|
||||
Pair("94.100.28.57", true),
|
||||
Pair("94.100.29.73", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"forums.e-hentai.org",
|
||||
Pair("94.100.18.243", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt0.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt1.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt2.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt3.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ul.ehgt.org",
|
||||
Pair("94.100.24.82", true),
|
||||
Pair("94.100.24.72", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"raw.githubusercontent.com",
|
||||
Pair("151.101.0.133", false),
|
||||
Pair("151.101.64.133", false),
|
||||
Pair("151.101.128.133", false),
|
||||
Pair("151.101.192.133", false),
|
||||
)
|
||||
}
|
||||
|
||||
private fun put(
|
||||
map: MutableMap<String, List<InetAddress>>,
|
||||
host: String,
|
||||
vararg ips: Pair<String, Boolean>
|
||||
) {
|
||||
map[host] = ips.mapNotNull { pair ->
|
||||
Hosts.toInetAddress(host, pair.first).takeUnless { Settings.getDF() && pair.second }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.getBuiltInHosts() }
|
||||
?: Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
}
|
||||
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,79 +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.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class EhSSLSocketFactory extends SSLSocketFactory {
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
SSLSocketFactory defaultFactory = (SSLSocketFactory) getDefault();
|
||||
if (!Settings.getDF()) {
|
||||
return defaultFactory.createSocket(s, host, port, autoClose);
|
||||
}
|
||||
String address = s.getInetAddress().getHostAddress();
|
||||
SSLSocket socket = (SSLSocket) defaultFactory.createSocket(s, address, port, autoClose);
|
||||
SSLSession sslSession = socket.getSession();
|
||||
Log.d("EhSSLSocketFactory", "Host: " + host + " Address: " + address + " Protocol:" + sslSession.getProtocol() + " CipherSuite:" + sslSession.getCipherSuite());
|
||||
socket.setEnabledProtocols(socket.getSupportedProtocols());
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return getDefault().createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return getDefault().createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.util.Log
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
|
||||
object EhSSLSocketFactory : SSLSocketFactory() {
|
||||
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||
override fun getDefaultCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.defaultCipherSuites
|
||||
}
|
||||
|
||||
override fun getSupportedCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.supportedCipherSuites
|
||||
}
|
||||
|
||||
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 sslSession = socket.session
|
||||
Log.d(
|
||||
"EhSSLSocketFactory",
|
||||
"Host: " + host + " Address: " + address + " Protocol:" + sslSession.protocol + " CipherSuite:" + sslSession.cipherSuite
|
||||
)
|
||||
return socket
|
||||
}
|
||||
|
||||
override fun createSocket(host: String, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
host: String,
|
||||
port: Int,
|
||||
localHost: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(host, port, localHost, localPort)
|
||||
}
|
||||
|
||||
override fun createSocket(host: InetAddress, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
address: InetAddress,
|
||||
port: Int,
|
||||
localAddress: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(address, port, localAddress, localPort)
|
||||
}
|
||||
}
|
||||
@@ -1,436 +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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.BufferedSource;
|
||||
import okio.Okio;
|
||||
|
||||
public class EhTagDatabase {
|
||||
|
||||
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
|
||||
// TODO more lock for different language
|
||||
private static final Lock lock = new ReentrantLock();
|
||||
private static volatile EhTagDatabase instance;
|
||||
|
||||
static {
|
||||
NAMESPACE_TO_PREFIX.put("artist", "a:");
|
||||
NAMESPACE_TO_PREFIX.put("cosplayer", "cos:");
|
||||
NAMESPACE_TO_PREFIX.put("character", "c:");
|
||||
NAMESPACE_TO_PREFIX.put("female", "f:");
|
||||
NAMESPACE_TO_PREFIX.put("group", "g:");
|
||||
NAMESPACE_TO_PREFIX.put("language", "l:");
|
||||
NAMESPACE_TO_PREFIX.put("male", "m:");
|
||||
NAMESPACE_TO_PREFIX.put("mixed", "x:");
|
||||
NAMESPACE_TO_PREFIX.put("other", "o:");
|
||||
NAMESPACE_TO_PREFIX.put("parody", "p:");
|
||||
NAMESPACE_TO_PREFIX.put("reclass", "r:");
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final byte[] tags;
|
||||
|
||||
public EhTagDatabase(String name, BufferedSource source) throws IOException {
|
||||
this.name = name;
|
||||
String[] tmp;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
source.readInt();
|
||||
for (String i : source.readUtf8().split("\n")) {
|
||||
tmp = i.split("\r", 2);
|
||||
buffer.append(tmp[0]);
|
||||
buffer.append("\r");
|
||||
try {
|
||||
buffer.append(new String(Base64.decode(tmp[1], Base64.DEFAULT), StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
buffer.append(tmp[1]);
|
||||
}
|
||||
buffer.append("\n");
|
||||
}
|
||||
byte[] b = buffer.toString().getBytes(StandardCharsets.UTF_8);
|
||||
int totalBytes = b.length;
|
||||
tags = new byte[totalBytes];
|
||||
System.arraycopy(b, 0, tags, 0, totalBytes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static EhTagDatabase getInstance(Context context) {
|
||||
if (isPossible(context)) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String namespaceToPrefix(String namespace) {
|
||||
return NAMESPACE_TO_PREFIX.get(namespace);
|
||||
}
|
||||
|
||||
private static String[] getMetadata(Context context) {
|
||||
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
|
||||
if (metadata.length == 4) {
|
||||
return metadata;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPossible(Context context) {
|
||||
return getMetadata(context) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] getFileContent(File file, int length) {
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
|
||||
byte[] content = new byte[length];
|
||||
source.readFully(content);
|
||||
return content;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] getFileSha1(File file) {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
int n;
|
||||
byte[] buffer = new byte[4 * 1024];
|
||||
while ((n = is.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, n);
|
||||
}
|
||||
return digest.digest();
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean equals(byte[] b1, byte[] b2) {
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
if (b1 == null || b2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (b1.length != b2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < b1.length; i++) {
|
||||
if (b1[i] != b2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkData(File sha1File, File dataFile) {
|
||||
byte[] s1 = getFileContent(sha1File, 20);
|
||||
if (s1 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] s2 = getFileSha1(dataFile);
|
||||
if (s2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return equals(s1, s2);
|
||||
}
|
||||
|
||||
private static boolean save(OkHttpClient client, String url, File file) {
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
Call call = client.newCall(request);
|
||||
try (Response response = call.execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
return false;
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
|
||||
IOUtils.copy(is, os);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(Context context) {
|
||||
String[] urls = getMetadata(context);
|
||||
if (urls == null || urls.length != 4) {
|
||||
// Clear tags if it's not possible
|
||||
instance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String sha1Name = urls[0];
|
||||
String sha1Url = urls[1];
|
||||
String dataName = urls[2];
|
||||
String dataUrl = urls[3];
|
||||
|
||||
// Clear tags if name if different
|
||||
EhTagDatabase tmp = instance;
|
||||
if (tmp != null && !tmp.name.equals(dataName)) {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
||||
if (!lock.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File dir = AppConfig.getFilesDir("tag-translations");
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check current sha1 and current data
|
||||
File sha1File = new File(dir, sha1Name);
|
||||
File dataFile = new File(dir, dataName);
|
||||
if (!checkData(sha1File, dataFile)) {
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
}
|
||||
|
||||
// Read current EhTagDatabase
|
||||
if (instance == null && dataFile.exists()) {
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
|
||||
instance = new EhTagDatabase(dataName, source);
|
||||
} catch (IOException e) {
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
OkHttpClient client = EhApplication.getOkHttpClient(context);
|
||||
|
||||
// Save new sha1
|
||||
File tempSha1File = new File(dir, sha1Name + ".tmp");
|
||||
if (!save(client, sha1Url, tempSha1File)) {
|
||||
FileUtils.delete(tempSha1File);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check new sha1 and current data
|
||||
if (checkData(tempSha1File, dataFile)) {
|
||||
// The data is the same
|
||||
FileUtils.delete(tempSha1File);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save new data
|
||||
File tempDataFile = new File(dir, dataName + ".tmp");
|
||||
if (!save(client, dataUrl, tempDataFile)) {
|
||||
FileUtils.delete(tempDataFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check new sha1 and new data
|
||||
if (!checkData(tempSha1File, tempDataFile)) {
|
||||
FileUtils.delete(tempSha1File);
|
||||
FileUtils.delete(tempDataFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace current sha1 and current data with new sha1 and new data
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
tempSha1File.renameTo(sha1File);
|
||||
tempDataFile.renameTo(dataFile);
|
||||
|
||||
// Read new EhTagDatabase
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
|
||||
instance = new EhTagDatabase(dataName, source);
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getTranslation(String tag) {
|
||||
return search(tags, tag.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String search(byte[] tags, byte[] tag) {
|
||||
int low = 0;
|
||||
int high = tags.length;
|
||||
while (low < high) {
|
||||
int start = (low + high) / 2;
|
||||
// Look for the starting '\n'
|
||||
while (start > -1 && tags[start] != '\n') {
|
||||
start--;
|
||||
}
|
||||
start++;
|
||||
|
||||
// Look for the middle '\r'.
|
||||
int middle = 1;
|
||||
while (tags[start + middle] != '\r') {
|
||||
middle++;
|
||||
}
|
||||
|
||||
// Look for the ending '\n'
|
||||
int end = middle + 1;
|
||||
while (tags[start + end] != '\n') {
|
||||
end++;
|
||||
}
|
||||
|
||||
int compare;
|
||||
int tagIndex = 0;
|
||||
int curIndex = start;
|
||||
|
||||
for (; ; ) {
|
||||
int tagByte = tag[tagIndex] & 0xff;
|
||||
int curByte = tags[curIndex] & 0xff;
|
||||
compare = tagByte - curByte;
|
||||
if (compare != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
tagIndex++;
|
||||
curIndex++;
|
||||
if (tagIndex == tag.length && curIndex == start + middle) {
|
||||
break;
|
||||
}
|
||||
if (tagIndex == tag.length) {
|
||||
compare = -1;
|
||||
break;
|
||||
}
|
||||
if (curIndex == start + middle) {
|
||||
compare = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (compare < 0) {
|
||||
high = start - 1;
|
||||
} else if (compare > 0) {
|
||||
low = start + end + 1;
|
||||
} else {
|
||||
return new String(tags, start + middle + 1, end - middle - 1, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<Pair<String, String>> suggest(String keyword) {
|
||||
return searchTag(tags, keyword);
|
||||
}
|
||||
|
||||
private ArrayList<Pair<String, String>> searchTag(byte[] tags, String keyword) {
|
||||
ArrayList<Pair<String, String>> searchHints = new ArrayList<>();
|
||||
int begin = 0;
|
||||
while (begin < tags.length - 1) {
|
||||
int start = begin;
|
||||
// Look for the starting '\n'
|
||||
while (tags[start] != '\n') {
|
||||
start++;
|
||||
}
|
||||
|
||||
// Look for the middle '\r'.
|
||||
int middle = 1;
|
||||
while (tags[start + middle] != '\r') {
|
||||
middle++;
|
||||
}
|
||||
|
||||
// Look for the ending '\n'
|
||||
int end = middle + 1;
|
||||
while (tags[start + end] != '\n') {
|
||||
end++;
|
||||
}
|
||||
|
||||
begin = start + end;
|
||||
|
||||
byte[] hintBytes = new byte[end - middle - 1];
|
||||
System.arraycopy(tags, start + middle + 1, hintBytes, 0, end - middle - 1);
|
||||
String hint = new String(hintBytes, StandardCharsets.UTF_8);
|
||||
byte[] tagBytes = new byte[middle];
|
||||
System.arraycopy(tags, start + 1, tagBytes, 0, middle);
|
||||
String tag = new String(tagBytes, StandardCharsets.UTF_8);
|
||||
int index = tag.indexOf(':');
|
||||
boolean keywordMatches;
|
||||
if (index == -1 || index >= tag.length() - 1 || keyword.length() > 2) {
|
||||
keywordMatches = containsIgnoreSpace(tag, keyword);
|
||||
} else {
|
||||
keywordMatches = containsIgnoreSpace(tag.substring(index + 1), keyword);
|
||||
}
|
||||
|
||||
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
|
||||
Pair<String, String> pair = new Pair<>(hint, tag);
|
||||
if (!searchHints.contains(pair)) {
|
||||
searchHints.add(pair);
|
||||
}
|
||||
}
|
||||
if (searchHints.size() > 20) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return searchHints;
|
||||
}
|
||||
|
||||
private boolean containsIgnoreSpace(String text, String key) {
|
||||
return text.replace(" ", "").contains(key.replace(" ", ""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.ehviewer.AppConfig
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
|
||||
import com.hippo.ehviewer.R
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.BufferedSource
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
object EhTagDatabase {
|
||||
private lateinit var tags: JSONObject
|
||||
private lateinit var tagList: ArrayList<Pair<String, String>>
|
||||
|
||||
fun isInitialized(): Boolean {
|
||||
return this::tags.isInitialized && this::tagList.isInitialized
|
||||
}
|
||||
|
||||
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
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTranslation(prefix: String?, tag: String?): String? {
|
||||
return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
/* Construct a cold flow for tag database suggestions */
|
||||
fun suggestFlow(
|
||||
keyword: String,
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
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 val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
|
||||
it["artist"] = "a"
|
||||
it["cosplayer"] = "cos"
|
||||
it["character"] = "c"
|
||||
it["female"] = "f"
|
||||
it["group"] = "g"
|
||||
it["language"] = "l"
|
||||
it["male"] = "m"
|
||||
it["mixed"] = "x"
|
||||
it["other"] = "o"
|
||||
it["parody"] = "p"
|
||||
it["reclass"] = "r"
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun namespaceToPrefix(namespace: String): String? {
|
||||
return NAMESPACE_TO_PREFIX[namespace]
|
||||
}
|
||||
|
||||
private fun getMetadata(context: Context): Array<String>? {
|
||||
return context.resources.getStringArray(R.array.tag_translation_metadata)
|
||||
.takeIf { it.size == 4 }
|
||||
}
|
||||
|
||||
fun isTranslatable(context: Context): Boolean {
|
||||
return context.resources.getBoolean(R.bool.tag_translatable)
|
||||
}
|
||||
|
||||
private fun getFileContent(file: File): String? {
|
||||
try {
|
||||
file.source().buffer().use { return it.readString(StandardCharsets.UTF_8) }
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileSha1(file: File): String? {
|
||||
try {
|
||||
FileInputStream(file).use { stream ->
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
var n: Int
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
while (stream.read(buffer).also { n = it } != -1) {
|
||||
digest.update(buffer, 0, n)
|
||||
}
|
||||
return HashCodeUtils.bytesToHexString(digest.digest())
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkData(sha1File: File, dataFile: File): Boolean {
|
||||
val s1 = getFileContent(sha1File) ?: return false
|
||||
val s2 = getFileSha1(dataFile) ?: return false
|
||||
return s1 == s2
|
||||
}
|
||||
|
||||
private fun save(client: OkHttpClient, url: String, file: File): Boolean {
|
||||
val request: Request = Request.Builder().url(url).build()
|
||||
val call = client.newCall(request)
|
||||
try {
|
||||
call.execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return false
|
||||
}
|
||||
val body = response.body
|
||||
body.byteStream()
|
||||
.use { ins -> FileOutputStream(file).use { os -> IOUtils.copy(ins, os) } }
|
||||
return true
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
FileUtils.delete(file)
|
||||
t.printStackTrace()
|
||||
ExceptionUtils.throwIfFatal(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@JvmStatic
|
||||
fun update() {
|
||||
launchIO {
|
||||
updateInternal()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateInternal() {
|
||||
val urls = getMetadata(EhApplication.application)
|
||||
urls?.let {
|
||||
val sha1Name = urls[0]
|
||||
val sha1Url = urls[1]
|
||||
val dataName = urls[2]
|
||||
val dataUrl = urls[3]
|
||||
|
||||
try {
|
||||
val dir = AppConfig.getFilesDir("tag-translations")
|
||||
checkNotNull(dir)
|
||||
|
||||
// Check current sha1 and current data
|
||||
val sha1File = File(dir, sha1Name)
|
||||
val dataFile = File(dir, dataName)
|
||||
if (!checkData(sha1File, dataFile)) {
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
}
|
||||
|
||||
// Read current EhTagDatabase
|
||||
if (!isInitialized() && dataFile.exists()) {
|
||||
try {
|
||||
dataFile.source().buffer().use { updateData(it) }
|
||||
} catch (e: IOException) {
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
}
|
||||
}
|
||||
val client = okHttpClient
|
||||
|
||||
// Save new sha1
|
||||
val tempSha1File = File(dir, "$sha1Name.tmp")
|
||||
check(save(client, sha1Url, tempSha1File))
|
||||
|
||||
// Check new sha1 and current data
|
||||
if (checkData(tempSha1File, dataFile)) {
|
||||
// The data is the same
|
||||
FileUtils.delete(tempSha1File)
|
||||
return
|
||||
}
|
||||
|
||||
// Save new data
|
||||
val tempDataFile = File(dir, "$dataName.tmp")
|
||||
check(save(client, dataUrl, tempDataFile))
|
||||
|
||||
// Check new sha1 and new data
|
||||
if (!checkData(tempSha1File, tempDataFile)) {
|
||||
FileUtils.delete(tempSha1File)
|
||||
FileUtils.delete(tempDataFile)
|
||||
return
|
||||
}
|
||||
|
||||
// Replace current sha1 and current data with new sha1 and new data
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
tempSha1File.renameTo(sha1File)
|
||||
tempDataFile.renameTo(dataFile)
|
||||
|
||||
// Read new EhTagDatabase
|
||||
try {
|
||||
dataFile.source().buffer().use { updateData(it) }
|
||||
} catch (_: IOException) {
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
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;
|
||||
private static final String URL_PREFIX_THUMB_E = "https://ehgt.org/";
|
||||
private static final String URL_PREFIX_THUMB_EX = "https://exhentai.org/t/";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getThumbUrlPrefix() {
|
||||
switch (Settings.getGallerySite()) {
|
||||
default:
|
||||
//case SITE_E:
|
||||
return URL_PREFIX_THUMB_E;
|
||||
//case SITE_EX:
|
||||
// return URL_PREFIX_THUMB_EX;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFixedPreviewThumbUrl(String originUrl) {
|
||||
HttpUrl url = HttpUrl.parse(originUrl);
|
||||
if (url == null) return originUrl;
|
||||
List<String> pathSegments = url.pathSegments();
|
||||
if (pathSegments.size() < 3) return originUrl;
|
||||
|
||||
ListIterator<String> iterator = pathSegments.listIterator(pathSegments.size());
|
||||
// The last segments, like
|
||||
// 317a1a254cd9c3269e71b2aa2671fe8d28c91097-260198-640-480-png_250.jpg
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String lastSegment = iterator.previous();
|
||||
// The second last segments, like
|
||||
// 7a
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String secondLastSegment = iterator.previous();
|
||||
// The third last segments, like
|
||||
// 31
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String thirdLastSegment = iterator.previous();
|
||||
// Check path segments
|
||||
if (lastSegment != null && secondLastSegment != null
|
||||
&& thirdLastSegment != null
|
||||
&& lastSegment.startsWith(thirdLastSegment)
|
||||
&& lastSegment.startsWith(secondLastSegment, thirdLastSegment.length())) {
|
||||
return getThumbUrlPrefix() + thirdLastSegment + "/" + secondLastSegment + "/" + lastSegment;
|
||||
} else {
|
||||
return originUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.network.UrlBuilder
|
||||
|
||||
object EhUrl {
|
||||
const val SITE_E = 0
|
||||
const val SITE_EX = 1
|
||||
const val DOMAIN_EX = "exhentai.org"
|
||||
const val DOMAIN_E = "e-hentai.org"
|
||||
const val DOMAIN_LOFI = "lofi.e-hentai.org"
|
||||
const val HOST_EX = "https://$DOMAIN_EX/"
|
||||
const val API_EX = HOST_EX + "api.php"
|
||||
const val URL_FAVORITES_EX = HOST_EX + "favorites.php"
|
||||
const val URL_UCONFIG_EX = HOST_EX + "uconfig.php"
|
||||
const val URL_MY_TAGS_EX = HOST_EX + "mytags"
|
||||
const val URL_WATCHED_EX = HOST_EX + "watched"
|
||||
const val HOST_E = "https://$DOMAIN_E/"
|
||||
const val API_E = HOST_E + "api.php"
|
||||
const val URL_FAVORITES_E = HOST_E + "favorites.php"
|
||||
const val URL_UCONFIG_E = HOST_E + "uconfig.php"
|
||||
const val URL_MY_TAGS_E = HOST_E + "mytags"
|
||||
const val URL_WATCHED_E = HOST_E + "watched"
|
||||
const val API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01"
|
||||
const val URL_POPULAR_E = "https://e-hentai.org/popular"
|
||||
const val URL_POPULAR_EX = "https://exhentai.org/popular"
|
||||
const val URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php"
|
||||
const val URL_IMAGE_SEARCH_EX = "https://exhentai.org/upload/image_lookup.php"
|
||||
const val URL_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login"
|
||||
const val URL_REGISTER = "https://forums.e-hentai.org/index.php?act=Reg&CODE=00"
|
||||
const val URL_FORUMS = "https://forums.e-hentai.org/"
|
||||
const val URL_FUNDS = "https://e-hentai.org/exchange.php?t=gp"
|
||||
const val REFERER_EX = "https://$DOMAIN_EX"
|
||||
const val ORIGIN_EX = REFERER_EX
|
||||
const val REFERER_E = "https://$DOMAIN_E"
|
||||
const val ORIGIN_E = REFERER_E
|
||||
|
||||
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(' ', '_')
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -31,10 +31,8 @@ import java.util.regex.Pattern;
|
||||
public class EhUtils {
|
||||
|
||||
public static final int NONE = -1; // Use it for homepage
|
||||
public static final int UNKNOWN = 0x400;
|
||||
|
||||
public static final int ALL_CATEGORY = EhUtils.UNKNOWN - 1;
|
||||
//DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC;
|
||||
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;
|
||||
@@ -50,10 +48,10 @@ public class EhUtils {
|
||||
|
||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
|
||||
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
|
||||
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
|
||||
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\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+)*$",
|
||||
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final int[] CATEGORY_VALUES = {
|
||||
@@ -67,6 +65,7 @@ public class EhUtils {
|
||||
EhConfig.ASIAN_PORN,
|
||||
EhConfig.NON_H,
|
||||
EhConfig.WESTERN,
|
||||
PRIVATE,
|
||||
UNKNOWN};
|
||||
|
||||
private static final String[][] CATEGORY_STRINGS = {
|
||||
@@ -80,6 +79,7 @@ public class EhUtils {
|
||||
new String[]{"asianporn", "Asian Porn"},
|
||||
new String[]{"non-h"},
|
||||
new String[]{"western"},
|
||||
new String[]{"private"},
|
||||
new String[]{"unknown"}
|
||||
};
|
||||
|
||||
@@ -94,6 +94,7 @@ public class EhUtils {
|
||||
return CATEGORY_VALUES[i];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getCategory(int type) {
|
||||
int i;
|
||||
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
|
||||
@@ -104,48 +105,38 @@ public class EhUtils {
|
||||
}
|
||||
|
||||
public static int getCategoryColor(int category) {
|
||||
switch (category) {
|
||||
case EhConfig.DOUJINSHI:
|
||||
return BG_COLOR_DOUJINSHI;
|
||||
case EhConfig.MANGA:
|
||||
return BG_COLOR_MANGA;
|
||||
case EhConfig.ARTIST_CG:
|
||||
return BG_COLOR_ARTIST_CG;
|
||||
case EhConfig.GAME_CG:
|
||||
return BG_COLOR_GAME_CG;
|
||||
case EhConfig.WESTERN:
|
||||
return BG_COLOR_WESTERN;
|
||||
case EhConfig.NON_H:
|
||||
return BG_COLOR_NON_H;
|
||||
case EhConfig.IMAGE_SET:
|
||||
return BG_COLOR_IMAGE_SET;
|
||||
case EhConfig.COSPLAY:
|
||||
return BG_COLOR_COSPLAY;
|
||||
case EhConfig.ASIAN_PORN:
|
||||
return BG_COLOR_ASIAN_PORN;
|
||||
case EhConfig.MISC:
|
||||
return BG_COLOR_MISC;
|
||||
default:
|
||||
return BG_COLOR_UNKNOWN;
|
||||
}
|
||||
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(Context context) {
|
||||
EhApplication.getEhCookieStore(context).signOut();
|
||||
public static void signOut() {
|
||||
EhApplication.getEhCookieStore().signOut();
|
||||
Settings.putAvatar(null);
|
||||
Settings.putDisplayName(null);
|
||||
Settings.putGallerySite(EhUrl.SITE_E);
|
||||
Settings.putNeedSignIn(true);
|
||||
}
|
||||
|
||||
public static boolean needSignedIn(Context context) {
|
||||
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore(context).hasSignedIn();
|
||||
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;
|
||||
return TextUtils.isEmpty(gi.getTitleJpn()) ? gi.getTitle() : gi.getTitleJpn();
|
||||
} else {
|
||||
return TextUtils.isEmpty(gi.title) ? gi.titleJpn : gi.title;
|
||||
return TextUtils.isEmpty(gi.getTitle()) ? gi.getTitleJpn() : gi.getTitle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-17
@@ -15,30 +15,23 @@
|
||||
* 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
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
import android.annotation.SuppressLint
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class EhX509TrustManager implements X509TrustManager {
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
object EhX509TrustManager : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
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,120 +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 android.text.TextUtils;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
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 int mIndex;
|
||||
private String mKeyword;
|
||||
private int mFavCat = FAV_CAT_ALL;
|
||||
|
||||
public FavListUrlBuilder() {
|
||||
}
|
||||
|
||||
protected FavListUrlBuilder(Parcel in) {
|
||||
this.mIndex = in.readInt();
|
||||
this.mKeyword = in.readString();
|
||||
this.mFavCat = in.readInt();
|
||||
}
|
||||
|
||||
public static boolean isValidFavCat(int favCat) {
|
||||
return favCat >= 0 && favCat <= 9;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
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)) {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
}
|
||||
if (mIndex > 0) {
|
||||
ub.addQuery("page", Integer.toString(mIndex));
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mIndex);
|
||||
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);
|
||||
}
|
||||
}
|
||||
+21
-19
@@ -13,24 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class ReadFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.read_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragmentTitle() {
|
||||
return R.string.settings_read;
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
+11
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,10 +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.conaco;
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
public interface ProgressNotifier {
|
||||
|
||||
void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
}
|
||||
@Parcelize
|
||||
class GalleryCommentList(
|
||||
@JvmField
|
||||
var comments: Array<GalleryComment>?,
|
||||
@JvmField
|
||||
var hasMore: Boolean
|
||||
) : Parcelable
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GalleryDetail extends GalleryInfo {
|
||||
|
||||
public static final Creator<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
|
||||
@Override
|
||||
public GalleryDetail createFromParcel(Parcel source) {
|
||||
return new GalleryDetail(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryDetail[] newArray(int size) {
|
||||
return new GalleryDetail[size];
|
||||
}
|
||||
};
|
||||
public long apiUid = -1L;
|
||||
public String apiKey;
|
||||
public int torrentCount;
|
||||
public String torrentUrl;
|
||||
public String archiveUrl;
|
||||
public String parent;
|
||||
public ArrayList<GalleryInfo> newerVersions = new ArrayList<>();
|
||||
public String visible;
|
||||
public String language;
|
||||
public String size;
|
||||
public int pages;
|
||||
public int favoriteCount;
|
||||
public boolean isFavorited;
|
||||
public int ratingCount;
|
||||
public GalleryTagGroup[] tags;
|
||||
public GalleryCommentList comments;
|
||||
public int previewPages;
|
||||
public PreviewSet previewSet;
|
||||
|
||||
public GalleryDetail() {
|
||||
}
|
||||
|
||||
protected GalleryDetail(Parcel in) {
|
||||
super(in);
|
||||
this.torrentCount = in.readInt();
|
||||
this.torrentUrl = in.readString();
|
||||
this.archiveUrl = in.readString();
|
||||
this.parent = in.readString();
|
||||
//noinspection unchecked
|
||||
this.newerVersions = in.readArrayList(GalleryInfo.class.getClassLoader());
|
||||
this.visible = in.readString();
|
||||
this.language = in.readString();
|
||||
this.size = in.readString();
|
||||
this.pages = in.readInt();
|
||||
this.favoriteCount = in.readInt();
|
||||
this.isFavorited = in.readByte() != 0;
|
||||
this.ratingCount = in.readInt();
|
||||
Parcelable[] array = in.readParcelableArray(GalleryTagGroup.class.getClassLoader());
|
||||
if (array != null) {
|
||||
this.tags = Arrays.copyOf(array, array.length, GalleryTagGroup[].class);
|
||||
} else {
|
||||
this.tags = null;
|
||||
}
|
||||
this.comments = in.readParcelable(getClass().getClassLoader());
|
||||
this.previewPages = in.readInt();
|
||||
this.previewSet = in.readParcelable(PreviewSet.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.torrentCount);
|
||||
dest.writeString(this.torrentUrl);
|
||||
dest.writeString(this.archiveUrl);
|
||||
dest.writeString(this.parent);
|
||||
dest.writeList(this.newerVersions);
|
||||
dest.writeString(this.visible);
|
||||
dest.writeString(this.language);
|
||||
dest.writeString(this.size);
|
||||
dest.writeInt(this.pages);
|
||||
dest.writeInt(this.favoriteCount);
|
||||
dest.writeByte(isFavorited ? (byte) 1 : (byte) 0);
|
||||
dest.writeInt(this.ratingCount);
|
||||
dest.writeParcelableArray(this.tags, flags);
|
||||
dest.writeParcelable(this.comments, flags);
|
||||
dest.writeInt(this.previewPages);
|
||||
dest.writeParcelable(previewSet, flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class GalleryDetail(
|
||||
@JvmField
|
||||
val galleryInfo: GalleryInfo = BaseGalleryInfo(),
|
||||
|
||||
@JvmField
|
||||
var apiUid: Long = -1L,
|
||||
|
||||
@JvmField
|
||||
var apiKey: String? = null,
|
||||
|
||||
@JvmField
|
||||
var torrentCount: Int = 0,
|
||||
|
||||
@JvmField
|
||||
var torrentUrl: String? = null,
|
||||
|
||||
@JvmField
|
||||
var archiveUrl: String? = null,
|
||||
|
||||
@JvmField
|
||||
var parent: String? = null,
|
||||
|
||||
@JvmField
|
||||
var newerVersions: ArrayList<GalleryInfo> = ArrayList(),
|
||||
|
||||
@JvmField
|
||||
var visible: String? = null,
|
||||
|
||||
@JvmField
|
||||
var language: String? = null,
|
||||
|
||||
@JvmField
|
||||
var size: String? = null,
|
||||
|
||||
@JvmField
|
||||
var favoriteCount: Int = 0,
|
||||
|
||||
@JvmField
|
||||
var isFavorited: Boolean = false,
|
||||
|
||||
@JvmField
|
||||
var ratingCount: Int = 0,
|
||||
|
||||
@JvmField
|
||||
var tags: Array<GalleryTagGroup>? = null,
|
||||
|
||||
@JvmField
|
||||
var comments: GalleryCommentList? = null,
|
||||
|
||||
@JvmField
|
||||
var previewPages: Int = 0,
|
||||
|
||||
@JvmField
|
||||
var previewSet: PreviewSet? = null,
|
||||
) : AbstractGalleryInfo by galleryInfo, GalleryInfo
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GalleryInfo implements Parcelable {
|
||||
|
||||
/**
|
||||
* ISO 639-1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static final String S_LANG_JA = "JA";
|
||||
public static final String S_LANG_EN = "EN";
|
||||
public static final String S_LANG_ZH = "ZH";
|
||||
public static final String S_LANG_NL = "NL";
|
||||
public static final String S_LANG_FR = "FR";
|
||||
public static final String S_LANG_DE = "DE";
|
||||
public static final String S_LANG_HU = "HU";
|
||||
public static final String S_LANG_IT = "IT";
|
||||
public static final String S_LANG_KO = "KO";
|
||||
public static final String S_LANG_PL = "PL";
|
||||
public static final String S_LANG_PT = "PT";
|
||||
public static final String S_LANG_RU = "RU";
|
||||
public static final String S_LANG_ES = "ES";
|
||||
public static final String S_LANG_TH = "TH";
|
||||
public static final String S_LANG_VI = "VI";
|
||||
|
||||
public static final String[] S_LANGS = {
|
||||
S_LANG_EN,
|
||||
S_LANG_ZH,
|
||||
S_LANG_ES,
|
||||
S_LANG_KO,
|
||||
S_LANG_RU,
|
||||
S_LANG_FR,
|
||||
S_LANG_PT,
|
||||
S_LANG_TH,
|
||||
S_LANG_DE,
|
||||
S_LANG_IT,
|
||||
S_LANG_VI,
|
||||
S_LANG_PL,
|
||||
S_LANG_HU,
|
||||
S_LANG_NL,
|
||||
};
|
||||
|
||||
public static final Pattern[] S_LANG_PATTERNS = {
|
||||
Pattern.compile("[(\\[]eng(?:lish)?[)\\]]|英訳", Pattern.CASE_INSENSITIVE),
|
||||
// [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
|
||||
Pattern.compile("[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
};
|
||||
|
||||
public static final String[] S_LANG_TAGS = {
|
||||
"language:english",
|
||||
"language:chinese",
|
||||
"language:spanish",
|
||||
"language:korean",
|
||||
"language:russian",
|
||||
"language:french",
|
||||
"language:portuguese",
|
||||
"language:thai",
|
||||
"language:german",
|
||||
"language:italian",
|
||||
"language:vietnamese",
|
||||
"language:polish",
|
||||
"language:hungarian",
|
||||
"language:dutch",
|
||||
};
|
||||
public static final Parcelable.Creator<GalleryInfo> CREATOR = new Parcelable.Creator<GalleryInfo>() {
|
||||
|
||||
@Override
|
||||
public GalleryInfo createFromParcel(Parcel source) {
|
||||
return new GalleryInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryInfo[] newArray(int size) {
|
||||
return new GalleryInfo[size];
|
||||
}
|
||||
};
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
public long gid;
|
||||
@ColumnInfo(name = "TOKEN")
|
||||
public String token;
|
||||
@ColumnInfo(name = "TITLE")
|
||||
public String title;
|
||||
@ColumnInfo(name = "TITLE_JPN")
|
||||
public String titleJpn;
|
||||
@ColumnInfo(name = "THUMB")
|
||||
public String thumb;
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
public int category;
|
||||
@ColumnInfo(name = "POSTED")
|
||||
public String posted;
|
||||
@ColumnInfo(name = "UPLOADER")
|
||||
public String uploader;
|
||||
@Ignore
|
||||
public boolean disowned;
|
||||
@ColumnInfo(name = "RATING")
|
||||
public float rating;
|
||||
@Ignore
|
||||
public boolean rated;
|
||||
@Nullable
|
||||
@Ignore
|
||||
public String[] simpleTags;
|
||||
@Ignore
|
||||
public int pages;
|
||||
@Ignore
|
||||
public int thumbWidth;
|
||||
@Ignore
|
||||
public int thumbHeight;
|
||||
@Ignore
|
||||
public int spanSize;
|
||||
@Ignore
|
||||
public int spanIndex;
|
||||
@Ignore
|
||||
public int spanGroupIndex;
|
||||
/**
|
||||
* language from title
|
||||
*/
|
||||
@ColumnInfo(name = "SIMPLE_LANGUAGE")
|
||||
public String simpleLanguage;
|
||||
@Ignore
|
||||
public int favoriteSlot = -2;
|
||||
@Ignore
|
||||
public String favoriteName;
|
||||
|
||||
public GalleryInfo() {
|
||||
}
|
||||
|
||||
protected GalleryInfo(Parcel in) {
|
||||
this.gid = in.readLong();
|
||||
this.token = in.readString();
|
||||
this.title = in.readString();
|
||||
this.titleJpn = in.readString();
|
||||
this.thumb = in.readString();
|
||||
this.category = in.readInt();
|
||||
this.posted = in.readString();
|
||||
this.uploader = in.readString();
|
||||
this.rating = in.readFloat();
|
||||
this.rated = in.readByte() != 0;
|
||||
this.simpleLanguage = in.readString();
|
||||
this.simpleTags = in.createStringArray();
|
||||
this.thumbWidth = in.readInt();
|
||||
this.thumbHeight = in.readInt();
|
||||
this.spanSize = in.readInt();
|
||||
this.spanIndex = in.readInt();
|
||||
this.spanGroupIndex = in.readInt();
|
||||
this.favoriteSlot = in.readInt();
|
||||
this.favoriteName = in.readString();
|
||||
this.disowned = in.readByte() != 0;
|
||||
}
|
||||
|
||||
public final void generateSLang() {
|
||||
if (simpleTags != null) {
|
||||
generateSLangFromTags();
|
||||
}
|
||||
if (simpleLanguage == null && title != null) {
|
||||
generateSLangFromTitle();
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSLangFromTags() {
|
||||
assert simpleTags != null;
|
||||
for (String tag : simpleTags) {
|
||||
for (int i = 0; i < S_LANGS.length; i++) {
|
||||
if (S_LANG_TAGS[i].equals(tag)) {
|
||||
simpleLanguage = S_LANGS[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSLangFromTitle() {
|
||||
for (int i = 0; i < S_LANGS.length; i++) {
|
||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
||||
simpleLanguage = S_LANGS[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
simpleLanguage = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(this.gid);
|
||||
dest.writeString(this.token);
|
||||
dest.writeString(this.title);
|
||||
dest.writeString(this.titleJpn);
|
||||
dest.writeString(this.thumb);
|
||||
dest.writeInt(this.category);
|
||||
dest.writeString(this.posted);
|
||||
dest.writeString(this.uploader);
|
||||
dest.writeFloat(this.rating);
|
||||
dest.writeByte(this.rated ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(this.simpleLanguage);
|
||||
dest.writeStringArray(this.simpleTags);
|
||||
dest.writeInt(this.thumbWidth);
|
||||
dest.writeInt(this.thumbHeight);
|
||||
dest.writeInt(this.spanSize);
|
||||
dest.writeInt(this.spanIndex);
|
||||
dest.writeInt(this.spanGroupIndex);
|
||||
dest.writeInt(this.favoriteSlot);
|
||||
dest.writeString(this.favoriteName);
|
||||
dest.writeByte(this.disowned ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import java.util.regex.Pattern
|
||||
|
||||
interface GalleryInfo : AbstractGalleryInfo, Parcelable {
|
||||
fun generateSLang() {
|
||||
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) }
|
||||
?: title?.let { generateSLangFromTitle(it) }
|
||||
}
|
||||
|
||||
private fun generateSLangFromTags(simpleTags: Array<String>): String? {
|
||||
for (tag in simpleTags) {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_TAGS[i] == tag) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun generateSLangFromTitle(title: String): String? {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* ISO 639-1
|
||||
*/
|
||||
const val S_LANG_JA = "JA"
|
||||
const val S_LANG_EN = "EN"
|
||||
const val S_LANG_ZH = "ZH"
|
||||
const val S_LANG_NL = "NL"
|
||||
const val S_LANG_FR = "FR"
|
||||
const val S_LANG_DE = "DE"
|
||||
const val S_LANG_HU = "HU"
|
||||
const val S_LANG_IT = "IT"
|
||||
const val S_LANG_KO = "KO"
|
||||
const val S_LANG_PL = "PL"
|
||||
const val S_LANG_PT = "PT"
|
||||
const val S_LANG_RU = "RU"
|
||||
const val S_LANG_ES = "ES"
|
||||
const val S_LANG_TH = "TH"
|
||||
const val S_LANG_VI = "VI"
|
||||
|
||||
val S_LANGS = arrayOf(
|
||||
S_LANG_EN,
|
||||
S_LANG_ZH,
|
||||
S_LANG_ES,
|
||||
S_LANG_KO,
|
||||
S_LANG_RU,
|
||||
S_LANG_FR,
|
||||
S_LANG_PT,
|
||||
S_LANG_TH,
|
||||
S_LANG_DE,
|
||||
S_LANG_IT,
|
||||
S_LANG_VI,
|
||||
S_LANG_PL,
|
||||
S_LANG_HU,
|
||||
S_LANG_NL
|
||||
)
|
||||
val S_LANG_PATTERNS = arrayOf(
|
||||
Pattern.compile(
|
||||
"[(\\[]eng(?:lish)?[)\\]]|英訳",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), // [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
|
||||
Pattern.compile(
|
||||
"[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
),
|
||||
Pattern.compile(
|
||||
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
),
|
||||
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile(
|
||||
"[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
),
|
||||
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile(
|
||||
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
),
|
||||
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE)
|
||||
)
|
||||
val S_LANG_TAGS = arrayOf(
|
||||
"language:english",
|
||||
"language:chinese",
|
||||
"language:spanish",
|
||||
"language:korean",
|
||||
"language:russian",
|
||||
"language:french",
|
||||
"language:portuguese",
|
||||
"language:thai",
|
||||
"language:german",
|
||||
"language:italian",
|
||||
"language:vietnamese",
|
||||
"language:polish",
|
||||
"language:hungarian",
|
||||
"language:dutch"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.hippo.widget.LoadImageView;
|
||||
|
||||
public class GalleryPreview implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
|
||||
@Override
|
||||
public GalleryPreview createFromParcel(Parcel source) {
|
||||
return new GalleryPreview(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryPreview[] newArray(int size) {
|
||||
return new GalleryPreview[size];
|
||||
}
|
||||
};
|
||||
String imageKey;
|
||||
String imageUrl;
|
||||
String pageUrl;
|
||||
int position;
|
||||
int offsetX = Integer.MIN_VALUE;
|
||||
int offsetY = Integer.MIN_VALUE;
|
||||
int clipWidth = Integer.MIN_VALUE;
|
||||
int clipHeight = Integer.MIN_VALUE;
|
||||
|
||||
public GalleryPreview() {
|
||||
}
|
||||
|
||||
protected GalleryPreview(Parcel in) {
|
||||
this.imageUrl = in.readString();
|
||||
this.pageUrl = in.readString();
|
||||
this.position = in.readInt();
|
||||
this.offsetX = in.readInt();
|
||||
this.offsetY = in.readInt();
|
||||
this.clipWidth = in.readInt();
|
||||
this.clipHeight = in.readInt();
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void load(LoadImageView view) {
|
||||
view.setClip(offsetX, offsetY, clipWidth, clipHeight);
|
||||
view.load(imageKey, imageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.imageUrl);
|
||||
dest.writeString(this.pageUrl);
|
||||
dest.writeInt(this.position);
|
||||
dest.writeInt(this.offsetX);
|
||||
dest.writeInt(this.offsetY);
|
||||
dest.writeInt(this.clipWidth);
|
||||
dest.writeInt(this.clipHeight);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 630 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user