feat: new download/upload commands to cache/sync remote or local files to the peer (#3243)

This commit is contained in:
三咲雅 misaki masa 2025-10-16 01:22:55 +08:00 committed by GitHub
parent c68e2df8c0
commit e067a705ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 407 additions and 264 deletions

238
Cargo.lock generated
View file

@ -254,7 +254,7 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
"object", "object",
"rustc-demangle", "rustc-demangle",
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -459,9 +459,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.40" version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -512,7 +512,7 @@ dependencies = [
"js-sys", "js-sys",
"num-traits", "num-traits",
"wasm-bindgen", "wasm-bindgen",
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -1008,7 +1008,7 @@ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.61.1", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1154,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.1", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1243,15 +1243,15 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.2" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@ -1381,9 +1381,9 @@ dependencies = [
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@ -1467,12 +1467,13 @@ dependencies = [
[[package]] [[package]]
name = "half" name = "half"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crunchy", "crunchy",
"zerocopy",
] ]
[[package]] [[package]]
@ -1561,7 +1562,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"windows-core 0.62.1", "windows-core 0.62.2",
] ]
[[package]] [[package]]
@ -1824,9 +1825,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.176" version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
@ -2051,9 +2052,9 @@ dependencies = [
[[package]] [[package]]
name = "moxcms" name = "moxcms"
version = "0.7.5" version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"pxfm", "pxfm",
@ -2119,11 +2120,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.1" version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -2242,9 +2243,9 @@ dependencies = [
[[package]] [[package]]
name = "objc2" name = "objc2"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [ dependencies = [
"objc2-encode", "objc2-encode",
] ]
@ -2257,9 +2258,9 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]] [[package]]
name = "objc2-foundation" name = "objc2-foundation"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
"objc2", "objc2",
@ -2453,7 +2454,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -2679,9 +2680,9 @@ dependencies = [
[[package]] [[package]]
name = "pxfm" name = "pxfm"
version = "0.1.24" version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -2887,9 +2888,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.17" version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
] ]
@ -2907,9 +2908,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.3" version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -2919,9 +2920,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -2930,9 +2931,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.6" version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
[[package]] [[package]]
name = "rfc6979" name = "rfc6979"
@ -2987,9 +2988,9 @@ dependencies = [
[[package]] [[package]]
name = "russh" name = "russh"
version = "0.54.4" version = "0.54.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b1296a0e9d970321e59d77367b8d1511311445f3dd5d42f24e7d9a1706cf51" checksum = "ca3ee9363fcf66d434d8015d9ae7d879681206981534c21bfdff8a7e34f52cca"
dependencies = [ dependencies = [
"aes", "aes",
"base64ct", "base64ct",
@ -3116,7 +3117,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.11.0", "linux-raw-sys 0.11.0",
"windows-sys 0.61.1", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -3259,9 +3260,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@ -3390,12 +3391,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -3710,14 +3711,14 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.9.7" version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",
"serde_spanned 1.0.2", "serde_spanned 1.0.3",
"toml_datetime 0.7.2", "toml_datetime 0.7.3",
"toml_parser", "toml_parser",
"toml_writer", "toml_writer",
"winnow", "winnow",
@ -3734,9 +3735,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@ -3756,18 +3757,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [ dependencies = [
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_writer" name = "toml_writer"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]] [[package]]
name = "tracing" name = "tracing"
@ -4188,7 +4189,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.61.1", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -4214,7 +4215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1"
dependencies = [ dependencies = [
"windows-core 0.59.0", "windows-core 0.59.0",
"windows-targets 0.53.4", "windows-targets 0.53.5",
] ]
[[package]] [[package]]
@ -4236,23 +4237,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce"
dependencies = [ dependencies = [
"windows-implement 0.59.0", "windows-implement 0.59.0",
"windows-interface 0.59.2", "windows-interface 0.59.3",
"windows-result 0.3.4", "windows-result 0.3.4",
"windows-strings 0.3.1", "windows-strings 0.3.1",
"windows-targets 0.53.4", "windows-targets 0.53.5",
] ]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.62.1" version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [ dependencies = [
"windows-implement 0.60.1", "windows-implement 0.60.2",
"windows-interface 0.59.2", "windows-interface 0.59.3",
"windows-link 0.2.0", "windows-link 0.2.1",
"windows-result 0.4.0", "windows-result 0.4.1",
"windows-strings 0.5.0", "windows-strings 0.5.1",
] ]
[[package]] [[package]]
@ -4279,9 +4280,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-implement" name = "windows-implement"
version = "0.60.1" version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4301,9 +4302,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-interface" name = "windows-interface"
version = "0.59.2" version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4318,9 +4319,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]] [[package]]
name = "windows-result" name = "windows-result"
@ -4342,11 +4343,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.4.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [ dependencies = [
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -4360,11 +4361,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [ dependencies = [
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -4391,16 +4392,16 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [ dependencies = [
"windows-targets 0.53.4", "windows-targets 0.53.5",
] ]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.1" version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [ dependencies = [
"windows-link 0.2.0", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@ -4421,19 +4422,19 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.4" version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [ dependencies = [
"windows-link 0.2.0", "windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.0", "windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.0", "windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.0", "windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.0", "windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.0", "windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.0", "windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.0", "windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.0", "windows_x86_64_msvc 0.53.1",
] ]
[[package]] [[package]]
@ -4444,9 +4445,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -4456,9 +4457,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -4468,9 +4469,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
@ -4480,9 +4481,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -4492,9 +4493,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -4504,9 +4505,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -4516,9 +4517,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -4528,9 +4529,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.53.0" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
@ -4678,7 +4679,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"toml 0.9.7", "toml 0.9.8",
"twox-hash", "twox-hash",
"vergen-gitcl", "vergen-gitcl",
"yazi-boot", "yazi-boot",
@ -4711,7 +4712,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"tokio", "tokio",
"toml 0.9.7", "toml 0.9.8",
"tracing", "tracing",
"yazi-codegen", "yazi-codegen",
"yazi-fs", "yazi-fs",
@ -4792,7 +4793,6 @@ name = "yazi-fm"
version = "25.9.15" version = "25.9.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64",
"better-panic", "better-panic",
"crossterm 0.29.0", "crossterm 0.29.0",
"fdlimit", "fdlimit",
@ -4834,7 +4834,6 @@ version = "25.9.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
"base64",
"bitflags 2.9.4", "bitflags 2.9.4",
"core-foundation-sys", "core-foundation-sys",
"dirs", "dirs",
@ -4850,9 +4849,8 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"trash", "trash",
"twox-hash",
"uzers", "uzers",
"windows-sys 0.61.1", "windows-sys 0.61.2",
"yazi-ffi", "yazi-ffi",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
@ -4909,7 +4907,7 @@ dependencies = [
"twox-hash", "twox-hash",
"unicode-width 0.2.0", "unicode-width 0.2.0",
"uzers", "uzers",
"windows-sys 0.61.1", "windows-sys 0.61.2",
"yazi-adapter", "yazi-adapter",
"yazi-binding", "yazi-binding",
"yazi-boot", "yazi-boot",
@ -5002,7 +5000,7 @@ dependencies = [
"serde", "serde",
"tokio", "tokio",
"uzers", "uzers",
"windows-sys 0.61.1", "windows-sys 0.61.2",
"yazi-macro", "yazi-macro",
] ]
@ -5022,7 +5020,7 @@ dependencies = [
"libc", "libc",
"parking_lot", "parking_lot",
"tracing", "tracing",
"windows-sys 0.61.1", "windows-sys 0.61.2",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
] ]
@ -5039,7 +5037,6 @@ dependencies = [
"russh", "russh",
"tokio", "tokio",
"tracing", "tracing",
"uzers",
"yazi-config", "yazi-config",
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
@ -5052,7 +5049,6 @@ name = "yazi-watcher"
version = "25.9.15" version = "25.9.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64",
"hashbrown 0.16.0", "hashbrown 0.16.0",
"notify", "notify",
"parking_lot", "parking_lot",
@ -5060,11 +5056,11 @@ dependencies = [
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"twox-hash",
"yazi-adapter", "yazi-adapter",
"yazi-dds", "yazi-dds",
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-proxy",
"yazi-shared", "yazi-shared",
"yazi-vfs", "yazi-vfs",
] ]

View file

@ -33,7 +33,7 @@ futures = "0.3.31"
globset = "0.4.16" globset = "0.4.16"
hashbrown = { version = "0.16.0", features = [ "serde" ] } hashbrown = { version = "0.16.0", features = [ "serde" ] }
indexmap = { version = "2.11.4", features = [ "serde" ] } indexmap = { version = "2.11.4", features = [ "serde" ] }
libc = "0.2.176" libc = "0.2.177"
lru = "0.16.1" lru = "0.16.1"
mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
objc = "0.2.7" objc = "0.2.7"
@ -42,8 +42,8 @@ parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.11.3" regex = "1.12.1"
russh = { version = "0.54.4", default-features = false, features = [ "ring", "rsa" ] } russh = { version = "0.54.5", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.145" serde_json = "1.0.145"
@ -51,7 +51,7 @@ syntect = { version = "5.3.0", default-features = false, features =
tokio = { version = "1.47.1", features = [ "full" ] } tokio = { version = "1.47.1", features = [ "full" ] }
tokio-stream = "0.1.17" tokio-stream = "0.1.17"
tokio-util = "0.7.16" tokio-util = "0.7.16"
toml = { version = "0.9.7" } toml = { version = "0.9.8" }
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] } tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
unicode-width = { version = "0.2.0", default-features = false } unicode-width = { version = "0.2.0", default-features = false }

View file

@ -1,4 +1,4 @@
use std::{ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref}; use std::{ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use crossterm::{execute, style::Print}; use crossterm::{execute, style::Print};
@ -11,7 +11,7 @@ use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider
use yazi_macro::{err, succ}; use yazi_macro::{err, succ};
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER, TasksProxy}; use yazi_proxy::{AppProxy, HIDER, TasksProxy};
use yazi_shared::{OsStrJoin, data::Data, terminal_clear, url::{Component, Url, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{OsStrJoin, data::Data, terminal_clear, url::{AsUrl, Component, UrlBuf, UrlCow, UrlLike}};
use yazi_term::tty::TTY; use yazi_term::tty::TTY;
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -53,7 +53,7 @@ impl Actor for BulkRename {
defer! { tokio::spawn(Local.remove_file(tmp.clone())); } defer! { tokio::spawn(Local.remove_file(tmp.clone())); }
TasksProxy::process_exec( TasksProxy::process_exec(
cwd, cwd,
Splatter::new(&[UrlCow::default(), Url::regular(&tmp).into()]).splat(&opener.run), Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()], vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
opener.block, opener.block,
opener.orphan, opener.orphan,
@ -150,7 +150,7 @@ impl BulkRename {
} }
fn opener() -> Option<&'static OpenerRule> { fn opener() -> Option<&'static OpenerRule> {
YAZI.opener.block(YAZI.open.all(Url::regular("bulk-rename.txt"), "text/plain")) YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain"))
} }
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> { async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {

View file

@ -50,6 +50,7 @@ yazi_macro::mod_flat!(
update_peeked update_peeked
update_spotted update_spotted
update_yanked update_yanked
upload
visual_mode visual_mode
watch watch
yank yank

View file

@ -0,0 +1,21 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::mgr::UploadOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Upload;
impl Actor for Upload {
type Options = UploadOpt;
const NAME: &str = "upload";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
for url in opt.urls {
cx.tasks.scheduler.file_upload(url.into_owned());
}
succ!();
}
}

View file

@ -41,12 +41,6 @@ impl From<Url> for yazi_shared::url::UrlBuf {
fn from(value: Url) -> Self { value.inner } fn from(value: Url) -> Self { value.inner }
} }
impl<'a> From<&'a Url> for UrlCow<'a> {
fn from(value: &'a Url) -> Self {
UrlCow::Borrowed { loc: value.loc.as_loc(), scheme: value.scheme.as_ref().into() }
}
}
impl From<Url> for yazi_shared::url::UrlBufCov { impl From<Url> for yazi_shared::url::UrlBufCov {
fn from(value: Url) -> Self { Self(value.inner) } fn from(value: Url) -> Self { Self(value.inner) }
} }

View file

@ -1,9 +1,9 @@
use std::{env, ffi::OsStr, fmt::Write}; use std::{env, ffi::OsStr, fmt::Write, path::Path};
use regex::Regex; use regex::Regex;
use yazi_adapter::Mux; use yazi_adapter::Mux;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_shared::{timestamp_us, url::Url}; use yazi_shared::timestamp_us;
use super::Actions; use super::Actions;
@ -59,17 +59,17 @@ impl Actions {
writeln!( writeln!(
s, s,
" default : {:?}", " default : {:?}",
YAZI.opener.first(YAZI.open.all(Url::regular("f75a.txt"), "text/plain")) YAZI.opener.first(YAZI.open.all(Path::new("f75a.txt"), "text/plain"))
)?; )?;
writeln!( writeln!(
s, s,
" block-create: {:?}", " block-create: {:?}",
YAZI.opener.block(YAZI.open.all(Url::regular("bulk-create.txt"), "text/plain")) YAZI.opener.block(YAZI.open.all(Path::new("bulk-create.txt"), "text/plain"))
)?; )?;
writeln!( writeln!(
s, s,
" block-rename: {:?}", " block-rename: {:?}",
YAZI.opener.block(YAZI.open.all(Url::regular("bulk-rename.txt"), "text/plain")) YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain"))
)?; )?;
writeln!(s, "\nMultiplexers")?; writeln!(s, "\nMultiplexers")?;

View file

@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_fs::{Xdg, path::expand_url}; use yazi_fs::{Xdg, path::expand_url};
use yazi_shared::{SStr, timestamp_us, url::Url}; use yazi_shared::{SStr, timestamp_us};
use super::PreviewWrap; use super::PreviewWrap;
@ -50,7 +50,7 @@ impl Preview {
self.cache_dir = if self.cache_dir.as_os_str().is_empty() { self.cache_dir = if self.cache_dir.as_os_str().is_empty() {
Xdg::cache_dir().to_owned() Xdg::cache_dir().to_owned()
} else if let Some(p) = expand_url(Url::regular(&self.cache_dir)).into_path() { } else if let Some(p) = expand_url(self.cache_dir).into_path() {
p p
} else { } else {
bail!("[preview].cache_dir must be a path within local filesystem."); bail!("[preview].cache_dir must be a path within local filesystem.");

View file

@ -2,7 +2,6 @@ use std::{io, path::PathBuf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use yazi_fs::path::expand_url; use yazi_fs::path::expand_url;
use yazi_shared::url::Url;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")] #[serde(tag = "type", rename_all = "kebab-case")]
@ -45,7 +44,7 @@ pub struct ProviderSftp {
impl ProviderSftp { impl ProviderSftp {
fn reshape(&mut self) -> io::Result<()> { fn reshape(&mut self) -> io::Result<()> {
if !self.key_file.as_os_str().is_empty() { if !self.key_file.as_os_str().is_empty() {
self.key_file = expand_url(Url::regular(&self.key_file)) self.key_file = expand_url(&self.key_file)
.into_path() .into_path()
.ok_or_else(|| io::Error::other("key_file must be a path within local filesystem"))?; .ok_or_else(|| io::Error::other("key_file must be a path within local filesystem"))?;
} }
@ -56,7 +55,7 @@ impl ProviderSftp {
.filter(|p| p.is_absolute()) .filter(|p| p.is_absolute())
.unwrap_or_default() .unwrap_or_default()
} else { } else {
expand_url(Url::regular(&self.identity_agent)) expand_url(&self.identity_agent)
.into_path() .into_path()
.ok_or_else(|| io::Error::other("identity_agent must be a path within local filesystem"))? .ok_or_else(|| io::Error::other("identity_agent must be a path within local filesystem"))?
}; };

View file

@ -67,6 +67,7 @@ pub enum Spark<'a> {
UpdatePeeked(yazi_parser::mgr::UpdatePeekedOpt), UpdatePeeked(yazi_parser::mgr::UpdatePeekedOpt),
UpdateSpotted(yazi_parser::mgr::UpdateSpottedOpt), UpdateSpotted(yazi_parser::mgr::UpdateSpottedOpt),
UpdateYanked(yazi_parser::mgr::UpdateYankedOpt<'a>), UpdateYanked(yazi_parser::mgr::UpdateYankedOpt<'a>),
Upload(yazi_parser::mgr::UploadOpt),
VisualMode(yazi_parser::mgr::VisualModeOpt), VisualMode(yazi_parser::mgr::VisualModeOpt),
Watch(yazi_parser::VoidOpt), Watch(yazi_parser::VoidOpt),
Yank(yazi_parser::mgr::YankOpt), Yank(yazi_parser::mgr::YankOpt),
@ -189,6 +190,7 @@ impl<'a> IntoLua for Spark<'a> {
Self::UpdatePeeked(b) => b.into_lua(lua), Self::UpdatePeeked(b) => b.into_lua(lua),
Self::UpdateSpotted(b) => b.into_lua(lua), Self::UpdateSpotted(b) => b.into_lua(lua),
Self::UpdateYanked(b) => b.into_lua(lua), Self::UpdateYanked(b) => b.into_lua(lua),
Self::Upload(b) => b.into_lua(lua),
Self::VisualMode(b) => b.into_lua(lua), Self::VisualMode(b) => b.into_lua(lua),
Self::Watch(b) => b.into_lua(lua), Self::Watch(b) => b.into_lua(lua),
Self::Yank(b) => b.into_lua(lua), Self::Yank(b) => b.into_lua(lua),
@ -315,6 +317,7 @@ try_from_spark!(mgr::UpdatePagedOpt, mgr:update_paged);
try_from_spark!(mgr::UpdatePeekedOpt, mgr:update_peeked); try_from_spark!(mgr::UpdatePeekedOpt, mgr:update_peeked);
try_from_spark!(mgr::UpdateSpottedOpt, mgr:update_spotted); try_from_spark!(mgr::UpdateSpottedOpt, mgr:update_spotted);
try_from_spark!(mgr::UpdateYankedOpt<'a>, mgr:update_yanked); try_from_spark!(mgr::UpdateYankedOpt<'a>, mgr:update_yanked);
try_from_spark!(mgr::UploadOpt, mgr:upload);
try_from_spark!(mgr::VisualModeOpt, mgr:visual_mode); try_from_spark!(mgr::VisualModeOpt, mgr:visual_mode);
try_from_spark!(mgr::YankOpt, mgr:yank); try_from_spark!(mgr::YankOpt, mgr:yank);
try_from_spark!(notify::TickOpt, notify:tick); try_from_spark!(notify::TickOpt, notify:tick);

View file

@ -42,7 +42,6 @@ yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" }
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" } yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }
# External dependencies # External dependencies
base64 = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
better-panic = "0.3.0" better-panic = "0.3.0"
crossterm = { workspace = true } crossterm = { workspace = true }

View file

@ -136,6 +136,7 @@ impl<'a> Executor<'a> {
// VFS // VFS
on!(download); on!(download);
on!(upload);
match cmd.name.as_ref() { match cmd.name.as_ref() {
// Help // Help

View file

@ -17,7 +17,6 @@ yazi-shim = { path = "../yazi-shim", version = "25.9.15" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
arc-swap = "1.7.1" arc-swap = "1.7.1"
base64 = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
dirs = { workspace = true } dirs = { workspace = true }
foldhash = { workspace = true } foldhash = { workspace = true }
@ -29,14 +28,13 @@ scopeguard = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
twox-hash = { workspace = true }
[target."cfg(unix)".dependencies] [target."cfg(unix)".dependencies]
libc = { workspace = true } libc = { workspace = true }
uzers = { workspace = true } uzers = { workspace = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.1", features = [ "Win32_Storage_FileSystem" ] } windows-sys = { version = "0.61.2", features = [ "Win32_Storage_FileSystem" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-foundation-sys = { workspace = true } core-foundation-sys = { workspace = true }

View file

@ -54,6 +54,7 @@ impl Hash for File {
self.url.hash(state); self.url.hash(state);
self.cha.len.hash(state); self.cha.len.hash(state);
self.cha.btime.hash(state); self.cha.btime.hash(state);
self.cha.ctime.hash(state);
self.cha.mtime.hash(state); self.cha.mtime.hash(state);
} }
} }

View file

@ -1,13 +1,13 @@
use std::{borrow::Cow, path::{Path, PathBuf}}; use std::{borrow::Cow, path::{Path, PathBuf}};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{loc::LocBuf, url::{UrlBuf, UrlCow, UrlLike}};
pub fn path_relative_to<'a>( pub fn path_relative_to<'a>(
from: impl AsRef<Path>, from: impl AsRef<Path>,
to: &'a impl AsRef<Path>, to: &'a impl AsRef<Path>,
) -> Result<Cow<'a, Path>> { ) -> Result<Cow<'a, Path>> {
Ok(match url_relative_to(Url::regular(&from).into(), Url::regular(to).into())? { Ok(match url_relative_to(from.as_ref().into(), to.as_ref().into())? {
UrlCow::Borrowed { loc, .. } => Cow::Borrowed(loc.as_path()), UrlCow::Borrowed { loc, .. } => Cow::Borrowed(loc.as_path()),
UrlCow::Owned { loc, .. } => Cow::Owned(loc.into_path()), UrlCow::Owned { loc, .. } => Cow::Owned(loc.into_path()),
}) })

View file

@ -38,6 +38,7 @@ yazi_macro::mod_flat!(
update_peeked update_peeked
update_spotted update_spotted
update_yanked update_yanked
upload
visual_mode visual_mode
yank yank
); );

View file

@ -0,0 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::UrlCow};
#[derive(Debug, Default)]
pub struct UploadOpt {
pub urls: Vec<UrlCow<'static>>,
}
impl From<CmdCow> for UploadOpt {
fn from(mut c: CmdCow) -> Self { Self { urls: c.take_seq() } }
}
impl FromLua for UploadOpt {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
}
impl IntoLua for UploadOpt {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
}

View file

@ -51,7 +51,7 @@ libc = { workspace = true }
uzers = { workspace = true } uzers = { workspace = true }
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]
windows-sys = { version = "0.61.1", features = [ "Win32_System_JobObjects" ] } windows-sys = { version = "0.61.2", features = [ "Win32_System_JobObjects" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -2,7 +2,8 @@ local M = {}
function M:peek(job) function M:peek(job)
local cmd = os.getenv("YAZI_FILE_ONE") or "file" local cmd = os.getenv("YAZI_FILE_ONE") or "file"
local output, err = Command(cmd):arg({ "-bL", "--", tostring(job.file.url) }):stdout(Command.PIPED):output() local path = tostring(job.file.cache or job.file.url)
local output, err = Command(cmd):arg({ "-bL", "--", path }):stdout(Command.PIPED):output()
local text local text
if output then if output then

View file

@ -55,6 +55,13 @@ impl MgrProxy {
} }
pub fn update_paged_by(page: usize, only_if: &UrlBuf) { pub fn update_paged_by(page: usize, only_if: &UrlBuf) {
emit!(Call(relay!(mgr:update_paged, [page]).with_any("only-if", only_if.clone()))); emit!(Call(relay!(mgr:update_paged, [page]).with("only-if", only_if)));
}
pub fn upload<I>(urls: I)
where
I: IntoIterator<Item = UrlBuf>,
{
emit!(Call(relay!(mgr:upload).with_seq(urls)));
} }
} }

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::VecDeque, hash::{BuildHasher, Hash, Hasher}, path::{Path, PathBuf}}; use std::{borrow::Cow, collections::VecDeque, hash::{BuildHasher, Hash, Hasher}};
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
@ -6,11 +6,11 @@ use tracing::warn;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::{FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}}; use yazi_fs::{FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found; use yazi_macro::ok_or_not_found;
use yazi_shared::{timestamp_us, url::{AsUrl, Url, UrlCow, UrlLike}}; use yazi_shared::{timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name}; use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
use crate::{LOW, NORMAL, TaskIn, TaskOp, TaskOps, file::{FileInDownload, FileInUpload, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutPaste, FileOutPasteDo, FileOutTrash, FileOutUpload, FileOutUploadDo}}; use crate::{LOW, NORMAL, TaskIn, TaskOp, TaskOps, file::{FileInDownload, FileInUpload, FileInUploadDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutPaste, FileOutPasteDo, FileOutTrash, FileOutUpload, FileOutUploadDo}};
pub(crate) struct File { pub(crate) struct File {
ops: TaskOps, ops: TaskOps,
@ -353,11 +353,11 @@ impl File {
let cache = task.url.cache().context("Cannot determine cache path")?; let cache = task.url.cache().context("Cannot determine cache path")?;
let cache_tmp = Self::tmp(&cache).await?; let cache_tmp = Self::tmp(&cache).await?;
let mut it = copy_with_progress(&task.url, Url::regular(&cache_tmp), cha); let mut it = copy_with_progress(&task.url, &cache_tmp, cha);
while let Some(res) = it.recv().await { while let Some(res) = it.recv().await {
match res { match res {
Ok(0) => { Ok(0) => {
Local.rename(&cache_tmp, &cache).await?; provider::rename(&cache_tmp, &cache).await?;
let lock = task.url.cache_lock().context("Cannot determine cache lock")?; let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
Local.write(lock, format!("{:x}", cha.hash_u128())).await?; Local.write(lock, format!("{:x}", cha.hash_u128())).await?;
@ -385,66 +385,98 @@ impl File {
Ok(self.ops.out(task.id, FileOutDownloadDo::Succ)) Ok(self.ops.out(task.id, FileOutDownloadDo::Succ))
} }
pub(crate) async fn upload(&self, mut task: FileInUpload) -> Result<(), FileOutUpload> { pub(crate) async fn upload(&self, task: FileInUpload) -> Result<(), FileOutUpload> {
todo!(); let cha = Self::cha(&task.url, true, None).await?;
// if task.cha.is_none() { if cha.is_orphan() {
// task.cha = Some(Self::cha(Url::regular(&task.path), true, None).await?); Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?;
// } }
// let cha = task.cha.unwrap(); if !cha.is_dir() {
// if cha.is_orphan() { let cache = task.url.cache().context("Cannot determine cache path")?;
// Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?; match Self::cha(&cache, true, None).await {
// } Ok(c) if c.mtime == cha.mtime => {}
Ok(c) => {
self.ops.out(task.id, FileOutUpload::New(c.len));
self.queue(FileInUploadDo { id: task.id, url: task.url, cha, cache }, LOW);
}
Err(e) if e.kind() == NotFound => {}
Err(e) => Err(e)?,
};
return Ok(self.ops.out(task.id, FileOutUpload::Succ));
}
// if !cha.is_dir() { macro_rules! continue_unless_ok {
// let id = task.id; ($result:expr) => {
// self.ops.out(id, FileOutUpload::New(cha.len)); match $result {
// self.queue(task, LOW); Ok(v) => v,
// return Ok(self.ops.out(id, FileOutUpload::Succ)); Err(e) => {
// } self.ops.out(task.id, FileOutUpload::Deform(e.to_string()));
continue;
}
}
};
}
// macro_rules! continue_unless_ok { let mut dirs = VecDeque::from([task.url]);
// ($result:expr) => { while let Some(src) = dirs.pop_front() {
// match $result { let mut it = continue_unless_ok!(provider::read_dir(&src).await);
// Ok(v) => v, while let Ok(Some(entry)) = it.next().await {
// Err(e) => { let from = entry.url();
// self.ops.out(task.id, FileOutUpload::Deform(e.to_string())); let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await);
// continue;
// }
// }
// };
// }
// let mut dirs = VecDeque::from([task.path.clone()]); if cha.is_orphan() {
// while let Some(src) = dirs.pop_front() { continue_unless_ok!(Err("Source of symlink doesn't exist"));
// let cache = continue_unless_ok!(src.cache().ok_or("Cannot determine } else if cha.is_dir() {
// cache path")); continue_unless_ok!(match dirs.push_back(from);
// Local.create_dir(&cache).await { Err(e) if e.kind() != AlreadyExists continue;
// => Err(e), _ => Ok(()), }
// });
// let mut it = continue_unless_ok!(provider::read_dir(&src).await); let cache = continue_unless_ok!(from.cache().ok_or("Cannot determine cache path"));
// while let Ok(Some(entry)) = it.next().await { let cache_cha = continue_unless_ok!(match Self::cha(&cache, true, None).await {
// let from = entry.url(); Ok(c) if c.mtime == cha.mtime => continue,
// let cha = continue_unless_ok!(Self::cha(&from, true, Ok(c) => Ok(c),
// Some(entry)).await); Err(e) if e.kind() == NotFound => continue,
Err(e) => Err(e),
});
// if cha.is_orphan() { self.ops.out(task.id, FileOutUpload::New(cache_cha.len));
// continue_unless_ok!(Err("Source of symlink doesn't exist")); self.queue(FileInUploadDo { id: task.id, url: from, cha, cache }, LOW);
// } else if cha.is_dir() { }
// dirs.push_back(from); }
// } else {
// self.ops.out(task.id, FileOutUpload::New(cha.len));
// self.queue(task.spawn(from, cha), LOW);
// }
// }
// }
// Ok(self.ops.out(task.id, FileOutUpload::Succ)) Ok(self.ops.out(task.id, FileOutUpload::Succ))
} }
pub(crate) async fn upload_do(&self, mut task: FileInUpload) -> Result<(), FileOutUploadDo> { pub(crate) async fn upload_do(&self, task: FileInUploadDo) -> Result<(), FileOutUploadDo> {
todo!() let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
let hash = Local.read_to_string(&lock).await.context("Cannot read cache lock")?;
let hash = u128::from_str_radix(&hash, 16).context("Cannot parse hash from lock")?;
if hash != task.cha.hash_u128() {
Err(anyhow!("Remote file has changed since last download"))?;
}
let tmp = Self::tmp(&task.url).await?;
let mut it = copy_with_progress(&task.cache, &tmp, task.cha);
while let Some(res) = it.recv().await {
match res {
Ok(0) if hash != Self::cha(&task.url, true, None).await?.hash_u128() => {
Err(anyhow!("Remote file has changed during upload"))?
}
Ok(0) => {
provider::rename(&tmp, &task.url).await?;
let hash = format!("{:x}", Self::cha(&task.url, true, None).await?.hash_u128());
Local.write(lock, hash).await?;
break;
}
Ok(n) => self.ops.out(task.id, FileOutUploadDo::Adv(n)),
Err(e) => Err(e)?,
}
}
Ok(self.ops.out(task.id, FileOutUploadDo::Succ))
} }
async fn cha<U>(url: U, follow: bool, entry: Option<DirEntry>) -> io::Result<Cha> async fn cha<U>(url: U, follow: bool, entry: Option<DirEntry>) -> io::Result<Cha>
@ -459,17 +491,20 @@ impl File {
Ok(if follow { Cha::from_follow(url, cha).await } else { cha }) Ok(if follow { Cha::from_follow(url, cha).await } else { cha })
} }
async fn tmp(path: &Path) -> io::Result<PathBuf> { async fn tmp<U>(url: U) -> io::Result<UrlBuf>
let Some(parent) = path.parent() else { where
Err(io::Error::new(io::ErrorKind::InvalidInput, "Path has no parent"))? U: AsUrl,
{
let url = url.as_url();
let Some(parent) = url.parent() else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Url has no parent"))?
}; };
let mut h = foldhash::fast::FixedState::default().build_hasher(); let mut h = foldhash::fast::FixedState::default().build_hasher();
path.hash(&mut h); url.hash(&mut h);
timestamp_us().hash(&mut h); timestamp_us().hash(&mut h);
let u = parent.join(format!(".{:x}.%tmp", h.finish())).into(); unique_name(parent.join(format!(".{:x}.%tmp", h.finish())), async { false }).await
Ok(unique_name(u, async { false }).await?.into_path().expect("a path"))
} }
} }

View file

@ -102,13 +102,15 @@ impl FileInDownload {
// --- Upload // --- Upload
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct FileInUpload { pub(crate) struct FileInUpload {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) path: PathBuf, pub(crate) url: UrlBuf,
pub(crate) cha: Option<Cha>,
} }
impl FileInUpload { // --- UploadDo
pub(super) fn spawn(&self, path: PathBuf, cha: Cha) -> Self { #[derive(Clone, Debug)]
Self { id: self.id, path, cha: Some(cha) } pub(crate) struct FileInUploadDo {
} pub(crate) id: Id,
pub(crate) url: UrlBuf,
pub(crate) cha: Cha,
pub(crate) cache: PathBuf,
} }

View file

@ -354,6 +354,10 @@ impl From<std::io::Error> for FileOutUpload {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
} }
impl From<anyhow::Error> for FileOutUpload {
fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }
}
impl FileOutUpload { impl FileOutUpload {
pub(crate) fn reduce(self, task: &mut Task) { pub(crate) fn reduce(self, task: &mut Task) {
let TaskProg::FileUpload(prog) = &mut task.prog else { return }; let TaskProg::FileUpload(prog) = &mut task.prog else { return };
@ -382,7 +386,6 @@ impl FileOutUpload {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum FileOutUploadDo { pub(crate) enum FileOutUploadDo {
Adv(u64), Adv(u64),
Log(String),
Succ, Succ,
Fail(String), Fail(String),
} }
@ -402,9 +405,6 @@ impl FileOutUploadDo {
Self::Adv(size) => { Self::Adv(size) => {
prog.processed_bytes += size; prog.processed_bytes += size;
} }
Self::Log(line) => {
task.log(line);
}
Self::Succ => { Self::Succ => {
prog.success_files += 1; prog.success_files += 1;
} }

View file

@ -1,6 +1,6 @@
use yazi_shared::Id; use yazi_shared::Id;
use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUpload}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}}; use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUpload, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}};
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum TaskIn { pub(crate) enum TaskIn {
@ -12,10 +12,9 @@ pub(crate) enum TaskIn {
FileTrash(FileInTrash), FileTrash(FileInTrash),
FileDownload(FileInDownload), FileDownload(FileInDownload),
FileUpload(FileInUpload), FileUpload(FileInUpload),
FileUploadDo(FileInUploadDo),
// Plugin // Plugin
PluginEntry(PluginInEntry), PluginEntry(PluginInEntry),
// Prework // Prework
PreworkFetch(PreworkInFetch), PreworkFetch(PreworkInFetch),
PreworkLoad(PreworkInLoad), PreworkLoad(PreworkInLoad),
@ -24,7 +23,7 @@ pub(crate) enum TaskIn {
impl_from_in! { impl_from_in! {
// File // File
FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload), FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload), FileUploadDo(FileInUploadDo),
// Plugin // Plugin
PluginEntry(PluginInEntry), PluginEntry(PluginInEntry),
// Prework // Prework
@ -42,6 +41,7 @@ impl TaskIn {
Self::FileTrash(r#in) => r#in.id, Self::FileTrash(r#in) => r#in.id,
Self::FileDownload(r#in) => r#in.id, Self::FileDownload(r#in) => r#in.id,
Self::FileUpload(r#in) => r#in.id, Self::FileUpload(r#in) => r#in.id,
Self::FileUploadDo(r#in) => r#in.id,
// Plugin // Plugin
Self::PluginEntry(r#in) => r#in.id, Self::PluginEntry(r#in) => r#in.id,
// Prework // Prework

View file

@ -6,14 +6,13 @@ use parking_lot::Mutex;
use tokio::{select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use tokio::{select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_fs::FsUrl;
use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt}; use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}}; use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}};
use yazi_vfs::{must_be_dir, provider, unique_name}; use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp}; use super::{Ongoing, TaskOp};
use crate::{HIGH, LOW, NORMAL, TaskIn, TaskOps, TaskOut, file::{File, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileOutDelete, FileOutDownload, FileOutHardlink, FileOutPaste, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}}; use crate::{HIGH, LOW, NORMAL, TaskIn, TaskOps, TaskOut, file::{File, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUpload, FileOutDelete, FileOutDownload, FileOutHardlink, FileOutPaste, FileOutUpload, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash, FileProgUpload}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
pub struct Scheduler { pub struct Scheduler {
file: Arc<File>, file: Arc<File>,
@ -226,6 +225,18 @@ impl Scheduler {
}); });
} }
pub fn file_upload(&self, url: UrlBuf) {
let mut ongoing = self.ongoing.lock();
let id = ongoing.add::<FileProgUpload>(format!("Upload {}", url.display()));
if !url.scheme.is_virtual() {
return self.ops.out(id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned()));
};
let file = self.file.clone();
self.send_micro(id, LOW, async move { file.upload(FileInUpload { id, url }).await });
}
pub fn plugin_micro(&self, opt: PluginOpt) { pub fn plugin_micro(&self, opt: PluginOpt) {
let id = self.ongoing.lock().add::<PluginProgEntry>(format!("Run micro plugin `{}`", opt.id)); let id = self.ongoing.lock().add::<PluginProgEntry>(format!("Run micro plugin `{}`", opt.id));
@ -398,7 +409,8 @@ impl Scheduler {
TaskIn::FileDelete(r#in) => file.delete_do(r#in).await.map_err(Into::into), TaskIn::FileDelete(r#in) => file.delete_do(r#in).await.map_err(Into::into),
TaskIn::FileTrash(r#in) => file.trash_do(r#in).await.map_err(Into::into), TaskIn::FileTrash(r#in) => file.trash_do(r#in).await.map_err(Into::into),
TaskIn::FileDownload(r#in) => file.download_do(r#in).await.map_err(Into::into), TaskIn::FileDownload(r#in) => file.download_do(r#in).await.map_err(Into::into),
TaskIn::FileUpload(r#in) => file.upload_do(r#in).await.map_err(Into::into), TaskIn::FileUpload(r#in) => file.upload(r#in).await.map_err(Into::into),
TaskIn::FileUploadDo(r#in) => file.upload_do(r#in).await.map_err(Into::into),
// Plugin // Plugin
TaskIn::PluginEntry(r#in) => plugin.macro_do(r#in).await.map_err(Into::into), TaskIn::PluginEntry(r#in) => plugin.macro_do(r#in).await.map_err(Into::into),
// Prework // Prework

View file

@ -30,7 +30,7 @@ libc = { workspace = true }
uzers = { workspace = true } uzers = { workspace = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.1", features = [ "Win32_UI_Shell" ] } windows-sys = { version = "0.61.2", features = [ "Win32_UI_Shell" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -98,6 +98,10 @@ impl From<Id> for Data {
fn from(value: Id) -> Self { Self::Id(value) } fn from(value: Id) -> Self { Self::Id(value) }
} }
impl From<UrlBuf> for Data {
fn from(value: UrlBuf) -> Self { Self::Url(value) }
}
impl From<&UrlBuf> for Data { impl From<&UrlBuf> for Data {
fn from(value: &UrlBuf) -> Self { Self::Url(value.clone()) } fn from(value: &UrlBuf) -> Self { Self::Url(value.clone()) }
} }

View file

@ -65,9 +65,13 @@ impl Cmd {
self self
} }
pub fn with_opt(mut self, name: impl Into<DataKey>, value: Option<impl Into<Data>>) -> Self { pub fn with_seq<I>(mut self, values: I) -> Self
if let Some(v) = value { where
self.args.insert(name.into(), v.into()); I: IntoIterator,
I::Item: Into<Data>,
{
for (i, v) in values.into_iter().enumerate() {
self.args.insert(DataKey::Integer(i as i64), v.into());
} }
self self
} }

View file

@ -19,12 +19,11 @@ impl<'a> From<Url<'a>> for UrlCow<'a> {
fn from(value: Url<'a>) -> Self { Self::Borrowed { loc: value.loc, scheme: value.scheme.into() } } fn from(value: Url<'a>) -> Self { Self::Borrowed { loc: value.loc, scheme: value.scheme.into() } }
} }
impl<'a> From<&'a UrlBuf> for UrlCow<'a> { impl<'a, T> From<&'a T> for UrlCow<'a>
fn from(value: &'a UrlBuf) -> Self { value.as_url().into() } where
} T: AsUrl + ?Sized,
{
impl<'a> From<&'a UrlCow<'a>> for UrlCow<'a> { fn from(value: &'a T) -> Self { value.as_url().into() }
fn from(value: &'a UrlCow<'a>) -> Self { value.as_url().into() }
} }
impl From<UrlBuf> for UrlCow<'_> { impl From<UrlBuf> for UrlCow<'_> {

View file

@ -1,12 +1,32 @@
use std::{borrow::Cow, ffi::OsStr, path::Path}; use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
use crate::url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}; use crate::{scheme::SchemeRef, url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}};
// --- AsUrl // --- AsUrl
pub trait AsUrl { pub trait AsUrl {
fn as_url(&self) -> Url<'_>; fn as_url(&self) -> Url<'_>;
} }
impl AsUrl for Path {
#[inline]
fn as_url(&self) -> Url<'_> { Url { loc: self.into(), scheme: SchemeRef::Regular } }
}
impl AsUrl for &Path {
#[inline]
fn as_url(&self) -> Url<'_> { (*self).as_url() }
}
impl AsUrl for PathBuf {
#[inline]
fn as_url(&self) -> Url<'_> { self.as_path().as_url() }
}
impl AsUrl for &PathBuf {
#[inline]
fn as_url(&self) -> Url<'_> { (*self).as_path().as_url() }
}
impl AsUrl for Url<'_> { impl AsUrl for Url<'_> {
#[inline] #[inline]
fn as_url(&self) -> Url<'_> { *self } fn as_url(&self) -> Url<'_> { *self }
@ -42,11 +62,17 @@ impl AsUrl for &UrlCow<'_> {
fn as_url(&self) -> Url<'_> { (**self).as_url() } fn as_url(&self) -> Url<'_> { (**self).as_url() }
} }
impl<'a, T: AsUrl> From<&'a T> for Url<'a> { impl<'a, T> From<&'a T> for Url<'a>
where
T: AsUrl + ?Sized,
{
fn from(value: &'a T) -> Self { value.as_url() } fn from(value: &'a T) -> Self { value.as_url() }
} }
impl<'a, T: AsUrl> From<&'a mut T> for Url<'a> { impl<'a, T> From<&'a mut T> for Url<'a>
where
T: AsUrl + ?Sized,
{
fn from(value: &'a mut T) -> Self { value.as_url() } fn from(value: &'a mut T) -> Self { value.as_url() }
} }
@ -96,4 +122,6 @@ where
fn urn(&self) -> &Urn { self.as_url().urn() } fn urn(&self) -> &Urn { self.as_url().urn() }
} }
impl<T: AsUrl> UrlLike for T {} impl UrlLike for Url<'_> {}
impl UrlLike for UrlBuf {}
impl UrlLike for UrlCow<'_> {}

View file

@ -26,4 +26,4 @@ libc = { workspace = true }
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.1", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] } windows-sys = { version = "0.61.2", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] }

View file

@ -24,6 +24,3 @@ parking_lot = { workspace = true }
russh = { workspace = true } russh = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[target."cfg(unix)".dependencies]
uzers = { workspace = true }

View file

@ -13,6 +13,7 @@ yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" }
yazi-dds = { path = "../yazi-dds", version = "25.9.15" } yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
@ -21,11 +22,9 @@ tracing = { workspace = true }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
base64 = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
notify = { version = "8.2.0", default-features = false, features = [ "macos_fsevent" ] } notify = { version = "8.2.0", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = { workspace = true } parking_lot = { workspace = true }
percent-encoding = { workspace = true } percent-encoding = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-stream = { workspace = true } tokio-stream = { workspace = true }
twox-hash = { workspace = true }

View file

@ -40,7 +40,7 @@ impl Linked {
pub fn from_file(&self, url: Url) -> Vec<PathBuf> { pub fn from_file(&self, url: Url) -> Vec<PathBuf> {
let Some(path) = url.as_path() else { return vec![] }; let Some(path) = url.as_path() else { return vec![] };
if let Some((parent, name)) = path.parent().zip(path.file_name()) { if let Some((parent, name)) = path.parent().zip(path.file_name()) {
self.from_dir(Url::regular(parent)).map(|p| p.join(name)).collect() self.from_dir(parent).map(|p| p.join(name)).collect()
} else { } else {
vec![] vec![]
} }
@ -51,7 +51,7 @@ impl Linked {
let watched = watched.read(); let watched = watched.read();
// Remove entries that are no longer watched // Remove entries that are no longer watched
linked.write().retain(|from, _| watched.contains(Url::regular(from))); linked.write().retain(|from, _| watched.contains(from));
// Update existing entries and remove broken links // Update existing entries and remove broken links
for from in watched.paths() { for from in watched.paths() {

View file

@ -1,10 +1,13 @@
use std::time::Duration; use std::time::{Duration, SystemTime};
use anyhow::Result; use anyhow::Result;
use hashbrown::HashSet; use hashbrown::HashSet;
use tokio::{pin, sync::mpsc::UnboundedReceiver}; use tokio::{pin, sync::mpsc::UnboundedReceiver};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::{File, FilesOp};
use yazi_proxy::MgrProxy;
use yazi_shared::url::{Url, UrlBuf, UrlLike}; use yazi_shared::url::{Url, UrlBuf, UrlLike};
use yazi_vfs::VfsFile;
use crate::{Reporter, WATCHER}; use crate::{Reporter, WATCHER};
@ -27,14 +30,30 @@ impl Remote {
while let Some(chunk) = rx.next().await { while let Some(chunk) = rx.next().await {
let urls: HashSet<_> = chunk.into_iter().collect(); let urls: HashSet<_> = chunk.into_iter().collect();
let _permit = WATCHER.acquire().await.unwrap(); let _permit = WATCHER.acquire().await.unwrap();
let mut ops = Vec::with_capacity(urls.len());
let mut ups = Vec::with_capacity(urls.len());
for u in urls { for u in urls {
let Some((parent, urn)) = u.pair() else { continue }; let Some((parent, urn)) = u.pair() else { continue };
let Ok(mut file) = File::new(&u).await else {
ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
continue;
};
// FIXME let is_file = file.is_file();
tracing::debug!("Remote changed: {}", u.display()); file.cha.ctime = Some(SystemTime::now());
ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));
if is_file {
ups.push(u);
}
} }
FilesOp::mutate(ops);
MgrProxy::upload(ups);
} }
} }
} }

View file

@ -33,15 +33,18 @@ impl Reporter {
let watched = WATCHED.read(); let watched = WATCHED.read();
for parent in [parent].into_iter().chain(linked) { for parent in [parent].into_iter().chain(linked) {
if watched.contains(parent) {
self.local_tx.send(url.to_owned()).ok();
self.local_tx.send(parent.to_owned()).ok();
}
if name.extension() == Some("%tmp".as_ref()) {
continue;
}
// SFTP caches // SFTP caches
if let Some(dir) = watched.find_by_cache(&parent.loc) { if let Some(dir) = watched.find_by_cache(&parent.loc) {
self.remote_tx.send(dir.join(name)).ok(); self.remote_tx.send(dir.join(name)).ok();
self.remote_tx.send(dir.to_owned()).ok(); self.remote_tx.send(dir.to_owned()).ok();
} }
if watched.contains(parent) {
self.local_tx.send(url.to_owned()).ok();
self.local_tx.send(parent.to_owned()).ok();
}
} }
} }