Compare changes

Choose any two refs to compare.

Changed files
+927 -30
src
+3 -1
.gitignore
··· 1 1 /target 2 - .idea 2 + .idea 3 + .env 4 + .DS_Store
+696 -1
Cargo.lock
··· 77 77 ] 78 78 79 79 [[package]] 80 + name = "anyhow" 81 + version = "1.0.100" 82 + source = "registry+https://github.com/rust-lang/crates.io-index" 83 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 84 + 85 + [[package]] 80 86 name = "async-compression" 81 87 version = "0.4.32" 82 88 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 85 91 "compression-codecs", 86 92 "compression-core", 87 93 "futures-core", 94 + "futures-io", 88 95 "pin-project-lite", 96 + "tokio", 97 + ] 98 + 99 + [[package]] 100 + name = "async-trait" 101 + version = "0.1.89" 102 + source = "registry+https://github.com/rust-lang/crates.io-index" 103 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 104 + dependencies = [ 105 + "proc-macro2", 106 + "quote", 107 + "syn", 89 108 ] 90 109 91 110 [[package]] ··· 95 114 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 96 115 97 116 [[package]] 117 + name = "attohttpc" 118 + version = "0.30.1" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" 121 + dependencies = [ 122 + "base64", 123 + "http", 124 + "log", 125 + "native-tls", 126 + "serde", 127 + "serde_json", 128 + "url", 129 + ] 130 + 131 + [[package]] 132 + name = "aws-creds" 133 + version = "0.39.0" 134 + source = "registry+https://github.com/rust-lang/crates.io-index" 135 + checksum = "b13804829a843b3f26e151c97acbb315ee1177a2724690edfcd28f1894146200" 136 + dependencies = [ 137 + "attohttpc", 138 + "home", 139 + "log", 140 + "quick-xml", 141 + "rust-ini", 142 + "serde", 143 + "thiserror", 144 + "time", 145 + "url", 146 + ] 147 + 148 + [[package]] 149 + name = "aws-region" 150 + version = "0.28.0" 151 + source = "registry+https://github.com/rust-lang/crates.io-index" 152 + checksum = "5532f65342f789f9c1b7078ea9c9cd9293cd62dcc284fa99adc4a1c9ba43469c" 153 + dependencies = [ 154 + "thiserror", 155 + ] 156 + 157 + [[package]] 98 158 name = "backtrace" 99 159 version = "0.3.76" 100 160 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 122 182 checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 123 183 124 184 [[package]] 185 + name = "block-buffer" 186 + version = "0.10.4" 187 + source = "registry+https://github.com/rust-lang/crates.io-index" 188 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 189 + dependencies = [ 190 + "generic-array", 191 + ] 192 + 193 + [[package]] 125 194 name = "bumpalo" 126 195 version = "3.19.0" 127 196 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 134 203 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 135 204 136 205 [[package]] 206 + name = "castaway" 207 + version = "0.2.4" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 210 + dependencies = [ 211 + "rustversion", 212 + ] 213 + 214 + [[package]] 137 215 name = "cc" 138 216 version = "1.2.41" 139 217 source = "registry+https://github.com/rust-lang/crates.io-index" 140 218 checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" 141 219 dependencies = [ 142 220 "find-msvc-tools", 221 + "jobserver", 222 + "libc", 143 223 "shlex", 144 224 ] 145 225 ··· 196 276 checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 197 277 198 278 [[package]] 279 + name = "compact_str" 280 + version = "0.7.1" 281 + source = "registry+https://github.com/rust-lang/crates.io-index" 282 + checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" 283 + dependencies = [ 284 + "castaway", 285 + "cfg-if", 286 + "itoa", 287 + "ryu", 288 + "static_assertions", 289 + ] 290 + 291 + [[package]] 199 292 name = "compression-codecs" 200 293 version = "0.4.31" 201 294 source = "registry+https://github.com/rust-lang/crates.io-index" 202 295 checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" 203 296 dependencies = [ 204 297 "compression-core", 298 + "zstd", 299 + "zstd-safe", 205 300 ] 206 301 207 302 [[package]] ··· 211 306 checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" 212 307 213 308 [[package]] 309 + name = "const-random" 310 + version = "0.1.18" 311 + source = "registry+https://github.com/rust-lang/crates.io-index" 312 + checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" 313 + dependencies = [ 314 + "const-random-macro", 315 + ] 316 + 317 + [[package]] 318 + name = "const-random-macro" 319 + version = "0.1.16" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 322 + dependencies = [ 323 + "getrandom 0.2.16", 324 + "once_cell", 325 + "tiny-keccak", 326 + ] 327 + 328 + [[package]] 214 329 name = "core-foundation" 215 330 version = "0.9.4" 216 331 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 227 342 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 228 343 229 344 [[package]] 345 + name = "cpufeatures" 346 + version = "0.2.17" 347 + source = "registry+https://github.com/rust-lang/crates.io-index" 348 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 349 + dependencies = [ 350 + "libc", 351 + ] 352 + 353 + [[package]] 354 + name = "crunchy" 355 + version = "0.2.4" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 358 + 359 + [[package]] 360 + name = "crypto-common" 361 + version = "0.1.6" 362 + source = "registry+https://github.com/rust-lang/crates.io-index" 363 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 364 + dependencies = [ 365 + "generic-array", 366 + "typenum", 367 + ] 368 + 369 + [[package]] 370 + name = "deranged" 371 + version = "0.5.4" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 374 + dependencies = [ 375 + "powerfmt", 376 + "serde_core", 377 + ] 378 + 379 + [[package]] 380 + name = "digest" 381 + version = "0.10.7" 382 + source = "registry+https://github.com/rust-lang/crates.io-index" 383 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 384 + dependencies = [ 385 + "block-buffer", 386 + "crypto-common", 387 + "subtle", 388 + ] 389 + 390 + [[package]] 230 391 name = "displaydoc" 231 392 version = "0.2.5" 232 393 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 235 396 "proc-macro2", 236 397 "quote", 237 398 "syn", 399 + ] 400 + 401 + [[package]] 402 + name = "dlv-list" 403 + version = "0.5.2" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" 406 + dependencies = [ 407 + "const-random", 238 408 ] 239 409 240 410 [[package]] ··· 334 504 ] 335 505 336 506 [[package]] 507 + name = "futures" 508 + version = "0.3.31" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 511 + dependencies = [ 512 + "futures-channel", 513 + "futures-core", 514 + "futures-executor", 515 + "futures-io", 516 + "futures-sink", 517 + "futures-task", 518 + "futures-util", 519 + ] 520 + 521 + [[package]] 337 522 name = "futures-channel" 338 523 version = "0.3.31" 339 524 source = "registry+https://github.com/rust-lang/crates.io-index" 340 525 checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 341 526 dependencies = [ 342 527 "futures-core", 528 + "futures-sink", 343 529 ] 344 530 345 531 [[package]] ··· 349 535 checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 350 536 351 537 [[package]] 538 + name = "futures-executor" 539 + version = "0.3.31" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 542 + dependencies = [ 543 + "futures-core", 544 + "futures-task", 545 + "futures-util", 546 + ] 547 + 548 + [[package]] 549 + name = "futures-io" 550 + version = "0.3.31" 551 + source = "registry+https://github.com/rust-lang/crates.io-index" 552 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 553 + 554 + [[package]] 555 + name = "futures-macro" 556 + version = "0.3.31" 557 + source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 559 + dependencies = [ 560 + "proc-macro2", 561 + "quote", 562 + "syn", 563 + ] 564 + 565 + [[package]] 352 566 name = "futures-sink" 353 567 version = "0.3.31" 354 568 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 366 580 source = "registry+https://github.com/rust-lang/crates.io-index" 367 581 checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 368 582 dependencies = [ 583 + "futures-channel", 369 584 "futures-core", 585 + "futures-io", 586 + "futures-macro", 587 + "futures-sink", 370 588 "futures-task", 589 + "memchr", 371 590 "pin-project-lite", 372 591 "pin-utils", 592 + "slab", 593 + ] 594 + 595 + [[package]] 596 + name = "generic-array" 597 + version = "0.14.7" 598 + source = "registry+https://github.com/rust-lang/crates.io-index" 599 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 600 + dependencies = [ 601 + "typenum", 602 + "version_check", 373 603 ] 374 604 375 605 [[package]] ··· 422 652 423 653 [[package]] 424 654 name = "hashbrown" 655 + version = "0.14.5" 656 + source = "registry+https://github.com/rust-lang/crates.io-index" 657 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 658 + 659 + [[package]] 660 + name = "hashbrown" 425 661 version = "0.16.0" 426 662 source = "registry+https://github.com/rust-lang/crates.io-index" 427 663 checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" ··· 431 667 version = "0.5.0" 432 668 source = "registry+https://github.com/rust-lang/crates.io-index" 433 669 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 670 + 671 + [[package]] 672 + name = "hex" 673 + version = "0.4.3" 674 + source = "registry+https://github.com/rust-lang/crates.io-index" 675 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 676 + 677 + [[package]] 678 + name = "hmac" 679 + version = "0.12.1" 680 + source = "registry+https://github.com/rust-lang/crates.io-index" 681 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 682 + dependencies = [ 683 + "digest", 684 + ] 685 + 686 + [[package]] 687 + name = "home" 688 + version = "0.5.11" 689 + source = "registry+https://github.com/rust-lang/crates.io-index" 690 + checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 691 + dependencies = [ 692 + "windows-sys 0.59.0", 693 + ] 434 694 435 695 [[package]] 436 696 name = "http" ··· 666 926 checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 667 927 dependencies = [ 668 928 "equivalent", 669 - "hashbrown", 929 + "hashbrown 0.16.0", 670 930 ] 671 931 672 932 [[package]] ··· 733 993 ] 734 994 735 995 [[package]] 996 + name = "jobserver" 997 + version = "0.1.34" 998 + source = "registry+https://github.com/rust-lang/crates.io-index" 999 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 1000 + dependencies = [ 1001 + "getrandom 0.3.3", 1002 + "libc", 1003 + ] 1004 + 1005 + [[package]] 736 1006 name = "js-sys" 737 1007 version = "0.3.81" 738 1008 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 767 1037 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 768 1038 769 1039 [[package]] 1040 + name = "maybe-async" 1041 + version = "0.2.10" 1042 + source = "registry+https://github.com/rust-lang/crates.io-index" 1043 + checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" 1044 + dependencies = [ 1045 + "proc-macro2", 1046 + "quote", 1047 + "syn", 1048 + ] 1049 + 1050 + [[package]] 1051 + name = "md5" 1052 + version = "0.8.0" 1053 + source = "registry+https://github.com/rust-lang/crates.io-index" 1054 + checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" 1055 + 1056 + [[package]] 770 1057 name = "memchr" 771 1058 version = "2.7.6" 772 1059 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 777 1064 version = "0.3.17" 778 1065 source = "registry+https://github.com/rust-lang/crates.io-index" 779 1066 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1067 + 1068 + [[package]] 1069 + name = "minidom" 1070 + version = "0.16.0" 1071 + source = "registry+https://github.com/rust-lang/crates.io-index" 1072 + checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" 1073 + dependencies = [ 1074 + "rxml", 1075 + ] 780 1076 781 1077 [[package]] 782 1078 name = "miniz_oxide" ··· 816 1112 ] 817 1113 818 1114 [[package]] 1115 + name = "ntapi" 1116 + version = "0.4.1" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 1119 + dependencies = [ 1120 + "winapi", 1121 + ] 1122 + 1123 + [[package]] 1124 + name = "num-conv" 1125 + version = "0.1.0" 1126 + source = "registry+https://github.com/rust-lang/crates.io-index" 1127 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1128 + 1129 + [[package]] 1130 + name = "objc2-core-foundation" 1131 + version = "0.3.2" 1132 + source = "registry+https://github.com/rust-lang/crates.io-index" 1133 + checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" 1134 + dependencies = [ 1135 + "bitflags", 1136 + ] 1137 + 1138 + [[package]] 1139 + name = "objc2-io-kit" 1140 + version = "0.3.2" 1141 + source = "registry+https://github.com/rust-lang/crates.io-index" 1142 + checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" 1143 + dependencies = [ 1144 + "libc", 1145 + "objc2-core-foundation", 1146 + ] 1147 + 1148 + [[package]] 819 1149 name = "object" 820 1150 version = "0.37.3" 821 1151 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 881 1211 ] 882 1212 883 1213 [[package]] 1214 + name = "ordered-multimap" 1215 + version = "0.7.3" 1216 + source = "registry+https://github.com/rust-lang/crates.io-index" 1217 + checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" 1218 + dependencies = [ 1219 + "dlv-list", 1220 + "hashbrown 0.14.5", 1221 + ] 1222 + 1223 + [[package]] 884 1224 name = "pds_whatsit_compress_test" 885 1225 version = "0.1.0" 886 1226 dependencies = [ 1227 + "anyhow", 887 1228 "async-compression", 888 1229 "clap", 889 1230 "dotenvy", 890 1231 "env_logger", 1232 + "futures", 891 1233 "log", 892 1234 "reqwest", 1235 + "rust-s3", 1236 + "serde", 1237 + "serde_json", 893 1238 "tokio", 1239 + "tokio-stream", 1240 + "tokio-util", 894 1241 ] 895 1242 896 1243 [[package]] ··· 942 1289 ] 943 1290 944 1291 [[package]] 1292 + name = "powerfmt" 1293 + version = "0.2.0" 1294 + source = "registry+https://github.com/rust-lang/crates.io-index" 1295 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1296 + 1297 + [[package]] 945 1298 name = "proc-macro2" 946 1299 version = "1.0.101" 947 1300 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 951 1304 ] 952 1305 953 1306 [[package]] 1307 + name = "quick-xml" 1308 + version = "0.38.3" 1309 + source = "registry+https://github.com/rust-lang/crates.io-index" 1310 + checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" 1311 + dependencies = [ 1312 + "memchr", 1313 + "serde", 1314 + ] 1315 + 1316 + [[package]] 954 1317 name = "quote" 955 1318 version = "1.0.41" 956 1319 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1004 1367 "bytes", 1005 1368 "encoding_rs", 1006 1369 "futures-core", 1370 + "futures-util", 1007 1371 "h2", 1008 1372 "http", 1009 1373 "http-body", ··· 1025 1389 "sync_wrapper", 1026 1390 "tokio", 1027 1391 "tokio-native-tls", 1392 + "tokio-util", 1028 1393 "tower", 1029 1394 "tower-http", 1030 1395 "tower-service", 1031 1396 "url", 1032 1397 "wasm-bindgen", 1033 1398 "wasm-bindgen-futures", 1399 + "wasm-streams", 1034 1400 "web-sys", 1035 1401 ] 1036 1402 ··· 1049 1415 ] 1050 1416 1051 1417 [[package]] 1418 + name = "rust-ini" 1419 + version = "0.21.3" 1420 + source = "registry+https://github.com/rust-lang/crates.io-index" 1421 + checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" 1422 + dependencies = [ 1423 + "cfg-if", 1424 + "ordered-multimap", 1425 + ] 1426 + 1427 + [[package]] 1428 + name = "rust-s3" 1429 + version = "0.37.0" 1430 + source = "registry+https://github.com/rust-lang/crates.io-index" 1431 + checksum = "94f9b973bd4097f5bb47e5827dcb9fb5dc17e93879e46badc27d2a4e9a4e5588" 1432 + dependencies = [ 1433 + "async-trait", 1434 + "aws-creds", 1435 + "aws-region", 1436 + "base64", 1437 + "bytes", 1438 + "cfg-if", 1439 + "futures-util", 1440 + "hex", 1441 + "hmac", 1442 + "http", 1443 + "log", 1444 + "maybe-async", 1445 + "md5", 1446 + "minidom", 1447 + "percent-encoding", 1448 + "quick-xml", 1449 + "reqwest", 1450 + "serde", 1451 + "serde_derive", 1452 + "serde_json", 1453 + "sha2", 1454 + "sysinfo", 1455 + "thiserror", 1456 + "time", 1457 + "tokio", 1458 + "tokio-stream", 1459 + "url", 1460 + ] 1461 + 1462 + [[package]] 1052 1463 name = "rustc-demangle" 1053 1464 version = "0.1.26" 1054 1465 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1107 1518 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1108 1519 1109 1520 [[package]] 1521 + name = "rxml" 1522 + version = "0.11.1" 1523 + source = "registry+https://github.com/rust-lang/crates.io-index" 1524 + checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" 1525 + dependencies = [ 1526 + "bytes", 1527 + "rxml_validation", 1528 + ] 1529 + 1530 + [[package]] 1531 + name = "rxml_validation" 1532 + version = "0.11.0" 1533 + source = "registry+https://github.com/rust-lang/crates.io-index" 1534 + checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" 1535 + dependencies = [ 1536 + "compact_str", 1537 + ] 1538 + 1539 + [[package]] 1110 1540 name = "ryu" 1111 1541 version = "1.0.20" 1112 1542 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1200 1630 ] 1201 1631 1202 1632 [[package]] 1633 + name = "sha2" 1634 + version = "0.10.9" 1635 + source = "registry+https://github.com/rust-lang/crates.io-index" 1636 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1637 + dependencies = [ 1638 + "cfg-if", 1639 + "cpufeatures", 1640 + "digest", 1641 + ] 1642 + 1643 + [[package]] 1203 1644 name = "shlex" 1204 1645 version = "1.3.0" 1205 1646 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1234 1675 checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1235 1676 1236 1677 [[package]] 1678 + name = "static_assertions" 1679 + version = "1.1.0" 1680 + source = "registry+https://github.com/rust-lang/crates.io-index" 1681 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1682 + 1683 + [[package]] 1237 1684 name = "strsim" 1238 1685 version = "0.11.1" 1239 1686 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1277 1724 ] 1278 1725 1279 1726 [[package]] 1727 + name = "sysinfo" 1728 + version = "0.37.2" 1729 + source = "registry+https://github.com/rust-lang/crates.io-index" 1730 + checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" 1731 + dependencies = [ 1732 + "libc", 1733 + "memchr", 1734 + "ntapi", 1735 + "objc2-core-foundation", 1736 + "objc2-io-kit", 1737 + "windows", 1738 + ] 1739 + 1740 + [[package]] 1280 1741 name = "system-configuration" 1281 1742 version = "0.6.1" 1282 1743 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1308 1769 "once_cell", 1309 1770 "rustix", 1310 1771 "windows-sys 0.61.2", 1772 + ] 1773 + 1774 + [[package]] 1775 + name = "thiserror" 1776 + version = "2.0.17" 1777 + source = "registry+https://github.com/rust-lang/crates.io-index" 1778 + checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 1779 + dependencies = [ 1780 + "thiserror-impl", 1781 + ] 1782 + 1783 + [[package]] 1784 + name = "thiserror-impl" 1785 + version = "2.0.17" 1786 + source = "registry+https://github.com/rust-lang/crates.io-index" 1787 + checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 1788 + dependencies = [ 1789 + "proc-macro2", 1790 + "quote", 1791 + "syn", 1792 + ] 1793 + 1794 + [[package]] 1795 + name = "time" 1796 + version = "0.3.44" 1797 + source = "registry+https://github.com/rust-lang/crates.io-index" 1798 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 1799 + dependencies = [ 1800 + "deranged", 1801 + "itoa", 1802 + "num-conv", 1803 + "powerfmt", 1804 + "serde", 1805 + "time-core", 1806 + "time-macros", 1807 + ] 1808 + 1809 + [[package]] 1810 + name = "time-core" 1811 + version = "0.1.6" 1812 + source = "registry+https://github.com/rust-lang/crates.io-index" 1813 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 1814 + 1815 + [[package]] 1816 + name = "time-macros" 1817 + version = "0.2.24" 1818 + source = "registry+https://github.com/rust-lang/crates.io-index" 1819 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 1820 + dependencies = [ 1821 + "num-conv", 1822 + "time-core", 1823 + ] 1824 + 1825 + [[package]] 1826 + name = "tiny-keccak" 1827 + version = "2.0.2" 1828 + source = "registry+https://github.com/rust-lang/crates.io-index" 1829 + checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 1830 + dependencies = [ 1831 + "crunchy", 1311 1832 ] 1312 1833 1313 1834 [[package]] ··· 1370 1891 ] 1371 1892 1372 1893 [[package]] 1894 + name = "tokio-stream" 1895 + version = "0.1.17" 1896 + source = "registry+https://github.com/rust-lang/crates.io-index" 1897 + checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 1898 + dependencies = [ 1899 + "futures-core", 1900 + "pin-project-lite", 1901 + "tokio", 1902 + ] 1903 + 1904 + [[package]] 1373 1905 name = "tokio-util" 1374 1906 version = "0.7.16" 1375 1907 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1377 1909 dependencies = [ 1378 1910 "bytes", 1379 1911 "futures-core", 1912 + "futures-io", 1380 1913 "futures-sink", 1381 1914 "pin-project-lite", 1382 1915 "tokio", ··· 1453 1986 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1454 1987 1455 1988 [[package]] 1989 + name = "typenum" 1990 + version = "1.19.0" 1991 + source = "registry+https://github.com/rust-lang/crates.io-index" 1992 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 1993 + 1994 + [[package]] 1456 1995 name = "unicode-ident" 1457 1996 version = "1.0.19" 1458 1997 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1495 2034 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1496 2035 1497 2036 [[package]] 2037 + name = "version_check" 2038 + version = "0.9.5" 2039 + source = "registry+https://github.com/rust-lang/crates.io-index" 2040 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2041 + 2042 + [[package]] 1498 2043 name = "want" 1499 2044 version = "0.3.1" 1500 2045 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1600 2145 ] 1601 2146 1602 2147 [[package]] 2148 + name = "wasm-streams" 2149 + version = "0.4.2" 2150 + source = "registry+https://github.com/rust-lang/crates.io-index" 2151 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2152 + dependencies = [ 2153 + "futures-util", 2154 + "js-sys", 2155 + "wasm-bindgen", 2156 + "wasm-bindgen-futures", 2157 + "web-sys", 2158 + ] 2159 + 2160 + [[package]] 1603 2161 name = "web-sys" 1604 2162 version = "0.3.81" 1605 2163 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1610 2168 ] 1611 2169 1612 2170 [[package]] 2171 + name = "winapi" 2172 + version = "0.3.9" 2173 + source = "registry+https://github.com/rust-lang/crates.io-index" 2174 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2175 + dependencies = [ 2176 + "winapi-i686-pc-windows-gnu", 2177 + "winapi-x86_64-pc-windows-gnu", 2178 + ] 2179 + 2180 + [[package]] 2181 + name = "winapi-i686-pc-windows-gnu" 2182 + version = "0.4.0" 2183 + source = "registry+https://github.com/rust-lang/crates.io-index" 2184 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2185 + 2186 + [[package]] 2187 + name = "winapi-x86_64-pc-windows-gnu" 2188 + version = "0.4.0" 2189 + source = "registry+https://github.com/rust-lang/crates.io-index" 2190 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2191 + 2192 + [[package]] 2193 + name = "windows" 2194 + version = "0.61.3" 2195 + source = "registry+https://github.com/rust-lang/crates.io-index" 2196 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 2197 + dependencies = [ 2198 + "windows-collections", 2199 + "windows-core", 2200 + "windows-future", 2201 + "windows-link 0.1.3", 2202 + "windows-numerics", 2203 + ] 2204 + 2205 + [[package]] 2206 + name = "windows-collections" 2207 + version = "0.2.0" 2208 + source = "registry+https://github.com/rust-lang/crates.io-index" 2209 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 2210 + dependencies = [ 2211 + "windows-core", 2212 + ] 2213 + 2214 + [[package]] 2215 + name = "windows-core" 2216 + version = "0.61.2" 2217 + source = "registry+https://github.com/rust-lang/crates.io-index" 2218 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 2219 + dependencies = [ 2220 + "windows-implement", 2221 + "windows-interface", 2222 + "windows-link 0.1.3", 2223 + "windows-result", 2224 + "windows-strings", 2225 + ] 2226 + 2227 + [[package]] 2228 + name = "windows-future" 2229 + version = "0.2.1" 2230 + source = "registry+https://github.com/rust-lang/crates.io-index" 2231 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 2232 + dependencies = [ 2233 + "windows-core", 2234 + "windows-link 0.1.3", 2235 + "windows-threading", 2236 + ] 2237 + 2238 + [[package]] 2239 + name = "windows-implement" 2240 + version = "0.60.2" 2241 + source = "registry+https://github.com/rust-lang/crates.io-index" 2242 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 2243 + dependencies = [ 2244 + "proc-macro2", 2245 + "quote", 2246 + "syn", 2247 + ] 2248 + 2249 + [[package]] 2250 + name = "windows-interface" 2251 + version = "0.59.3" 2252 + source = "registry+https://github.com/rust-lang/crates.io-index" 2253 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 2254 + dependencies = [ 2255 + "proc-macro2", 2256 + "quote", 2257 + "syn", 2258 + ] 2259 + 2260 + [[package]] 1613 2261 name = "windows-link" 1614 2262 version = "0.1.3" 1615 2263 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1622 2270 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1623 2271 1624 2272 [[package]] 2273 + name = "windows-numerics" 2274 + version = "0.2.0" 2275 + source = "registry+https://github.com/rust-lang/crates.io-index" 2276 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 2277 + dependencies = [ 2278 + "windows-core", 2279 + "windows-link 0.1.3", 2280 + ] 2281 + 2282 + [[package]] 1625 2283 name = "windows-registry" 1626 2284 version = "0.5.3" 1627 2285 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1717 2375 "windows_x86_64_gnu 0.53.1", 1718 2376 "windows_x86_64_gnullvm 0.53.1", 1719 2377 "windows_x86_64_msvc 0.53.1", 2378 + ] 2379 + 2380 + [[package]] 2381 + name = "windows-threading" 2382 + version = "0.1.0" 2383 + source = "registry+https://github.com/rust-lang/crates.io-index" 2384 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 2385 + dependencies = [ 2386 + "windows-link 0.1.3", 1720 2387 ] 1721 2388 1722 2389 [[package]] ··· 1910 2577 "quote", 1911 2578 "syn", 1912 2579 ] 2580 + 2581 + [[package]] 2582 + name = "zstd" 2583 + version = "0.13.3" 2584 + source = "registry+https://github.com/rust-lang/crates.io-index" 2585 + checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2586 + dependencies = [ 2587 + "zstd-safe", 2588 + ] 2589 + 2590 + [[package]] 2591 + name = "zstd-safe" 2592 + version = "7.2.4" 2593 + source = "registry+https://github.com/rust-lang/crates.io-index" 2594 + checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 2595 + dependencies = [ 2596 + "zstd-sys", 2597 + ] 2598 + 2599 + [[package]] 2600 + name = "zstd-sys" 2601 + version = "2.0.16+zstd.1.5.7" 2602 + source = "registry+https://github.com/rust-lang/crates.io-index" 2603 + checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 2604 + dependencies = [ 2605 + "cc", 2606 + "pkg-config", 2607 + ]
+9 -2
Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [dependencies] 7 - async-compression = "0.4.32" 7 + anyhow = "1.0.100" 8 + async-compression = { version = "0.4.30", features = ["futures-io", "tokio", "zstd"] } 8 9 clap = { version = "4.5", features = ["derive"] } 9 10 dotenvy = "0.15" 10 11 env_logger = "0.11" 11 12 log = "0.4" 12 - reqwest = "0.12.23" 13 + reqwest = { version = "0.12.23", features = ["stream"] } 13 14 tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 15 + futures = "0.3.31" 16 + tokio-util = { version = "0.7.16", features = ["compat"] } 17 + tokio-stream = { version = "0.1.17", features = ["io-util"] } 18 + rust-s3 = "0.37.0" 19 + serde = { version = "1", features = ["derive"] } 20 + serde_json = "1"
+219 -26
src/main.rs
··· 1 - use std::fs; 2 1 use std::path::PathBuf; 2 + use std::{env, fs}; 3 3 4 + use anyhow; 5 + use async_compression::futures::bufread::{ZstdDecoder, ZstdEncoder}; 4 6 use clap::{Parser, Subcommand}; 5 7 use dotenvy::dotenv; 8 + use futures::io::BufReader; 6 9 use log::{error, info}; 10 + use reqwest::header::{ACCEPT, ACCEPT_ENCODING}; 11 + use s3::creds::Credentials; 12 + use s3::{Bucket, Region}; 13 + use std::future::Future; 14 + use tokio::io::AsyncWriteExt; 15 + use tokio_stream::wrappers::LinesStream; 16 + use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; 17 + use tokio_util::io::StreamReader; 18 + use serde::Deserialize; 7 19 8 20 #[derive(Parser, Debug)] 9 21 #[command(author, version, about = "PDS Whatsit Compress Test CLI", long_about = None)] ··· 15 27 #[derive(Subcommand, Debug)] 16 28 enum Commands { 17 29 /// Export data from a PDS instance 18 - Export { 30 + Backup { 19 31 /// Output directory path (defaults to ./export) 20 - #[arg(short, long)] 21 - out: Option<PathBuf>, 32 + // #[arg(short, long)] 33 + // out: Option<PathBuf>, 22 34 23 35 /// The DID to export 24 36 #[arg(long)] ··· 28 40 #[arg(long, value_name = "URL")] 29 41 pds_url: String, 30 42 }, 43 + 44 + /// Import: download from S3, decompress zstd, and save locally 45 + Restore { 46 + /// The DID to import 47 + #[arg(long)] 48 + did: String, 49 + }, 31 50 } 32 51 33 52 fn init_logging() { ··· 45 64 fs::create_dir_all(parent)?; 46 65 } 47 66 } 48 - if !path.exists() { 49 - fs::create_dir_all(path)?; 50 - } 51 67 Ok(()) 52 68 } 53 69 54 70 #[tokio::main] 55 - async fn main() { 71 + async fn main() -> anyhow::Result<()> { 56 72 init_logging(); 57 73 58 74 let cli = Cli::parse(); 75 + // Custom region requires valid region name and endpoint 76 + let region_name = env::var("S3_REGION")?; 77 + let endpoint = env::var("S3_ENDPOINT")?; 78 + let region = Region::Custom { 79 + region: region_name, 80 + endpoint, 81 + }; 82 + 83 + let bucket = Bucket::new( 84 + env::var("S3_BUCKET_NAME")?.as_str(), 85 + region, 86 + // Credentials are collected from environment, config, profile or instance metadata 87 + Credentials::new( 88 + Some(env::var("S3_ACCESS_KEY")?.as_str()), 89 + Some(env::var("S3_SECRET_KEY")?.as_str()), 90 + None, 91 + None, 92 + None, 93 + )?, 94 + )?; 59 95 60 96 match cli.command { 61 - Commands::Export { out, did, pds_url } => { 62 - let out_dir = out.unwrap_or_else(|| PathBuf::from("export")); 97 + Commands::Backup { did, pds_url } => { 98 + info!("Export requested: did={}, pds_url={}", did, pds_url,); 99 + 100 + match do_backup(pds_url, did, bucket).await { 101 + Ok(_) => { 102 + info!("Export completed"); 103 + Ok(()) 104 + } 105 + Err(err) => { 106 + error!("Export failed: {}", err); 107 + Err(err) 108 + } 109 + } 110 + } 111 + Commands::Restore { did } => { 112 + info!("Import requested: did={}", did); 113 + match do_restore(did, bucket).await { 114 + Ok(path) => { 115 + info!("Import completed, wrote {}", path.display()); 116 + Ok(()) 117 + } 118 + Err(err) => { 119 + error!("Import failed: {}", err); 120 + Err(err) 121 + } 122 + } 123 + } 124 + } 125 + } 126 + 127 + async fn do_backup(pds_url: String, did: String, bucket: Box<Bucket>) -> anyhow::Result<()> { 128 + use futures::TryStreamExt; 129 + let atproto_client = reqwest::Client::new(); 130 + let back_up_path = format!("users/{did}"); 131 + 132 + // 1) Backup the full repo CAR (compressed) 133 + let response_reader = atproto_client 134 + .get(format!("{pds_url}/xrpc/com.atproto.sync.getRepo?did={did}")) 135 + .header(ACCEPT, "application/vnd.ipld.car") 136 + .send() 137 + .await? 138 + .error_for_status()? 139 + .bytes_stream() 140 + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) 141 + .into_async_read(); 142 + 143 + let buf_reader = BufReader::new(response_reader); 144 + let zstd_encoder = ZstdEncoder::new(buf_reader); 145 + let mut zstd_tokio_reader = zstd_encoder.compat(); 146 + 147 + bucket 148 + .put_object_stream_builder(format!("/{back_up_path}/{did}.car.zst").as_str()) 149 + .with_content_type("application/vnd.ipld.car") 150 + .with_content_encoding("zstd")? 151 + .execute_stream(&mut zstd_tokio_reader) 152 + .await?; 153 + 154 + // 2) Paginate listBlobs and upload each as zstd-compressed 155 + #[derive(Deserialize)] 156 + struct ListBlobsResponse { 157 + #[allow(dead_code)] 158 + cursor: Option<String>, 159 + cids: Vec<String>, 160 + } 161 + 162 + let mut cursor: Option<String> = None; 163 + let limit = 1000u32; 164 + 165 + loop { 166 + let mut url = format!( 167 + "{}/xrpc/com.atproto.sync.listBlobs?did={}&limit={}", 168 + pds_url, did, limit 169 + ); 170 + if let Some(ref c) = cursor { 171 + if !c.is_empty() { 172 + url.push_str("&cursor="); 173 + url.push_str(c); 174 + } 175 + } 176 + 177 + info!("Listing blobs: {}", url); 178 + let resp = atproto_client 179 + .get(url) 180 + .header(ACCEPT, "application/json") 181 + .send() 182 + .await? 183 + .error_for_status()?; 184 + let bytes = resp.bytes().await?; 185 + let page: ListBlobsResponse = serde_json::from_slice(&bytes)?; 63 186 64 - if let Err(e) = ensure_dir(&out_dir) { 65 - error!("Failed to create/export directory {}: {}", out_dir.display(), e); 66 - std::process::exit(1); 187 + if page.cids.is_empty() { 188 + if cursor.is_none() || cursor.as_deref() == Some("") { 189 + break; 67 190 } 191 + } 68 192 69 - info!( 70 - "Export requested: did={}, pds_url={}, out_dir={}", 71 - did, 72 - pds_url, 73 - out_dir.display() 193 + for cid in page.cids { 194 + let blob_url = format!( 195 + "{}/xrpc/com.atproto.sync.getBlob?did={}&cid={}", 196 + pds_url, did, cid 74 197 ); 198 + info!("Downloading blob {}", cid); 199 + let blob_reader = atproto_client 200 + .get(blob_url) 201 + .header(ACCEPT, "*/*") 202 + .send() 203 + .await? 204 + .error_for_status()? 205 + .bytes_stream() 206 + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) 207 + .into_async_read(); 75 208 76 - // Placeholder for actual export logic. 77 - // Implement your export functionality here. 78 - println!( 79 - "Export would run here with did={} pds_url={} out_dir={}", 80 - did, 81 - pds_url, 82 - out_dir.display() 83 - ); 209 + let blob_buf = BufReader::new(blob_reader); 210 + let blob_zstd = ZstdEncoder::new(blob_buf); 211 + let mut blob_tokio_reader = blob_zstd.compat(); 212 + 213 + let object_key = format!("/{}/blobs/{}.zst", back_up_path, cid); 214 + bucket 215 + .put_object_stream_builder(&object_key) 216 + .with_content_type("application/octet-stream") 217 + .with_content_encoding("zstd")? 218 + .execute_stream(&mut blob_tokio_reader) 219 + .await?; 220 + } 221 + 222 + // Update or finish based on cursor 223 + match page.cursor { 224 + Some(c) if !c.is_empty() => { 225 + cursor = Some(c); 226 + } 227 + _ => break, 84 228 } 85 229 } 230 + 231 + Ok(()) 232 + } 233 + 234 + async fn do_restore(did: String, bucket: Box<Bucket>) -> anyhow::Result<PathBuf> { 235 + use futures::StreamExt; 236 + 237 + let back_up_path = format!("users/{did}"); 238 + 239 + // Stream download from S3 240 + let mut s3_stream = bucket 241 + .get_object_stream(format!("/{back_up_path}/{did}.car.zst")) 242 + .await?; 243 + 244 + // Convert the stream of Bytes into a tokio AsyncRead 245 + let byte_stream = s3_stream 246 + .bytes() 247 + .map(|res| res.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))); 248 + 249 + let tokio_reader = StreamReader::new(byte_stream); 250 + 251 + // Convert tokio AsyncRead -> futures AsyncRead, then buffer for decoder 252 + let futures_reader = tokio_reader.compat(); 253 + let futures_buf = futures::io::BufReader::new(futures_reader); 254 + 255 + // Zstd decode 256 + let decoder = ZstdDecoder::new(futures_buf); 257 + 258 + // Convert back to tokio AsyncRead for writing 259 + let mut decoded_tokio_reader = decoder.compat(); 260 + 261 + // Prepare local output path, labeled as decompressed 262 + let out_path: PathBuf = [ 263 + "export", 264 + "users", 265 + did.as_str(), 266 + &format!("{}-decompressed.car", did), 267 + ] 268 + .iter() 269 + .collect(); 270 + ensure_dir(&out_path)?; 271 + 272 + let mut out_file = tokio::fs::File::create(&out_path).await?; 273 + 274 + // Stream copy decoded content to file 275 + tokio::io::copy(&mut decoded_tokio_reader, &mut out_file).await?; 276 + out_file.flush().await?; 277 + 278 + Ok(out_path) 86 279 }