summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rustfmt.toml1
-rw-r--r--Cargo.lock376
-rw-r--r--Cargo.toml3
-rw-r--r--src/apis/modrinth.rs19
-rw-r--r--src/cache.rs4
-rw-r--r--src/commands/download.rs93
-rw-r--r--src/commands/io.rs72
-rw-r--r--src/commands/list.rs85
-rw-r--r--src/commands/modification.rs204
-rw-r--r--src/commands/update.rs222
-rw-r--r--src/config.rs51
-rw-r--r--src/db.rs216
-rw-r--r--src/error.rs14
-rw-r--r--src/files.rs199
-rw-r--r--src/lib.rs72
-rw-r--r--src/main.rs206
16 files changed, 1080 insertions, 757 deletions
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..df99c69
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1 @@
max_width = 80
diff --git a/Cargo.lock b/Cargo.lock
index c842b90..c72ac69 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,11 +19,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 19
20[[package]] 20[[package]]
21name = "ahash" 21name = "ahash"
22version = "0.7.6" 22version = "0.8.3"
23source = "registry+https://github.com/rust-lang/crates.io-index" 23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 24checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
25dependencies = [ 25dependencies = [
26 "getrandom", 26 "cfg-if",
27 "once_cell", 27 "once_cell",
28 "version_check", 28 "version_check",
29] 29]
@@ -39,9 +39,9 @@ dependencies = [
39 39
40[[package]] 40[[package]]
41name = "anstream" 41name = "anstream"
42version = "0.3.1" 42version = "0.3.2"
43source = "registry+https://github.com/rust-lang/crates.io-index" 43source = "registry+https://github.com/rust-lang/crates.io-index"
44checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" 44checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
45dependencies = [ 45dependencies = [
46 "anstyle", 46 "anstyle",
47 "anstyle-parse", 47 "anstyle-parse",
@@ -109,9 +109,9 @@ dependencies = [
109 109
110[[package]] 110[[package]]
111name = "base64" 111name = "base64"
112version = "0.21.0" 112version = "0.21.2"
113source = "registry+https://github.com/rust-lang/crates.io-index" 113source = "registry+https://github.com/rust-lang/crates.io-index"
114checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 114checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
115 115
116[[package]] 116[[package]]
117name = "bitflags" 117name = "bitflags"
@@ -121,15 +121,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
121 121
122[[package]] 122[[package]]
123name = "bitflags" 123name = "bitflags"
124version = "2.2.1" 124version = "2.3.1"
125source = "registry+https://github.com/rust-lang/crates.io-index" 125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" 126checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
127 127
128[[package]] 128[[package]]
129name = "bumpalo" 129name = "bumpalo"
130version = "3.12.1" 130version = "3.13.0"
131source = "registry+https://github.com/rust-lang/crates.io-index" 131source = "registry+https://github.com/rust-lang/crates.io-index"
132checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" 132checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
133 133
134[[package]] 134[[package]]
135name = "bytes" 135name = "bytes"
@@ -166,9 +166,9 @@ dependencies = [
166 166
167[[package]] 167[[package]]
168name = "clap" 168name = "clap"
169version = "4.2.4" 169version = "4.3.0"
170source = "registry+https://github.com/rust-lang/crates.io-index" 170source = "registry+https://github.com/rust-lang/crates.io-index"
171checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" 171checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc"
172dependencies = [ 172dependencies = [
173 "clap_builder", 173 "clap_builder",
174 "clap_derive", 174 "clap_derive",
@@ -177,9 +177,9 @@ dependencies = [
177 177
178[[package]] 178[[package]]
179name = "clap_builder" 179name = "clap_builder"
180version = "4.2.4" 180version = "4.3.0"
181source = "registry+https://github.com/rust-lang/crates.io-index" 181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" 182checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990"
183dependencies = [ 183dependencies = [
184 "anstream", 184 "anstream",
185 "anstyle", 185 "anstyle",
@@ -190,40 +190,30 @@ dependencies = [
190 190
191[[package]] 191[[package]]
192name = "clap_complete" 192name = "clap_complete"
193version = "4.2.1" 193version = "4.3.0"
194source = "registry+https://github.com/rust-lang/crates.io-index" 194source = "registry+https://github.com/rust-lang/crates.io-index"
195checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473" 195checksum = "a04ddfaacc3bc9e6ea67d024575fafc2a813027cf374b8f24f7bc233c6b6be12"
196dependencies = [ 196dependencies = [
197 "clap", 197 "clap",
198] 198]
199 199
200[[package]] 200[[package]]
201name = "clap_derive" 201name = "clap_derive"
202version = "4.2.0" 202version = "4.3.0"
203source = "registry+https://github.com/rust-lang/crates.io-index" 203source = "registry+https://github.com/rust-lang/crates.io-index"
204checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" 204checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b"
205dependencies = [ 205dependencies = [
206 "heck", 206 "heck",
207 "proc-macro2", 207 "proc-macro2",
208 "quote", 208 "quote",
209 "syn 2.0.15", 209 "syn",
210] 210]
211 211
212[[package]] 212[[package]]
213name = "clap_lex" 213name = "clap_lex"
214version = "0.4.1" 214version = "0.5.0"
215source = "registry+https://github.com/rust-lang/crates.io-index"
216checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
217
218[[package]]
219name = "codespan-reporting"
220version = "0.11.1"
221source = "registry+https://github.com/rust-lang/crates.io-index" 215source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 216checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
223dependencies = [
224 "termcolor",
225 "unicode-width",
226]
227 217
228[[package]] 218[[package]]
229name = "colorchoice" 219name = "colorchoice"
@@ -232,6 +222,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
232checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 222checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
233 223
234[[package]] 224[[package]]
225name = "console"
226version = "0.15.7"
227source = "registry+https://github.com/rust-lang/crates.io-index"
228checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
229dependencies = [
230 "encode_unicode",
231 "lazy_static",
232 "libc",
233 "unicode-width",
234 "windows-sys 0.45.0",
235]
236
237[[package]]
235name = "core-foundation" 238name = "core-foundation"
236version = "0.9.3" 239version = "0.9.3"
237source = "registry+https://github.com/rust-lang/crates.io-index" 240source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -248,70 +251,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
248checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 251checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
249 252
250[[package]] 253[[package]]
251name = "cxx"
252version = "1.0.94"
253source = "registry+https://github.com/rust-lang/crates.io-index"
254checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
255dependencies = [
256 "cc",
257 "cxxbridge-flags",
258 "cxxbridge-macro",
259 "link-cplusplus",
260]
261
262[[package]]
263name = "cxx-build"
264version = "1.0.94"
265source = "registry+https://github.com/rust-lang/crates.io-index"
266checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
267dependencies = [
268 "cc",
269 "codespan-reporting",
270 "once_cell",
271 "proc-macro2",
272 "quote",
273 "scratch",
274 "syn 2.0.15",
275]
276
277[[package]]
278name = "cxxbridge-flags"
279version = "1.0.94"
280source = "registry+https://github.com/rust-lang/crates.io-index"
281checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
282
283[[package]]
284name = "cxxbridge-macro"
285version = "1.0.94"
286source = "registry+https://github.com/rust-lang/crates.io-index"
287checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
288dependencies = [
289 "proc-macro2",
290 "quote",
291 "syn 2.0.15",
292]
293
294[[package]]
295name = "dirs" 254name = "dirs"
296version = "5.0.0" 255version = "5.0.1"
297source = "registry+https://github.com/rust-lang/crates.io-index" 256source = "registry+https://github.com/rust-lang/crates.io-index"
298checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" 257checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
299dependencies = [ 258dependencies = [
300 "dirs-sys", 259 "dirs-sys",
301] 260]
302 261
303[[package]] 262[[package]]
304name = "dirs-sys" 263name = "dirs-sys"
305version = "0.4.0" 264version = "0.4.1"
306source = "registry+https://github.com/rust-lang/crates.io-index" 265source = "registry+https://github.com/rust-lang/crates.io-index"
307checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" 266checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
308dependencies = [ 267dependencies = [
309 "libc", 268 "libc",
269 "option-ext",
310 "redox_users", 270 "redox_users",
311 "windows-sys 0.45.0", 271 "windows-sys 0.48.0",
312] 272]
313 273
314[[package]] 274[[package]]
275name = "encode_unicode"
276version = "0.3.6"
277source = "registry+https://github.com/rust-lang/crates.io-index"
278checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
279
280[[package]]
315name = "encoding_rs" 281name = "encoding_rs"
316version = "0.8.32" 282version = "0.8.32"
317source = "registry+https://github.com/rust-lang/crates.io-index" 283source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -431,7 +397,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
431dependencies = [ 397dependencies = [
432 "proc-macro2", 398 "proc-macro2",
433 "quote", 399 "quote",
434 "syn 2.0.15", 400 "syn",
435] 401]
436 402
437[[package]] 403[[package]]
@@ -482,9 +448,9 @@ checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
482 448
483[[package]] 449[[package]]
484name = "h2" 450name = "h2"
485version = "0.3.18" 451version = "0.3.19"
486source = "registry+https://github.com/rust-lang/crates.io-index" 452source = "registry+https://github.com/rust-lang/crates.io-index"
487checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" 453checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
488dependencies = [ 454dependencies = [
489 "bytes", 455 "bytes",
490 "fnv", 456 "fnv",
@@ -504,17 +470,23 @@ name = "hashbrown"
504version = "0.12.3" 470version = "0.12.3"
505source = "registry+https://github.com/rust-lang/crates.io-index" 471source = "registry+https://github.com/rust-lang/crates.io-index"
506checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 472checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
473
474[[package]]
475name = "hashbrown"
476version = "0.13.2"
477source = "registry+https://github.com/rust-lang/crates.io-index"
478checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
507dependencies = [ 479dependencies = [
508 "ahash", 480 "ahash",
509] 481]
510 482
511[[package]] 483[[package]]
512name = "hashlink" 484name = "hashlink"
513version = "0.8.1" 485version = "0.8.2"
514source = "registry+https://github.com/rust-lang/crates.io-index" 486source = "registry+https://github.com/rust-lang/crates.io-index"
515checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" 487checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa"
516dependencies = [ 488dependencies = [
517 "hashbrown", 489 "hashbrown 0.13.2",
518] 490]
519 491
520[[package]] 492[[package]]
@@ -625,12 +597,11 @@ dependencies = [
625 597
626[[package]] 598[[package]]
627name = "iana-time-zone-haiku" 599name = "iana-time-zone-haiku"
628version = "0.1.1" 600version = "0.1.2"
629source = "registry+https://github.com/rust-lang/crates.io-index" 601source = "registry+https://github.com/rust-lang/crates.io-index"
630checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 602checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
631dependencies = [ 603dependencies = [
632 "cxx", 604 "cc",
633 "cxx-build",
634] 605]
635 606
636[[package]] 607[[package]]
@@ -650,7 +621,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
650checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 621checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
651dependencies = [ 622dependencies = [
652 "autocfg", 623 "autocfg",
653 "hashbrown", 624 "hashbrown 0.12.3",
625]
626
627[[package]]
628name = "indicatif"
629version = "0.17.3"
630source = "registry+https://github.com/rust-lang/crates.io-index"
631checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729"
632dependencies = [
633 "console",
634 "number_prefix",
635 "portable-atomic 0.3.20",
636 "unicode-width",
654] 637]
655 638
656[[package]] 639[[package]]
@@ -664,9 +647,9 @@ dependencies = [
664 647
665[[package]] 648[[package]]
666name = "io-lifetimes" 649name = "io-lifetimes"
667version = "1.0.10" 650version = "1.0.11"
668source = "registry+https://github.com/rust-lang/crates.io-index" 651source = "registry+https://github.com/rust-lang/crates.io-index"
669checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 652checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
670dependencies = [ 653dependencies = [
671 "hermit-abi 0.3.1", 654 "hermit-abi 0.3.1",
672 "libc", 655 "libc",
@@ -699,9 +682,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
699 682
700[[package]] 683[[package]]
701name = "js-sys" 684name = "js-sys"
702version = "0.3.61" 685version = "0.3.63"
703source = "registry+https://github.com/rust-lang/crates.io-index" 686source = "registry+https://github.com/rust-lang/crates.io-index"
704checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 687checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
705dependencies = [ 688dependencies = [
706 "wasm-bindgen", 689 "wasm-bindgen",
707] 690]
@@ -714,9 +697,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
714 697
715[[package]] 698[[package]]
716name = "libc" 699name = "libc"
717version = "0.2.142" 700version = "0.2.144"
718source = "registry+https://github.com/rust-lang/crates.io-index" 701source = "registry+https://github.com/rust-lang/crates.io-index"
719checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" 702checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
720 703
721[[package]] 704[[package]]
722name = "libsqlite3-sys" 705name = "libsqlite3-sys"
@@ -730,19 +713,10 @@ dependencies = [
730] 713]
731 714
732[[package]] 715[[package]]
733name = "link-cplusplus"
734version = "1.0.8"
735source = "registry+https://github.com/rust-lang/crates.io-index"
736checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
737dependencies = [
738 "cc",
739]
740
741[[package]]
742name = "linux-raw-sys" 716name = "linux-raw-sys"
743version = "0.3.4" 717version = "0.3.8"
744source = "registry+https://github.com/rust-lang/crates.io-index" 718source = "registry+https://github.com/rust-lang/crates.io-index"
745checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" 719checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
746 720
747[[package]] 721[[package]]
748name = "lock_api" 722name = "lock_api"
@@ -798,7 +772,7 @@ dependencies = [
798 772
799[[package]] 773[[package]]
800name = "modlist" 774name = "modlist"
801version = "0.14.2" 775version = "0.15.0"
802dependencies = [ 776dependencies = [
803 "chrono", 777 "chrono",
804 "clap", 778 "clap",
@@ -806,6 +780,7 @@ dependencies = [
806 "dirs", 780 "dirs",
807 "error-chain", 781 "error-chain",
808 "futures-util", 782 "futures-util",
783 "indicatif",
809 "reqwest", 784 "reqwest",
810 "rusqlite", 785 "rusqlite",
811 "serde", 786 "serde",
@@ -862,6 +837,12 @@ dependencies = [
862] 837]
863 838
864[[package]] 839[[package]]
840name = "number_prefix"
841version = "0.4.0"
842source = "registry+https://github.com/rust-lang/crates.io-index"
843checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
844
845[[package]]
865name = "object" 846name = "object"
866version = "0.30.3" 847version = "0.30.3"
867source = "registry+https://github.com/rust-lang/crates.io-index" 848source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -899,7 +880,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
899dependencies = [ 880dependencies = [
900 "proc-macro2", 881 "proc-macro2",
901 "quote", 882 "quote",
902 "syn 2.0.15", 883 "syn",
903] 884]
904 885
905[[package]] 886[[package]]
@@ -921,6 +902,12 @@ dependencies = [
921] 902]
922 903
923[[package]] 904[[package]]
905name = "option-ext"
906version = "0.2.0"
907source = "registry+https://github.com/rust-lang/crates.io-index"
908checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
909
910[[package]]
924name = "parking_lot" 911name = "parking_lot"
925version = "0.12.1" 912version = "0.12.1"
926source = "registry+https://github.com/rust-lang/crates.io-index" 913source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -963,24 +950,39 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
963 950
964[[package]] 951[[package]]
965name = "pkg-config" 952name = "pkg-config"
966version = "0.3.26" 953version = "0.3.27"
967source = "registry+https://github.com/rust-lang/crates.io-index" 954source = "registry+https://github.com/rust-lang/crates.io-index"
968checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 955checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
956
957[[package]]
958name = "portable-atomic"
959version = "0.3.20"
960source = "registry+https://github.com/rust-lang/crates.io-index"
961checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e"
962dependencies = [
963 "portable-atomic 1.3.2",
964]
965
966[[package]]
967name = "portable-atomic"
968version = "1.3.2"
969source = "registry+https://github.com/rust-lang/crates.io-index"
970checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5"
969 971
970[[package]] 972[[package]]
971name = "proc-macro2" 973name = "proc-macro2"
972version = "1.0.56" 974version = "1.0.59"
973source = "registry+https://github.com/rust-lang/crates.io-index" 975source = "registry+https://github.com/rust-lang/crates.io-index"
974checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 976checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
975dependencies = [ 977dependencies = [
976 "unicode-ident", 978 "unicode-ident",
977] 979]
978 980
979[[package]] 981[[package]]
980name = "quote" 982name = "quote"
981version = "1.0.26" 983version = "1.0.28"
982source = "registry+https://github.com/rust-lang/crates.io-index" 984source = "registry+https://github.com/rust-lang/crates.io-index"
983checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 985checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
984dependencies = [ 986dependencies = [
985 "proc-macro2", 987 "proc-macro2",
986] 988]
@@ -1016,9 +1018,9 @@ dependencies = [
1016 1018
1017[[package]] 1019[[package]]
1018name = "reqwest" 1020name = "reqwest"
1019version = "0.11.16" 1021version = "0.11.18"
1020source = "registry+https://github.com/rust-lang/crates.io-index" 1022source = "registry+https://github.com/rust-lang/crates.io-index"
1021checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" 1023checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
1022dependencies = [ 1024dependencies = [
1023 "base64", 1025 "base64",
1024 "bytes", 1026 "bytes",
@@ -1059,7 +1061,7 @@ version = "0.29.0"
1059source = "registry+https://github.com/rust-lang/crates.io-index" 1061source = "registry+https://github.com/rust-lang/crates.io-index"
1060checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" 1062checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
1061dependencies = [ 1063dependencies = [
1062 "bitflags 2.2.1", 1064 "bitflags 2.3.1",
1063 "fallible-iterator", 1065 "fallible-iterator",
1064 "fallible-streaming-iterator", 1066 "fallible-streaming-iterator",
1065 "hashlink", 1067 "hashlink",
@@ -1075,9 +1077,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1075 1077
1076[[package]] 1078[[package]]
1077name = "rustix" 1079name = "rustix"
1078version = "0.37.15" 1080version = "0.37.19"
1079source = "registry+https://github.com/rust-lang/crates.io-index" 1081source = "registry+https://github.com/rust-lang/crates.io-index"
1080checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" 1082checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
1081dependencies = [ 1083dependencies = [
1082 "bitflags 1.3.2", 1084 "bitflags 1.3.2",
1083 "errno", 1085 "errno",
@@ -1109,16 +1111,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1109checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1111checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1110 1112
1111[[package]] 1113[[package]]
1112name = "scratch"
1113version = "1.0.5"
1114source = "registry+https://github.com/rust-lang/crates.io-index"
1115checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1116
1117[[package]]
1118name = "security-framework" 1114name = "security-framework"
1119version = "2.8.2" 1115version = "2.9.1"
1120source = "registry+https://github.com/rust-lang/crates.io-index" 1116source = "registry+https://github.com/rust-lang/crates.io-index"
1121checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" 1117checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
1122dependencies = [ 1118dependencies = [
1123 "bitflags 1.3.2", 1119 "bitflags 1.3.2",
1124 "core-foundation", 1120 "core-foundation",
@@ -1129,9 +1125,9 @@ dependencies = [
1129 1125
1130[[package]] 1126[[package]]
1131name = "security-framework-sys" 1127name = "security-framework-sys"
1132version = "2.8.0" 1128version = "2.9.0"
1133source = "registry+https://github.com/rust-lang/crates.io-index" 1129source = "registry+https://github.com/rust-lang/crates.io-index"
1134checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" 1130checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
1135dependencies = [ 1131dependencies = [
1136 "core-foundation-sys", 1132 "core-foundation-sys",
1137 "libc", 1133 "libc",
@@ -1139,22 +1135,22 @@ dependencies = [
1139 1135
1140[[package]] 1136[[package]]
1141name = "serde" 1137name = "serde"
1142version = "1.0.160" 1138version = "1.0.163"
1143source = "registry+https://github.com/rust-lang/crates.io-index" 1139source = "registry+https://github.com/rust-lang/crates.io-index"
1144checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" 1140checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
1145dependencies = [ 1141dependencies = [
1146 "serde_derive", 1142 "serde_derive",
1147] 1143]
1148 1144
1149[[package]] 1145[[package]]
1150name = "serde_derive" 1146name = "serde_derive"
1151version = "1.0.160" 1147version = "1.0.163"
1152source = "registry+https://github.com/rust-lang/crates.io-index" 1148source = "registry+https://github.com/rust-lang/crates.io-index"
1153checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" 1149checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
1154dependencies = [ 1150dependencies = [
1155 "proc-macro2", 1151 "proc-macro2",
1156 "quote", 1152 "quote",
1157 "syn 2.0.15", 1153 "syn",
1158] 1154]
1159 1155
1160[[package]] 1156[[package]]
@@ -1170,9 +1166,9 @@ dependencies = [
1170 1166
1171[[package]] 1167[[package]]
1172name = "serde_spanned" 1168name = "serde_spanned"
1173version = "0.6.1" 1169version = "0.6.2"
1174source = "registry+https://github.com/rust-lang/crates.io-index" 1170source = "registry+https://github.com/rust-lang/crates.io-index"
1175checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" 1171checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
1176dependencies = [ 1172dependencies = [
1177 "serde", 1173 "serde",
1178] 1174]
@@ -1231,20 +1227,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1231 1227
1232[[package]] 1228[[package]]
1233name = "syn" 1229name = "syn"
1234version = "1.0.109" 1230version = "2.0.17"
1235source = "registry+https://github.com/rust-lang/crates.io-index" 1231source = "registry+https://github.com/rust-lang/crates.io-index"
1236checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1232checksum = "45b6ddbb36c5b969c182aec3c4a0bce7df3fbad4b77114706a49aacc80567388"
1237dependencies = [
1238 "proc-macro2",
1239 "quote",
1240 "unicode-ident",
1241]
1242
1243[[package]]
1244name = "syn"
1245version = "2.0.15"
1246source = "registry+https://github.com/rust-lang/crates.io-index"
1247checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
1248dependencies = [ 1233dependencies = [
1249 "proc-macro2", 1234 "proc-macro2",
1250 "quote", 1235 "quote",
@@ -1265,15 +1250,6 @@ dependencies = [
1265] 1250]
1266 1251
1267[[package]] 1252[[package]]
1268name = "termcolor"
1269version = "1.2.0"
1270source = "registry+https://github.com/rust-lang/crates.io-index"
1271checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
1272dependencies = [
1273 "winapi-util",
1274]
1275
1276[[package]]
1277name = "thiserror" 1253name = "thiserror"
1278version = "1.0.40" 1254version = "1.0.40"
1279source = "registry+https://github.com/rust-lang/crates.io-index" 1255source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1290,7 +1266,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
1290dependencies = [ 1266dependencies = [
1291 "proc-macro2", 1267 "proc-macro2",
1292 "quote", 1268 "quote",
1293 "syn 2.0.15", 1269 "syn",
1294] 1270]
1295 1271
1296[[package]] 1272[[package]]
@@ -1321,9 +1297,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1321 1297
1322[[package]] 1298[[package]]
1323name = "tokio" 1299name = "tokio"
1324version = "1.28.0" 1300version = "1.28.1"
1325source = "registry+https://github.com/rust-lang/crates.io-index" 1301source = "registry+https://github.com/rust-lang/crates.io-index"
1326checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" 1302checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
1327dependencies = [ 1303dependencies = [
1328 "autocfg", 1304 "autocfg",
1329 "bytes", 1305 "bytes",
@@ -1346,7 +1322,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
1346dependencies = [ 1322dependencies = [
1347 "proc-macro2", 1323 "proc-macro2",
1348 "quote", 1324 "quote",
1349 "syn 2.0.15", 1325 "syn",
1350] 1326]
1351 1327
1352[[package]] 1328[[package]]
@@ -1375,9 +1351,9 @@ dependencies = [
1375 1351
1376[[package]] 1352[[package]]
1377name = "toml" 1353name = "toml"
1378version = "0.7.3" 1354version = "0.7.4"
1379source = "registry+https://github.com/rust-lang/crates.io-index" 1355source = "registry+https://github.com/rust-lang/crates.io-index"
1380checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" 1356checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
1381dependencies = [ 1357dependencies = [
1382 "serde", 1358 "serde",
1383 "serde_spanned", 1359 "serde_spanned",
@@ -1387,18 +1363,18 @@ dependencies = [
1387 1363
1388[[package]] 1364[[package]]
1389name = "toml_datetime" 1365name = "toml_datetime"
1390version = "0.6.1" 1366version = "0.6.2"
1391source = "registry+https://github.com/rust-lang/crates.io-index" 1367source = "registry+https://github.com/rust-lang/crates.io-index"
1392checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" 1368checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
1393dependencies = [ 1369dependencies = [
1394 "serde", 1370 "serde",
1395] 1371]
1396 1372
1397[[package]] 1373[[package]]
1398name = "toml_edit" 1374name = "toml_edit"
1399version = "0.19.8" 1375version = "0.19.10"
1400source = "registry+https://github.com/rust-lang/crates.io-index" 1376source = "registry+https://github.com/rust-lang/crates.io-index"
1401checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" 1377checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
1402dependencies = [ 1378dependencies = [
1403 "indexmap", 1379 "indexmap",
1404 "serde", 1380 "serde",
@@ -1415,19 +1391,20 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
1415 1391
1416[[package]] 1392[[package]]
1417name = "tracing" 1393name = "tracing"
1418version = "0.1.38" 1394version = "0.1.37"
1419source = "registry+https://github.com/rust-lang/crates.io-index" 1395source = "registry+https://github.com/rust-lang/crates.io-index"
1420checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" 1396checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
1421dependencies = [ 1397dependencies = [
1398 "cfg-if",
1422 "pin-project-lite", 1399 "pin-project-lite",
1423 "tracing-core", 1400 "tracing-core",
1424] 1401]
1425 1402
1426[[package]] 1403[[package]]
1427name = "tracing-core" 1404name = "tracing-core"
1428version = "0.1.30" 1405version = "0.1.31"
1429source = "registry+https://github.com/rust-lang/crates.io-index" 1406source = "registry+https://github.com/rust-lang/crates.io-index"
1430checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1407checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
1431dependencies = [ 1408dependencies = [
1432 "once_cell", 1409 "once_cell",
1433] 1410]
@@ -1446,9 +1423,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
1446 1423
1447[[package]] 1424[[package]]
1448name = "unicode-ident" 1425name = "unicode-ident"
1449version = "1.0.8" 1426version = "1.0.9"
1450source = "registry+https://github.com/rust-lang/crates.io-index" 1427source = "registry+https://github.com/rust-lang/crates.io-index"
1451checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1428checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
1452 1429
1453[[package]] 1430[[package]]
1454name = "unicode-normalization" 1431name = "unicode-normalization"
@@ -1518,9 +1495,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1518 1495
1519[[package]] 1496[[package]]
1520name = "wasm-bindgen" 1497name = "wasm-bindgen"
1521version = "0.2.84" 1498version = "0.2.86"
1522source = "registry+https://github.com/rust-lang/crates.io-index" 1499source = "registry+https://github.com/rust-lang/crates.io-index"
1523checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" 1500checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
1524dependencies = [ 1501dependencies = [
1525 "cfg-if", 1502 "cfg-if",
1526 "wasm-bindgen-macro", 1503 "wasm-bindgen-macro",
@@ -1528,24 +1505,24 @@ dependencies = [
1528 1505
1529[[package]] 1506[[package]]
1530name = "wasm-bindgen-backend" 1507name = "wasm-bindgen-backend"
1531version = "0.2.84" 1508version = "0.2.86"
1532source = "registry+https://github.com/rust-lang/crates.io-index" 1509source = "registry+https://github.com/rust-lang/crates.io-index"
1533checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" 1510checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
1534dependencies = [ 1511dependencies = [
1535 "bumpalo", 1512 "bumpalo",
1536 "log", 1513 "log",
1537 "once_cell", 1514 "once_cell",
1538 "proc-macro2", 1515 "proc-macro2",
1539 "quote", 1516 "quote",
1540 "syn 1.0.109", 1517 "syn",
1541 "wasm-bindgen-shared", 1518 "wasm-bindgen-shared",
1542] 1519]
1543 1520
1544[[package]] 1521[[package]]
1545name = "wasm-bindgen-futures" 1522name = "wasm-bindgen-futures"
1546version = "0.4.34" 1523version = "0.4.36"
1547source = "registry+https://github.com/rust-lang/crates.io-index" 1524source = "registry+https://github.com/rust-lang/crates.io-index"
1548checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" 1525checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e"
1549dependencies = [ 1526dependencies = [
1550 "cfg-if", 1527 "cfg-if",
1551 "js-sys", 1528 "js-sys",
@@ -1555,9 +1532,9 @@ dependencies = [
1555 1532
1556[[package]] 1533[[package]]
1557name = "wasm-bindgen-macro" 1534name = "wasm-bindgen-macro"
1558version = "0.2.84" 1535version = "0.2.86"
1559source = "registry+https://github.com/rust-lang/crates.io-index" 1536source = "registry+https://github.com/rust-lang/crates.io-index"
1560checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" 1537checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
1561dependencies = [ 1538dependencies = [
1562 "quote", 1539 "quote",
1563 "wasm-bindgen-macro-support", 1540 "wasm-bindgen-macro-support",
@@ -1565,22 +1542,22 @@ dependencies = [
1565 1542
1566[[package]] 1543[[package]]
1567name = "wasm-bindgen-macro-support" 1544name = "wasm-bindgen-macro-support"
1568version = "0.2.84" 1545version = "0.2.86"
1569source = "registry+https://github.com/rust-lang/crates.io-index" 1546source = "registry+https://github.com/rust-lang/crates.io-index"
1570checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" 1547checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
1571dependencies = [ 1548dependencies = [
1572 "proc-macro2", 1549 "proc-macro2",
1573 "quote", 1550 "quote",
1574 "syn 1.0.109", 1551 "syn",
1575 "wasm-bindgen-backend", 1552 "wasm-bindgen-backend",
1576 "wasm-bindgen-shared", 1553 "wasm-bindgen-shared",
1577] 1554]
1578 1555
1579[[package]] 1556[[package]]
1580name = "wasm-bindgen-shared" 1557name = "wasm-bindgen-shared"
1581version = "0.2.84" 1558version = "0.2.86"
1582source = "registry+https://github.com/rust-lang/crates.io-index" 1559source = "registry+https://github.com/rust-lang/crates.io-index"
1583checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" 1560checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
1584 1561
1585[[package]] 1562[[package]]
1586name = "wasm-streams" 1563name = "wasm-streams"
@@ -1597,9 +1574,9 @@ dependencies = [
1597 1574
1598[[package]] 1575[[package]]
1599name = "web-sys" 1576name = "web-sys"
1600version = "0.3.61" 1577version = "0.3.63"
1601source = "registry+https://github.com/rust-lang/crates.io-index" 1578source = "registry+https://github.com/rust-lang/crates.io-index"
1602checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" 1579checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
1603dependencies = [ 1580dependencies = [
1604 "js-sys", 1581 "js-sys",
1605 "wasm-bindgen", 1582 "wasm-bindgen",
@@ -1622,15 +1599,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1622checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1599checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1623 1600
1624[[package]] 1601[[package]]
1625name = "winapi-util"
1626version = "0.1.5"
1627source = "registry+https://github.com/rust-lang/crates.io-index"
1628checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1629dependencies = [
1630 "winapi",
1631]
1632
1633[[package]]
1634name = "winapi-x86_64-pc-windows-gnu" 1602name = "winapi-x86_64-pc-windows-gnu"
1635version = "0.4.0" 1603version = "0.4.0"
1636source = "registry+https://github.com/rust-lang/crates.io-index" 1604source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1794,9 +1762,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
1794 1762
1795[[package]] 1763[[package]]
1796name = "winnow" 1764name = "winnow"
1797version = "0.4.1" 1765version = "0.4.6"
1798source = "registry+https://github.com/rust-lang/crates.io-index" 1766source = "registry+https://github.com/rust-lang/crates.io-index"
1799checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" 1767checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
1800dependencies = [ 1768dependencies = [
1801 "memchr", 1769 "memchr",
1802] 1770]
diff --git a/Cargo.toml b/Cargo.toml
index 345f60d..c0fca21 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "modlist" 2name = "modlist"
3version = "0.14.2" 3version = "0.15.0"
4edition = "2021" 4edition = "2021"
5 5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -18,3 +18,4 @@ error-chain = "0.12.4"
18dirs = "5.0.0" 18dirs = "5.0.0"
19clap = { version = "4.2.1", features = ["derive"] } 19clap = { version = "4.2.1", features = ["derive"] }
20clap_complete = "4.2.0" 20clap_complete = "4.2.0"
21indicatif = "0.17.3"
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs
index 525cc0d..9a22633 100644
--- a/src/apis/modrinth.rs
+++ b/src/apis/modrinth.rs
@@ -127,10 +127,13 @@ pub enum GameVersionType {
127 release, 127 release,
128 snapshot, 128 snapshot,
129 alpha, 129 alpha,
130 beta 130 beta,
131} 131}
132 132
133async fn get(api: &str, path: &str) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> { 133async fn get(
134 api: &str,
135 path: &str,
136) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
134 let url = format!(r#"{}{}"#, api, path); 137 let url = format!(r#"{}{}"#, api, path);
135 138
136 let client = Client::builder() 139 let client = Client::builder()
@@ -170,7 +173,7 @@ pub async fn projects(api: &str, ids: Vec<String>) -> Vec<Project> {
170pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> { 173pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
171 let url = format!( 174 let url = format!(
172 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, 175 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#,
173 id, list.modloader.to_string(), list.mc_version 176 id, list.modloader, list.mc_version
174 ); 177 );
175 178
176 let data = get(api, &url).await.unwrap(); 179 let data = get(api, &url).await.unwrap();
@@ -182,7 +185,10 @@ pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
182} 185}
183 186
184///Get version with the version ids 187///Get version with the version ids
185pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { 188pub async fn get_raw_versions(
189 api: &str,
190 versions: Vec<String>,
191) -> Vec<Version> {
186 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); 192 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
187 193
188 let data = get(api, &url).await.unwrap().unwrap(); 194 let data = get(api, &url).await.unwrap().unwrap();
@@ -208,7 +214,10 @@ pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> {
208} 214}
209 215
210pub async fn get_game_versions() -> Vec<GameVersion> { 216pub async fn get_game_versions() -> Vec<GameVersion> {
211 let data = get("https://api.modrinth.com/v2/", "tag/game_version").await.unwrap().unwrap(); 217 let data = get("https://api.modrinth.com/v2/", "tag/game_version")
218 .await
219 .unwrap()
220 .unwrap();
212 221
213 serde_json::from_slice(&data).unwrap() 222 serde_json::from_slice(&data).unwrap()
214} 223}
diff --git a/src/cache.rs b/src/cache.rs
index c928670..8df4d2f 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -31,7 +31,7 @@ pub fn get_cached_versions(path: &str) -> HashMap<String, String> {
31/// Panics if . 31/// Panics if .
32pub fn copy_cached_version(version_path: &str, download_path: &str) { 32pub fn copy_cached_version(version_path: &str, download_path: &str) {
33 let versplit: Vec<&str> = version_path.split('/').collect(); 33 let versplit: Vec<&str> = version_path.split('/').collect();
34 let download = format!("{}/{}", download_path, versplit[versplit.len() - 1]); 34 let download =
35 // println!("{:#?}", download); 35 format!("{}/{}", download_path, versplit[versplit.len() - 1]);
36 copy(version_path, download).unwrap(); 36 copy(version_path, download).unwrap();
37} 37}
diff --git a/src/commands/download.rs b/src/commands/download.rs
index ebfb4eb..a7cf744 100644
--- a/src/commands/download.rs
+++ b/src/commands/download.rs
@@ -1,26 +1,48 @@
1use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2
1use crate::{config::Cfg, List}; 3use crate::{config::Cfg, List};
2use crate::{ 4use crate::{
3 db::userlist_get_all_current_versions_with_mods, 5 db::userlist_get_all_current_versions_with_mods,
4 error::{ErrorType, MLError, MLE}, 6 error::{ErrorType, MLError, MLE},
5 files::{ 7 files::{
6 clean_list_dir, delete_version, disable_version, download_versions, get_downloaded_versions, 8 clean_list_dir, delete_version, disable_version, download_versions,
9 get_downloaded_versions,
7 }, 10 },
8 modrinth::get_raw_versions, 11 modrinth::get_raw_versions,
9}; 12};
13use crate::{PROGRESS_CHARS, STYLE_BAR_POS};
10 14
11pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old: bool) -> MLE<()> { 15pub async fn download(
16 config: &Cfg,
17 liststack: Vec<List>,
18 clean: bool,
19 delete_old: bool,
20) -> MLE<()> {
21 let mp = MultiProgress::new();
22 let download_p =
23 mp.add(ProgressBar::new(liststack.len().try_into().unwrap()));
24 download_p.set_style(
25 ProgressStyle::with_template(STYLE_BAR_POS)
26 .unwrap()
27 .progress_chars(PROGRESS_CHARS),
28 );
12 29
13 for current_list in liststack { 30 for current_list in liststack {
14 println!("Downloading current versions of mods in {}", current_list.id); 31 download_p.set_message(format!("Download in {}", current_list.id));
15 let downloaded_versions = get_downloaded_versions(current_list.clone())?; 32
16 // println!("To download: {:#?}", downloaded_versions); 33 let downloaded_versions =
17 let current_version_ids = match userlist_get_all_current_versions_with_mods( 34 get_downloaded_versions(current_list.clone())?;
18 config.clone(), 35 let current_version_ids =
19 String::from(&current_list.id), 36 match userlist_get_all_current_versions_with_mods(
20 ) { 37 config,
21 Ok(i) => Ok(i), 38 String::from(&current_list.id),
22 Err(e) => Err(MLError::new(ErrorType::DBError, e.to_string().as_str())), 39 ) {
23 }?; 40 Ok(i) => Ok(i),
41 Err(e) => Err(MLError::new(
42 ErrorType::DBError,
43 e.to_string().as_str(),
44 )),
45 }?;
24 46
25 let mut to_download: Vec<String> = vec![]; 47 let mut to_download: Vec<String> = vec![];
26 //(mod_id, version_id) 48 //(mod_id, version_id)
@@ -39,7 +61,10 @@ pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old
39 .ok_or("SOMETHING_HAS_REALLY_GONE_WRONG") 61 .ok_or("SOMETHING_HAS_REALLY_GONE_WRONG")
40 .unwrap(); 62 .unwrap();
41 if &current_version != downloaded_version { 63 if &current_version != downloaded_version {
42 to_disable.push((mod_id.clone(), String::from(downloaded_version))); 64 to_disable.push((
65 mod_id.clone(),
66 String::from(downloaded_version),
67 ));
43 to_download.push(current_version); 68 to_download.push(current_version);
44 } 69 }
45 } 70 }
@@ -54,23 +79,57 @@ pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old
54 current_list.clone(), 79 current_list.clone(),
55 config.clone(), 80 config.clone(),
56 get_raw_versions(&config.apis.modrinth, to_download).await, 81 get_raw_versions(&config.apis.modrinth, to_download).await,
82 &mp,
83 &download_p,
57 ) 84 )
58 .await?; 85 .await?;
59 } else { 86 } else {
60 println!("There are no new versions to download"); 87 download_p.println(format!(
88 "There are no new versions to download for {}",
89 current_list.id
90 ));
61 } 91 }
62 92
63 if !to_disable.is_empty() { 93 if !to_disable.is_empty() {
94 let d_p = mp.insert_before(
95 &download_p,
96 ProgressBar::new(to_disable.len().try_into().unwrap()),
97 );
98 d_p.set_style(
99 ProgressStyle::with_template(STYLE_BAR_POS)
100 .unwrap()
101 .progress_chars(PROGRESS_CHARS),
102 );
64 for ver in to_disable { 103 for ver in to_disable {
65 if delete_old { 104 if delete_old {
66 println!("Deleting version {} for mod {}", ver.1, ver.0); 105 d_p.set_message(format!("Delete version {}", ver.1));
67 delete_version(current_list.clone(), ver.1)?; 106 d_p.inc(1);
107 delete_version(&current_list, ver.1)?;
68 } else { 108 } else {
69 disable_version(config.clone(), current_list.clone(), ver.1, ver.0)?; 109 d_p.set_message(format!("Disable version {}", ver.1));
110 d_p.inc(1);
111 disable_version(
112 config,
113 current_list.clone(),
114 ver.1,
115 ver.0,
116 )?;
70 }; 117 };
71 } 118 }
119
120 let del_msg = if delete_old {
121 "Deleted all old versions"
122 } else {
123 "Disabled all old versions"
124 };
125
126 d_p.finish_with_message(del_msg);
72 } 127 }
128
129 download_p.inc(1);
73 } 130 }
74 131
132 download_p.finish_with_message("Downloaded all lists");
133
75 Ok(()) 134 Ok(())
76} 135}
diff --git a/src/commands/io.rs b/src/commands/io.rs
index dd294bc..8e44b2b 100644
--- a/src/commands/io.rs
+++ b/src/commands/io.rs
@@ -1,12 +1,16 @@
1use indicatif::{ProgressBar, ProgressStyle};
1use serde::{Deserialize, Serialize}; 2use serde::{Deserialize, Serialize};
2use std::fs::File; 3use std::fs::File;
3use std::io::prelude::*; 4use std::io::prelude::*;
4 5
5use crate::{ 6use crate::{
6 config::Cfg, 7 config::Cfg,
7 db::{lists_get, lists_get_all_ids, lists_insert, userlist_get_set_version, userlist_get_all_ids, userlist_get_current_version}, 8 db::{
9 lists_get, lists_get_all_ids, lists_insert, userlist_get_all_ids,
10 userlist_get_current_version, userlist_get_set_version,
11 },
8 error::MLE, 12 error::MLE,
9 mod_add, IDSelector, List, Modloader, AddMod, 13 mod_add, AddMod, IDSelector, List, Modloader, STYLE_OPERATION,
10}; 14};
11 15
12#[derive(Debug, Serialize, Deserialize)] 16#[derive(Debug, Serialize, Deserialize)]
@@ -17,14 +21,14 @@ struct Export {
17#[derive(Debug, Serialize, Deserialize)] 21#[derive(Debug, Serialize, Deserialize)]
18struct ExportVersion { 22struct ExportVersion {
19 version: String, 23 version: String,
20 set: bool 24 set: bool,
21} 25}
22 26
23impl ExportVersion { 27impl ExportVersion {
24 fn from(config: Cfg, list_id: &str, mod_id: &str) -> MLE<Self> { 28 fn from(config: &Cfg, list_id: &str, mod_id: &str) -> MLE<Self> {
25 Ok(Self { 29 Ok(Self {
26 version: userlist_get_current_version(config.clone(), list_id, mod_id)?, 30 version: userlist_get_current_version(config, list_id, mod_id)?,
27 set: userlist_get_set_version(config.clone(), list_id, mod_id)? 31 set: userlist_get_set_version(config, list_id, mod_id)?,
28 }) 32 })
29 } 33 }
30} 34}
@@ -39,18 +43,18 @@ struct ExportList {
39} 43}
40 44
41impl ExportList { 45impl ExportList {
42 pub fn from(config: Cfg, list_id: String, download: bool) -> MLE<Self> { 46 pub fn from(config: &Cfg, list_id: &str, download: bool) -> MLE<Self> {
43 let list = lists_get(config.clone(), String::from(&list_id))?; 47 let list = lists_get(config, list_id)?;
44 48
45 let mut dl_folder = None; 49 let mut dl_folder = None;
46 if download { 50 if download {
47 dl_folder = Some(list.download_folder) 51 dl_folder = Some(list.download_folder)
48 }; 52 };
49 53
50 let mods = userlist_get_all_ids(config.clone(), &list_id)?; 54 let mods = userlist_get_all_ids(config, list_id)?;
51 let mut versions = vec![]; 55 let mut versions = vec![];
52 for m in mods { 56 for m in mods {
53 versions.push(ExportVersion::from(config.clone(), &list_id, &m)?) 57 versions.push(ExportVersion::from(config, list_id, &m)?)
54 } 58 }
55 59
56 Ok(Self { 60 Ok(Self {
@@ -63,29 +67,46 @@ impl ExportList {
63 } 67 }
64} 68}
65 69
66pub fn export(config: Cfg, list: Option<String>) -> MLE<()> { 70pub fn export(config: &Cfg, list: Option<String>) -> MLE<()> {
71 let progress = ProgressBar::new_spinner();
72 progress.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
73
67 let mut list_ids: Vec<String> = vec![]; 74 let mut list_ids: Vec<String> = vec![];
68 if list.is_none() { 75 if list.is_none() {
69 list_ids = lists_get_all_ids(config.clone())?; 76 list_ids = lists_get_all_ids(config)?;
70 } else { 77 } else {
71 list_ids.push(lists_get(config.clone(), list.unwrap())?.id); 78 list_ids.push(lists_get(config, &list.unwrap())?.id);
72 } 79 }
80
73 let mut lists: Vec<ExportList> = vec![]; 81 let mut lists: Vec<ExportList> = vec![];
74 for list_id in list_ids { 82 for list_id in list_ids {
75 lists.push(ExportList::from(config.clone(), list_id, true)?); 83 progress.set_message(format!("Export {}", list_id));
84 //TODO download option/ new download on import
85 lists.push(ExportList::from(config, &list_id, true)?);
76 } 86 }
77 87
78 let toml = toml::to_string(&Export { lists })?; 88 let toml = toml::to_string(&Export { lists })?;
79 89
80 let filestr = dirs::home_dir().unwrap().join("mlexport.toml"); 90 let filestr = dirs::home_dir()
91 .unwrap()
92 .join("mlexport.toml")
93 .into_os_string()
94 .into_string()
95 .unwrap();
81 96
82 let mut file = File::create(filestr.into_os_string().into_string().unwrap().as_str())?; 97 progress.set_message("Create file");
98 let mut file = File::create(&filestr)?;
83 file.write_all(toml.as_bytes())?; 99 file.write_all(toml.as_bytes())?;
100 progress.finish_with_message(format!("Exported to {}", filestr));
84 101
85 Ok(()) 102 Ok(())
86} 103}
87 104
88pub async fn import(config: Cfg, file_str: String, direct_download: bool) -> MLE<()> { 105pub async fn import(
106 config: &Cfg,
107 file_str: &str,
108 direct_download: bool,
109) -> MLE<()> {
89 let mut file = File::open(file_str)?; 110 let mut file = File::open(file_str)?;
90 let mut content = String::new(); 111 let mut content = String::new();
91 file.read_to_string(&mut content)?; 112 file.read_to_string(&mut content)?;
@@ -99,18 +120,21 @@ pub async fn import(config: Cfg, file_str: String, direct_download: bool) -> MLE
99 download_folder: exportlist.download_folder.ok_or("NO_DL").unwrap(), 120 download_folder: exportlist.download_folder.ok_or("NO_DL").unwrap(),
100 }; 121 };
101 lists_insert( 122 lists_insert(
102 config.clone(), 123 config,
103 list.id.clone(), 124 &list.id,
104 list.mc_version.clone(), 125 &list.mc_version,
105 list.modloader.clone(), 126 &list.modloader,
106 String::from(&list.download_folder), 127 &list.download_folder,
107 )?; 128 )?;
108 129
109 let mut ver_ids = vec![]; 130 let mut ver_ids = vec![];
110 for id in exportlist.versions { 131 for id in exportlist.versions {
111 ver_ids.push(AddMod { id: IDSelector::VersionID(id.version), set_version: id.set} ); 132 ver_ids.push(AddMod {
133 id: IDSelector::VersionID(id.version),
134 set_version: id.set,
135 });
112 } 136 }
113 mod_add(config.clone(), ver_ids, list, direct_download).await?; 137 mod_add(config, ver_ids, list, direct_download).await?;
114 } 138 }
115 Ok(()) 139 Ok(())
116} 140}
diff --git a/src/commands/list.rs b/src/commands/list.rs
index 4aa4306..3665446 100644
--- a/src/commands/list.rs
+++ b/src/commands/list.rs
@@ -1,11 +1,13 @@
1use indicatif::{ProgressBar, ProgressStyle};
2
1use crate::{ 3use crate::{
2 config::Cfg, 4 config::Cfg,
3 db::{ 5 db::{
4 config_change_current_list, config_get_current_list, lists_get, lists_insert, lists_remove, 6 config_change_current_list, config_get_current_list, lists_get,
5 lists_version, lists_get_all_ids, 7 lists_get_all_ids, lists_insert, lists_remove, lists_version,
6 }, 8 },
7 error::{MLE, MLError, ErrorType}, 9 error::{ErrorType, MLError, MLE},
8 update, Modloader, 10 update, Modloader, STYLE_OPERATION,
9}; 11};
10 12
11#[derive(Debug, Clone, PartialEq, Eq)] 13#[derive(Debug, Clone, PartialEq, Eq)]
@@ -16,31 +18,47 @@ pub struct List {
16 pub download_folder: String, 18 pub download_folder: String,
17} 19}
18 20
19pub fn get_current_list(config: Cfg) -> MLE<List> { 21pub fn get_current_list(config: &Cfg) -> MLE<List> {
20 let id = config_get_current_list(config.clone())?; 22 let id = config_get_current_list(config)?;
21 lists_get(config, id) 23 lists_get(config, &id)
22} 24}
23 25
24pub fn list_add( 26pub fn list_add(
25 config: Cfg, 27 config: &Cfg,
26 id: String, 28 id: &str,
27 mc_version: String, 29 mc_version: &str,
28 modloader: Modloader, 30 modloader: &Modloader,
29 directory: String, 31 directory: &str,
30) -> MLE<()> { 32) -> MLE<()> {
31 lists_insert(config, id, mc_version, modloader, directory) 33 let p = ProgressBar::new_spinner();
34 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
35 p.set_message(format!("Create {}", id));
36 lists_insert(config, id, mc_version, modloader, directory)?;
37 p.finish_with_message(format!("Created {}", id));
38 Ok(())
32} 39}
33 40
34pub fn list_change(config: Cfg, id: String) -> MLE<()> { 41pub fn list_change(config: &Cfg, id: &str) -> MLE<()> {
35 if lists_get_all_ids(config.clone())?.into_iter().find(|l| l == &id).is_none() { 42 let p = ProgressBar::new_spinner();
43 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
44 p.set_message(format!("Change default list to {}", id));
45
46 if !lists_get_all_ids(config)?.into_iter().any(|l| l == id) {
36 return Err(MLError::new(ErrorType::ArgumentError, "List not found")); 47 return Err(MLError::new(ErrorType::ArgumentError, "List not found"));
37 }; 48 };
38 println!("Change default list to: {}", id); 49 config_change_current_list(config, id)?;
39 config_change_current_list(config, id) 50
51 p.finish_with_message(format!("Changed default list to {}", id));
52 Ok(())
40} 53}
41 54
42pub fn list_remove(config: Cfg, id: String) -> MLE<()> { 55pub fn list_remove(config: &Cfg, id: &str) -> MLE<()> {
43 lists_remove(config, id) 56 let p = ProgressBar::new_spinner();
57 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
58 p.set_message(format!("Remove {}", id));
59 lists_remove(config, id)?;
60 p.finish_with_message(format!("Removed {}", id));
61 Ok(())
44} 62}
45 63
46///Changing the current lists version and updating it 64///Changing the current lists version and updating it
@@ -50,31 +68,34 @@ pub fn list_remove(config: Cfg, id: String) -> MLE<()> {
50/// * `config` - The current config 68/// * `config` - The current config
51/// * `args` - All args, to extract the new version 69/// * `args` - All args, to extract the new version
52pub async fn list_version( 70pub async fn list_version(
53 config: Cfg, 71 config: &Cfg,
54 id: String, 72 id: &str,
55 mc_version: String, 73 mc_version: String,
56 download: bool, 74 download: bool,
57 delete: bool, 75 delete: bool,
58) -> MLE<()> { 76) -> MLE<()> {
59 println!( 77 let p = ProgressBar::new_spinner();
78 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
79 p.set_message(format!(
60 "Change version for list {} to minecraft version: {}", 80 "Change version for list {} to minecraft version: {}",
61 id, mc_version 81 id, mc_version
62 ); 82 ));
63 83
64 lists_version(config.clone(), &id, &mc_version)?; 84 lists_version(config, id, &mc_version)?;
85
86 p.finish_with_message(format!(
87 "Changed version for list {} to minecraft version: {}",
88 id, mc_version
89 ));
65 90
66 println!( 91 let list = lists_get(config, id)?;
67 "\nCheck for updates for new minecraft version in list {}",
68 id
69 );
70 let list = lists_get(config.clone(), id)?;
71 update(config, vec![list], true, download, delete).await 92 update(config, vec![list], true, download, delete).await
72} 93}
73 94
74pub fn list_list(config: Cfg) -> MLE<()> { 95pub fn list_list(config: &Cfg) -> MLE<()> {
75 let lists = lists_get_all_ids(config.clone())?; 96 let lists = lists_get_all_ids(config)?;
76 for list in lists { 97 for list in lists {
77 let l = lists_get(config.clone(), list)?; 98 let l = lists_get(config, &list)?;
78 println!("{}: | {} | {}", l.id, l.mc_version, l.modloader) 99 println!("{}: | {} | {}", l.id, l.mc_version, l.modloader)
79 } 100 }
80 Ok(()) 101 Ok(())
diff --git a/src/commands/modification.rs b/src/commands/modification.rs
index 9a1a651..4488b70 100644
--- a/src/commands/modification.rs
+++ b/src/commands/modification.rs
@@ -1,24 +1,30 @@
1use std::{io::Write, collections::HashMap}; 1use std::collections::HashMap;
2
3use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2 4
3use crate::{ 5use crate::{
4 config::Cfg, 6 config::Cfg,
5 db::{ 7 db::{
6 lists_get_all_ids, mods_get_id, mods_insert, mods_remove, userlist_get_all_ids, 8 lists_get_all_ids, mods_get_id, mods_get_info, mods_insert,
7 userlist_get_current_version, userlist_insert, userlist_remove, mods_get_info, 9 mods_remove, userlist_get_all_ids, userlist_get_current_version,
10 userlist_insert, userlist_remove,
8 }, 11 },
9 error::{ErrorType, MLError, MLE}, 12 error::{ErrorType, MLError, MLE},
10 files::{delete_version, download_versions}, 13 files::{delete_version, download_versions},
11 modrinth::{extract_current_version, get_raw_versions, project, projects, versions, Version}, 14 modrinth::{
12 List, 15 extract_current_version, get_raw_versions, project, projects, versions,
16 Version,
17 },
18 List, PROGRESS_CHARS, STYLE_BAR_POS, STYLE_OPERATION,
13}; 19};
14 20
15#[derive(Debug, Clone)] 21#[derive(Debug)]
16pub struct AddMod { 22pub struct AddMod {
17 pub id: IDSelector, 23 pub id: IDSelector,
18 pub set_version: bool 24 pub set_version: bool,
19} 25}
20 26
21#[derive(Debug, Clone, PartialEq, Eq)] 27#[derive(Debug, PartialEq, Eq)]
22pub enum IDSelector { 28pub enum IDSelector {
23 ModificationID(String), 29 ModificationID(String),
24 VersionID(String), 30 VersionID(String),
@@ -36,54 +42,76 @@ pub struct ProjectInfo {
36} 42}
37 43
38pub async fn mod_add( 44pub async fn mod_add(
39 config: Cfg, 45 config: &Cfg,
40 mods: Vec<AddMod>, 46 mods: Vec<AddMod>,
41 list: List, 47 list: List,
42 direct_download: bool, 48 direct_download: bool,
43) -> MLE<()> { 49) -> MLE<()> {
44 println!("Add mods to {}", list.id); 50 let mp = MultiProgress::new();
45 println!(" └Add mods:");
46 51
47 let mut mod_ids: Vec<(String, bool)> = Vec::new(); 52 let mut mod_ids: Vec<(String, bool)> = Vec::new();
48 let mut ver_ids: Vec<(String, bool)> = Vec::new(); 53 let mut ver_ids: Vec<(String, bool)> = Vec::new();
49 54
55 let add_p = mp.add(ProgressBar::new(mods.len().try_into().unwrap()));
56 add_p.set_style(
57 ProgressStyle::with_template(STYLE_BAR_POS)
58 .unwrap()
59 .progress_chars(PROGRESS_CHARS),
60 );
61 add_p.set_message("Sort ids");
62
50 //"Sort" project ids from version ids to be able to handle them differently but in a batch 63 //"Sort" project ids from version ids to be able to handle them differently but in a batch
51 for m in mods { 64 for m in mods {
65 add_p.inc(1);
52 match m.id { 66 match m.id {
53 IDSelector::ModificationID(pid) => mod_ids.push((pid, m.set_version)), 67 IDSelector::ModificationID(pid) => {
68 mod_ids.push((pid, m.set_version))
69 }
54 IDSelector::VersionID(vid) => ver_ids.push((vid, m.set_version)), 70 IDSelector::VersionID(vid) => ver_ids.push((vid, m.set_version)),
55 } 71 }
56 } 72 }
57 73
74 add_p.set_message("Get infos");
75
58 let mut projectinfo: Vec<ProjectInfo> = Vec::new(); 76 let mut projectinfo: Vec<ProjectInfo> = Vec::new();
59 if !mod_ids.is_empty() { 77 if !mod_ids.is_empty() {
60 projectinfo.append(&mut get_mod_infos(config.clone(), mod_ids, list.clone()).await?) 78 projectinfo
79 .append(&mut get_mod_infos(config, mod_ids, list.clone()).await?);
61 }; 80 };
62 if !ver_ids.is_empty() { 81 if !ver_ids.is_empty() {
63 projectinfo.append(&mut get_ver_info(config.clone(), ver_ids).await?) 82 projectinfo.append(&mut get_ver_info(config, ver_ids).await?);
64 }; 83 };
65 84
66 if projectinfo.is_empty() { 85 if projectinfo.is_empty() {
67 return Err(MLError::new(ErrorType::ArgumentError, "NO_IDS?")); 86 return Err(MLError::new(ErrorType::ArgumentError, "NO_IDS?"));
68 }; 87 };
69 88
89 add_p.set_message("Add mods to database");
90
70 let mut downloadstack: Vec<Version> = Vec::new(); 91 let mut downloadstack: Vec<Version> = Vec::new();
71 92
72 //Adding each mod to the lists and downloadstack 93 //Adding each mod to the lists and downloadstack
73 if projectinfo.len() == 1 { 94 let project_p = mp.insert_before(
74 println!(" └Insert mod in list {} and save infos", list.id); 95 &add_p,
75 } else { 96 ProgressBar::new(projectinfo.len().try_into().unwrap()),
76 println!(" └Insert mods in list {} and save infos", list.id); 97 );
77 } 98 project_p.set_style(
99 ProgressStyle::with_template(STYLE_BAR_POS)
100 .unwrap()
101 .progress_chars(PROGRESS_CHARS),
102 );
78 103
79 for project in projectinfo { 104 for project in projectinfo {
105 project_p.set_message(format!("Add {}", project.title));
106
80 let current_version_id = if project.current_version.is_none() { 107 let current_version_id = if project.current_version.is_none() {
81 String::from("NONE") 108 String::from("NONE")
82 } else { 109 } else {
83 project.current_version.clone().unwrap().id 110 project.current_version.clone().unwrap().id
84 }; 111 };
112
85 match userlist_insert( 113 match userlist_insert(
86 config.clone(), 114 config,
87 &list.id, 115 &list.id,
88 &project.mod_id, 116 &project.mod_id,
89 &current_version_id, 117 &current_version_id,
@@ -92,7 +120,10 @@ pub async fn mod_add(
92 project.set_version, 120 project.set_version,
93 ) { 121 ) {
94 Err(e) => { 122 Err(e) => {
95 let expected_err = format!("SQL: UNIQUE constraint failed: {}.mod_id", list.id); 123 let expected_err = format!(
124 "SQL: UNIQUE constraint failed: {}.mod_id",
125 list.id
126 );
96 if e.to_string() == expected_err { 127 if e.to_string() == expected_err {
97 Err(MLError::new( 128 Err(MLError::new(
98 ErrorType::ModError, 129 ErrorType::ModError,
@@ -106,7 +137,7 @@ pub async fn mod_add(
106 }?; 137 }?;
107 138
108 match mods_insert( 139 match mods_insert(
109 config.clone(), 140 config,
110 &project.mod_id, 141 &project.mod_id,
111 &project.slug, 142 &project.slug,
112 &project.title, 143 &project.title,
@@ -124,18 +155,35 @@ pub async fn mod_add(
124 if project.current_version.is_some() { 155 if project.current_version.is_some() {
125 downloadstack.push(project.current_version.unwrap()) 156 downloadstack.push(project.current_version.unwrap())
126 }; 157 };
158
159 project_p.inc(1);
127 } 160 }
128 161
162 project_p.finish_with_message("Added all mods to the database");
163
129 //Download all the added mods 164 //Download all the added mods
130 if direct_download { 165 if direct_download {
131 download_versions(list.clone(), config.clone(), downloadstack).await?; 166 add_p.set_message("Download mods");
167 download_versions(
168 list.clone(),
169 config.clone(),
170 downloadstack,
171 &mp,
172 &add_p,
173 )
174 .await?;
132 }; 175 };
133 176
177 add_p.finish_with_message("Added all mods");
178
134 Ok(()) 179 Ok(())
135} 180}
136 181
137async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) -> MLE<Vec<ProjectInfo>> { 182async fn get_mod_infos(
138 183 config: &Cfg,
184 mod_ids: Vec<(String, bool)>,
185 list: List,
186) -> MLE<Vec<ProjectInfo>> {
139 let mut setmap: HashMap<String, bool> = HashMap::new(); 187 let mut setmap: HashMap<String, bool> = HashMap::new();
140 188
141 let mut ids = vec![]; 189 let mut ids = vec![];
@@ -154,8 +202,6 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
154 _ => panic!("PANIC"), 202 _ => panic!("PANIC"),
155 }; 203 };
156 for project in m_projects { 204 for project in m_projects {
157 println!("\t└{}", project.title);
158 println!("\t └Get versions");
159 let available_versions = versions( 205 let available_versions = versions(
160 &config.apis.modrinth, 206 &config.apis.modrinth,
161 String::from(&project.id), 207 String::from(&project.id),
@@ -167,8 +213,8 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
167 let current_version: Option<Version>; 213 let current_version: Option<Version>;
168 let file: String; 214 let file: String;
169 if !available_versions.is_empty() { 215 if !available_versions.is_empty() {
170 let current_id = extract_current_version(available_versions.clone())?; 216 let current_id =
171 println!("\t └Current version: {}", current_id); 217 extract_current_version(available_versions.clone())?;
172 218
173 current_version = Some( 219 current_version = Some(
174 available_versions 220 available_versions
@@ -177,19 +223,15 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
177 .find(|v| v.id == current_id) 223 .find(|v| v.id == current_id)
178 .unwrap(), 224 .unwrap(),
179 ); 225 );
180 226
181 // match primary, if none? 227 // match primary, if none?
182 let files = current_version 228 let files = current_version.clone().ok_or("").unwrap().files;
183 .clone()
184 .ok_or("")
185 .unwrap()
186 .files;
187 229
188 file = match files.clone().into_iter().find(|f| f.primary) { 230 file = match files.clone().into_iter().find(|f| f.primary) {
189 Some(f) => f, 231 Some(f) => f,
190 None => { files[0].clone() } 232 None => files[0].clone(),
191 } 233 }
192 .url; 234 .url;
193 235
194 for ver in available_versions { 236 for ver in available_versions {
195 available_versions_vec.push(ver.id); 237 available_versions_vec.push(ver.id);
@@ -197,15 +239,14 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
197 239
198 projectinfo.push(ProjectInfo { 240 projectinfo.push(ProjectInfo {
199 mod_id: String::from(&project.id), 241 mod_id: String::from(&project.id),
200 slug: project.slug, 242 slug: project.slug.clone(),
201 title: project.title, 243 title: project.title,
202 current_version, 244 current_version,
203 applicable_versions: available_versions_vec, 245 applicable_versions: available_versions_vec,
204 download_link: file, 246 download_link: file,
205 set_version: setmap.get(&project.id).unwrap().clone(), 247 set_version: *setmap.get(&project.slug).unwrap(),
206 }) 248 })
207 } else { 249 } else {
208 println!("\t └There's currently no mod version for your specified target");
209 current_version = None; 250 current_version = None;
210 file = String::from("NONE"); 251 file = String::from("NONE");
211 available_versions_vec.push(String::from("NONE")); 252 available_versions_vec.push(String::from("NONE"));
@@ -216,7 +257,7 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
216 current_version, 257 current_version,
217 applicable_versions: available_versions_vec, 258 applicable_versions: available_versions_vec,
218 download_link: file, 259 download_link: file,
219 set_version: setmap.get(&project.id).unwrap().clone(), 260 set_version: *setmap.get(&project.id).unwrap(),
220 }) 261 })
221 } 262 }
222 } 263 }
@@ -224,8 +265,10 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
224 Ok(projectinfo) 265 Ok(projectinfo)
225} 266}
226 267
227async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<ProjectInfo>> { 268async fn get_ver_info(
228 269 config: &Cfg,
270 ver_ids: Vec<(String, bool)>,
271) -> MLE<Vec<ProjectInfo>> {
229 let mut setmap: HashMap<String, bool> = HashMap::new(); 272 let mut setmap: HashMap<String, bool> = HashMap::new();
230 273
231 let mut ids = vec![]; 274 let mut ids = vec![];
@@ -248,14 +291,15 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
248 291
249 for (i, project) in v_projects.into_iter().enumerate() { 292 for (i, project) in v_projects.into_iter().enumerate() {
250 let version = &v_versions[i]; 293 let version = &v_versions[i];
251 println!("\t└{}({})", project.title, version.id); 294
252 let file = version 295 let files = version.clone().files;
253 .clone() 296
254 .files 297 let file = match files.clone().into_iter().find(|f| f.primary) {
255 .into_iter() 298 Some(f) => f,
256 .find(|f| f.primary) 299 None => files[0].clone(),
257 .unwrap() 300 }
258 .url; 301 .url;
302
259 projectinfo.push(ProjectInfo { 303 projectinfo.push(ProjectInfo {
260 mod_id: String::from(&project.id), 304 mod_id: String::from(&project.id),
261 slug: project.slug, 305 slug: project.slug,
@@ -263,7 +307,7 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
263 current_version: Some(version.clone()), 307 current_version: Some(version.clone()),
264 applicable_versions: vec![String::from(&version.id)], 308 applicable_versions: vec![String::from(&version.id)],
265 download_link: file, 309 download_link: file,
266 set_version: setmap.get(&version.id).unwrap().clone(), 310 set_version: *setmap.get(&version.id).unwrap(),
267 }) 311 })
268 } 312 }
269 Ok(projectinfo) 313 Ok(projectinfo)
@@ -275,48 +319,45 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
275/// * `config` - config struct 319/// * `config` - config struct
276/// * `id` - name, slug or id of the mod 320/// * `id` - name, slug or id of the mod
277/// * `list` - List struct 321/// * `list` - List struct
278pub fn mod_remove(config: Cfg, id: &str, list: List) -> MLE<()> { 322pub fn mod_remove(config: &Cfg, id: &str, list: &List) -> MLE<()> {
323 let progress = ProgressBar::new_spinner();
324 progress.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
325
279 let mod_id = mods_get_id(&config.data, id)?; 326 let mod_id = mods_get_id(&config.data, id)?;
280 327
281 println!("Remove mod {} from {}", mods_get_info(config.clone(), &mod_id)?.title, list.id); 328 let info = mods_get_info(config, &mod_id)?;
282 let version = userlist_get_current_version(config.clone(), &list.id, &mod_id)?;
283 329
284 print!(" └Remove from list"); 330 progress.set_message(format!("Remove {} from {}", info.title, list.id));
285 //Force flush of stdout, else print! doesn't print instantly
286 std::io::stdout().flush()?;
287 userlist_remove(config.clone(), &list.id, &mod_id)?;
288 println!(" ✓");
289 331
290 print!(" └Delete file"); 332 let version = userlist_get_current_version(config, &list.id, &mod_id)?;
291 //Force flush of stdout, else print! doesn't print instantly 333
292 std::io::stdout().flush()?; 334 userlist_remove(config, &list.id, &mod_id)?;
335
336 progress.set_message("Delete file");
293 match delete_version(list, version) { 337 match delete_version(list, version) {
294 Ok(_) => (), 338 Ok(_) => (),
295 Err(err) => { 339 Err(err) => {
296 if err.to_string() != "User input not accepted: VERSION_NOT_FOUND_IN_FILES" { 340 if err.to_string()
341 != "User input not accepted: VERSION_NOT_FOUND_IN_FILES"
342 {
297 return Err(err); 343 return Err(err);
298 }; 344 };
299 () 345 }
300 },
301 }; 346 };
302 println!(" ✓");
303 347
304 print!(" └Clean main db table"); 348 progress.set_message("Check main list");
305 //Force flush of stdout, else print! doesn't print instantly 349 let list_ids = lists_get_all_ids(config)?;
306 std::io::stdout().flush()?;
307 let list_ids = lists_get_all_ids(config.clone())?;
308 350
309 // Remove mod from main list if not used elsewhere 351 // Remove mod from main list if not used elsewhere
310 let mut mod_used = false; 352 let mut mod_used = false;
311 for id in list_ids { 353 for id in list_ids {
312 let mods = match userlist_get_all_ids(config.clone(), &id) { 354 let mods = match userlist_get_all_ids(config, &id) {
313 Ok(m) => m, 355 Ok(m) => m,
314 Err(err) => { 356 Err(err) => {
315 if err.to_string() == "Database: NO_MODS_USERLIST" { 357 if err.to_string() == "Database: NO_MODS_USERLIST" {
316 println!(" ✓");
317 return Ok(()); 358 return Ok(());
318 }; 359 };
319 return Err(err) 360 return Err(err);
320 } 361 }
321 }; 362 };
322 if mods.contains(&mod_id) { 363 if mods.contains(&mod_id) {
@@ -326,9 +367,14 @@ pub fn mod_remove(config: Cfg, id: &str, list: List) -> MLE<()> {
326 } 367 }
327 368
328 if !mod_used { 369 if !mod_used {
329 mods_remove(config, mod_id)?; 370 progress.set_message("Remove from main list");
371 mods_remove(config, &mod_id)?;
330 }; 372 };
331 println!(" ✓"); 373
374 progress.finish_with_message(format!(
375 "Removed {} from {}",
376 info.title, list.id
377 ));
332 378
333 Ok(()) 379 Ok(())
334} 380}
diff --git a/src/commands/update.rs b/src/commands/update.rs
index d3a282b..c19c02c 100644
--- a/src/commands/update.rs
+++ b/src/commands/update.rs
@@ -1,49 +1,81 @@
1use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2
1use crate::{ 3use crate::{
2 config::Cfg, 4 config::Cfg,
3 db::{ 5 db::{
4 mods_get_info, userlist_change_versions, userlist_get_all_ids, 6 mods_get_info, userlist_change_versions, userlist_get_all_ids,
5 userlist_get_applicable_versions, userlist_get_current_version, userlist_get_set_version, 7 userlist_get_applicable_versions, userlist_get_current_version,
8 userlist_get_set_version,
6 }, 9 },
7 error::{ErrorType, MLError, MLE}, 10 error::{ErrorType, MLError, MLE},
8 files::{clean_list_dir, delete_version, disable_version, download_versions}, 11 files::{
12 clean_list_dir, delete_version, disable_version, download_versions,
13 },
9 modrinth::{extract_current_version, versions, Version}, 14 modrinth::{extract_current_version, versions, Version},
10 List, 15 List, PROGRESS_CHARS, STYLE_BAR_POS, STYLE_OPERATION,
11}; 16};
12 17
13pub async fn update( 18pub async fn update(
14 config: Cfg, 19 config: &Cfg,
15 liststack: Vec<List>, 20 liststack: Vec<List>,
16 clean: bool, 21 clean: bool,
17 direct_download: bool, 22 direct_download: bool,
18 delete_old: bool, 23 delete_old: bool,
19) -> MLE<()> { 24) -> MLE<()> {
25 let mp = MultiProgress::new();
26
27 let update_p =
28 mp.add(ProgressBar::new(liststack.len().try_into().unwrap()));
29 update_p.set_style(
30 ProgressStyle::with_template(STYLE_BAR_POS)
31 .unwrap()
32 .progress_chars(PROGRESS_CHARS),
33 );
34
20 for current_list in liststack { 35 for current_list in liststack {
21 println!("Update mods in {}", current_list.id); 36 update_p.set_message(format!("Update {}", current_list.id));
22 let mods = userlist_get_all_ids(config.clone(), &current_list.id)?;
23 37
24 let mut current_versions: Vec<(String, String)> = vec![]; 38 let list_p = mp.insert_before(&update_p, ProgressBar::new(2));
39 list_p
40 .set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
41 list_p.set_message("Update mods");
42
43 let mods = userlist_get_all_ids(config, &current_list.id)?;
44
45 let list_u_p = mp.insert_before(
46 &list_p,
47 ProgressBar::new(mods.len().try_into().unwrap()),
48 );
49 list_u_p.set_style(
50 ProgressStyle::with_template(STYLE_BAR_POS)
51 .unwrap()
52 .progress_chars(PROGRESS_CHARS),
53 );
25 54
55 let mut current_versions: Vec<(String, String)> = vec![];
26 let mut updatestack: Vec<Version> = vec![]; 56 let mut updatestack: Vec<Version> = vec![];
27 57
28 for id in mods { 58 for id in mods {
29 let info = mods_get_info(config.clone(), &id)?; 59 let info = mods_get_info(config, &id)?;
30 println!(" {}", info.title); 60 list_u_p.set_message(format!("Update {}", info.title));
31 61
32 if userlist_get_set_version(config.clone(), &current_list.id, &id)? { 62 //Skip check if version is set
33 println!(" │ └Set version, skipping update"); 63 if userlist_get_set_version(config, &current_list.id, &id)? {
64 list_u_p.inc(1);
34 continue; 65 continue;
35 } 66 }
36 67
37 //Getting current installed version for disable or delete 68 //Getting current installed version for disable or delete
38 let disable_version = 69 let disable_version =
39 userlist_get_current_version(config.clone(), &current_list.id, &id)?; 70 userlist_get_current_version(config, &current_list.id, &id)?;
40 71
41 updatestack.push( 72 updatestack.push(
42 match specific_update( 73 match specific_update(
43 config.clone(), 74 config,
44 clean, 75 clean,
45 current_list.clone(), 76 current_list.clone(),
46 String::from(&id), 77 &id,
78 &list_u_p,
47 ) 79 )
48 .await 80 .await
49 { 81 {
@@ -53,46 +85,94 @@ pub async fn update(
53 } 85 }
54 Err(e) => { 86 Err(e) => {
55 if e.to_string() == "Mod: NO_UPDATE_AVAILABLE" { 87 if e.to_string() == "Mod: NO_UPDATE_AVAILABLE" {
56 println!(
57 " │ └No new version found for the specified minecraft version"
58 );
59 } else { 88 } else {
60 return Err(e); 89 return Err(e);
61 }; 90 };
91 list_u_p.inc(1);
62 continue; 92 continue;
63 } 93 }
64 }, 94 },
65 ) 95 );
96 list_u_p.inc(1);
66 } 97 }
67 98
99 list_u_p.finish_with_message(format!(
100 "Updated mods in {}",
101 current_list.id
102 ));
103
68 if clean { 104 if clean {
105 list_p.set_message("Cleaning");
69 clean_list_dir(&current_list)?; 106 clean_list_dir(&current_list)?;
70 }; 107 };
71 108
72 if direct_download && !updatestack.is_empty() { 109 if direct_download && !updatestack.is_empty() {
73 download_versions(current_list.clone(), config.clone(), updatestack).await?; 110 download_versions(
111 current_list.clone(),
112 config.clone(),
113 updatestack,
114 &mp,
115 &list_p,
116 )
117 .await?;
74 118
75 //Disable old versions 119 //Disable old versions
76 if !clean { 120 if !clean {
121 let d_p = mp.insert_before(
122 &list_p,
123 ProgressBar::new(
124 current_versions.len().try_into().unwrap(),
125 ),
126 );
127 d_p.set_style(
128 ProgressStyle::with_template(STYLE_BAR_POS)
129 .unwrap()
130 .progress_chars(PROGRESS_CHARS),
131 );
77 for ver in current_versions { 132 for ver in current_versions {
78 if delete_old { 133 if delete_old {
79 println!(" └Delete version {}", ver.0); 134 d_p.set_message(format!("Delete version {}", ver.0));
80 delete_version(current_list.clone(), ver.0)?; 135 d_p.inc(1);
136 delete_version(&current_list, ver.0)?;
81 } else if ver.0 != "NONE" { 137 } else if ver.0 != "NONE" {
82 println!(" └Disable version {}", ver.0); 138 d_p.set_message(format!("Disable version {}", ver.0));
83 disable_version(config.clone(), current_list.clone(), ver.0, ver.1)?; 139 d_p.inc(1);
140 disable_version(
141 config,
142 current_list.clone(),
143 ver.0,
144 ver.1,
145 )?;
84 }; 146 };
85 } 147 }
148
149 let del_msg = if delete_old {
150 "Deleted all old versions"
151 } else {
152 "Disabled all old versions"
153 };
154
155 d_p.finish_with_message(del_msg);
86 } 156 }
87 }; 157 };
158 list_p.finish_with_message(format!("Updated {}", current_list.id));
159 update_p.inc(1);
88 } 160 }
89 161
162 update_p.finish_with_message("Updated all lists");
163
90 Ok(()) 164 Ok(())
91} 165}
92 166
93async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> MLE<Version> { 167async fn specific_update(
168 config: &Cfg,
169 clean: bool,
170 list: List,
171 id: &str,
172 progress: &ProgressBar,
173) -> MLE<Version> {
94 let applicable_versions = 174 let applicable_versions =
95 versions(&config.apis.modrinth, String::from(&id), list.clone()).await; 175 versions(&config.apis.modrinth, String::from(id), list.clone()).await;
96 176
97 let mut versions: Vec<String> = vec![]; 177 let mut versions: Vec<String> = vec![];
98 178
@@ -108,19 +188,19 @@ async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> ML
108 if clean 188 if clean
109 || (versions.join("|") 189 || (versions.join("|")
110 != userlist_get_applicable_versions( 190 != userlist_get_applicable_versions(
111 config.clone(), 191 config,
112 String::from(&list.id), 192 String::from(&list.id),
113 String::from(&id), 193 String::from(id),
114 )?) 194 )?)
115 { 195 {
116 let current_str = extract_current_version(applicable_versions.clone())?; 196 let current_str = extract_current_version(applicable_versions.clone())?;
117 197
118 if clean { 198 if !clean {
119 println!("\t └Add version to downloadstack"); 199 progress.println(format!(
120 } else { 200 "Found new version for {}",
121 println!("\t └Get versions for specified minecraft versions"); 201 mods_get_info(config, id).unwrap().title
122 println!("\t └New current version: {}", current_str); 202 ));
123 }; 203 }
124 204
125 //get new versions 205 //get new versions
126 let current_ver = match applicable_versions 206 let current_ver = match applicable_versions
@@ -133,73 +213,27 @@ async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> ML
133 }?; 213 }?;
134 current.push(current_ver.clone()); 214 current.push(current_ver.clone());
135 215
136 let link = match current_ver 216 let files = &current_ver.files;
137 .files 217
138 .into_iter() 218 let link = match files.clone().into_iter().find(|f| f.primary) {
139 .find(|f| f.primary) 219 Some(f) => f,
140 .ok_or("!no primary in links") 220 None => files[0].clone(),
141 { 221 }
142 Ok(p) => Ok(p),
143 Err(e) => Err(MLError::new(ErrorType::Other, e)),
144 }?
145 .url; 222 .url;
146 userlist_change_versions(config, list.id, current_str, versions.join("|"), link, id)?; 223
224 userlist_change_versions(
225 config,
226 list.id,
227 current_str,
228 versions.join("|"),
229 link,
230 id.to_string(),
231 )?;
147 } 232 }
148 233
149 if current.is_empty() { 234 if current.is_empty() {
150 return Err(MLError::new(ErrorType::ModError, "NO_UPDATE_AVAILABLE")); 235 return Err(MLError::new(ErrorType::ModError, "NO_UPDATE_AVAILABLE"));
151 }; 236 };
152 237
153 //println!(" └✔️");
154 Ok(current[0].clone()) 238 Ok(current[0].clone())
155} 239}
156
157// #[tokio::test]
158// async fn download_updates_test() {
159// use crate::{
160// modrinth::{Hash, Version, VersionFile, VersionType},
161// List, Modloader,
162// };
163//
164// let config = Cfg::init().unwrap();
165// let current_list = List {
166// id: String::from("..."),
167// mc_version: String::from("..."),
168// modloader: Modloader::Fabric,
169// download_folder: String::from("./dev/tests/dl"),
170// };
171//
172// let versions = vec![Version {
173// id: "dEqtGnT9".to_string(),
174// project_id: "kYuIpRLv".to_string(),
175// author_id: "Qnt13hO8".to_string(),
176// featured: true,
177// name: "1.2.2-1.19 - Fabric".to_string(),
178// version_number: "1.2.2-1.19".to_string(),
179// changelog: None,
180// date_published: "2022-11-02T17:41:43.072267Z".to_string(),
181// downloads: 58,
182// version_type: VersionType::release,
183// files: vec![VersionFile {
184// hashes: Hash {
185// sha1: "fdc6dc39427fc92cc1d7ad8b275b5b83325e712b".to_string(),
186// sha512: "5b372f00d6e5d6a5ef225c3897826b9f6a2be5506905f7f71b9e939779765b41be6f2a9b029cfc752ad0751d0d2d5f8bb4544408df1363eebdde15641e99a849".to_string()
187// },
188// url: "https://cdn.modrinth.com/data/kYuIpRLv/versions/dEqtGnT9/waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(),
189// filename: "waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(),
190// primary: true,
191// size: 323176
192// }],
193// game_versions: vec![
194// "1.19".to_string(),
195// "1.19.1".to_string(),
196// "1.19.2".to_string()
197// ],
198// loaders: vec![
199// "fabric".to_string()
200// ]
201// }];
202// assert!(download_versions(current_list, config, versions)
203// .await
204// .is_ok())
205// }
diff --git a/src/config.rs b/src/config.rs
index e1049d1..f0eb8f7 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -4,9 +4,12 @@ use std::{
4 path::Path, 4 path::Path,
5}; 5};
6 6
7use indicatif::{ProgressBar, ProgressStyle};
7use serde::{Deserialize, Serialize}; 8use serde::{Deserialize, Serialize};
8 9
9use crate::{db::db_setup, error::MLE, Modloader, VersionLevel, check_game_versions}; 10use crate::{
11 check_game_versions, db::db_setup, error::MLE, Modloader, VersionLevel,
12};
10 13
11#[derive(Debug, Clone, Serialize, Deserialize)] 14#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Cfg { 15pub struct Cfg {
@@ -31,7 +34,7 @@ pub struct Defaults {
31impl Cfg { 34impl Cfg {
32 pub async fn init(path: Option<String>) -> MLE<Self> { 35 pub async fn init(path: Option<String>) -> MLE<Self> {
33 let configfile = match path.clone() { 36 let configfile = match path.clone() {
34 Some(p) => String::from(p), 37 Some(p) => p,
35 None => dirs::config_dir() 38 None => dirs::config_dir()
36 .unwrap() 39 .unwrap()
37 .join("modlist") 40 .join("modlist")
@@ -43,7 +46,8 @@ impl Cfg {
43 let mut file = match File::open(&configfile) { 46 let mut file = match File::open(&configfile) {
44 Ok(file) => file, 47 Ok(file) => file,
45 Err(err) => { 48 Err(err) => {
46 if err.kind() == std::io::ErrorKind::NotFound && path.is_none() { 49 if err.kind() == std::io::ErrorKind::NotFound && path.is_none()
50 {
47 create_config(&configfile)?; 51 create_config(&configfile)?;
48 File::open(&configfile)? 52 File::open(&configfile)?
49 } else { 53 } else {
@@ -71,16 +75,18 @@ impl Cfg {
71 Err(..) => { 75 Err(..) => {
72 create_versions_dummy(&versionfile).await?; 76 create_versions_dummy(&versionfile).await?;
73 check_game_versions(&versionfile, true).await?; 77 check_game_versions(&versionfile, true).await?;
74 }, 78 }
75 } 79 }
80
76 Ok(config) 81 Ok(config)
77 } 82 }
78} 83}
79 84
80fn create_config(path: &str) -> MLE<()> { 85fn create_config(path: &str) -> MLE<()> {
81 print!("No config file found, create default"); 86 let p = ProgressBar::new(1);
82 //Force flush of stdout, else print! doesn't print instantly 87 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
83 std::io::stdout().flush()?; 88 p.set_message("Create default config");
89
84 let cache_dir = dirs::cache_dir() 90 let cache_dir = dirs::cache_dir()
85 .unwrap() 91 .unwrap()
86 .join("modlist") 92 .join("modlist")
@@ -89,10 +95,10 @@ fn create_config(path: &str) -> MLE<()> {
89 let default_cfg = Cfg { 95 let default_cfg = Cfg {
90 data: cache_dir.clone(), 96 data: cache_dir.clone(),
91 cache: format!("{}/cache", cache_dir), 97 cache: format!("{}/cache", cache_dir),
92 versions: cache_dir.clone(), 98 versions: cache_dir,
93 defaults: Defaults { 99 defaults: Defaults {
94 modloader: Modloader::Fabric, 100 modloader: Modloader::Fabric,
95 version: VersionLevel::Release 101 version: VersionLevel::Release,
96 }, 102 },
97 apis: Apis { 103 apis: Apis {
98 modrinth: String::from("https://api.modrinth.com/v2/"), 104 modrinth: String::from("https://api.modrinth.com/v2/"),
@@ -101,37 +107,36 @@ fn create_config(path: &str) -> MLE<()> {
101 create_dir_all(path.split("config.toml").collect::<Vec<&str>>()[0])?; 107 create_dir_all(path.split("config.toml").collect::<Vec<&str>>()[0])?;
102 let mut file = File::create(path)?; 108 let mut file = File::create(path)?;
103 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?; 109 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?;
104 println!(" "); 110 p.finish_with_message(format!("Created default config ({})", path));
105 Ok(()) 111 Ok(())
106} 112}
107 113
108fn create_database(path: &str) -> MLE<()> { 114fn create_database(path: &str) -> MLE<()> {
109 print!("No database found, create base"); 115 let p = ProgressBar::new(1);
110 //Force flush of stdout, else print! doesn't print instantly 116 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
111 std::io::stdout().flush()?; 117 p.set_message("Create database");
112 118
113 File::create(path)?; 119 File::create(path)?;
114 db_setup(path)?; 120 db_setup(path)?;
115 println!(" "); 121 p.finish_with_message(format!("Created database ({})", path));
116 Ok(()) 122 Ok(())
117} 123}
118 124
119fn create_cache(path: &str) -> MLE<()> { 125fn create_cache(path: &str) -> MLE<()> {
120 print!("No cache direcory found, create one"); 126 let p = ProgressBar::new(1);
121 //Force flush of stdout, else print! doesn't print instantly 127 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
122 std::io::stdout().flush()?; 128 p.set_message("Create cache");
123 129
124 create_dir_all(path)?; 130 create_dir_all(path)?;
125 println!(" "); 131 p.finish_with_message(format!("Created cache ({})", path));
126 Ok(()) 132 Ok(())
127} 133}
128 134
129async fn create_versions_dummy(path: &str) -> MLE<()> { 135async fn create_versions_dummy(path: &str) -> MLE<()> {
130 print!("No version file found, create dummy"); 136 let p = ProgressBar::new(1);
131 //Force flush of stdout, else print! doesn't print instantly 137 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
132 std::io::stdout().flush()?; 138 p.set_message("Create version file");
133
134 File::create(path)?; 139 File::create(path)?;
135 println!(" "); 140 p.finish_with_message(format!("Created version file ({})", path));
136 Ok(()) 141 Ok(())
137} 142}
diff --git a/src/db.rs b/src/db.rs
index 8fd21b1..49db2fd 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -9,7 +9,7 @@ use crate::{
9}; 9};
10 10
11//MODS 11//MODS
12pub fn mods_insert(config: Cfg, id: &str, slug: &str, name: &str) -> MLE<()> { 12pub fn mods_insert(config: &Cfg, id: &str, slug: &str, name: &str) -> MLE<()> {
13 let data = format!("{}/data.db", config.data); 13 let data = format!("{}/data.db", config.data);
14 let connection = Connection::open(data)?; 14 let connection = Connection::open(data)?;
15 15
@@ -21,7 +21,9 @@ pub fn mods_insert(config: Cfg, id: &str, slug: &str, name: &str) -> MLE<()> {
21 Ok(()) 21 Ok(())
22} 22}
23 23
24pub fn mods_get_all_ids(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::Error>> { 24pub fn mods_get_all_ids(
25 config: &Cfg,
26) -> Result<Vec<String>, Box<dyn std::error::Error>> {
25 let data = format!("{}/data.db", config.data); 27 let data = format!("{}/data.db", config.data);
26 let connection = Connection::open(data).unwrap(); 28 let connection = Connection::open(data).unwrap();
27 29
@@ -64,8 +66,10 @@ pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
64 } 66 }
65 //get from id if no slug found 67 //get from id if no slug found
66 if mod_id.is_empty() { 68 if mod_id.is_empty() {
67 let mut stmt = connection.prepare("SELECT id FROM mods WHERE id = ?")?; 69 let mut stmt =
68 let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?; 70 connection.prepare("SELECT id FROM mods WHERE id = ?")?;
71 let id_iter =
72 stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
69 73
70 for id in id_iter { 74 for id in id_iter {
71 mod_id = id?; 75 mod_id = id?;
@@ -73,8 +77,10 @@ pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
73 } 77 }
74 //get from title if no id found from slug 78 //get from title if no id found from slug
75 if mod_id.is_empty() { 79 if mod_id.is_empty() {
76 let mut stmt = connection.prepare("SELECT id FROM mods WHERE title = ?")?; 80 let mut stmt =
77 let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?; 81 connection.prepare("SELECT id FROM mods WHERE title = ?")?;
82 let id_iter =
83 stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
78 84
79 for id in id_iter { 85 for id in id_iter {
80 mod_id = id?; 86 mod_id = id?;
@@ -93,12 +99,13 @@ pub struct ModInfo {
93 pub title: String, 99 pub title: String,
94} 100}
95 101
96pub fn mods_get_info(config: Cfg, id: &str) -> MLE<ModInfo> { 102pub fn mods_get_info(config: &Cfg, id: &str) -> MLE<ModInfo> {
97 let data = format!("{}/data.db", config.data); 103 let data = format!("{}/data.db", config.data);
98 let connection = Connection::open(data)?; 104 let connection = Connection::open(data)?;
99 105
100 let mut mod_info: Option<ModInfo> = None; 106 let mut mod_info: Option<ModInfo> = None;
101 let mut stmt = connection.prepare("SELECT title, slug FROM mods WHERE id = ?")?; 107 let mut stmt =
108 connection.prepare("SELECT title, slug FROM mods WHERE id = ?")?;
102 let name_iter = stmt.query_map([id], |row| { 109 let name_iter = stmt.query_map([id], |row| {
103 Ok(vec![ 110 Ok(vec![
104 row.get::<usize, String>(0)?, 111 row.get::<usize, String>(0)?,
@@ -120,9 +127,7 @@ pub fn mods_get_info(config: Cfg, id: &str) -> MLE<ModInfo> {
120 } 127 }
121} 128}
122 129
123pub fn mods_remove(config: Cfg, id: String) -> MLE<()> { 130pub fn mods_remove(config: &Cfg, id: &str) -> MLE<()> {
124 println!("Removing mod {} from database", id);
125
126 let data = format!("{}/data.db", config.data); 131 let data = format!("{}/data.db", config.data);
127 let connection = Connection::open(data)?; 132 let connection = Connection::open(data)?;
128 133
@@ -137,7 +142,10 @@ pub struct DBModlistVersions {
137 pub versions: String, 142 pub versions: String,
138} 143}
139 144
140pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVersions>> { 145pub fn mods_get_versions(
146 config: &Cfg,
147 mods: Vec<String>,
148) -> MLE<Vec<DBModlistVersions>> {
141 let data = format!("{}/data.db", config.data); 149 let data = format!("{}/data.db", config.data);
142 let connection = Connection::open(data)?; 150 let connection = Connection::open(data)?;
143 151
@@ -155,8 +163,9 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
155 } 163 }
156 164
157 let mut versionmaps: Vec<DBModlistVersions> = Vec::new(); 165 let mut versionmaps: Vec<DBModlistVersions> = Vec::new();
158 let mut stmt = connection 166 let mut stmt = connection.prepare(
159 .prepare(format!("SELECT id, versions, title FROM mods {}", wherestr).as_str())?; 167 format!("SELECT id, versions, title FROM mods {}", wherestr).as_str(),
168 )?;
160 let id_iter = stmt.query_map([], |row| { 169 let id_iter = stmt.query_map([], |row| {
161 Ok(vec![ 170 Ok(vec![
162 row.get::<usize, String>(0)?, 171 row.get::<usize, String>(0)?,
@@ -167,11 +176,6 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
167 176
168 for ver in id_iter { 177 for ver in id_iter {
169 let version = ver?; 178 let version = ver?;
170 println!(
171 "\t({}) Get versions from the database",
172 String::from(&version[2])
173 );
174 //println!("Found versions {} for mod {}", version[1], version[0]);
175 versionmaps.push(DBModlistVersions { 179 versionmaps.push(DBModlistVersions {
176 mod_id: String::from(&version[0]), 180 mod_id: String::from(&version[0]),
177 versions: String::from(&version[1]), 181 versions: String::from(&version[1]),
@@ -186,7 +190,7 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
186 190
187//userlist 191//userlist
188pub fn userlist_insert( 192pub fn userlist_insert(
189 config: Cfg, 193 config: &Cfg,
190 list_id: &str, 194 list_id: &str,
191 mod_id: &str, 195 mod_id: &str,
192 current_version: &str, 196 current_version: &str,
@@ -220,26 +224,29 @@ pub fn userlist_insert(
220 Ok(()) 224 Ok(())
221} 225}
222 226
223pub fn userlist_get_all_ids(config: Cfg, list_id: &str) -> MLE<Vec<String>> { 227pub fn userlist_get_all_ids(config: &Cfg, list_id: &str) -> MLE<Vec<String>> {
224 let data = format!("{}/data.db", config.data); 228 let data = format!("{}/data.db", config.data);
225 let connection = Connection::open(data).unwrap(); 229 let connection = Connection::open(data).unwrap();
226 230
227 let mut mod_ids: Vec<String> = Vec::new(); 231 let mut mod_ids: Vec<String> = Vec::new();
228 let mut stmt = connection.prepare(format!("SELECT mod_id FROM {}", list_id).as_str())?; 232 let mut stmt = connection
233 .prepare(format!("SELECT mod_id FROM {}", list_id).as_str())?;
229 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 234 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
230 235
231 for id in id_iter { 236 for id in id_iter {
232 //println!("Found id {:?}", id.as_ref().unwrap());
233 mod_ids.push(id?) 237 mod_ids.push(id?)
234 } 238 }
235 239
236 match mod_ids.is_empty() { 240 match mod_ids.is_empty() {
237 true => Err(MLError::new(ErrorType::DBError, "NO_MODS_USERLIST")), 241 true => Err(MLError::new(
242 ErrorType::DBError,
243 &format!("NO_MODS_USERLIST{}", list_id),
244 )),
238 false => Ok(mod_ids), 245 false => Ok(mod_ids),
239 } 246 }
240} 247}
241 248
242pub fn userlist_remove(config: Cfg, list_id: &str, mod_id: &str) -> MLE<()> { 249pub fn userlist_remove(config: &Cfg, list_id: &str, mod_id: &str) -> MLE<()> {
243 let data = format!("{}/data.db", config.data); 250 let data = format!("{}/data.db", config.data);
244 let connection = Connection::open(data)?; 251 let connection = Connection::open(data)?;
245 252
@@ -251,7 +258,7 @@ pub fn userlist_remove(config: Cfg, list_id: &str, mod_id: &str) -> MLE<()> {
251} 258}
252 259
253pub fn userlist_get_applicable_versions( 260pub fn userlist_get_applicable_versions(
254 config: Cfg, 261 config: &Cfg,
255 list_id: String, 262 list_id: String,
256 mod_id: String, 263 mod_id: String,
257) -> MLE<String> { 264) -> MLE<String> {
@@ -266,7 +273,8 @@ pub fn userlist_get_applicable_versions(
266 ) 273 )
267 .as_str(), 274 .as_str(),
268 )?; 275 )?;
269 let ver_iter = stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?; 276 let ver_iter =
277 stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
270 278
271 for ver in ver_iter { 279 for ver in ver_iter {
272 version = ver?; 280 version = ver?;
@@ -279,15 +287,16 @@ pub fn userlist_get_applicable_versions(
279} 287}
280 288
281pub fn userlist_get_all_applicable_versions_with_mods( 289pub fn userlist_get_all_applicable_versions_with_mods(
282 config: Cfg, 290 config: &Cfg,
283 list_id: String, 291 list_id: String,
284) -> MLE<Vec<(String, String)>> { 292) -> MLE<Vec<(String, String)>> {
285 let data = format!("{}/data.db", config.data); 293 let data = format!("{}/data.db", config.data);
286 let connection = Connection::open(data)?; 294 let connection = Connection::open(data)?;
287 295
288 let mut versions: Vec<(String, String)> = Vec::new(); 296 let mut versions: Vec<(String, String)> = Vec::new();
289 let mut stmt = connection 297 let mut stmt = connection.prepare(
290 .prepare(format!("SELECT mod_id, applicable_versions FROM {}", list_id).as_str())?; 298 format!("SELECT mod_id, applicable_versions FROM {}", list_id).as_str(),
299 )?;
291 let id_iter = stmt.query_map([], |row| { 300 let id_iter = stmt.query_map([], |row| {
292 Ok(vec![ 301 Ok(vec![
293 row.get::<usize, String>(0)?, 302 row.get::<usize, String>(0)?,
@@ -307,14 +316,21 @@ pub fn userlist_get_all_applicable_versions_with_mods(
307 Ok(versions) 316 Ok(versions)
308} 317}
309 318
310pub fn userlist_get_current_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE<String> { 319pub fn userlist_get_current_version(
320 config: &Cfg,
321 list_id: &str,
322 mod_id: &str,
323) -> MLE<String> {
311 let data = format!("{}/data.db", config.data); 324 let data = format!("{}/data.db", config.data);
312 let connection = Connection::open(data).unwrap(); 325 let connection = Connection::open(data).unwrap();
313 326
314 let mut version: String = String::new(); 327 let mut version: String = String::new();
315 let mut stmt = connection 328 let mut stmt = connection.prepare(
316 .prepare(format!("SELECT current_version FROM {} WHERE mod_id = ?", list_id).as_str())?; 329 format!("SELECT current_version FROM {} WHERE mod_id = ?", list_id)
317 let ver_iter = stmt.query_map([&mod_id], |row| row.get::<usize, String>(0))?; 330 .as_str(),
331 )?;
332 let ver_iter =
333 stmt.query_map([&mod_id], |row| row.get::<usize, String>(0))?;
318 334
319 for ver in ver_iter { 335 for ver in ver_iter {
320 version = ver?; 336 version = ver?;
@@ -327,15 +343,15 @@ pub fn userlist_get_current_version(config: Cfg, list_id: &str, mod_id: &str) ->
327} 343}
328 344
329pub fn userlist_get_all_current_version_ids( 345pub fn userlist_get_all_current_version_ids(
330 config: Cfg, 346 config: &Cfg,
331 list_id: String, 347 list_id: String,
332) -> MLE<Vec<String>> { 348) -> MLE<Vec<String>> {
333 let data = format!("{}/data.db", config.data); 349 let data = format!("{}/data.db", config.data);
334 let connection = Connection::open(data)?; 350 let connection = Connection::open(data)?;
335 351
336 let mut versions: Vec<String> = Vec::new(); 352 let mut versions: Vec<String> = Vec::new();
337 let mut stmt = 353 let mut stmt = connection
338 connection.prepare(format!("SELECT current_version FROM {}", list_id).as_str())?; 354 .prepare(format!("SELECT current_version FROM {}", list_id).as_str())?;
339 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 355 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
340 356
341 for id in id_iter { 357 for id in id_iter {
@@ -343,25 +359,23 @@ pub fn userlist_get_all_current_version_ids(
343 } 359 }
344 360
345 if versions.is_empty() { 361 if versions.is_empty() {
346 return Err(MLError::new( 362 return Err(MLError::new(ErrorType::DBError, "NO_MODS_ON_LIST"));
347 ErrorType::DBError,
348 "NO_MODS_ON_LIST",
349 ));
350 }; 363 };
351 364
352 Ok(versions) 365 Ok(versions)
353} 366}
354 367
355pub fn userlist_get_all_current_versions_with_mods( 368pub fn userlist_get_all_current_versions_with_mods(
356 config: Cfg, 369 config: &Cfg,
357 list_id: String, 370 list_id: String,
358) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> { 371) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
359 let data = format!("{}/data.db", config.data); 372 let data = format!("{}/data.db", config.data);
360 let connection = Connection::open(data)?; 373 let connection = Connection::open(data)?;
361 374
362 let mut versions: Vec<(String, String)> = Vec::new(); 375 let mut versions: Vec<(String, String)> = Vec::new();
363 let mut stmt = 376 let mut stmt = connection.prepare(
364 connection.prepare(format!("SELECT mod_id, current_version FROM {}", list_id).as_str())?; 377 format!("SELECT mod_id, current_version FROM {}", list_id).as_str(),
378 )?;
365 let id_iter = stmt.query_map([], |row| { 379 let id_iter = stmt.query_map([], |row| {
366 Ok(vec![ 380 Ok(vec![
367 row.get::<usize, String>(0)?, 381 row.get::<usize, String>(0)?,
@@ -384,14 +398,21 @@ pub fn userlist_get_all_current_versions_with_mods(
384 Ok(versions) 398 Ok(versions)
385} 399}
386 400
387pub fn userlist_get_set_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE<bool> { 401pub fn userlist_get_set_version(
402 config: &Cfg,
403 list_id: &str,
404 mod_id: &str,
405) -> MLE<bool> {
388 let data = format!("{}/data.db", config.data); 406 let data = format!("{}/data.db", config.data);
389 let connection = Connection::open(data).unwrap(); 407 let connection = Connection::open(data).unwrap();
390 408
391 let mut set_version: bool = false; 409 let mut set_version: bool = false;
392 let mut stmt = connection 410 let mut stmt = connection.prepare(
393 .prepare(format!("SELECT set_version FROM {} WHERE mod_id = ?", list_id).as_str())?; 411 format!("SELECT set_version FROM {} WHERE mod_id = ?", list_id)
394 let ver_iter = stmt.query_map([&mod_id], |row| row.get::<usize, bool>(0))?; 412 .as_str(),
413 )?;
414 let ver_iter =
415 stmt.query_map([&mod_id], |row| row.get::<usize, bool>(0))?;
395 416
396 for ver in ver_iter { 417 for ver in ver_iter {
397 set_version = ver?; 418 set_version = ver?;
@@ -401,7 +422,7 @@ pub fn userlist_get_set_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE
401} 422}
402 423
403pub fn userlist_change_versions( 424pub fn userlist_change_versions(
404 config: Cfg, 425 config: &Cfg,
405 list_id: String, 426 list_id: String,
406 current_version: String, 427 current_version: String,
407 versions: String, 428 versions: String,
@@ -416,7 +437,7 @@ pub fn userlist_change_versions(
416} 437}
417 438
418pub fn userlist_add_disabled_versions( 439pub fn userlist_add_disabled_versions(
419 config: Cfg, 440 config: &Cfg,
420 list_id: String, 441 list_id: String,
421 disabled_version: String, 442 disabled_version: String,
422 mod_id: String, 443 mod_id: String,
@@ -424,11 +445,16 @@ pub fn userlist_add_disabled_versions(
424 let data = format!("{}/data.db", config.data); 445 let data = format!("{}/data.db", config.data);
425 let connection = Connection::open(data)?; 446 let connection = Connection::open(data)?;
426 447
427 let currently_disabled_versions = 448 let currently_disabled_versions = userlist_get_disabled_versions(
428 userlist_get_disabled_versions(config, String::from(&list_id), String::from(&mod_id))?; 449 config,
450 String::from(&list_id),
451 String::from(&mod_id),
452 )?;
429 let disabled_versions = match currently_disabled_versions == "NONE" { 453 let disabled_versions = match currently_disabled_versions == "NONE" {
430 true => disabled_version, 454 true => disabled_version,
431 false => format!("{}|{}", currently_disabled_versions, disabled_version), 455 false => {
456 format!("{}|{}", currently_disabled_versions, disabled_version)
457 }
432 }; 458 };
433 459
434 connection.execute( 460 connection.execute(
@@ -442,14 +468,21 @@ pub fn userlist_add_disabled_versions(
442 Ok(()) 468 Ok(())
443} 469}
444 470
445pub fn userlist_get_disabled_versions(config: Cfg, list_id: String, mod_id: String) -> MLE<String> { 471pub fn userlist_get_disabled_versions(
472 config: &Cfg,
473 list_id: String,
474 mod_id: String,
475) -> MLE<String> {
446 let data = format!("{}/data.db", config.data); 476 let data = format!("{}/data.db", config.data);
447 let connection = Connection::open(data).unwrap(); 477 let connection = Connection::open(data).unwrap();
448 478
449 let mut version: String = String::new(); 479 let mut version: String = String::new();
450 let mut stmt = connection 480 let mut stmt = connection.prepare(
451 .prepare(format!("SELECT disabled_versions FROM {} WHERE mod_id = ?", list_id).as_str())?; 481 format!("SELECT disabled_versions FROM {} WHERE mod_id = ?", list_id)
452 let ver_iter = stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?; 482 .as_str(),
483 )?;
484 let ver_iter =
485 stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
453 486
454 for ver in ver_iter { 487 for ver in ver_iter {
455 version = ver?; 488 version = ver?;
@@ -462,20 +495,20 @@ pub fn userlist_get_disabled_versions(config: Cfg, list_id: String, mod_id: Stri
462} 495}
463 496
464pub fn userlist_get_all_downloads( 497pub fn userlist_get_all_downloads(
465 config: Cfg, 498 config: &Cfg,
466 list_id: String, 499 list_id: String,
467) -> Result<Vec<String>, Box<dyn std::error::Error>> { 500) -> Result<Vec<String>, Box<dyn std::error::Error>> {
468 let data = format!("{}/data.db", config.data); 501 let data = format!("{}/data.db", config.data);
469 let connection = Connection::open(data).unwrap(); 502 let connection = Connection::open(data).unwrap();
470 503
471 let mut links: Vec<String> = Vec::new(); 504 let mut links: Vec<String> = Vec::new();
472 let mut stmt = 505 let mut stmt = connection.prepare(
473 connection.prepare(format!("SELECT current_download FROM {}", list_id).as_str())?; 506 format!("SELECT current_download FROM {}", list_id).as_str(),
507 )?;
474 let link_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 508 let link_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
475 509
476 for link in link_iter { 510 for link in link_iter {
477 let l = link?; 511 let l = link?;
478 println!("Found link {}", String::from(&l));
479 links.push(l) 512 links.push(l)
480 } 513 }
481 514
@@ -492,32 +525,25 @@ pub fn userlist_get_all_downloads(
492//lists 525//lists
493///Inserts into lists table and creates new table 526///Inserts into lists table and creates new table
494pub fn lists_insert( 527pub fn lists_insert(
495 config: Cfg, 528 config: &Cfg,
496 id: String, 529 id: &str,
497 mc_version: String, 530 mc_version: &str,
498 mod_loader: Modloader, 531 mod_loader: &Modloader,
499 download_folder: String, 532 download_folder: &str,
500) -> MLE<()> { 533) -> MLE<()> {
501 println!("Creating list {}", id);
502
503 let data = format!("{}/data.db", config.data); 534 let data = format!("{}/data.db", config.data);
504 let connection = Connection::open(data)?; 535 let connection = Connection::open(data)?;
505 536
506 connection.execute( 537 connection.execute(
507 "INSERT INTO lists VALUES (?1, ?2, ?3, ?4)", 538 "INSERT INTO lists VALUES (?1, ?2, ?3, ?4)",
508 [ 539 [id, mc_version, &mod_loader.to_string(), download_folder],
509 id.clone(),
510 mc_version,
511 mod_loader.to_string(),
512 download_folder,
513 ],
514 )?; 540 )?;
515 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE', 'set_version' INTEGER, CONSTRAINT {}_PK PRIMARY KEY (mod_id) )", id, id).as_str(), [])?; 541 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE', 'set_version' INTEGER, CONSTRAINT {}_PK PRIMARY KEY (mod_id) )", id, id).as_str(), [])?;
516 542
517 Ok(()) 543 Ok(())
518} 544}
519 545
520pub fn lists_remove(config: Cfg, id: String) -> MLE<()> { 546pub fn lists_remove(config: &Cfg, id: &str) -> MLE<()> {
521 let data = format!("{}/data.db", config.data); 547 let data = format!("{}/data.db", config.data);
522 let connection = Connection::open(data)?; 548 let connection = Connection::open(data)?;
523 549
@@ -526,7 +552,7 @@ pub fn lists_remove(config: Cfg, id: String) -> MLE<()> {
526 Ok(()) 552 Ok(())
527} 553}
528 554
529pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> { 555pub fn lists_get(config: &Cfg, list_id: &str) -> MLE<List> {
530 let data = format!("{}/data.db", config.data); 556 let data = format!("{}/data.db", config.data);
531 let connection = Connection::open(data).unwrap(); 557 let connection = Connection::open(data).unwrap();
532 558
@@ -536,8 +562,9 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
536 modloader: Modloader::Fabric, 562 modloader: Modloader::Fabric,
537 download_folder: String::new(), 563 download_folder: String::new(),
538 }; 564 };
539 let mut stmt = connection 565 let mut stmt = connection.prepare(
540 .prepare("SELECT mc_version, modloader, download_folder FROM lists WHERE id = ?")?; 566 "SELECT mc_version, modloader, download_folder FROM lists WHERE id = ?",
567 )?;
541 568
542 let list_iter = stmt.query_map([&list_id], |row| { 569 let list_iter = stmt.query_map([&list_id], |row| {
543 Ok(vec![ 570 Ok(vec![
@@ -550,7 +577,7 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
550 for l in list_iter { 577 for l in list_iter {
551 let li = l?; 578 let li = l?;
552 list = List { 579 list = List {
553 id: String::from(&list_id), 580 id: list_id.to_string(),
554 mc_version: String::from(&li[0]), 581 mc_version: String::from(&li[0]),
555 modloader: Modloader::from(&li[1])?, 582 modloader: Modloader::from(&li[1])?,
556 download_folder: String::from(&li[2]), 583 download_folder: String::from(&li[2]),
@@ -564,7 +591,7 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
564 Ok(list) 591 Ok(list)
565} 592}
566 593
567pub fn lists_version(config: Cfg, list_id: &str, version: &str) -> MLE<()> { 594pub fn lists_version(config: &Cfg, list_id: &str, version: &str) -> MLE<()> {
568 let data = format!("{}/data.db", config.data); 595 let data = format!("{}/data.db", config.data);
569 let connection = Connection::open(data).unwrap(); 596 let connection = Connection::open(data).unwrap();
570 597
@@ -575,7 +602,7 @@ pub fn lists_version(config: Cfg, list_id: &str, version: &str) -> MLE<()> {
575 Ok(()) 602 Ok(())
576} 603}
577 604
578pub fn lists_get_all_ids(config: Cfg) -> MLE<Vec<String>> { 605pub fn lists_get_all_ids(config: &Cfg) -> MLE<Vec<String>> {
579 let data = format!("{}/data.db", config.data); 606 let data = format!("{}/data.db", config.data);
580 let connection = Connection::open(data).unwrap(); 607 let connection = Connection::open(data).unwrap();
581 608
@@ -594,7 +621,7 @@ pub fn lists_get_all_ids(config: Cfg) -> MLE<Vec<String>> {
594} 621}
595 622
596//config 623//config
597pub fn config_change_current_list(config: Cfg, id: String) -> MLE<()> { 624pub fn config_change_current_list(config: &Cfg, id: &str) -> MLE<()> {
598 let data = format!("{}/data.db", config.data); 625 let data = format!("{}/data.db", config.data);
599 let connection = Connection::open(data)?; 626 let connection = Connection::open(data)?;
600 627
@@ -605,12 +632,13 @@ pub fn config_change_current_list(config: Cfg, id: String) -> MLE<()> {
605 Ok(()) 632 Ok(())
606} 633}
607 634
608pub fn config_get_current_list(config: Cfg) -> MLE<String> { 635pub fn config_get_current_list(config: &Cfg) -> MLE<String> {
609 let data = format!("{}/data.db", config.data); 636 let data = format!("{}/data.db", config.data);
610 let connection = Connection::open(data).unwrap(); 637 let connection = Connection::open(data).unwrap();
611 638
612 let mut list_id = String::new(); 639 let mut list_id = String::new();
613 let mut stmt = connection.prepare("SELECT value FROM user_config WHERE id = 'current_list'")?; 640 let mut stmt = connection
641 .prepare("SELECT value FROM user_config WHERE id = 'current_list'")?;
614 let list_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 642 let list_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
615 643
616 for list in list_iter { 644 for list in list_iter {
@@ -626,7 +654,7 @@ pub fn config_get_current_list(config: Cfg) -> MLE<String> {
626 654
627//SETUP(UPDATES) 655//SETUP(UPDATES)
628pub fn s_userlist_update_download( 656pub fn s_userlist_update_download(
629 config: Cfg, 657 config: &Cfg,
630 list_id: String, 658 list_id: String,
631 mod_id: String, 659 mod_id: String,
632 link: String, 660 link: String,
@@ -645,7 +673,9 @@ pub fn s_userlist_update_download(
645 Ok(()) 673 Ok(())
646} 674}
647 675
648pub fn s_config_create_version(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { 676pub fn s_config_create_version(
677 config: &Cfg,
678) -> Result<(), Box<dyn std::error::Error>> {
649 let data = format!("{}/data.db", config.data); 679 let data = format!("{}/data.db", config.data);
650 let connection = Connection::open(data)?; 680 let connection = Connection::open(data)?;
651 681
@@ -656,7 +686,10 @@ pub fn s_config_create_version(config: Cfg) -> Result<(), Box<dyn std::error::Er
656 Ok(()) 686 Ok(())
657} 687}
658 688
659pub fn s_config_update_version(config: Cfg, ver: String) -> Result<(), Box<dyn std::error::Error>> { 689pub fn s_config_update_version(
690 config: &Cfg,
691 ver: String,
692) -> Result<(), Box<dyn std::error::Error>> {
660 let data = format!("{}/data.db", config.data); 693 let data = format!("{}/data.db", config.data);
661 let connection = Connection::open(data)?; 694 let connection = Connection::open(data)?;
662 695
@@ -667,12 +700,15 @@ pub fn s_config_update_version(config: Cfg, ver: String) -> Result<(), Box<dyn s
667 Ok(()) 700 Ok(())
668} 701}
669 702
670pub fn s_config_get_version(config: Cfg) -> Result<String, Box<dyn std::error::Error>> { 703pub fn s_config_get_version(
704 config: &Cfg,
705) -> Result<String, Box<dyn std::error::Error>> {
671 let data = format!("{}/data.db", config.data); 706 let data = format!("{}/data.db", config.data);
672 let connection = Connection::open(data)?; 707 let connection = Connection::open(data)?;
673 708
674 let mut version: String = String::new(); 709 let mut version: String = String::new();
675 let mut stmt = connection.prepare("SELECT value FROM user_config WHERE id = 'db_version'")?; 710 let mut stmt = connection
711 .prepare("SELECT value FROM user_config WHERE id = 'db_version'")?;
676 let ver_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 712 let ver_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
677 713
678 for ver in ver_iter { 714 for ver in ver_iter {
@@ -689,7 +725,7 @@ pub fn s_config_get_version(config: Cfg) -> Result<String, Box<dyn std::error::E
689} 725}
690 726
691pub fn s_insert_column( 727pub fn s_insert_column(
692 config: Cfg, 728 config: &Cfg,
693 table: String, 729 table: String,
694 column: String, 730 column: String,
695 c_type: String, 731 c_type: String,
diff --git a/src/error.rs b/src/error.rs
index e6afeaa..a2b37a8 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -34,8 +34,12 @@ impl std::error::Error for MLError {
34impl fmt::Display for MLError { 34impl fmt::Display for MLError {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 match self.etype { 36 match self.etype {
37 ErrorType::ArgumentError => write!(f, "User input not accepted: {}", self.message), 37 ErrorType::ArgumentError => {
38 ErrorType::ArgumentCountError => write!(f, "Too many/too few arguments"), 38 write!(f, "User input not accepted: {}", self.message)
39 }
40 ErrorType::ArgumentCountError => {
41 write!(f, "Too many/too few arguments")
42 }
39 ErrorType::ConfigError => write!(f, "CONFIG"), 43 ErrorType::ConfigError => write!(f, "CONFIG"),
40 ErrorType::DBError => write!(f, "Database: {}", self.message), 44 ErrorType::DBError => write!(f, "Database: {}", self.message),
41 ErrorType::ModError => write!(f, "Mod: {}", self.message), 45 ErrorType::ModError => write!(f, "Mod: {}", self.message),
@@ -106,9 +110,11 @@ impl From<std::io::Error> for MLError {
106 110
107impl From<serde_json::error::Error> for MLError { 111impl From<serde_json::error::Error> for MLError {
108 fn from(value: serde_json::error::Error) -> Self { 112 fn from(value: serde_json::error::Error) -> Self {
109 Self { etype: ErrorType::LibJson, message: value.to_string() } 113 Self {
114 etype: ErrorType::LibJson,
115 message: value.to_string(),
116 }
110 } 117 }
111
112} 118}
113 119
114impl MLError { 120impl MLError {
diff --git a/src/files.rs b/src/files.rs
index a4c128e..3a16c62 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,10 +1,13 @@
1use futures_util::StreamExt; 1use futures_util::StreamExt;
2use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2use reqwest::Client; 3use reqwest::Client;
3use std::{ 4use std::{
5 cmp::min,
4 collections::HashMap, 6 collections::HashMap,
5 fs::{copy, read_dir, remove_file, rename, File}, 7 fs::{copy, read_dir, remove_file, rename, File},
6 io::Write, 8 io::Write,
7}; 9};
10use tokio::task::JoinSet;
8 11
9use crate::{ 12use crate::{
10 cache::{copy_cached_version, get_cached_versions}, 13 cache::{copy_cached_version, get_cached_versions},
@@ -12,99 +15,160 @@ use crate::{
12 db::{mods_get_info, userlist_add_disabled_versions}, 15 db::{mods_get_info, userlist_add_disabled_versions},
13 error::{ErrorType, MLError, MLE}, 16 error::{ErrorType, MLError, MLE},
14 modrinth::Version, 17 modrinth::Version,
15 List, 18 List, PROGRESS_CHARS, STYLE_BAR_BYTE, STYLE_BAR_POS, STYLE_SPINNER,
16}; 19};
17 20
18pub async fn download_versions(list: List, config: Cfg, versions: Vec<Version>) -> MLE<String> { 21pub async fn download_versions(
19 let mut cached = get_cached_versions(&config.cache); 22 list: List,
23 config: Cfg,
24 versions: Vec<Version>,
25 progress: &MultiProgress,
26 progress_before: &ProgressBar,
27) -> MLE<()> {
28 let cached = get_cached_versions(&config.cache);
20 29
21 // println!("{:#?}", cached); 30 let mut js = JoinSet::new();
22 31
23 let dl_path = String::from(&list.download_folder); 32 let style_spinner = ProgressStyle::with_template(STYLE_SPINNER).unwrap();
24 33
25 println!(" └Download mods to {}", dl_path); 34 let all = progress.insert_before(
35 progress_before,
36 ProgressBar::new(versions.len().try_into().unwrap()),
37 );
38 all.set_style(
39 ProgressStyle::with_template(STYLE_BAR_POS)
40 .unwrap()
41 .progress_chars(PROGRESS_CHARS),
42 );
43 all.set_message(format!("✓Downloading {}", list.id));
26 44
27 for ver in versions { 45 for ver in versions {
28 let project_info = mods_get_info(config.clone(), &ver.project_id)?; 46 let p = progress.insert_before(&all, ProgressBar::new(1));
29 47 p.set_style(style_spinner.clone());
30 //Check cache if already downloaded 48 js.spawn(download_version(
31 let c = cached.remove(&ver.id); 49 config.clone(),
32 if c.is_some() { 50 list.clone(),
33 print!( 51 ver,
34 "\t└({})Get version {} from cache", 52 cached.clone(),
35 project_info.title, ver.id 53 p,
36 ); 54 ));
37 //Force flush of stdout, else print! doesn't print instantly 55 }
38 std::io::stdout().flush()?; 56
39 copy_cached_version(&c.unwrap(), &dl_path); 57 while js.join_next().await.is_some() {
40 println!(" ✓"); 58 all.inc(1)
41 } else { 59 }
42 print!("\t└({})Download version {}", project_info.title, ver.id); 60
43 //Force flush of stdout, else print! doesn't print instantly 61 all.finish_with_message(format!("✓Downloading {}", list.id));
44 std::io::stdout().flush().unwrap(); 62
45 let files = ver.files; 63 Ok(())
46 let file = match files.clone().into_iter().find(|f| f.primary) { 64}
47 Some(f) => f, 65
48 None => files[0].clone() 66async fn download_version(
49 }; 67 config: Cfg,
50 let mut splitname: Vec<&str> = file.filename.split('.').collect(); 68 list: List,
51 let extension = match splitname.pop().ok_or("") { 69 version: Version,
52 Ok(e) => e, 70 mut cached: HashMap<String, String>,
53 Err(..) => return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION")), 71 progress: ProgressBar,
54 }; 72) -> MLE<()> {
55 let filename = format!( 73 let project_info = mods_get_info(&config, &version.project_id)?;
56 "{}.mr.{}.{}.{}", 74
57 splitname.join("."), 75 let dl_path = String::from(&list.download_folder);
58 ver.project_id, 76
59 ver.id, 77 progress.set_message(format!("{} - {}", project_info.title, version.id));
60 extension 78
61 ); 79 let mut cache_msg = "";
62 download_file( 80 //Check cache if already downloaded
63 file.url, 81 let c = cached.remove(&version.id);
64 list.clone().download_folder, 82 if c.is_some() {
65 filename.clone(), 83 progress.set_message(format!("Get {} from cache", version.id));
66 ) 84 cache_msg = " (cached)";
85 copy_cached_version(&c.unwrap(), &dl_path);
86 } else {
87 let files = version.files;
88 let file = match files.clone().into_iter().find(|f| f.primary) {
89 Some(f) => f,
90 None => files[0].clone(),
91 };
92 let mut splitname: Vec<&str> = file.filename.split('.').collect();
93 let extension = match splitname.pop().ok_or("") {
94 Ok(e) => e,
95 Err(..) => {
96 return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION"))
97 }
98 };
99 let filename = format!(
100 "{}.mr.{}.{}.{}",
101 splitname.join("."),
102 version.project_id,
103 version.id,
104 extension
105 );
106
107 download_file(&file.url, &list.download_folder, &filename, &progress)
67 .await?; 108 .await?;
68 println!(" ✓"); 109
69 //Copy file to cache 110 progress.set_message(format!("Copy {} to cache", version.id));
70 print!("\t └Copy to cache"); 111 let dl_path_file = format!("{}/{}", list.download_folder, filename);
71 //Force flush of stdout, else print! doesn't print instantly 112 let cache_path = format!("{}/{}", &config.cache, filename);
72 std::io::stdout().flush().unwrap(); 113
73 let dl_path_file = format!("{}/{}", list.download_folder, filename); 114 copy(dl_path_file, cache_path)?;
74 let cache_path = format!("{}/{}", &config.clone().cache, filename);
75 // println!("{}:{}", dl_path_file, cache_path);
76 copy(dl_path_file, cache_path)?;
77 println!(" ✓");
78 }
79 } 115 }
80 116
81 Ok(dl_path) 117 progress.finish_with_message(format!(
118 "✓{} - {}{}",
119 project_info.title, version.id, cache_msg
120 ));
121
122 Ok(())
82} 123}
83 124
84async fn download_file(url: String, path: String, name: String) -> MLE<()> { 125async fn download_file(
126 url: &str,
127 path: &str,
128 name: &str,
129 progress: &ProgressBar,
130) -> MLE<()> {
85 let dl_path_file = format!("{}/{}", path, name); 131 let dl_path_file = format!("{}/{}", path, name);
86 let res = Client::new().get(String::from(&url)).send().await?; 132 let res = Client::new().get(url).send().await?;
133
134 let size = res.content_length().expect("Couldn't get content length");
135
136 let style_bar_byte = ProgressStyle::with_template(STYLE_BAR_BYTE)
137 .unwrap()
138 .progress_chars(PROGRESS_CHARS);
139
140 progress.set_length(size);
141 progress.set_style(style_bar_byte);
87 142
88 // download chunks 143 // download chunks
89 let mut file = File::create(&dl_path_file)?; 144 let mut file = File::create(&dl_path_file)?;
90 let mut stream = res.bytes_stream(); 145 let mut stream = res.bytes_stream();
91 146
147 let mut downloaded: u64 = 0;
148
92 while let Some(item) = stream.next().await { 149 while let Some(item) = stream.next().await {
150 // progress.inc(1);
93 let chunk = item?; 151 let chunk = item?;
94 file.write_all(&chunk)?; 152 file.write_all(&chunk)?;
153
154 // Progress bar
155 let new = min(downloaded + (chunk.len() as u64), size);
156 downloaded = new;
157 progress.set_position(new);
158
159 // std::thread::sleep(std::time::Duration::from_millis(100));
95 } 160 }
96 161
97 Ok(()) 162 Ok(())
98} 163}
99 164
100pub fn disable_version( 165pub fn disable_version(
101 config: Cfg, 166 config: &Cfg,
102 current_list: List, 167 current_list: List,
103 versionid: String, 168 versionid: String,
104 mod_id: String, 169 mod_id: String,
105) -> MLE<()> { 170) -> MLE<()> {
106 //println!("Disabling version {} for mod {}", versionid, mod_id); 171 let file = get_file_path(&current_list, String::from(&versionid))?;
107 let file = get_file_path(current_list.clone(), String::from(&versionid))?;
108 let disabled = format!("{}.disabled", file); 172 let disabled = format!("{}.disabled", file);
109 173
110 rename(file, disabled)?; 174 rename(file, disabled)?;
@@ -114,7 +178,7 @@ pub fn disable_version(
114 Ok(()) 178 Ok(())
115} 179}
116 180
117pub fn delete_version(list: List, version: String) -> MLE<()> { 181pub fn delete_version(list: &List, version: String) -> MLE<()> {
118 let file = get_file_path(list, version)?; 182 let file = get_file_path(list, version)?;
119 183
120 remove_file(file)?; 184 remove_file(file)?;
@@ -122,14 +186,16 @@ pub fn delete_version(list: List, version: String) -> MLE<()> {
122 Ok(()) 186 Ok(())
123} 187}
124 188
125pub fn get_file_path(list: List, versionid: String) -> MLE<String> { 189pub fn get_file_path(list: &List, versionid: String) -> MLE<String> {
126 let mut names: HashMap<String, String> = HashMap::new(); 190 let mut names: HashMap<String, String> = HashMap::new();
127 for file in read_dir(list.download_folder)? { 191 for file in read_dir(&list.download_folder)? {
128 let path = file?.path(); 192 let path = file?.path();
129 if path.is_file() { 193 if path.is_file() {
130 let pathstr = match path.to_str().ok_or("") { 194 let pathstr = match path.to_str().ok_or("") {
131 Ok(s) => s, 195 Ok(s) => s,
132 Err(..) => return Err(MLError::new(ErrorType::Other, "INVALID_PATH")), 196 Err(..) => {
197 return Err(MLError::new(ErrorType::Other, "INVALID_PATH"))
198 }
133 }; 199 };
134 let namesplit: Vec<&str> = pathstr.split('.').collect(); 200 let namesplit: Vec<&str> = pathstr.split('.').collect();
135 let ver_id = namesplit[namesplit.len() - 2]; 201 let ver_id = namesplit[namesplit.len() - 2];
@@ -168,7 +234,6 @@ pub fn get_downloaded_versions(list: List) -> MLE<HashMap<String, String>> {
168 234
169pub fn clean_list_dir(list: &List) -> MLE<()> { 235pub fn clean_list_dir(list: &List) -> MLE<()> {
170 let dl_path = &list.download_folder; 236 let dl_path = &list.download_folder;
171 println!(" └Clean directory for: {}", list.id);
172 for entry in std::fs::read_dir(dl_path)? { 237 for entry in std::fs::read_dir(dl_path)? {
173 let entry = entry?; 238 let entry = entry?;
174 std::fs::remove_file(entry.path())?; 239 std::fs::remove_file(entry.path())?;
diff --git a/src/lib.rs b/src/lib.rs
index 1c40ceb..f77befc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,14 +6,28 @@ pub mod db;
6pub mod error; 6pub mod error;
7pub mod files; 7pub mod files;
8 8
9use std::{fmt::Display, fs::{File, remove_file, self}, io::{Write, Read}, time::Duration}; 9use std::{
10 fmt::Display,
11 fs::{self, remove_file, File},
12 io::{Read, Write},
13 time::Duration,
14};
10 15
11pub use apis::*;
12use apis::modrinth::{get_game_versions, GameVersion, GameVersionType}; 16use apis::modrinth::{get_game_versions, GameVersion, GameVersionType};
17pub use apis::*;
13pub use commands::*; 18pub use commands::*;
14use error::{ErrorType, MLError, MLE}; 19use error::{ErrorType, MLError, MLE};
20use indicatif::{ProgressBar, ProgressStyle};
15use serde::{Deserialize, Serialize}; 21use serde::{Deserialize, Serialize};
16 22
23pub static STYLE_BAR_BYTE: &str =
24 "{spinner:.green}{wide_msg}{bytes}/{total_bytes} [{bar:.green/lime}]";
25pub static STYLE_BAR_POS: &str = " {wide_msg}{pos}/{len} [{bar:.green/lime}]";
26pub static STYLE_SPINNER: &str = "{spinner:.green}{wide_msg}";
27pub static STYLE_OPERATION: &str = " {wide_msg}";
28pub static STYLE_MESSAGE: &str = "{wide_msg}";
29pub static PROGRESS_CHARS: &str = "#>-";
30
17#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 31#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
18pub enum Modloader { 32pub enum Modloader {
19 #[serde(rename(serialize = "fabric", deserialize = "fabric"))] 33 #[serde(rename(serialize = "fabric", deserialize = "fabric"))]
@@ -30,7 +44,9 @@ impl Modloader {
30 "forge" => Ok(Modloader::Forge), 44 "forge" => Ok(Modloader::Forge),
31 "fabric" => Ok(Modloader::Fabric), 45 "fabric" => Ok(Modloader::Fabric),
32 "quilt" => Ok(Modloader::Quilt), 46 "quilt" => Ok(Modloader::Quilt),
33 _ => Err(MLError::new(ErrorType::ArgumentError, "UNKNOWN_MODLOADER")), 47 _ => {
48 Err(MLError::new(ErrorType::ArgumentError, "UNKNOWN_MODLOADER"))
49 }
34 } 50 }
35 } 51 }
36} 52}
@@ -51,21 +67,29 @@ pub enum VersionLevel {
51 Release, 67 Release,
52 #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))] 68 #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))]
53 Snapshot, 69 Snapshot,
54 Version(String) 70 Version(String),
55} 71}
56 72
57/// Checks if update needed (time) 73/// Checks if update needed (time)
58/// if yes: get versions, update 74/// if yes: get versions, update
59pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> { 75pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> {
76 let p = ProgressBar::new(1);
77 p.set_style(ProgressStyle::with_template(STYLE_MESSAGE).unwrap());
78 p.set_message("Update minecraft versions");
79
60 let creation_time = fs::metadata(path)?.created()?; 80 let creation_time = fs::metadata(path)?.created()?;
61 if !force && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24) { return Ok(()); } 81 if !force
62 print!("Update minecraft versions"); 82 && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24)
63 std::io::stdout().flush()?; 83 {
84 return Ok(());
85 }
86
64 let versions = get_game_versions().await; 87 let versions = get_game_versions().await;
65 remove_file(path)?; 88 remove_file(path)?;
66 let mut file = File::create(path)?; 89 let mut file = File::create(path)?;
67 file.write_all(&serde_json::to_string_pretty(&versions)?.as_bytes())?; 90 file.write_all(serde_json::to_string_pretty(&versions)?.as_bytes())?;
68 println!(" ✓"); 91
92 p.finish_with_message("Updated minecraft versions");
69 Ok(()) 93 Ok(())
70} 94}
71 95
@@ -79,7 +103,6 @@ pub fn load_game_versions(path: &str) -> MLE<Vec<GameVersion>> {
79} 103}
80 104
81impl VersionLevel { 105impl VersionLevel {
82
83 pub fn from(str: &str) -> Self { 106 pub fn from(str: &str) -> Self {
84 match str { 107 match str {
85 "release" => VersionLevel::Release, 108 "release" => VersionLevel::Release,
@@ -88,29 +111,38 @@ impl VersionLevel {
88 } 111 }
89 } 112 }
90 113
91 pub async fn get(self, versions_path: &str, force_update: bool) -> MLE<String> { 114 pub async fn get(
115 self,
116 versions_path: &str,
117 force_update: bool,
118 ) -> MLE<String> {
92 let path = format!("{}/versions.json", versions_path); 119 let path = format!("{}/versions.json", versions_path);
93 check_game_versions(&path, force_update).await?; 120 check_game_versions(&path, force_update).await?;
94 let mut versions = load_game_versions(&path)?.into_iter(); 121 let mut versions = load_game_versions(&path)?.into_iter();
95 122
96 match self { 123 match self {
97 VersionLevel::Release => { 124 VersionLevel::Release => {
98 let release = versions.find(|ver| ver.version_type == GameVersionType::release).unwrap(); 125 let release = versions
99 println!("{:?}", release); 126 .find(|ver| ver.version_type == GameVersionType::release)
127 .unwrap();
100 Ok(release.version) 128 Ok(release.version)
101 }, 129 }
102 VersionLevel::Snapshot => { 130 VersionLevel::Snapshot => {
103 let snapshot = versions.find(|ver| ver.version_type == GameVersionType::snapshot).unwrap(); 131 let snapshot = versions
104 println!("{:?}", snapshot); 132 .find(|ver| ver.version_type == GameVersionType::snapshot)
133 .unwrap();
105 Ok(snapshot.version) 134 Ok(snapshot.version)
106 }, 135 }
107 VersionLevel::Version(v) => { 136 VersionLevel::Version(v) => {
108 if versions.find(|ver| ver.version == v).is_some() { 137 if versions.any(|ver| ver.version == v) {
109 Ok(v) 138 Ok(v)
110 } else { 139 } else {
111 Err(MLError::new(ErrorType::ConfigError, "unknown minecraft version")) 140 Err(MLError::new(
141 ErrorType::ConfigError,
142 "unknown minecraft version",
143 ))
112 } 144 }
113 }, 145 }
114 } 146 }
115 } 147 }
116} 148}
diff --git a/src/main.rs b/src/main.rs
index 31a320b..5d60a17 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,8 +2,9 @@ use clap::{Parser, Subcommand};
2use modlist::{ 2use modlist::{
3 config::Cfg, 3 config::Cfg,
4 db::{config_get_current_list, lists_get, lists_get_all_ids}, 4 db::{config_get_current_list, lists_get, lists_get_all_ids},
5 download, export, get_current_list, import, list_add, list_change, list_remove, list_version, 5 download, export, get_current_list, import, list_add, list_change,
6 mod_add, mod_remove, update, IDSelector, List, Modloader, VersionLevel, list_list, AddMod, 6 list_list, list_remove, list_version, mod_add, mod_remove, update, AddMod,
7 IDSelector, List, Modloader, VersionLevel,
7}; 8};
8 9
9#[derive(Parser)] 10#[derive(Parser)]
@@ -15,10 +16,6 @@ struct Cli {
15 /// config file path 16 /// config file path
16 #[arg(short, long)] 17 #[arg(short, long)]
17 config: Option<String>, 18 config: Option<String>,
18
19 /// Force GameVersion update
20 #[arg(long)]
21 force_gameupdate: bool,
22} 19}
23 20
24#[derive(Subcommand)] 21#[derive(Subcommand)]
@@ -30,6 +27,10 @@ enum Commands {
30 List { 27 List {
31 #[command(subcommand)] 28 #[command(subcommand)]
32 command: ListCommands, 29 command: ListCommands,
30
31 /// Force GameVersion update
32 #[arg(long)]
33 force_gameupdate: bool,
33 }, 34 },
34 Download { 35 Download {
35 /// download all lists 36 /// download all lists
@@ -43,7 +44,7 @@ enum Commands {
43 /// remove disabled versions 44 /// remove disabled versions
44 #[arg(short, long)] 45 #[arg(short, long)]
45 remove: bool, 46 remove: bool,
46 47
47 /// optional List selection, else default list will be used 48 /// optional List selection, else default list will be used
48 #[arg(short, long)] 49 #[arg(short, long)]
49 list: Option<String>, 50 list: Option<String>,
@@ -81,7 +82,7 @@ enum Commands {
81 /// the list you want to export 82 /// the list you want to export
82 list: Option<String>, 83 list: Option<String>,
83 }, 84 },
84 Test 85 Test,
85} 86}
86 87
87#[derive(Subcommand)] 88#[derive(Subcommand)]
@@ -160,119 +161,134 @@ async fn main() {
160 let config = Cfg::init(cli.config).await.unwrap(); 161 let config = Cfg::init(cli.config).await.unwrap();
161 162
162 match cli.command { 163 match cli.command {
163 Commands::Mod { command } => { 164 Commands::Mod { command } => match command {
164 match command { 165 ModCommands::Add {
165 #[allow(unused_variables)] 166 id,
166 ModCommands::Add { 167 version,
167 id, 168 list,
168 version, 169 download,
169 list, 170 lock,
170 download, 171 } => {
171 lock, 172 let listf = match list {
172 } => { 173 Some(list) => lists_get(&config, &list).unwrap(),
173 let listf = match list { 174 None => lists_get(
174 Some(list) => lists_get(config.clone(), list).unwrap(), 175 &config,
175 None => lists_get( 176 &config_get_current_list(&config).unwrap(),
176 config.clone(), 177 )
177 config_get_current_list(config.clone()).unwrap(), 178 .unwrap(),
178 ) 179 };
179 .unwrap(),
180 };
181 180
182 let marked_id = match version { 181 let marked_id = match version {
183 true => IDSelector::VersionID(id), 182 true => IDSelector::VersionID(id),
184 false => IDSelector::ModificationID(id), 183 false => IDSelector::ModificationID(id),
185 }; 184 };
186 185
187 let add_id = AddMod { id: marked_id, set_version: lock }; 186 let add_id = AddMod {
187 id: marked_id,
188 set_version: lock,
189 };
188 190
189 mod_add(config, vec![add_id], listf, download).await 191 mod_add(&config, vec![add_id], listf, download).await
190 }
191 ModCommands::Remove { id, list } => {
192 let listf = match list {
193 Some(list) => lists_get(config.clone(), list).unwrap(),
194 None => lists_get(
195 config.clone(),
196 config_get_current_list(config.clone()).unwrap(),
197 )
198 .unwrap(),
199 };
200 mod_remove(config, &id, listf)
201 }
202 } 192 }
203 } 193 ModCommands::Remove { id, list } => {
204 Commands::List { command } => { 194 let listf = match list {
205 match command { 195 Some(list) => lists_get(&config, &list).unwrap(),
206 ListCommands::Add { 196 None => lists_get(
207 id, 197 &config,
208 directory, 198 &config_get_current_list(&config).unwrap(),
209 modloader, 199 )
210 version, 200 .unwrap(),
211 } => { 201 };
212 let ml = match modloader { 202 mod_remove(&config, &id, &listf)
213 Some(ml) => Modloader::from(&ml).unwrap(),
214 None => config.clone().defaults.modloader,
215 };
216
217 let versions_path = &config.versions;
218 let ver = match version {
219 Some(ver) => VersionLevel::from(&ver).get(versions_path, cli.force_gameupdate).await.unwrap(),
220 None => config.clone().defaults.version.get(versions_path, cli.force_gameupdate).await.unwrap(),
221 };
222
223 list_add(config, id, ver, ml, directory)
224 }
225 ListCommands::Remove { id } => list_remove(config, id),
226 ListCommands::List => {
227 list_list(config)
228 }
229 ListCommands::Change { id } => list_change(config, id),
230 ListCommands::Version {
231 id,
232 version,
233 download,
234 remove,
235 } => list_version(config, id, version, download, remove).await,
236 } 203 }
237 } 204 },
205 Commands::List {
206 command,
207 force_gameupdate,
208 } => match command {
209 ListCommands::Add {
210 id,
211 directory,
212 modloader,
213 version,
214 } => {
215 let ml = match modloader {
216 Some(ml) => Modloader::from(&ml).unwrap(),
217 None => config.defaults.modloader.clone(),
218 };
219
220 let versions_path = &config.versions;
221 let ver = match version {
222 Some(ver) => VersionLevel::from(&ver)
223 .get(versions_path, force_gameupdate)
224 .await
225 .unwrap(),
226 None => config
227 .defaults
228 .version
229 .clone()
230 .get(versions_path, force_gameupdate)
231 .await
232 .unwrap(),
233 };
234
235 list_add(&config, &id, &ver, &ml, &directory)
236 }
237 ListCommands::Remove { id } => list_remove(&config, &id),
238 ListCommands::List => list_list(&config),
239 ListCommands::Change { id } => list_change(&config, &id),
240 ListCommands::Version {
241 id,
242 version,
243 download,
244 remove,
245 } => list_version(&config, &id, version, download, remove).await,
246 },
238 Commands::Update { 247 Commands::Update {
239 all, 248 all,
240 download, 249 download,
241 clean, 250 clean,
242 remove, 251 remove,
243 list 252 list,
244 } => { 253 } => {
245 let mut liststack: Vec<List> = vec![]; 254 let mut liststack: Vec<List> = vec![];
246 if all { 255 if all {
247 let list_ids = lists_get_all_ids(config.clone()).unwrap(); 256 let list_ids = lists_get_all_ids(&config).unwrap();
248 for id in list_ids { 257 for id in list_ids {
249 liststack.push(lists_get(config.clone(), id).unwrap()); 258 liststack.push(lists_get(&config, &id).unwrap());
250 } 259 }
251 } else { 260 } else {
252 let current = match list { 261 let current = match list {
253 Some(l) => lists_get(config.clone(), l).unwrap(), 262 Some(l) => lists_get(&config, &l).unwrap(),
254 None => get_current_list(config.clone()).unwrap(), 263 None => get_current_list(&config).unwrap(),
255 }; 264 };
256 liststack.push(current) 265 liststack.push(current)
257 } 266 }
258 update(config, liststack, clean, download, remove).await 267
268 update(&config, liststack, clean, download, remove).await
259 } 269 }
260 Commands::Download { all, clean, remove, list } => { 270 Commands::Download {
271 all,
272 clean,
273 remove,
274 list,
275 } => {
261 let mut liststack: Vec<List> = vec![]; 276 let mut liststack: Vec<List> = vec![];
262 if all { 277 if all {
263 let list_ids = lists_get_all_ids(config.clone()).unwrap(); 278 let list_ids = lists_get_all_ids(&config).unwrap();
264 for id in list_ids { 279 for id in list_ids {
265 liststack.push(lists_get(config.clone(), id).unwrap()); 280 liststack.push(lists_get(&config, &id).unwrap());
266 } 281 }
267 } else { 282 } else {
268 let current = match list { 283 let current = match list {
269 Some(l) => lists_get(config.clone(), l).unwrap(), 284 Some(l) => lists_get(&config, &l).unwrap(),
270 None => get_current_list(config.clone()).unwrap(), 285 None => get_current_list(&config).unwrap(),
271 }; 286 };
272 liststack.push(current) 287 liststack.push(current)
273 } 288 }
274 download(config, liststack, clean, remove).await 289
275 }, 290 download(&config, liststack, clean, remove).await
291 }
276 Commands::Import { file, download } => { 292 Commands::Import { file, download } => {
277 let filestr: String = match file { 293 let filestr: String = match file {
278 Some(args) => args, 294 Some(args) => args,
@@ -284,9 +300,9 @@ async fn main() {
284 .unwrap(), 300 .unwrap(),
285 }; 301 };
286 302
287 import(config, filestr, download).await 303 import(&config, &filestr, download).await
288 } 304 }
289 Commands::Export { list } => export(config, list), 305 Commands::Export { list } => export(&config, list),
290 Commands::Test => Ok(()), 306 Commands::Test => Ok(()),
291 } 307 }
292 .unwrap(); 308 .unwrap();