From e067a705acd1323b3292a83fa09f28973703c41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Thu, 16 Oct 2025 01:22:55 +0800 Subject: [PATCH] feat: new `download`/`upload` commands to cache/sync remote or local files to the peer (#3243) --- Cargo.lock | 238 ++++++++++++++-------------- Cargo.toml | 8 +- yazi-actor/src/mgr/bulk_rename.rs | 8 +- yazi-actor/src/mgr/mod.rs | 1 + yazi-actor/src/mgr/upload.rs | 21 +++ yazi-binding/src/url.rs | 6 - yazi-boot/src/actions/debug.rs | 10 +- yazi-config/src/preview/preview.rs | 4 +- yazi-config/src/vfs/provider.rs | 5 +- yazi-dds/src/spark/spark.rs | 3 + yazi-fm/Cargo.toml | 1 - yazi-fm/src/executor.rs | 1 + yazi-fs/Cargo.toml | 4 +- yazi-fs/src/file.rs | 1 + yazi-fs/src/path/relative.rs | 4 +- yazi-parser/src/mgr/mod.rs | 1 + yazi-parser/src/mgr/upload.rs | 19 +++ yazi-plugin/Cargo.toml | 2 +- yazi-plugin/preset/plugins/file.lua | 3 +- yazi-proxy/src/mgr.rs | 9 +- yazi-scheduler/src/file/file.rs | 159 +++++++++++-------- yazi-scheduler/src/file/in.rs | 16 +- yazi-scheduler/src/file/out.rs | 8 +- yazi-scheduler/src/in.rs | 8 +- yazi-scheduler/src/scheduler.rs | 18 ++- yazi-shared/Cargo.toml | 2 +- yazi-shared/src/data/data.rs | 4 + yazi-shared/src/event/cmd.rs | 10 +- yazi-shared/src/url/cow.rs | 11 +- yazi-shared/src/url/traits.rs | 38 ++++- yazi-term/Cargo.toml | 2 +- yazi-vfs/Cargo.toml | 3 - yazi-watcher/Cargo.toml | 3 +- yazi-watcher/src/local/linked.rs | 4 +- yazi-watcher/src/remote/remote.rs | 25 ++- yazi-watcher/src/reporter.rs | 11 +- 36 files changed, 407 insertions(+), 264 deletions(-) create mode 100644 yazi-actor/src/mgr/upload.rs create mode 100644 yazi-parser/src/mgr/upload.rs diff --git a/Cargo.lock b/Cargo.lock index 3de89a67..c4dbe3d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -512,7 +512,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -1008,7 +1008,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1154,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1243,15 +1243,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -1381,9 +1381,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1467,12 +1467,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1561,7 +1562,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.1", + "windows-core 0.62.2", ] [[package]] @@ -1824,9 +1825,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libfuzzer-sys" @@ -2051,9 +2052,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40" dependencies = [ "num-traits", "pxfm", @@ -2119,11 +2120,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2242,9 +2243,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", ] @@ -2257,9 +2258,9 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.9.4", "objc2", @@ -2453,7 +2454,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -2679,9 +2680,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] @@ -2887,9 +2888,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -2907,9 +2908,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" dependencies = [ "aho-corasick", "memchr", @@ -2919,9 +2920,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" dependencies = [ "aho-corasick", "memchr", @@ -2930,9 +2931,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" [[package]] name = "rfc6979" @@ -2987,9 +2988,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.54.4" +version = "0.54.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b1296a0e9d970321e59d77367b8d1511311445f3dd5d42f24e7d9a1706cf51" +checksum = "ca3ee9363fcf66d434d8015d9ae7d879681206981534c21bfdff8a7e34f52cca" dependencies = [ "aes", "base64ct", @@ -3116,7 +3117,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3259,9 +3260,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -3390,12 +3391,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3710,14 +3711,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", "toml_parser", "toml_writer", "winnow", @@ -3734,9 +3735,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] @@ -3756,18 +3757,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tracing" @@ -4188,7 +4189,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -4214,7 +4215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ "windows-core 0.59.0", - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] @@ -4236,23 +4237,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ "windows-implement 0.59.0", - "windows-interface 0.59.2", + "windows-interface 0.59.3", "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-core" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.1", - "windows-interface 0.59.2", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -4279,9 +4280,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -4301,9 +4302,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -4318,9 +4319,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" @@ -4342,11 +4343,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -4360,11 +4361,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -4391,16 +4392,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -4421,19 +4422,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.0", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -4444,9 +4445,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -4456,9 +4457,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -4468,9 +4469,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -4480,9 +4481,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -4492,9 +4493,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -4504,9 +4505,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -4516,9 +4517,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -4528,9 +4529,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -4678,7 +4679,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml 0.9.7", + "toml 0.9.8", "twox-hash", "vergen-gitcl", "yazi-boot", @@ -4711,7 +4712,7 @@ dependencies = [ "regex", "serde", "tokio", - "toml 0.9.7", + "toml 0.9.8", "tracing", "yazi-codegen", "yazi-fs", @@ -4792,7 +4793,6 @@ name = "yazi-fm" version = "25.9.15" dependencies = [ "anyhow", - "base64", "better-panic", "crossterm 0.29.0", "fdlimit", @@ -4834,7 +4834,6 @@ version = "25.9.15" dependencies = [ "anyhow", "arc-swap", - "base64", "bitflags 2.9.4", "core-foundation-sys", "dirs", @@ -4850,9 +4849,8 @@ dependencies = [ "tokio", "tracing", "trash", - "twox-hash", "uzers", - "windows-sys 0.61.1", + "windows-sys 0.61.2", "yazi-ffi", "yazi-macro", "yazi-shared", @@ -4909,7 +4907,7 @@ dependencies = [ "twox-hash", "unicode-width 0.2.0", "uzers", - "windows-sys 0.61.1", + "windows-sys 0.61.2", "yazi-adapter", "yazi-binding", "yazi-boot", @@ -5002,7 +5000,7 @@ dependencies = [ "serde", "tokio", "uzers", - "windows-sys 0.61.1", + "windows-sys 0.61.2", "yazi-macro", ] @@ -5022,7 +5020,7 @@ dependencies = [ "libc", "parking_lot", "tracing", - "windows-sys 0.61.1", + "windows-sys 0.61.2", "yazi-macro", "yazi-shared", ] @@ -5039,7 +5037,6 @@ dependencies = [ "russh", "tokio", "tracing", - "uzers", "yazi-config", "yazi-fs", "yazi-macro", @@ -5052,7 +5049,6 @@ name = "yazi-watcher" version = "25.9.15" dependencies = [ "anyhow", - "base64", "hashbrown 0.16.0", "notify", "parking_lot", @@ -5060,11 +5056,11 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "twox-hash", "yazi-adapter", "yazi-dds", "yazi-fs", "yazi-macro", + "yazi-proxy", "yazi-shared", "yazi-vfs", ] diff --git a/Cargo.toml b/Cargo.toml index 53570d40..792cddd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ futures = "0.3.31" globset = "0.4.16" hashbrown = { version = "0.16.0", features = [ "serde" ] } indexmap = { version = "2.11.4", features = [ "serde" ] } -libc = "0.2.176" +libc = "0.2.177" lru = "0.16.1" mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } objc = "0.2.7" @@ -42,8 +42,8 @@ parking_lot = "0.12.5" paste = "1.0.15" percent-encoding = "2.3.2" ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } -regex = "1.11.3" -russh = { version = "0.54.4", default-features = false, features = [ "ring", "rsa" ] } +regex = "1.12.1" +russh = { version = "0.54.5", default-features = false, features = [ "ring", "rsa" ] } scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } 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-stream = "0.1.17" 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" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] } unicode-width = { version = "0.2.0", default-features = false } diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index 71340eb2..c8c841f5 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -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 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_parser::VoidOpt; 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_vfs::{VfsFile, maybe_exists, provider}; use yazi_watcher::WATCHER; @@ -53,7 +53,7 @@ impl Actor for BulkRename { defer! { tokio::spawn(Local.remove_file(tmp.clone())); } TasksProxy::process_exec( 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()], opener.block, opener.orphan, @@ -150,7 +150,7 @@ impl BulkRename { } 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<()> { diff --git a/yazi-actor/src/mgr/mod.rs b/yazi-actor/src/mgr/mod.rs index 49f9b3d4..29842efb 100644 --- a/yazi-actor/src/mgr/mod.rs +++ b/yazi-actor/src/mgr/mod.rs @@ -50,6 +50,7 @@ yazi_macro::mod_flat!( update_peeked update_spotted update_yanked + upload visual_mode watch yank diff --git a/yazi-actor/src/mgr/upload.rs b/yazi-actor/src/mgr/upload.rs new file mode 100644 index 00000000..4e01e6b3 --- /dev/null +++ b/yazi-actor/src/mgr/upload.rs @@ -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 { + for url in opt.urls { + cx.tasks.scheduler.file_upload(url.into_owned()); + } + succ!(); + } +} diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index 163c32d6..77405eaa 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -41,12 +41,6 @@ impl From for yazi_shared::url::UrlBuf { 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 for yazi_shared::url::UrlBufCov { fn from(value: Url) -> Self { Self(value.inner) } } diff --git a/yazi-boot/src/actions/debug.rs b/yazi-boot/src/actions/debug.rs index 414486d7..b64302f8 100644 --- a/yazi-boot/src/actions/debug.rs +++ b/yazi-boot/src/actions/debug.rs @@ -1,9 +1,9 @@ -use std::{env, ffi::OsStr, fmt::Write}; +use std::{env, ffi::OsStr, fmt::Write, path::Path}; use regex::Regex; use yazi_adapter::Mux; use yazi_config::YAZI; -use yazi_shared::{timestamp_us, url::Url}; +use yazi_shared::timestamp_us; use super::Actions; @@ -59,17 +59,17 @@ impl Actions { writeln!( s, " 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!( s, " 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!( s, " 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")?; diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index c8ab411a..18c7b613 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use yazi_codegen::DeserializeOver2; use yazi_fs::{Xdg, path::expand_url}; -use yazi_shared::{SStr, timestamp_us, url::Url}; +use yazi_shared::{SStr, timestamp_us}; use super::PreviewWrap; @@ -50,7 +50,7 @@ impl Preview { self.cache_dir = if self.cache_dir.as_os_str().is_empty() { 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 } else { bail!("[preview].cache_dir must be a path within local filesystem."); diff --git a/yazi-config/src/vfs/provider.rs b/yazi-config/src/vfs/provider.rs index 24f5b5f0..841b47a6 100644 --- a/yazi-config/src/vfs/provider.rs +++ b/yazi-config/src/vfs/provider.rs @@ -2,7 +2,6 @@ use std::{io, path::PathBuf}; use serde::{Deserialize, Serialize}; use yazi_fs::path::expand_url; -use yazi_shared::url::Url; #[derive(Deserialize, Serialize)] #[serde(tag = "type", rename_all = "kebab-case")] @@ -45,7 +44,7 @@ pub struct ProviderSftp { impl ProviderSftp { fn reshape(&mut self) -> io::Result<()> { 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() .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()) .unwrap_or_default() } else { - expand_url(Url::regular(&self.identity_agent)) + expand_url(&self.identity_agent) .into_path() .ok_or_else(|| io::Error::other("identity_agent must be a path within local filesystem"))? }; diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index 8d8853f0..992f58c9 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -67,6 +67,7 @@ pub enum Spark<'a> { UpdatePeeked(yazi_parser::mgr::UpdatePeekedOpt), UpdateSpotted(yazi_parser::mgr::UpdateSpottedOpt), UpdateYanked(yazi_parser::mgr::UpdateYankedOpt<'a>), + Upload(yazi_parser::mgr::UploadOpt), VisualMode(yazi_parser::mgr::VisualModeOpt), Watch(yazi_parser::VoidOpt), Yank(yazi_parser::mgr::YankOpt), @@ -189,6 +190,7 @@ impl<'a> IntoLua for Spark<'a> { Self::UpdatePeeked(b) => b.into_lua(lua), Self::UpdateSpotted(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::Watch(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::UpdateSpottedOpt, mgr:update_spotted); 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::YankOpt, mgr:yank); try_from_spark!(notify::TickOpt, notify:tick); diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 58d6ae98..1f20ca23 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -42,7 +42,6 @@ yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" } yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" } # External dependencies -base64 = { workspace = true } anyhow = { workspace = true } better-panic = "0.3.0" crossterm = { workspace = true } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 905f73ee..60a8b2f2 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -136,6 +136,7 @@ impl<'a> Executor<'a> { // VFS on!(download); + on!(upload); match cmd.name.as_ref() { // Help diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index 41925c1b..49c7fa1c 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -17,7 +17,6 @@ yazi-shim = { path = "../yazi-shim", version = "25.9.15" } # External dependencies anyhow = { workspace = true } arc-swap = "1.7.1" -base64 = { workspace = true } bitflags = { workspace = true } dirs = { workspace = true } foldhash = { workspace = true } @@ -29,14 +28,13 @@ scopeguard = { workspace = true } serde = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } -twox-hash = { workspace = true } [target."cfg(unix)".dependencies] libc = { workspace = true } uzers = { workspace = true } [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] core-foundation-sys = { workspace = true } diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index f322058f..123376dc 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -54,6 +54,7 @@ impl Hash for File { self.url.hash(state); self.cha.len.hash(state); self.cha.btime.hash(state); + self.cha.ctime.hash(state); self.cha.mtime.hash(state); } } diff --git a/yazi-fs/src/path/relative.rs b/yazi-fs/src/path/relative.rs index fcf1ddb0..d7dc51fd 100644 --- a/yazi-fs/src/path/relative.rs +++ b/yazi-fs/src/path/relative.rs @@ -1,13 +1,13 @@ use std::{borrow::Cow, path::{Path, PathBuf}}; 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>( from: impl AsRef, to: &'a impl AsRef, ) -> Result> { - 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::Owned { loc, .. } => Cow::Owned(loc.into_path()), }) diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index 3b10bc99..caec442e 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -38,6 +38,7 @@ yazi_macro::mod_flat!( update_peeked update_spotted update_yanked + upload visual_mode yank ); diff --git a/yazi-parser/src/mgr/upload.rs b/yazi-parser/src/mgr/upload.rs new file mode 100644 index 00000000..2c876ddc --- /dev/null +++ b/yazi-parser/src/mgr/upload.rs @@ -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>, +} + +impl From for UploadOpt { + fn from(mut c: CmdCow) -> Self { Self { urls: c.take_seq() } } +} + +impl FromLua for UploadOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for UploadOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 5ee182c4..2af7df35 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -51,7 +51,7 @@ libc = { workspace = true } uzers = { workspace = true } [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] crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-plugin/preset/plugins/file.lua b/yazi-plugin/preset/plugins/file.lua index f1663260..64fc7774 100644 --- a/yazi-plugin/preset/plugins/file.lua +++ b/yazi-plugin/preset/plugins/file.lua @@ -2,7 +2,8 @@ local M = {} function M:peek(job) 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 if output then diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index d29958a9..6acc6a65 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -55,6 +55,13 @@ impl MgrProxy { } 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(urls: I) + where + I: IntoIterator, + { + emit!(Call(relay!(mgr:upload).with_seq(urls))); } } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 000fa719..043f5e36 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -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 tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; @@ -6,11 +6,11 @@ use tracing::warn; 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_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 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 { ops: TaskOps, @@ -353,11 +353,11 @@ impl File { let cache = task.url.cache().context("Cannot determine cache path")?; 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 { match res { 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")?; Local.write(lock, format!("{:x}", cha.hash_u128())).await?; @@ -385,66 +385,98 @@ impl File { Ok(self.ops.out(task.id, FileOutDownloadDo::Succ)) } - pub(crate) async fn upload(&self, mut task: FileInUpload) -> Result<(), FileOutUpload> { - todo!(); - // if task.cha.is_none() { - // task.cha = Some(Self::cha(Url::regular(&task.path), true, None).await?); - // } + pub(crate) async fn upload(&self, task: FileInUpload) -> Result<(), FileOutUpload> { + let cha = Self::cha(&task.url, true, None).await?; + if cha.is_orphan() { + Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?; + } - // let cha = task.cha.unwrap(); - // if cha.is_orphan() { - // Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?; - // } + if !cha.is_dir() { + let cache = task.url.cache().context("Cannot determine cache path")?; + 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() { - // let id = task.id; - // self.ops.out(id, FileOutUpload::New(cha.len)); - // self.queue(task, LOW); - // return Ok(self.ops.out(id, FileOutUpload::Succ)); - // } + macro_rules! continue_unless_ok { + ($result:expr) => { + match $result { + Ok(v) => v, + Err(e) => { + self.ops.out(task.id, FileOutUpload::Deform(e.to_string())); + continue; + } + } + }; + } - // macro_rules! continue_unless_ok { - // ($result:expr) => { - // match $result { - // Ok(v) => v, - // Err(e) => { - // self.ops.out(task.id, FileOutUpload::Deform(e.to_string())); - // continue; - // } - // } - // }; - // } + let mut dirs = VecDeque::from([task.url]); + while let Some(src) = dirs.pop_front() { + let mut it = continue_unless_ok!(provider::read_dir(&src).await); + while let Ok(Some(entry)) = it.next().await { + let from = entry.url(); + let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await); - // let mut dirs = VecDeque::from([task.path.clone()]); - // while let Some(src) = dirs.pop_front() { - // let cache = continue_unless_ok!(src.cache().ok_or("Cannot determine - // cache path")); continue_unless_ok!(match - // Local.create_dir(&cache).await { Err(e) if e.kind() != AlreadyExists - // => Err(e), _ => Ok(()), - // }); + if cha.is_orphan() { + continue_unless_ok!(Err("Source of symlink doesn't exist")); + } else if cha.is_dir() { + dirs.push_back(from); + continue; + } - // let mut it = continue_unless_ok!(provider::read_dir(&src).await); - // while let Ok(Some(entry)) = it.next().await { - // let from = entry.url(); - // let cha = continue_unless_ok!(Self::cha(&from, true, - // Some(entry)).await); + let cache = continue_unless_ok!(from.cache().ok_or("Cannot determine cache path")); + let cache_cha = continue_unless_ok!(match Self::cha(&cache, true, None).await { + Ok(c) if c.mtime == cha.mtime => continue, + Ok(c) => Ok(c), + Err(e) if e.kind() == NotFound => continue, + Err(e) => Err(e), + }); - // if cha.is_orphan() { - // continue_unless_ok!(Err("Source of symlink doesn't exist")); - // } 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); - // } - // } - // } + self.ops.out(task.id, FileOutUpload::New(cache_cha.len)); + self.queue(FileInUploadDo { id: task.id, url: from, cha, cache }, 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> { - todo!() + pub(crate) async fn upload_do(&self, task: FileInUploadDo) -> Result<(), FileOutUploadDo> { + 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(url: U, follow: bool, entry: Option) -> io::Result @@ -459,17 +491,20 @@ impl File { Ok(if follow { Cha::from_follow(url, cha).await } else { cha }) } - async fn tmp(path: &Path) -> io::Result { - let Some(parent) = path.parent() else { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Path has no parent"))? + async fn tmp(url: U) -> io::Result + where + 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(); - path.hash(&mut h); + url.hash(&mut h); timestamp_us().hash(&mut h); - let u = parent.join(format!(".{:x}.%tmp", h.finish())).into(); - Ok(unique_name(u, async { false }).await?.into_path().expect("a path")) + unique_name(parent.join(format!(".{:x}.%tmp", h.finish())), async { false }).await } } diff --git a/yazi-scheduler/src/file/in.rs b/yazi-scheduler/src/file/in.rs index 7b4069db..f56b07c3 100644 --- a/yazi-scheduler/src/file/in.rs +++ b/yazi-scheduler/src/file/in.rs @@ -102,13 +102,15 @@ impl FileInDownload { // --- Upload #[derive(Clone, Debug)] pub(crate) struct FileInUpload { - pub(crate) id: Id, - pub(crate) path: PathBuf, - pub(crate) cha: Option, + pub(crate) id: Id, + pub(crate) url: UrlBuf, } -impl FileInUpload { - pub(super) fn spawn(&self, path: PathBuf, cha: Cha) -> Self { - Self { id: self.id, path, cha: Some(cha) } - } +// --- UploadDo +#[derive(Clone, Debug)] +pub(crate) struct FileInUploadDo { + pub(crate) id: Id, + pub(crate) url: UrlBuf, + pub(crate) cha: Cha, + pub(crate) cache: PathBuf, } diff --git a/yazi-scheduler/src/file/out.rs b/yazi-scheduler/src/file/out.rs index 95bdb090..1d193163 100644 --- a/yazi-scheduler/src/file/out.rs +++ b/yazi-scheduler/src/file/out.rs @@ -354,6 +354,10 @@ impl From for FileOutUpload { fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } } +impl From for FileOutUpload { + fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) } +} + impl FileOutUpload { pub(crate) fn reduce(self, task: &mut Task) { let TaskProg::FileUpload(prog) = &mut task.prog else { return }; @@ -382,7 +386,6 @@ impl FileOutUpload { #[derive(Debug)] pub(crate) enum FileOutUploadDo { Adv(u64), - Log(String), Succ, Fail(String), } @@ -402,9 +405,6 @@ impl FileOutUploadDo { Self::Adv(size) => { prog.processed_bytes += size; } - Self::Log(line) => { - task.log(line); - } Self::Succ => { prog.success_files += 1; } diff --git a/yazi-scheduler/src/in.rs b/yazi-scheduler/src/in.rs index 57605e47..8487577d 100644 --- a/yazi-scheduler/src/in.rs +++ b/yazi-scheduler/src/in.rs @@ -1,6 +1,6 @@ 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)] pub(crate) enum TaskIn { @@ -12,10 +12,9 @@ pub(crate) enum TaskIn { FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload), - + FileUploadDo(FileInUploadDo), // Plugin PluginEntry(PluginInEntry), - // Prework PreworkFetch(PreworkInFetch), PreworkLoad(PreworkInLoad), @@ -24,7 +23,7 @@ pub(crate) enum TaskIn { impl_from_in! { // 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 PluginEntry(PluginInEntry), // Prework @@ -42,6 +41,7 @@ impl TaskIn { Self::FileTrash(r#in) => r#in.id, Self::FileDownload(r#in) => r#in.id, Self::FileUpload(r#in) => r#in.id, + Self::FileUploadDo(r#in) => r#in.id, // Plugin Self::PluginEntry(r#in) => r#in.id, // Prework diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index c68d3a28..2460c61a 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -6,14 +6,13 @@ use parking_lot::Mutex; use tokio::{select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_dds::Pump; -use yazi_fs::FsUrl; use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt}; use yazi_proxy::TasksProxy; use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}}; use yazi_vfs::{must_be_dir, provider, unique_name}; 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 { file: Arc, @@ -226,6 +225,18 @@ impl Scheduler { }); } + pub fn file_upload(&self, url: UrlBuf) { + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add::(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) { let id = self.ongoing.lock().add::(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::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::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 TaskIn::PluginEntry(r#in) => plugin.macro_do(r#in).await.map_err(Into::into), // Prework diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 61d8c882..ec42c45a 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -30,7 +30,7 @@ libc = { workspace = true } uzers = { workspace = true } [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] crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-shared/src/data/data.rs b/yazi-shared/src/data/data.rs index fc8c28ce..ba8ab6db 100644 --- a/yazi-shared/src/data/data.rs +++ b/yazi-shared/src/data/data.rs @@ -98,6 +98,10 @@ impl From for Data { fn from(value: Id) -> Self { Self::Id(value) } } +impl From for Data { + fn from(value: UrlBuf) -> Self { Self::Url(value) } +} + impl From<&UrlBuf> for Data { fn from(value: &UrlBuf) -> Self { Self::Url(value.clone()) } } diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index 259351ee..9bc9c08f 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -65,9 +65,13 @@ impl Cmd { self } - pub fn with_opt(mut self, name: impl Into, value: Option>) -> Self { - if let Some(v) = value { - self.args.insert(name.into(), v.into()); + pub fn with_seq(mut self, values: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + for (i, v) in values.into_iter().enumerate() { + self.args.insert(DataKey::Integer(i as i64), v.into()); } self } diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 8474bde5..8f482655 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -19,12 +19,11 @@ impl<'a> From> for UrlCow<'a> { fn from(value: Url<'a>) -> Self { Self::Borrowed { loc: value.loc, scheme: value.scheme.into() } } } -impl<'a> From<&'a UrlBuf> for UrlCow<'a> { - fn from(value: &'a UrlBuf) -> Self { value.as_url().into() } -} - -impl<'a> From<&'a UrlCow<'a>> for UrlCow<'a> { - fn from(value: &'a UrlCow<'a>) -> Self { value.as_url().into() } +impl<'a, T> From<&'a T> for UrlCow<'a> +where + T: AsUrl + ?Sized, +{ + fn from(value: &'a T) -> Self { value.as_url().into() } } impl From for UrlCow<'_> { diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index 08cd2be3..c2030f9a 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -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 pub trait AsUrl { 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<'_> { #[inline] fn as_url(&self) -> Url<'_> { *self } @@ -42,11 +62,17 @@ impl AsUrl for &UrlCow<'_> { 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() } } -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() } } @@ -96,4 +122,6 @@ where fn urn(&self) -> &Urn { self.as_url().urn() } } -impl UrlLike for T {} +impl UrlLike for Url<'_> {} +impl UrlLike for UrlBuf {} +impl UrlLike for UrlCow<'_> {} diff --git a/yazi-term/Cargo.toml b/yazi-term/Cargo.toml index a9261b2b..a75c8946 100644 --- a/yazi-term/Cargo.toml +++ b/yazi-term/Cargo.toml @@ -26,4 +26,4 @@ libc = { workspace = true } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } [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" ] } diff --git a/yazi-vfs/Cargo.toml b/yazi-vfs/Cargo.toml index 6161f26d..4e755ae8 100644 --- a/yazi-vfs/Cargo.toml +++ b/yazi-vfs/Cargo.toml @@ -24,6 +24,3 @@ parking_lot = { workspace = true } russh = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } - -[target."cfg(unix)".dependencies] -uzers = { workspace = true } diff --git a/yazi-watcher/Cargo.toml b/yazi-watcher/Cargo.toml index e8d086da..00a6aa2b 100644 --- a/yazi-watcher/Cargo.toml +++ b/yazi-watcher/Cargo.toml @@ -13,6 +13,7 @@ yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" } yazi-dds = { path = "../yazi-dds", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", 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-vfs = { path = "../yazi-vfs", version = "25.9.15" } @@ -21,11 +22,9 @@ tracing = { workspace = true } # External dependencies anyhow = { workspace = true } -base64 = { workspace = true } hashbrown = { workspace = true } notify = { version = "8.2.0", default-features = false, features = [ "macos_fsevent" ] } parking_lot = { workspace = true } percent-encoding = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } -twox-hash = { workspace = true } diff --git a/yazi-watcher/src/local/linked.rs b/yazi-watcher/src/local/linked.rs index db2944f2..e8c9afb5 100644 --- a/yazi-watcher/src/local/linked.rs +++ b/yazi-watcher/src/local/linked.rs @@ -40,7 +40,7 @@ impl Linked { pub fn from_file(&self, url: Url) -> Vec { let Some(path) = url.as_path() else { return vec![] }; 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 { vec![] } @@ -51,7 +51,7 @@ impl Linked { let watched = watched.read(); // 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 for from in watched.paths() { diff --git a/yazi-watcher/src/remote/remote.rs b/yazi-watcher/src/remote/remote.rs index 2ad480f8..dde45220 100644 --- a/yazi-watcher/src/remote/remote.rs +++ b/yazi-watcher/src/remote/remote.rs @@ -1,10 +1,13 @@ -use std::time::Duration; +use std::time::{Duration, SystemTime}; use anyhow::Result; use hashbrown::HashSet; use tokio::{pin, sync::mpsc::UnboundedReceiver}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; +use yazi_fs::{File, FilesOp}; +use yazi_proxy::MgrProxy; use yazi_shared::url::{Url, UrlBuf, UrlLike}; +use yazi_vfs::VfsFile; use crate::{Reporter, WATCHER}; @@ -27,14 +30,30 @@ impl Remote { while let Some(chunk) = rx.next().await { let urls: HashSet<_> = chunk.into_iter().collect(); + 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 { 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 - tracing::debug!("Remote changed: {}", u.display()); + let is_file = file.is_file(); + 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); } } } diff --git a/yazi-watcher/src/reporter.rs b/yazi-watcher/src/reporter.rs index 4fc8733c..e85e1526 100644 --- a/yazi-watcher/src/reporter.rs +++ b/yazi-watcher/src/reporter.rs @@ -33,15 +33,18 @@ impl Reporter { let watched = WATCHED.read(); 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 if let Some(dir) = watched.find_by_cache(&parent.loc) { self.remote_tx.send(dir.join(name)).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(); - } } }