Compare commits
4 commits
164b9f2395
...
31c31801ce
Author | SHA1 | Date | |
---|---|---|---|
|
31c31801ce | ||
|
627bf71703 | ||
|
a9e1256227 | ||
|
4047b29a50 |
10 changed files with 397 additions and 115 deletions
285
Cargo.lock
generated
285
Cargo.lock
generated
|
@ -52,7 +52,16 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -130,7 +139,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -184,6 +193,18 @@ version = "3.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
|
@ -329,7 +350,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -340,7 +361,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
|||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -394,7 +415,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -440,6 +461,22 @@ version = "2.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "figment"
|
||||
version = "0.10.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"parking_lot",
|
||||
"pear",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"uncased",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -880,6 +917,12 @@ version = "2.0.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "inlinable_string"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.7"
|
||||
|
@ -890,7 +933,7 @@ dependencies = [
|
|||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -950,16 +993,19 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"axum",
|
||||
"chrono",
|
||||
"figment",
|
||||
"julid-rs",
|
||||
"local-ip-address",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-dialog",
|
||||
"network-interface",
|
||||
"ratatui",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha256",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
]
|
||||
|
@ -1018,6 +1064,18 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "local-ip-address"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "656b3b27f8893f7bbf9485148ff9a65f019e3f33bd5cdc87c83cab16b3fd9ec8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"neli",
|
||||
"thiserror 2.0.12",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
|
@ -1109,7 +1167,7 @@ dependencies = [
|
|||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"versions",
|
||||
"wfd",
|
||||
"which",
|
||||
|
@ -1133,6 +1191,44 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93062a0dce6da2517ea35f301dfc88184ce18d3601ec786a727a87bf535deca9"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"neli-proc-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli-proc-macros"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8034b7fbb6f9455b2a96c19e6edf8dc9fc34c70449938d8ee3b4df363f61fe"
|
||||
dependencies = [
|
||||
"either",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "network-interface"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3329f515506e4a2de3aa6e07027a6758e22e0f0e8eaf64fa47261cec2282602"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
|
@ -1330,7 +1426,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1386,6 +1482,29 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
|
||||
dependencies = [
|
||||
"inlinable_string",
|
||||
"pear_codegen",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear_codegen"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -1437,6 +1556,19 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2-diagnostics"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
"version_check",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
|
@ -1526,7 +1658,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
|||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1715,7 +1847,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1740,6 +1872,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -1871,7 +2012,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1880,6 +2021,17 @@ version = "2.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
|
@ -1908,7 +2060,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1945,13 +2097,33 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1962,7 +2134,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2003,7 +2175,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2039,6 +2211,47 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
@ -2118,6 +2331,15 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
|
@ -2250,7 +2472,7 @@ dependencies = [
|
|||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -2285,7 +2507,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -2374,7 +2596,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2385,7 +2607,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2578,6 +2800,15 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
|
@ -2599,6 +2830,12 @@ version = "0.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
|
@ -2619,7 +2856,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
@ -2640,7 +2877,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2660,7 +2897,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
@ -2700,5 +2937,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
|
|
@ -6,10 +6,13 @@ edition = "2024"
|
|||
[dependencies]
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
chrono = "0.4"
|
||||
figment = { version = "0.10", features = ["toml", "test", "env"] }
|
||||
julid-rs = { version = "1", default-features = false, features = ["serde"] }
|
||||
local-ip-address = "0.6"
|
||||
mime = "0.3"
|
||||
mime_guess = "2"
|
||||
native-dialog = "0.9"
|
||||
network-interface = { version = "2", features = ["serde"] }
|
||||
ratatui = "0.29"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{ConnectInfo, State},
|
||||
Extension, Json,
|
||||
};
|
||||
|
||||
use crate::{models::device::DeviceInfo, Client};
|
||||
use crate::{Client, JoecalState, models::device::DeviceInfo};
|
||||
|
||||
impl Client {
|
||||
pub async fn announce_http(&self, ip: Option<SocketAddr>) -> crate::error::Result<()> {
|
||||
if let Some(ip) = ip {
|
||||
let url = format!("http://{}/api/localsend/v2/register", ip);
|
||||
let url = format!("http://{ip}/api/localsend/v2/register");
|
||||
let client = reqwest::Client::new();
|
||||
client.post(&url).json(&self.device).send().await?;
|
||||
}
|
||||
|
@ -19,16 +18,17 @@ impl Client {
|
|||
}
|
||||
|
||||
pub async fn announce_http_legacy(&self) -> crate::error::Result<()> {
|
||||
// send the reqwest to all local ip addresses from 192.168.0.0 to 192.168.255.255
|
||||
// send the reqwest to all local ip addresses from 192.168.0.0 to
|
||||
// 192.168.255.255
|
||||
let mut address_list = Vec::new();
|
||||
for j in 0..256 {
|
||||
for k in 0..256 {
|
||||
address_list.push(format!("192.168.{:03}.{}:53317", j, k));
|
||||
address_list.push(format!("192.168.{j:03}.{k}:53317"));
|
||||
}
|
||||
}
|
||||
|
||||
for ip in address_list {
|
||||
let url = format!("http://{}/api/localsend/v2/register", ip);
|
||||
let url = format!("http://{ip}/api/localsend/v2/register");
|
||||
self.http_client
|
||||
.post(&url)
|
||||
.json(&self.device)
|
||||
|
@ -40,16 +40,16 @@ impl Client {
|
|||
}
|
||||
|
||||
pub async fn register_device(
|
||||
State(peers): State<Arc<Mutex<HashMap<String, (SocketAddr, DeviceInfo)>>>>,
|
||||
Extension(client): Extension<DeviceInfo>,
|
||||
State(state): State<Arc<JoecalState>>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Json(device): Json<DeviceInfo>,
|
||||
) -> Json<DeviceInfo> {
|
||||
let mut addr = addr;
|
||||
addr.set_port(device.port);
|
||||
peers
|
||||
addr.set_port(state.device.port);
|
||||
state
|
||||
.peers
|
||||
.lock()
|
||||
.await
|
||||
.insert(device.fingerprint.clone(), (addr, device.clone()));
|
||||
Json(client)
|
||||
Json(device)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use crate::{models::device::DeviceInfo, Client};
|
||||
use crate::{Client, models::device::DeviceInfo};
|
||||
|
||||
pub mod http;
|
||||
pub mod multicast;
|
||||
|
@ -22,21 +22,22 @@ impl Client {
|
|||
src.set_port(device.port); // Update the port to the one the device sent
|
||||
|
||||
let mut peers = self.peers.lock().await;
|
||||
peers.insert(device.fingerprint.clone(), (src.clone(), device.clone()));
|
||||
peers.insert(device.fingerprint.clone(), (src, device.clone()));
|
||||
|
||||
if device.announce != Some(true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Announce in return upon receiving a valid device message and it wants announcements
|
||||
// Announce in return upon receiving a valid device message and it wants
|
||||
// announcements
|
||||
if let Err(e) = self.announce_multicast().await {
|
||||
eprintln!("Error during multicast announcement: {}", e);
|
||||
eprintln!("Error during multicast announcement: {e}");
|
||||
}
|
||||
if let Err(e) = self.announce_http(Some(src)).await {
|
||||
eprintln!("Error during HTTP announcement: {}", e);
|
||||
eprintln!("Error during HTTP announcement: {e}");
|
||||
};
|
||||
} else {
|
||||
eprintln!("Received invalid message: {}", message);
|
||||
eprintln!("Received invalid message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,12 @@ pub enum LocalSendError {
|
|||
|
||||
#[error("Cancel Failed")]
|
||||
CancelFailed,
|
||||
|
||||
#[error("IPv6 is not supported")]
|
||||
IPv6Unsupported,
|
||||
|
||||
#[error("Error getting local IP")]
|
||||
IpAddrError(#[from] local_ip_address::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, LocalSendError>;
|
||||
|
|
27
src/lib.rs
27
src/lib.rs
|
@ -4,15 +4,17 @@ pub mod models;
|
|||
pub mod server;
|
||||
pub mod transfer;
|
||||
|
||||
use crate::models::device::DeviceInfo;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use tokio::{net::UdpSocket, sync::Mutex, task::JoinHandle};
|
||||
use transfer::session::Session;
|
||||
|
||||
use crate::models::device::DeviceInfo;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
pub device: DeviceInfo,
|
||||
|
@ -25,6 +27,13 @@ pub struct Client {
|
|||
pub download_dir: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JoecalState {
|
||||
pub device: DeviceInfo,
|
||||
pub peers: Arc<Mutex<HashMap<String, (SocketAddr, DeviceInfo)>>>,
|
||||
pub sessions: Arc<Mutex<HashMap<String, Session>>>, // Session ID to Session
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn default() -> crate::error::Result<Self> {
|
||||
let device = DeviceInfo::default();
|
||||
|
@ -93,7 +102,7 @@ impl Client {
|
|||
let client = self.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = client.listen_multicast().await {
|
||||
eprintln!("UDP listener error: {}", e);
|
||||
eprintln!("UDP listener error: {e}");
|
||||
}
|
||||
})
|
||||
};
|
||||
|
@ -103,7 +112,7 @@ impl Client {
|
|||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Err(e) = client.announce(None).await {
|
||||
eprintln!("Announcement error: {}", e);
|
||||
eprintln!("Announcement error: {e}");
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -1,17 +1,39 @@
|
|||
use joecalsend::{models::device::DeviceInfo, Client};
|
||||
use joecalsend::{Client, JoecalState, error, models::device::DeviceInfo};
|
||||
use local_ip_address::local_ip;
|
||||
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> error::Result<()> {
|
||||
let device = DeviceInfo::default();
|
||||
dbg!(device);
|
||||
dbg!(&device);
|
||||
|
||||
let client = Client::with_config(
|
||||
DeviceInfo::default(),
|
||||
53317,
|
||||
"/home/ardent/joecalsend".into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let std::net::IpAddr::V4(ip) = local_ip()? else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// for enumerating subnet peers when multicast fails (https://github.com/localsend/protocol?tab=readme-ov-file#32-http-legacy-mode)
|
||||
let mut network_ip = ip;
|
||||
let nifs = NetworkInterface::show().unwrap();
|
||||
for addr in nifs.into_iter().flat_map(|i| i.addr) {
|
||||
if let Addr::V4(V4IfAddr {
|
||||
ip: ifip,
|
||||
netmask: Some(netmask),
|
||||
..
|
||||
}) = addr
|
||||
&& ip == ifip
|
||||
{
|
||||
network_ip = ip & netmask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dbg!(network_ip);
|
||||
|
||||
let client = Client::with_config(device, 53317, "/home/ardent/joecalsend".into())
|
||||
.await
|
||||
.unwrap();
|
||||
let (h1, h2, h3) = client.start().await.unwrap();
|
||||
tokio::join!(h1, h2, h3);
|
||||
|
||||
let _ = tokio::join!(h1, h2, h3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::error::LocalSendError;
|
||||
use std::{path::Path, time::SystemTime};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::LocalSendError;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
Extension, Json, Router,
|
||||
extract::DefaultBodyLimit,
|
||||
routing::{get, post},
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::limit::RequestBodyLimitLayer;
|
||||
|
||||
use crate::{
|
||||
Client, JoecalState,
|
||||
discovery::http::register_device,
|
||||
transfer::upload::{register_prepare_upload, register_upload},
|
||||
Client,
|
||||
};
|
||||
|
||||
impl Client {
|
||||
|
@ -19,7 +20,7 @@ impl Client {
|
|||
let addr = SocketAddr::from(([0, 0, 0, 0], self.port));
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("HTTP server listening on {}", addr);
|
||||
println!("HTTP server listening on {addr}");
|
||||
|
||||
axum::serve(
|
||||
listener,
|
||||
|
@ -32,6 +33,11 @@ impl Client {
|
|||
fn create_router(&self) -> Router {
|
||||
let peers = self.peers.clone();
|
||||
let device = self.device.clone();
|
||||
let state = Arc::new(JoecalState {
|
||||
peers,
|
||||
device: device.clone(),
|
||||
sessions: self.sessions.clone(),
|
||||
});
|
||||
|
||||
Router::new()
|
||||
.route("/api/localsend/v2/register", post(register_device))
|
||||
|
@ -49,9 +55,7 @@ impl Client {
|
|||
.route("/api/localsend/v2/upload", post(register_upload))
|
||||
.layer(DefaultBodyLimit::disable())
|
||||
.layer(RequestBodyLimitLayer::new(1024 * 1024 * 1024))
|
||||
.layer(Extension(self.device.clone()))
|
||||
.layer(Extension(self.sessions.clone()))
|
||||
.layer(Extension(self.download_dir.clone()))
|
||||
.with_state(peers)
|
||||
.with_state(state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
|
||||
|
||||
use axum::body::Bytes;
|
||||
use axum::extract::{ConnectInfo, Query};
|
||||
use axum::http::StatusCode;
|
||||
use axum::Extension;
|
||||
use axum::{response::IntoResponse, Json};
|
||||
|
||||
use crate::error::{LocalSendError, Result};
|
||||
use crate::transfer::session::{Session, SessionStatus};
|
||||
use crate::{
|
||||
models::{device::DeviceInfo, file::FileMetadata},
|
||||
Client,
|
||||
use axum::{
|
||||
Extension, Json,
|
||||
body::Bytes,
|
||||
extract::{ConnectInfo, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use julid::Julid;
|
||||
use native_dialog::MessageDialogBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
Client, JoecalState,
|
||||
error::{LocalSendError, Result},
|
||||
models::{device::DeviceInfo, file::FileMetadata},
|
||||
transfer::session::{Session, SessionStatus},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -45,7 +43,7 @@ impl Client {
|
|||
}
|
||||
|
||||
let peer = self.peers.lock().await.get(&peer).unwrap().clone();
|
||||
println!("Peer: {:?}", peer);
|
||||
println!("Peer: {peer:?}");
|
||||
|
||||
let response = self
|
||||
.http_client
|
||||
|
@ -61,7 +59,7 @@ impl Client {
|
|||
.send()
|
||||
.await?;
|
||||
|
||||
println!("Response: {:?}", response);
|
||||
println!("Response: {response:?}");
|
||||
|
||||
let response: PrepareUploadResponse = response.json().await?;
|
||||
|
||||
|
@ -103,18 +101,16 @@ impl Client {
|
|||
|
||||
let request = self
|
||||
.http_client
|
||||
.post(&format!(
|
||||
"{}://{}/api/localsend/v2/upload?sessionId={}&fileId={}&token={}",
|
||||
session.receiver.protocol, session.addr, session_id, file_id, token
|
||||
))
|
||||
//.post(&format!("https://webhook.site/2f23a529-b687-4375-ad5f-54906ab26ac7?session_id={}&file_id={}&token={}", session_id, file_id, token))
|
||||
.post(format!(
|
||||
"{}://{}/api/localsend/v2/upload?sessionId={session_id}&fileId={file_id}&token={token}",
|
||||
session.receiver.protocol, session.addr))
|
||||
.body(body);
|
||||
|
||||
println!("Uploading file: {:?}", request);
|
||||
println!("Uploading file: {request:?}");
|
||||
let response = request.send().await?;
|
||||
|
||||
if response.status() != 200 {
|
||||
println!("Upload failed: {:?}", response);
|
||||
println!("Upload failed: {response:?}");
|
||||
return Err(LocalSendError::UploadFailed);
|
||||
}
|
||||
|
||||
|
@ -160,7 +156,7 @@ impl Client {
|
|||
|
||||
let request = self
|
||||
.http_client
|
||||
.post(&format!(
|
||||
.post(format!(
|
||||
"{}://{}/api/localsend/v2/cancel?sessionId={}",
|
||||
session.receiver.protocol, session.addr, session_id
|
||||
))
|
||||
|
@ -176,8 +172,7 @@ impl Client {
|
|||
}
|
||||
|
||||
pub async fn register_prepare_upload(
|
||||
Extension(client): Extension<DeviceInfo>,
|
||||
Extension(sessions): Extension<Arc<Mutex<HashMap<String, Session>>>>,
|
||||
State(state): State<Arc<JoecalState>>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Json(req): Json<PrepareUploadRequest>,
|
||||
) -> impl IntoResponse {
|
||||
|
@ -195,38 +190,42 @@ pub async fn register_prepare_upload(
|
|||
|
||||
let file_tokens: HashMap<String, String> = req
|
||||
.files
|
||||
.iter()
|
||||
.map(|(id, _)| (id.clone(), Julid::new().to_string())) // Replace with actual token logic
|
||||
.keys()
|
||||
.map(|id| (id.clone(), Julid::new().to_string())) // Replace with actual token logic
|
||||
.collect();
|
||||
|
||||
let session = Session {
|
||||
session_id: session_id.clone(),
|
||||
files: req.files.clone(),
|
||||
file_tokens: file_tokens.clone(),
|
||||
receiver: client.clone(),
|
||||
receiver: state.device.clone(),
|
||||
sender: req.info.clone(),
|
||||
status: SessionStatus::Active,
|
||||
addr,
|
||||
};
|
||||
|
||||
sessions.lock().await.insert(session_id.clone(), session);
|
||||
state
|
||||
.sessions
|
||||
.lock()
|
||||
.await
|
||||
.insert(session_id.clone(), session);
|
||||
|
||||
return (
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(PrepareUploadResponse {
|
||||
session_id,
|
||||
files: file_tokens,
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
.into_response()
|
||||
} else {
|
||||
return StatusCode::FORBIDDEN.into_response();
|
||||
StatusCode::FORBIDDEN.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_upload(
|
||||
Query(params): Query<UploadParams>,
|
||||
Extension(sessions): Extension<Arc<Mutex<HashMap<String, Session>>>>,
|
||||
State(state): State<Arc<JoecalState>>,
|
||||
Extension(download_dir): Extension<String>,
|
||||
body: Bytes,
|
||||
) -> impl IntoResponse {
|
||||
|
@ -236,7 +235,7 @@ pub async fn register_upload(
|
|||
let token = ¶ms.token;
|
||||
|
||||
// Get session and validate
|
||||
let mut sessions_lock = sessions.lock().await;
|
||||
let mut sessions_lock = state.sessions.lock().await;
|
||||
let session = match sessions_lock.get_mut(session_id) {
|
||||
Some(session) => session,
|
||||
None => return StatusCode::BAD_REQUEST.into_response(),
|
||||
|
@ -259,7 +258,7 @@ pub async fn register_upload(
|
|||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"File not found".to_string(),
|
||||
)
|
||||
.into_response()
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -267,7 +266,7 @@ pub async fn register_upload(
|
|||
if let Err(e) = tokio::fs::create_dir_all(&*download_dir).await {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to create directory: {}", e),
|
||||
format!("Failed to create directory: {e}"),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
@ -279,7 +278,7 @@ pub async fn register_upload(
|
|||
if let Err(e) = tokio::fs::write(&file_path, body).await {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to write file: {}", e),
|
||||
format!("Failed to write file: {e}"),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
@ -298,9 +297,9 @@ pub struct UploadParams {
|
|||
|
||||
pub async fn register_cancel(
|
||||
Query(params): Query<CancelParams>,
|
||||
Extension(sessions): Extension<Arc<Mutex<HashMap<String, Session>>>>,
|
||||
State(state): State<JoecalState>,
|
||||
) -> impl IntoResponse {
|
||||
let mut sessions_lock = sessions.lock().await;
|
||||
let mut sessions_lock = state.sessions.lock().await;
|
||||
let session = match sessions_lock.get_mut(¶ms.session_id) {
|
||||
Some(session) => session,
|
||||
None => return StatusCode::BAD_REQUEST.into_response(),
|
||||
|
|
Loading…
Reference in a new issue