Compare commits
1697 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1064b1ba7 | |||
3a9f59b7a5 | |||
ccbe240846 | |||
2c0d96917f | |||
0316cf47ed | |||
e4c8de1145 | |||
9c978c608d | |||
b4a88926ed | |||
de593f458f | |||
f3c8b37928 | |||
91f22c03c0 | |||
81ee1ce39a | |||
2ed54eed73 | |||
a9ef4bef8e | |||
c9b988aca6 | |||
b803dbe3af | |||
35ef07d720 | |||
4f596d68b9 | |||
26776f8430 | |||
471eb36a92 | |||
8b8b377c29 | |||
d70d2cdff5 | |||
b0704063f2 | |||
dde7254dcf | |||
4edb9ed398 | |||
9d764781c3 | |||
f7d991ab54 | |||
59b3b7d79d | |||
354bf362c0 | |||
69304466a7 | |||
6e4d4739a6 | |||
e8947b3107 | |||
153641a249 | |||
60706e8b8b | |||
19dc859ef2 | |||
06ff4444c5 | |||
156cfb0c74 | |||
e49a25b604 | |||
b34a137c07 | |||
1fb6b3775d | |||
b1c9a204c1 | |||
8435be1b1f | |||
7affb9ab63 | |||
5627ad0801 | |||
08017a0cd1 | |||
b6603c3425 | |||
f63323feab | |||
eea5f0ba6a | |||
9f51dfad33 | |||
c31789c112 | |||
86043fbb31 | |||
1136ec2ad4 | |||
7910f6e420 | |||
a087b0bf4b | |||
d0929c1dc5 | |||
46500dcb32 | |||
f85c34b807 | |||
dfc3a5df42 | |||
2b78925c68 | |||
eb2fedd669 | |||
b8b5e70151 | |||
8c00905c99 | |||
598af5ebd5 | |||
8adedee973 | |||
4dc2143160 | |||
aded163e0a | |||
11a553cbf4 | |||
a0048e9397 | |||
a8a22b5803 | |||
4122af491c | |||
0de42b6654 | |||
d7aa1144cf | |||
855f768e29 | |||
87fbda0b29 | |||
99dd9a0750 | |||
c8befdd5ea | |||
54cf97170d | |||
a5aa89512a | |||
b02813f30a | |||
51c8430e9c | |||
a846f7da47 | |||
b4e5443e56 | |||
3fdfd91fff | |||
e4b52c036a | |||
43098aa61b | |||
e4d8fea138 | |||
6da22ea8b0 | |||
5e618e0134 | |||
fd09f64fe6 | |||
ef2241e0cb | |||
bcfe5af135 | |||
a598afec43 | |||
3c9ec48da3 | |||
7ce15caded | |||
7e65e0de0e | |||
07a70d8c92 | |||
038574b107 | |||
24067e1e97 | |||
68b13c6425 | |||
a9de6a123e | |||
b0251b8177 | |||
c069d75ede | |||
5886cb7406 | |||
1713dd4ea0 | |||
f5f7971cb5 | |||
5271abbd1f | |||
7d2ded5944 | |||
d2b0319d63 | |||
c15f4c7fd0 | |||
3aee05bf26 | |||
9d148a70c8 | |||
4a64bb250d | |||
e6f5ea172a | |||
6a0ab3526a | |||
fa29b914cc | |||
3a79f8fb50 | |||
321eb38f2d | |||
46dccf8a2d | |||
7f02a533fa | |||
38d329a601 | |||
1c0f08fc5b | |||
a076deeb5f | |||
a006bc7d93 | |||
2b215524b6 | |||
f497e605b4 | |||
b09f1fff51 | |||
92af538b0a | |||
f0b821b122 | |||
4c5e99142f | |||
b0f39a7d0a | |||
38b469755f | |||
3bfda338ef | |||
6e1da22353 | |||
60a0303d7f | |||
6135b4daae | |||
2fe38192b4 | |||
ef3f4c2e17 | |||
e4e069ccca | |||
505d2e4274 | |||
4cdf2f468c | |||
8c7a2c4262 | |||
156ce14dd8 | |||
27dc6443ec | |||
0a5773211d | |||
b3b0c39163 | |||
bc6b4d4be6 | |||
a14e5a99d9 | |||
d643b947a9 | |||
2033e82ff0 | |||
c2c872d000 | |||
ae9377f8be | |||
93a84cad34 | |||
f9ceb92495 | |||
bfdd5c9ec3 | |||
d800528550 | |||
6ec656b094 | |||
29c5da1494 | |||
0a21a9e5f0 | |||
d3aaa3656b | |||
69a8223d06 | |||
6959370087 | |||
1d0937397f | |||
aeeafe8556 | |||
38c5163ab9 | |||
4646e4b0d9 | |||
e9f105c52a | |||
3b225405cb | |||
b96a870c4e | |||
b70d70e82d | |||
f4d1c27cde | |||
4195b00e48 | |||
0a37dabf4b | |||
2d5ac20c46 | |||
700fd61f34 | |||
a0462fb480 | |||
d9d969406e | |||
f19685787f | |||
60caba86a5 | |||
80fa7ebe6c | |||
9096edfc92 | |||
f30ad78a4c | |||
ce0d99b78f | |||
3ffb80c6f1 | |||
4e3c407583 | |||
aaf9fc5d92 | |||
5043f840a2 | |||
fac3bd1edd | |||
976cd41a87 | |||
4aeaee65c3 | |||
f880a0f1a3 | |||
3a886e39fb | |||
005d43a3ec | |||
1d760f4728 | |||
02e3148efe | |||
e9ada7e5fb | |||
2ce01a1c86 | |||
1af8bc95a8 | |||
0046ed879f | |||
4f8c6455bc | |||
c80b8c8ce9 | |||
5435c2fb53 | |||
88963758b1 | |||
e4d40c7693 | |||
2f928ac034 | |||
509d7b20b4 | |||
35c4d0bf4e | |||
5e5f2b0f1a | |||
58135c965c | |||
5a9b84fe00 | |||
c9a10d9033 | |||
730c316e89 | |||
29a07429df | |||
e44616fb3c | |||
cb6ca49607 | |||
ff8b58ee62 | |||
b1350928ee | |||
52641494d3 | |||
9bfe4ed829 | |||
d6f0c0837b | |||
074a1bbca4 | |||
178cd2b52d | |||
e35f807218 | |||
63ca5cc66b | |||
6b66d4a34c | |||
49580a7630 | |||
0300c1057b | |||
f5b8d3120d | |||
fd88db37f7 | |||
39e36f957d | |||
e84c39bf7b | |||
5641b33bdd | |||
72681bef83 | |||
6de16125f7 | |||
46478546a8 | |||
d4659ffaae | |||
e192806950 | |||
1170334fde | |||
d368064549 | |||
ada0703d17 | |||
06d3a753ac | |||
567c6ea8e7 | |||
bfc0868721 | |||
f5b7a8db1a | |||
bef0a44447 | |||
e9ff202851 | |||
4a3323f7d0 | |||
0014a0ca6c | |||
7e99a9f789 | |||
03e5c5ca10 | |||
e4ff988f4d | |||
dde2320afc | |||
988d508025 | |||
24850450cb | |||
e97d1ac257 | |||
bc44eee5b7 | |||
7a010e3446 | |||
cb4fbb19ed | |||
ddf0357c5c | |||
800c01ea42 | |||
68bfba486e | |||
c09f6cb20c | |||
959bad0247 | |||
efb8555d76 | |||
a393772083 | |||
b4ade8c15d | |||
03502f6533 | |||
42c7669bca | |||
1916751f4f | |||
e46dd808e7 | |||
5e92664761 | |||
aa5fd22db0 | |||
56b1c07f58 | |||
2ecac08bcc | |||
95002a6e87 | |||
cea2b42b41 | |||
8bc5a7d746 | |||
85723b975b | |||
a2e05cf80b | |||
87363e47b0 | |||
aefa7a1a4a | |||
18f90587f2 | |||
f47e9b6e14 | |||
a7e4c1295d | |||
50097eef9f | |||
949cdccbae | |||
3d0dc64de1 | |||
2e0102d689 | |||
2557111607 | |||
46a626d2c5 | |||
fb92b1a0b4 | |||
8e6ed194a6 | |||
01175e687c | |||
00d2d4f969 | |||
43715f9835 | |||
b99229b033 | |||
62df1263b1 | |||
a6f0e7f9b9 | |||
f1472d4f8b | |||
52ad282492 | |||
e7b39f29f2 | |||
c62d3abbc5 | |||
fb3ce226b5 | |||
de5f43713f | |||
84aba68b96 | |||
1be0171398 | |||
82c49a42c3 | |||
599a4c7b17 | |||
895dbbdacb | |||
77026d4eeb | |||
256b1881f0 | |||
2a299485cc | |||
a6909a76ed | |||
1ecba5bb7e | |||
c381b737d3 | |||
70e7974396 | |||
2104b60b81 | |||
09dcb8029c | |||
f2ed26479f | |||
d03d9b344a | |||
9b5fbda9a2 | |||
647fbd1f4b | |||
fa6ed901df | |||
23ac3d18e5 | |||
f18891a07e | |||
c85825f3c7 | |||
319571edf3 | |||
1f20d96191 | |||
c2e50305eb | |||
d00e1284d6 | |||
ef4430216d | |||
643f666178 | |||
989b968e5b | |||
92484e26e5 | |||
429056f2ca | |||
fa2b44eeb3 | |||
52e742049b | |||
97a86269e6 | |||
654d98b5c4 | |||
0509db1935 | |||
acf879d28c | |||
cefe45e8cc | |||
03b44fff22 | |||
71c373926f | |||
1af11f076f | |||
41c99c33a6 | |||
58cce53746 | |||
df6cafd6d1 | |||
cc6c1b5641 | |||
d61adc0259 | |||
9ed7ee65c3 | |||
5f94e230f9 | |||
4d8f44ddae | |||
264e04368b | |||
43c11c6ac5 | |||
ede42cd6aa | |||
33901dd72f | |||
ac4ae8103e | |||
2c93704b4a | |||
e2158af592 | |||
b06189ff95 | |||
d72c51c8dd | |||
e29e94a5a1 | |||
04d7eccc63 | |||
e58896bdfb | |||
d2bb4487e1 | |||
e8623fbd7d | |||
0a6de2b3ea | |||
23a8e31791 | |||
286d2b4cf6 | |||
1ab07d169d | |||
abf2d4b718 | |||
37045c77cb | |||
fd5d52a709 | |||
abcb21491c | |||
33d2b0984f | |||
fc7a040509 | |||
be09dded20 | |||
6493b9a6b3 | |||
f07fbcc196 | |||
cae90ddd08 | |||
7bd9b21e5a | |||
e29fb68375 | |||
33e0a34916 | |||
fccb2e762d | |||
956b3284d3 | |||
937d0852b3 | |||
cf2967cf34 | |||
c1e64a839d | |||
c06928a7a5 | |||
8c0cafc793 | |||
543ca43e24 | |||
31b86f539b | |||
dff1f4dd52 | |||
276cfed832 | |||
bd61c33097 | |||
e2a9c09597 | |||
5059c6295f | |||
916c1c9de2 | |||
2661700d9a | |||
02502c4ea5 | |||
382e85a8a0 | |||
05d830eb58 | |||
7b149425fa | |||
6f0e1965b6 | |||
970f1466b5 | |||
e422b1d6c1 | |||
8992f47915 | |||
fe65f4d6f8 | |||
8077e421e6 | |||
4f806a921f | |||
8efe0502f3 | |||
0eb61f34f6 | |||
cc7735e284 | |||
e24ddb9106 | |||
67d284fc35 | |||
a9d32fea37 | |||
3374481912 | |||
1972cc25a9 | |||
6a3cd0054b | |||
548dbf4b78 | |||
74af40a352 | |||
8f26c4bd07 | |||
1d51b06d3e | |||
305ee3c12e | |||
90ce89193d | |||
2ad3fa1593 | |||
4bb9949768 | |||
c4f1d2f153 | |||
ed6f82af0f | |||
3ba8cff60f | |||
7307ddd1b8 | |||
0601bedb0b | |||
0c988d95ee | |||
d593362ba8 | |||
f97f4c68ba | |||
6d60d14f4a | |||
d13bc9d420 | |||
2a41e4ce68 | |||
850654dccc | |||
2f9dfed073 | |||
57911c42d1 | |||
6064eda68f | |||
fe803a4588 | |||
2787116171 | |||
0a509cb382 | |||
b25ab941ba | |||
6ef59a5949 | |||
17fc8deb19 | |||
45b5c1c262 | |||
7fd547a2d6 | |||
d19d787f6e | |||
52474e39d9 | |||
d243ee4b4c | |||
2c2f8f5853 | |||
64ba127e7d | |||
9d22a9e664 | |||
96c55db7ca | |||
0f48563e29 | |||
7c014292d2 | |||
19507d1837 | |||
81b2dfd9d0 | |||
3f63b320c4 | |||
4da760d614 | |||
a95ac8ad83 | |||
f841d3d259 | |||
902700e1f4 | |||
bbf456a4aa | |||
134dbd2f1d | |||
d371b093d8 | |||
979c49b99a | |||
dad010a891 | |||
9f974c9401 | |||
fba3ed2244 | |||
aa1d927da6 | |||
292655cbdc | |||
a088c9ca7c | |||
8c1ec43500 | |||
1ac1c6dc9e | |||
87ffd6eef2 | |||
41139cea76 | |||
d98b7275d1 | |||
77cca1ce84 | |||
c0bbbdb7ee | |||
8e6b7aaec0 | |||
aae6820fdc | |||
2a4f35959b | |||
f71708e5c5 | |||
6de00b1f21 | |||
2b27b40142 | |||
55101b1a4a | |||
c014fd7f6d | |||
53a3be0703 | |||
04a178e7da | |||
6d5b6b2ff7 | |||
ab97de2763 | |||
34f7e4d448 | |||
f7c139030f | |||
c967308859 | |||
02207f6cfd | |||
badddcf0de | |||
b6f1c516e2 | |||
932a47a8a7 | |||
f69f78db34 | |||
fc2c465bac | |||
f2a7f8efda | |||
e6c172ac22 | |||
d8e7481118 | |||
2485ef8547 | |||
b17762f8d9 | |||
b3611eef9d | |||
a68c1adba6 | |||
42b536e40b | |||
d6e49be268 | |||
3d10dad780 | |||
c9a727594e | |||
cd25e1283a | |||
6675de1a26 | |||
96618e9517 | |||
dff239141d | |||
abbb329ba6 | |||
5bc77fc6e8 | |||
9c820fcca1 | |||
bb064a1ba7 | |||
a5d9fb518a | |||
01dd46c5ed | |||
ed51989796 | |||
bd20977ebc | |||
11e10f6eff | |||
c88265ac04 | |||
3f655ca50a | |||
359d4dc1b2 | |||
ca47446b46 | |||
cffb2db7ae | |||
0272907596 | |||
321a4b24b9 | |||
8a243ffb57 | |||
c2330fe3af | |||
9876732875 | |||
29e453c201 | |||
6c14402992 | |||
bdee525336 | |||
fbf13efe74 | |||
415df2357c | |||
4fc8800a37 | |||
6a532b836d | |||
31b94fd3ff | |||
fd733e819d | |||
7b6d52c613 | |||
df69559b39 | |||
e388f0d563 | |||
85e7b78b21 | |||
42a97f8c40 | |||
5cff247aa4 | |||
f115691365 | |||
deb66436cb | |||
b8152dd7f9 | |||
678c004a64 | |||
d9f44c1f7d | |||
088160ed32 | |||
8de004d281 | |||
bcde4337ac | |||
401210da44 | |||
beb81b657e | |||
f7b3450d65 | |||
29776c739a | |||
bdf322ceb0 | |||
fae763dbb0 | |||
c0792522a4 | |||
c67e62bac3 | |||
9dc184adff | |||
7118817df7 | |||
287b83b6c6 | |||
5d03eef051 | |||
48f7b06549 | |||
4e111cebbe | |||
fe32332e2a | |||
0bb6e1cdc2 | |||
e67bb64311 | |||
9058536406 | |||
5bc9e1632a | |||
a0a8899801 | |||
aedb4749a2 | |||
f52d49ad00 | |||
a1e7592bd8 | |||
c784d24fa6 | |||
0375c0b2c5 | |||
135e55fe27 | |||
1ed291086a | |||
f6e25627de | |||
8fe79a1fb5 | |||
a1df78517f | |||
92fa8d683a | |||
df27138401 | |||
7c7d40ea44 | |||
aa70be525d | |||
f7ac969a4a | |||
4f5e52fdd4 | |||
5183848250 | |||
69af1baf7a | |||
9a28cbc1e4 | |||
c38b457ba0 | |||
1a50f7062a | |||
8cfd80ba84 | |||
e5e14e1f9e | |||
6611464f73 | |||
51d93f0217 | |||
fdc7981d18 | |||
a63d165dd3 | |||
145a744ce0 | |||
df625a998f | |||
21c1a499ac | |||
9a81cabece | |||
b50dc206eb | |||
9044760a10 | |||
0b811773e1 | |||
627a720d4b | |||
89d45e7775 | |||
c0e6e03dc6 | |||
05fd8e2a38 | |||
dd59748bf0 | |||
38ceaf5253 | |||
b2fba5083b | |||
be6a209fe9 | |||
0f0305c602 | |||
d5350fd719 | |||
ea75f63dfb | |||
de8e530b37 | |||
bff927c6eb | |||
985bb44559 | |||
9c44cae5b8 | |||
6872455922 | |||
ce13a5152b | |||
d7c13cc291 | |||
022c0746c0 | |||
06c3f57f62 | |||
9da27cc56e | |||
92c5497eab | |||
f115fe47fe | |||
224f08279b | |||
a3b660a2c9 | |||
89df50da4e | |||
cce3b3a559 | |||
f53cc10338 | |||
61fb4d584c | |||
b93e2ca4cf | |||
325b3c6271 | |||
859e9ca653 | |||
441e2a69d8 | |||
af937f2e31 | |||
912629c2dc | |||
879fa484f6 | |||
e86103fdcc | |||
920ffa8c24 | |||
488f81ef74 | |||
0a5461cbea | |||
40c934c544 | |||
bb43e2aa03 | |||
ed277357cf | |||
bf1fb8b7bd | |||
e615cba9a5 | |||
8adabe1f74 | |||
896b34d1a2 | |||
5fd1865504 | |||
0b318a19c6 | |||
622d6c0cab | |||
3f4140900d | |||
b434bc93a3 | |||
11529f2795 | |||
d86b030796 | |||
a7e4657752 | |||
2f28fcba05 | |||
6da350aee6 | |||
9585f9a1a6 | |||
fd4876be24 | |||
31e2fe6a4c | |||
aa51968603 | |||
a3336368e5 | |||
33c0c6ff3b | |||
eaf37c828b | |||
d65a8e84f0 | |||
b000d96dd6 | |||
cd867f800e | |||
124f0e7093 | |||
fb897e37d1 | |||
3e5a48e5e4 | |||
3f88a67865 | |||
ef0b546d4c | |||
088f8b8b54 | |||
1ebcafb25d | |||
90396153f4 | |||
0fa93cf615 | |||
15a7a2b0ea | |||
446c254bc8 | |||
e41edc1fb7 | |||
738f776d36 | |||
e77db309b8 | |||
a6c1de1cb2 | |||
ace54f8175 | |||
d7043bcaeb | |||
04bbc764a0 | |||
91f7056767 | |||
8299093bf8 | |||
a0dffcf51f | |||
b3daf7d760 | |||
724e1d33b6 | |||
9deaff9181 | |||
b9ea6e8d96 | |||
231771e16c | |||
5b8308b3d2 | |||
f91f9c4862 | |||
24b848faac | |||
7d0ea614da | |||
2615b067e2 | |||
cd1abb60d7 | |||
436ec0ced7 | |||
937fb85376 | |||
a405324907 | |||
fba55711f2 | |||
208552f0b4 | |||
c7cdb950ce | |||
921169b3ad | |||
d7d3731567 | |||
d5ff5fd6f8 | |||
e195257d2a | |||
466ec7b962 | |||
8bfe59c8a8 | |||
de512216c4 | |||
9936b402a6 | |||
bd2dfaad2e | |||
8e539bebea | |||
022cde2c00 | |||
572f58a3a4 | |||
07e2bdac81 | |||
fb00929ee9 | |||
fb5da15746 | |||
48363aa3b0 | |||
090a7794b5 | |||
c63d8e7a30 | |||
d6ea69a115 | |||
4358f9fd2a | |||
af2ef36d68 | |||
316211372c | |||
29a2d41331 | |||
9f8046324d | |||
af05c34da3 | |||
a4410f3a02 | |||
4503199d25 | |||
c275adbb91 | |||
4061232fe3 | |||
507471e318 | |||
f0f613e2cf | |||
0ec8def0d8 | |||
fbaaed1516 | |||
152c196c65 | |||
e4dc84a5d8 | |||
5fb3b0e0e3 | |||
2854fb5f6c | |||
a9adb2f1a2 | |||
51383afd50 | |||
10bdde4459 | |||
4b02ecd6e8 | |||
b84638ac5a | |||
79f2882aaf | |||
1c978f64b1 | |||
5e9496ef36 | |||
8b6268966e | |||
52434819c3 | |||
29eb87b7ef | |||
c38026886a | |||
63e330b83d | |||
164da0fd9f | |||
e30b1de100 | |||
6940ad3fd9 | |||
8ae15141f6 | |||
89007923c7 | |||
4c10b9844b | |||
f3d69599aa | |||
fc8f91baec | |||
853bf3065a | |||
8d712c81d4 | |||
aa0597da2a | |||
a29f33020d | |||
9eb441ac44 | |||
10e7c96379 | |||
6c474daacd | |||
bb7ed73743 | |||
ea749d69a3 | |||
5bc0dfd616 | |||
fef34dfe82 | |||
a3dd5c1e92 | |||
d873d653d0 | |||
d9934ad8db | |||
26e5364aea | |||
91451111a2 | |||
b104bec49d | |||
0ac33b64b1 | |||
82141cec6e | |||
218313428f | |||
44b47b49bc | |||
2f69317f5d | |||
e1eff7b744 | |||
3aa12281c3 | |||
72920130c0 | |||
0fd00331e1 | |||
7a4763ee68 | |||
647a78b791 | |||
82faa91ce3 | |||
ad664dfb9f | |||
005ac9e732 | |||
d8e3fe542d | |||
c40e4f6c5a | |||
9bb3195bca | |||
eee0bc4985 | |||
a24d670f54 | |||
51e049ab78 | |||
01e37dfab8 | |||
74087edebb | |||
db58c9b77f | |||
c4dad1c20b | |||
cae04656b9 | |||
6586c4e387 | |||
4e60a81b36 | |||
464b4b18a4 | |||
e5c0969047 | |||
ab0d144300 | |||
ac3823e10a | |||
d3a4126e27 | |||
3a62acc54d | |||
bf8268adc4 | |||
7d4f25b354 | |||
0f2d480036 | |||
043e3784e8 | |||
58ab06b4f8 | |||
11544fe8ef | |||
8776a45ee9 | |||
a7bb059096 | |||
ba8977b47f | |||
032a6adaab | |||
460fbb18c7 | |||
978ac50015 | |||
b323b9c843 | |||
1afcf34829 | |||
48d9ad00e1 | |||
ca10356fd9 | |||
275bd44e15 | |||
beb2d880ea | |||
61d2107e9c | |||
b06f1c81bc | |||
8bb83782c7 | |||
aa05458f1d | |||
c694160c9c | |||
5b24a8f21d | |||
2c23c42c98 | |||
edcadb7dd1 | |||
3bce3502d2 | |||
9942227c6c | |||
02b5c3da71 | |||
ef533b4c87 | |||
3ecc883944 | |||
a1fadce7c6 | |||
79bc1290ae | |||
10272ef395 | |||
f03c49850b | |||
6677c10173 | |||
3223a3ac54 | |||
497fe1e68a | |||
3b334c4230 | |||
7f115f2e83 | |||
12aa04be93 | |||
a7ece4fdf3 | |||
57d1ed1073 | |||
b04ebb1782 | |||
958dbfdfa5 | |||
eb724336f5 | |||
9cdd4bee97 | |||
74cc77400c | |||
247a39c0a9 | |||
1b0c13a417 | |||
abb2e231f6 | |||
50ef4cc5da | |||
34bf9b729e | |||
0a6f607e22 | |||
9e4c61c139 | |||
144418434b | |||
a20ad68fe3 | |||
a50a3df716 | |||
f29124773b | |||
c1235897df | |||
4a52869d23 | |||
f9098b5379 | |||
6a95ff56df | |||
340829bb71 | |||
8aa48effaa | |||
a5fadcda15 | |||
f515674dff | |||
72ef5d8f8d | |||
a0a077eaaa | |||
8feb4365dd | |||
94b2dd74de | |||
678597cc96 | |||
417d82de58 | |||
f3adff1da1 | |||
0473c36c6f | |||
c9cb75aee1 | |||
36011e124a | |||
b9420040f5 | |||
59b925a028 | |||
7af075633b | |||
09891bb0ad | |||
160ebe01d9 | |||
a096e6b337 | |||
3c41a5e910 | |||
a3e39987d4 | |||
47f5ea881f | |||
faf8f1fbbc | |||
9f9de27a57 | |||
9a3ec56eb4 | |||
4f03ee814a | |||
6f84815801 | |||
9588a582ce | |||
ed8e549ecb | |||
c79ebd4eeb | |||
2a85bb28b9 | |||
13ea1342fb | |||
c707d4bfd8 | |||
aeacdad484 | |||
6d9bec3e0b | |||
1b5554eeda | |||
bf140be75e | |||
eb4c7c6841 | |||
dcd3c709fe | |||
f966187ea4 | |||
a288c0f280 | |||
a746d4cc3a | |||
f1cca207fc | |||
f4bb9b604a | |||
7be6ee9a68 | |||
a22c79c58a | |||
b642e019e8 | |||
578bab5fdd | |||
b48f08e65c | |||
81c14ba610 | |||
4b84fb5ac5 | |||
7f5e650796 | |||
c22e2e8159 | |||
ee8a53188c | |||
98f86a44ef | |||
1b3169e0d0 | |||
8273a396c8 | |||
5bad914411 | |||
879d260202 | |||
ce4d75f62a | |||
f30622424a | |||
09e7d56ff2 | |||
5ca23b5363 | |||
f7e70d25ea | |||
4338c41112 | |||
01f9b25be2 | |||
8b0458cdf6 | |||
bed978a26a | |||
73fbc81067 | |||
0d7f84857c | |||
42d6815ece | |||
d1db9fb659 | |||
b74fb2ef5e | |||
f3e228e8a4 | |||
6b5742c1ff | |||
57595988f5 | |||
81418a7712 | |||
885c7bbb10 | |||
d4c25359bd | |||
44f406b4b9 | |||
427d2fed8c | |||
51d454cded | |||
ab2bdfc508 | |||
3892b93bca | |||
83c2e907c7 | |||
32b7cc68b9 | |||
a61e3cd689 | |||
f922598127 | |||
e11c289150 | |||
1b37c61b5a | |||
2d3bfa9a89 | |||
e414b9edf1 | |||
62d3fc65e0 | |||
262ad45b79 | |||
cd90702fe5 | |||
012b1b56aa | |||
ff999a6dda | |||
797553ce16 | |||
8f82c8ad3d | |||
7a2c132b8e | |||
ba9f6fef99 | |||
6633a96245 | |||
745f8d32b5 | |||
f715478070 | |||
5f2aaeac57 | |||
044a4f7575 | |||
83d5e458ca | |||
f7669b6797 | |||
eb56567812 | |||
489f981e40 | |||
19adbeebd5 | |||
dc93368e03 | |||
8d3166c5fe | |||
07caea8b4e | |||
141b7ac554 | |||
4bc5f1401f | |||
13a2d3dfdd | |||
d62f0de862 | |||
0073ddf237 | |||
e411f54236 | |||
6025b44e5b | |||
f4f427dd2a | |||
ea226a1697 | |||
26c5c9c839 | |||
5cddb269d6 | |||
0d5099f230 | |||
b55814a1c0 | |||
eb5382e0de | |||
b20c028566 | |||
9b883b1a09 | |||
39d509a756 | |||
b3f1714ba9 | |||
708525ef9d | |||
df14e6d43e | |||
faedd325be | |||
600fbb2ef8 | |||
b4aedb5f84 | |||
53402459f2 | |||
ed7ebf2da1 | |||
dd1e6402c9 | |||
78689e7443 | |||
bc6a1a1da6 | |||
aa57b1bc77 | |||
491d476cac | |||
f0053a2f78 | |||
10e7a3b35b | |||
4147fd6b19 | |||
2bb903088e | |||
c90f985fcc | |||
2ebaacfc89 | |||
c339bd49d0 | |||
c349fb0e37 | |||
bc825bdefa | |||
ed49ce8e1d | |||
ad2ecd538d | |||
ff8e3f0af4 | |||
698e17178a | |||
ebeee70931 | |||
b8b118bdeb | |||
5ddd7d1b14 | |||
450b23436f | |||
89793ac338 | |||
c456812a46 | |||
6f8f6e9233 | |||
5770d00f81 | |||
a0fb1eff4a | |||
89dc240a22 | |||
ee4f069341 | |||
011bb9f5b1 | |||
08b06e1b4e | |||
0416a2ff15 | |||
3a7cdfcaa4 | |||
c36a47576d | |||
f14af7cf83 | |||
4014c48c62 | |||
6c9135c093 | |||
19993199db | |||
5b9f362925 | |||
690d2fb15b | |||
7ac188709b | |||
93c8773e91 | |||
eefe95c2ee | |||
af9825d369 | |||
59f38cb343 | |||
810d2d4776 | |||
027cb10d0f | |||
24a6eba94e | |||
80ea9001b3 | |||
24bb94ceac | |||
0f16351f5f | |||
b60b26bbd0 | |||
86e53e08de | |||
d3cb10a74e | |||
7d6cfff719 | |||
cc0fe0a1a9 | |||
25327342fb | |||
e02cf67f85 | |||
bf4bef6d62 | |||
76645bce6e | |||
9276c491bc | |||
fa59b4f8a7 | |||
934a37c36b | |||
5362f62078 | |||
ccd360687e | |||
5a2e8a838c | |||
c1c43bb6fb | |||
0804550539 | |||
3156482334 | |||
052317b38a | |||
6ff684f638 | |||
6857c8c1fe | |||
44d563e689 | |||
6ce2809450 | |||
0759036536 | |||
6f6490b7ac | |||
da12a4b17f | |||
1302bc84dd | |||
1c4a8046d0 | |||
141edac99b | |||
b64ecfb836 | |||
d5f4db5eb7 | |||
7a3f6e9f63 | |||
b3ecc91be9 | |||
2a27eacf5e | |||
e747686ad8 | |||
282af20146 | |||
f25cd9fdbf | |||
2369547d59 | |||
d86b8cc78c | |||
b4c1e6a44c | |||
45fc2f2e0e | |||
9cfcacf45e | |||
801fd83649 | |||
16f1dcd922 | |||
8f36d698cf | |||
a89a36fd4c | |||
8a7f8c4068 | |||
9c70e69300 | |||
a0b490b10f | |||
d69dc375a3 | |||
ac6dbbcd89 | |||
14879c4466 | |||
dce08d4922 | |||
5195cb8eda | |||
68de7b516e | |||
10095205d8 | |||
c4c988f7a4 | |||
333dfbc642 | |||
e915fd28cb | |||
46636b537c | |||
cd5545284e | |||
3dac6366ff | |||
054a26cf65 | |||
024c76d480 | |||
9abdfeac77 | |||
0f12ae0cf7 | |||
71182e664a | |||
7fc6d53cae | |||
4f2985469c | |||
9cc24a3be3 | |||
7ec18861bf | |||
3d1c02136a | |||
aca34155b9 | |||
d04ffb7ea9 | |||
5b3e72db54 | |||
f811cc5c87 | |||
e7abe27bb6 | |||
126d875547 | |||
a2b7228e95 | |||
10d6b3a6ca | |||
4c9be5557d | |||
37e0ac0895 | |||
8934d251d9 | |||
b5263a6968 | |||
15bd8e964d | |||
d931027067 | |||
39ffd3c3bc | |||
9ff8235de4 | |||
54075733b7 | |||
2ea0538825 | |||
275e20eabd | |||
7fe742e6ed | |||
3abae1cc75 | |||
b68ef8c983 | |||
d5f5ba95bb | |||
e8638cb0b3 | |||
62f9071adc | |||
1d079dd9a4 | |||
cccb56bda1 | |||
5d8dc241d8 | |||
9ba7312caf | |||
8ebda219c4 | |||
47f14e8555 | |||
974a24d03b | |||
15f225537e | |||
a32572fc96 | |||
be3ed9b6af | |||
a0939e1c48 | |||
003dca9d45 | |||
5c1770247c | |||
021dde66eb | |||
5840a3e1e2 | |||
7c6478fe6b | |||
9a6bb69df8 | |||
4c93ca6914 | |||
51939ea652 | |||
256a4a2e7f | |||
c217cab5db | |||
c93b6deaf8 | |||
55196b86b3 | |||
c5ab79f618 | |||
59fcfbe9d2 | |||
6ce70296a6 | |||
fdef687f0c | |||
cdd7f42532 | |||
65955fd7dc | |||
b785f68154 | |||
895191814e | |||
b06f253f83 | |||
0814439886 | |||
a978cb90e8 | |||
39e0d434ad | |||
6ada3cbf59 | |||
4aff768b8e | |||
e08e569135 | |||
8cac0fca67 | |||
f447b06eff | |||
78b2045b14 | |||
a427401d66 | |||
6da6ca710e | |||
f46c42f522 | |||
c4df7b496b | |||
6c4332f5c2 | |||
86076d890a | |||
76a041f504 | |||
c9bdc72d5d | |||
f6a8b23498 | |||
2a60c828d7 | |||
ea7ff432b2 | |||
5c2fbec80a | |||
f0109aa796 | |||
66b92f3eb7 | |||
ec4af65c36 | |||
852c1a423d | |||
1904be277d | |||
ebb1022100 | |||
8d85ec3cd9 | |||
1d36c3269e | |||
a218f4a48b | |||
68aca55e6f | |||
b14c3cc49f | |||
cbc4a0f3ab | |||
70a024d3de | |||
28d43bbecc | |||
55a3b2f3a1 | |||
2545b22ab1 | |||
77c07d13c0 | |||
98ac8a69c2 | |||
0e22af7ba0 | |||
6951ce34c7 | |||
24e10d6037 | |||
f1b08bf56e | |||
3cc3799ebb | |||
aed20a790e | |||
81b20a23bf | |||
bd27cb74a7 | |||
07fd3575c5 | |||
6da504c999 | |||
23c1827838 | |||
49833fcc48 | |||
0e4c0fba5c | |||
bfe67f1cdf | |||
863e349711 | |||
5e13df9bd8 | |||
65abb8fa6c | |||
fe5c0295c3 | |||
4d15ac2fa3 | |||
5e4b9fb15b | |||
8a70dffae8 | |||
8119eb4b34 | |||
1bae7ba8f5 | |||
d4f1014df6 | |||
dce685e711 | |||
c4e6668c22 | |||
9b5608c86d | |||
2485a00d34 | |||
8abe2d76b0 | |||
ba674935f4 | |||
a053d55fbc | |||
38ba8852a3 | |||
3533359fae | |||
8e9087226f | |||
8ccc3c8b0c | |||
0a988d1c69 | |||
5f9e65cc9b | |||
026188268d | |||
0e3464457c | |||
56195434e7 | |||
9b3579acf6 | |||
ba2194f435 | |||
6f2ff6a77e | |||
f772501159 | |||
cadd389658 | |||
6f36331818 | |||
d684eb5147 | |||
349546cf87 | |||
352593ebc6 | |||
c6b28cbcaf | |||
c5e69d2080 | |||
cd4964d086 | |||
e7df172da1 | |||
e7606e6dca | |||
8d4c0f505c | |||
ac3967e997 | |||
5df18f2558 | |||
0b054126bc | |||
d4740c57be | |||
739fc9f95d | |||
96b1340aec | |||
57d83e3d1b | |||
4e2c9dc083 | |||
fa6f60d454 | |||
71fe2bda97 | |||
f24c5e944e | |||
603fd84753 | |||
8f2878a841 | |||
77296348a0 | |||
a62a7d5330 | |||
bf60aae9d8 | |||
ecc1520100 | |||
f1f6a2b341 | |||
55bf1c31a6 | |||
5fbe1a8614 | |||
e47dd3d587 | |||
af0e3a278f | |||
493ad93957 | |||
dbe8f3cfbe | |||
08cdac968d | |||
f12d5ba689 | |||
0afd77d110 | |||
7551941ef2 | |||
9ca0307e1c | |||
9a6f8be28c | |||
9baf3b5a09 | |||
ca3f0873f3 | |||
adb0201449 | |||
cf293642fb | |||
10e1106760 | |||
3f2d375a53 | |||
f8e121ee06 | |||
0ee005579b | |||
6ecd7fced8 | |||
aeaf4d78f8 | |||
7baf0ddcc2 | |||
d79e141fe5 | |||
030071e659 | |||
9cbf226cfd | |||
36aabf23e1 | |||
8b67255186 | |||
3186661420 | |||
46896d9e86 | |||
2c4fd340c8 | |||
ae6d052978 | |||
974891a085 | |||
d44cd16682 | |||
23e99a3ed8 | |||
024a457250 | |||
788cb843fc | |||
790e0908a3 | |||
7a45cd5b56 | |||
f61a8ce51d | |||
fcce29a467 | |||
5f568733f3 | |||
96340de17d | |||
3611f67fb4 | |||
353ccbd444 | |||
3c1179d27b | |||
e502caee9f | |||
da8b870670 | |||
6b26859983 | |||
7afd224aff | |||
62e7bead73 | |||
116f7d1c4a | |||
18f89cc341 | |||
42169783e1 | |||
90574937df | |||
ec0fe2210d | |||
450fcccaa6 | |||
72504ca53f | |||
6bfddb3ece | |||
7c99ae1b3b | |||
b62fcbc2ec | |||
411dda0e75 | |||
d9d71c8745 | |||
35239af039 | |||
840e571917 | |||
2fd4204db8 | |||
1fd105ac56 | |||
6ae90e0a7d | |||
43dc12a0f3 | |||
28d05b629f | |||
30c12fc9de | |||
cdb62f502c | |||
5cfdbbce7a | |||
16dc4d298d | |||
762c378bd6 | |||
515289134e | |||
3d1afe7cf2 | |||
fd825b1049 | |||
136e90638a | |||
9bf071132d | |||
014bb2f426 | |||
e2af1af656 | |||
a96c25c37c | |||
c1abec3b4c | |||
c5a488ca3f | |||
dd4a18a0b2 | |||
34df0759f3 | |||
b6a3f6ebd2 | |||
ad2819a3bc | |||
0f2be86d5a | |||
4342584c32 | |||
71c10df270 | |||
db90b5d2cb | |||
56927927c8 | |||
b19a4d2977 | |||
f4b838d8e2 | |||
c6cfd24f19 | |||
10f36f40d6 | |||
9d5cf9163a | |||
9abce0cca3 | |||
c6245f4fa3 | |||
75fc160204 | |||
263198dd89 | |||
345f96055d | |||
51144aa45e | |||
86a599d13f | |||
0cf81e6f7a | |||
8874fe973c | |||
f8a03226ee | |||
32db1e3045 | |||
303e6c0102 | |||
18883f1ba3 | |||
5c31271e91 | |||
00981cf4e8 | |||
968f4a69e8 | |||
5cc38c1369 | |||
e7c48a98df | |||
d03c49db58 | |||
b69af710ad | |||
4d3b469c48 | |||
8cc6c0171b | |||
8be5be4720 | |||
995a1155e4 | |||
234c3bb72a | |||
aafe863774 | |||
e7e1a9bf50 | |||
fe1becb001 | |||
7789171c71 | |||
3fd2222c99 | |||
fb1db914aa | |||
7aa8abdd98 | |||
4bd965a795 | |||
df2a4779bf | |||
d2dc063c8e | |||
6de36a88c0 | |||
b37685542d | |||
a2b1b9e746 | |||
8017324033 | |||
7464497c88 | |||
499def3daa | |||
6931b75cc5 | |||
f853610578 | |||
69f51b88bf | |||
e0d680201a | |||
1566b8f8b8 | |||
4bbf78e840 | |||
7ab16a69df | |||
95e60ed775 | |||
d38cd2547a | |||
45f4c63941 | |||
5e968e5651 | |||
3e3c0a1f14 | |||
c48bebe0b2 | |||
0dd9e9e015 | |||
d2a2e17e91 | |||
2159b72e69 | |||
09dbd723e4 | |||
81c23bbf9d | |||
eb965542cc | |||
615fa05a75 | |||
87a2ac7887 | |||
0d5b8edf31 | |||
fcdb80830b | |||
50b48ab25c | |||
31b45666b0 | |||
233e76724a | |||
af637a82c3 | |||
ea32ea11f2 | |||
1b7a0de745 | |||
50e0cb65d9 | |||
ba4807f62c | |||
5efc02a238 | |||
8e50ac67bc | |||
a3c03e8ceb | |||
5a3e30b30a | |||
e3ab90042d | |||
07ce90ab8c | |||
a71ae29c98 | |||
f35c15f7d2 | |||
32387cd034 | |||
117214c671 | |||
cf5c816483 | |||
bf9b9ca54c | |||
0ca2ca33c2 | |||
51f25e96e9 | |||
1875047638 | |||
fa4d61eaf0 | |||
49eb638e15 | |||
fc1f290b85 | |||
9194dc0161 | |||
0d480dbf7c | |||
183e83684a | |||
7b4ac7998a | |||
d75c6b0c36 | |||
40b222f8bc | |||
aa7dfb7bee | |||
6c1453eb54 | |||
c54d26d6ba | |||
5447bd098b | |||
2b7c0e8e80 | |||
c1845aec83 | |||
eb8479ac9a | |||
636c027298 | |||
02e187f066 | |||
854112095b | |||
a71c805959 | |||
47966d89f2 | |||
b28a2c3bd4 | |||
8f51abfc97 | |||
e9bea8ed47 | |||
c2c1b6d2e8 | |||
ded22f1717 | |||
126da3979c | |||
d892f2f7f4 | |||
c3ced0d089 | |||
80996ea63e | |||
f18b32626a | |||
8c8f2585aa | |||
aff51f8af1 | |||
ccbb81e9f5 | |||
f88dd28c51 | |||
a65a71df5d | |||
22f2ecc433 | |||
7f90ad7847 | |||
1292c0ecea | |||
55b7d5025b | |||
6a310bbaa9 | |||
bc8753da85 | |||
7f63e318f1 | |||
6c749319cf | |||
7a4463e104 | |||
e1be4ba925 | |||
34d21c1de3 | |||
fae36aebf4 | |||
75e828923a | |||
b499b87f8c | |||
233dbec4b3 | |||
d56ff9592e | |||
08f6317beb | |||
a75457ad88 | |||
b0482003bd | |||
634356e72f | |||
6d3cc16ab1 | |||
6027671c09 | |||
29d0cb4a15 | |||
fe7001975a | |||
ac88f1c146 | |||
b5b86218c5 | |||
bdcc6e52e6 | |||
0eae817aa6 | |||
8994b42760 | |||
6a63ce992a | |||
1a811d0917 | |||
9af552c15a | |||
263cc1d97c | |||
9ae6285eef | |||
8f9737f567 | |||
f287d313c3 | |||
e745836404 | |||
08baf798aa | |||
8bcb14c65d | |||
d94dc68830 | |||
297fed6aef | |||
d690d6e0e3 | |||
9ba8d88b07 | |||
34a40b0131 | |||
182bf5f2bd | |||
dec4471871 | |||
04638535d8 | |||
c8122ebd68 | |||
b766ddea54 | |||
5cb219d83e | |||
908128b55d | |||
b240960a8a | |||
b91d8e4c19 | |||
4853ff7eee | |||
e1582bd73a | |||
ac17335dbe | |||
7053777997 | |||
1107b95ebd | |||
36003fd622 | |||
84121ff901 | |||
bcc2ec1668 | |||
08dffda2a1 | |||
9f4540a4f1 | |||
f19ef9aa01 | |||
f10a06341b | |||
67ac95f15f | |||
cd291f0a27 | |||
bb6b88a703 | |||
239b36c31a | |||
dcb6ae44dd | |||
5ed365cf0d | |||
b20b3d6f5c | |||
32d02f9329 | |||
3da7c47bf5 | |||
029f159aea | |||
0a300822a4 | |||
8d48e0289a | |||
9dbb59f337 | |||
a243aeafba | |||
5c671de29e | |||
e161e5d30d | |||
2fd64d0ca7 | |||
d981c75600 | |||
492adb7035 | |||
811bca18d9 | |||
3222e0646d | |||
fa6790856d | |||
38a15d4158 | |||
77cd459c51 | |||
fa115ed9a0 | |||
30a2b572ab | |||
b1ab77188d | |||
e212e4581d | |||
1b8daa7e09 | |||
99d126fd3a | |||
9fee7c6b19 | |||
1b5a83563f | |||
c8a8eb0a4d | |||
0a7812bb2c | |||
061b434687 | |||
f3be1a6d09 | |||
1e797bfe8a | |||
97435dbe0f | |||
3fdbb95b15 | |||
961a6c0b4f | |||
c42f011a05 | |||
957c50088d | |||
3b129176d6 | |||
f36327ecc9 | |||
5f48bb8e7d | |||
aba8d01818 | |||
03cb7062f2 | |||
d4b35736c0 | |||
c30aa710f3 | |||
0adac4217e | |||
7e102edd0a | |||
adefe87129 | |||
8a05fc8966 | |||
8fa798cf63 | |||
01b20b2acb | |||
a8d72dd6f8 | |||
84042c61e0 | |||
407ba6c628 | |||
16a3c676ef | |||
9b3a99432b | |||
036f757086 | |||
a391529b32 | |||
d10b782308 | |||
a58f780452 | |||
606de03c7c | |||
2b988c51ad | |||
cb73e55db1 | |||
8fb4093dc6 | |||
f5c4535cb0 | |||
e4f2bffbc2 | |||
5c942460f9 | |||
cce1f2c20d | |||
516a3bd017 | |||
63d58c7a4f | |||
c49b865ee7 | |||
097a83e3ba | |||
02c70d868d | |||
c836f52460 | |||
caa1e1ef09 | |||
5c63531066 | |||
15b2bbc6d1 | |||
3cafbd9141 | |||
dc0405a373 | |||
f84832716a | |||
200aa4042f | |||
f44c5adcc0 | |||
3c43bebe64 |
24
.gitattributes
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
* text=auto
|
||||||
|
* text eol=lf
|
||||||
|
|
||||||
|
# Windows forced line-endings
|
||||||
|
/.idea/* text eol=crlf
|
||||||
|
|
||||||
|
# Gradle wrapper
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
|
# Images
|
||||||
|
*.webp binary
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.swp binary
|
8
.github/CONTRIBUTING.md
vendored
Normal file → Executable file
@ -1,10 +1,10 @@
|
|||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/WrBkRk4)
|
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
||||||
3. What is your type of issue?
|
3. What is your type of issue?
|
||||||
* [Catalogue request](#catalogue-requests)
|
* [Catalogue request](#catalogue-requests)
|
||||||
* [Bugs](#bugs)
|
* [Bugs](#bugs)
|
||||||
* [Feature requests](#feature-requests)
|
* [Feature requests](#feature-requests)
|
||||||
* [Translations](https://github.com/inorichi/tachiyomi/wiki/Translation)
|
* [Translations](https://tachiyomi.org/help/contribution/#translation)
|
||||||
4. After following 1. and 3. you can [open your issue](https://github.com/inorichi/tachiyomi/issues/new)
|
4. After following 1. and 3. you can [open your issue](https://github.com/inorichi/tachiyomi/issues/new)
|
||||||
|
|
||||||
***
|
***
|
||||||
@ -29,5 +29,5 @@ DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
|||||||
|
|
||||||
# Feature requests
|
# Feature requests
|
||||||
|
|
||||||
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
|
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github: inorichi
|
||||||
|
ko_fi: inorichi
|
27
.github/ISSUE_TEMPLATE.md
vendored
Normal file → Executable file
@ -1 +1,26 @@
|
|||||||
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
**PLEASE READ THIS**
|
||||||
|
|
||||||
|
I acknowledge that:
|
||||||
|
|
||||||
|
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||||
|
- I have updated all extensions
|
||||||
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Device information
|
||||||
|
* Tachiyomi version: ?
|
||||||
|
* Android version: ?
|
||||||
|
* Device: ?
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
|
||||||
|
## Issue/Request
|
||||||
|
?
|
||||||
|
|
||||||
|
## Other details
|
||||||
|
Additional details and attachments.
|
||||||
|
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: "🐞 Bug report"
|
||||||
|
about: Report a bug
|
||||||
|
title: "[Bug] Write short description here"
|
||||||
|
labels: "bug"
|
||||||
|
---
|
||||||
|
|
||||||
|
**PLEASE READ THIS**
|
||||||
|
|
||||||
|
I acknowledge that:
|
||||||
|
|
||||||
|
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||||
|
- I have updated all extensions
|
||||||
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Device information
|
||||||
|
* Tachiyomi version: ?
|
||||||
|
* Android version: ?
|
||||||
|
* Device: ?
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
This should happen.
|
||||||
|
|
||||||
|
### Actual behavior
|
||||||
|
This happened instead.
|
||||||
|
|
||||||
|
### Other details
|
||||||
|
Additional details and attachments.
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: "🌟 Feature request"
|
||||||
|
about: Suggest a feature to improve Tachiyomi
|
||||||
|
title: "[Feature Request] Write short description here"
|
||||||
|
labels: "feature"
|
||||||
|
---
|
||||||
|
|
||||||
|
**PLEASE READ THIS**
|
||||||
|
|
||||||
|
I acknowledge that:
|
||||||
|
|
||||||
|
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||||
|
- I have updated all extensions
|
||||||
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Why/User Benefit/User Problem
|
||||||
|
(explain why this feature should be added)
|
||||||
|
|
||||||
|
### What/Requirements
|
||||||
|
(explain how this feature would behave)
|
8
.github/ISSUE_TEMPLATE/source_issue.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: "Extension/source/catalogue issue"
|
||||||
|
about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions"
|
||||||
|
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions"
|
||||||
|
labels: "catalog"
|
||||||
|
---
|
||||||
|
|
||||||
|
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
BIN
.github/readme-images/app-icon.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
.github/readme-images/screens.png
vendored
Normal file
After Width: | Height: | Size: 1.0 MiB |
25
.github/workflows/PullRequestTester.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Pull Request Checker
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
apk:
|
||||||
|
name: Generate APK
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- name: Get NDK
|
||||||
|
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570"
|
||||||
|
- name: Build Release APK
|
||||||
|
run: bash ./gradlew assembleDebug --stacktrace
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: TachiyomiSY-${{ github.sha }}.apk
|
||||||
|
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
31
.github/workflows/TachiyomiSY-Preview-Builder.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name: Remote Dispatch Action Initiator
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
repository_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ping-pong:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
fetch-depth: '0'
|
||||||
|
- name: TAG - Bump version and push tag
|
||||||
|
uses: anothrNick/github-tag-action@1.17.2
|
||||||
|
if: github.event.action != 'pong'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
WITH_V: true
|
||||||
|
RELEASE_BRANCHES: master
|
||||||
|
- name: PING - Dispatch initiating repository event
|
||||||
|
if: github.event.action != 'pong'
|
||||||
|
run: |
|
||||||
|
curl -X POST https://api.github.com/repos/jobobby04/TachiyomiSYPreview/dispatches \
|
||||||
|
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||||
|
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||||
|
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||||
|
- name: ACK - Acknowledge pong from remote repository
|
||||||
|
if: github.event.action == 'pong'
|
||||||
|
run: |
|
||||||
|
echo "PONG received from '${{ github.event.client_payload.repository }}'"
|
13
.github/workflows/issue_closer.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: Issue closer
|
||||||
|
on: [issues]
|
||||||
|
jobs:
|
||||||
|
autoclose:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Autoclose issue
|
||||||
|
uses: arkon/issue-closer-action@v1.0
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-close-message: "@${issue.user.login} this issue was automatically closed because it was not filled in correctly or the acknowledgment section was not removed."
|
||||||
|
issue-title-pattern: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||||
|
issue-body-pattern: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
18
.gitignore
vendored
Normal file → Executable file
@ -2,8 +2,22 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
|
||||||
.idea/
|
.idea/
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
*/build
|
/mainframer
|
||||||
|
/.mainframer
|
||||||
|
|
||||||
|
# Built files
|
||||||
|
*/build
|
||||||
|
/build
|
||||||
|
*.apk
|
||||||
|
app/**/output.json
|
||||||
|
|
||||||
|
# Hebrew assets are copied on build
|
||||||
|
app/src/main/res/values-iw/
|
||||||
|
|
||||||
|
TODO.md
|
||||||
|
CHANGELOG.md
|
||||||
|
/captures
|
||||||
|
build.sh
|
||||||
|
108
.travis.yml
@ -1,57 +1,81 @@
|
|||||||
|
dist: trusty
|
||||||
language: android
|
language: android
|
||||||
|
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-27.0.1
|
- tools
|
||||||
- android-26
|
- platform-tools
|
||||||
- extra-android-m2repository
|
- build-tools-29.0.3
|
||||||
- extra-google-m2repository
|
- android-29
|
||||||
- extra-android-support
|
- extra-android-m2repository
|
||||||
- extra-google-google_play_services
|
- extra-google-m2repository
|
||||||
|
- extra-android-support
|
||||||
|
- extra-google-google_play_services
|
||||||
|
|
||||||
licenses:
|
licenses:
|
||||||
- android-sdk-license-.+
|
- 'android-sdk-license-.+'
|
||||||
|
- 'android-sdk-preview-license-.+'
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
- yes | sdkmanager "platforms;android-29" # workaround for accepting the license
|
||||||
|
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
||||||
tar xf secrets.tar;
|
tar xf secrets.tar;
|
||||||
mv debug.keystore "$HOME/.android";
|
mv debug.keystore "$HOME/.android";
|
||||||
fi
|
fi
|
||||||
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk-root
|
- mkdir "$ANDROID_HOME/licenses" || true
|
||||||
- export ANDROID_NDK_HOME=$HOME/android-ndk-root
|
- echo -e "\n24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
|
||||||
- mkdir "$ANDROID_HOME/licenses" || true
|
- echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
|
||||||
- echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
|
|
||||||
- echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
|
install:
|
||||||
|
- echo y | sdkmanager "ndk-bundle"
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
|
||||||
|
|
||||||
script: ".travis/build.sh"
|
script: ".travis/build.sh"
|
||||||
|
|
||||||
before_cache:
|
before_cache:
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- "$HOME/.gradle/caches/"
|
- "$HOME/.gradle/caches/"
|
||||||
- "$HOME/.gradle/wrapper/"
|
- "$HOME/.gradle/wrapper/"
|
||||||
- "$HOME/.android/build-cache"
|
- "$HOME/.android/build-cache"
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: releases
|
- provider: releases
|
||||||
api_key:
|
api_key:
|
||||||
secure: qmS9SyMq8xPDqaY83rvAFyZcvic24lGBj3MFt22RhVJzIXAAN/vqL1R70PnNiCF7CE+R7PaDlBpwjxDMBiuh0QQNc1oX6cgepUbro4/Nt7NFFfCvKXaFdR1cSgYouhuHmy0SS0/alrcfhQ2bPwcm1/vAOiSa8Wu7hsXhCcxbFyEbXZVD11QZmiffEM0py+OeuqOFo2JxZaGRu2z04E/u5TWep1ZEuhFRCC87PGgFqABgg6jYYebQOUZADG/0G8581HTGU0mdwueYsiA35ncRzpV2V8DajEEBd5wOe5d8SyMtE+6Qs5PD9KcXAqGGe4QRmrJMX5EcLQaLZf/Qd5s9SFZVHb1aJIw/y05w4L5dlVpsjx5WuUAYAVg7Ol5UawofFo/hYkYCNmfub67wJQdHSIxPif7V6YeON6RQQMpc5GBYY9eA6ZxhrdA2m7eyoOT3jcbdaVJwC0jMGhn26hkgJfTo1LfAUs85Cs3BrK8w6Poqc/Jb+4Y0NhdGIKgO5tS3vY54cTJVVrQTq4/XmME4ZnzOX3HaOqzfyt/6M4gEQMvaeFksxwoFhocV7wfaCq9ps/Kdq2dl4KwoqRV2WqVaauqzCP4XPSlVLaJqztsw0wboupcaZepWJ2a/6j9IrKo1pEnyeHF5y+k0SUAxL0X8iKZ0uPxsgoVrlNtqXJWNGvA=
|
secure: qmS9SyMq8xPDqaY83rvAFyZcvic24lGBj3MFt22RhVJzIXAAN/vqL1R70PnNiCF7CE+R7PaDlBpwjxDMBiuh0QQNc1oX6cgepUbro4/Nt7NFFfCvKXaFdR1cSgYouhuHmy0SS0/alrcfhQ2bPwcm1/vAOiSa8Wu7hsXhCcxbFyEbXZVD11QZmiffEM0py+OeuqOFo2JxZaGRu2z04E/u5TWep1ZEuhFRCC87PGgFqABgg6jYYebQOUZADG/0G8581HTGU0mdwueYsiA35ncRzpV2V8DajEEBd5wOe5d8SyMtE+6Qs5PD9KcXAqGGe4QRmrJMX5EcLQaLZf/Qd5s9SFZVHb1aJIw/y05w4L5dlVpsjx5WuUAYAVg7Ol5UawofFo/hYkYCNmfub67wJQdHSIxPif7V6YeON6RQQMpc5GBYY9eA6ZxhrdA2m7eyoOT3jcbdaVJwC0jMGhn26hkgJfTo1LfAUs85Cs3BrK8w6Poqc/Jb+4Y0NhdGIKgO5tS3vY54cTJVVrQTq4/XmME4ZnzOX3HaOqzfyt/6M4gEQMvaeFksxwoFhocV7wfaCq9ps/Kdq2dl4KwoqRV2WqVaauqzCP4XPSlVLaJqztsw0wboupcaZepWJ2a/6j9IrKo1pEnyeHF5y+k0SUAxL0X8iKZ0uPxsgoVrlNtqXJWNGvA=
|
||||||
file: tachiyomi-v*.apk
|
file: tachiyomi-v*.apk
|
||||||
file_glob: true
|
file_glob: true
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: inorichi/tachiyomi
|
repo: inorichi/tachiyomi
|
||||||
- provider: script
|
- provider: script
|
||||||
script: ".travis/deploy.sh"
|
script: ".travis/deploy.sh"
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
branch: master
|
branch: master
|
||||||
condition: "-z $TRAVIS_TAG"
|
condition: "-z $TRAVIS_TAG"
|
||||||
repo: inorichi/tachiyomi
|
repo: inorichi/tachiyomi
|
||||||
|
- provider: script
|
||||||
|
script: ".travis/deploy.sh"
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
branch: dev
|
||||||
|
condition: "-z $TRAVIS_TAG"
|
||||||
|
repo: inorichi/tachiyomi
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- secure: Ita1+tzo7P5IC2yqU3KgRcXt+5DTpP0103Hx/ECYi42/7rLt+TC7PMjl2yH3Z189+tGwLq0Ol1KJ2Z5Rn3q7EaQgD0+WRkH/ijtrjKoVh7dyItIBp7yowZpA0TJHQ4EZpGSxZakKbIP4di8XMxJ2+5VzEivYUt04LCUpzugemL6b6XOfUmOZykVxV2UDAlPPggklITYBXkHUa0mwJhjS1aPPeeR3PhVXomkqfuOJOKejPXXXJope9fhAnmopHA7ISfjIrTuwDVQJqNSuco+O9kQShmlu0C8pob1vFGPEDvafaDS8SZ9A6gKT1ZfgSUqVmvDbx0WLX8XugBLrQedtZv63esOa1WUyGhgFVpeJjexlszXlhyfP1gH5QbzRr+EiSaagCyjf9II2veLAtU5cFY+nj6KBdKQsazIMRHf8SAQlWASyJYMED/N9RnUFxSf1rnLGqiY2ezjycx4ieFj7vhlbTgyao1GHjjR9cwNuntdMYWhY8+Vc7Fctmzm46xOyyz9oJGdyim76Y4w4MZvQNKeZOBAjdEgX6cXBk15scoM2Vj9ENox+MKZLaKRawXg2U1ujK+bWAQkXiVvPriv05/JtYsNUft8qAsm+69vtohDsUW7Wu0bBIKDL+v0W30ty1PpyNehBB2OquUE7fp53gitOmYl7TyuxktkMY8PXKKU=
|
- secure: Ita1+tzo7P5IC2yqU3KgRcXt+5DTpP0103Hx/ECYi42/7rLt+TC7PMjl2yH3Z189+tGwLq0Ol1KJ2Z5Rn3q7EaQgD0+WRkH/ijtrjKoVh7dyItIBp7yowZpA0TJHQ4EZpGSxZakKbIP4di8XMxJ2+5VzEivYUt04LCUpzugemL6b6XOfUmOZykVxV2UDAlPPggklITYBXkHUa0mwJhjS1aPPeeR3PhVXomkqfuOJOKejPXXXJope9fhAnmopHA7ISfjIrTuwDVQJqNSuco+O9kQShmlu0C8pob1vFGPEDvafaDS8SZ9A6gKT1ZfgSUqVmvDbx0WLX8XugBLrQedtZv63esOa1WUyGhgFVpeJjexlszXlhyfP1gH5QbzRr+EiSaagCyjf9II2veLAtU5cFY+nj6KBdKQsazIMRHf8SAQlWASyJYMED/N9RnUFxSf1rnLGqiY2ezjycx4ieFj7vhlbTgyao1GHjjR9cwNuntdMYWhY8+Vc7Fctmzm46xOyyz9oJGdyim76Y4w4MZvQNKeZOBAjdEgX6cXBk15scoM2Vj9ENox+MKZLaKRawXg2U1ujK+bWAQkXiVvPriv05/JtYsNUft8qAsm+69vtohDsUW7Wu0bBIKDL+v0W30ty1PpyNehBB2OquUE7fp53gitOmYl7TyuxktkMY8PXKKU=
|
||||||
- secure: NABCfigMUVM/9TLALYBpQLp/p3rG6MbH5y34/oqCSej/oh2u0nyhFSi8veS0lFpDIcv0TZvxHJXoSA0zeZijb1fUU8jYVNT2azuPWE6Gu7sf0TfBeCvulqbgLMoaA6JuWbEbZwHcxpKHg4vLSMjNk+ZP4v2dffI6A620fxLltxxhTpsYkYYsfKG857CpQtdgN/HqcOaxyvzXFmWWyVWHala1uMcMeXZCwgnlVAqau9o0bsU092txSmHqoesHoAinidSCTCmTlEqp/1AFaYQTbxmnfNC1yLgzxWAlJcS3NWzNo3ellMvKjsiIGn3JJpAjTGcyf3yPsvhs1cY3MZbmJYVyKH5HbnkA5ms6mx0DDJ2UOs5H2dmED82m14+hu62Xb8XN8zAdq+bySNSwgsGzvr1PT74pT4BW1T+D7L1xvUe6k1enZ38GIMJbJPhBybRQazhjKPmXRB30Thxoqe5VqU8UeiXHAEps7JYAWUR1PLZvEYQb6MWurmTxs9be/OTwrUT1LDiqeJZg6XkDGgQwuR2YBaQJHJD17Piq6q1BUX8abhK6wzAAYVqyGvpmUCmQCtHZgenE6ulwcKChzBv4n97OjE21LGWnbNF5ViUhfAbGcKOVufd1chZsfbkJ7a3tHYCfLnxHUIhKvHk26f5Em8h68D0wQkPnkcVVkfh7XpI=
|
- secure: NABCfigMUVM/9TLALYBpQLp/p3rG6MbH5y34/oqCSej/oh2u0nyhFSi8veS0lFpDIcv0TZvxHJXoSA0zeZijb1fUU8jYVNT2azuPWE6Gu7sf0TfBeCvulqbgLMoaA6JuWbEbZwHcxpKHg4vLSMjNk+ZP4v2dffI6A620fxLltxxhTpsYkYYsfKG857CpQtdgN/HqcOaxyvzXFmWWyVWHala1uMcMeXZCwgnlVAqau9o0bsU092txSmHqoesHoAinidSCTCmTlEqp/1AFaYQTbxmnfNC1yLgzxWAlJcS3NWzNo3ellMvKjsiIGn3JJpAjTGcyf3yPsvhs1cY3MZbmJYVyKH5HbnkA5ms6mx0DDJ2UOs5H2dmED82m14+hu62Xb8XN8zAdq+bySNSwgsGzvr1PT74pT4BW1T+D7L1xvUe6k1enZ38GIMJbJPhBybRQazhjKPmXRB30Thxoqe5VqU8UeiXHAEps7JYAWUR1PLZvEYQb6MWurmTxs9be/OTwrUT1LDiqeJZg6XkDGgQwuR2YBaQJHJD17Piq6q1BUX8abhK6wzAAYVqyGvpmUCmQCtHZgenE6ulwcKChzBv4n97OjE21LGWnbNF5ViUhfAbGcKOVufd1chZsfbkJ7a3tHYCfLnxHUIhKvHk26f5Em8h68D0wQkPnkcVVkfh7XpI=
|
||||||
- secure: C93UADV5aR0zhLCLwR6tCyz+fwUYslZbhjBl3PHQp+0boIGS/Be2UQTFHp/NB9mQmhWqbwqHoAVFENZFytV04ePgOuNtMFcjAIfnzm19Am7iRAMFixD45pF/CuYYfLupElkAcSequtAzN0g4G0sQ5KR1hibaDIoz9kfA2YcUAMaZ4T5bhCr8os/xA2nOlmvPDWsRWCFBYkSpnmbsSsgYAhulA/V5bSNAWnp9LPw3CBLibW3WsVP4wuhZAkXznKwn/mHT31kfQlpMH3qNhXpsN9huUkZ/k8QWeakcHJKugung0Z2T1StK8rlI8OrJstVcwueHTa2ses4f5VbhWog/Z8HDkdll9W9RM/QqXjNDtOVBt/SPuhCp4k2rvJixFUxzvSqgSWQvQnbHwjWxIUVVyHtnb0/zc/S9ONZG14TOwB/+Lkgacb85PNszurZ2f3mH0O6slIh1mH+5d9J4+L976ot4nTPlK1OtothagVyKGOrn9HycrPk/MjftIJuElHzo7NEJd/wRPqIb5y12iZN1RSPriU+itg1uSAVP891/o3peJyuqY9WSB7dYwgDJos6dDvbr19emtdyxkQR+eAb5duyH6s4R58wh1kJ1d4zu0X6uSnF4AIc+6teKkN24rSXcqB/hrcojS49jgLy5P0/CVsUbYZPI/tx8E/IJfr8m36E=
|
- secure: C93UADV5aR0zhLCLwR6tCyz+fwUYslZbhjBl3PHQp+0boIGS/Be2UQTFHp/NB9mQmhWqbwqHoAVFENZFytV04ePgOuNtMFcjAIfnzm19Am7iRAMFixD45pF/CuYYfLupElkAcSequtAzN0g4G0sQ5KR1hibaDIoz9kfA2YcUAMaZ4T5bhCr8os/xA2nOlmvPDWsRWCFBYkSpnmbsSsgYAhulA/V5bSNAWnp9LPw3CBLibW3WsVP4wuhZAkXznKwn/mHT31kfQlpMH3qNhXpsN9huUkZ/k8QWeakcHJKugung0Z2T1StK8rlI8OrJstVcwueHTa2ses4f5VbhWog/Z8HDkdll9W9RM/QqXjNDtOVBt/SPuhCp4k2rvJixFUxzvSqgSWQvQnbHwjWxIUVVyHtnb0/zc/S9ONZG14TOwB/+Lkgacb85PNszurZ2f3mH0O6slIh1mH+5d9J4+L976ot4nTPlK1OtothagVyKGOrn9HycrPk/MjftIJuElHzo7NEJd/wRPqIb5y12iZN1RSPriU+itg1uSAVP891/o3peJyuqY9WSB7dYwgDJos6dDvbr19emtdyxkQR+eAb5duyH6s4R58wh1kJ1d4zu0X6uSnF4AIc+6teKkN24rSXcqB/hrcojS49jgLy5P0/CVsUbYZPI/tx8E/IJfr8m36E=
|
||||||
- secure: mawwBvllvESc/mp+JHvncq1iUhiC7nyokPgXmOehffc0K3byMLs2L25HjNsU6EnXG9Lcae1cfP8S9bWLquU2C3kpAkLBUpjEbdx7K0654uvs7Rrvb5hcTRHwjzaEVmVaBFX4ROcjUhBYny/Wjj/YENCkSWpkfcMd1esFbVsO+fOLyaAPvrb6auKY7H+pUSqlEwaEnrkYeBBZIHa7KqwL4g5DHbq6K368tjmval/wBzwMB0V8V3dt/ik8RMVDtKPrik4Bu0V9UmXZUIo/a06ii/CM82ekFRh3eUb0DKkdkmYbdH6MBMoLTfQtMa6A4luXaA0oycAnTX3OGB5MWIjK39KhWRavh6ybSIt4aHKoolxzH8Zgmk7xMhFSot/laX5q5IzjZu5KU6F2SmdV0kcQugM8oAjANFySetPvY1q7nZ8pM+NO1xKS/mH0w4vChhdJFD1mw7aCoh8FdeUf0Eym2+pp5Q9uAisWMmNn5XN8/fL5q6PzAxkXmkedfrr1N61FmIL6EKx8qiWpOUNlRRTIMJ4GMhCyckCF6cNxDkBItp52c+Hmkbn+ZEInEyX6gpjYVm3xyEi0Z5kLCi/fMX2nBNczc5BuGLzzmJnITv4ovpeYn2/vPvHbaPgPC4LqDK3AjlpVadMZk/M5Egn+hWY7Mni57CmpZD+SpxUbbsItI0c=
|
- secure: mawwBvllvESc/mp+JHvncq1iUhiC7nyokPgXmOehffc0K3byMLs2L25HjNsU6EnXG9Lcae1cfP8S9bWLquU2C3kpAkLBUpjEbdx7K0654uvs7Rrvb5hcTRHwjzaEVmVaBFX4ROcjUhBYny/Wjj/YENCkSWpkfcMd1esFbVsO+fOLyaAPvrb6auKY7H+pUSqlEwaEnrkYeBBZIHa7KqwL4g5DHbq6K368tjmval/wBzwMB0V8V3dt/ik8RMVDtKPrik4Bu0V9UmXZUIo/a06ii/CM82ekFRh3eUb0DKkdkmYbdH6MBMoLTfQtMa6A4luXaA0oycAnTX3OGB5MWIjK39KhWRavh6ybSIt4aHKoolxzH8Zgmk7xMhFSot/laX5q5IzjZu5KU6F2SmdV0kcQugM8oAjANFySetPvY1q7nZ8pM+NO1xKS/mH0w4vChhdJFD1mw7aCoh8FdeUf0Eym2+pp5Q9uAisWMmNn5XN8/fL5q6PzAxkXmkedfrr1N61FmIL6EKx8qiWpOUNlRRTIMJ4GMhCyckCF6cNxDkBItp52c+Hmkbn+ZEInEyX6gpjYVm3xyEi0Z5kLCi/fMX2nBNczc5BuGLzzmJnITv4ovpeYn2/vPvHbaPgPC4LqDK3AjlpVadMZk/M5Egn+hWY7Mni57CmpZD+SpxUbbsItI0c=
|
||||||
- secure: PJPDkUg1zc57brxUvNpSh+Q3ZEaGpBqZzwDavqslkn0WmjBTLrE6/OG7TFHKNmO+P56qFl+pMEKqThxqR3+4bWEeEx8ykkixDVzxNJMmws+7A7ImJ75iQyB6giMW/4tykVMMHgIPNAdcnI8VOWn0LGHnpFWUd70yoyAGX8s6cspHCKgcuWMA3GS410KJfHpyd0B9/QS7ZyWzSETW7zSPyLPa81SBO95EhOF3TOGZYLt/mBhdtU3YGFs4k9fZ8jDDcm9XmBfqVlUhb8HiZcxJiZDdRvxODERfNnwc47uaJk6+kxGDzIW2uAxrMXXVKkG04GeMOokXoR9kW1Hl2JmoyySLKLZmB7I/XEtVWdzZw16mWi+4zmhjLhfB0phSW+/5I+0VtZZ6jO031J5FL/JqVrcq1ws/aw4QlaOdPUco/x2u4LNHyYYgOi5arD9xSyu6IRy0jCC4Xa1zuqM5adGJX+rZyVfKZ0TxOW661HTxlo8COtkB2i0WR2deZGVN75ooCAEO8DauQoUcFH1OelahmPtzVs1/6ZczuxGdp9ED7ZQq9NHEOsOdUGCj/D79Dm1hWFQsIsslnnGYWitAycNCgEwmlt2Q6fbrv2CJrmLqZ9a9r3AhzxoHn9Qx1GyuyfhZJzm/6Ff2kcOjma2kcz13KUwTxdW+2G5dDCotK3f7aiI=
|
- secure: PJPDkUg1zc57brxUvNpSh+Q3ZEaGpBqZzwDavqslkn0WmjBTLrE6/OG7TFHKNmO+P56qFl+pMEKqThxqR3+4bWEeEx8ykkixDVzxNJMmws+7A7ImJ75iQyB6giMW/4tykVMMHgIPNAdcnI8VOWn0LGHnpFWUd70yoyAGX8s6cspHCKgcuWMA3GS410KJfHpyd0B9/QS7ZyWzSETW7zSPyLPa81SBO95EhOF3TOGZYLt/mBhdtU3YGFs4k9fZ8jDDcm9XmBfqVlUhb8HiZcxJiZDdRvxODERfNnwc47uaJk6+kxGDzIW2uAxrMXXVKkG04GeMOokXoR9kW1Hl2JmoyySLKLZmB7I/XEtVWdzZw16mWi+4zmhjLhfB0phSW+/5I+0VtZZ6jO031J5FL/JqVrcq1ws/aw4QlaOdPUco/x2u4LNHyYYgOi5arD9xSyu6IRy0jCC4Xa1zuqM5adGJX+rZyVfKZ0TxOW661HTxlo8COtkB2i0WR2deZGVN75ooCAEO8DauQoUcFH1OelahmPtzVs1/6ZczuxGdp9ED7ZQq9NHEOsOdUGCj/D79Dm1hWFQsIsslnnGYWitAycNCgEwmlt2Q6fbrv2CJrmLqZ9a9r3AhzxoHn9Qx1GyuyfhZJzm/6Ff2kcOjma2kcz13KUwTxdW+2G5dDCotK3f7aiI=
|
||||||
- secure: FIIZfEEYfjNMKODs33Czh603CYVn6LRrzpFNIiPHYTb8iQWv9qAYhsg4FpHfOjDikokTwb5X/h8G7AX93Z0xKyyDi75ACT11oPeTNTArDdcmdDVlOYBvYHc2Ci7pMW5r8LGejB7Y3mWM8uKyA3oKvneEFutB65vO3JVZvFWrm03Lmqqe7+mA4qNqNqTbN7R7fmk5b7zt7A3DHvDu0JPTbSSUwpso/p2I5WJYjrf71I7YMQwIFLoMfplC1onVA3EFS3lZsF65zE+xVRy34AKa41iZAMbhVDyqUHEnx6L0dwEdn2Z5XLlK0ov1+qLTLlQsBE4Knre6TNkWMfktk7MKA+ch8RYxvEYLODhQkIrOkLSNWhZPhdaT+xD4fr0RCKSHo6uWRC4aofsJx8wSqb8ZL4j2zopUp9VisMOI202UEnvFDBtOkVGJSxxYbFjifIB7NCJBn788w+3k+k4IbOg537VdyoK2PMBR8/TDdjImWhWHY1i7+345ejwmzHL7ZPfb6GTNnQTWkajT77/n6Yk41twR5vvegOSTKuuO++WN/pUks4PGqtcQe9fnSfx2OcOq1ofLiG+JDorJ7z8kHSG13wHLq+QYMDayQbyJEYpDzmn/w3Ou1s2o0a7A41+cIkRzAgH9y3v4lgjp9GcMP2S74ZPA7OecWbFSexM7tL/dYxY=
|
- secure: FIIZfEEYfjNMKODs33Czh603CYVn6LRrzpFNIiPHYTb8iQWv9qAYhsg4FpHfOjDikokTwb5X/h8G7AX93Z0xKyyDi75ACT11oPeTNTArDdcmdDVlOYBvYHc2Ci7pMW5r8LGejB7Y3mWM8uKyA3oKvneEFutB65vO3JVZvFWrm03Lmqqe7+mA4qNqNqTbN7R7fmk5b7zt7A3DHvDu0JPTbSSUwpso/p2I5WJYjrf71I7YMQwIFLoMfplC1onVA3EFS3lZsF65zE+xVRy34AKa41iZAMbhVDyqUHEnx6L0dwEdn2Z5XLlK0ov1+qLTLlQsBE4Knre6TNkWMfktk7MKA+ch8RYxvEYLODhQkIrOkLSNWhZPhdaT+xD4fr0RCKSHo6uWRC4aofsJx8wSqb8ZL4j2zopUp9VisMOI202UEnvFDBtOkVGJSxxYbFjifIB7NCJBn788w+3k+k4IbOg537VdyoK2PMBR8/TDdjImWhWHY1i7+345ejwmzHL7ZPfb6GTNnQTWkajT77/n6Yk41twR5vvegOSTKuuO++WN/pUks4PGqtcQe9fnSfx2OcOq1ofLiG+JDorJ7z8kHSG13wHLq+QYMDayQbyJEYpDzmn/w3Ou1s2o0a7A41+cIkRzAgH9y3v4lgjp9GcMP2S74ZPA7OecWbFSexM7tL/dYxY=
|
||||||
- secure: DKCGc4E9PKeTX68r9pbbNg5qITsN0bApQ1m0x8xdEoi8GLRKVMYNn6ahoAxvy1YsBXC9Zlt5++gLmUV1I1JyDMyJXMr/lZrp4oarW0xWpTBmn3HzOph/K2W4i/fTGgMFieumPEbQIFOnU3JSjK6UJB8qVGEXD2OqS7A//EdrGDbAYVDL3ZTKE6JUlTNHgaKaNHhn+Dq4aBLTSYPwlLyqo+WNBVUUCKCHOq62ULF8MpX5YGaPFNxKYzircV7HpF1hCbV31dmpkeYT9xztra5V0SIBM27jAcQqGmtHH2mhx1sLu+gjhFJbbtY6cggA9EedzYYLDx/NPmgfyuOJfyVbSwTF3vhDUYfskqc1THWpwOSKO0Ry+8/xYb9crxg+FSwuI5hnfkIFk9woBvRGBhjto3/1buMNY9dSFiWtEbN6Let8e747l0wIGJCpJxSeh7vn7F1mWjixhf9GX1+V9BrUvGTd3XJDNb9cVnafYa1RTj8BLteA4HBza7Z9R3dvG4YWp16L/94UuaTzgAQfERLTZGopQth/hsaVTlYesJmJLF70lGM+W83y3YuNkSaX1zQ5FAIvp7oH0O16t7ISm6GprUFwN2Uox7AAbPZdWHxJbly+D+yCFNcqS3Bz9mV3YCLo690Sy1ePNHr+nCseVfBMo7OYyavSS/EjPWfEy65Wq04=
|
- secure: DKCGc4E9PKeTX68r9pbbNg5qITsN0bApQ1m0x8xdEoi8GLRKVMYNn6ahoAxvy1YsBXC9Zlt5++gLmUV1I1JyDMyJXMr/lZrp4oarW0xWpTBmn3HzOph/K2W4i/fTGgMFieumPEbQIFOnU3JSjK6UJB8qVGEXD2OqS7A//EdrGDbAYVDL3ZTKE6JUlTNHgaKaNHhn+Dq4aBLTSYPwlLyqo+WNBVUUCKCHOq62ULF8MpX5YGaPFNxKYzircV7HpF1hCbV31dmpkeYT9xztra5V0SIBM27jAcQqGmtHH2mhx1sLu+gjhFJbbtY6cggA9EedzYYLDx/NPmgfyuOJfyVbSwTF3vhDUYfskqc1THWpwOSKO0Ry+8/xYb9crxg+FSwuI5hnfkIFk9woBvRGBhjto3/1buMNY9dSFiWtEbN6Let8e747l0wIGJCpJxSeh7vn7F1mWjixhf9GX1+V9BrUvGTd3XJDNb9cVnafYa1RTj8BLteA4HBza7Z9R3dvG4YWp16L/94UuaTzgAQfERLTZGopQth/hsaVTlYesJmJLF70lGM+W83y3YuNkSaX1zQ5FAIvp7oH0O16t7ISm6GprUFwN2Uox7AAbPZdWHxJbly+D+yCFNcqS3Bz9mV3YCLo690Sy1ePNHr+nCseVfBMo7OYyavSS/EjPWfEy65Wq04=
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
git fetch --unshallow #required for commit count
|
|
||||||
|
|
||||||
if [ -z "$TRAVIS_TAG" ]; then
|
|
||||||
./gradlew clean assembleStandardDebug
|
|
||||||
|
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
|
||||||
export ARTIFACT="tachiyomi-r${COMMIT_COUNT}.apk"
|
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/debug/app-standard-debug.apk $ARTIFACT
|
|
||||||
else
|
|
||||||
./gradlew clean assembleStandardRelease
|
|
||||||
|
|
||||||
TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
|
|
||||||
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
|
||||||
|
|
||||||
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
|
||||||
${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:STORE_PASS --key-pass env:KEY_PASS --out $ARTIFACT app-aligned.apk
|
|
||||||
fi
|
|
@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
pattern="tachiyomi-r*"
|
|
||||||
files=( $pattern )
|
|
||||||
export ARTIFACT="${files[0]}"
|
|
||||||
|
|
||||||
if [ -z "$ARTIFACT" ]; then
|
|
||||||
echo "Artifact not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export SSHOPTIONS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${DEPLOY_KEY}"
|
|
||||||
|
|
||||||
scp $SSHOPTIONS $ARTIFACT $DEPLOY_USER@$DEPLOY_HOST:builds/
|
|
||||||
ssh $SSHOPTIONS $DEPLOY_USER@$DEPLOY_HOST ln -sf $ARTIFACT builds/latest
|
|
43
README.md
@ -1,42 +1,3 @@
|
|||||||
| Build | Download | F-Droid | Contribute | Contact |
|
I haven't started a readme
|
||||||
|-------|----------|---------|------------|---------|
|
|
||||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) | [](https://github.com/inorichi/tachiyomi/wiki/Translation) | [](https://discord.gg/WrBkRk4) |
|
|
||||||
|
|
||||||
### **Contact us on [Discord](https://discord.gg/WrBkRk4)**
|
Automated Preview Builds(with updater): https://github.com/jobobby04/TachiyomiSYPreview/releases
|
||||||
If you want to open an issue, please read [contributing guidelines](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md). Your issue may be closed otherwise.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
Tachiyomi is a free and open source manga reader for Android.
|
|
||||||
|
|
||||||
Keep in mind it's still a beta, so expect it to crash sometimes.
|
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
* Online and offline reading
|
|
||||||
* Configurable reader with multiple viewers and settings
|
|
||||||
* MyAnimeList support
|
|
||||||
* Resume from the next unread chapter
|
|
||||||
* Chapter filtering
|
|
||||||
* Schedule searching for updates
|
|
||||||
* Categories to organize your library
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Copyright 2015 Javier Tomás
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
|
|
||||||
The developer of this application does not have any affiliation with the content providers available.
|
|
||||||
|
4
app/.gitignore
vendored
Normal file → Executable file
@ -1,4 +1,6 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
custom.gradle
|
||||||
|
google-services.json
|
||||||
|
output.json
|
274
app/build.gradle
Normal file → Executable file
@ -1,10 +1,15 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||||
|
//noinspection GradleDependency
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||||
|
// Realm (EH)
|
||||||
|
apply plugin: 'realm-android'
|
||||||
|
|
||||||
shortcutHelper.filePath = './shortcuts.xml'
|
shortcutHelper.filePath = './shortcuts.xml'
|
||||||
|
|
||||||
@ -29,40 +34,51 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 26
|
compileSdkVersion 29
|
||||||
buildToolsVersion "27.0.1"
|
buildToolsVersion '29.0.3'
|
||||||
publishNonDefault true
|
publishNonDefault true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "eu.kanade.tachiyomi"
|
applicationId "eu.kanade.tachiyomi.sy"
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 26
|
targetSdkVersion 29
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 28
|
versionCode 1
|
||||||
versionName "0.6.5"
|
versionName "0.9.2.7"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
multiDexEnabled true
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
versionNameSuffix "-${getCommitCount()}"
|
versionNameSuffix "-${getCommitCount()}"
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
multiDexEnabled true
|
ext.enableCrashlytics = false
|
||||||
|
}
|
||||||
|
releaseTest {
|
||||||
|
applicationIdSuffix ".rt"
|
||||||
|
// minifyEnabled true
|
||||||
|
// shrinkResources true
|
||||||
|
zipAlignEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
multiDexEnabled false
|
zipAlignEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,22 +90,27 @@ android {
|
|||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||||
dimension "default"
|
dimension "default"
|
||||||
}
|
}
|
||||||
fdroid {
|
|
||||||
dimension "default"
|
|
||||||
}
|
|
||||||
dev {
|
dev {
|
||||||
minSdkVersion 21
|
|
||||||
resConfigs "en", "xxhdpi"
|
resConfigs "en", "xxhdpi"
|
||||||
dimension "default"
|
dimension "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
targetCompatibility 1.8
|
||||||
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
exclude 'META-INF/DEPENDENCIES'
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
exclude 'LICENSE.txt'
|
exclude 'LICENSE.txt'
|
||||||
exclude 'META-INF/LICENSE'
|
exclude 'META-INF/LICENSE'
|
||||||
exclude 'META-INF/LICENSE.txt'
|
exclude 'META-INF/LICENSE.txt'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
|
exclude 'META-INF/*.kotlin_module'
|
||||||
|
|
||||||
|
// Compatibility for two RxJava versions (EXH)
|
||||||
|
exclude 'META-INF/rxjava.properties'
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@ -97,72 +118,99 @@ android {
|
|||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// Modified dependencies
|
// Modified dependencies
|
||||||
implementation 'com.github.inorichi:subsampling-scale-image-view:c19b883'
|
implementation 'com.github.inorichi:subsampling-scale-image-view:ac0dae7'
|
||||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||||
|
|
||||||
// Android support library
|
// Android support library
|
||||||
final support_library_version = '27.0.1'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation "com.android.support:support-v4:$support_library_version"
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "com.android.support:appcompat-v7:$support_library_version"
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation "com.android.support:cardview-v7:$support_library_version"
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation "com.android.support:design:$support_library_version"
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation "com.android.support:recyclerview-v7:$support_library_version"
|
implementation 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation "com.android.support:preference-v7:$support_library_version"
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation "com.android.support:support-annotations:$support_library_version"
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation "com.android.support:customtabs:$support_library_version"
|
implementation 'androidx.biometric:biometric:1.0.1'
|
||||||
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
final lifecycle_version = '2.2.0'
|
||||||
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||||
|
|
||||||
implementation 'com.android.support:multidex:1.0.1'
|
// UI library
|
||||||
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
|
|
||||||
|
standardImplementation 'com.google.firebase:firebase-core:17.4.1'
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
implementation 'io.reactivex:rxjava:1.3.4'
|
implementation 'io.reactivex:rxjava:1.3.8'
|
||||||
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
||||||
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||||
implementation 'com.github.pwittchen:reactivenetwork:0.7.0'
|
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
implementation "com.squareup.okhttp3:okhttp:3.9.1"
|
final okhttp_version = '4.7.2'
|
||||||
implementation 'com.squareup.okio:okio:1.13.0'
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||||
|
implementation 'com.squareup.okio:okio:2.6.0'
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
final retrofit_version = '2.3.0'
|
final retrofit_version = '2.9.0'
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
implementation 'com.google.code.gson:gson:2.8.2'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||||
|
|
||||||
// YAML
|
|
||||||
implementation 'com.github.bmoliveira:snake-yaml:v1.18-android'
|
|
||||||
|
|
||||||
// JavaScript engine
|
// JavaScript engine
|
||||||
implementation 'com.squareup.duktape:duktape-android:1.2.0'
|
implementation 'com.squareup.duktape:duktape-android:1.2.0' // Stuck on 1.2.0 to fix MangaPlus extension
|
||||||
|
|
||||||
// Disk
|
// Disk
|
||||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||||
implementation 'com.github.seven332:unifile:1.0.0'
|
implementation 'com.github.inorichi:unifile:e9ee588'
|
||||||
|
|
||||||
// HTML parser
|
// HTML parser
|
||||||
implementation 'org.jsoup:jsoup:1.10.2'
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation 'com.evernote:android-job:1.2.0'
|
final work_version = '2.3.4'
|
||||||
implementation 'com.google.android.gms:play-services-gcm:11.6.0'
|
implementation "androidx.work:work-runtime:$work_version"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
|
|
||||||
|
// [EXH] Android 7 SSL Workaround
|
||||||
|
implementation 'com.google.android.gms:play-services-safetynet:17.0.0'
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation "com.pushtorefresh.storio:sqlite:1.13.0"
|
implementation 'androidx.sqlite:sqlite:2.1.0'
|
||||||
|
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
|
||||||
|
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
|
||||||
|
implementation 'io.requery:sqlite-android:3.31.0'
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
||||||
|
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
final nucleus_version = '3.0.0'
|
final nucleus_version = '3.0.0'
|
||||||
@ -170,22 +218,20 @@ dependencies {
|
|||||||
implementation "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
implementation "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation "uy.kohesive.injekt:injekt-core:1.16.1"
|
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
final glide_version = '4.3.1'
|
final glide_version = '4.10.0'
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||||
|
|
||||||
// Transformations
|
|
||||||
implementation 'jp.wasabeef:glide-transformations:3.0.1'
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'com.jakewharton.timber:timber:4.6.0'
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
|
||||||
// Crash reports
|
// Crash reports
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
final acra_version = '5.5.0'
|
||||||
|
implementation "ch.acra:acra-http:$acra_version"
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||||
@ -193,30 +239,38 @@ dependencies {
|
|||||||
// UI
|
// UI
|
||||||
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
||||||
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
implementation 'eu.davidea:flexible-adapter:5.0.0-rc3'
|
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||||
|
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
||||||
implementation 'com.nononsenseapps:filepicker:2.5.2'
|
implementation 'com.nononsenseapps:filepicker:2.5.2'
|
||||||
implementation 'com.github.amulyakhare:TextDrawable:558677e'
|
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0'
|
||||||
implementation('com.afollestad.material-dialogs:core:0.9.4.7') {
|
implementation 'com.github.mthli:Slice:v1.3'
|
||||||
exclude group: "com.android.support", module: "support-v13"
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
}
|
implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a'
|
||||||
implementation 'me.zhanghai.android.systemuihelper:library:1.0.0'
|
|
||||||
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4'
|
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices
|
||||||
implementation 'com.github.mthli:Slice:v1.2'
|
final material_dialogs_version = '3.1.1'
|
||||||
|
implementation "com.afollestad.material-dialogs:core:$material_dialogs_version"
|
||||||
|
implementation "com.afollestad.material-dialogs:input:$material_dialogs_version"
|
||||||
|
implementation "com.afollestad.material-dialogs:datetime:$material_dialogs_version"
|
||||||
|
|
||||||
// Conductor
|
// Conductor
|
||||||
implementation "com.bluelinelabs:conductor:2.1.4"
|
implementation 'com.bluelinelabs:conductor:2.1.5'
|
||||||
implementation 'com.github.inorichi:conductor-support-preference:26.0.2'
|
implementation("com.bluelinelabs:conductor-support:2.1.5") {
|
||||||
|
exclude group: "com.android.support"
|
||||||
|
}
|
||||||
|
implementation 'com.github.inorichi:conductor-support-preference:a32c357'
|
||||||
|
|
||||||
// RxBindings
|
// FlowBinding
|
||||||
final rxbindings_version = '1.0.1'
|
final flowbinding_version = '0.11.1'
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-kotlin:$rxbindings_version"
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-android:$flowbinding_version"
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:$rxbindings_version"
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbinding_version"
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-support-v4-kotlin:$rxbindings_version"
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbinding_version"
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version"
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbinding_version"
|
||||||
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
testImplementation 'org.assertj:assertj-core:1.7.1'
|
testImplementation 'org.assertj:assertj-core:3.12.2'
|
||||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||||
|
|
||||||
final robolectric_version = '3.1.4'
|
final robolectric_version = '3.1.4'
|
||||||
@ -224,15 +278,69 @@ dependencies {
|
|||||||
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
||||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
|
||||||
final coroutines_version = '0.19.1'
|
final coroutines_version = '1.3.7'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version"
|
||||||
|
|
||||||
|
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||||
|
|
||||||
|
// Text distance (EH)
|
||||||
|
implementation 'info.debatty:java-string-similarity:1.2.1'
|
||||||
|
|
||||||
|
// Pin lock view (EH)
|
||||||
|
implementation 'com.github.jawnnypoo:pinlockview:2.2.0'
|
||||||
|
|
||||||
|
// Reprint (EH)
|
||||||
|
implementation 'com.github.ajalt.reprint:core:3.2.1@aar'
|
||||||
|
implementation 'com.github.ajalt.reprint:rxjava:3.2.1@aar' // optional: the RxJava 1 interface
|
||||||
|
|
||||||
|
// Swirl (EH)
|
||||||
|
implementation 'com.mattprecious.swirl:swirl:1.2.0'
|
||||||
|
|
||||||
|
// RxJava 2 interop for Realm (EH)
|
||||||
|
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
|
||||||
|
|
||||||
|
// Firebase (EH)
|
||||||
|
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||||
|
|
||||||
|
// Better logging (EH)
|
||||||
|
implementation 'com.elvishew:xlog:1.6.1'
|
||||||
|
|
||||||
|
// Time utils (EH)
|
||||||
|
def typed_time_version = '1.0.2'
|
||||||
|
implementation "com.github.kizitonwose.time:time:$typed_time_version"
|
||||||
|
implementation "com.github.kizitonwose.time:time-android:$typed_time_version"
|
||||||
|
|
||||||
|
// Debug utils (EH)
|
||||||
|
debugImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||||
|
releaseTestImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||||
|
releaseImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||||
|
testImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||||
|
|
||||||
|
// Humanize (EH)
|
||||||
|
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
||||||
|
|
||||||
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
|
|
||||||
|
final def markwon_version = '4.1.0'
|
||||||
|
|
||||||
|
implementation "io.noties.markwon:core:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:ext-tables:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:html:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:image:$markwon_version"
|
||||||
|
implementation "io.noties.markwon:linkify:$markwon_version"
|
||||||
|
|
||||||
|
implementation 'com.google.guava:guava:27.0.1-android'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.61'
|
ext.kotlin_version = '1.3.72'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
@ -245,8 +353,22 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
||||||
experimental {
|
tasks.withType(AbstractKotlinCompile).all {
|
||||||
coroutines 'enable'
|
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||||
|
task copyResources(type: Copy) {
|
||||||
|
from './src/main/res/values-he'
|
||||||
|
into './src/main/res/values-iw'
|
||||||
|
include '**/*'
|
||||||
|
}
|
||||||
|
|
||||||
|
preBuild.dependsOn(ktlintFormat, copyResources)
|
||||||
|
|
||||||
|
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
// Firebase (EH)
|
||||||
|
apply plugin: 'io.fabric'
|
||||||
}
|
}
|
||||||
|
234
app/proguard-rules.pro
vendored
@ -1,35 +1,36 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-dontwarn eu.kanade.tachiyomi.**
|
|
||||||
-keep class eu.kanade.tachiyomi.**
|
|
||||||
-keep class eu.kanade.tachiyomi.source.model.** { *; }
|
|
||||||
|
|
||||||
-keep class com.hippo.image.** { *; }
|
|
||||||
-keep interface com.hippo.image.** { *; }
|
|
||||||
-dontwarn nucleus.view.NucleusActionBarActivity
|
|
||||||
|
|
||||||
# Extensions may require methods unused in the core app
|
# Extensions may require methods unused in the core app
|
||||||
|
-dontwarn eu.kanade.tachiyomi.**
|
||||||
|
-keep class eu.kanade.tachiyomi.** { public protected private *; }
|
||||||
|
|
||||||
-keep class org.jsoup.** { *; }
|
-keep class org.jsoup.** { *; }
|
||||||
-keep class kotlin.** { *; }
|
-keep class kotlin.** { *; }
|
||||||
-keep class okhttp3.** { *; }
|
-keep class okhttp3.** { *; }
|
||||||
-keep class com.google.gson.** { *; }
|
-keep class com.google.gson.** { *; }
|
||||||
-keep class com.github.salomonbrys.kotson.** { *; }
|
-keep class com.github.salomonbrys.kotson.** { *; }
|
||||||
|
-keep class com.squareup.duktape.** { *; }
|
||||||
|
|
||||||
# OkHttp
|
# === Keep EH classes
|
||||||
-dontwarn okhttp3.**
|
-keep class exh.** { *; }
|
||||||
-dontwarn okio.**
|
-keep class xyz.nulldev.** { *; }
|
||||||
-dontwarn javax.annotation.**
|
|
||||||
-dontwarn retrofit2.Platform$Java8
|
|
||||||
|
|
||||||
# Glide specific rules #
|
# === Keep RxAndroid, https://github.com/ReactiveX/RxAndroid/issues/350
|
||||||
# https://github.com/bumptech/glide
|
-keep class rx.android.** { *; }
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
||||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
# Design library
|
||||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
-dontwarn com.google.android.material.**
|
||||||
**[] $VALUES;
|
-keep class com.google.android.material.** { *; }
|
||||||
public *;
|
-keep interface com.google.android.material.** { *; }
|
||||||
|
-keep public class com.google.android.material.R$* { *; }
|
||||||
|
|
||||||
|
-keep class com.hippo.image.** { *; }
|
||||||
|
-keep interface com.hippo.image.** { *; }
|
||||||
|
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||||
|
<init>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# RxJava 1.1.0
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
@ -46,37 +47,41 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
### Support v7, Design
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
# http://stackoverflow.com/questions/29679177/cardview-shadow-not-appearing-in-lollipop-after-obfuscate-with-proguard/29698051
|
|
||||||
-keep class android.support.v7.widget.RoundRectDrawable { *; }
|
|
||||||
|
|
||||||
-keep public class android.support.v7.widget.** { *; }
|
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
||||||
-keep public class android.support.v7.internal.widget.** { *; }
|
-dontwarn com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
-keep public class android.support.v7.internal.view.menu.** { *; }
|
-dontwarn io.reactivex.functions.Function
|
||||||
-keep public class android.support.v7.graphics.drawable.** { *; }
|
-dontwarn rx.internal.util.**
|
||||||
|
-dontwarn sun.misc.Unsafe
|
||||||
|
|
||||||
-keep public class * extends android.support.v4.view.ActionProvider {
|
# === Okhttp: https://github.com/square/okhttp/blob/3637fc56f70f87da696847defd311dbfb28e87b5/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
||||||
public <init>(android.content.Context);
|
# JSR 305 annotations are for embedding nullability information.
|
||||||
}
|
-dontwarn javax.annotation.**
|
||||||
|
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||||
|
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||||
|
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||||
|
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
|
||||||
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||||
|
|
||||||
-dontwarn android.support.**
|
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
||||||
-dontwarn android.support.design.**
|
-dontwarn okio.**
|
||||||
-keep class android.support.design.** { *; }
|
|
||||||
-keep interface android.support.design.** { *; }
|
|
||||||
-keep public class android.support.design.R$* { *; }
|
|
||||||
|
|
||||||
|
|
||||||
# ReactiveNetwork
|
|
||||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
|
||||||
|
|
||||||
## GSON ##
|
|
||||||
|
|
||||||
|
# === GSON: https://raw.githubusercontent.com/google/gson/master/examples/android-proguard-example/proguard.cfg
|
||||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||||
# removes such information by default, so configure it to keep all of it.
|
# removes such information by default, so configure it to keep all of it.
|
||||||
-keepattributes Signature
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
# Gson specific classes
|
# Gson specific classes
|
||||||
-keep class sun.misc.Unsafe { *; }
|
-dontwarn sun.misc.**
|
||||||
|
#-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
|
# Application classes that will be serialized/deserialized over Gson
|
||||||
|
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||||
|
|
||||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||||
@ -84,9 +89,144 @@
|
|||||||
-keep class * implements com.google.gson.JsonSerializer
|
-keep class * implements com.google.gson.JsonSerializer
|
||||||
-keep class * implements com.google.gson.JsonDeserializer
|
-keep class * implements com.google.gson.JsonDeserializer
|
||||||
|
|
||||||
# SnakeYaml
|
# Prevent R8 from leaving Data object members always null
|
||||||
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
-keepclassmembers,allowobfuscation class * {
|
||||||
-dontwarn org.yaml.snakeyaml.**
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
# Duktape
|
# == Nucleus
|
||||||
-keep class com.squareup.duktape.** { *; }
|
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||||
|
<init>();
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO Changeloglib? Does it need proguard?
|
||||||
|
|
||||||
|
# === Injekt
|
||||||
|
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
||||||
|
-keep class uy.kohesive.injekt.** { *; }
|
||||||
|
|
||||||
|
|
||||||
|
# === Glide
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||||
|
|
||||||
|
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
|
||||||
|
-dontwarn jp.co.cyberagent.android.gpuimage.**
|
||||||
|
|
||||||
|
# === Conductor
|
||||||
|
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
||||||
|
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||||
|
public <init>();
|
||||||
|
}
|
||||||
|
|
||||||
|
# === RxBinding
|
||||||
|
-dontwarn com.google.auto.value.AutoValue
|
||||||
|
|
||||||
|
# === Crashlytics
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
-keep class com.crashlytics.** { *; }
|
||||||
|
-dontwarn com.crashlytics.**
|
||||||
|
|
||||||
|
# === Humanize + Guava: https://github.com/google/guava/wiki/UsingProGuardWithGuava
|
||||||
|
-dontwarn javax.lang.model.element.Modifier
|
||||||
|
-keep class org.ocpsoft.prettytime.i18n.**
|
||||||
|
|
||||||
|
# Note: We intentionally don't add the flags we'd need to make Enums work.
|
||||||
|
# That's because the Proguard configuration required to make it work on
|
||||||
|
# optimized code would preclude lots of optimization, like converting enums
|
||||||
|
# into ints.
|
||||||
|
|
||||||
|
# Throwables uses internal APIs for lazy stack trace resolution
|
||||||
|
-dontnote sun.misc.SharedSecrets
|
||||||
|
-keep class sun.misc.SharedSecrets {
|
||||||
|
*** getJavaLangAccess(...);
|
||||||
|
}
|
||||||
|
-dontnote sun.misc.JavaLangAccess
|
||||||
|
-keep class sun.misc.JavaLangAccess {
|
||||||
|
*** getStackTraceElement(...);
|
||||||
|
*** getStackTraceDepth(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
# FinalizableReferenceQueue calls this reflectively
|
||||||
|
# Proguard is intelligent enough to spot the use of reflection onto this, so we
|
||||||
|
# only need to keep the names, and allow it to be stripped out if
|
||||||
|
# FinalizableReferenceQueue is unused.
|
||||||
|
-keepnames class com.google.common.base.internal.Finalizer {
|
||||||
|
*** startFinalizer(...);
|
||||||
|
}
|
||||||
|
# However, it cannot "spot" that this method needs to be kept IF the class is.
|
||||||
|
-keepclassmembers class com.google.common.base.internal.Finalizer {
|
||||||
|
*** startFinalizer(...);
|
||||||
|
}
|
||||||
|
-keepnames class com.google.common.base.FinalizableReference {
|
||||||
|
void finalizeReferent();
|
||||||
|
}
|
||||||
|
-keepclassmembers class com.google.common.base.FinalizableReference {
|
||||||
|
void finalizeReferent();
|
||||||
|
}
|
||||||
|
|
||||||
|
# Striped64, LittleEndianByteArray, UnsignedBytes, AbstractFuture
|
||||||
|
-dontwarn sun.misc.Unsafe
|
||||||
|
|
||||||
|
# Striped64 appears to make some assumptions about object layout that
|
||||||
|
# really might not be safe. This should be investigated.
|
||||||
|
-keepclassmembers class com.google.common.cache.Striped64 {
|
||||||
|
*** base;
|
||||||
|
*** busy;
|
||||||
|
}
|
||||||
|
-keepclassmembers class com.google.common.cache.Striped64$Cell {
|
||||||
|
<fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn java.lang.SafeVarargs
|
||||||
|
|
||||||
|
-keep class java.lang.Throwable {
|
||||||
|
*** addSuppressed(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Futures.getChecked, in both of its variants, is incompatible with proguard.
|
||||||
|
|
||||||
|
# Used by AtomicReferenceFieldUpdater and sun.misc.Unsafe
|
||||||
|
-keepclassmembers class com.google.common.util.concurrent.AbstractFuture** {
|
||||||
|
*** waiters;
|
||||||
|
*** value;
|
||||||
|
*** listeners;
|
||||||
|
*** thread;
|
||||||
|
*** next;
|
||||||
|
}
|
||||||
|
-keepclassmembers class com.google.common.util.concurrent.AtomicDouble {
|
||||||
|
*** value;
|
||||||
|
}
|
||||||
|
-keepclassmembers class com.google.common.util.concurrent.AggregateFutureState {
|
||||||
|
*** remaining;
|
||||||
|
*** seenExceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Since Unsafe is using the field offsets of these inner classes, we don't want
|
||||||
|
# to have class merging or similar tricks applied to these classes and their
|
||||||
|
# fields. It's safe to allow obfuscation, since the by-name references are
|
||||||
|
# already preserved in the -keep statement above.
|
||||||
|
-keep,allowshrinking,allowobfuscation class com.google.common.util.concurrent.AbstractFuture** {
|
||||||
|
<fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Futures.getChecked (which often won't work with Proguard anyway) uses this. It
|
||||||
|
# has a fallback, but again, don't use Futures.getChecked on Android regardless.
|
||||||
|
-dontwarn java.lang.ClassValue
|
||||||
|
|
||||||
|
# MoreExecutors references AppEngine
|
||||||
|
-dontnote com.google.appengine.api.ThreadManager
|
||||||
|
-keep class com.google.appengine.api.ThreadManager {
|
||||||
|
static *** currentRequestThreadFactory(...);
|
||||||
|
}
|
||||||
|
-dontnote com.google.apphosting.api.ApiProxy
|
||||||
|
-keep class com.google.apphosting.api.ApiProxy {
|
||||||
|
static *** getCurrentEnvironment (...);
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_book_48dp"
|
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
android:shortcutId="show_library"
|
android:shortcutId="show_library"
|
||||||
android:shortcutLongLabel="@string/label_library"
|
android:shortcutLongLabel="@string/label_library"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
</shortcut>
|
</shortcut>
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_update_48dp"
|
android:icon="@drawable/sc_new_releases_48dp"
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
android:shortcutId="show_recently_updated"
|
android:shortcutId="show_recently_updated"
|
||||||
android:shortcutLongLabel="@string/label_recent_updates"
|
android:shortcutLongLabel="@string/label_recent_updates"
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</shortcut>
|
</shortcut>
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_glasses_48dp"
|
android:icon="@drawable/sc_history_48dp"
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
android:shortcutId="show_recently_read"
|
android:shortcutId="show_recently_read"
|
||||||
android:shortcutLongLabel="@string/label_recent_manga"
|
android:shortcutLongLabel="@string/label_recent_manga"
|
||||||
@ -38,8 +38,8 @@
|
|||||||
android:icon="@drawable/sc_explore_48dp"
|
android:icon="@drawable/sc_explore_48dp"
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
android:shortcutId="show_catalogues"
|
android:shortcutId="show_catalogues"
|
||||||
android:shortcutLongLabel="@string/label_catalogues"
|
android:shortcutLongLabel="@string/browse"
|
||||||
android:shortcutShortLabel="@string/label_catalogues">
|
android:shortcutShortLabel="@string/browse">
|
||||||
<intent
|
<intent
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
97
app/src/debug/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108.0"
|
||||||
|
android:viewportHeight="108.0">
|
||||||
|
<path
|
||||||
|
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:fillColor="#000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:fillColor="#455A64"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:fillColor="#607D8B"/>
|
||||||
|
<path
|
||||||
|
android:name="path_3"
|
||||||
|
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_4"
|
||||||
|
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||||
|
android:fillColor="#CE2828"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_5"
|
||||||
|
android:pathData="M 54 54.5 M 34.06 54.5 C 33.964 50.23 35.243 46.04 37.707 42.551 C 40.171 39.062 43.692 36.455 47.748 35.117 C 51.805 33.779 56.185 33.779 60.242 35.117 C 64.298 36.455 67.819 39.062 70.283 42.551 C 72.747 46.04 74.026 50.23 73.93 54.5 C 74.026 58.77 72.747 62.96 70.283 66.449 C 67.819 69.938 64.298 72.545 60.242 73.883 C 56.185 75.221 51.805 75.221 47.748 73.883 C 43.692 72.545 40.171 69.938 37.707 66.449 C 35.243 62.96 33.964 58.77 34.06 54.5"
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_6"
|
||||||
|
android:pathData="M 54.174 36.266 C 64.147 36.266 72.234 44.397 72.234 54.426 C 72.234 64.459 64.147 72.593 54.174 72.593 C 44.197 72.593 36.113 64.459 36.113 54.426 C 36.113 44.397 44.197 36.266 54.174 36.266 Z"
|
||||||
|
android:fillColor="#ffcc4d"
|
||||||
|
android:strokeColor="#ffcc4d"
|
||||||
|
android:strokeWidth="4.628571428571428"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"/>
|
||||||
|
<path
|
||||||
|
android:name="path_7"
|
||||||
|
android:pathData="M 48.774 45.158 C 49.988 45.158 50.973 46.452 50.973 48.05 C 50.973 49.65 49.988 50.946 48.774 50.946 C 47.559 50.946 46.576 49.65 46.576 48.05 C 46.576 46.452 47.559 45.158 48.774 45.158 Z"
|
||||||
|
android:fillColor="#674600"
|
||||||
|
android:strokeColor="#674600"
|
||||||
|
android:strokeWidth="0.1"
|
||||||
|
android:strokeLineJoin="round"/>
|
||||||
|
<path
|
||||||
|
android:name="path_8"
|
||||||
|
android:pathData="M 62.02 45.158 C 63.235 45.158 64.219 46.452 64.219 48.05 C 64.219 49.65 63.235 50.946 62.02 50.946 C 60.805 50.946 59.821 49.65 59.821 48.05 C 59.821 46.452 60.805 45.158 62.02 45.158 Z"
|
||||||
|
android:fillColor="#674600"
|
||||||
|
android:strokeColor="#674600"
|
||||||
|
android:strokeWidth="0.1"
|
||||||
|
android:strokeLineJoin="round"/>
|
||||||
|
<path
|
||||||
|
android:name="path_9"
|
||||||
|
android:pathData="M 44.687 42.102 C 44.687 42.102 48.404 40.049 53.119 42.102"
|
||||||
|
android:strokeColor="#674600"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_10"
|
||||||
|
android:pathData="M 58.035 42.102 C 58.035 42.102 61.751 40.049 66.465 42.102"
|
||||||
|
android:strokeColor="#674600"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_11"
|
||||||
|
android:pathData="M 49.191 56.685 C 49.191 56.685 51.703 59.685 56.992 59.685"
|
||||||
|
android:strokeColor="#674600"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_12"
|
||||||
|
android:pathData="M 34.072 67.511 C 34.072 67.511 34.524 66.248 37.476 63.634 C 37.476 63.634 39.143 61.991 37.876 58.114 C 37.876 58.114 37.534 56.06 39.681 57.255 C 39.681 57.255 44.129 60.46 40.871 65 C 40.871 65 40.241 66.117 41.652 65.78 L 53.939 63.103 C 53.939 63.103 55.275 62.752 55.535 64.163 C 55.535 64.163 55.696 65.372 54.641 65.672 L 47.742 67.378 C 47.742 67.378 50.72 69.597 47.742 70.825 C 47.742 70.825 50.294 72.779 47.336 74.268 C 47.336 74.268 49.276 76.015 46.407 77.019 C 46.407 77.019 42.525 78.474 39.209 77.831 C 35.509 77.103 31.947 72.114 34.072 67.511 Z"
|
||||||
|
android:fillColor="#f4900c"
|
||||||
|
android:strokeColor="#f4900c"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.1"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:name="path_13"
|
||||||
|
android:pathData="M 56.182 67.511 C 56.182 67.511 56.633 66.248 59.585 63.634 C 59.585 63.634 61.253 61.991 59.985 58.114 C 59.985 58.114 59.642 56.06 61.789 57.255 C 61.789 57.255 66.239 60.46 62.98 65 C 62.98 65 62.351 66.117 63.761 65.78 L 76.066 63.103 C 76.066 63.103 77.386 62.752 77.641 64.163 C 77.641 64.163 77.812 65.372 76.746 65.672 L 69.85 67.378 C 69.85 67.378 72.829 69.597 69.85 70.825 C 69.85 70.825 72.404 72.779 69.446 74.268 C 69.446 74.268 71.387 76.015 68.516 77.019 C 68.516 77.019 64.633 78.474 61.317 77.831 C 57.618 77.103 54.057 72.114 56.182 67.511 Z"
|
||||||
|
android:fillColor="#f4900c"
|
||||||
|
android:strokeColor="#f4900c"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.1"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
6
app/src/debug/res/drawable/ic_migrate_direction.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="100dp"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 -5,5z"/>
|
||||||
|
</vector>
|
5
app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@android:color/transparent"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@android:color/transparent"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 18 KiB |
253
app/src/main/AndroidManifest.xml
Normal file → Executable file
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="eu.kanade.tachiyomi">
|
package="eu.kanade.tachiyomi">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@ -8,37 +9,85 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
<!-- Lock vibrate -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:theme="@style/Theme.Tachiyomi">
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:theme="@style/Theme.Tachiyomi.Light"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/Theme.Splash">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!--suppress AndroidDomInspection -->
|
<!--suppress AndroidDomInspection -->
|
||||||
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcuts" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.main.ForceCloseActivity"
|
||||||
|
android:clearTaskOnLaunch="true"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.main.DeepLinkActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.searchable"
|
||||||
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:theme="@style/Theme.Reader" />
|
android:launchMode="singleTask" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.security.BiometricUnlockActivity"
|
||||||
|
android:theme="@style/Theme.Splash" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.webview.WebViewActivity"
|
||||||
|
android:configChanges="uiMode|orientation|screenSize" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".widget.CustomLayoutPickerActivity"
|
android:name=".widget.CustomLayoutPickerActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/FilePickerTheme" />
|
android:theme="@style/FilePickerTheme" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.AnilistLoginActivity"
|
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||||
android:label="Anilist">
|
android:label="Anilist">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@ -51,9 +100,48 @@
|
|||||||
android:scheme="tachiyomi" />
|
android:scheme="tachiyomi" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
||||||
|
android:label="Shikimori">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="shikimori-auth"
|
||||||
|
android:scheme="tachiyomi" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.setting.track.BangumiLoginActivity"
|
||||||
|
android:label="Bangumi">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="bangumi-auth"
|
||||||
|
android:scheme="tachiyomi" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".extension.util.ExtensionInstallActivity"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||||
|
android:theme="@style/Theme.MaterialComponents" />
|
||||||
|
<activity
|
||||||
|
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||||
|
android:theme="@style/Theme.MaterialComponents" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
@ -62,16 +150,6 @@
|
|||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="eu.kanade.tachiyomi.util.ZipContentProvider"
|
|
||||||
android:authorities="${applicationId}.zip-provider"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="eu.kanade.tachiyomi.util.RarContentProvider"
|
|
||||||
android:authorities="${applicationId}.rar-provider"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@ -85,17 +163,150 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.updater.UpdateDownloaderService"
|
android:name=".data.updater.UpdaterService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.backup.BackupCreateService"
|
android:name=".data.backup.BackupCreateService"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.backup.BackupRestoreService"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- EH -->
|
||||||
|
<service
|
||||||
|
android:name="exh.eh.EHentaiUpdateWorker"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name="exh.ui.intercept.InterceptActivity"
|
||||||
|
android:label="TachiyomiEH"
|
||||||
|
android:theme="@style/Theme.EHActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<!-- EH -->
|
||||||
|
<data
|
||||||
|
android:host="g.e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="g.e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- EXH -->
|
||||||
|
<data
|
||||||
|
android:host="exhentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="exhentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- nhentai -->
|
||||||
|
<data
|
||||||
|
android:host="nhentai.net"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="nhentai.net"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Perv Eden -->
|
||||||
|
<data
|
||||||
|
android:host="www.perveden.com"
|
||||||
|
android:pathPattern="/.*/.*-manga/.*"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.perveden.com"
|
||||||
|
android:pathPattern="/.*/.*-manga/.*"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Hentai Cafe -->
|
||||||
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:pathPattern="/.*/.*"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:pathPattern="/.*/.*"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Tsumino -->
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Book/Info/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Book/Info/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Read/View/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Read/View/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Hitomi.la -->
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/galleries/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/galleries/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Pururin.io -->
|
||||||
|
<data
|
||||||
|
android:host="pururin.io"
|
||||||
|
android:pathPrefix="/gallery/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="pururin.io"
|
||||||
|
android:pathPrefix="/gallery/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- HBrowse -->
|
||||||
|
<data
|
||||||
|
android:host="www.hbrowse.com"
|
||||||
|
android:pathPrefix="/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.hbrowse.com"
|
||||||
|
android:pathPrefix="/"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||||
|
android:theme="@style/Theme.EHActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
BIN
app/src/main/ic_launcher-web.png
Normal file → Executable file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
208
app/src/main/java/eu/kanade/tachiyomi/App.kt
Normal file → Executable file
@ -3,48 +3,88 @@ package eu.kanade.tachiyomi
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.support.multidex.MultiDex
|
import android.graphics.Color
|
||||||
import com.evernote.android.job.JobManager
|
import android.os.Build
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
import android.os.Environment
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import androidx.multidex.MultiDex
|
||||||
|
import com.elvishew.xlog.LogConfiguration
|
||||||
|
import com.elvishew.xlog.LogLevel
|
||||||
|
import com.elvishew.xlog.XLog
|
||||||
|
import com.elvishew.xlog.printer.AndroidPrinter
|
||||||
|
import com.elvishew.xlog.printer.Printer
|
||||||
|
import com.elvishew.xlog.printer.file.FilePrinter
|
||||||
|
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||||
|
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
|
||||||
|
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||||
|
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||||
|
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||||
|
import com.google.android.gms.security.ProviderInstaller
|
||||||
|
import com.kizitonwose.time.days
|
||||||
|
import com.ms_square.debugoverlay.DebugOverlay
|
||||||
|
import com.ms_square.debugoverlay.modules.FpsModule
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.LocaleHelper
|
import eu.kanade.tachiyomi.ui.main.ForceCloseActivity
|
||||||
import org.acra.ACRA
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
import org.acra.annotation.ReportsCrashes
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import exh.debug.DebugToggles
|
||||||
|
import exh.log.CrashlyticsPrinter
|
||||||
|
import exh.log.EHDebugModeOverlay
|
||||||
|
import exh.log.EHLogLevel
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import java.io.File
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.InjektScope
|
import uy.kohesive.injekt.api.InjektScope
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||||
|
|
||||||
@ReportsCrashes(
|
open class App : Application(), LifecycleObserver {
|
||||||
formUri = "http://tachiyomi.kanade.eu/crash_report",
|
|
||||||
reportType = org.acra.sender.HttpSender.Type.JSON,
|
|
||||||
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
|
||||||
buildConfigClass = BuildConfig::class,
|
|
||||||
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
|
|
||||||
)
|
|
||||||
open class App : Application() {
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
|
setupExhLogging() // EXH logging
|
||||||
|
|
||||||
|
workaroundAndroid7BrokenSSL()
|
||||||
|
|
||||||
|
// Enforce WebView availability
|
||||||
|
if (!WebViewUtil.supportsWebView(this)) {
|
||||||
|
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||||
|
ForceCloseActivity.closeApp(this)
|
||||||
|
}
|
||||||
|
|
||||||
Injekt = InjektScope(DefaultRegistrar())
|
Injekt = InjektScope(DefaultRegistrar())
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
|
||||||
|
|
||||||
setupAcra()
|
|
||||||
setupJobManager()
|
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
|
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
|
||||||
|
// Reprint.initialize(this) //Setup fingerprint (EH)
|
||||||
|
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
||||||
|
setupDebugOverlay()
|
||||||
|
}
|
||||||
|
|
||||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||||
|
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
if (BuildConfig.DEBUG) {
|
MultiDex.install(this)
|
||||||
MultiDex.install(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
@ -52,18 +92,32 @@ open class App : Application() {
|
|||||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
LocaleHelper.updateConfiguration(this, newConfig, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupAcra() {
|
private fun workaroundAndroid7BrokenSSL() {
|
||||||
ACRA.init(this)
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N ||
|
||||||
|
Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
SSLContext.getInstance("TLSv1.2")
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProviderInstaller.installIfNeeded(applicationContext)
|
||||||
|
} catch (e: GooglePlayServicesRepairableException) {
|
||||||
|
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||||
|
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupJobManager() {
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
JobManager.create(this).addJobCreator { tag ->
|
@Suppress("unused")
|
||||||
when (tag) {
|
fun onAppBackgrounded() {
|
||||||
LibraryUpdateJob.TAG -> LibraryUpdateJob()
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
UpdateCheckerJob.TAG -> UpdateCheckerJob()
|
if (preferences.lockAppAfter().get() >= 0) {
|
||||||
BackupCreatorJob.TAG -> BackupCreatorJob()
|
SecureActivityDelegate.locked = true
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,4 +125,92 @@ open class App : Application() {
|
|||||||
Notifications.createChannels(this)
|
Notifications.createChannels(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EXH
|
||||||
|
private fun deleteOldMetadataRealm() {
|
||||||
|
Realm.init(this)
|
||||||
|
val config = RealmConfiguration.Builder()
|
||||||
|
.name("gallery-metadata.realm")
|
||||||
|
.schemaVersion(3)
|
||||||
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
.build()
|
||||||
|
Realm.deleteRealm(config)
|
||||||
|
|
||||||
|
// Delete old paper db files
|
||||||
|
listOf(
|
||||||
|
File(filesDir, "gallery-ex"),
|
||||||
|
File(filesDir, "gallery-perveden"),
|
||||||
|
File(filesDir, "gallery-nhentai")
|
||||||
|
).forEach {
|
||||||
|
if (it.exists()) {
|
||||||
|
thread {
|
||||||
|
it.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXH
|
||||||
|
private fun setupExhLogging() {
|
||||||
|
EHLogLevel.init(this)
|
||||||
|
|
||||||
|
val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) {
|
||||||
|
LogLevel.ALL
|
||||||
|
} else {
|
||||||
|
LogLevel.WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
val logConfig = LogConfiguration.Builder()
|
||||||
|
.logLevel(logLevel)
|
||||||
|
.t()
|
||||||
|
.st(2)
|
||||||
|
.nb()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val printers = mutableListOf<Printer>(AndroidPrinter())
|
||||||
|
|
||||||
|
val logFolder = File(
|
||||||
|
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||||
|
getString(R.string.app_name),
|
||||||
|
"logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
printers += FilePrinter
|
||||||
|
.Builder(logFolder.absolutePath)
|
||||||
|
.fileNameGenerator(object : DateFileNameGenerator() {
|
||||||
|
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||||
|
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue))
|
||||||
|
.backupStrategy(NeverBackupStrategy())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Install Crashlytics in prod
|
||||||
|
if (!BuildConfig.DEBUG) {
|
||||||
|
printers += CrashlyticsPrinter(LogLevel.ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
XLog.init(
|
||||||
|
logConfig,
|
||||||
|
*printers.toTypedArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
XLog.d("Application booting...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXH
|
||||||
|
private fun setupDebugOverlay() {
|
||||||
|
try {
|
||||||
|
DebugOverlay.Builder(this)
|
||||||
|
.modules(FpsModule(), EHDebugModeOverlay(this))
|
||||||
|
.bgColor(Color.parseColor("#7F000000"))
|
||||||
|
.notification(false)
|
||||||
|
.allowSystemLayer(false)
|
||||||
|
.build()
|
||||||
|
.install()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
// Crashes if app is in background
|
||||||
|
XLog.e("Failed to initialize debug overlay, app in background?", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
46
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Normal file → Executable file
@ -8,34 +8,58 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import exh.eh.EHentaiUpdateHelper
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
import uy.kohesive.injekt.api.addSingleton
|
||||||
import uy.kohesive.injekt.api.addSingletonFactory
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class AppModule(val app: Application) : InjektModule {
|
class AppModule(val app: Application) : InjektModule {
|
||||||
|
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
|
addSingleton(app)
|
||||||
|
|
||||||
addSingletonFactory { PreferencesHelper(app) }
|
addSingletonFactory { PreferencesHelper(app) }
|
||||||
|
|
||||||
addSingletonFactory { DatabaseHelper(app) }
|
addSingletonFactory { DatabaseHelper(app) }
|
||||||
|
|
||||||
addSingletonFactory { ChapterCache(app) }
|
addSingletonFactory { ChapterCache(app) }
|
||||||
|
|
||||||
addSingletonFactory { CoverCache(app) }
|
addSingletonFactory { CoverCache(app) }
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app) }
|
addSingletonFactory { NetworkHelper(app) }
|
||||||
|
|
||||||
addSingletonFactory { SourceManager(app) }
|
addSingletonFactory { SourceManager(app).also { get<ExtensionManager>().init(it) } }
|
||||||
|
|
||||||
addSingletonFactory { DownloadManager(app) }
|
addSingletonFactory { ExtensionManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { TrackManager(app) }
|
addSingletonFactory { DownloadManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { Gson() }
|
addSingletonFactory { TrackManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { Gson() }
|
||||||
|
|
||||||
|
addSingletonFactory { EHentaiUpdateHelper(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { Markwon.create(app) }
|
||||||
|
|
||||||
|
// Asynchronously init expensive components for a faster cold start
|
||||||
|
|
||||||
|
GlobalScope.launch { get<PreferencesHelper>() }
|
||||||
|
|
||||||
|
GlobalScope.launch { get<NetworkHelper>() }
|
||||||
|
|
||||||
|
GlobalScope.launch { get<SourceManager>() }
|
||||||
|
|
||||||
|
GlobalScope.launch { get<DatabaseHelper>() }
|
||||||
|
|
||||||
|
GlobalScope.launch { get<DownloadManager>() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||||
|
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object Migrations {
|
object Migrations {
|
||||||
|
|
||||||
|
// TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a migration when the application is updated.
|
* Performs a migration when the application is updated.
|
||||||
*
|
*
|
||||||
@ -16,18 +20,33 @@ object Migrations {
|
|||||||
*/
|
*/
|
||||||
fun upgrade(preferences: PreferencesHelper): Boolean {
|
fun upgrade(preferences: PreferencesHelper): Boolean {
|
||||||
val context = preferences.context
|
val context = preferences.context
|
||||||
val oldVersion = preferences.lastVersionCode().getOrDefault()
|
val oldVersion = preferences.lastVersionCode().get()
|
||||||
|
|
||||||
|
// Cancel app updater job for debug builds that don't include it
|
||||||
|
if (BuildConfig.DEBUG && !BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
UpdaterJob.cancelTask(context)
|
||||||
|
}
|
||||||
|
|
||||||
if (oldVersion < BuildConfig.VERSION_CODE) {
|
if (oldVersion < BuildConfig.VERSION_CODE) {
|
||||||
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
|
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
|
||||||
|
|
||||||
if (oldVersion == 0) return false
|
// Fresh install
|
||||||
|
if (oldVersion == 0) {
|
||||||
|
// Set up default background tasks
|
||||||
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
UpdaterJob.setupTask(context)
|
||||||
|
}
|
||||||
|
ExtensionUpdateJob.setupTask(context)
|
||||||
|
LibraryUpdateJob.setupTask(context)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (oldVersion < 14) {
|
if (oldVersion < 14) {
|
||||||
// Restore jobs after upgrading to evernote's job scheduler.
|
// Restore jobs after upgrading to Evernote's job scheduler.
|
||||||
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
UpdateCheckerJob.setupTask()
|
UpdaterJob.setupTask(context)
|
||||||
}
|
}
|
||||||
LibraryUpdateJob.setupTask()
|
LibraryUpdateJob.setupTask(context)
|
||||||
}
|
}
|
||||||
if (oldVersion < 15) {
|
if (oldVersion < 15) {
|
||||||
// Delete internal chapter cache dir.
|
// Delete internal chapter cache dir.
|
||||||
@ -39,7 +58,7 @@ object Migrations {
|
|||||||
if (oldDir.exists()) {
|
if (oldDir.exists()) {
|
||||||
val destDir = context.getExternalFilesDir("covers")
|
val destDir = context.getExternalFilesDir("covers")
|
||||||
if (destDir != null) {
|
if (destDir != null) {
|
||||||
oldDir.listFiles().forEach {
|
oldDir.listFiles()?.forEach {
|
||||||
it.renameTo(File(destDir, it.name))
|
it.renameTo(File(destDir, it.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,9 +74,25 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 43) {
|
||||||
|
// Restore jobs after migrating from Evernote's job scheduler to WorkManager.
|
||||||
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
UpdaterJob.setupTask(context)
|
||||||
|
}
|
||||||
|
LibraryUpdateJob.setupTask(context)
|
||||||
|
BackupCreatorJob.setupTask(context)
|
||||||
|
|
||||||
|
// New extension update check job
|
||||||
|
ExtensionUpdateJob.setupTask(context)
|
||||||
|
}
|
||||||
|
if (oldVersion < 44) {
|
||||||
|
// Reset sorting preference if using removed sort by source
|
||||||
|
if (preferences.librarySortingMode().get() == LibrarySort.SOURCE) {
|
||||||
|
preferences.librarySortingMode().set(LibrarySort.ALPHA)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -1,23 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||||
|
|
||||||
object BackupConst {
|
object BackupConst {
|
||||||
|
|
||||||
const val INTENT_FILTER = "SettingsBackupFragment"
|
private const val NAME = "BackupRestoreServices"
|
||||||
const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG"
|
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
||||||
const val ACTION_SET_PROGRESS_DIALOG = "$ID.$INTENT_FILTER.ACTION_SET_PROGRESS_DIALOG"
|
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
||||||
const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG"
|
}
|
||||||
const val ACTION_ERROR_RESTORE_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_RESTORE_DIALOG"
|
|
||||||
const val ACTION_RESTORE_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_RESTORE_COMPLETED_DIALOG"
|
|
||||||
const val ACTION = "$ID.$INTENT_FILTER.ACTION"
|
|
||||||
const val EXTRA_PROGRESS = "$ID.$INTENT_FILTER.EXTRA_PROGRESS"
|
|
||||||
const val EXTRA_AMOUNT = "$ID.$INTENT_FILTER.EXTRA_AMOUNT"
|
|
||||||
const val EXTRA_ERRORS = "$ID.$INTENT_FILTER.EXTRA_ERRORS"
|
|
||||||
const val EXTRA_CONTENT = "$ID.$INTENT_FILTER.EXTRA_CONTENT"
|
|
||||||
const val EXTRA_ERROR_MESSAGE = "$ID.$INTENT_FILTER.EXTRA_ERROR_MESSAGE"
|
|
||||||
const val EXTRA_URI = "$ID.$INTENT_FILTER.EXTRA_URI"
|
|
||||||
const val EXTRA_TIME = "$ID.$INTENT_FILTER.EXTRA_TIME"
|
|
||||||
const val EXTRA_ERROR_FILE_PATH = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE_PATH"
|
|
||||||
const val EXTRA_ERROR_FILE = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE"
|
|
||||||
}
|
|
||||||
|
@ -1,36 +1,23 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import android.app.IntentService
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.github.salomonbrys.kotson.set
|
import android.os.Build
|
||||||
import com.google.gson.JsonArray
|
import android.os.IBinder
|
||||||
import com.google.gson.JsonObject
|
import android.os.PowerManager
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
|
||||||
import timber.log.Timber
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [IntentService] used to backup [Manga] information to [JsonArray]
|
* Service for backing up library information to a JSON file.
|
||||||
*/
|
*/
|
||||||
class BackupCreateService : IntentService(NAME) {
|
class BackupCreateService : Service() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Name of class
|
|
||||||
private const val NAME = "BackupCreateService"
|
|
||||||
|
|
||||||
// Backup called from job
|
|
||||||
private const val EXTRA_IS_JOB = "$ID.$NAME.EXTRA_IS_JOB"
|
|
||||||
// Options for backup
|
|
||||||
private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
|
||||||
|
|
||||||
// Filter options
|
// Filter options
|
||||||
internal const val BACKUP_CATEGORY = 0x1
|
internal const val BACKUP_CATEGORY = 0x1
|
||||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
internal const val BACKUP_CATEGORY_MASK = 0x1
|
||||||
@ -42,121 +29,91 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
internal const val BACKUP_TRACK_MASK = 0x8
|
internal const val BACKUP_TRACK_MASK = 0x8
|
||||||
internal const val BACKUP_ALL = 0xF
|
internal const val BACKUP_ALL = 0xF
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of the service.
|
||||||
|
*
|
||||||
|
* @param context the application context.
|
||||||
|
* @return true if the service is running, false otherwise.
|
||||||
|
*/
|
||||||
|
fun isRunning(context: Context): Boolean =
|
||||||
|
context.isServiceRunning(BackupCreateService::class.java)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a backup from library
|
* Make a backup from library
|
||||||
*
|
*
|
||||||
* @param context context of application
|
* @param context context of application
|
||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
* @param flags determines what to backup
|
* @param flags determines what to backup
|
||||||
* @param isJob backup called from job
|
|
||||||
*/
|
*/
|
||||||
fun makeBackup(context: Context, uri: Uri, flags: Int, isJob: Boolean = false) {
|
fun start(context: Context, uri: Uri, flags: Int) {
|
||||||
val intent = Intent(context, BackupCreateService::class.java).apply {
|
if (!isRunning(context)) {
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
val intent = Intent(context, BackupCreateService::class.java).apply {
|
||||||
putExtra(EXTRA_IS_JOB, isJob)
|
putExtra(BackupConst.EXTRA_URI, uri)
|
||||||
putExtra(EXTRA_FLAGS, flags)
|
putExtra(BackupConst.EXTRA_FLAGS, flags)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
context.startService(intent)
|
||||||
|
} else {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
context.startService(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private val backupManager by lazy { BackupManager(this) }
|
|
||||||
|
|
||||||
override fun onHandleIntent(intent: Intent?) {
|
|
||||||
if (intent == null) return
|
|
||||||
|
|
||||||
// Get values
|
|
||||||
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
|
||||||
val isJob = intent.getBooleanExtra(EXTRA_IS_JOB, false)
|
|
||||||
val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
|
|
||||||
// Create backup
|
|
||||||
createBackupFromApp(uri, flags, isJob)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create backup Json file from database
|
* Wake lock that will be held until the service is destroyed.
|
||||||
*
|
|
||||||
* @param uri path of Uri
|
|
||||||
* @param isJob backup called from job
|
|
||||||
*/
|
*/
|
||||||
private fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) {
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
// Create root object
|
|
||||||
val root = JsonObject()
|
|
||||||
|
|
||||||
// Create manga array
|
private lateinit var backupManager: BackupManager
|
||||||
val mangaEntries = JsonArray()
|
private lateinit var notifier: BackupNotifier
|
||||||
|
|
||||||
// Create category array
|
override fun onCreate() {
|
||||||
val categoryEntries = JsonArray()
|
super.onCreate()
|
||||||
|
|
||||||
// Add value's to root
|
notifier = BackupNotifier(this)
|
||||||
root[VERSION] = Backup.CURRENT_VERSION
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
root[MANGAS] = mangaEntries
|
|
||||||
root[CATEGORIES] = categoryEntries
|
|
||||||
|
|
||||||
backupManager.databaseHelper.inTransaction {
|
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||||
// Get manga from database
|
}
|
||||||
val mangas = backupManager.getFavoriteManga()
|
|
||||||
|
|
||||||
// Backup library manga and its dependencies
|
override fun stopService(name: Intent?): Boolean {
|
||||||
mangas.forEach { manga ->
|
destroyJob()
|
||||||
mangaEntries.add(backupManager.backupMangaObject(manga, flags))
|
return super.stopService(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backup categories
|
override fun onDestroy() {
|
||||||
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
|
destroyJob()
|
||||||
backupManager.backupCategories(categoryEntries)
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
private fun destroyJob() {
|
||||||
// When BackupCreatorJob
|
if (wakeLock.isHeld) {
|
||||||
if (isJob) {
|
wakeLock.release()
|
||||||
// Get dir of file and create
|
|
||||||
var dir = UniFile.fromUri(this, uri)
|
|
||||||
dir = dir.createDirectory("automatic")
|
|
||||||
|
|
||||||
// Delete older backups
|
|
||||||
val numberOfBackups = backupManager.numberOfBackups()
|
|
||||||
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
|
|
||||||
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
|
||||||
.orEmpty()
|
|
||||||
.sortedByDescending { it.name }
|
|
||||||
.drop(numberOfBackups - 1)
|
|
||||||
.forEach { it.delete() }
|
|
||||||
|
|
||||||
// Create new file to place backup
|
|
||||||
val newFile = dir.createFile(Backup.getDefaultFilename())
|
|
||||||
?: throw Exception("Couldn't create backup file")
|
|
||||||
|
|
||||||
newFile.openOutputStream().bufferedWriter().use {
|
|
||||||
backupManager.parser.toJson(root, it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val file = UniFile.fromUri(this, uri)
|
|
||||||
?: throw Exception("Couldn't create backup file")
|
|
||||||
file.openOutputStream().bufferedWriter().use {
|
|
||||||
backupManager.parser.toJson(root, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show completed dialog
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED_DIALOG)
|
|
||||||
putExtra(BackupConst.EXTRA_URI, file.uri.toString())
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
if (!isJob) {
|
|
||||||
// Show error dialog
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_BACKUP_DIALOG)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
|
*/
|
||||||
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
if (intent == null) return START_NOT_STICKY
|
||||||
|
|
||||||
|
try {
|
||||||
|
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
||||||
|
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
|
||||||
|
backupManager = BackupManager(this)
|
||||||
|
|
||||||
|
val backupFileUri = Uri.parse(backupManager.createBackup(uri, backupFlags, false))
|
||||||
|
val unifile = UniFile.fromUri(this, backupFileUri)
|
||||||
|
notifier.showBackupComplete(unifile)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
notifier.showBackupError(e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSelf(startId)
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,41 +1,51 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.evernote.android.job.Job
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import com.evernote.android.job.JobManager
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
import com.evernote.android.job.JobRequest
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import java.util.concurrent.TimeUnit
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class BackupCreatorJob : Job() {
|
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
|
Worker(context, workerParams) {
|
||||||
|
|
||||||
override fun onRunJob(params: Params): Result {
|
override fun doWork(): Result {
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val uri = Uri.parse(preferences.backupsDirectory().getOrDefault())
|
val backupManager = BackupManager(context)
|
||||||
|
val uri = Uri.parse(preferences.backupsDirectory().get())
|
||||||
val flags = BackupCreateService.BACKUP_ALL
|
val flags = BackupCreateService.BACKUP_ALL
|
||||||
BackupCreateService.makeBackup(context, uri, flags, true)
|
return try {
|
||||||
return Result.SUCCESS
|
backupManager.createBackup(uri, flags, true)
|
||||||
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "BackupCreator"
|
private const val TAG = "BackupCreator"
|
||||||
|
|
||||||
fun setupTask(prefInterval: Int? = null) {
|
fun setupTask(context: Context, prefInterval: Int? = null) {
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
|
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
JobRequest.Builder(TAG)
|
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||||
.setPeriodic(interval * 60 * 60 * 1000L, 10 * 60 * 1000)
|
interval.toLong(), TimeUnit.HOURS,
|
||||||
.setUpdateCurrent(true)
|
10, TimeUnit.MINUTES
|
||||||
.build()
|
)
|
||||||
.schedule()
|
.addTag(TAG)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||||
|
} else {
|
||||||
|
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelTask() {
|
|
||||||
JobManager.instance().cancelAllForTag(TAG)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
291
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Normal file → Executable file
@ -1,8 +1,17 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.github.salomonbrys.kotson.*
|
import android.net.Uri
|
||||||
import com.google.gson.*
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
|
import com.github.salomonbrys.kotson.registerTypeAdapter
|
||||||
|
import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter
|
||||||
|
import com.github.salomonbrys.kotson.set
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
||||||
@ -11,35 +20,49 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HIST
|
|||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
|
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
|
import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
|
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
|
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
||||||
import eu.kanade.tachiyomi.data.backup.serializer.*
|
import eu.kanade.tachiyomi.data.backup.serializer.CategoryTypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.backup.serializer.ChapterTypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.backup.serializer.HistoryTypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.backup.serializer.MangaTypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.backup.serializer.TrackTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
|
import exh.eh.EHentaiThrottleManager
|
||||||
|
import kotlin.math.max
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Database.
|
|
||||||
*/
|
|
||||||
internal val databaseHelper: DatabaseHelper by injectLazy()
|
internal val databaseHelper: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
|
||||||
* Source manager.
|
|
||||||
*/
|
|
||||||
internal val sourceManager: SourceManager by injectLazy()
|
internal val sourceManager: SourceManager by injectLazy()
|
||||||
|
internal val trackManager: TrackManager by injectLazy()
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version of parser
|
* Version of parser
|
||||||
@ -52,11 +75,6 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*/
|
*/
|
||||||
var parser: Gson = initParser()
|
var parser: Gson = initParser()
|
||||||
|
|
||||||
/**
|
|
||||||
* Preferences
|
|
||||||
*/
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set version of parser
|
* Set version of parser
|
||||||
*
|
*
|
||||||
@ -67,17 +85,114 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
parser = initParser()
|
parser = initParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initParser(): Gson {
|
private fun initParser(): Gson = when (version) {
|
||||||
return when (version) {
|
1 -> GsonBuilder().create()
|
||||||
1 -> GsonBuilder().create()
|
2 ->
|
||||||
2 -> GsonBuilder()
|
GsonBuilder()
|
||||||
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
||||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||||
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
||||||
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
||||||
.create()
|
.create()
|
||||||
else -> throw Exception("Json version unknown")
|
else -> throw Exception("Json version unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create backup Json file from database
|
||||||
|
*
|
||||||
|
* @param uri path of Uri
|
||||||
|
* @param isJob backup called from job
|
||||||
|
*/
|
||||||
|
fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
|
||||||
|
// Create root object
|
||||||
|
val root = JsonObject()
|
||||||
|
|
||||||
|
// Create manga array
|
||||||
|
val mangaEntries = JsonArray()
|
||||||
|
|
||||||
|
// Create category array
|
||||||
|
val categoryEntries = JsonArray()
|
||||||
|
|
||||||
|
// Create extension ID/name mapping
|
||||||
|
val extensionEntries = JsonArray()
|
||||||
|
|
||||||
|
// Add value's to root
|
||||||
|
root[Backup.VERSION] = CURRENT_VERSION
|
||||||
|
root[Backup.MANGAS] = mangaEntries
|
||||||
|
root[CATEGORIES] = categoryEntries
|
||||||
|
root[EXTENSIONS] = extensionEntries
|
||||||
|
|
||||||
|
databaseHelper.inTransaction {
|
||||||
|
// Get manga from database
|
||||||
|
val mangas = getFavoriteManga()
|
||||||
|
|
||||||
|
val extensions: MutableSet<String> = mutableSetOf()
|
||||||
|
|
||||||
|
// Backup library manga and its dependencies
|
||||||
|
mangas.forEach { manga ->
|
||||||
|
mangaEntries.add(backupMangaObject(manga, flags))
|
||||||
|
|
||||||
|
// Maintain set of extensions/sources used (excludes local source)
|
||||||
|
if (manga.source != LocalSource.ID) {
|
||||||
|
sourceManager.get(manga.source)?.let {
|
||||||
|
extensions.add("${manga.source}:${it.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup categories
|
||||||
|
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
|
||||||
|
backupCategories(categoryEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup extension ID/name mapping
|
||||||
|
backupExtensionInfo(extensionEntries, extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// When BackupCreatorJob
|
||||||
|
if (isJob) {
|
||||||
|
// Get dir of file and create
|
||||||
|
var dir = UniFile.fromUri(context, uri)
|
||||||
|
dir = dir.createDirectory("automatic")
|
||||||
|
|
||||||
|
// Delete older backups
|
||||||
|
val numberOfBackups = numberOfBackups()
|
||||||
|
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
|
||||||
|
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
||||||
|
.orEmpty()
|
||||||
|
.sortedByDescending { it.name }
|
||||||
|
.drop(numberOfBackups - 1)
|
||||||
|
.forEach { it.delete() }
|
||||||
|
|
||||||
|
// Create new file to place backup
|
||||||
|
val newFile = dir.createFile(Backup.getDefaultFilename())
|
||||||
|
?: throw Exception("Couldn't create backup file")
|
||||||
|
|
||||||
|
newFile.openOutputStream().bufferedWriter().use {
|
||||||
|
parser.toJson(root, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFile.uri.toString()
|
||||||
|
} else {
|
||||||
|
val file = UniFile.fromUri(context, uri)
|
||||||
|
?: throw Exception("Couldn't create backup file")
|
||||||
|
file.openOutputStream().bufferedWriter().use {
|
||||||
|
parser.toJson(root, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.uri.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun backupExtensionInfo(root: JsonArray, extensions: Set<String>) {
|
||||||
|
extensions.sorted().forEach {
|
||||||
|
root.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +223,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
||||||
// Backup all the chapters
|
// Backup all the chapters
|
||||||
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
if (!chapters.isEmpty()) {
|
if (chapters.isNotEmpty()) {
|
||||||
val chaptersJson = parser.toJsonTree(chapters)
|
val chaptersJson = parser.toJsonTree(chapters)
|
||||||
if (chaptersJson.asJsonArray.size() > 0) {
|
if (chaptersJson.asJsonArray.size() > 0) {
|
||||||
entry[CHAPTERS] = chaptersJson
|
entry[CHAPTERS] = chaptersJson
|
||||||
@ -120,7 +235,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
||||||
// Backup categories for this manga
|
// Backup categories for this manga
|
||||||
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
||||||
if (!categoriesForManga.isEmpty()) {
|
if (categoriesForManga.isNotEmpty()) {
|
||||||
val categoriesNames = categoriesForManga.map { it.name }
|
val categoriesNames = categoriesForManga.map { it.name }
|
||||||
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
||||||
}
|
}
|
||||||
@ -129,7 +244,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if user wants track information in backup
|
// Check if user wants track information in backup
|
||||||
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
||||||
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
||||||
if (!tracks.isEmpty()) {
|
if (tracks.isNotEmpty()) {
|
||||||
entry[TRACK] = parser.toJsonTree(tracks)
|
entry[TRACK] = parser.toJsonTree(tracks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +252,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if user wants history information in backup
|
// Check if user wants history information in backup
|
||||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
||||||
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
||||||
if (!historyForManga.isEmpty()) {
|
if (historyForManga.isNotEmpty()) {
|
||||||
val historyData = historyForManga.mapNotNull { history ->
|
val historyData = historyForManga.mapNotNull { history ->
|
||||||
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
||||||
url?.let { DHistory(url, history.last_read) }
|
url?.let { DHistory(url, history.last_read) }
|
||||||
@ -168,13 +283,13 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*/
|
*/
|
||||||
fun restoreMangaFetchObservable(source: Source, manga: Manga): Observable<Manga> {
|
fun restoreMangaFetchObservable(source: Source, manga: Manga): Observable<Manga> {
|
||||||
return source.fetchMangaDetails(manga)
|
return source.fetchMangaDetails(manga)
|
||||||
.map { networkManga ->
|
.map { networkManga ->
|
||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
manga.favorite = true
|
manga.favorite = true
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
manga.id = insertManga(manga)
|
manga.id = insertManga(manga)
|
||||||
manga
|
manga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,15 +299,29 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
* @param manga manga that needs updating
|
* @param manga manga that needs updating
|
||||||
* @return [Observable] that contains manga
|
* @return [Observable] that contains manga
|
||||||
*/
|
*/
|
||||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
return source.fetchChapterList(manga)
|
return (
|
||||||
.map { syncChaptersWithSource(databaseHelper, it, manga, source) }
|
if (source is EHentai) {
|
||||||
.doOnNext {
|
source.fetchChapterList(manga, throttleManager::throttle)
|
||||||
if (it.first.isNotEmpty()) {
|
} else {
|
||||||
chapters.forEach { it.manga_id = manga.id }
|
source.fetchChapterList(manga)
|
||||||
insertChapters(chapters)
|
}
|
||||||
}
|
).map {
|
||||||
|
if (it.last().chapter_number == -99F) {
|
||||||
|
chapters.forEach { chapter ->
|
||||||
|
chapter.name = "Chapter ${chapter.chapter_number} restored by dummy source"
|
||||||
}
|
}
|
||||||
|
syncChaptersWithSource(databaseHelper, chapters, manga, source)
|
||||||
|
} else {
|
||||||
|
syncChaptersWithSource(databaseHelper, it, manga, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doOnNext { pair ->
|
||||||
|
if (pair.first.isNotEmpty()) {
|
||||||
|
chapters.forEach { it.manga_id = manga.id }
|
||||||
|
insertChapters(chapters)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,7 +377,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
if (!mangaCategoriesToUpdate.isEmpty()) {
|
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
||||||
val mangaAsList = ArrayList<Manga>()
|
val mangaAsList = ArrayList<Manga>()
|
||||||
mangaAsList.add(manga)
|
mangaAsList.add(manga)
|
||||||
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
||||||
@ -269,7 +398,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if history already in database and update
|
// Check if history already in database and update
|
||||||
if (dbHistory != null) {
|
if (dbHistory != null) {
|
||||||
dbHistory.apply {
|
dbHistory.apply {
|
||||||
last_read = Math.max(lastRead, dbHistory.last_read)
|
last_read = max(lastRead, dbHistory.last_read)
|
||||||
}
|
}
|
||||||
historyToBeUpdated.add(dbHistory)
|
historyToBeUpdated.add(dbHistory)
|
||||||
} else {
|
} else {
|
||||||
@ -300,27 +429,33 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
val trackToUpdate = ArrayList<Track>()
|
val trackToUpdate = ArrayList<Track>()
|
||||||
|
|
||||||
for (track in tracks) {
|
for (track in tracks) {
|
||||||
var isInDatabase = false
|
val service = trackManager.getService(track.sync_id)
|
||||||
for (dbTrack in dbTracks) {
|
if (service != null && service.isLogged) {
|
||||||
if (track.sync_id == dbTrack.sync_id) {
|
var isInDatabase = false
|
||||||
// The sync is already in the db, only update its fields
|
for (dbTrack in dbTracks) {
|
||||||
if (track.remote_id != dbTrack.remote_id) {
|
if (track.sync_id == dbTrack.sync_id) {
|
||||||
dbTrack.remote_id = track.remote_id
|
// The sync is already in the db, only update its fields
|
||||||
|
if (track.media_id != dbTrack.media_id) {
|
||||||
|
dbTrack.media_id = track.media_id
|
||||||
|
}
|
||||||
|
if (track.library_id != dbTrack.library_id) {
|
||||||
|
dbTrack.library_id = track.library_id
|
||||||
|
}
|
||||||
|
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
||||||
|
isInDatabase = true
|
||||||
|
trackToUpdate.add(dbTrack)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read)
|
|
||||||
isInDatabase = true
|
|
||||||
trackToUpdate.add(dbTrack)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
if (!isInDatabase) {
|
||||||
if (!isInDatabase) {
|
// Insert new sync. Let the db assign the id
|
||||||
// Insert new sync. Let the db assign the id
|
track.id = null
|
||||||
track.id = null
|
trackToUpdate.add(track)
|
||||||
trackToUpdate.add(track)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update database
|
// Update database
|
||||||
if (!trackToUpdate.isEmpty()) {
|
if (trackToUpdate.isNotEmpty()) {
|
||||||
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,8 +471,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
// Return if fetch is needed
|
// Return if fetch is needed
|
||||||
if (dbChapters.isEmpty() || dbChapters.size < chapters.size)
|
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for (chapter in chapters) {
|
for (chapter in chapters) {
|
||||||
val pos = dbChapters.indexOf(chapter)
|
val pos = dbChapters.indexOf(chapter)
|
||||||
@ -361,32 +497,29 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*
|
*
|
||||||
* @return [Manga], null if not found
|
* @return [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
internal fun getMangaFromDatabase(manga: Manga): Manga? {
|
internal fun getMangaFromDatabase(manga: Manga): Manga? =
|
||||||
return databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list containing manga from library
|
* Returns list containing manga from library
|
||||||
*
|
*
|
||||||
* @return [Manga] from library
|
* @return [Manga] from library
|
||||||
*/
|
*/
|
||||||
internal fun getFavoriteManga(): List<Manga> {
|
internal fun getFavoriteManga(): List<Manga> =
|
||||||
return databaseHelper.getFavoriteMangas().executeAsBlocking()
|
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts manga and returns id
|
* Inserts manga and returns id
|
||||||
*
|
*
|
||||||
* @return id of [Manga], null if not found
|
* @return id of [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
internal fun insertManga(manga: Manga): Long? {
|
internal fun insertManga(manga: Manga): Long? =
|
||||||
return databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts list of chapters
|
* Inserts list of chapters
|
||||||
*/
|
*/
|
||||||
internal fun insertChapters(chapters: List<Chapter>) {
|
private fun insertChapters(chapters: List<Chapter>) {
|
||||||
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +528,5 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*
|
*
|
||||||
* @return number of backups selected by user
|
* @return number of backups selected by user
|
||||||
*/
|
*/
|
||||||
fun numberOfBackups(): Int {
|
fun numberOfBackups(): Int = preferences.numberOfBackups().get()
|
||||||
return preferences.numberOfBackups().getOrDefault()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
internal class BackupNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS) {
|
||||||
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
setAutoCancel(false)
|
||||||
|
setOngoing(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) {
|
||||||
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
setAutoCancel(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationCompat.Builder.show(id: Int) {
|
||||||
|
context.notificationManager.notify(id, build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showBackupProgress(): NotificationCompat.Builder {
|
||||||
|
val builder = with(progressNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.creating_backup))
|
||||||
|
|
||||||
|
setProgress(0, 0, true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.show(Notifications.ID_BACKUP_PROGRESS)
|
||||||
|
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showBackupError(error: String?) {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
||||||
|
|
||||||
|
with(completeNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.creating_backup_error))
|
||||||
|
setContentText(error)
|
||||||
|
|
||||||
|
show(Notifications.ID_BACKUP_COMPLETE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showBackupComplete(unifile: UniFile) {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
||||||
|
|
||||||
|
with(completeNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.backup_created))
|
||||||
|
|
||||||
|
if (unifile.filePath != null) {
|
||||||
|
setContentText(unifile.filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear old actions if they exist
|
||||||
|
if (mActions.isNotEmpty()) {
|
||||||
|
mActions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_share_24dp,
|
||||||
|
context.getString(R.string.action_share),
|
||||||
|
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE)
|
||||||
|
)
|
||||||
|
|
||||||
|
show(Notifications.ID_BACKUP_COMPLETE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showRestoreProgress(content: String = "", progress: Int = 0, maxAmount: Int = 100): NotificationCompat.Builder {
|
||||||
|
val builder = with(progressNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.restoring_backup))
|
||||||
|
|
||||||
|
if (!preferences.hideNotificationContent()) {
|
||||||
|
setContentText(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
setProgress(maxAmount, progress, false)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
|
||||||
|
// Clear old actions if they exist
|
||||||
|
if (mActions.isNotEmpty()) {
|
||||||
|
mActions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_close_24dp,
|
||||||
|
context.getString(R.string.action_stop),
|
||||||
|
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.show(Notifications.ID_RESTORE_PROGRESS)
|
||||||
|
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showRestoreError(error: String?) {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS)
|
||||||
|
|
||||||
|
with(completeNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.restoring_backup_error))
|
||||||
|
setContentText(error)
|
||||||
|
|
||||||
|
show(Notifications.ID_RESTORE_COMPLETE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showRestoreComplete(time: Long, errorCount: Int, path: String?, file: String?) {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS)
|
||||||
|
|
||||||
|
val timeString = context.getString(
|
||||||
|
R.string.restore_duration,
|
||||||
|
TimeUnit.MILLISECONDS.toMinutes(time),
|
||||||
|
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
|
||||||
|
TimeUnit.MILLISECONDS.toMinutes(time)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with(completeNotificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.restore_completed))
|
||||||
|
setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
|
||||||
|
|
||||||
|
// Clear old actions if they exist
|
||||||
|
if (mActions.isNotEmpty()) {
|
||||||
|
mActions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCount > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
|
||||||
|
val destFile = File(path, file)
|
||||||
|
val uri = destFile.getUriCompat(context)
|
||||||
|
|
||||||
|
addAction(
|
||||||
|
R.drawable.nnf_ic_file_folder,
|
||||||
|
context.getString(R.string.action_open_log),
|
||||||
|
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
show(Notifications.ID_RESTORE_COMPLETE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,13 @@ import android.app.Service
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -20,24 +23,33 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
|||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.chop
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
import exh.EXHMigrations
|
||||||
import rx.Observable
|
import exh.eh.EHentaiThrottleManager
|
||||||
import rx.Subscription
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import timber.log.Timber
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.Locale
|
||||||
import java.util.concurrent.Executors
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import rx.Observable
|
||||||
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores backup from json file
|
* Restores backup from a JSON file.
|
||||||
*/
|
*/
|
||||||
class BackupRestoreService : Service() {
|
class BackupRestoreService : Service() {
|
||||||
|
|
||||||
@ -49,9 +61,8 @@ class BackupRestoreService : Service() {
|
|||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @return true if the service is running, false otherwise.
|
* @return true if the service is running, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isRunning(context: Context): Boolean {
|
fun isRunning(context: Context): Boolean =
|
||||||
return context.isServiceRunning(BackupRestoreService::class.java)
|
context.isServiceRunning(BackupRestoreService::class.java)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a service to restore a backup from Json
|
* Starts a service to restore a backup from Json
|
||||||
@ -64,7 +75,11 @@ class BackupRestoreService : Service() {
|
|||||||
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
putExtra(BackupConst.EXTRA_URI, uri)
|
||||||
}
|
}
|
||||||
context.startService(intent)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
context.startService(intent)
|
||||||
|
} else {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +90,8 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
fun stop(context: Context) {
|
fun stop(context: Context) {
|
||||||
context.stopService(Intent(context, BackupRestoreService::class.java))
|
context.stopService(Intent(context, BackupRestoreService::class.java))
|
||||||
|
|
||||||
|
BackupNotifier(context).showRestoreError(context.getString(R.string.restoring_backup_canceled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,10 +100,9 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
|
|
||||||
/**
|
private var job: Job? = null
|
||||||
* Subscription where the update is done.
|
|
||||||
*/
|
private val throttleManager = EHentaiThrottleManager()
|
||||||
private var subscription: Subscription? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The progress of a backup restore
|
* The progress of a backup restore
|
||||||
@ -98,53 +114,57 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private var restoreAmount = 0
|
private var restoreAmount = 0
|
||||||
|
|
||||||
|
private var skippedAmount = 0
|
||||||
|
|
||||||
|
private var totalAmount = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of source ID to source name from backup data
|
||||||
|
*/
|
||||||
|
private var sourceMapping: Map<Long, String> = emptyMap()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List containing errors
|
* List containing errors
|
||||||
*/
|
*/
|
||||||
private val errors = mutableListOf<Pair<Date, String>>()
|
private val errors = mutableListOf<Pair<Date, String>>()
|
||||||
|
|
||||||
/**
|
|
||||||
* Backup manager
|
|
||||||
*/
|
|
||||||
private lateinit var backupManager: BackupManager
|
private lateinit var backupManager: BackupManager
|
||||||
|
private lateinit var notifier: BackupNotifier
|
||||||
|
|
||||||
/**
|
|
||||||
* Database
|
|
||||||
*/
|
|
||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
lateinit var executor: ExecutorService
|
private val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called when the service is created. It injects dependencies and acquire the wake lock.
|
|
||||||
*/
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
notifier = BackupNotifier(this)
|
||||||
wakeLock.acquire()
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
executor = Executors.newSingleThreadExecutor()
|
|
||||||
|
startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopService(name: Intent?): Boolean {
|
||||||
|
destroyJob()
|
||||||
|
return super.stopService(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called when the service is destroyed. It destroys the running subscription and
|
|
||||||
* releases the wake lock.
|
|
||||||
*/
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
subscription?.unsubscribe()
|
destroyJob()
|
||||||
executor.shutdown() // must be called after unsubscribe
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun destroyJob() {
|
||||||
|
job?.cancel()
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
}
|
}
|
||||||
super.onDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method needs to be implemented, but it's not used/needed.
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
*/
|
*/
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when the service receives an intent.
|
* Method called when the service receives an intent.
|
||||||
@ -155,104 +175,303 @@ class BackupRestoreService : Service() {
|
|||||||
* @return the start value of the command.
|
* @return the start value of the command.
|
||||||
*/
|
*/
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent == null) return Service.START_NOT_STICKY
|
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||||
|
|
||||||
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
throttleManager.resetThrottle()
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed.
|
// Cancel any previous job if needed.
|
||||||
subscription?.unsubscribe()
|
job?.cancel()
|
||||||
|
val handler = CoroutineExceptionHandler { _, exception ->
|
||||||
|
Timber.e(exception)
|
||||||
|
writeErrorLog()
|
||||||
|
|
||||||
subscription = Observable.using(
|
notifier.showRestoreError(exception.message)
|
||||||
{ db.lowLevel().beginTransaction() },
|
|
||||||
{ getRestoreObservable(uri).doOnNext{ db.lowLevel().setTransactionSuccessful() } },
|
|
||||||
{ executor.execute { db.lowLevel().endTransaction() } })
|
|
||||||
.doAfterTerminate { stopSelf(startId) }
|
|
||||||
.subscribeOn(Schedulers.from(executor))
|
|
||||||
.subscribe()
|
|
||||||
|
|
||||||
return Service.START_NOT_STICKY
|
stopSelf(startId)
|
||||||
|
}
|
||||||
|
job = GlobalScope.launch(handler) {
|
||||||
|
if (!restoreBackup(uri)) {
|
||||||
|
notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
job?.invokeOnCompletion {
|
||||||
|
stopSelf(startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an [Observable] containing restore process.
|
* Restores data from backup file.
|
||||||
*
|
*
|
||||||
* @param uri restore file
|
* @param uri backup file to restore
|
||||||
* @return [Observable<Manga>]
|
|
||||||
*/
|
*/
|
||||||
private fun getRestoreObservable(uri: Uri): Observable<List<Manga>> {
|
private fun restoreBackup(uri: Uri): Boolean {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
return Observable.just(Unit)
|
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
.map {
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
val reader = JsonReader(contentResolver.openInputStream(uri).bufferedReader())
|
|
||||||
val json = JsonParser().parse(reader).asJsonObject
|
|
||||||
|
|
||||||
// Get parser version
|
// Get parser version
|
||||||
val version = json.get(VERSION)?.asInt ?: 1
|
val version = json.get(VERSION)?.asInt ?: 1
|
||||||
|
|
||||||
// Initialize manager
|
// Initialize manager
|
||||||
backupManager = BackupManager(this, version)
|
backupManager = BackupManager(this, version)
|
||||||
|
|
||||||
val mangasJson = json.get(MANGAS).asJsonArray
|
val mangasJson = json.get(MANGAS).asJsonArray
|
||||||
|
|
||||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
val validManga = mangasJson.filter {
|
||||||
restoreProgress = 0
|
var manga = backupManager.parser.fromJson<MangaImpl>(it.asJsonObject.get(MANGA))
|
||||||
errors.clear()
|
// EXH -->
|
||||||
|
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||||
|
val sourced = backupManager.sourceManager.get(manga.source) != null
|
||||||
|
if (!sourced) {
|
||||||
|
restoreAmount -= 1
|
||||||
|
}
|
||||||
|
sourced
|
||||||
|
}
|
||||||
|
|
||||||
// Restore categories
|
totalAmount = mangasJson.size()
|
||||||
json.get(CATEGORIES)?.let {
|
restoreAmount = validManga.count() + 1 // +1 for categories
|
||||||
backupManager.restoreCategories(it.asJsonArray)
|
skippedAmount = mangasJson.size() - validManga.count()
|
||||||
restoreProgress += 1
|
restoreProgress = 0
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, "Categories added", errors.size)
|
errors.clear()
|
||||||
}
|
|
||||||
|
|
||||||
mangasJson
|
// Restore categories
|
||||||
|
restoreCategories(json.get(CATEGORIES))
|
||||||
|
|
||||||
|
// Store source mapping for error messages
|
||||||
|
sourceMapping = BackupRestoreValidator.getSourceMapping(json)
|
||||||
|
|
||||||
|
// Restore individual manga
|
||||||
|
mangasJson.forEach {
|
||||||
|
if (job?.isActive != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreManga(it.asJsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
val endTime = System.currentTimeMillis()
|
||||||
|
val time = endTime - startTime
|
||||||
|
|
||||||
|
val logFile = writeErrorLog()
|
||||||
|
|
||||||
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreCategories(categoriesJson: JsonElement) {
|
||||||
|
db.inTransaction {
|
||||||
|
backupManager.restoreCategories(categoriesJson.asJsonArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreProgress += 1
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreManga(mangaJson: JsonObject) {
|
||||||
|
var manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||||
|
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||||
|
mangaJson.get(CHAPTERS)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
val categories = backupManager.parser.fromJson<List<String>>(
|
||||||
|
mangaJson.get(CATEGORIES)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
val history = backupManager.parser.fromJson<List<DHistory>>(
|
||||||
|
mangaJson.get(HISTORY)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
|
||||||
|
mangaJson.get(TRACK)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
// EXH -->
|
||||||
|
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||||
|
// <-- EXH
|
||||||
|
|
||||||
|
try {
|
||||||
|
val source = backupManager.sourceManager.get(manga.source)
|
||||||
|
if (source != null) {
|
||||||
|
restoreMangaData(manga, source, chapters, categories, history, tracks)
|
||||||
|
} else {
|
||||||
|
val message = if (manga.source in sourceMapping) {
|
||||||
|
getString(R.string.source_not_found_name, sourceMapping[manga.source])
|
||||||
|
} else {
|
||||||
|
getString(R.string.source_not_found)
|
||||||
}
|
}
|
||||||
.flatMap { Observable.from(it) }
|
|
||||||
.concatMap {
|
|
||||||
val obj = it.asJsonObject
|
|
||||||
val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA))
|
|
||||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS) ?: JsonArray())
|
|
||||||
val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES) ?: JsonArray())
|
|
||||||
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY) ?: JsonArray())
|
|
||||||
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK) ?: JsonArray())
|
|
||||||
|
|
||||||
val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks)
|
errors.add(Date() to "${manga.title} - $message")
|
||||||
if (observable != null) {
|
}
|
||||||
observable
|
} catch (e: Exception) {
|
||||||
} else {
|
errors.add(Date() to "${manga.title} - ${e.message}")
|
||||||
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found)}")
|
}
|
||||||
restoreProgress += 1
|
|
||||||
val content = getString(R.string.dialog_restoring_source_not_found, manga.title.chop(15))
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, errors.size, content)
|
|
||||||
Observable.just(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
.doOnNext {
|
|
||||||
val endTime = System.currentTimeMillis()
|
|
||||||
val time = endTime - startTime
|
|
||||||
val logFile = writeErrorLog()
|
|
||||||
val completeIntent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_TIME, time)
|
|
||||||
putExtra(BackupConst.EXTRA_ERRORS, errors.size)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_FILE_PATH, logFile.parent)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_FILE, logFile.name)
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_RESTORE_COMPLETED_DIALOG)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(completeIntent)
|
|
||||||
|
|
||||||
|
restoreProgress += 1
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a manga restore observable
|
||||||
|
*
|
||||||
|
* @param manga manga data from json
|
||||||
|
* @param source source to get manga data from
|
||||||
|
* @param chapters chapters data from json
|
||||||
|
* @param categories categories data from json
|
||||||
|
* @param history history data from json
|
||||||
|
* @param tracks tracking data from json
|
||||||
|
*/
|
||||||
|
private fun restoreMangaData(
|
||||||
|
manga: Manga,
|
||||||
|
source: Source,
|
||||||
|
chapters: List<Chapter>,
|
||||||
|
categories: List<String>,
|
||||||
|
history: List<DHistory>,
|
||||||
|
tracks: List<Track>
|
||||||
|
) {
|
||||||
|
val dbManga = backupManager.getMangaFromDatabase(manga)
|
||||||
|
|
||||||
|
db.inTransaction {
|
||||||
|
if (dbManga == null) {
|
||||||
|
// Manga not in database
|
||||||
|
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
|
||||||
|
} else { // Manga in database
|
||||||
|
// Copy information from manga already in database
|
||||||
|
backupManager.restoreMangaNoFetch(manga, dbManga)
|
||||||
|
// Fetch rest of manga information
|
||||||
|
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Observable] that fetches manga information
|
||||||
|
*
|
||||||
|
* @param manga manga that needs updating
|
||||||
|
* @param chapters chapters of manga that needs updating
|
||||||
|
* @param categories categories that need updating
|
||||||
|
*/
|
||||||
|
private fun restoreMangaFetch(
|
||||||
|
source: Source,
|
||||||
|
manga: Manga,
|
||||||
|
chapters: List<Chapter>,
|
||||||
|
categories: List<String>,
|
||||||
|
history: List<DHistory>,
|
||||||
|
tracks: List<Track>
|
||||||
|
) {
|
||||||
|
backupManager.restoreMangaFetchObservable(source, manga)
|
||||||
|
.onErrorReturn {
|
||||||
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
|
manga
|
||||||
|
}
|
||||||
|
.filter { it.id != null }
|
||||||
|
.flatMap {
|
||||||
|
chapterFetchObservable(source, it, chapters)
|
||||||
|
// Convert to the manga that contains new chapters.
|
||||||
|
.map { manga }
|
||||||
|
}
|
||||||
|
.doOnNext {
|
||||||
|
restoreExtraForManga(it, categories, history, tracks)
|
||||||
|
}
|
||||||
|
.flatMap {
|
||||||
|
trackingFetchObservable(it, tracks)
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreMangaNoFetch(
|
||||||
|
source: Source,
|
||||||
|
backupManga: Manga,
|
||||||
|
chapters: List<Chapter>,
|
||||||
|
categories: List<String>,
|
||||||
|
history: List<DHistory>,
|
||||||
|
tracks: List<Track>
|
||||||
|
) {
|
||||||
|
Observable.just(backupManga)
|
||||||
|
.flatMap { manga ->
|
||||||
|
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
|
||||||
|
chapterFetchObservable(source, manga, chapters)
|
||||||
|
.map { manga }
|
||||||
|
} else {
|
||||||
|
Observable.just(manga)
|
||||||
}
|
}
|
||||||
.doOnError { error ->
|
}
|
||||||
Timber.e(error)
|
.doOnNext {
|
||||||
writeErrorLog()
|
restoreExtraForManga(it, categories, history, tracks)
|
||||||
val errorIntent = Intent(BackupConst.INTENT_FILTER).apply {
|
}
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_RESTORE_DIALOG)
|
.flatMap { manga ->
|
||||||
putExtra(BackupConst.EXTRA_ERROR_MESSAGE, error.message)
|
trackingFetchObservable(manga, tracks)
|
||||||
}
|
}
|
||||||
sendLocalBroadcast(errorIntent)
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
|
||||||
|
// Restore categories
|
||||||
|
backupManager.restoreCategoriesForManga(manga, categories)
|
||||||
|
|
||||||
|
// Restore history
|
||||||
|
backupManager.restoreHistoryForManga(history)
|
||||||
|
|
||||||
|
// Restore tracking
|
||||||
|
backupManager.restoreTrackForManga(manga, tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Observable] that fetches chapter information
|
||||||
|
*
|
||||||
|
* @param source source of manga
|
||||||
|
* @param manga manga that needs updating
|
||||||
|
* @return [Observable] that contains manga
|
||||||
|
*/
|
||||||
|
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
|
return backupManager.restoreChapterFetchObservable(source, manga, chapters, throttleManager)
|
||||||
|
// If there's any error, return empty update and continue.
|
||||||
|
.onErrorReturn {
|
||||||
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
|
Pair(emptyList(), emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Observable] that refreshes tracking information
|
||||||
|
* @param manga manga that needs updating.
|
||||||
|
* @param tracks list containing tracks from restore file.
|
||||||
|
* @return [Observable] that contains updated track item
|
||||||
|
*/
|
||||||
|
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
|
||||||
|
return Observable.from(tracks)
|
||||||
|
.flatMap { track ->
|
||||||
|
val service = trackManager.getService(track.sync_id)
|
||||||
|
if (service != null && service.isLogged) {
|
||||||
|
service.refresh(track)
|
||||||
|
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
||||||
|
.onErrorReturn {
|
||||||
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
|
track
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.add(Date() to "${manga.title} - ${getString(R.string.tracker_not_logged_in, service?.name)}")
|
||||||
|
Observable.empty()
|
||||||
}
|
}
|
||||||
.onErrorReturn { emptyList() }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to update dialog in [BackupConst]
|
||||||
|
*
|
||||||
|
* @param progress restore progress
|
||||||
|
* @param amount total restoreAmount of manga
|
||||||
|
* @param title title of restored manga
|
||||||
|
*/
|
||||||
|
private fun showRestoreProgress(
|
||||||
|
progress: Int,
|
||||||
|
amount: Int,
|
||||||
|
title: String
|
||||||
|
) {
|
||||||
|
notifier.showRestoreProgress(title, progress, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -261,7 +480,7 @@ class BackupRestoreService : Service() {
|
|||||||
private fun writeErrorLog(): File {
|
private fun writeErrorLog(): File {
|
||||||
try {
|
try {
|
||||||
if (errors.isNotEmpty()) {
|
if (errors.isNotEmpty()) {
|
||||||
val destFile = File(externalCacheDir, "tachiyomi_restore.log")
|
val destFile = File(externalCacheDir, "tachiyomi_restore.txt")
|
||||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||||
|
|
||||||
destFile.bufferedWriter().use { out ->
|
destFile.bufferedWriter().use { out ->
|
||||||
@ -276,135 +495,4 @@ class BackupRestoreService : Service() {
|
|||||||
}
|
}
|
||||||
return File("")
|
return File("")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Returns a manga restore observable
|
|
||||||
*
|
|
||||||
* @param manga manga data from json
|
|
||||||
* @param chapters chapters data from json
|
|
||||||
* @param categories categories data from json
|
|
||||||
* @param history history data from json
|
|
||||||
* @param tracks tracking data from json
|
|
||||||
* @return [Observable] containing manga restore information
|
|
||||||
*/
|
|
||||||
private fun getMangaRestoreObservable(manga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga>? {
|
|
||||||
// Get source
|
|
||||||
val source = backupManager.sourceManager.get(manga.source) ?: return null
|
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
|
||||||
|
|
||||||
if (dbManga == null) {
|
|
||||||
// Manga not in database
|
|
||||||
return mangaFetchObservable(source, manga, chapters, categories, history, tracks)
|
|
||||||
} else { // Manga in database
|
|
||||||
// Copy information from manga already in database
|
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
|
||||||
// Fetch rest of manga information
|
|
||||||
return mangaNoFetchObservable(source, manga, chapters, categories, history, tracks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Observable] that fetches manga information
|
|
||||||
*
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @param chapters chapters of manga that needs updating
|
|
||||||
* @param categories categories that need updating
|
|
||||||
*/
|
|
||||||
private fun mangaFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga> {
|
|
||||||
return backupManager.restoreMangaFetchObservable(source, manga)
|
|
||||||
.onErrorReturn {
|
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
.filter { it.id != null }
|
|
||||||
.flatMap {
|
|
||||||
chapterFetchObservable(source, it, chapters)
|
|
||||||
// Convert to the manga that contains new chapters.
|
|
||||||
.map { manga }
|
|
||||||
}
|
|
||||||
.doOnNext {
|
|
||||||
// Restore categories
|
|
||||||
backupManager.restoreCategoriesForManga(it, categories)
|
|
||||||
|
|
||||||
// Restore history
|
|
||||||
backupManager.restoreHistoryForManga(history)
|
|
||||||
|
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(it, tracks)
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, errors.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mangaNoFetchObservable(source: Source, backupManga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga> {
|
|
||||||
|
|
||||||
return Observable.just(backupManga)
|
|
||||||
.flatMap { manga ->
|
|
||||||
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
|
|
||||||
chapterFetchObservable(source, manga, chapters)
|
|
||||||
.map { manga }
|
|
||||||
} else {
|
|
||||||
Observable.just(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.doOnNext {
|
|
||||||
// Restore categories
|
|
||||||
backupManager.restoreCategoriesForManga(it, categories)
|
|
||||||
|
|
||||||
// Restore history
|
|
||||||
backupManager.restoreHistoryForManga(history)
|
|
||||||
|
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(it, tracks)
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, backupManga.title, errors.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Observable] that fetches chapter information
|
|
||||||
*
|
|
||||||
* @param source source of manga
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @return [Observable] that contains manga
|
|
||||||
*/
|
|
||||||
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
|
||||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
|
||||||
// If there's any error, return empty update and continue.
|
|
||||||
.onErrorReturn {
|
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
|
||||||
Pair(emptyList<Chapter>(), emptyList<Chapter>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to update dialog in [BackupConst]
|
|
||||||
*
|
|
||||||
* @param progress restore progress
|
|
||||||
* @param amount total restoreAmount of manga
|
|
||||||
* @param title title of restored manga
|
|
||||||
*/
|
|
||||||
private fun showRestoreProgress(progress: Int, amount: Int, title: String, errors: Int,
|
|
||||||
content: String = getString(R.string.dialog_restoring_backup, title.chop(15))) {
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_PROGRESS, progress)
|
|
||||||
putExtra(BackupConst.EXTRA_AMOUNT, amount)
|
|
||||||
putExtra(BackupConst.EXTRA_CONTENT, content)
|
|
||||||
putExtra(BackupConst.EXTRA_ERRORS, errors)
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_SET_PROGRESS_DIALOG)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
|
|
||||||
|
object BackupRestoreValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for critical backup file data.
|
||||||
|
*
|
||||||
|
* @throws Exception if version or manga cannot be found.
|
||||||
|
* @return List of required sources.
|
||||||
|
*/
|
||||||
|
fun validate(context: Context, uri: Uri): Map<Long, String> {
|
||||||
|
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
|
val version = json.get(Backup.VERSION)
|
||||||
|
val mangasJson = json.get(Backup.MANGAS)
|
||||||
|
if (version == null || mangasJson == null) {
|
||||||
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangasJson.asJsonArray.size() == 0) {
|
||||||
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSourceMapping(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||||
|
val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap()
|
||||||
|
|
||||||
|
return extensionsMapping.asJsonArray
|
||||||
|
.map {
|
||||||
|
val items = it.asString.split(":")
|
||||||
|
items[0].toLong() to items[1]
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,25 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.models
|
package eu.kanade.tachiyomi.data.backup.models
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
/**
|
|
||||||
* Json values
|
/**
|
||||||
*/
|
* Json values
|
||||||
object Backup {
|
*/
|
||||||
const val CURRENT_VERSION = 2
|
object Backup {
|
||||||
const val MANGA = "manga"
|
const val CURRENT_VERSION = 2
|
||||||
const val MANGAS = "mangas"
|
const val MANGA = "manga"
|
||||||
const val TRACK = "track"
|
const val MANGAS = "mangas"
|
||||||
const val CHAPTERS = "chapters"
|
const val TRACK = "track"
|
||||||
const val CATEGORIES = "categories"
|
const val CHAPTERS = "chapters"
|
||||||
const val HISTORY = "history"
|
const val CATEGORIES = "categories"
|
||||||
const val VERSION = "version"
|
const val EXTENSIONS = "extensions"
|
||||||
|
const val HISTORY = "history"
|
||||||
fun getDefaultFilename(): String {
|
const val VERSION = "version"
|
||||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
|
||||||
return "tachiyomi_$date.json"
|
fun getDefaultFilename(): String {
|
||||||
}
|
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
||||||
}
|
return "tachiyomi_$date.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.models
|
package eu.kanade.tachiyomi.data.backup.models
|
||||||
|
|
||||||
data class DHistory(val url: String,val lastRead: Long)
|
data class DHistory(val url: String, val lastRead: Long)
|
||||||
|
@ -28,4 +28,4 @@ object CategoryTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,7 @@ object ChapterTypeAdapter {
|
|||||||
beginObject()
|
beginObject()
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
if (peek() == JsonToken.NAME) {
|
if (peek() == JsonToken.NAME) {
|
||||||
val name = nextName()
|
when (nextName()) {
|
||||||
|
|
||||||
when (name) {
|
|
||||||
URL -> chapter.url = nextString()
|
URL -> chapter.url = nextString()
|
||||||
READ -> chapter.read = nextInt() == 1
|
READ -> chapter.read = nextInt() == 1
|
||||||
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
||||||
@ -58,4 +56,4 @@ object ChapterTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,4 @@ object HistoryTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,4 @@ object MangaTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,11 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|||||||
object TrackTypeAdapter {
|
object TrackTypeAdapter {
|
||||||
|
|
||||||
private const val SYNC = "s"
|
private const val SYNC = "s"
|
||||||
private const val REMOTE = "r"
|
private const val MEDIA = "r"
|
||||||
|
private const val LIBRARY = "ml"
|
||||||
private const val TITLE = "t"
|
private const val TITLE = "t"
|
||||||
private const val LAST_READ = "l"
|
private const val LAST_READ = "l"
|
||||||
|
private const val TRACKING_URL = "u"
|
||||||
|
|
||||||
fun build(): TypeAdapter<TrackImpl> {
|
fun build(): TypeAdapter<TrackImpl> {
|
||||||
return typeAdapter {
|
return typeAdapter {
|
||||||
@ -23,10 +25,14 @@ object TrackTypeAdapter {
|
|||||||
value(it.title)
|
value(it.title)
|
||||||
name(SYNC)
|
name(SYNC)
|
||||||
value(it.sync_id)
|
value(it.sync_id)
|
||||||
name(REMOTE)
|
name(MEDIA)
|
||||||
value(it.remote_id)
|
value(it.media_id)
|
||||||
|
name(LIBRARY)
|
||||||
|
value(it.library_id)
|
||||||
name(LAST_READ)
|
name(LAST_READ)
|
||||||
value(it.last_chapter_read)
|
value(it.last_chapter_read)
|
||||||
|
name(TRACKING_URL)
|
||||||
|
value(it.tracking_url)
|
||||||
endObject()
|
endObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,13 +41,13 @@ object TrackTypeAdapter {
|
|||||||
beginObject()
|
beginObject()
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
if (peek() == JsonToken.NAME) {
|
if (peek() == JsonToken.NAME) {
|
||||||
val name = nextName()
|
when (nextName()) {
|
||||||
|
|
||||||
when (name) {
|
|
||||||
TITLE -> track.title = nextString()
|
TITLE -> track.title = nextString()
|
||||||
SYNC -> track.sync_id = nextInt()
|
SYNC -> track.sync_id = nextInt()
|
||||||
REMOTE -> track.remote_id = nextInt()
|
MEDIA -> track.media_id = nextInt()
|
||||||
|
LIBRARY -> track.library_id = nextLong()
|
||||||
LAST_READ -> track.last_chapter_read = nextInt()
|
LAST_READ -> track.last_chapter_read = nextInt()
|
||||||
|
TRACKING_URL -> track.tracking_url = nextString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,4 +56,4 @@ object TrackTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
vendored
Normal file → Executable file
@ -6,15 +6,22 @@ import com.github.salomonbrys.kotson.fromJson
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import okhttp3.Response
|
|
||||||
import okio.Okio
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to create chapter cache
|
* Class used to create chapter cache
|
||||||
@ -36,19 +43,32 @@ class ChapterCache(private val context: Context) {
|
|||||||
|
|
||||||
/** The number of values per cache entry. Must be positive. */
|
/** The number of values per cache entry. Must be positive. */
|
||||||
const val PARAMETER_VALUE_COUNT = 1
|
const val PARAMETER_VALUE_COUNT = 1
|
||||||
|
|
||||||
/** The maximum number of bytes this cache should use to store. */
|
|
||||||
const val PARAMETER_CACHE_SIZE = 75L * 1024 * 1024
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
/** Google Json class used for parsing JSON files. */
|
/** Google Json class used for parsing JSON files. */
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
|
// --> EH
|
||||||
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
// <-- EH
|
||||||
|
|
||||||
/** Cache class used for cache management. */
|
/** Cache class used for cache management. */
|
||||||
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
// --> EH
|
||||||
PARAMETER_APP_VERSION,
|
private var diskCache = setupDiskCache(prefs.eh_cacheSize().get().toLong())
|
||||||
PARAMETER_VALUE_COUNT,
|
|
||||||
PARAMETER_CACHE_SIZE)
|
init {
|
||||||
|
prefs.eh_cacheSize().asFlow()
|
||||||
|
.onEach {
|
||||||
|
// Save old cache for destruction later
|
||||||
|
val oldCache = diskCache
|
||||||
|
diskCache = setupDiskCache(it.toLong())
|
||||||
|
oldCache.close()
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
// <-- EH
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns directory of cache.
|
* Returns directory of cache.
|
||||||
@ -68,6 +88,18 @@ class ChapterCache(private val context: Context) {
|
|||||||
val readableSize: String
|
val readableSize: String
|
||||||
get() = Formatter.formatFileSize(context, realSize)
|
get() = Formatter.formatFileSize(context, realSize)
|
||||||
|
|
||||||
|
// --> EH
|
||||||
|
// Cache size is in MB
|
||||||
|
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||||
|
return DiskLruCache.open(
|
||||||
|
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||||
|
PARAMETER_APP_VERSION,
|
||||||
|
PARAMETER_VALUE_COUNT,
|
||||||
|
cacheSize * 1024 * 1024
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// <-- EH
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove file from cache.
|
* Remove file from cache.
|
||||||
*
|
*
|
||||||
@ -76,16 +108,17 @@ class ChapterCache(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun removeFileFromCache(file: String): Boolean {
|
fun removeFileFromCache(file: String): Boolean {
|
||||||
// Make sure we don't delete the journal file (keeps track of cache).
|
// Make sure we don't delete the journal file (keeps track of cache).
|
||||||
if (file == "journal" || file.startsWith("journal."))
|
if (file == "journal" || file.startsWith("journal.")) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
return try {
|
||||||
// Remove the extension from the file to get the key of the cache
|
// Remove the extension from the file to get the key of the cache
|
||||||
val key = file.substringBeforeLast(".")
|
val key = file.substringBeforeLast(".")
|
||||||
// Remove file from cache.
|
// Remove file from cache.
|
||||||
return diskCache.remove(key)
|
diskCache.remove(key)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +159,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
editor = diskCache.edit(key) ?: return
|
editor = diskCache.edit(key) ?: return
|
||||||
|
|
||||||
// Write chapter urls to cache.
|
// Write chapter urls to cache.
|
||||||
Okio.buffer(Okio.sink(editor.newOutputStream(0))).use {
|
editor.newOutputStream(0).sink().buffer().use {
|
||||||
it.write(cachedValue.toByteArray())
|
it.write(cachedValue.toByteArray())
|
||||||
it.flush()
|
it.flush()
|
||||||
}
|
}
|
||||||
@ -134,7 +167,6 @@ class ChapterCache(private val context: Context) {
|
|||||||
diskCache.flush()
|
diskCache.flush()
|
||||||
editor.commit()
|
editor.commit()
|
||||||
editor.abortUnlessCommitted()
|
editor.abortUnlessCommitted()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
} finally {
|
} finally {
|
||||||
@ -149,10 +181,10 @@ class ChapterCache(private val context: Context) {
|
|||||||
* @return true if in cache otherwise false.
|
* @return true if in cache otherwise false.
|
||||||
*/
|
*/
|
||||||
fun isImageInCache(imageUrl: String): Boolean {
|
fun isImageInCache(imageUrl: String): Boolean {
|
||||||
try {
|
return try {
|
||||||
return diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)) != null
|
diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)) != null
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +202,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add image to cache.
|
* Add image to cache.
|
||||||
*
|
*
|
||||||
* @param imageUrl url of image.
|
* @param imageUrl url of image.
|
||||||
* @param response http response from page.
|
* @param response http response from page.
|
||||||
* @throws IOException image error.
|
* @throws IOException image error.
|
||||||
@ -186,12 +218,12 @@ class ChapterCache(private val context: Context) {
|
|||||||
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
||||||
|
|
||||||
// Get OutputStream and write image with Okio.
|
// Get OutputStream and write image with Okio.
|
||||||
response.body()!!.source().saveTo(editor.newOutputStream(0))
|
response.body!!.source().saveTo(editor.newOutputStream(0))
|
||||||
|
|
||||||
diskCache.flush()
|
diskCache.flush()
|
||||||
editor.commit()
|
editor.commit()
|
||||||
} finally {
|
} finally {
|
||||||
response.body()?.close()
|
response.body?.close()
|
||||||
editor?.abortUnlessCommitted()
|
editor?.abortUnlessCommitted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,4 +232,3 @@ class ChapterCache(private val context: Context) {
|
|||||||
return "${chapter.manga_id}${chapter.url}"
|
return "${chapter.manga_id}${chapter.url}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Normal file → Executable file
@ -1,7 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -17,51 +18,89 @@ import java.io.InputStream
|
|||||||
*/
|
*/
|
||||||
class CoverCache(private val context: Context) {
|
class CoverCache(private val context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val COVERS_DIR = "covers"
|
||||||
|
private const val CUSTOM_COVERS_DIR = "covers/custom"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache directory used for cache management.
|
* Cache directory used for cache management.
|
||||||
*/
|
*/
|
||||||
private val cacheDir = context.getExternalFilesDir("covers") ?:
|
private val cacheDir = getCacheDir(COVERS_DIR)
|
||||||
File(context.filesDir, "covers").also { it.mkdirs() }
|
|
||||||
|
private val customCoverCacheDir = getCacheDir(CUSTOM_COVERS_DIR)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cover from cache.
|
* Returns the cover from cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl the thumbnail url.
|
* @param manga the manga.
|
||||||
* @return cover image.
|
* @return cover image.
|
||||||
*/
|
*/
|
||||||
fun getCoverFile(thumbnailUrl: String): File {
|
fun getCoverFile(manga: Manga): File? {
|
||||||
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
return manga.thumbnail_url?.let {
|
||||||
|
File(cacheDir, DiskUtil.hashKeyForDisk(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy the given stream to this cache.
|
* Returns the custom cover from cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl url of the thumbnail.
|
* @param manga the manga.
|
||||||
* @param inputStream the stream to copy.
|
* @return cover image.
|
||||||
|
*/
|
||||||
|
fun getCustomCoverFile(manga: Manga): File {
|
||||||
|
return File(customCoverCacheDir, DiskUtil.hashKeyForDisk(manga.id.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given stream as the manga's custom cover to cache.
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @param inputStream the stream to copy.
|
||||||
* @throws IOException if there's any error.
|
* @throws IOException if there's any error.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun copyToCache(thumbnailUrl: String, inputStream: InputStream) {
|
fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) {
|
||||||
// Get destination file.
|
getCustomCoverFile(manga).outputStream().use {
|
||||||
val destFile = getCoverFile(thumbnailUrl)
|
inputStream.copyTo(it)
|
||||||
|
}
|
||||||
destFile.outputStream().use { inputStream.copyTo(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the cover file from the cache.
|
* Delete the cover files of the manga from the cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl the thumbnail url.
|
* @param manga the manga.
|
||||||
* @return status of deletion.
|
* @param deleteCustomCover whether the custom cover should be deleted.
|
||||||
|
* @return number of files that were deleted.
|
||||||
*/
|
*/
|
||||||
fun deleteFromCache(thumbnailUrl: String?): Boolean {
|
fun deleteFromCache(manga: Manga, deleteCustomCover: Boolean = false): Int {
|
||||||
// Check if url is empty.
|
var deleted = 0
|
||||||
if (thumbnailUrl.isNullOrEmpty())
|
|
||||||
return false
|
|
||||||
|
|
||||||
// Remove file.
|
getCoverFile(manga)?.let {
|
||||||
val file = getCoverFile(thumbnailUrl!!)
|
if (it.exists() && it.delete()) ++deleted
|
||||||
return file.exists() && file.delete()
|
}
|
||||||
|
|
||||||
|
if (deleteCustomCover) {
|
||||||
|
if (deleteCustomCover(manga)) ++deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete custom cover of the manga from the cache
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @return whether the cover was deleted.
|
||||||
|
*/
|
||||||
|
fun deleteCustomCover(manga: Manga): Boolean {
|
||||||
|
return getCustomCoverFile(manga).let {
|
||||||
|
it.exists() && it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCacheDir(dir: String): File {
|
||||||
|
return context.getExternalFilesDir(dir)
|
||||||
|
?: File(context.filesDir, dir).also { it.mkdirs() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
63
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
Normal file → Executable file
@ -1,29 +1,64 @@
|
|||||||
package eu.kanade.tachiyomi.data.database
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||||
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.*
|
import eu.kanade.tachiyomi.data.database.mappers.CategoryTypeMapping
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.mappers.ChapterTypeMapping
|
||||||
import eu.kanade.tachiyomi.data.database.queries.*
|
import eu.kanade.tachiyomi.data.database.mappers.HistoryTypeMapping
|
||||||
|
import eu.kanade.tachiyomi.data.database.mappers.MangaCategoryTypeMapping
|
||||||
|
import eu.kanade.tachiyomi.data.database.mappers.MangaTypeMapping
|
||||||
|
import eu.kanade.tachiyomi.data.database.mappers.TrackTypeMapping
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.CategoryQueries
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.ChapterQueries
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||||
|
import exh.metadata.sql.mappers.SearchMetadataTypeMapping
|
||||||
|
import exh.metadata.sql.mappers.SearchTagTypeMapping
|
||||||
|
import exh.metadata.sql.mappers.SearchTitleTypeMapping
|
||||||
|
import exh.metadata.sql.models.SearchMetadata
|
||||||
|
import exh.metadata.sql.models.SearchTag
|
||||||
|
import exh.metadata.sql.models.SearchTitle
|
||||||
|
import exh.metadata.sql.queries.SearchMetadataQueries
|
||||||
|
import exh.metadata.sql.queries.SearchTagQueries
|
||||||
|
import exh.metadata.sql.queries.SearchTitleQueries
|
||||||
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides operations to manage the database through its interfaces.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context)
|
open class DatabaseHelper(context: Context) :
|
||||||
: MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
|
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries, /* EXH --> */ SearchMetadataQueries, SearchTagQueries, SearchTitleQueries /* EXH <-- */ {
|
||||||
|
|
||||||
|
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||||
|
.name(DbOpenCallback.DATABASE_NAME)
|
||||||
|
.callback(DbOpenCallback())
|
||||||
|
.build()
|
||||||
|
|
||||||
override val db = DefaultStorIOSQLite.builder()
|
override val db = DefaultStorIOSQLite.builder()
|
||||||
.sqliteOpenHelper(DbOpenHelper(context))
|
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
||||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||||
.build()
|
// EXH -->
|
||||||
|
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||||
|
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||||
|
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||||
|
// EXH <--
|
||||||
|
.build()
|
||||||
|
|
||||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||||
|
|
||||||
fun lowLevel() = db.lowLevel()
|
fun lowLevel() = db.lowLevel()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1
app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
Normal file → Executable file
@ -22,4 +22,3 @@ inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
|
|||||||
lowLevel().endTransaction()
|
lowLevel().endTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Executable file
@ -0,0 +1,74 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MergedTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||||
|
import exh.metadata.sql.tables.SearchMetadataTable
|
||||||
|
import exh.metadata.sql.tables.SearchTagTable
|
||||||
|
import exh.metadata.sql.tables.SearchTitleTable
|
||||||
|
|
||||||
|
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Name of the database file.
|
||||||
|
*/
|
||||||
|
const val DATABASE_NAME = "tachiyomi.db"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of the database.
|
||||||
|
*/
|
||||||
|
const val DATABASE_VERSION = 2 // [SY]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||||
|
execSQL(MangaTable.createTableQuery)
|
||||||
|
execSQL(ChapterTable.createTableQuery)
|
||||||
|
execSQL(TrackTable.createTableQuery)
|
||||||
|
execSQL(CategoryTable.createTableQuery)
|
||||||
|
execSQL(MangaCategoryTable.createTableQuery)
|
||||||
|
execSQL(HistoryTable.createTableQuery)
|
||||||
|
// EXH -->
|
||||||
|
execSQL(SearchMetadataTable.createTableQuery)
|
||||||
|
execSQL(SearchTagTable.createTableQuery)
|
||||||
|
execSQL(SearchTitleTable.createTableQuery)
|
||||||
|
// EXH <--
|
||||||
|
// AZ -->
|
||||||
|
execSQL(MergedTable.createTableQuery)
|
||||||
|
// AZ <--
|
||||||
|
|
||||||
|
// DB indexes
|
||||||
|
execSQL(MangaTable.createUrlIndexQuery)
|
||||||
|
execSQL(MangaTable.createLibraryIndexQuery)
|
||||||
|
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||||
|
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||||
|
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||||
|
// EXH -->
|
||||||
|
db.execSQL(SearchMetadataTable.createUploaderIndexQuery)
|
||||||
|
db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
|
||||||
|
db.execSQL(SearchTagTable.createMangaIdIndexQuery)
|
||||||
|
db.execSQL(SearchTagTable.createNamespaceNameIndexQuery)
|
||||||
|
db.execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||||
|
db.execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||||
|
// EXH <--
|
||||||
|
// AZ -->
|
||||||
|
execSQL(MergedTable.createIndexQuery)
|
||||||
|
// AZ <--
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
db.execSQL(MangaTable.addCoverLastModified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||||
|
db.setForeignKeyConstraintsEnabled(true)
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.*
|
|
||||||
|
|
||||||
class DbOpenHelper(context: Context)
|
|
||||||
: SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Name of the database file.
|
|
||||||
*/
|
|
||||||
const val DATABASE_NAME = "tachiyomi.db"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of the database.
|
|
||||||
*/
|
|
||||||
const val DATABASE_VERSION = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(db: SQLiteDatabase) = with(db) {
|
|
||||||
execSQL(MangaTable.createTableQuery)
|
|
||||||
execSQL(ChapterTable.createTableQuery)
|
|
||||||
execSQL(TrackTable.createTableQuery)
|
|
||||||
execSQL(CategoryTable.createTableQuery)
|
|
||||||
execSQL(MangaCategoryTable.createTableQuery)
|
|
||||||
execSQL(HistoryTable.createTableQuery)
|
|
||||||
|
|
||||||
// DB indexes
|
|
||||||
execSQL(MangaTable.createUrlIndexQuery)
|
|
||||||
execSQL(MangaTable.createFavoriteIndexQuery)
|
|
||||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
|
||||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
|
||||||
|
|
||||||
// Fix kissmanga covers after supporting cloudflare
|
|
||||||
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
|
||||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
|
||||||
}
|
|
||||||
if (oldVersion < 3) {
|
|
||||||
// Initialize history tables
|
|
||||||
db.execSQL(HistoryTable.createTableQuery)
|
|
||||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 4) {
|
|
||||||
db.execSQL(ChapterTable.bookmarkUpdateQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 5) {
|
|
||||||
db.execSQL(ChapterTable.addScanlator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigure(db: SQLiteDatabase) {
|
|
||||||
db.setForeignKeyConstraintsEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
3
app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
Normal file → Executable file
@ -5,5 +5,4 @@ import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
|||||||
interface DbProvider {
|
interface DbProvider {
|
||||||
|
|
||||||
val db: DefaultStorIOSQLite
|
val db: DefaultStorIOSQLite
|
||||||
|
}
|
||||||
}
|
|
||||||
|
32
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
Normal file → Executable file
@ -13,33 +13,36 @@ import eu.kanade.tachiyomi.data.database.models.Category
|
|||||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
||||||
|
|
||||||
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
||||||
CategoryPutResolver(),
|
CategoryPutResolver(),
|
||||||
CategoryGetResolver(),
|
CategoryGetResolver(),
|
||||||
CategoryDeleteResolver()
|
CategoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
|
override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
put(COL_NAME, obj.name)
|
put(COL_NAME, obj.name)
|
||||||
put(COL_ORDER, obj.order)
|
put(COL_ORDER, obj.order)
|
||||||
put(COL_FLAGS, obj.flags)
|
put(COL_FLAGS, obj.flags)
|
||||||
|
val orderString = obj.mangaOrder.joinToString("/")
|
||||||
|
put(COL_MANGA_ORDER, orderString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,14 +53,17 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
|
|||||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||||
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
||||||
|
|
||||||
|
val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER))
|
||||||
|
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
|
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
27
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
Normal file → Executable file
@ -26,22 +26,22 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
|
||||||
|
|
||||||
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
||||||
ChapterPutResolver(),
|
ChapterPutResolver(),
|
||||||
ChapterGetResolver(),
|
ChapterGetResolver(),
|
||||||
ChapterDeleteResolver()
|
ChapterDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
|
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -80,9 +80,8 @@ class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
|||||||
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
|
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
Normal file → Executable file
@ -18,22 +18,22 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
||||||
|
|
||||||
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
||||||
HistoryPutResolver(),
|
HistoryPutResolver(),
|
||||||
HistoryGetResolver(),
|
HistoryGetResolver(),
|
||||||
HistoryDeleteResolver()
|
HistoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: History) = ContentValues(4).apply {
|
override fun mapToContentValues(obj: History) = ContentValues(4).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -56,8 +56,8 @@ class HistoryGetResolver : DefaultGetResolver<History>() {
|
|||||||
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
|
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
26
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
Normal file → Executable file
@ -16,22 +16,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
|
||||||
|
|
||||||
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
||||||
MangaCategoryPutResolver(),
|
MangaCategoryPutResolver(),
|
||||||
MangaCategoryGetResolver(),
|
MangaCategoryGetResolver(),
|
||||||
MangaCategoryDeleteResolver()
|
MangaCategoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
|
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -52,8 +52,8 @@ class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
|||||||
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
|
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
29
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Normal file → Executable file
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
||||||
@ -29,22 +30,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
|
||||||
|
|
||||||
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
||||||
MangaPutResolver(),
|
MangaPutResolver(),
|
||||||
MangaGetResolver(),
|
MangaGetResolver(),
|
||||||
MangaDeleteResolver()
|
MangaDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -62,6 +63,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
put(COL_INITIALIZED, obj.initialized)
|
put(COL_INITIALIZED, obj.initialized)
|
||||||
put(COL_VIEWER, obj.viewer)
|
put(COL_VIEWER, obj.viewer)
|
||||||
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
||||||
|
put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +84,7 @@ interface BaseMangaGetResolver {
|
|||||||
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
||||||
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||||
|
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +98,8 @@ open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver
|
|||||||
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
|
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
46
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
Normal file → Executable file
@ -11,45 +11,53 @@ import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_FINISH_DATE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LAST_CHAPTER_READ
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LAST_CHAPTER_READ
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LIBRARY_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MANGA_ID
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MANGA_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_REMOTE_ID
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MEDIA_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SCORE
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SCORE
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_START_DATE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_STATUS
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_STATUS
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SYNC_ID
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SYNC_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TITLE
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TITLE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TOTAL_CHAPTERS
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TOTAL_CHAPTERS
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
||||||
|
|
||||||
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
||||||
TrackPutResolver(),
|
TrackPutResolver(),
|
||||||
TrackGetResolver(),
|
TrackGetResolver(),
|
||||||
TrackDeleteResolver()
|
TrackDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class TrackPutResolver : DefaultPutResolver<Track>() {
|
class TrackPutResolver : DefaultPutResolver<Track>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Track) = ContentValues(9).apply {
|
override fun mapToContentValues(obj: Track) = ContentValues(10).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
put(COL_MANGA_ID, obj.manga_id)
|
put(COL_MANGA_ID, obj.manga_id)
|
||||||
put(COL_SYNC_ID, obj.sync_id)
|
put(COL_SYNC_ID, obj.sync_id)
|
||||||
put(COL_REMOTE_ID, obj.remote_id)
|
put(COL_MEDIA_ID, obj.media_id)
|
||||||
|
put(COL_LIBRARY_ID, obj.library_id)
|
||||||
put(COL_TITLE, obj.title)
|
put(COL_TITLE, obj.title)
|
||||||
put(COL_LAST_CHAPTER_READ, obj.last_chapter_read)
|
put(COL_LAST_CHAPTER_READ, obj.last_chapter_read)
|
||||||
put(COL_TOTAL_CHAPTERS, obj.total_chapters)
|
put(COL_TOTAL_CHAPTERS, obj.total_chapters)
|
||||||
put(COL_STATUS, obj.status)
|
put(COL_STATUS, obj.status)
|
||||||
|
put(COL_TRACKING_URL, obj.tracking_url)
|
||||||
put(COL_SCORE, obj.score)
|
put(COL_SCORE, obj.score)
|
||||||
|
put(COL_START_DATE, obj.started_reading_date)
|
||||||
|
put(COL_FINISH_DATE, obj.finished_reading_date)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,20 +67,24 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
|
|||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
||||||
remote_id = cursor.getInt(cursor.getColumnIndex(COL_REMOTE_ID))
|
media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID))
|
||||||
|
library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID))
|
||||||
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
||||||
last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ))
|
last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ))
|
||||||
total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS))
|
total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS))
|
||||||
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
||||||
score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE))
|
score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE))
|
||||||
|
tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL))
|
||||||
|
started_reading_date = cursor.getLong(cursor.getColumnIndex(COL_START_DATE))
|
||||||
|
finished_reading_date = cursor.getLong(cursor.getColumnIndex(COL_FINISH_DATE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
|
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Normal file → Executable file
@ -12,6 +12,8 @@ interface Category : Serializable {
|
|||||||
|
|
||||||
var flags: Int
|
var flags: Int
|
||||||
|
|
||||||
|
var mangaOrder: List<Long>
|
||||||
|
|
||||||
val nameLower: String
|
val nameLower: String
|
||||||
get() = name.toLowerCase()
|
get() = name.toLowerCase()
|
||||||
|
|
||||||
@ -23,5 +25,4 @@ interface Category : Serializable {
|
|||||||
|
|
||||||
fun createDefault(): Category = create("Default").apply { id = 0 }
|
fun createDefault(): Category = create("Default").apply { id = 0 }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Normal file → Executable file
@ -10,6 +10,8 @@ class CategoryImpl : Category {
|
|||||||
|
|
||||||
override var flags: Int = 0
|
override var flags: Int = 0
|
||||||
|
|
||||||
|
override var mangaOrder: List<Long> = emptyList()
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
@ -22,5 +24,4 @@ class CategoryImpl : Category {
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return name.hashCode()
|
return name.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
Normal file → Executable file
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Normal file → Executable file
@ -37,5 +37,4 @@ class ChapterImpl : Chapter {
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return url.hashCode()
|
return url.hashCode()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
Normal file → Executable file
@ -35,7 +35,7 @@ interface History : Serializable {
|
|||||||
* @param chapter chapter object
|
* @param chapter chapter object
|
||||||
* @return history object
|
* @return history object
|
||||||
*/
|
*/
|
||||||
fun create(chapter: Chapter): History = HistoryImpl().apply {
|
fun create(chapter: Chapter): History = HistoryImpl().apply {
|
||||||
this.chapter_id = chapter.id!!
|
this.chapter_id = chapter.id!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,4 @@ class LibraryManga : MangaImpl() {
|
|||||||
var unread: Int = 0
|
var unread: Int = 0
|
||||||
|
|
||||||
var category: Int = 0
|
var category: Int = 0
|
||||||
|
}
|
||||||
}
|
|
||||||
|
12
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Normal file → Executable file
@ -16,6 +16,8 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
var chapter_flags: Int
|
var chapter_flags: Int
|
||||||
|
|
||||||
|
var cover_last_modified: Long
|
||||||
|
|
||||||
fun setChapterOrder(order: Int) {
|
fun setChapterOrder(order: Int) {
|
||||||
setFlags(order, SORT_MASK)
|
setFlags(order, SORT_MASK)
|
||||||
}
|
}
|
||||||
@ -28,6 +30,10 @@ interface Manga : SManga {
|
|||||||
return chapter_flags and SORT_MASK == SORT_DESC
|
return chapter_flags and SORT_MASK == SORT_DESC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getGenres(): List<String>? {
|
||||||
|
return genre?.split(", ")?.map { it.trim() }
|
||||||
|
}
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
// Used to display the chapter's title one way or another
|
||||||
var displayMode: Int
|
var displayMode: Int
|
||||||
get() = chapter_flags and DISPLAY_MASK
|
get() = chapter_flags and DISPLAY_MASK
|
||||||
@ -72,7 +78,8 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
const val SORTING_SOURCE = 0x00000000
|
const val SORTING_SOURCE = 0x00000000
|
||||||
const val SORTING_NUMBER = 0x00000100
|
const val SORTING_NUMBER = 0x00000100
|
||||||
const val SORTING_MASK = 0x00000100
|
const val SORTING_UPLOAD_DATE = 0x00000200
|
||||||
|
const val SORTING_MASK = 0x00000300
|
||||||
|
|
||||||
const val DISPLAY_NAME = 0x00000000
|
const val DISPLAY_NAME = 0x00000000
|
||||||
const val DISPLAY_NUMBER = 0x00100000
|
const val DISPLAY_NUMBER = 0x00100000
|
||||||
@ -88,5 +95,4 @@ interface Manga : SManga {
|
|||||||
this.source = source
|
this.source = source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
1
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
Normal file → Executable file
@ -17,5 +17,4 @@ class MangaCategory {
|
|||||||
return mc
|
return mc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
Normal file → Executable file
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
Normal file → Executable file
@ -5,6 +5,6 @@ package eu.kanade.tachiyomi.data.database.models
|
|||||||
*
|
*
|
||||||
* @param manga object containing manga
|
* @param manga object containing manga
|
||||||
* @param chapter object containing chater
|
* @param chapter object containing chater
|
||||||
* @param history object containing history
|
* @param history object containing history
|
||||||
*/
|
*/
|
||||||
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History)
|
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History)
|
||||||
|
6
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Normal file → Executable file
@ -8,7 +8,7 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
|
|
||||||
override lateinit var title: String
|
override var title: String = ""
|
||||||
|
|
||||||
override var artist: String? = null
|
override var artist: String? = null
|
||||||
|
|
||||||
@ -32,6 +32,8 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var chapter_flags: Int = 0
|
override var chapter_flags: Int = 0
|
||||||
|
|
||||||
|
override var cover_last_modified: Long = 0
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
@ -39,11 +41,9 @@ open class MangaImpl : Manga {
|
|||||||
val manga = other as Manga
|
val manga = other as Manga
|
||||||
|
|
||||||
return url == manga.url
|
return url == manga.url
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return url.hashCode()
|
return url.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
14
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
Normal file → Executable file
@ -10,7 +10,9 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var sync_id: Int
|
var sync_id: Int
|
||||||
|
|
||||||
var remote_id: Int
|
var media_id: Int
|
||||||
|
|
||||||
|
var library_id: Long?
|
||||||
|
|
||||||
var title: String
|
var title: String
|
||||||
|
|
||||||
@ -22,17 +24,23 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var status: Int
|
var status: Int
|
||||||
|
|
||||||
|
var started_reading_date: Long
|
||||||
|
|
||||||
|
var finished_reading_date: Long
|
||||||
|
|
||||||
|
var tracking_url: String
|
||||||
|
|
||||||
fun copyPersonalFrom(other: Track) {
|
fun copyPersonalFrom(other: Track) {
|
||||||
last_chapter_read = other.last_chapter_read
|
last_chapter_read = other.last_chapter_read
|
||||||
score = other.score
|
score = other.score
|
||||||
status = other.status
|
status = other.status
|
||||||
|
started_reading_date = other.started_reading_date
|
||||||
|
finished_reading_date = other.finished_reading_date
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun create(serviceId: Int): Track = TrackImpl().apply {
|
fun create(serviceId: Int): Track = TrackImpl().apply {
|
||||||
sync_id = serviceId
|
sync_id = serviceId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
15
app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
Normal file → Executable file
@ -8,7 +8,9 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
override var sync_id: Int = 0
|
override var sync_id: Int = 0
|
||||||
|
|
||||||
override var remote_id: Int = 0
|
override var media_id: Int = 0
|
||||||
|
|
||||||
|
override var library_id: Long? = null
|
||||||
|
|
||||||
override lateinit var title: String
|
override lateinit var title: String
|
||||||
|
|
||||||
@ -20,6 +22,12 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
override var status: Int = 0
|
override var status: Int = 0
|
||||||
|
|
||||||
|
override var started_reading_date: Long = 0
|
||||||
|
|
||||||
|
override var finished_reading_date: Long = 0
|
||||||
|
|
||||||
|
override var tracking_url: String = ""
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
@ -28,14 +36,13 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
if (manga_id != other.manga_id) return false
|
if (manga_id != other.manga_id) return false
|
||||||
if (sync_id != other.sync_id) return false
|
if (sync_id != other.sync_id) return false
|
||||||
return remote_id == other.remote_id
|
return media_id == other.media_id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
||||||
result = 31 * result + sync_id
|
result = 31 * result + sync_id
|
||||||
result = 31 * result + remote_id
|
result = 31 * result + media_id
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
31
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
Normal file → Executable file
@ -10,20 +10,24 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
|||||||
interface CategoryQueries : DbProvider {
|
interface CategoryQueries : DbProvider {
|
||||||
|
|
||||||
fun getCategories() = db.get()
|
fun getCategories() = db.get()
|
||||||
.listOfObjects(Category::class.java)
|
.listOfObjects(Category::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(CategoryTable.TABLE)
|
Query.builder()
|
||||||
.orderBy(CategoryTable.COL_ORDER)
|
.table(CategoryTable.TABLE)
|
||||||
.build())
|
.orderBy(CategoryTable.COL_ORDER)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getCategoriesForManga(manga: Manga) = db.get()
|
fun getCategoriesForManga(manga: Manga) = db.get()
|
||||||
.listOfObjects(Category::class.java)
|
.listOfObjects(Category::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getCategoriesForMangaQuery())
|
RawQuery.builder()
|
||||||
.args(manga.id)
|
.query(getCategoriesForMangaQuery())
|
||||||
.build())
|
.args(manga.id)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
||||||
|
|
||||||
@ -32,5 +36,4 @@ interface CategoryQueries : DbProvider {
|
|||||||
fun deleteCategory(category: Category) = db.delete().`object`(category).prepare()
|
fun deleteCategory(category: Category) = db.delete().`object`(category).prepare()
|
||||||
|
|
||||||
fun deleteCategories(categories: List<Category>) = db.delete().objects(categories).prepare()
|
fun deleteCategories(categories: List<Category>) = db.delete().objects(categories).prepare()
|
||||||
|
}
|
||||||
}
|
|
||||||
|
129
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
Normal file → Executable file
@ -11,47 +11,87 @@ import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
|||||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
interface ChapterQueries : DbProvider {
|
interface ChapterQueries : DbProvider {
|
||||||
|
|
||||||
fun getChapters(manga: Manga) = db.get()
|
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
|
||||||
.listOfObjects(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||||
.table(ChapterTable.TABLE)
|
.listOfObjects(Chapter::class.java)
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
.withQuery(
|
||||||
.whereArgs(manga.id)
|
Query.builder()
|
||||||
.build())
|
.table(ChapterTable.TABLE)
|
||||||
.prepare()
|
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
|
.whereArgs(mangaId)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||||
|
.listOfObjects(Chapter::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getMergedChaptersQuery(mangaId))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getRecentChapters(date: Date) = db.get()
|
fun getRecentChapters(date: Date) = db.get()
|
||||||
.listOfObjects(MangaChapter::class.java)
|
.listOfObjects(MangaChapter::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getRecentsQuery())
|
RawQuery.builder()
|
||||||
.args(date.time)
|
.query(getRecentsQuery())
|
||||||
.observesTables(ChapterTable.TABLE)
|
.args(date.time)
|
||||||
.build())
|
.observesTables(ChapterTable.TABLE)
|
||||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
.build()
|
||||||
.prepare()
|
)
|
||||||
|
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getChapter(id: Long) = db.get()
|
fun getChapter(id: Long) = db.get()
|
||||||
.`object`(Chapter::class.java)
|
.`object`(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(ChapterTable.TABLE)
|
Query.builder()
|
||||||
.where("${ChapterTable.COL_ID} = ?")
|
.table(ChapterTable.TABLE)
|
||||||
.whereArgs(id)
|
.where("${ChapterTable.COL_ID} = ?")
|
||||||
.build())
|
.whereArgs(id)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getChapter(url: String) = db.get()
|
fun getChapter(url: String) = db.get()
|
||||||
.`object`(Chapter::class.java)
|
.`object`(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(ChapterTable.TABLE)
|
Query.builder()
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.table(ChapterTable.TABLE)
|
||||||
.whereArgs(url)
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.build())
|
.whereArgs(url)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getChapter(url: String, mangaId: Long) = db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
|
.whereArgs(url, mangaId)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getChapters(url: String) = db.get()
|
||||||
|
.listOfObjects(Chapter::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
|
.whereArgs(url)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||||
|
|
||||||
@ -62,23 +102,22 @@ interface ChapterQueries : DbProvider {
|
|||||||
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
||||||
|
|
||||||
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
||||||
.objects(chapters)
|
.objects(chapters)
|
||||||
.withPutResolver(ChapterBackupPutResolver())
|
.withPutResolver(ChapterBackupPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateChapterProgress(chapter: Chapter) = db.put()
|
fun updateChapterProgress(chapter: Chapter) = db.put()
|
||||||
.`object`(chapter)
|
.`object`(chapter)
|
||||||
.withPutResolver(ChapterProgressPutResolver())
|
.withPutResolver(ChapterProgressPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
|
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
|
||||||
.objects(chapters)
|
.objects(chapters)
|
||||||
.withPutResolver(ChapterProgressPutResolver())
|
.withPutResolver(ChapterProgressPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
|
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
|
||||||
.objects(chapters)
|
.objects(chapters)
|
||||||
.withPutResolver(ChapterSourceOrderPutResolver())
|
.withPutResolver(ChapterSourceOrderPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
}
|
||||||
}
|
|
||||||
|
107
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
Normal file → Executable file
@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
|||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
interface HistoryQueries : DbProvider {
|
interface HistoryQueries : DbProvider {
|
||||||
|
|
||||||
@ -22,33 +22,56 @@ interface HistoryQueries : DbProvider {
|
|||||||
* Returns history of recent manga containing last read chapter
|
* Returns history of recent manga containing last read chapter
|
||||||
* @param date recent date range
|
* @param date recent date range
|
||||||
*/
|
*/
|
||||||
fun getRecentManga(date: Date) = db.get()
|
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
|
||||||
.listOfObjects(MangaChapterHistory::class.java)
|
.listOfObjects(MangaChapterHistory::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getRecentMangasQuery())
|
RawQuery.builder()
|
||||||
.args(date.time)
|
.query(getRecentMangasQuery(offset, search))
|
||||||
.observesTables(HistoryTable.TABLE)
|
.args(date.time)
|
||||||
.build())
|
.observesTables(HistoryTable.TABLE)
|
||||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
.build()
|
||||||
.prepare()
|
)
|
||||||
|
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns history of recent manga containing last read chapter in 25s
|
||||||
|
* @param date recent date range
|
||||||
|
* @offset offset the db by
|
||||||
|
*/
|
||||||
|
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
|
||||||
|
.listOfObjects(MangaChapterHistory::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getRecentMangasLimitQuery(limit, search))
|
||||||
|
.args(date.time)
|
||||||
|
.observesTables(HistoryTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
||||||
.listOfObjects(History::class.java)
|
.listOfObjects(History::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getHistoryByMangaId())
|
RawQuery.builder()
|
||||||
.args(mangaId)
|
.query(getHistoryByMangaId())
|
||||||
.observesTables(HistoryTable.TABLE)
|
.args(mangaId)
|
||||||
.build())
|
.observesTables(HistoryTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
|
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
|
||||||
.`object`(History::class.java)
|
.`object`(History::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getHistoryByChapterUrl())
|
RawQuery.builder()
|
||||||
.args(chapterUrl)
|
.query(getHistoryByChapterUrl())
|
||||||
.observesTables(HistoryTable.TABLE)
|
.args(chapterUrl)
|
||||||
.build())
|
.observesTables(HistoryTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the history last read.
|
* Updates the history last read.
|
||||||
@ -56,9 +79,9 @@ interface HistoryQueries : DbProvider {
|
|||||||
* @param history history object
|
* @param history history object
|
||||||
*/
|
*/
|
||||||
fun updateHistoryLastRead(history: History) = db.put()
|
fun updateHistoryLastRead(history: History) = db.put()
|
||||||
.`object`(history)
|
.`object`(history)
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
.withPutResolver(HistoryLastReadPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the history last read.
|
* Updates the history last read.
|
||||||
@ -66,21 +89,25 @@ interface HistoryQueries : DbProvider {
|
|||||||
* @param historyList history object list
|
* @param historyList history object list
|
||||||
*/
|
*/
|
||||||
fun updateHistoryLastRead(historyList: List<History>) = db.put()
|
fun updateHistoryLastRead(historyList: List<History>) = db.put()
|
||||||
.objects(historyList)
|
.objects(historyList)
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
.withPutResolver(HistoryLastReadPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteHistory() = db.delete()
|
fun deleteHistory() = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(HistoryTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.build())
|
.table(HistoryTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteHistoryNoLastRead() = db.delete()
|
fun deleteHistoryNoLastRead() = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(HistoryTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
.table(HistoryTable.TABLE)
|
||||||
.whereArgs(0)
|
.where("${HistoryTable.COL_LAST_READ} = ?")
|
||||||
.build())
|
.whereArgs(0)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
}
|
}
|
||||||
|
25
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
Normal file → Executable file
@ -15,18 +15,23 @@ interface MangaCategoryQueries : DbProvider {
|
|||||||
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
||||||
|
|
||||||
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(MangaCategoryTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
.table(MangaCategoryTable.TABLE)
|
||||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||||
.build())
|
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
deleteOldMangasCategories(mangas).executeAsBlocking()
|
mangas.chunked(100) { chunk ->
|
||||||
insertMangasCategories(mangasCategories).executeAsBlocking()
|
deleteOldMangasCategories(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
|
mangasCategories.chunked(100) { chunk ->
|
||||||
|
insertMangasCategories(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
235
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Normal file → Executable file
@ -7,99 +7,216 @@ import eu.kanade.tachiyomi.data.database.DbProvider
|
|||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
import exh.metadata.sql.tables.SearchMetadataTable
|
||||||
|
|
||||||
interface MangaQueries : DbProvider {
|
interface MangaQueries : DbProvider {
|
||||||
|
|
||||||
fun getMangas() = db.get()
|
fun getMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(MangaTable.TABLE)
|
Query.builder()
|
||||||
.build())
|
.table(MangaTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getLibraryMangas() = db.get()
|
fun getLibraryMangas() = db.get()
|
||||||
.listOfObjects(LibraryManga::class.java)
|
.listOfObjects(LibraryManga::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(libraryQuery)
|
RawQuery.builder()
|
||||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
.query(libraryQuery)
|
||||||
.build())
|
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
.build()
|
||||||
.prepare()
|
)
|
||||||
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getFavoriteMangas() = db.get()
|
fun getFavoriteMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(MangaTable.TABLE)
|
Query.builder()
|
||||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
.table(MangaTable.TABLE)
|
||||||
.whereArgs(1)
|
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||||
.orderBy(MangaTable.COL_TITLE)
|
.whereArgs(1)
|
||||||
.build())
|
.orderBy(MangaTable.COL_TITLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getManga(url: String, sourceId: Long) = db.get()
|
fun getManga(url: String, sourceId: Long) = db.get()
|
||||||
.`object`(Manga::class.java)
|
.`object`(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(MangaTable.TABLE)
|
Query.builder()
|
||||||
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
.table(MangaTable.TABLE)
|
||||||
.whereArgs(url, sourceId)
|
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
||||||
.build())
|
.whereArgs(url, sourceId)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getManga(id: Long) = db.get()
|
fun getManga(id: Long) = db.get()
|
||||||
.`object`(Manga::class.java)
|
.`object`(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(MangaTable.TABLE)
|
Query.builder()
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
.table(MangaTable.TABLE)
|
||||||
.whereArgs(id)
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
.build())
|
.whereArgs(id)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getMergedMangas(id: Long) = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getMergedMangaQuery(id))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||||
|
|
||||||
fun updateFlags(manga: Manga) = db.put()
|
fun updateFlags(manga: Manga) = db.put()
|
||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaFlagsPutResolver())
|
.withPutResolver(MangaFlagsPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateLastUpdated(manga: Manga) = db.put()
|
fun updateLastUpdated(manga: Manga) = db.put()
|
||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaFavorite(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaFavoritePutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaViewer(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaViewerPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaTitle(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaTitlePutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaCoverLastModified(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
|
|
||||||
fun deleteMangasNotInLibrary() = db.delete()
|
fun deleteMangasNotInLibrary() = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(MangaTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
.table(MangaTable.TABLE)
|
||||||
.whereArgs(0)
|
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||||
.build())
|
.whereArgs(0)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteMangas() = db.delete()
|
fun deleteMangas() = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(MangaTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.build())
|
.table(MangaTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getLastReadManga() = db.get()
|
fun getLastReadManga() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
.query(getLastReadMangaQuery())
|
RawQuery.builder()
|
||||||
.observesTables(MangaTable.TABLE)
|
.query(getLastReadMangaQuery())
|
||||||
.build())
|
.observesTables(MangaTable.TABLE)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java)
|
fun getTotalChapterManga() = db.get()
|
||||||
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare();
|
.listOfObjects(Manga::class.java)
|
||||||
}
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getTotalChapterMangaQuery())
|
||||||
|
.observesTables(MangaTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getLatestChapterManga() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getLatestChapterMangaQuery())
|
||||||
|
.observesTables(MangaTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getMangaWithMetadata() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
|
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||||
|
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||||
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getFavoriteMangaWithMetadata() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
|
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||||
|
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||||
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
|
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||||
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getIdsOfFavoriteMangaWithMetadata() = db.get()
|
||||||
|
.cursor()
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
|
SELECT ${MangaTable.TABLE}.${MangaTable.COL_ID} FROM ${MangaTable.TABLE}
|
||||||
|
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||||
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
|
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||||
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
93
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Normal file → Executable file
@ -5,11 +5,39 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query to get the manga merged into a merged manga
|
||||||
|
*/
|
||||||
|
fun getMergedMangaQuery(id: Long) =
|
||||||
|
"""
|
||||||
|
SELECT ${Manga.TABLE}.*
|
||||||
|
FROM (
|
||||||
|
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||||
|
) AS M
|
||||||
|
JOIN ${Manga.TABLE}
|
||||||
|
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||||
|
"""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query to get the chapters of all manga in a merged manga
|
||||||
|
*/
|
||||||
|
fun getMergedChaptersQuery(id: Long) =
|
||||||
|
"""
|
||||||
|
SELECT ${Chapter.TABLE}.*
|
||||||
|
FROM (
|
||||||
|
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||||
|
) AS M
|
||||||
|
JOIN ${Chapter.TABLE}
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the manga from the library, with their categories and unread count.
|
* Query to get the manga from the library, with their categories and unread count.
|
||||||
*/
|
*/
|
||||||
val libraryQuery = """
|
val libraryQuery =
|
||||||
|
"""
|
||||||
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}
|
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}
|
||||||
@ -33,7 +61,8 @@ val libraryQuery = """
|
|||||||
/**
|
/**
|
||||||
* Query to get the recent chapters of manga from the library up to a date.
|
* Query to get the recent chapters of manga from the library up to a date.
|
||||||
*/
|
*/
|
||||||
fun getRecentsQuery() = """
|
fun getRecentsQuery() =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
|
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||||
@ -47,7 +76,8 @@ fun getRecentsQuery() = """
|
|||||||
* and are read after the given time period
|
* and are read after the given time period
|
||||||
* @return return limit is 25
|
* @return return limit is 25
|
||||||
*/
|
*/
|
||||||
fun getRecentMangasQuery() = """
|
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
@ -61,11 +91,40 @@ fun getRecentMangasQuery() = """
|
|||||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||||
LIMIT 25
|
LIMIT 25 OFFSET $offset
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getHistoryByMangaId() = """
|
/**
|
||||||
|
* Query to get the recently read chapters of manga from the library up to a date.
|
||||||
|
* The max_last_read table contains the most recent chapters grouped by manga
|
||||||
|
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
||||||
|
* and are read after the given time period
|
||||||
|
*/
|
||||||
|
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
|
||||||
|
"""
|
||||||
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||||
|
FROM ${Manga.TABLE}
|
||||||
|
JOIN ${Chapter.TABLE}
|
||||||
|
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
|
JOIN ${History.TABLE}
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
JOIN (
|
||||||
|
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||||
|
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||||
|
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
|
||||||
|
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||||
|
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||||
|
LIMIT $limit
|
||||||
|
"""
|
||||||
|
|
||||||
|
fun getHistoryByMangaId() =
|
||||||
|
"""
|
||||||
SELECT ${History.TABLE}.*
|
SELECT ${History.TABLE}.*
|
||||||
FROM ${History.TABLE}
|
FROM ${History.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
@ -73,7 +132,8 @@ fun getHistoryByMangaId() = """
|
|||||||
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getHistoryByChapterUrl() = """
|
fun getHistoryByChapterUrl() =
|
||||||
|
"""
|
||||||
SELECT ${History.TABLE}.*
|
SELECT ${History.TABLE}.*
|
||||||
FROM ${History.TABLE}
|
FROM ${History.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
@ -81,7 +141,8 @@ fun getHistoryByChapterUrl() = """
|
|||||||
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getLastReadMangaQuery() = """
|
fun getLastReadMangaQuery() =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
@ -93,7 +154,8 @@ fun getLastReadMangaQuery() = """
|
|||||||
ORDER BY max DESC
|
ORDER BY max DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getTotalChapterMangaQuery()= """
|
fun getTotalChapterMangaQuery() =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.*
|
SELECT ${Manga.TABLE}.*
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
@ -102,12 +164,23 @@ fun getTotalChapterMangaQuery()= """
|
|||||||
ORDER by COUNT(*)
|
ORDER by COUNT(*)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
fun getLatestChapterMangaQuery() =
|
||||||
|
"""
|
||||||
|
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max
|
||||||
|
FROM ${Manga.TABLE}
|
||||||
|
JOIN ${Chapter.TABLE}
|
||||||
|
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
|
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||||
|
ORDER by max DESC
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the categories for a manga.
|
* Query to get the categories for a manga.
|
||||||
*/
|
*/
|
||||||
fun getCategoriesForMangaQuery() = """
|
fun getCategoriesForMangaQuery() =
|
||||||
|
"""
|
||||||
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
|
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
|
||||||
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
|
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
|
||||||
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
||||||
WHERE ${MangaCategory.COL_MANGA_ID} = ?
|
WHERE ${MangaCategory.COL_MANGA_ID} = ?
|
||||||
"""
|
"""
|
||||||
|
71
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
Normal file → Executable file
@ -1,34 +1,37 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
|
|
||||||
interface TrackQueries : DbProvider {
|
interface TrackQueries : DbProvider {
|
||||||
|
|
||||||
fun getTracks(manga: Manga) = db.get()
|
fun getTracks(manga: Manga) = db.get()
|
||||||
.listOfObjects(Track::class.java)
|
.listOfObjects(Track::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(TrackTable.TABLE)
|
Query.builder()
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
.table(TrackTable.TABLE)
|
||||||
.whereArgs(manga.id)
|
.where("${TrackTable.COL_MANGA_ID} = ?")
|
||||||
.build())
|
.whereArgs(manga.id)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
fun insertTrack(track: Track) = db.put().`object`(track).prepare()
|
.prepare()
|
||||||
|
|
||||||
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
|
fun insertTrack(track: Track) = db.put().`object`(track).prepare()
|
||||||
|
|
||||||
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
|
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(TrackTable.TABLE)
|
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
.byQuery(
|
||||||
.whereArgs(manga.id, sync.id)
|
DeleteQuery.builder()
|
||||||
.build())
|
.table(TrackTable.TABLE)
|
||||||
.prepare()
|
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
||||||
|
.whereArgs(manga.id, sync.id)
|
||||||
}
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
@ -20,16 +20,14 @@ class ChapterBackupPutResolver : PutResolver<Chapter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.whereArgs(chapter.url)
|
.whereArgs(chapter.url)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
||||||
put(ChapterTable.COL_READ, chapter.read)
|
put(ChapterTable.COL_READ, chapter.read)
|
||||||
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
||||||
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
Normal file → Executable file
@ -20,16 +20,14 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_ID} = ?")
|
.where("${ChapterTable.COL_ID} = ?")
|
||||||
.whereArgs(chapter.id)
|
.whereArgs(chapter.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
||||||
put(ChapterTable.COL_READ, chapter.read)
|
put(ChapterTable.COL_READ, chapter.read)
|
||||||
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
||||||
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
Normal file → Executable file
@ -20,13 +20,12 @@ class ChapterSourceOrderPutResolver : PutResolver<Chapter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(chapter.url, chapter.manga_id)
|
.whereArgs(chapter.url, chapter.manga_id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply {
|
fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply {
|
||||||
put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order)
|
put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
27
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.support.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
@ -19,25 +19,25 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
|||||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
||||||
val updateQuery = mapToUpdateQuery(history)
|
val updateQuery = mapToUpdateQuery(history)
|
||||||
|
|
||||||
val cursor = db.lowLevel().query(Query.builder()
|
val cursor = db.lowLevel().query(
|
||||||
|
Query.builder()
|
||||||
.table(updateQuery.table())
|
.table(updateQuery.table())
|
||||||
.where(updateQuery.where())
|
.where(updateQuery.where())
|
||||||
.whereArgs(updateQuery.whereArgs())
|
.whereArgs(updateQuery.whereArgs())
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
val putResult: PutResult
|
val putResult: PutResult
|
||||||
|
|
||||||
try {
|
putResult = cursor.use { putCursor ->
|
||||||
if (cursor.count == 0) {
|
if (putCursor.count == 0) {
|
||||||
val insertQuery = mapToInsertQuery(history)
|
val insertQuery = mapToInsertQuery(history)
|
||||||
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
|
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
|
||||||
putResult = PutResult.newInsertResult(insertedId, insertQuery.table())
|
PutResult.newInsertResult(insertedId, insertQuery.table())
|
||||||
} else {
|
} else {
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history))
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history))
|
||||||
putResult = PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
cursor.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
putResult
|
putResult
|
||||||
@ -48,10 +48,10 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
|||||||
* @param obj history object
|
* @param obj history object
|
||||||
*/
|
*/
|
||||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
||||||
.table(HistoryTable.TABLE)
|
.table(HistoryTable.TABLE)
|
||||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
||||||
.whereArgs(obj.chapter_id)
|
.whereArgs(obj.chapter_id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create content query
|
* Create content query
|
||||||
@ -60,5 +60,4 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
|
|||||||
fun mapToUpdateContentValues(history: History) = ContentValues(1).apply {
|
fun mapToUpdateContentValues(history: History) = ContentValues(1).apply {
|
||||||
put(HistoryTable.COL_LAST_READ, history.last_read)
|
put(HistoryTable.COL_LAST_READ, history.last_read)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
Normal file → Executable file
@ -21,5 +21,4 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
|
|||||||
|
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
Normal file → Executable file
@ -24,5 +24,4 @@ class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
|||||||
|
|
||||||
return MangaChapter(manga, chapter)
|
return MangaChapter(manga, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
Normal file → Executable file
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaCoverLastModifiedPutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_COVER_LAST_MODIFIED, manga.cover_last_modified)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaFavoritePutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_FAVORITE, manga.favorite)
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
Normal file → Executable file
@ -20,14 +20,12 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
.whereArgs(manga.id)
|
.whereArgs(manga.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
|
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|