Compare commits

..

7 commits

Author SHA1 Message Date
Joe Ardent
00092dc97b deadlock less 2025-09-04 11:48:38 -07:00
Joe Ardent
6167522aaa ready for new release, 1.61803398 2025-09-03 15:37:06 -07:00
Joe Ardent
8572f1431e remove unsafe code, add CWD to file picking and fuzzy selecting 2025-09-03 15:32:32 -07:00
Joe Ardent
4d78d67abe release 1.6180339, with MSRV 1.89 2025-08-22 12:28:06 -07:00
Joe Ardent
3eb6169a7b add MSRV 2025-08-22 12:26:17 -07:00
Joe Ardent
04b6388b2c release 1.618033 2025-08-21 12:11:30 -07:00
Joe Ardent
77d44b8868 update simsearch, fill out help screen 2025-08-21 12:09:33 -07:00
12 changed files with 217 additions and 175 deletions

145
Cargo.lock generated
View file

@ -292,9 +292,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.2" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@ -346,10 +346,11 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.33" version = "1.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3"
dependencies = [ dependencies = [
"find-msvc-tools",
"jobserver", "jobserver",
"libc", "libc",
"shlex", "shlex",
@ -397,9 +398,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.45" version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -407,21 +408,21 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.44" version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim 0.11.1", "strsim",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.45" version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -545,7 +546,7 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.11.1", "strsim",
"syn 2.0.106", "syn 2.0.106",
] ]
@ -562,9 +563,9 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.4.0" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
] ]
@ -712,6 +713,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -741,9 +748,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.1" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
] ]
@ -892,7 +899,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.2+wasi-0.2.4", "wasi 0.14.3+wasi-0.2.4",
] ]
[[package]] [[package]]
@ -1203,9 +1210,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.0.3" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [ dependencies = [
"idna_adapter", "idna_adapter",
"smallvec", "smallvec",
@ -1224,9 +1231,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.10.0" version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -1259,9 +1266,9 @@ dependencies = [
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.9" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
@ -1316,9 +1323,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"libc", "libc",
@ -1326,7 +1333,7 @@ dependencies = [
[[package]] [[package]]
name = "jocalsend" name = "jocalsend"
version = "1.6.1803" version = "1.6.1803398"
dependencies = [ dependencies = [
"axum", "axum",
"axum-server", "axum-server",
@ -1461,9 +1468,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.27" version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]] [[package]]
name = "lru" name = "lru"
@ -1744,9 +1751,9 @@ dependencies = [
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
@ -1768,9 +1775,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
dependencies = [ dependencies = [
"zerovec", "zerovec",
] ]
@ -1933,9 +1940,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.1" version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1945,9 +1952,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1956,9 +1963,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
@ -2281,11 +2288,11 @@ dependencies = [
[[package]] [[package]]
name = "simsearch" name = "simsearch"
version = "0.2.5" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c869b25830e4824ef7279015cfc298a0674aca6a54eeff2efce8d12bf3701fe" checksum = "629d21c4ebf25655995cda9eb93e85539fa68b0438acb85e9e5d10f6fe2404bc"
dependencies = [ dependencies = [
"strsim 0.10.0", "strsim",
"triple_accel", "triple_accel",
] ]
@ -2323,12 +2330,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -2428,31 +2429,31 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.20.0" version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix 1.0.8", "rustix 1.0.8",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.15" version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.15" version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2461,9 +2462,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.41" version = "0.3.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
dependencies = [ dependencies = [
"deranged", "deranged",
"num-conv", "num-conv",
@ -2474,9 +2475,9 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.4" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]] [[package]]
name = "tinystr" name = "tinystr"
@ -2699,9 +2700,9 @@ dependencies = [
[[package]] [[package]]
name = "triple_accel" name = "triple_accel"
version = "0.3.4" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622b09ce2fe2df4618636fb92176d205662f59803f39e70d1c333393082de96c" checksum = "22048bc95dfb2ffd05b1ff9a756290a009224b60b2f0e7525faeee7603851e63"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
@ -2799,13 +2800,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
"percent-encoding", "percent-encoding",
"serde",
] ]
[[package]] [[package]]
@ -2849,11 +2851,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.14.2+wasi-0.2.4" version = "0.14.3+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
dependencies = [ dependencies = [
"wit-bindgen-rt", "wit-bindgen",
] ]
[[package]] [[package]]
@ -3199,21 +3201,18 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.12" version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen"
version = "0.39.0" version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "writeable" name = "writeable"

View file

@ -1,12 +1,13 @@
[package] [package]
name = "jocalsend" name = "jocalsend"
# 1.61803398874989484 # 1.61803398874989484
#-------^ #----------^
version = "1.6.1803" version = "1.6.1803398"
edition = "2024" edition = "2024"
rust-version = "1.89"
authors = ["Joe Ardent <code@ardent.nebcorp.com>"] authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
keywords = ["p2p", "localsend", "tui", "linux"] keywords = ["p2p", "localsend", "tui", "linux"]
description = "A terminal implementation of the LocalSend protocol" description = "A TUI for LocalSend"
readme = "README.md" readme = "README.md"
license-file = "LICENSE.md" license-file = "LICENSE.md"
repository = "https://git.kittencollective.com/nebkor/joecalsend" repository = "https://git.kittencollective.com/nebkor/joecalsend"
@ -34,7 +35,7 @@ rustix = { version = "1", default-features = false, features = ["system"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sha256 = "1.6" sha256 = "1.6"
simsearch = "0.2" simsearch = "0.3"
thiserror = "2" thiserror = "2"
tokio = { version = "1", default-features = false, features = ["time", "macros", "rt-multi-thread"] } tokio = { version = "1", default-features = false, features = ["time", "macros", "rt-multi-thread"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "logging"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "logging"] }

View file

@ -10,7 +10,12 @@ that uses [Ratatui](https://github.com/ratatui/ratatui) to provide an interactiv
application, and is compatible with the official app. application, and is compatible with the official app.
Install with `cargo install jocalsend` (requires [Rust](https://rustup.rs/)); tested on Linux, it Install with `cargo install jocalsend` (requires [Rust](https://rustup.rs/)); tested on Linux, it
will probably work on Macs but if you're on a Mac, you probably have AirDrop. will probably work on Macs but if you're on a Mac, you probably have AirDrop. It's also available in
nixpkgs, and so if you're a NixOS user, `nix-shell -p jocalsend` will do what you expect.
## BLOG POSTS!
- [Announcement post](https://proclamations.nebcorp-hias.com/sundries/jocalsend/)
- [Design and development](https://proclamations.nebcorp-hias.com/rnd/jocalsend-development/)
## Capabilities and screenshots ## Capabilities and screenshots
@ -22,18 +27,21 @@ available:
- `S` -> go to the sending screen, defaulting to sending files - `S` -> go to the sending screen, defaulting to sending files
- `R` -> go to the receiving screen to approve or deny incoming transfers - `R` -> go to the receiving screen to approve or deny incoming transfers
- `L` -> go to the logging screen where you can adjust the log level - `L` -> go to the logging screen where you can adjust the log level
- `C` -> clear the list of local peers and re-discover them
- `H` or `?` -> go to help screen
- `ESC` -> go back to the previous screen - `ESC` -> go back to the previous screen
- `Q` -> exit the application - `Q` -> exit the application
Additionally, when in the sending screen, the following are available When in the sending screen, the following are available
- `TAB` -> switch between content selection and peer selection - `TAB` -> switch between content selection and peer selection
- `P` -> switch to peer selection - `T` -> enter text directly to send, `ESC` to cancel
- `T` -> switch to entering text to send - `/` -> fuzzy filename search, use `ESC` to stop inputting text
- `F` -> switch to selecting files to send (not available when entering text, use `ESC` to exit text entry)
In addition to the interactive commands, it will also accept commandline arguments to pre-select a When in the receiving screen, use `A` to approve the incoming transfer request, or `D` to deny it.
file or pre-populate text to send:
Finally, it will also accept commandline arguments to pre-select a file or pre-populate text to
send:
``` ```
$ jocalsend -h $ jocalsend -h

View file

@ -1 +1 @@
1.61803 1.61803398

View file

@ -15,20 +15,18 @@ pub(crate) struct FileFinder {
pub input: Input, pub input: Input,
} }
fn searcher() -> SimSearch<usize> {
SimSearch::new_with(
SearchOptions::new()
.stop_words(vec![std::path::MAIN_SEPARATOR_STR.to_string()])
.stop_whitespace(false)
.threshold(0.0),
)
}
impl FileFinder { impl FileFinder {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let fuzzy = SimSearch::new_with(
SearchOptions::new()
.stop_words(vec![std::path::MAIN_SEPARATOR_STR.to_string()])
.stop_whitespace(false)
.threshold(0.0),
);
Ok(Self { Ok(Self {
explorer: FileExplorer::new()?, explorer: FileExplorer::new()?,
fuzzy: searcher(), fuzzy,
working_dir: None, working_dir: None,
input: Default::default(), input: Default::default(),
}) })
@ -54,14 +52,10 @@ impl FileFinder {
} }
pub fn reset_fuzzy(&mut self) { pub fn reset_fuzzy(&mut self) {
self.clear_fuzzy(); self.fuzzy.clear();
self.input.reset(); self.input.reset();
} }
fn clear_fuzzy(&mut self) {
self.fuzzy = searcher();
}
pub fn index(&mut self) { pub fn index(&mut self) {
if let Some(owd) = self.working_dir.as_ref() if let Some(owd) = self.working_dir.as_ref()
&& owd == self.cwd() && owd == self.cwd()
@ -69,7 +63,7 @@ impl FileFinder {
return; return;
} }
self.working_dir = Some(self.cwd().to_path_buf()); self.working_dir = Some(self.cwd().to_path_buf());
self.clear_fuzzy(); self.reset_fuzzy();
for (i, f) in self.explorer.files().iter().enumerate() { for (i, f) in self.explorer.files().iter().enumerate() {
self.fuzzy.insert(i, f.name()); self.fuzzy.insert(i, f.name());

View file

@ -29,8 +29,8 @@ impl App {
_ => match mode { _ => match mode {
CurrentScreen::Main | CurrentScreen::Help => {} CurrentScreen::Main | CurrentScreen::Help => {}
CurrentScreen::Logging => match code { CurrentScreen::Logging => match code {
KeyCode::Left => super::change_log_level(-1), KeyCode::Left => change_log_level(LogDelta::Down),
KeyCode::Right => super::change_log_level(1), KeyCode::Right => change_log_level(LogDelta::Up),
_ => {} _ => {}
}, },
CurrentScreen::Receiving => match code { CurrentScreen::Receiving => match code {
@ -325,3 +325,17 @@ impl App {
} }
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum LogDelta {
Up,
Down,
}
fn change_log_level(delta: LogDelta) {
let level = match delta {
LogDelta::Up => log::max_level().increment_severity(),
LogDelta::Down => log::max_level().decrement_severity(),
};
log::set_max_level(level);
}

View file

@ -4,7 +4,6 @@ use crossterm::event::{Event, EventStream, KeyEventKind};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use jocalsend::{JocalEvent, JocalService, ReceiveRequest, error::Result}; use jocalsend::{JocalEvent, JocalService, ReceiveRequest, error::Result};
use julid::Julid; use julid::Julid;
use log::LevelFilter;
use ratatui::{ use ratatui::{
Frame, Frame,
widgets::{ListState, TableState}, widgets::{ListState, TableState},
@ -85,18 +84,6 @@ impl App {
pub async fn handle_events(&mut self) -> Result<()> { pub async fn handle_events(&mut self) -> Result<()> {
tokio::select! { tokio::select! {
terminal_event = self.terminal_events.next().fuse() => {
if let Some(Ok(evt)) = terminal_event {
match evt {
Event::Key(key)
if key.kind == KeyEventKind::Press
=> self.handle_key_event(key, evt).await,
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
_ => {}
}
}
}
jocal_event = self.jocal_event_rx.recv().fuse() => { jocal_event = self.jocal_event_rx.recv().fuse() => {
if let Some(event) = jocal_event { if let Some(event) = jocal_event {
log::trace!("got JocalEvent {event:?}"); log::trace!("got JocalEvent {event:?}");
@ -115,6 +102,19 @@ impl App {
} }
} }
} }
terminal_event = self.terminal_events.next().fuse() => {
if let Some(Ok(evt)) = terminal_event {
match evt {
Event::Key(key)
if key.kind == KeyEventKind::Press
=> self.handle_key_event(key, evt).await,
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
_ => {}
}
}
}
} }
Ok(()) Ok(())
@ -143,13 +143,3 @@ impl App {
frame.render_widget(self, frame.area()); frame.render_widget(self, frame.area());
} }
} }
fn change_log_level(delta: isize) {
let level = log::max_level() as isize;
let max = log::LevelFilter::max() as isize;
let level = (level + delta).clamp(0, max) as usize;
// levelfilter is repr(usize) so this is safe
let level = unsafe { std::mem::transmute::<usize, LevelFilter>(level) };
log::set_max_level(level);
}

View file

@ -192,18 +192,30 @@ impl Widget for &mut App {
} }
let file_area = header_left.inner(header_margin); let file_area = header_left.inner(header_margin);
let cwd = self
.file_finder
.cwd()
.as_os_str()
.to_string_lossy()
.into_owned();
match sending_screen { match sending_screen {
SendingScreen::Files(FileMode::Picking) SendingScreen::Files(FileMode::Picking)
| SendingScreen::Peers | SendingScreen::Peers
| SendingScreen::Text => { | SendingScreen::Text => {
let layout = Layout::vertical([Constraint::Length(3), Constraint::Min(5)]);
let [cwd_area, file_area] = layout.areas(file_area);
let cwd: Line = cwd.into();
Paragraph::new(cwd)
.centered()
.block(Block::bordered())
.render(cwd_area, buf);
self.file_finder.widget().render(file_area, buf); self.file_finder.widget().render(file_area, buf);
} }
SendingScreen::Files(FileMode::Fuzzy) => { SendingScreen::Files(FileMode::Fuzzy) => {
let layout = Layout::vertical([Constraint::Max(6), Constraint::Min(5)]); let layout = Layout::vertical([Constraint::Max(3), Constraint::Min(5)]);
let [input, files] = layout.areas(file_area); let [input_area, files_area] = layout.areas(file_area);
text_popup(self.file_finder.input.value(), "fuzzy search", input, buf); text_popup(self.file_finder.input.value(), &cwd, input_area, buf);
self.file_finder.widget().render(files, buf); self.file_finder.widget().render(files_area, buf);
} }
} }
@ -253,8 +265,6 @@ fn outer_frame(screen: &CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer
} }
fn help_screen(area: Rect, buf: &mut Buffer) { fn help_screen(area: Rect, buf: &mut Buffer) {
let intro = "JocalSend is a mode-based application that responds to key-presses. Most modes support the following key bindings:".to_line().centered();
let intro = Paragraph::new(intro).wrap(Wrap { trim: true });
let spacer = "".to_line().centered(); let spacer = "".to_line().centered();
let main_bindings = vec![ let main_bindings = vec![
Row::new(vec!["".to_line(), spacer.clone(), "".to_line()]), Row::new(vec!["".to_line(), spacer.clone(), "".to_line()]),
@ -266,7 +276,7 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
]), ]),
// Receiving // Receiving
Row::new(vec![ Row::new(vec![
"Manage incoming data requests (receive data)" "Manage incoming transfer requests"
.bold() .bold()
.into_right_aligned_line(), .into_right_aligned_line(),
spacer.clone(), spacer.clone(),
@ -280,23 +290,19 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
spacer.clone(), spacer.clone(),
"L".bold().into_left_aligned_line(), "L".bold().into_left_aligned_line(),
]), ]),
// misc: pop
Row::new(vec![
"Go to previous screen".bold().into_right_aligned_line(),
spacer.clone(),
"ESC".bold().into_left_aligned_line(),
]),
// misc: main menu // misc: main menu
Row::new(vec![ Row::new(vec![
"Go to the main screen".bold().into_right_aligned_line(), "Go to the main screen".bold().into_right_aligned_line(),
spacer.clone(), spacer.clone(),
"M".bold().into_left_aligned_line(), "M".bold().into_left_aligned_line(),
]), ]),
// misc: quit // misc: clear peers
Row::new(vec![ Row::new(vec![
"Quit the application".bold().into_right_aligned_line(), "Clear peers and rediscover"
.bold()
.into_right_aligned_line(),
spacer.clone(), spacer.clone(),
"Q".bold().into_left_aligned_line(), "C".bold().into_left_aligned_line(),
]), ]),
// misc: help // misc: help
Row::new(vec![ Row::new(vec![
@ -304,10 +310,27 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
spacer.clone(), spacer.clone(),
"H or ?".bold().into_left_aligned_line(), "H or ?".bold().into_left_aligned_line(),
]), ]),
// misc: pop
Row::new(vec![
"Go to previous screen".bold().into_right_aligned_line(),
spacer.clone(),
"ESC".bold().into_left_aligned_line(),
]),
// misc: quit
Row::new(vec![
"Quit the application".bold().into_right_aligned_line(),
spacer.clone(),
"Q".bold().into_left_aligned_line(),
]),
]; ];
let layout = Layout::vertical(vec![Constraint::Max(3), Constraint::Min(1)]); let layout = Layout::vertical(vec![
let [intro_area, bindings_area] = layout.areas(area); Constraint::Max(3),
Constraint::Max(12),
Constraint::Max(3),
])
.flex(Flex::SpaceAround);
let [intro_area, bindings_area, outro_area] = layout.areas(area);
let widths = vec![ let widths = vec![
Constraint::Percentage(50), Constraint::Percentage(50),
@ -322,8 +345,15 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
Clear.render(area, buf); Clear.render(area, buf);
let intro = "JocalSend is a mode-based application that responds to key-presses. Most modes support the following key bindings:".to_line().centered();
let intro = Paragraph::new(intro).wrap(Wrap { trim: true });
let outro = "Additional key bindings are available when in the sending or receiving screens, and are displayed at the bottom of the screen there.".to_line().centered();
let outro = Paragraph::new(outro).wrap(Wrap { trim: true });
intro.render(intro_area, buf); intro.render(intro_area, buf);
main_bindings.render(bindings_area, buf); main_bindings.render(bindings_area, buf);
outro.render(outro_area, buf);
} }
fn logger(area: Rect, buf: &mut Buffer) { fn logger(area: Rect, buf: &mut Buffer) {

View file

@ -32,6 +32,8 @@ impl JocalService {
log::info!("starting http server"); log::info!("starting http server");
// need to make a custom tls acceptor, see
// https://github.com/programatik29/axum-server/blob/master/examples/rustls_session.rs
axum_server::bind_rustls(addr, ssl_config) axum_server::bind_rustls(addr, ssl_config)
.handle(handle) .handle(handle)
.serve(app.into_make_service_with_connect_info::<SocketAddr>()) .serve(app.into_make_service_with_connect_info::<SocketAddr>())

View file

@ -30,7 +30,7 @@ use transfer::Session;
pub const DEFAULT_PORT: u16 = 53317; pub const DEFAULT_PORT: u16 = 53317;
pub const MULTICAST_IP: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 167); pub const MULTICAST_IP: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 167);
pub const DEFAULT_INTERVAL: Duration = Duration::from_micros(33333); pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(100);
pub type Peers = Arc<Mutex<BTreeMap<String, (SocketAddr, Device)>>>; pub type Peers = Arc<Mutex<BTreeMap<String, (SocketAddr, Device)>>>;
pub type Sessions = Arc<Mutex<BTreeMap<String, Session>>>; // Session ID to Session pub type Sessions = Arc<Mutex<BTreeMap<String, Session>>>; // Session ID to Session
@ -178,22 +178,26 @@ impl JocalService {
let service = self.clone(); let service = self.clone();
handles.spawn(async move { handles.spawn(async move {
loop { loop {
if let Err(e) = service.announce(None).await {
error!("Announcement error: {e}");
}
tokio::time::sleep(Duration::from_secs(2)).await;
let rstate = service.running_state.lock().await; let rstate = service.running_state.lock().await;
if *rstate == RunningState::Stopping { if *rstate == RunningState::Stopping {
break; break;
} }
if let Err(e) = service.announce(None).await {
error!("Announcement error: {e}");
}
tokio::time::sleep(Duration::from_secs(2)).await;
} }
JocalTasks::Udp JocalTasks::Udp
}); });
} }
pub async fn stop(&self) { pub async fn stop(&self) {
let mut rstate = self.running_state.lock().await; {
*rstate = RunningState::Stopping; let mut rstate = self.running_state.lock().await;
*rstate = RunningState::Stopping;
}
log::info!("shutting down http server"); log::info!("shutting down http server");
self.http_handle self.http_handle
.get() .get()

View file

@ -6,7 +6,7 @@ use log::{error, info};
use ratatui::DefaultTerminal; use ratatui::DefaultTerminal;
use ratatui_explorer::FileExplorer; use ratatui_explorer::FileExplorer;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use tui_logger::{LevelFilter, init_logger, set_env_filter_from_env}; use tui_logger::{LevelFilter, init_logger};
mod app; mod app;
use app::{App, CurrentScreen, Peer}; use app::{App, CurrentScreen, Peer};
@ -18,13 +18,11 @@ fn main() -> Result<()> {
// just in case we need to display the help // just in case we need to display the help
let _ = Cli::parse(); let _ = Cli::parse();
if std::env::var("RUST_LOG").is_err() {
unsafe {
std::env::set_var("RUST_LOG", "jocalsend");
}
}
init_logger(LevelFilter::Info).map_err(|e| std::io::Error::other(format!("{e}")))?; init_logger(LevelFilter::Info).map_err(|e| std::io::Error::other(format!("{e}")))?;
set_env_filter_from_env(None);
tui_logger::set_env_filter_from_string(
&std::env::var("RUST_LOG").unwrap_or("jocalsend".to_string()),
);
let config = Config::new()?; let config = Config::new()?;

View file

@ -131,7 +131,6 @@ impl JocalService {
let token = match prepare_response.files.get(&metadata.id) { let token = match prepare_response.files.get(&metadata.id) {
Some(t) => t, Some(t) => t,
None => { None => {
log::warn!("");
send_tx( send_tx(
JocalEvent::SendFailed { JocalEvent::SendFailed {
error: "missing token in prepare response from remote".into(), error: "missing token in prepare response from remote".into(),
@ -144,6 +143,15 @@ impl JocalService {
let content_id = &metadata.id; let content_id = &metadata.id;
let session_id = prepare_response.session_id; let session_id = prepare_response.session_id;
log::info!(
"sending {content_id} to {}",
peers
.lock()
.await
.get(&peer)
.map(|(_, peer)| peer.alias.as_str())
.unwrap_or("unknown peer")
);
let resp = do_send_bytes(sessions, client, &session_id, content_id, token, bytes).await; let resp = do_send_bytes(sessions, client, &session_id, content_id, token, bytes).await;
match resp { match resp {
@ -273,11 +281,7 @@ pub async fn handle_receive_upload(
let file_metadata = match session.files.get(file_id) { let file_metadata = match session.files.get(file_id) {
Some(metadata) => metadata, Some(metadata) => metadata,
None => { None => {
return ( return (StatusCode::BAD_REQUEST, "File not found".to_string()).into_response();
StatusCode::INTERNAL_SERVER_ERROR,
"File not found".to_string(),
)
.into_response();
} }
}; };
@ -285,11 +289,8 @@ pub async fn handle_receive_upload(
// Create directory if it doesn't exist // Create directory if it doesn't exist
if let Err(e) = tokio::fs::create_dir_all(download_dir).await { if let Err(e) = tokio::fs::create_dir_all(download_dir).await {
return ( log::error!("could not create download directory '{download_dir:?}', got {e}");
StatusCode::INTERNAL_SERVER_ERROR, return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response();
format!("Failed to create directory: {e}"),
)
.into_response();
} }
// Create file path // Create file path
@ -297,13 +298,14 @@ pub async fn handle_receive_upload(
// Write file // Write file
if let Err(e) = tokio::fs::write(&file_path, body).await { if let Err(e) = tokio::fs::write(&file_path, body).await {
return ( log::warn!("could not save content to {file_path:?}, got {e}");
StatusCode::INTERNAL_SERVER_ERROR, return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response();
format!("Failed to write file: {e}"),
)
.into_response();
} }
log::info!(
"saved content from {} to {file_path:?}",
&session.sender.alias
);
if let Ok(id) = Julid::from_str(session_id) { if let Ok(id) = Julid::from_str(session_id) {
service.send_event(JocalEvent::ReceivedInbound(id)); service.send_event(JocalEvent::ReceivedInbound(id));
}; };