├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs └── demo-screenshot.png └── src ├── cli.rs ├── config.rs ├── files.rs ├── main.rs ├── network.rs ├── qr.rs ├── receive.rs ├── resources └── form.html ├── send.rs └── server.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Workflow - Swift File 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | name: release ${{ matrix.target }} 10 | runs-on: ubuntu-latest 11 | if: github.event_name == 'release' 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - target: x86_64-pc-windows-gnu 17 | archive: zip 18 | - target: x86_64-unknown-linux-musl 19 | archive: tar.gz 20 | - target: x86_64-apple-darwin 21 | archive: zip 22 | steps: 23 | - uses: actions/checkout@master 24 | - name: Compile and release 25 | id: compile 26 | uses: rust-build/rust-build.action@v1.4.4 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | RUSTTARGET: ${{ matrix.target }} 31 | ARCHIVE_TYPES: ${{ matrix.archive }} 32 | - name: Upload artifact 33 | uses: actions/upload-artifact@v3 34 | with: 35 | name: Binary 36 | path: | 37 | ${{ steps.compile.outputs.BUILT_ARCHIVE }} 38 | ${{ steps.compile.outputs.BUILT_CHECKSUM }} 39 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust - Swift File 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build_and_test: 14 | name: Swift File CI 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | toolchain: 19 | - stable 20 | - beta 21 | - nightly 22 | steps: 23 | - uses: actions/checkout@v3 24 | - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Run tests 28 | run: cargo test --verbose 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle" 51 | version = "1.0.4" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 54 | 55 | [[package]] 56 | name = "anstyle-parse" 57 | version = "0.2.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" 60 | dependencies = [ 61 | "utf8parse", 62 | ] 63 | 64 | [[package]] 65 | name = "anstyle-query" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 69 | dependencies = [ 70 | "windows-sys", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle-wincon" 75 | version = "3.0.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 78 | dependencies = [ 79 | "anstyle", 80 | "windows-sys", 81 | ] 82 | 83 | [[package]] 84 | name = "anyhow" 85 | version = "1.0.75" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 88 | 89 | [[package]] 90 | name = "async-compression" 91 | version = "0.3.15" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" 94 | dependencies = [ 95 | "bzip2", 96 | "flate2", 97 | "futures-core", 98 | "futures-io", 99 | "memchr", 100 | "pin-project-lite", 101 | "xz2", 102 | "zstd", 103 | "zstd-safe", 104 | ] 105 | 106 | [[package]] 107 | name = "async-trait" 108 | version = "0.1.74" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" 111 | dependencies = [ 112 | "proc-macro2", 113 | "quote", 114 | "syn", 115 | ] 116 | 117 | [[package]] 118 | name = "async_zip" 119 | version = "0.0.15" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "795310de3218cde15219fc98c1cf7d8fe9db4865aab27fcf1d535d6cb61c6b54" 122 | dependencies = [ 123 | "async-compression", 124 | "chrono", 125 | "crc32fast", 126 | "futures-util", 127 | "log", 128 | "pin-project", 129 | "thiserror", 130 | "tokio", 131 | "tokio-util", 132 | ] 133 | 134 | [[package]] 135 | name = "autocfg" 136 | version = "1.1.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 139 | 140 | [[package]] 141 | name = "axum" 142 | version = "0.6.20" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" 145 | dependencies = [ 146 | "async-trait", 147 | "axum-core", 148 | "bitflags", 149 | "bytes", 150 | "futures-util", 151 | "http", 152 | "http-body", 153 | "hyper", 154 | "itoa", 155 | "matchit", 156 | "memchr", 157 | "mime", 158 | "multer", 159 | "percent-encoding", 160 | "pin-project-lite", 161 | "rustversion", 162 | "serde", 163 | "serde_json", 164 | "serde_path_to_error", 165 | "serde_urlencoded", 166 | "sync_wrapper", 167 | "tokio", 168 | "tower", 169 | "tower-layer", 170 | "tower-service", 171 | ] 172 | 173 | [[package]] 174 | name = "axum-core" 175 | version = "0.3.4" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" 178 | dependencies = [ 179 | "async-trait", 180 | "bytes", 181 | "futures-util", 182 | "http", 183 | "http-body", 184 | "mime", 185 | "rustversion", 186 | "tower-layer", 187 | "tower-service", 188 | ] 189 | 190 | [[package]] 191 | name = "backtrace" 192 | version = "0.3.69" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 195 | dependencies = [ 196 | "addr2line", 197 | "cc", 198 | "cfg-if", 199 | "libc", 200 | "miniz_oxide", 201 | "object", 202 | "rustc-demangle", 203 | ] 204 | 205 | [[package]] 206 | name = "bitflags" 207 | version = "1.3.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 210 | 211 | [[package]] 212 | name = "bstr" 213 | version = "1.8.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" 216 | dependencies = [ 217 | "memchr", 218 | "regex-automata", 219 | "serde", 220 | ] 221 | 222 | [[package]] 223 | name = "bumpalo" 224 | version = "3.14.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 227 | 228 | [[package]] 229 | name = "bytemuck" 230 | version = "1.14.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" 233 | 234 | [[package]] 235 | name = "byteorder" 236 | version = "1.5.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 239 | 240 | [[package]] 241 | name = "bytes" 242 | version = "1.5.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 245 | 246 | [[package]] 247 | name = "bzip2" 248 | version = "0.4.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" 251 | dependencies = [ 252 | "bzip2-sys", 253 | "libc", 254 | ] 255 | 256 | [[package]] 257 | name = "bzip2-sys" 258 | version = "0.1.11+1.0.8" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 261 | dependencies = [ 262 | "cc", 263 | "libc", 264 | "pkg-config", 265 | ] 266 | 267 | [[package]] 268 | name = "cc" 269 | version = "1.0.83" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 272 | dependencies = [ 273 | "jobserver", 274 | "libc", 275 | ] 276 | 277 | [[package]] 278 | name = "cfg-if" 279 | version = "1.0.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 282 | 283 | [[package]] 284 | name = "checked_int_cast" 285 | version = "1.0.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" 288 | 289 | [[package]] 290 | name = "chrono" 291 | version = "0.4.31" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 294 | dependencies = [ 295 | "android-tzdata", 296 | "iana-time-zone", 297 | "js-sys", 298 | "num-traits", 299 | "wasm-bindgen", 300 | "windows-targets", 301 | ] 302 | 303 | [[package]] 304 | name = "clap" 305 | version = "4.4.8" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" 308 | dependencies = [ 309 | "clap_builder", 310 | "clap_derive", 311 | ] 312 | 313 | [[package]] 314 | name = "clap_builder" 315 | version = "4.4.8" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" 318 | dependencies = [ 319 | "anstream", 320 | "anstyle", 321 | "clap_lex", 322 | "strsim", 323 | ] 324 | 325 | [[package]] 326 | name = "clap_derive" 327 | version = "4.4.7" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 330 | dependencies = [ 331 | "heck", 332 | "proc-macro2", 333 | "quote", 334 | "syn", 335 | ] 336 | 337 | [[package]] 338 | name = "clap_lex" 339 | version = "0.6.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 342 | 343 | [[package]] 344 | name = "color_quant" 345 | version = "1.1.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 348 | 349 | [[package]] 350 | name = "colorchoice" 351 | version = "1.0.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 354 | 355 | [[package]] 356 | name = "core-foundation" 357 | version = "0.9.3" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 360 | dependencies = [ 361 | "core-foundation-sys", 362 | "libc", 363 | ] 364 | 365 | [[package]] 366 | name = "core-foundation-sys" 367 | version = "0.8.4" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 370 | 371 | [[package]] 372 | name = "crc32fast" 373 | version = "1.3.2" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 376 | dependencies = [ 377 | "cfg-if", 378 | ] 379 | 380 | [[package]] 381 | name = "default-net" 382 | version = "0.21.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "85dc7576d8346d3c86ad64dc64d26d0f6c970ba4795b850f15ee94467d8e53eb" 385 | dependencies = [ 386 | "dlopen2", 387 | "libc", 388 | "memalloc", 389 | "netlink-packet-core", 390 | "netlink-packet-route", 391 | "netlink-sys", 392 | "once_cell", 393 | "system-configuration", 394 | "windows", 395 | ] 396 | 397 | [[package]] 398 | name = "dlopen2" 399 | version = "0.5.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" 402 | dependencies = [ 403 | "libc", 404 | "once_cell", 405 | "winapi", 406 | ] 407 | 408 | [[package]] 409 | name = "encoding_rs" 410 | version = "0.8.33" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 413 | dependencies = [ 414 | "cfg-if", 415 | ] 416 | 417 | [[package]] 418 | name = "flate2" 419 | version = "1.0.28" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 422 | dependencies = [ 423 | "crc32fast", 424 | "miniz_oxide", 425 | ] 426 | 427 | [[package]] 428 | name = "fnv" 429 | version = "1.0.7" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 432 | 433 | [[package]] 434 | name = "form_urlencoded" 435 | version = "1.2.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 438 | dependencies = [ 439 | "percent-encoding", 440 | ] 441 | 442 | [[package]] 443 | name = "futures-channel" 444 | version = "0.3.29" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 447 | dependencies = [ 448 | "futures-core", 449 | ] 450 | 451 | [[package]] 452 | name = "futures-core" 453 | version = "0.3.29" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 456 | 457 | [[package]] 458 | name = "futures-io" 459 | version = "0.3.29" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" 462 | 463 | [[package]] 464 | name = "futures-macro" 465 | version = "0.3.29" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" 468 | dependencies = [ 469 | "proc-macro2", 470 | "quote", 471 | "syn", 472 | ] 473 | 474 | [[package]] 475 | name = "futures-sink" 476 | version = "0.3.29" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 479 | 480 | [[package]] 481 | name = "futures-task" 482 | version = "0.3.29" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 485 | 486 | [[package]] 487 | name = "futures-util" 488 | version = "0.3.29" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 491 | dependencies = [ 492 | "futures-core", 493 | "futures-io", 494 | "futures-macro", 495 | "futures-task", 496 | "memchr", 497 | "pin-project-lite", 498 | "pin-utils", 499 | "slab", 500 | ] 501 | 502 | [[package]] 503 | name = "getrandom" 504 | version = "0.2.11" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 507 | dependencies = [ 508 | "cfg-if", 509 | "libc", 510 | "wasi", 511 | ] 512 | 513 | [[package]] 514 | name = "gimli" 515 | version = "0.28.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 518 | 519 | [[package]] 520 | name = "heck" 521 | version = "0.4.1" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 524 | 525 | [[package]] 526 | name = "hermit-abi" 527 | version = "0.3.3" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 530 | 531 | [[package]] 532 | name = "http" 533 | version = "0.2.11" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 536 | dependencies = [ 537 | "bytes", 538 | "fnv", 539 | "itoa", 540 | ] 541 | 542 | [[package]] 543 | name = "http-body" 544 | version = "0.4.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 547 | dependencies = [ 548 | "bytes", 549 | "http", 550 | "pin-project-lite", 551 | ] 552 | 553 | [[package]] 554 | name = "httparse" 555 | version = "1.8.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 558 | 559 | [[package]] 560 | name = "httpdate" 561 | version = "1.0.3" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 564 | 565 | [[package]] 566 | name = "humansize" 567 | version = "2.1.3" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" 570 | dependencies = [ 571 | "libm", 572 | ] 573 | 574 | [[package]] 575 | name = "hyper" 576 | version = "0.14.27" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 579 | dependencies = [ 580 | "bytes", 581 | "futures-channel", 582 | "futures-core", 583 | "futures-util", 584 | "http", 585 | "http-body", 586 | "httparse", 587 | "httpdate", 588 | "itoa", 589 | "pin-project-lite", 590 | "socket2 0.4.10", 591 | "tokio", 592 | "tower-service", 593 | "tracing", 594 | "want", 595 | ] 596 | 597 | [[package]] 598 | name = "iana-time-zone" 599 | version = "0.1.58" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" 602 | dependencies = [ 603 | "android_system_properties", 604 | "core-foundation-sys", 605 | "iana-time-zone-haiku", 606 | "js-sys", 607 | "wasm-bindgen", 608 | "windows-core", 609 | ] 610 | 611 | [[package]] 612 | name = "iana-time-zone-haiku" 613 | version = "0.1.2" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 616 | dependencies = [ 617 | "cc", 618 | ] 619 | 620 | [[package]] 621 | name = "image" 622 | version = "0.23.14" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 625 | dependencies = [ 626 | "bytemuck", 627 | "byteorder", 628 | "color_quant", 629 | "num-iter", 630 | "num-rational", 631 | "num-traits", 632 | ] 633 | 634 | [[package]] 635 | name = "itoa" 636 | version = "1.0.9" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 639 | 640 | [[package]] 641 | name = "jobserver" 642 | version = "0.1.27" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" 645 | dependencies = [ 646 | "libc", 647 | ] 648 | 649 | [[package]] 650 | name = "js-sys" 651 | version = "0.3.65" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" 654 | dependencies = [ 655 | "wasm-bindgen", 656 | ] 657 | 658 | [[package]] 659 | name = "libc" 660 | version = "0.2.150" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 663 | 664 | [[package]] 665 | name = "libm" 666 | version = "0.2.8" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 669 | 670 | [[package]] 671 | name = "lock_api" 672 | version = "0.4.11" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 675 | dependencies = [ 676 | "autocfg", 677 | "scopeguard", 678 | ] 679 | 680 | [[package]] 681 | name = "log" 682 | version = "0.4.20" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 685 | 686 | [[package]] 687 | name = "lzma-sys" 688 | version = "0.1.20" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" 691 | dependencies = [ 692 | "cc", 693 | "libc", 694 | "pkg-config", 695 | ] 696 | 697 | [[package]] 698 | name = "matchit" 699 | version = "0.7.3" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 702 | 703 | [[package]] 704 | name = "memalloc" 705 | version = "0.1.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" 708 | 709 | [[package]] 710 | name = "memchr" 711 | version = "2.6.4" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 714 | 715 | [[package]] 716 | name = "mime" 717 | version = "0.3.17" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 720 | 721 | [[package]] 722 | name = "miniz_oxide" 723 | version = "0.7.1" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 726 | dependencies = [ 727 | "adler", 728 | ] 729 | 730 | [[package]] 731 | name = "mio" 732 | version = "0.8.9" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 735 | dependencies = [ 736 | "libc", 737 | "wasi", 738 | "windows-sys", 739 | ] 740 | 741 | [[package]] 742 | name = "multer" 743 | version = "2.1.0" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 746 | dependencies = [ 747 | "bytes", 748 | "encoding_rs", 749 | "futures-util", 750 | "http", 751 | "httparse", 752 | "log", 753 | "memchr", 754 | "mime", 755 | "spin", 756 | "version_check", 757 | ] 758 | 759 | [[package]] 760 | name = "netlink-packet-core" 761 | version = "0.7.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" 764 | dependencies = [ 765 | "anyhow", 766 | "byteorder", 767 | "netlink-packet-utils", 768 | ] 769 | 770 | [[package]] 771 | name = "netlink-packet-route" 772 | version = "0.17.1" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" 775 | dependencies = [ 776 | "anyhow", 777 | "bitflags", 778 | "byteorder", 779 | "libc", 780 | "netlink-packet-core", 781 | "netlink-packet-utils", 782 | ] 783 | 784 | [[package]] 785 | name = "netlink-packet-utils" 786 | version = "0.5.2" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" 789 | dependencies = [ 790 | "anyhow", 791 | "byteorder", 792 | "paste", 793 | "thiserror", 794 | ] 795 | 796 | [[package]] 797 | name = "netlink-sys" 798 | version = "0.8.5" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" 801 | dependencies = [ 802 | "bytes", 803 | "libc", 804 | "log", 805 | ] 806 | 807 | [[package]] 808 | name = "new_mime_guess" 809 | version = "4.0.1" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "c2d684d1b59e0dc07b37e2203ef576987473288f530082512aff850585c61b1f" 812 | dependencies = [ 813 | "mime", 814 | "unicase", 815 | ] 816 | 817 | [[package]] 818 | name = "normpath" 819 | version = "1.1.1" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" 822 | dependencies = [ 823 | "windows-sys", 824 | ] 825 | 826 | [[package]] 827 | name = "num-integer" 828 | version = "0.1.45" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 831 | dependencies = [ 832 | "autocfg", 833 | "num-traits", 834 | ] 835 | 836 | [[package]] 837 | name = "num-iter" 838 | version = "0.1.43" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 841 | dependencies = [ 842 | "autocfg", 843 | "num-integer", 844 | "num-traits", 845 | ] 846 | 847 | [[package]] 848 | name = "num-rational" 849 | version = "0.3.2" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 852 | dependencies = [ 853 | "autocfg", 854 | "num-integer", 855 | "num-traits", 856 | ] 857 | 858 | [[package]] 859 | name = "num-traits" 860 | version = "0.2.17" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 863 | dependencies = [ 864 | "autocfg", 865 | ] 866 | 867 | [[package]] 868 | name = "num_cpus" 869 | version = "1.16.0" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 872 | dependencies = [ 873 | "hermit-abi", 874 | "libc", 875 | ] 876 | 877 | [[package]] 878 | name = "object" 879 | version = "0.32.1" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 882 | dependencies = [ 883 | "memchr", 884 | ] 885 | 886 | [[package]] 887 | name = "once_cell" 888 | version = "1.18.0" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 891 | 892 | [[package]] 893 | name = "opener" 894 | version = "0.6.1" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" 897 | dependencies = [ 898 | "bstr", 899 | "normpath", 900 | "winapi", 901 | ] 902 | 903 | [[package]] 904 | name = "parking_lot" 905 | version = "0.12.1" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 908 | dependencies = [ 909 | "lock_api", 910 | "parking_lot_core", 911 | ] 912 | 913 | [[package]] 914 | name = "parking_lot_core" 915 | version = "0.9.9" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 918 | dependencies = [ 919 | "cfg-if", 920 | "libc", 921 | "redox_syscall", 922 | "smallvec", 923 | "windows-targets", 924 | ] 925 | 926 | [[package]] 927 | name = "paste" 928 | version = "1.0.14" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 931 | 932 | [[package]] 933 | name = "percent-encoding" 934 | version = "2.3.0" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 937 | 938 | [[package]] 939 | name = "pin-project" 940 | version = "1.1.3" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 943 | dependencies = [ 944 | "pin-project-internal", 945 | ] 946 | 947 | [[package]] 948 | name = "pin-project-internal" 949 | version = "1.1.3" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 952 | dependencies = [ 953 | "proc-macro2", 954 | "quote", 955 | "syn", 956 | ] 957 | 958 | [[package]] 959 | name = "pin-project-lite" 960 | version = "0.2.13" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 963 | 964 | [[package]] 965 | name = "pin-utils" 966 | version = "0.1.0" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 969 | 970 | [[package]] 971 | name = "pkg-config" 972 | version = "0.3.27" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 975 | 976 | [[package]] 977 | name = "proc-macro2" 978 | version = "1.0.69" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 981 | dependencies = [ 982 | "unicode-ident", 983 | ] 984 | 985 | [[package]] 986 | name = "qrcode" 987 | version = "0.12.0" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" 990 | dependencies = [ 991 | "checked_int_cast", 992 | "image", 993 | ] 994 | 995 | [[package]] 996 | name = "quote" 997 | version = "1.0.33" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 1000 | dependencies = [ 1001 | "proc-macro2", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "redox_syscall" 1006 | version = "0.4.1" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1009 | dependencies = [ 1010 | "bitflags", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "regex-automata" 1015 | version = "0.4.3" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 1018 | 1019 | [[package]] 1020 | name = "rustc-demangle" 1021 | version = "0.1.23" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1024 | 1025 | [[package]] 1026 | name = "rustversion" 1027 | version = "1.0.14" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1030 | 1031 | [[package]] 1032 | name = "ryu" 1033 | version = "1.0.15" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 1036 | 1037 | [[package]] 1038 | name = "scopeguard" 1039 | version = "1.2.0" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1042 | 1043 | [[package]] 1044 | name = "serde" 1045 | version = "1.0.193" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 1048 | dependencies = [ 1049 | "serde_derive", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "serde_derive" 1054 | version = "1.0.193" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 1057 | dependencies = [ 1058 | "proc-macro2", 1059 | "quote", 1060 | "syn", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "serde_json" 1065 | version = "1.0.108" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 1068 | dependencies = [ 1069 | "itoa", 1070 | "ryu", 1071 | "serde", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "serde_path_to_error" 1076 | version = "0.1.14" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" 1079 | dependencies = [ 1080 | "itoa", 1081 | "serde", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "serde_urlencoded" 1086 | version = "0.7.1" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1089 | dependencies = [ 1090 | "form_urlencoded", 1091 | "itoa", 1092 | "ryu", 1093 | "serde", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "signal-hook-registry" 1098 | version = "1.4.1" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1101 | dependencies = [ 1102 | "libc", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "slab" 1107 | version = "0.4.9" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1110 | dependencies = [ 1111 | "autocfg", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "smallvec" 1116 | version = "1.11.2" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1119 | 1120 | [[package]] 1121 | name = "socket2" 1122 | version = "0.4.10" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 1125 | dependencies = [ 1126 | "libc", 1127 | "winapi", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "socket2" 1132 | version = "0.5.5" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1135 | dependencies = [ 1136 | "libc", 1137 | "windows-sys", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "spin" 1142 | version = "0.9.8" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1145 | 1146 | [[package]] 1147 | name = "strsim" 1148 | version = "0.10.0" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1151 | 1152 | [[package]] 1153 | name = "swift_file" 1154 | version = "0.1.9" 1155 | dependencies = [ 1156 | "anyhow", 1157 | "async_zip", 1158 | "axum", 1159 | "chrono", 1160 | "clap", 1161 | "default-net", 1162 | "humansize", 1163 | "mime", 1164 | "new_mime_guess", 1165 | "opener", 1166 | "qrcode", 1167 | "tokio", 1168 | "tokio-util", 1169 | "uuid", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "syn" 1174 | version = "2.0.39" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 1177 | dependencies = [ 1178 | "proc-macro2", 1179 | "quote", 1180 | "unicode-ident", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "sync_wrapper" 1185 | version = "0.1.2" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1188 | 1189 | [[package]] 1190 | name = "system-configuration" 1191 | version = "0.5.1" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1194 | dependencies = [ 1195 | "bitflags", 1196 | "core-foundation", 1197 | "system-configuration-sys", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "system-configuration-sys" 1202 | version = "0.5.0" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1205 | dependencies = [ 1206 | "core-foundation-sys", 1207 | "libc", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "thiserror" 1212 | version = "1.0.50" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 1215 | dependencies = [ 1216 | "thiserror-impl", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "thiserror-impl" 1221 | version = "1.0.50" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 1224 | dependencies = [ 1225 | "proc-macro2", 1226 | "quote", 1227 | "syn", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "tokio" 1232 | version = "1.34.0" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" 1235 | dependencies = [ 1236 | "backtrace", 1237 | "bytes", 1238 | "libc", 1239 | "mio", 1240 | "num_cpus", 1241 | "parking_lot", 1242 | "pin-project-lite", 1243 | "signal-hook-registry", 1244 | "socket2 0.5.5", 1245 | "tokio-macros", 1246 | "windows-sys", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "tokio-macros" 1251 | version = "2.2.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1254 | dependencies = [ 1255 | "proc-macro2", 1256 | "quote", 1257 | "syn", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "tokio-util" 1262 | version = "0.7.10" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1265 | dependencies = [ 1266 | "bytes", 1267 | "futures-core", 1268 | "futures-io", 1269 | "futures-sink", 1270 | "pin-project-lite", 1271 | "tokio", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "tower" 1276 | version = "0.4.13" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1279 | dependencies = [ 1280 | "futures-core", 1281 | "futures-util", 1282 | "pin-project", 1283 | "pin-project-lite", 1284 | "tokio", 1285 | "tower-layer", 1286 | "tower-service", 1287 | "tracing", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "tower-layer" 1292 | version = "0.3.2" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1295 | 1296 | [[package]] 1297 | name = "tower-service" 1298 | version = "0.3.2" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1301 | 1302 | [[package]] 1303 | name = "tracing" 1304 | version = "0.1.40" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1307 | dependencies = [ 1308 | "log", 1309 | "pin-project-lite", 1310 | "tracing-core", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "tracing-core" 1315 | version = "0.1.32" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1318 | dependencies = [ 1319 | "once_cell", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "try-lock" 1324 | version = "0.2.4" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1327 | 1328 | [[package]] 1329 | name = "unicase" 1330 | version = "2.7.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1333 | dependencies = [ 1334 | "version_check", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "unicode-ident" 1339 | version = "1.0.12" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1342 | 1343 | [[package]] 1344 | name = "utf8parse" 1345 | version = "0.2.1" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1348 | 1349 | [[package]] 1350 | name = "uuid" 1351 | version = "1.6.1" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" 1354 | dependencies = [ 1355 | "getrandom", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "version_check" 1360 | version = "0.9.4" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1363 | 1364 | [[package]] 1365 | name = "want" 1366 | version = "0.3.1" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1369 | dependencies = [ 1370 | "try-lock", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "wasi" 1375 | version = "0.11.0+wasi-snapshot-preview1" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1378 | 1379 | [[package]] 1380 | name = "wasm-bindgen" 1381 | version = "0.2.88" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" 1384 | dependencies = [ 1385 | "cfg-if", 1386 | "wasm-bindgen-macro", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "wasm-bindgen-backend" 1391 | version = "0.2.88" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" 1394 | dependencies = [ 1395 | "bumpalo", 1396 | "log", 1397 | "once_cell", 1398 | "proc-macro2", 1399 | "quote", 1400 | "syn", 1401 | "wasm-bindgen-shared", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "wasm-bindgen-macro" 1406 | version = "0.2.88" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" 1409 | dependencies = [ 1410 | "quote", 1411 | "wasm-bindgen-macro-support", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "wasm-bindgen-macro-support" 1416 | version = "0.2.88" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" 1419 | dependencies = [ 1420 | "proc-macro2", 1421 | "quote", 1422 | "syn", 1423 | "wasm-bindgen-backend", 1424 | "wasm-bindgen-shared", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "wasm-bindgen-shared" 1429 | version = "0.2.88" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" 1432 | 1433 | [[package]] 1434 | name = "winapi" 1435 | version = "0.3.9" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1438 | dependencies = [ 1439 | "winapi-i686-pc-windows-gnu", 1440 | "winapi-x86_64-pc-windows-gnu", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "winapi-i686-pc-windows-gnu" 1445 | version = "0.4.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1448 | 1449 | [[package]] 1450 | name = "winapi-x86_64-pc-windows-gnu" 1451 | version = "0.4.0" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1454 | 1455 | [[package]] 1456 | name = "windows" 1457 | version = "0.48.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 1460 | dependencies = [ 1461 | "windows-targets", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "windows-core" 1466 | version = "0.51.1" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 1469 | dependencies = [ 1470 | "windows-targets", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "windows-sys" 1475 | version = "0.48.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1478 | dependencies = [ 1479 | "windows-targets", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "windows-targets" 1484 | version = "0.48.5" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1487 | dependencies = [ 1488 | "windows_aarch64_gnullvm", 1489 | "windows_aarch64_msvc", 1490 | "windows_i686_gnu", 1491 | "windows_i686_msvc", 1492 | "windows_x86_64_gnu", 1493 | "windows_x86_64_gnullvm", 1494 | "windows_x86_64_msvc", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "windows_aarch64_gnullvm" 1499 | version = "0.48.5" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1502 | 1503 | [[package]] 1504 | name = "windows_aarch64_msvc" 1505 | version = "0.48.5" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1508 | 1509 | [[package]] 1510 | name = "windows_i686_gnu" 1511 | version = "0.48.5" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1514 | 1515 | [[package]] 1516 | name = "windows_i686_msvc" 1517 | version = "0.48.5" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1520 | 1521 | [[package]] 1522 | name = "windows_x86_64_gnu" 1523 | version = "0.48.5" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1526 | 1527 | [[package]] 1528 | name = "windows_x86_64_gnullvm" 1529 | version = "0.48.5" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1532 | 1533 | [[package]] 1534 | name = "windows_x86_64_msvc" 1535 | version = "0.48.5" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1538 | 1539 | [[package]] 1540 | name = "xz2" 1541 | version = "0.1.7" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" 1544 | dependencies = [ 1545 | "lzma-sys", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "zstd" 1550 | version = "0.11.2+zstd.1.5.2" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" 1553 | dependencies = [ 1554 | "zstd-safe", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "zstd-safe" 1559 | version = "5.0.2+zstd.1.5.2" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" 1562 | dependencies = [ 1563 | "libc", 1564 | "zstd-sys", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "zstd-sys" 1569 | version = "2.0.9+zstd.1.5.5" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" 1572 | dependencies = [ 1573 | "cc", 1574 | "pkg-config", 1575 | ] 1576 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Mateo Radman "] 3 | name = "swift_file" 4 | version = "0.1.9" 5 | edition = "2021" 6 | description = "Send or receive files between devices using Wi-Fi network" 7 | license = "MIT" 8 | documentation = "https://github.com/mateoradman/swift_file" 9 | homepage = "https://github.com/mateoradman/swift_file" 10 | repository = "https://github.com/mateoradman/swift_file" 11 | keywords = ["cli", "file-transfer", "axum", "api"] 12 | categories = ["command-line-utilities", "web-programming"] 13 | 14 | [[bin]] 15 | bench = false 16 | name = "sf" 17 | path = "src/main.rs" 18 | 19 | [dependencies] 20 | anyhow = "1.0.71" 21 | async_zip = { version = "0.0.15", features = ["full"] } 22 | axum = { version = "0.6.15", features = ["multipart"] } 23 | chrono = "0.4.24" 24 | clap = { version = "4.4.8", features = ["derive"] } 25 | default-net = "0.21.0" 26 | humansize = "2.1.3" 27 | mime = "0.3.17" 28 | new_mime_guess = "4.0.1" 29 | opener = "0.6.1" 30 | qrcode = "0.12.0" 31 | tokio = { version = "1.27.0", features = ["full"] } 32 | tokio-util = { version = "0.7.7", features = ["io"] } 33 | uuid = { version = "1.3.1", features = ["v4"] } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mateo Radman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift_file 2 | 3 | Rust implementation of transferring files between devices over Wi-Fi network using a QR code. 4 | Tool is inspired by [claudiodangelis/qrcp](https://github.com/claudiodangelis/qrcp). 5 | 6 | ## How does it work? 7 | 8 | ![](docs/demo-screenshot.png?raw=true) 9 | 10 | The sf server is bound to the IP address of a default network interface of the machine the server is running on. Alternatively, the IP address (`--port`), particular network interface (`--interface`), and port (`--port`) can be selected by the user. 11 | 12 | The QR code that is printed encodes a http URL which is typically of the following format: 13 | 14 | `http://{ip}:{port}/{download|receive}/[optional suffix]` 15 | 16 | ## Known limitations 17 | 18 | - Some browsers on iOS are unable to download the file. It always works with Safari but fails with Brave. The failed download might also occur on other Chromium-based iOS browsers. 19 | 20 | ## Safety 21 | 22 | This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. 23 | 24 | ## Installation options 25 | 26 | ### Install with cargo 27 | 28 | [swift_file](https://crates.io/crates/swift_file) is published on crates.io. 29 | In order to install a Rust crate from crates.io, it is required to have [Rust and cargo installed](https://doc.rust-lang.org/cargo/getting-started/installation.html) on your system. 30 | 31 | ```sh 32 | cargo install swift_file 33 | ``` 34 | 35 | ### Manual installation from an archive 36 | 37 | [Latest release](https://github.com/mateoradman/swift_file/releases/latest) page provides an option to manually install the sf binary from an archive. The archive is available for Linux, MacOS, and Windows. 38 | Download, extract and move the binary to the desired directory, and set execution permissions. 39 | 40 | #### Linux 41 | 42 | 1. Download the Linux tar.gz archive from the latest [release](https://github.com/mateoradman/swift_file/releases/latest) 43 | 2. Extract the archive 44 | 45 | ```sh 46 | tar xf swift_file_*_x86_64-unknown-linux-musl.tar.gz 47 | ``` 48 | 49 | 3. Move the binary 50 | 51 | ```sh 52 | sudo mv sf /usr/local/bin 53 | ``` 54 | 55 | 4. Set execution permissions 56 | 57 | ```sh 58 | sudo chmod +x /usr/local/bin/sf 59 | ``` 60 | 61 | 5. Run sf 62 | 63 | ```sh 64 | sf --help 65 | ``` 66 | 67 | #### MacOS 68 | 69 | 1. Download the MacOS (apple-darwin) ZIP archive from the latest [release](https://github.com/mateoradman/swift_file/releases/latest) 70 | 2. Extract the archive 71 | 72 | ```sh 73 | unzip swift_file_*_x86_64-apple-darwin.zip 74 | ``` 75 | 76 | 3. Move the binary 77 | 78 | ```sh 79 | sudo mv sf /usr/local/bin 80 | ``` 81 | 82 | 4. Set execution permissions 83 | 84 | ```sh 85 | sudo chmod +x /usr/local/bin/sf 86 | ``` 87 | 88 | 5. Run sf 89 | 90 | ```sh 91 | sf --help 92 | ``` 93 | 94 | #### Windows 95 | 96 | 1. Download the Windows ZIP archive from the latest [release](https://github.com/mateoradman/swift_file/releases/latest) 97 | 2. Extract the archive 98 | 3. Run sf.exe 99 | 100 | ## CLI Usage 101 | 102 | ``` 103 | Send or receive files between devices using Wi-Fi network 104 | 105 | Usage: sf [OPTIONS] 106 | 107 | Commands: 108 | send Send a file 109 | receive Receive a file 110 | help Print this message or the help of the given subcommand(s) 111 | 112 | Options: 113 | --ip IP Address to bind to 114 | -i, --interface Network interface to use (ignored if --ip provided) 115 | -p, --port Server port 116 | -h, --help Print help 117 | -V, --version Print version 118 | ``` 119 | 120 | ### Sending a file to another device 121 | 122 | ``` 123 | Send a file 124 | 125 | Usage: sf send [OPTIONS] 126 | 127 | Arguments: 128 | File path to send 129 | 130 | Options: 131 | --ip IP Address to bind to 132 | --zip ZIP file or directory before transferring 133 | -i, --interface Network interface to use (ignored if --ip provided) 134 | -p, --port Server port 135 | -h, --help Print help 136 | ``` 137 | 138 | ### Receiving files from another device 139 | 140 | ``` 141 | Receive files 142 | 143 | Usage: sf receive [OPTIONS] 144 | 145 | Options: 146 | -d, --dest-dir Destination directory 147 | --ip IP Address to bind to 148 | -i, --interface Network interface to use (ignored if --ip provided) 149 | --no-open Disable opening the received file automatically using the system default program 150 | -p, --port Server port 151 | -h, --help Print help 152 | ``` 153 | -------------------------------------------------------------------------------- /docs/demo-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mateoradman/swift_file/639f9f3caa8b12213c3c4089be36b809cbea5c33/docs/demo-screenshot.png -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | files, 3 | network::{is_ip_address_valid, is_network_interface_valid, is_port_valid}, 4 | qr::generate_qr_code, 5 | GlobalConfig, 6 | }; 7 | use clap::{Parser, Subcommand}; 8 | use std::{net::IpAddr, path::PathBuf, process::exit}; 9 | use uuid::Uuid; 10 | 11 | #[derive(Debug, Parser)] 12 | #[command(author, version, about, long_about = None)] 13 | pub struct Cli { 14 | /// IP Address to bind to 15 | #[arg(long, value_parser = is_ip_address_valid, global = true)] 16 | ip: Option, 17 | /// Network interface to use (ignored if --ip provided) 18 | #[arg(short, long, value_parser = is_network_interface_valid, global = true)] 19 | interface: Option, 20 | #[arg(short, long, value_parser = is_port_valid, global = true)] 21 | /// Server port 22 | port: Option, 23 | #[command(subcommand)] 24 | command: Commands, 25 | } 26 | 27 | impl Cli { 28 | pub async fn into_config(self) -> GlobalConfig { 29 | let mut global_config = GlobalConfig::new(&self.ip, &self.interface, &self.port); 30 | let route = match &self.command { 31 | Commands::Send { path, zip } => self.send(&mut global_config, path.clone(), zip).await, 32 | Commands::Receive { dest_dir, no_open } => { 33 | self.receive(&mut global_config, dest_dir, no_open).await 34 | } 35 | }; 36 | generate_qr_code(&global_config.socket_addr, &route).await; 37 | global_config 38 | } 39 | 40 | async fn send(&self, global_config: &mut GlobalConfig, path: PathBuf, zip: &bool) -> String { 41 | if !path.exists() { 42 | eprintln!("Path {} does not exist on disk", path.to_str().unwrap()); 43 | exit(1) 44 | } 45 | if path.is_dir() && !zip { 46 | eprintln!("Unable to send a directory without creating a zip. Tip: use --zip"); 47 | exit(1) 48 | } 49 | 50 | let mut file_path = path.clone(); 51 | if *zip { 52 | let file_stem = match path.file_stem() { 53 | Some(val) => val.to_str().unwrap_or("file"), 54 | None => { 55 | eprintln!("Unable to determine file stem from the provided file path"); 56 | exit(1) 57 | } 58 | }; 59 | file_path = match files::create_archive(file_stem, &path).await { 60 | Ok(path) => path, 61 | Err(err) => { 62 | eprintln!("Unable to create a zip file from {file_stem} due to {err}"); 63 | exit(1) 64 | } 65 | }; 66 | } 67 | 68 | let uuid = Uuid::new_v4().to_string(); 69 | global_config 70 | .uuid_path_map 71 | .lock() 72 | .expect("global state already locked in the same thread") 73 | .insert(uuid.clone(), file_path); 74 | global_config.zip = *zip; 75 | format!("/download/{uuid}") 76 | } 77 | 78 | async fn receive( 79 | &self, 80 | global_config: &mut GlobalConfig, 81 | dest_dir: &Option, 82 | no_open: &bool, 83 | ) -> String { 84 | if let Some(path) = dest_dir { 85 | if !path.is_dir() { 86 | eprintln!( 87 | "Destination directory must exist but {} does not exist", 88 | path.display() 89 | ); 90 | exit(1); 91 | } 92 | global_config.destination_dir = dest_dir.clone(); 93 | } 94 | global_config.auto_open = !no_open; 95 | String::from("/receive") 96 | } 97 | } 98 | 99 | #[derive(Debug, Subcommand)] 100 | pub enum Commands { 101 | /// Send a file or directory 102 | Send { 103 | /// Path to a file (or directory if using --zip) 104 | path: PathBuf, 105 | #[arg(long, default_value_t = false)] 106 | /// ZIP file or directory before transferring 107 | zip: bool, 108 | }, 109 | 110 | /// Receive files 111 | Receive { 112 | #[arg(short, long)] 113 | /// Destination directory 114 | dest_dir: Option, 115 | #[arg(long, default_value_t = false)] 116 | /// Disable opening the received file automatically using the system default program 117 | no_open: bool, 118 | }, 119 | } 120 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::{IpAddr, SocketAddr}; 3 | use std::path::PathBuf; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | use crate::network::get_socket_addr; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct GlobalConfig { 10 | pub uuid_path_map: Arc>>, 11 | pub destination_dir: Option, 12 | pub auto_open: bool, 13 | pub zip: bool, 14 | pub socket_addr: SocketAddr, 15 | } 16 | 17 | impl GlobalConfig { 18 | pub fn new( 19 | ip: &Option, 20 | interface: &Option, 21 | port: &Option, 22 | ) -> GlobalConfig { 23 | let socket_addr = get_socket_addr(ip, interface, port); 24 | GlobalConfig { 25 | destination_dir: None, 26 | auto_open: true, 27 | zip: false, 28 | socket_addr, 29 | uuid_path_map: Arc::new(Mutex::new(HashMap::new())), 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/files.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | use anyhow::{anyhow, bail, Result}; 7 | use async_zip::{tokio::write::ZipFileWriter, Compression, ZipEntryBuilder}; 8 | use chrono::Local; 9 | use tokio::io::AsyncReadExt; 10 | 11 | fn set_available_filename(path: &mut PathBuf) { 12 | if path.is_dir() || path.is_file() { 13 | let now = Local::now().format("%d-%m-%Y %H:%M:%S").to_string(); 14 | let new_filename = match path.file_name() { 15 | Some(filename) => format!("{now} - {}", filename.to_str().unwrap()), 16 | None => String::from("uploaded file"), 17 | }; 18 | path.set_file_name(new_filename); 19 | }; 20 | } 21 | 22 | pub fn get_destination_path(filename: &str, destination_dir: &Option) -> String { 23 | let cwd = env::current_dir().unwrap(); 24 | let working_dir = match destination_dir { 25 | Some(dest_dir) => dest_dir, 26 | None => &cwd, 27 | }; 28 | let mut dest_path = working_dir.join(filename); 29 | set_available_filename(&mut dest_path); 30 | dest_path.into_os_string().into_string().unwrap() 31 | } 32 | 33 | pub async fn create_archive(original_filename: &str, original_path: &Path) -> Result { 34 | let zip_path = PathBuf::from(format!("{original_filename}.zip")); 35 | println!("Creating a ZIP file..."); 36 | let mut zip_file = tokio::fs::File::create(&zip_path).await?; 37 | let mut writer = ZipFileWriter::with_tokio(&mut zip_file); 38 | if original_path.is_dir() { 39 | handle_directory(original_path, &mut writer).await?; 40 | } else { 41 | handle_singular(original_path, &mut writer).await?; 42 | } 43 | 44 | writer.close().await?; 45 | println!( 46 | "Successfully created a ZIP file {}", 47 | original_path.to_str().ok_or(anyhow!("invalid zip path"))? 48 | ); 49 | Ok(zip_path) 50 | } 51 | 52 | async fn handle_singular( 53 | input_path: &Path, 54 | writer: &mut ZipFileWriter<&mut tokio::fs::File>, 55 | ) -> Result<()> { 56 | let filename = input_path 57 | .file_name() 58 | .ok_or(anyhow!("Input path terminates in '...'."))?; 59 | let filename = filename 60 | .to_str() 61 | .ok_or(anyhow!("Input path not valid UTF-8."))?; 62 | 63 | write_entry(filename, input_path, writer).await 64 | } 65 | 66 | async fn handle_directory( 67 | input_path: &Path, 68 | writer: &mut ZipFileWriter<&mut tokio::fs::File>, 69 | ) -> Result<()> { 70 | let entries = walk_dir(input_path.into()).await?; 71 | let input_dir_str = input_path 72 | .as_os_str() 73 | .to_str() 74 | .ok_or(anyhow!("Input path not valid UTF-8."))?; 75 | 76 | for entry_path_buf in entries { 77 | let entry_path = entry_path_buf.as_path(); 78 | let entry_str = entry_path 79 | .as_os_str() 80 | .to_str() 81 | .ok_or(anyhow!("Directory file path not valid UTF-8."))?; 82 | 83 | if !entry_str.starts_with(input_dir_str) { 84 | bail!("Directory file path does not start with base input directory path."); 85 | } 86 | 87 | let entry_str = &entry_str[input_dir_str.len() + 1..]; 88 | write_entry(entry_str, entry_path, writer).await?; 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | async fn write_entry( 95 | filename: &str, 96 | input_path: &Path, 97 | writer: &mut ZipFileWriter<&mut tokio::fs::File>, 98 | ) -> Result<()> { 99 | let mut input_file = tokio::fs::File::open(input_path).await?; 100 | let input_file_size = input_file.metadata().await?.len() as usize; 101 | 102 | let mut buffer = Vec::with_capacity(input_file_size); 103 | input_file.read_to_end(&mut buffer).await?; 104 | 105 | let builder = ZipEntryBuilder::new(filename.into(), Compression::Deflate); 106 | writer.write_entry_whole(builder, &buffer).await?; 107 | 108 | Ok(()) 109 | } 110 | 111 | async fn walk_dir(dir: PathBuf) -> Result> { 112 | let mut dirs = vec![dir]; 113 | let mut files = vec![]; 114 | 115 | while !dirs.is_empty() { 116 | let mut dir_iter = tokio::fs::read_dir(dirs.remove(0)).await?; 117 | 118 | while let Some(entry) = dir_iter.next_entry().await? { 119 | let entry_path_buf = entry.path(); 120 | 121 | if entry_path_buf.is_dir() { 122 | dirs.push(entry_path_buf); 123 | } else { 124 | files.push(entry_path_buf); 125 | } 126 | } 127 | } 128 | 129 | Ok(files) 130 | } 131 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | mod cli; 4 | mod config; 5 | mod files; 6 | mod network; 7 | mod qr; 8 | mod receive; 9 | mod send; 10 | mod server; 11 | 12 | use crate::{cli::Cli, config::GlobalConfig, server::start_server}; 13 | use clap::Parser; 14 | 15 | #[tokio::main] 16 | async fn main() { 17 | let cli_args = Cli::parse(); 18 | let config = cli_args.into_config().await; 19 | start_server(config).await; 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn verify_cli() { 28 | use clap::CommandFactory; 29 | Cli::command().debug_assert() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/network.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | format, 3 | net::{IpAddr, SocketAddr, TcpListener}, 4 | ops::RangeInclusive, 5 | process::exit, 6 | }; 7 | 8 | use anyhow::{anyhow, Context, Ok, Result}; 9 | 10 | const PORT_RANGE: RangeInclusive = 1024..=49151; // user port range 11 | 12 | pub fn is_port_valid(s: &str) -> Result { 13 | let port: u16 = s 14 | .parse() 15 | .with_context(|| format!("`{s}` isn't a port number"))?; 16 | Ok(port) 17 | } 18 | 19 | pub fn is_ip_address_valid(s: &str) -> Result { 20 | let address: IpAddr = s 21 | .parse() 22 | .with_context(|| format!("{s} isn't a valid IP address"))?; 23 | let socket = SocketAddr::new(address, 0); 24 | TcpListener::bind(socket) 25 | .with_context(|| format!("cannot bind to the provided IP address `{s}`"))?; 26 | Ok(address) 27 | } 28 | 29 | pub fn is_network_interface_valid(s: &str) -> Result { 30 | for interface in default_net::get_interfaces() { 31 | if interface.name == s { 32 | if interface.ipv4.is_empty() && interface.ipv6.is_empty() { 33 | return Err(anyhow!( 34 | "interface {s} has no IPv4 or IPv6 address to bind to" 35 | )); 36 | } 37 | return Ok(interface); 38 | } 39 | } 40 | Err(anyhow!("{s} is not a valid interface name")) 41 | } 42 | 43 | pub fn get_socket_addr( 44 | ip: &Option, 45 | interface: &Option, 46 | port: &Option, 47 | ) -> SocketAddr { 48 | let ip_addr = match ip { 49 | Some(addr) => *addr, 50 | None => get_interface_ip(interface), 51 | }; 52 | let server_port = find_available_port(&ip_addr, port); 53 | SocketAddr::new(ip_addr, server_port) 54 | } 55 | 56 | fn get_interface_ip(interface: &Option) -> IpAddr { 57 | match interface { 58 | Some(iface) => { 59 | if iface.ipv4.is_empty() { 60 | IpAddr::V6(iface.ipv6[0].addr) 61 | } else { 62 | IpAddr::V4(iface.ipv4[0].addr) 63 | } 64 | } 65 | None => get_default_interface_ip(), 66 | } 67 | } 68 | 69 | fn get_default_interface_ip() -> IpAddr { 70 | match default_net::interface::get_local_ipaddr() { 71 | Some(ip) => ip, 72 | None => { 73 | eprintln!("Unable to get local IP address of a default network interface."); 74 | exit(1); 75 | } 76 | } 77 | } 78 | 79 | fn find_available_port(ip: &IpAddr, user_port: &Option) -> u16 { 80 | if let Some(port) = user_port { 81 | if can_listen_on_port(ip, port) { 82 | return *port; 83 | } 84 | println!("Selected port {port} is not available. Searching for another available port..."); 85 | } 86 | for port in PORT_RANGE { 87 | if can_listen_on_port(ip, &port) { 88 | return port; 89 | } 90 | } 91 | eprintln!("Unable to listen to any port on IP address {ip}."); 92 | exit(1); 93 | } 94 | 95 | fn can_listen_on_port(ip: &IpAddr, port: &u16) -> bool { 96 | let addr = SocketAddr::new(*ip, *port); 97 | TcpListener::bind(addr).is_ok() 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::*; 103 | use std::net::Ipv4Addr; 104 | 105 | const DEFAULT_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); 106 | 107 | #[test] 108 | fn test_is_port_valid() { 109 | assert!(is_port_valid("8300").is_ok()); 110 | } 111 | 112 | #[test] 113 | fn test_is_port_valid_fails() { 114 | assert!(is_port_valid("blabla").is_err()); 115 | } 116 | 117 | #[test] 118 | fn test_is_ip_valid() { 119 | assert!(is_ip_address_valid("0.0.0.0").is_ok()); 120 | } 121 | 122 | #[test] 123 | fn test_is_ip_valid_fails() { 124 | assert!(is_ip_address_valid("blabla").is_err()); 125 | } 126 | 127 | #[test] 128 | fn test_is_network_interface_valid_fails() { 129 | assert!(is_network_interface_valid("blabla").is_err()); 130 | } 131 | 132 | #[test] 133 | fn test_find_available_port() { 134 | let port = find_available_port(&DEFAULT_ADDRESS, &None); 135 | assert!(is_port_valid(&port.to_string()).is_ok()); 136 | } 137 | 138 | #[test] 139 | fn test_can_bind() { 140 | let port = find_available_port(&DEFAULT_ADDRESS, &None); 141 | let bindable = can_listen_on_port(&DEFAULT_ADDRESS, &port); 142 | assert!(bindable); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/qr.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use qrcode::render::unicode; 4 | use qrcode::QrCode; 5 | 6 | pub async fn generate_qr_code(server_addr: &SocketAddr, route: &str) { 7 | let complete_url = format!("http://{server_addr}{route}"); 8 | let code = QrCode::new(&complete_url).unwrap(); 9 | let image = code 10 | .render::() 11 | .dark_color(unicode::Dense1x2::Light) 12 | .light_color(unicode::Dense1x2::Dark) 13 | .build(); 14 | println!("Swift File server is warming up..."); 15 | println!("Scan the QR code to get started. Shut down the server by pressing \"Ctrl + c\""); 16 | println!("{complete_url}\n"); 17 | println!("{}", image); 18 | } 19 | -------------------------------------------------------------------------------- /src/receive.rs: -------------------------------------------------------------------------------- 1 | use humansize::{format_size, DECIMAL}; 2 | use std::{fs::write, net::SocketAddr}; 3 | 4 | use axum::{ 5 | extract::{ConnectInfo, Multipart, State}, 6 | http::StatusCode, 7 | response::{Html, Redirect}, 8 | routing::get, 9 | Router, 10 | }; 11 | 12 | use crate::{files::get_destination_path, GlobalConfig}; 13 | 14 | pub fn router() -> Router { 15 | Router::new().route("/receive", get(show_form).post(accept_form)) 16 | } 17 | 18 | static HTML_FORM: &str = include_str!("./resources/form.html"); 19 | async fn show_form() -> Html<&'static str> { 20 | Html(HTML_FORM) 21 | } 22 | 23 | async fn accept_form( 24 | ConnectInfo(client_addr): ConnectInfo, 25 | State(state): State, 26 | mut multipart: Multipart, 27 | ) -> Result { 28 | while let Some(field) = multipart 29 | .next_field() 30 | .await 31 | .map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()))? 32 | { 33 | let file_name = field.file_name().unwrap_or("uploaded file").to_string(); 34 | let content_type = field 35 | .content_type() 36 | .unwrap_or("application/octet-stream") 37 | .to_string(); 38 | let data = field.bytes().await.unwrap(); 39 | let size = data.len(); 40 | if size == 0 { 41 | return Err(( 42 | StatusCode::BAD_REQUEST, 43 | String::from("Please upload at least one file"), 44 | )); 45 | } 46 | let human_size: String = format_size(data.len(), DECIMAL); 47 | let dest_path = get_destination_path(&file_name, &state.destination_dir); 48 | let written: bool = match write(&dest_path, &data) { 49 | Ok(_) => { 50 | println!( 51 | "Client {} successfully transferred {}", 52 | &client_addr.ip(), 53 | construct_file_info(&file_name, &content_type, &human_size, &dest_path), 54 | ); 55 | true 56 | } 57 | Err(err) => { 58 | println!( 59 | "Client {} attempted to transfer {} but it failed due to `{}`", 60 | &client_addr.ip(), 61 | construct_file_info(&file_name, &content_type, &human_size, &dest_path), 62 | err 63 | ); 64 | false 65 | } 66 | }; 67 | 68 | if written && state.auto_open { 69 | match opener::open(&dest_path) { 70 | Ok(()) => println!("File opened using a system default program."), 71 | Err(err) => { 72 | println!("Unable to open a file using the system default program due to {err}") 73 | } 74 | } 75 | } 76 | } 77 | 78 | Ok(Redirect::to("/receive")) 79 | } 80 | 81 | fn construct_file_info( 82 | file_name: &str, 83 | content_type: &str, 84 | human_size: &str, 85 | destination_path: &str, 86 | ) -> String { 87 | format!( 88 | "file `{}` with content type `{}` and size `{}` to `{}`", 89 | file_name, content_type, human_size, destination_path 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /src/resources/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swift File 6 | 119 | 120 | 121 |

Swift File

122 |
123 | 126 | 127 |
    128 | 129 |
    130 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/send.rs: -------------------------------------------------------------------------------- 1 | use std::{format, net::SocketAddr, println}; 2 | 3 | use axum::{ 4 | body::StreamBody, 5 | extract::{ConnectInfo, Path, State}, 6 | http::{header, HeaderName, StatusCode}, 7 | routing::{get, Router}, 8 | }; 9 | 10 | use tokio_util::io::ReaderStream; 11 | 12 | use crate::GlobalConfig; 13 | 14 | pub fn router() -> Router { 15 | Router::new().route("/download/:file_uuid", get(send_file)) 16 | } 17 | 18 | async fn send_file( 19 | ConnectInfo(client_addr): ConnectInfo, 20 | State(state): State, 21 | Path(file_uuid): Path, 22 | ) -> Result< 23 | ( 24 | [(HeaderName, String); 3], 25 | StreamBody>, 26 | ), 27 | (StatusCode, String), 28 | > { 29 | let file_path = match state.uuid_path_map.lock() { 30 | Ok(lock) => match lock.get(&file_uuid) { 31 | Some(val) => val.clone(), 32 | None => return Err((StatusCode::NOT_FOUND, String::from("Wrong file UUID"))), 33 | }, 34 | Err(err) => { 35 | return Err(( 36 | StatusCode::INTERNAL_SERVER_ERROR, 37 | format!("Something went wrong with getting the file path: {}", err), 38 | )) 39 | } 40 | }; 41 | 42 | println!( 43 | "Client (IP {}) requested to download {}", 44 | &client_addr.ip(), 45 | file_path.canonicalize().unwrap().to_str().unwrap(), 46 | ); 47 | 48 | let file = match tokio::fs::File::open(&file_path).await { 49 | Ok(file) => file, 50 | Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))), 51 | }; 52 | let file_length = file.metadata().await.unwrap().len().to_string(); 53 | let stream = ReaderStream::new(file); 54 | let body = StreamBody::new(stream); 55 | 56 | // file metadata 57 | let filename = match file_path.file_name() { 58 | Some(val) => val.to_str().unwrap_or("file"), 59 | None => { 60 | return Err(( 61 | StatusCode::INTERNAL_SERVER_ERROR, 62 | String::from("Unable to determine filename"), 63 | )) 64 | } 65 | }; 66 | let content_disposition_header = format!("attachment; filename=\"{}\"", filename); 67 | let file_mime = match state.zip { 68 | true => String::from("application/zip"), 69 | false => new_mime_guess::from_path(file_path) 70 | .first_or_octet_stream() 71 | .to_string(), 72 | }; 73 | let headers = [ 74 | (header::CONTENT_TYPE, file_mime), 75 | (header::CONTENT_DISPOSITION, content_disposition_header), 76 | (header::CONTENT_LENGTH, file_length), 77 | ]; 78 | 79 | println!( 80 | "Client (IP {}) successfully downloaded the file.", 81 | &client_addr.ip() 82 | ); 83 | Ok((headers, body)) 84 | } 85 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | use std::process::exit; 4 | use std::{env, println}; 5 | 6 | use crate::config::GlobalConfig; 7 | use crate::{receive, send}; 8 | use axum::extract::DefaultBodyLimit; 9 | use axum::Router; 10 | use tokio::signal; 11 | 12 | pub async fn start_server(config: GlobalConfig) { 13 | let mut paths: Vec = Vec::new(); 14 | for path in config.uuid_path_map.lock().unwrap().values() { 15 | paths.push(path.clone()); 16 | } 17 | let zip_flag = config.zip; 18 | let server_address = config.socket_addr; 19 | let app = build_router(config).await; 20 | match axum::Server::try_bind(&server_address) { 21 | Ok(server) => { 22 | server 23 | .serve(app.into_make_service_with_connect_info::()) 24 | .with_graceful_shutdown(shutdown_signal(paths, zip_flag)) 25 | .await 26 | .unwrap(); 27 | } 28 | Err(_) => { 29 | eprintln!("Unable to bind the server to IP address {server_address}"); 30 | exit(1) 31 | } 32 | } 33 | } 34 | 35 | async fn build_router(shared_state: GlobalConfig) -> Router { 36 | Router::new() 37 | .nest("/", send::router()) 38 | .merge(receive::router()) 39 | .layer(DefaultBodyLimit::disable()) 40 | .with_state(shared_state) 41 | } 42 | 43 | async fn shutdown_signal(paths: Vec, zip_flag: bool) { 44 | let ctrl_c = async { 45 | signal::ctrl_c() 46 | .await 47 | .expect("failed to install Ctrl+C handler"); 48 | }; 49 | 50 | #[cfg(unix)] 51 | let terminate = async { 52 | signal::unix::signal(signal::unix::SignalKind::terminate()) 53 | .expect("failed to install signal handler") 54 | .recv() 55 | .await; 56 | }; 57 | 58 | #[cfg(not(unix))] 59 | let terminate = std::future::pending::<()>(); 60 | 61 | tokio::select! { 62 | _ = ctrl_c => {}, 63 | _ = terminate => {}, 64 | } 65 | 66 | println!("Shutting down sf server..."); 67 | if !zip_flag { 68 | return; 69 | } 70 | println!("Removing created ZIP files..."); 71 | let cwd = env::current_dir().unwrap(); 72 | for path in paths { 73 | let complete_path = path.canonicalize().unwrap(); 74 | let extension = match complete_path.extension() { 75 | Some(ext) => ext, 76 | None => continue, 77 | }; 78 | if complete_path.exists() 79 | && complete_path.starts_with(&cwd) 80 | && complete_path.is_file() 81 | && extension == "zip" 82 | { 83 | let _ = tokio::fs::remove_file(&complete_path).await; 84 | println!("Successfully removed {}", complete_path.to_str().unwrap()) 85 | } 86 | } 87 | } 88 | --------------------------------------------------------------------------------