From 782ecb16b260613f554e566ef9910debd9649af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Mon, 20 Oct 2025 13:48:57 +0800 Subject: [PATCH] feat!: live editing of remote files (#3264) --- Cargo.lock | 242 +++++++++++++++++------- Cargo.toml | 14 +- yazi-actor/src/mgr/bulk_rename.rs | 2 +- yazi-actor/src/mgr/download.rs | 56 ++++-- yazi-actor/src/mgr/open.rs | 2 +- yazi-actor/src/mgr/open_do.rs | 4 +- yazi-actor/src/mgr/shell.rs | 10 +- yazi-adapter/src/adapter.rs | 8 +- yazi-adapter/src/drivers/chafa.rs | 4 +- yazi-adapter/src/drivers/iip.rs | 4 +- yazi-adapter/src/drivers/kgp.rs | 4 +- yazi-adapter/src/drivers/kgp_old.rs | 4 +- yazi-adapter/src/drivers/sixel.rs | 4 +- yazi-adapter/src/drivers/ueberzug.rs | 11 +- yazi-adapter/src/image.rs | 9 +- yazi-adapter/src/info.rs | 5 +- yazi-binding/src/lib.rs | 2 +- yazi-binding/src/scheme.rs | 31 +++ yazi-binding/src/url.rs | 8 +- yazi-boot/Cargo.toml | 4 +- yazi-boot/src/args.rs | 2 +- yazi-cli/Cargo.toml | 4 +- yazi-cli/src/args.rs | 2 +- yazi-codegen/Cargo.toml | 2 +- yazi-config/src/theme/icon.rs | 4 +- yazi-fm/Cargo.toml | 2 +- yazi-fs/src/cha/cha.rs | 16 +- yazi-fs/src/cha/mode.rs | 9 + yazi-fs/src/cwd.rs | 17 +- yazi-fs/src/provider/attrs.rs | 53 ++++++ yazi-fs/src/provider/local/gate.rs | 8 +- yazi-fs/src/provider/local/local.rs | 30 +-- yazi-fs/src/provider/local/metadata.rs | 40 ---- yazi-fs/src/provider/local/mod.rs | 2 +- yazi-fs/src/provider/mod.rs | 2 +- yazi-fs/src/provider/traits.rs | 6 +- yazi-fs/src/url.rs | 4 +- yazi-parser/src/mgr/open.rs | 16 +- yazi-parser/src/mgr/open_do.rs | 4 +- yazi-parser/src/tasks/process_open.rs | 4 +- yazi-plugin/preset/plugins/mime-dir.lua | 6 +- yazi-plugin/src/external/highlighter.rs | 7 +- yazi-plugin/src/utils/image.rs | 17 +- yazi-plugin/src/utils/preview.rs | 8 +- yazi-proxy/src/mgr.rs | 6 +- yazi-proxy/src/tasks.rs | 2 +- yazi-scheduler/src/file/file.rs | 36 ++-- yazi-scheduler/src/file/out.rs | 32 ++-- yazi-scheduler/src/in.rs | 6 +- yazi-scheduler/src/process/in.rs | 8 +- yazi-scheduler/src/process/shell.rs | 4 +- yazi-scheduler/src/scheduler.rs | 5 +- yazi-sftp/src/operator.rs | 15 ++ yazi-sftp/src/packet.rs | 6 + yazi-sftp/src/requests/extended.rs | 21 ++ yazi-vfs/src/fns.rs | 15 +- yazi-vfs/src/provider/gate.rs | 8 +- yazi-vfs/src/provider/provider.rs | 15 +- yazi-vfs/src/provider/providers.rs | 8 +- yazi-vfs/src/provider/rw_file.rs | 12 +- yazi-vfs/src/provider/sftp/gate.rs | 14 +- yazi-vfs/src/provider/sftp/metadata.rs | 22 ++- yazi-vfs/src/provider/sftp/sftp.rs | 22 ++- yazi-watcher/src/local/local.rs | 10 +- 64 files changed, 621 insertions(+), 339 deletions(-) create mode 100644 yazi-binding/src/scheme.rs create mode 100644 yazi-fs/src/provider/attrs.rs delete mode 100644 yazi-fs/src/provider/local/metadata.rs diff --git a/Cargo.lock b/Cargo.lock index c4dbe3d0..2ef35382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -527,9 +527,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", "clap_derive", @@ -537,9 +537,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a" +checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c" dependencies = [ "clap", ] @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce" +checksum = "811159f339691baacdf7d534df2946b9d217014081099e23d31d887d99521e70" dependencies = [ "clap", "clap_complete", @@ -578,9 +578,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -590,9 +590,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "clipboard-win" @@ -671,6 +671,17 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-models" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94950e87ea550d6d68f1993f3e7bebc8cb7235157bff84337d46195c3aa0b3f0" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.2", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1399,20 +1410,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] @@ -1443,9 +1454,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "globset" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305" dependencies = [ "aho-corasick", "bstr", @@ -1467,9 +1478,9 @@ dependencies = [ [[package]] name = "half" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", @@ -1499,6 +1510,43 @@ dependencies = [ "serde", ] +[[package]] +name = "hax-lib" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "heck" version = "0.5.0" @@ -1620,9 +1668,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1718,17 +1766,6 @@ dependencies = [ "syn", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1774,7 +1811,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -1829,6 +1866,59 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libcrux-intrinsics" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3b41dcbc21a5fb7efbbb5af7405b2e79c4bfe443924e90b13afc0080318d31" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d368d3e8d6a74e277178d54921eca112a1e6b7837d7d8bc555091acb5d817f5" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "rand 0.9.2", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332737e629fe6ba7547f5c0f90559eac865d5dbecf98138ffae8f16ab8cbe33f" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d95de4257eafdfaf3bffecadb615219b0ca920c553722b3646d32dde76c797" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", +] + [[package]] name = "libfuzzer-sys" version = "0.4.10" @@ -1908,11 +1998,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -1997,7 +2087,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -2474,6 +2564,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -2803,7 +2899,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -2908,9 +3004,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -2920,9 +3016,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -2931,9 +3027,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rfc6979" @@ -2988,9 +3084,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.54.5" +version = "0.54.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3ee9363fcf66d434d8015d9ae7d879681206981534c21bfdff8a7e34f52cca" +checksum = "23ca8e9091b72afdc9979bddcd1988ad898fa7bc0b85c8da8c154c29d44319eb" dependencies = [ "aes", "base64ct", @@ -3017,6 +3113,7 @@ dependencies = [ "home", "inout", "internal-russh-forked-ssh-key", + "libcrux-ml-kem", "log", "md5", "num-bigint", @@ -3486,9 +3583,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -3591,9 +3688,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" dependencies = [ "cc", "libc", @@ -3601,9 +3698,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -3644,29 +3741,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -3945,6 +4039,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "uzers" version = "0.12.1" @@ -4039,15 +4144,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -4952,7 +5048,7 @@ dependencies = [ "futures", "hashbrown 0.16.0", "libc", - "lru 0.16.1", + "lru 0.16.2", "mlua", "ordered-float 5.1.0", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index 792cddd1..66cda694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,17 +24,17 @@ ansi-to-tui = "7.0.0" anyhow = "1.0.100" base64 = "0.22.1" bitflags = { version = "2.9.4", features = [ "serde" ] } -clap = { version = "4.5.48", features = [ "derive" ] } +clap = { version = "4.5.49", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.29.0", features = [ "event-stream" ] } dirs = "6.0.0" foldhash = "0.2.0" futures = "0.3.31" -globset = "0.4.16" +globset = "0.4.17" hashbrown = { version = "0.16.0", features = [ "serde" ] } -indexmap = { version = "2.11.4", features = [ "serde" ] } +indexmap = { version = "2.12.0", features = [ "serde" ] } libc = "0.2.177" -lru = "0.16.1" +lru = "0.16.2" mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } objc = "0.2.7" ordered-float = { version = "5.1.0", features = [ "serde" ] } @@ -42,13 +42,13 @@ 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.12.1" -russh = { version = "0.54.5", default-features = false, features = [ "ring", "rsa" ] } +regex = "1.12.2" +russh = { version = "0.54.6", default-features = false, features = [ "ring", "rsa" ] } scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } serde_json = "1.0.145" syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } -tokio = { version = "1.47.1", features = [ "full" ] } +tokio = { version = "1.48.0", features = [ "full" ] } tokio-stream = "0.1.17" tokio-util = "0.7.16" toml = { version = "0.9.8" } diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index c8c841f5..bf178b5e 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -52,7 +52,7 @@ impl Actor for BulkRename { defer! { tokio::spawn(Local.remove_file(tmp.clone())); } TasksProxy::process_exec( - cwd, + cwd.into(), Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run), vec![UrlCow::default(), UrlBuf::from(&tmp).into()], opener.block, diff --git a/yazi-actor/src/mgr/download.rs b/yazi-actor/src/mgr/download.rs index d57dcce2..ed10d07d 100644 --- a/yazi-actor/src/mgr/download.rs +++ b/yazi-actor/src/mgr/download.rs @@ -2,11 +2,13 @@ use std::{mem, time::{Duration, Instant}}; use anyhow::Result; use futures::{StreamExt, stream::FuturesUnordered}; +use hashbrown::HashSet; use tokio::sync::oneshot; -use yazi_fs::File; -use yazi_macro::{act, succ}; -use yazi_parser::mgr::DownloadOpt; -use yazi_shared::data::Data; +use yazi_fs::{File, FsUrl, provider::{Provider, local::Local}}; +use yazi_macro::succ; +use yazi_parser::mgr::{DownloadOpt, OpenOpt}; +use yazi_proxy::MgrProxy; +use yazi_shared::{data::Data, url::UrlCow}; use yazi_vfs::VfsFile; use crate::{Actor, Ctx}; @@ -19,18 +21,21 @@ impl Actor for Download { const NAME: &str = "download"; fn act(cx: &mut Ctx, opt: Self::Options) -> Result { - act!(mgr:escape_visual, cx)?; - - let mut wg1 = FuturesUnordered::new(); - for url in opt.urls { - let (tx, rx) = oneshot::channel(); - cx.tasks.scheduler.file_download(url.to_owned(), Some(tx)); - wg1.push(async move { (rx.await == Ok(true), url) }); - } - + let cwd = cx.cwd().clone(); let scheduler = cx.tasks.scheduler.clone(); + tokio::spawn(async move { + Self::prepare(&opt.urls).await; + + let mut wg1 = FuturesUnordered::new(); + for url in opt.urls { + let (tx, rx) = oneshot::channel(); + scheduler.file_download(url.to_owned(), Some(tx)); + wg1.push(async move { (rx.await == Ok(false), url) }); + } + let mut wg2 = vec![]; + let mut urls = Vec::with_capacity(wg1.len()); let mut files = Vec::with_capacity(wg1.len()); let mut instant = Instant::now(); while let Some((success, url)) = wg1.next().await { @@ -38,7 +43,8 @@ impl Actor for Download { continue; } - let Ok(f) = File::new(url).await else { continue }; + let Ok(f) = File::new(&url).await else { continue }; + urls.push(url); files.push(f); if instant.elapsed() >= Duration::from_secs(1) { @@ -50,9 +56,29 @@ impl Actor for Download { if !files.is_empty() { wg2.push(scheduler.fetch_mimetype(files)); } - futures::future::join_all(wg2).await; + if futures::future::join_all(wg2).await.into_iter().any(|b| !b) { + return; + } + if opt.open && !urls.is_empty() { + MgrProxy::open(OpenOpt { + cwd: Some(cwd.into()), + targets: urls, + interactive: false, + hovered: false, + }); + } }); succ!(); } } + +impl Download { + async fn prepare(urls: &[UrlCow<'_>]) { + let roots: HashSet<_> = urls.iter().filter_map(|u| u.cache_root()).collect(); + for mut root in roots { + root.push("%lock"); + Local.create_dir_all(root).await.ok(); + } + } +} diff --git a/yazi-actor/src/mgr/open.rs b/yazi-actor/src/mgr/open.rs index 6b409697..2e47e45b 100644 --- a/yazi-actor/src/mgr/open.rs +++ b/yazi-actor/src/mgr/open.rs @@ -48,7 +48,7 @@ impl Actor for Open { .map(|(i, _)| i) .collect(); - let cwd = cx.cwd().clone(); + let cwd = opt.cwd.unwrap_or_else(|| cx.cwd().clone().into()); if todo.is_empty() { return act!(mgr:open_do, cx, OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive }); } diff --git a/yazi-actor/src/mgr/open_do.rs b/yazi-actor/src/mgr/open_do.rs index 151f6de2..e16456a7 100644 --- a/yazi-actor/src/mgr/open_do.rs +++ b/yazi-actor/src/mgr/open_do.rs @@ -5,7 +5,7 @@ use yazi_fs::Splatter; use yazi_macro::succ; use yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt}; use yazi_proxy::{PickProxy, TasksProxy}; -use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}}; +use yazi_shared::{data::Data, url::UrlCow}; use crate::{Actor, Ctx}; @@ -60,7 +60,7 @@ impl Actor for OpenDo { impl OpenDo { // TODO: remove - fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(UrlCow<'static>, &str)>) { + fn match_and_open(cx: &Ctx, cwd: UrlCow<'static>, targets: Vec<(UrlCow<'static>, &str)>) { let mut openers = HashMap::new(); for (url, mime) in targets { if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) { diff --git a/yazi-actor/src/mgr/shell.rs b/yazi-actor/src/mgr/shell.rs index 3d08ff73..722788d1 100644 --- a/yazi-actor/src/mgr/shell.rs +++ b/yazi-actor/src/mgr/shell.rs @@ -39,12 +39,12 @@ impl Actor for Shell { } TasksProxy::open_shell_compat(ProcessOpenOpt { - cwd, - cmd: Splatter::new(&selected).splat(opt.run.as_ref()), - args: selected, - block: opt.block, + cwd: cwd.into(), + cmd: Splatter::new(&selected).splat(opt.run.as_ref()), + args: selected, + block: opt.block, orphan: opt.orphan, - done: None, + done: None, spread: true, }); }); diff --git a/yazi-adapter/src/adapter.rs b/yazi-adapter/src/adapter.rs index 658f2def..8a8f091b 100644 --- a/yazi-adapter/src/adapter.rs +++ b/yazi-adapter/src/adapter.rs @@ -1,4 +1,4 @@ -use std::{env, fmt::Display, path::Path}; +use std::{env, fmt::Display, path::PathBuf}; use anyhow::Result; use ratatui::layout::Rect; @@ -35,11 +35,15 @@ impl Display for Adapter { } impl Adapter { - pub async fn image_show(self, path: &Path, max: Rect) -> Result { + pub async fn image_show

(self, path: P, max: Rect) -> Result + where + P: Into, + { if max.is_empty() { return Ok(Rect::default()); } + let path = path.into(); match self { Self::Kgp => drivers::Kgp::image_show(path, max).await, Self::KgpOld => drivers::KgpOld::image_show(path, max).await, diff --git a/yazi-adapter/src/drivers/chafa.rs b/yazi-adapter/src/drivers/chafa.rs index b8c18b56..75593c55 100644 --- a/yazi-adapter/src/drivers/chafa.rs +++ b/yazi-adapter/src/drivers/chafa.rs @@ -1,4 +1,4 @@ -use std::{io::Write, path::Path, process::Stdio}; +use std::{io::Write, path::PathBuf, process::Stdio}; use ansi_to_tui::IntoText; use anyhow::{Result, bail}; @@ -11,7 +11,7 @@ use crate::{Adapter, Emulator}; pub(crate) struct Chafa; impl Chafa { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let child = Command::new("chafa") .args([ "-f", diff --git a/yazi-adapter/src/drivers/iip.rs b/yazi-adapter/src/drivers/iip.rs index 5c76fa63..c4612960 100644 --- a/yazi-adapter/src/drivers/iip.rs +++ b/yazi-adapter/src/drivers/iip.rs @@ -1,4 +1,4 @@ -use std::{fmt::Write, io::Write as ioWrite, path::Path}; +use std::{fmt::Write, io::Write as ioWrite, path::PathBuf}; use anyhow::Result; use base64::{Engine, engine::{Config, general_purpose::STANDARD}}; @@ -12,7 +12,7 @@ use crate::{CLOSE, Emulator, Image, START, adapter::Adapter}; pub(crate) struct Iip; impl Iip { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let img = Image::downscale(path, max).await?; let area = Image::pixel_area((img.width(), img.height()), max); let b = Self::encode(img).await?; diff --git a/yazi-adapter/src/drivers/kgp.rs b/yazi-adapter/src/drivers/kgp.rs index 88203280..7d673b1a 100644 --- a/yazi-adapter/src/drivers/kgp.rs +++ b/yazi-adapter/src/drivers/kgp.rs @@ -1,5 +1,5 @@ use core::str; -use std::{io::Write, path::Path}; +use std::{io::Write, path::PathBuf}; use anyhow::Result; use base64::{Engine, engine::general_purpose}; @@ -313,7 +313,7 @@ static DIACRITICS: [char; 297] = [ pub(crate) struct Kgp; impl Kgp { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let img = Image::downscale(path, max).await?; let area = Image::pixel_area((img.width(), img.height()), max); diff --git a/yazi-adapter/src/drivers/kgp_old.rs b/yazi-adapter/src/drivers/kgp_old.rs index 3b03f759..a2aa23d8 100644 --- a/yazi-adapter/src/drivers/kgp_old.rs +++ b/yazi-adapter/src/drivers/kgp_old.rs @@ -1,5 +1,5 @@ use core::str; -use std::{io::Write, path::Path}; +use std::{io::Write, path::PathBuf}; use anyhow::Result; use base64::{Engine, engine::general_purpose}; @@ -12,7 +12,7 @@ use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; pub(crate) struct KgpOld; impl KgpOld { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let img = Image::downscale(path, max).await?; let area = Image::pixel_area((img.width(), img.height()), max); let b = Self::encode(img).await?; diff --git a/yazi-adapter/src/drivers/sixel.rs b/yazi-adapter/src/drivers/sixel.rs index 6d7db947..d8e4ce8e 100644 --- a/yazi-adapter/src/drivers/sixel.rs +++ b/yazi-adapter/src/drivers/sixel.rs @@ -1,4 +1,4 @@ -use std::{io::Write, path::Path}; +use std::{io::Write, path::PathBuf}; use anyhow::{Result, bail}; use crossterm::{cursor::MoveTo, queue}; @@ -12,7 +12,7 @@ use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; pub(crate) struct Sixel; impl Sixel { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let img = Image::downscale(path, max).await?; let area = Image::pixel_area((img.width(), img.height()), max); let b = Self::encode(img).await?; diff --git a/yazi-adapter/src/drivers/ueberzug.rs b/yazi-adapter/src/drivers/ueberzug.rs index 42af1e3e..088d67fa 100644 --- a/yazi-adapter/src/drivers/ueberzug.rs +++ b/yazi-adapter/src/drivers/ueberzug.rs @@ -1,4 +1,4 @@ -use std::{path::{Path, PathBuf}, process::Stdio}; +use std::{path::PathBuf, process::Stdio}; use anyhow::{Result, bail}; use image::ImageReader; @@ -42,14 +42,13 @@ impl Ueberzug { DEMON.init(Some(tx)) } - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result { let Some(tx) = &*DEMON else { bail!("uninitialized ueberzugpp"); }; - let p = path.to_owned(); - let (w, h) = tokio::task::spawn_blocking(move || { - ImageReader::open(p)?.with_guessed_format()?.into_dimensions() + let (w, h, path) = tokio::task::spawn_blocking(move || { + ImageReader::open(&path)?.with_guessed_format()?.into_dimensions().map(|(w, h)| (w, h, path)) }) .await??; @@ -62,7 +61,7 @@ impl Ueberzug { }) .unwrap_or(max); - tx.send(Some((path.to_owned(), area)))?; + tx.send(Some((path, area)))?; Adapter::shown_store(area); Ok(area) } diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index 7f0a0c07..619a0c18 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Result; use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; @@ -11,7 +11,7 @@ use crate::Dimension; pub struct Image; impl Image { - pub async fn precache(src: &Path, cache: &Path) -> Result<()> { + pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> { let (mut img, orientation, icc) = Self::decode_from(src).await?; let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height)); @@ -42,7 +42,7 @@ impl Image { Ok(Local.write(cache, buf).await?) } - pub(super) async fn downscale(path: &Path, rect: Rect) -> Result { + pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result { let (mut img, orientation, _) = Self::decode_from(path).await?; let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect)); @@ -96,7 +96,7 @@ impl Image { } } - async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option>)> { + async fn decode_from(path: PathBuf) -> ImageResult<(DynamicImage, Orientation, Option>)> { let mut limits = Limits::no_limits(); if YAZI.tasks.image_alloc > 0 { limits.max_alloc = Some(YAZI.tasks.image_alloc as u64); @@ -108,7 +108,6 @@ impl Image { limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32); } - let path = path.to_owned(); tokio::task::spawn_blocking(move || { let mut reader = ImageReader::open(path)?; reader.limits(limits); diff --git a/yazi-adapter/src/info.rs b/yazi-adapter/src/info.rs index cdc1d60f..86d4052e 100644 --- a/yazi-adapter/src/info.rs +++ b/yazi-adapter/src/info.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::PathBuf; use image::{ImageDecoder, ImageError}; @@ -16,8 +16,7 @@ pub struct ImageInfo { } impl ImageInfo { - pub async fn new(path: &Path) -> image::ImageResult { - let path = path.to_owned(); + pub async fn new(path: PathBuf) -> image::ImageResult { tokio::task::spawn_blocking(move || { let reader = image::ImageReader::open(path)?.with_guessed_format()?; diff --git a/yazi-binding/src/lib.rs b/yazi-binding/src/lib.rs index 0271786a..2d1ffdc5 100644 --- a/yazi-binding/src/lib.rs +++ b/yazi-binding/src/lib.rs @@ -4,4 +4,4 @@ mod macros; yazi_macro::mod_pub!(elements); -yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime stage style url urn utils); +yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime scheme stage style url urn utils); diff --git a/yazi-binding/src/scheme.rs b/yazi-binding/src/scheme.rs new file mode 100644 index 00000000..73532b62 --- /dev/null +++ b/yazi-binding/src/scheme.rs @@ -0,0 +1,31 @@ +use std::ops::Deref; + +use mlua::{UserData, UserDataFields, Value}; + +use crate::cached_field; + +pub struct Scheme { + inner: yazi_shared::scheme::Scheme, + + v_kind: Option, +} + +impl Deref for Scheme { + type Target = yazi_shared::scheme::Scheme; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl Scheme { + pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self { + Self { inner: scheme.clone(), v_kind: None } + } +} + +impl UserData for Scheme { + fn add_fields>(fields: &mut F) { + cached_field!(fields, kind, |_, me| Ok(me.kind())); + + fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual())); + } +} diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index 77405eaa..d0611f39 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow, UrlLike}}; -use crate::{Urn, cached_field, deprecate}; +use crate::{Scheme, Urn, cached_field, deprecate}; pub type UrlRef = UserDataRef; @@ -16,6 +16,8 @@ pub struct Url { v_urn: Option, v_base: Option, v_parent: Option, + + v_scheme: Option, v_domain: Option, } @@ -64,6 +66,8 @@ impl Url { v_urn: None, v_base: None, v_parent: None, + + v_scheme: None, v_domain: None, } } @@ -105,6 +109,8 @@ impl UserData for Url { cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new))); cached_field!(fields, urn, |_, me| Ok(Urn::new(me.urn()))); cached_field!(fields, base, |_, me| Ok(me.base().map(Self::new))); + + cached_field!(fields, scheme, |_, me| Ok(Scheme::new(&me.scheme))); cached_field!(fields, domain, |lua, me| { me.scheme.domain().map(|s| lua.create_string(s)).transpose() }); diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 7db7890c..98230d28 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -28,7 +28,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" } # External dependencies clap = { workspace = true } -clap_complete = "4.5.58" +clap_complete = "4.5.59" clap_complete_fig = "4.5.2" -clap_complete_nushell = "4.5.8" +clap_complete_nushell = "4.5.9" vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] } diff --git a/yazi-boot/src/args.rs b/yazi-boot/src/args.rs index 76de82ae..d6071a2e 100644 --- a/yazi-boot/src/args.rs +++ b/yazi-boot/src/args.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use clap::{Parser, command}; +use clap::Parser; use yazi_shared::{Id, url::UrlBuf}; #[derive(Debug, Default, Parser)] diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 6351bc46..80599611 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -41,9 +41,9 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" } # External build dependencies anyhow = { workspace = true } clap = { workspace = true } -clap_complete = "4.5.58" +clap_complete = "4.5.59" clap_complete_fig = "4.5.2" -clap_complete_nushell = "4.5.8" +clap_complete_nushell = "4.5.9" serde_json = { workspace = true } vergen-gitcl = { version = "1.0.8", features = [ "build" ] } diff --git a/yazi-cli/src/args.rs b/yazi-cli/src/args.rs index c99e9648..f28730f2 100644 --- a/yazi-cli/src/args.rs +++ b/yazi-cli/src/args.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use anyhow::{Result, bail}; -use clap::{Parser, Subcommand, command}; +use clap::{Parser, Subcommand}; use yazi_shared::{Id, event::Cmd}; #[derive(Parser)] diff --git a/yazi-codegen/Cargo.toml b/yazi-codegen/Cargo.toml index 7b471bdd..83fbb4a7 100644 --- a/yazi-codegen/Cargo.toml +++ b/yazi-codegen/Cargo.toml @@ -13,5 +13,5 @@ proc-macro = true [dependencies] # External dependencies -syn = { version = "2.0.106", features = [ "full" ] } +syn = { version = "2.0.107", features = [ "full" ] } quote = "1.0.41" diff --git a/yazi-config/src/theme/icon.rs b/yazi-config/src/theme/icon.rs index f3fd9d2b..69892c2d 100644 --- a/yazi-config/src/theme/icon.rs +++ b/yazi-config/src/theme/icon.rs @@ -131,7 +131,7 @@ impl<'de> Deserialize<'de> for PatIcons { { #[derive(Deserialize)] struct Shadow { - name: Pattern, + url: Pattern, text: String, fg: Option, } @@ -139,7 +139,7 @@ impl<'de> Deserialize<'de> for PatIcons { Ok(Self( >::deserialize(deserializer)? .into_iter() - .map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } })) + .map(|s| (s.url, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } })) .collect(), )) } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 1f20ca23..9feaccbb 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -67,7 +67,7 @@ signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } [target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies] -tikv-jemallocator = "0.6.0" +tikv-jemallocator = "0.6.1" [[bin]] name = "yazi" diff --git a/yazi-fs/src/cha/cha.rs b/yazi-fs/src/cha/cha.rs index 5159e453..3a519814 100644 --- a/yazi-fs/src/cha/cha.rs +++ b/yazi-fs/src/cha/cha.rs @@ -155,14 +155,6 @@ impl Cha { } } - pub fn mtime_dur(self) -> anyhow::Result { - if let Some(mtime) = self.mtime { - Ok(mtime.duration_since(UNIX_EPOCH)?) - } else { - bail!("mtime not available"); - } - } - pub fn btime_dur(self) -> anyhow::Result { if let Some(btime) = self.btime { Ok(btime.duration_since(UNIX_EPOCH)?) @@ -178,4 +170,12 @@ impl Cha { bail!("ctime not available"); } } + + pub fn mtime_dur(self) -> anyhow::Result { + if let Some(mtime) = self.mtime { + Ok(mtime.duration_since(UNIX_EPOCH)?) + } else { + bail!("mtime not available"); + } + } } diff --git a/yazi-fs/src/cha/mode.rs b/yazi-fs/src/cha/mode.rs index 9d70e739..0961d1d1 100644 --- a/yazi-fs/src/cha/mode.rs +++ b/yazi-fs/src/cha/mode.rs @@ -75,6 +75,15 @@ impl TryFrom for ChaMode { } } +#[cfg(unix)] +impl From for std::fs::Permissions { + fn from(value: ChaMode) -> Self { + use std::os::unix::fs::PermissionsExt; + + std::fs::Permissions::from_mode(value.bits() as _) + } +} + impl ChaMode { // Convert a file mode to a string representation #[cfg(unix)] diff --git a/yazi-fs/src/cwd.rs b/yazi-fs/src/cwd.rs index 70f184b6..ec4935d1 100644 --- a/yazi-fs/src/cwd.rs +++ b/yazi-fs/src/cwd.rs @@ -47,7 +47,7 @@ impl Cwd { } pub fn ensure(url: Url) -> Cow { - use std::io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}; + use std::{io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}, path::Component as C}; let Some(cache) = url.cache() else { return url.loc.as_path().into(); @@ -58,7 +58,20 @@ impl Cwd { return cache.into(); } - let count = cache.strip_prefix(Xdg::cache_dir()).expect("under cache dir").components().count(); + let latter = cache.strip_prefix(Xdg::cache_dir()).expect("under cache dir"); + let mut it = latter.components().peekable(); + while it.peek() == Some(&C::CurDir) { + it.next().unwrap(); + } + + let mut count = 0; + for c in it { + match c { + C::Prefix(_) | C::RootDir | C::ParentDir => return cache.into(), + C::CurDir | C::Normal(_) => count += 1, + } + } + for n in (0..count).rev() { let mut it = cache.components(); for _ in 0..n { diff --git a/yazi-fs/src/provider/attrs.rs b/yazi-fs/src/provider/attrs.rs new file mode 100644 index 00000000..8f4955a1 --- /dev/null +++ b/yazi-fs/src/provider/attrs.rs @@ -0,0 +1,53 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use crate::cha::{Cha, ChaMode}; + +#[derive(Clone, Copy, Debug, Default)] +pub struct Attrs { + pub mode: Option, + pub atime: Option, + pub btime: Option, + pub mtime: Option, +} + +impl From for Attrs { + fn from(value: Cha) -> Self { + Self { mode: Some(value.mode), atime: value.atime, btime: value.btime, mtime: value.mtime } + } +} + +impl From for std::fs::FileTimes { + fn from(attrs: Attrs) -> Self { + let mut t = Self::new(); + + if let Some(atime) = attrs.atime { + t = t.set_accessed(atime); + } + + #[cfg(target_os = "macos")] + if let Some(btime) = attrs.btime { + use std::os::macos::fs::FileTimesExt; + t = t.set_created(btime); + } + + #[cfg(windows)] + if let Some(btime) = attrs.btime { + use std::os::windows::fs::FileTimesExt; + t = t.set_created(btime); + } + + if let Some(mtime) = attrs.mtime { + t = t.set_modified(mtime); + } + + t + } +} + +impl Attrs { + pub fn atime_dur(self) -> Option { self.atime?.duration_since(UNIX_EPOCH).ok() } + + pub fn btime_dur(self) -> Option { self.btime?.duration_since(UNIX_EPOCH).ok() } + + pub fn mtime_dur(self) -> Option { self.mtime?.duration_since(UNIX_EPOCH).ok() } +} diff --git a/yazi-fs/src/provider/local/gate.rs b/yazi-fs/src/provider/local/gate.rs index d1bea5c8..eabe87e1 100644 --- a/yazi-fs/src/provider/local/gate.rs +++ b/yazi-fs/src/provider/local/gate.rs @@ -2,7 +2,7 @@ use std::{io, path::Path}; use yazi_shared::scheme::SchemeRef; -use crate::{cha::Cha, provider::FileBuilder}; +use crate::provider::{Attrs, FileBuilder}; #[derive(Default)] pub struct Gate(tokio::fs::OpenOptions); @@ -15,9 +15,11 @@ impl FileBuilder for Gate { self } - fn cha(&mut self, _cha: Cha) -> &mut Self { + fn attrs(&mut self, _attrs: Attrs) -> &mut Self { #[cfg(unix)] - self.0.mode(_cha.mode.bits() as _); + if let Some(mode) = _attrs.mode { + self.0.mode(mode.bits() as _); + } self } diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/provider/local/local.rs index 6ac46de2..4815ee30 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/provider/local/local.rs @@ -2,7 +2,7 @@ use std::{io, path::{Path, PathBuf}}; use yazi_shared::url::{AsUrl, UrlCow}; -use crate::{cha::Cha, path::absolute_url, provider::Provider}; +use crate::{cha::Cha, path::absolute_url, provider::{Attrs, Provider}}; #[derive(Clone, Copy)] pub struct Local; @@ -40,14 +40,14 @@ impl Provider for Local { } #[inline] - async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result + async fn copy(&self, from: P, to: Q, attrs: Attrs) -> io::Result where P: AsRef, Q: AsRef, { let from = from.as_ref().to_owned(); let to = to.as_ref().to_owned(); - Self::copy_impl(from, to, cha).await + Self::copy_impl(from, to, attrs).await } #[inline] @@ -232,23 +232,25 @@ impl Provider for Local { } impl Local { - async fn copy_impl(from: PathBuf, to: PathBuf, cha: Cha) -> io::Result { + async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::Result { #[cfg(any(target_os = "linux", target_os = "android"))] { use std::os::unix::fs::OpenOptionsExt; tokio::task::spawn_blocking(move || { - let mut reader = std::fs::File::open(from)?; - let mut writer = std::fs::OpenOptions::new() - .mode(cha.mode.bits() as _) - .write(true) - .create(true) - .truncate(true) - .open(to)?; + let mut opts = std::fs::OpenOptions::new(); + if let Some(mode) = attrs.mode { + opts.mode(mode.bits() as _); + } + let mut reader = std::fs::File::open(from)?; + let mut writer = opts.write(true).create(true).truncate(true).open(to)?; let written = std::io::copy(&mut reader, &mut writer)?; - writer.set_permissions(cha.into()).ok(); - writer.set_times(cha.into()).ok(); + + if let Some(mode) = attrs.mode { + writer.set_permissions(mode.into()).ok(); + } + writer.set_times(attrs.into()).ok(); Ok(written) }) @@ -261,7 +263,7 @@ impl Local { let written = std::fs::copy(from, &to)?; if let Ok(file) = std::fs::File::options().write(true).open(to) { - file.set_times(cha.into()).ok(); + file.set_times(attrs.into()).ok(); } Ok(written) diff --git a/yazi-fs/src/provider/local/metadata.rs b/yazi-fs/src/provider/local/metadata.rs deleted file mode 100644 index 4944a2ce..00000000 --- a/yazi-fs/src/provider/local/metadata.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fs::FileTimes; - -use crate::cha::Cha; - -impl From for FileTimes { - fn from(cha: Cha) -> Self { - let mut t = std::fs::FileTimes::new(); - - if let Some(atime) = cha.atime { - t = t.set_accessed(atime); - } - - #[cfg(target_os = "macos")] - if let Some(btime) = cha.btime { - use std::os::macos::fs::FileTimesExt; - t = t.set_created(btime); - } - - #[cfg(windows)] - if let Some(btime) = cha.btime { - use std::os::windows::fs::FileTimesExt; - t = t.set_created(btime); - } - - if let Some(mtime) = cha.mtime { - t = t.set_modified(mtime); - } - - t - } -} - -#[cfg(unix)] -impl From for std::fs::Permissions { - fn from(cha: Cha) -> Self { - use std::os::unix::fs::PermissionsExt; - - std::fs::Permissions::from_mode(cha.mode.bits() as _) - } -} diff --git a/yazi-fs/src/provider/local/mod.rs b/yazi-fs/src/provider/local/mod.rs index 5e0f8777..2589b059 100644 --- a/yazi-fs/src/provider/local/mod.rs +++ b/yazi-fs/src/provider/local/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local metadata read_dir); +yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir); diff --git a/yazi-fs/src/provider/mod.rs b/yazi-fs/src/provider/mod.rs index 5db9cfd1..3297b1c6 100644 --- a/yazi-fs/src/provider/mod.rs +++ b/yazi-fs/src/provider/mod.rs @@ -1,3 +1,3 @@ yazi_macro::mod_pub!(local); -yazi_macro::mod_flat!(traits); +yazi_macro::mod_flat!(attrs traits); diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/provider/traits.rs index 5895e263..d5f5be44 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/provider/traits.rs @@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use yazi_macro::ok_or_not_found; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlCow}}; -use crate::cha::{Cha, ChaType}; +use crate::{cha::{Cha, ChaType}, provider::Attrs}; pub trait Provider { type File: AsyncRead + AsyncWrite + Unpin; @@ -23,7 +23,7 @@ pub trait Provider { where P: AsRef; - fn copy(&self, from: P, to: Q, cha: Cha) -> impl Future> + fn copy(&self, from: P, to: Q, attrs: Attrs) -> impl Future> where P: AsRef, Q: AsRef; @@ -245,7 +245,7 @@ pub trait FileBuilder { fn append(&mut self, append: bool) -> &mut Self; - fn cha(&mut self, cha: Cha) -> &mut Self; + fn attrs(&mut self, attrs: Attrs) -> &mut Self; fn create(&mut self, create: bool) -> &mut Self; diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index 462f54b0..4a3ceaa8 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -65,14 +65,14 @@ impl<'a> FsUrl<'a> for Url<'a> { } } -impl<'a> FsUrl<'a> for UrlBuf { +impl FsUrl<'_> for UrlBuf { fn cache(&self) -> Option { self.as_url().cache() } fn cache_lock(&self) -> Option { self.as_url().cache_lock() } fn cache_root(&self) -> Option { self.as_url().cache_root() } - fn unified_path(self) -> Cow<'a, Path> { + fn unified_path(self) -> Cow<'static, Path> { self.cache().unwrap_or_else(|| self.loc.into_path()).into() } } diff --git a/yazi-parser/src/mgr/open.rs b/yazi-parser/src/mgr/open.rs index 95d05397..763cf52a 100644 --- a/yazi-parser/src/mgr/open.rs +++ b/yazi-parser/src/mgr/open.rs @@ -3,18 +3,26 @@ use yazi_shared::{event::CmdCow, url::UrlCow}; #[derive(Debug)] pub struct OpenOpt { + pub cwd: Option>, pub targets: Vec>, pub interactive: bool, pub hovered: bool, } -impl From for OpenOpt { - fn from(mut c: CmdCow) -> Self { - Self { +impl TryFrom for OpenOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + if let Some(opt) = c.take_any2("opt") { + return opt; + } + + Ok(Self { + cwd: c.take("cwd").ok(), targets: c.take_seq(), interactive: c.bool("interactive"), hovered: c.bool("hovered"), - } + }) } } diff --git a/yazi-parser/src/mgr/open_do.rs b/yazi-parser/src/mgr/open_do.rs index 21114c3d..fa8d8c4c 100644 --- a/yazi-parser/src/mgr/open_do.rs +++ b/yazi-parser/src/mgr/open_do.rs @@ -1,10 +1,10 @@ use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; +use yazi_shared::{event::CmdCow, url::UrlCow}; #[derive(Debug, Default)] pub struct OpenDoOpt { - pub cwd: UrlBuf, + pub cwd: UrlCow<'static>, pub targets: Vec>, pub interactive: bool, } diff --git a/yazi-parser/src/tasks/process_open.rs b/yazi-parser/src/tasks/process_open.rs index c0a44855..99b1b6bf 100644 --- a/yazi-parser/src/tasks/process_open.rs +++ b/yazi-parser/src/tasks/process_open.rs @@ -3,12 +3,12 @@ use std::ffi::OsString; use anyhow::anyhow; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use tokio::sync::oneshot; -use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; +use yazi_shared::{event::CmdCow, url::UrlCow}; // --- Exec #[derive(Debug)] pub struct ProcessOpenOpt { - pub cwd: UrlBuf, + pub cwd: UrlCow<'static>, pub cmd: OsString, pub args: Vec>, pub block: bool, diff --git a/yazi-plugin/preset/plugins/mime-dir.lua b/yazi-plugin/preset/plugins/mime-dir.lua index 7ee392cf..38abab94 100644 --- a/yazi-plugin/preset/plugins/mime-dir.lua +++ b/yazi-plugin/preset/plugins/mime-dir.lua @@ -1,7 +1,11 @@ local function fetch(_, job) local updates = {} for _, file in ipairs(job.files) do - updates[file.url] = "folder/local" + if file.url.scheme.is_virtual then + updates[file.url] = "folder/remote" + else + updates[file.url] = "folder/local" + end end ya.emit("update_mimes", { updates = updates }) diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs index 8d1927e6..2d7fc0e2 100644 --- a/yazi-plugin/src/external/highlighter.rs +++ b/yazi-plugin/src/external/highlighter.rs @@ -17,7 +17,12 @@ pub struct Highlighter { impl Highlighter { #[inline] - pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } } + pub fn new

(path: P) -> Self + where + P: Into, + { + Self { path: path.into() } + } pub fn init() -> (&'static Theme, &'static SyntaxSet) { let f = || { diff --git a/yazi-plugin/src/utils/image.rs b/yazi-plugin/src/utils/image.rs index 2dc8c8b9..6cef3ab6 100644 --- a/yazi-plugin/src/utils/image.rs +++ b/yazi-plugin/src/utils/image.rs @@ -1,7 +1,8 @@ use mlua::{Function, IntoLuaMulti, Lua, Value}; use yazi_adapter::{ADAPTOR, Image}; use yazi_binding::{Error, UrlRef, elements::Rect}; -use yazi_shared::url::UrlLike; +use yazi_fs::FsUrl; +use yazi_shared::url::{AsUrl, UrlLike}; use super::Utils; use crate::bindings::ImageInfo; @@ -9,9 +10,7 @@ use crate::bindings::ImageInfo; impl Utils { pub(super) fn image_info(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, url: UrlRef| async move { - let Some(path) = url.as_path() else { - return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua); - }; + let path = url.as_url().unified_path().into_owned(); match yazi_adapter::ImageInfo::new(path).await { Ok(info) => ImageInfo::from(info).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua), @@ -21,9 +20,7 @@ impl Utils { pub(super) fn image_show(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move { - let Some(path) = url.as_path() else { - return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua); - }; + let path = url.as_url().unified_path(); match ADAPTOR.get().image_show(path, *rect).await { Ok(area) => Rect::from(area).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua), @@ -33,9 +30,11 @@ impl Utils { pub(super) fn image_precache(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, (src, dist): (UrlRef, UrlRef)| async move { - let Some((src, dist)) = src.as_path().zip(dist.as_path()) else { - return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua); + let Some(dist) = dist.as_path() else { + return (Value::Nil, Error::custom("Destination must be a local path")) + .into_lua_multi(&lua); }; + let src = src.as_url().unified_path().into_owned(); match Image::precache(src, dist).await { Ok(()) => true.into_lua_multi(&lua), Err(e) => (false, Error::custom(e.to_string())).into_lua_multi(&lua), diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 98f4e68e..eac529c9 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -1,9 +1,10 @@ use mlua::{AnyUserData, ExternalError, Function, IntoLuaMulti, Lua, Table, Value}; use yazi_binding::{Error, elements::{Area, Renderable, Text}}; use yazi_config::YAZI; +use yazi_fs::FsUrl; use yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt}; use yazi_proxy::MgrProxy; -use yazi_shared::{errors::PeekError, url::UrlLike}; +use yazi_shared::{errors::PeekError, url::AsUrl}; use super::Utils; use crate::external::Highlighter; @@ -14,10 +15,7 @@ impl Utils { let area: Area = t.raw_get("area")?; let mut lock = PreviewLock::try_from(t)?; - let Some(path) = lock.url.as_path() else { - return "Only local files are supported".into_lua_multi(&lua); - }; - + let path = lock.url.as_url().unified_path(); let inner = match Highlighter::new(path).highlight(lock.skip, area.size()).await { Ok(text) => text, Err(e @ PeekError::Exceed(max)) => return (e.to_string(), max).into_lua_multi(&lua), diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index 6acc6a65..dd8b156c 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use yazi_macro::{emit, relay}; -use yazi_parser::mgr::{FilterOpt, FindDoOpt, OpenDoOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt}; +use yazi_parser::mgr::{FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt}; use yazi_shared::{SStr, url::UrlBuf}; pub struct MgrProxy; @@ -19,6 +19,10 @@ impl MgrProxy { emit!(Call(relay!(mgr:arrow, [step.into()]))); } + pub fn open(opt: OpenOpt) { + emit!(Call(relay!(mgr:open).with_any("opt", opt))); + } + pub fn open_do(opt: OpenDoOpt) { emit!(Call(relay!(mgr:open_do).with_any("opt", opt))); } diff --git a/yazi-proxy/src/tasks.rs b/yazi-proxy/src/tasks.rs index cdcb8254..425330ad 100644 --- a/yazi-proxy/src/tasks.rs +++ b/yazi-proxy/src/tasks.rs @@ -14,7 +14,7 @@ impl TasksProxy { } pub async fn process_exec( - cwd: UrlBuf, + cwd: UrlCow<'static>, cmd: OsString, args: Vec>, block: bool, diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 043f5e36..2ed60dea 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result, anyhow}; use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; 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_fs::{Cwd, 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, UrlBuf, UrlCow, UrlLike}}; use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name}; @@ -319,13 +319,9 @@ impl File { let mut dirs = VecDeque::from([task.url.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(()), - }); - let mut it = continue_unless_ok!(provider::read_dir(&src).await); + tokio::task::spawn_blocking(move || _ = Cwd::ensure(src.as_url())).await.ok(); + while let Ok(Some(entry)) = it.next().await { let from = entry.url(); let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await); @@ -351,16 +347,17 @@ impl File { let cha = task.cha.unwrap(); let cache = task.url.cache().context("Cannot determine cache path")?; - let cache_tmp = Self::tmp(&cache).await?; + let cache_tmp = Self::tmp(&cache).await.context("Cannot determine temporary download cache")?; let mut it = copy_with_progress(&task.url, &cache_tmp, cha); while let Some(res) = it.recv().await { match res { Ok(0) => { - provider::rename(&cache_tmp, &cache).await?; + provider::rename(cache_tmp, cache).await.context("Cannot persist downloaded file")?; let lock = task.url.cache_lock().context("Cannot determine cache lock")?; - Local.write(lock, format!("{:x}", cha.hash_u128())).await?; + let hash = format!("{:x}", cha.hash_u128()); + Local.write(lock, hash).await.context("Cannot lock cache")?; break; } @@ -388,7 +385,7 @@ impl File { 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"))?; + Err(anyhow!("Source of symlink doesn't exist"))?; } if !cha.is_dir() { @@ -456,19 +453,22 @@ impl File { Err(anyhow!("Remote file has changed since last download"))?; } - let tmp = Self::tmp(&task.url).await?; + let tmp = Self::tmp(&task.url).await.context("Cannot determine temporary upload path")?; 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 cha = Self::cha(&task.url, true, None).await.context("Cannot stat original file")?; + if hash != cha.hash_u128() { + Err(anyhow!("Remote file has changed during upload"))?; + } - let hash = format!("{:x}", Self::cha(&task.url, true, None).await?.hash_u128()); - Local.write(lock, hash).await?; + provider::rename(&tmp, &task.url).await.context("Cannot persist uploaded file")?; + + let cha = Self::cha(&task.url, true, None).await.context("Cannot stat uploaded file")?; + let hash = format!("{:x}", cha.hash_u128()); + Local.write(lock, hash).await.context("Cannot lock cache")?; break; } diff --git a/yazi-scheduler/src/file/out.rs b/yazi-scheduler/src/file/out.rs index 1d193163..5994103c 100644 --- a/yazi-scheduler/src/file/out.rs +++ b/yazi-scheduler/src/file/out.rs @@ -11,7 +11,7 @@ pub(crate) enum FileOutPaste { } impl From for FileOutPaste { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutPaste { @@ -51,7 +51,7 @@ pub(crate) enum FileOutPasteDo { } impl From for FileOutPasteDo { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutPasteDo { @@ -83,11 +83,11 @@ pub(crate) enum FileOutLink { } impl From for FileOutLink { - fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl From for FileOutLink { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutLink { @@ -126,7 +126,7 @@ pub(crate) enum FileOutHardlink { } impl From for FileOutHardlink { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutHardlink { @@ -160,7 +160,7 @@ pub(crate) enum FileOutHardlinkDo { } impl From for FileOutHardlinkDo { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutHardlinkDo { @@ -188,7 +188,7 @@ pub(crate) enum FileOutDelete { } impl From for FileOutDelete { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutDelete { @@ -221,7 +221,7 @@ pub(crate) enum FileOutDeleteDo { } impl From for FileOutDeleteDo { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutDeleteDo { @@ -248,7 +248,7 @@ pub(crate) enum FileOutTrash { } impl From for FileOutTrash { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutTrash { @@ -276,7 +276,7 @@ pub(crate) enum FileOutDownload { } impl From for FileOutDownload { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutDownload { @@ -313,11 +313,11 @@ pub(crate) enum FileOutDownloadDo { } impl From for FileOutDownloadDo { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl From for FileOutDownloadDo { - fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutDownloadDo { @@ -351,11 +351,11 @@ pub(crate) enum FileOutUpload { } impl From for FileOutUpload { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl From for FileOutUpload { - fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutUpload { @@ -391,11 +391,11 @@ pub(crate) enum FileOutUploadDo { } impl From for FileOutUploadDo { - fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl From for FileOutUploadDo { - fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) } + fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) } } impl FileOutUploadDo { diff --git a/yazi-scheduler/src/in.rs b/yazi-scheduler/src/in.rs index 8487577d..3ba780cc 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, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}}; +use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}}; #[derive(Debug)] pub(crate) enum TaskIn { @@ -11,7 +11,6 @@ pub(crate) enum TaskIn { FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), - FileUpload(FileInUpload), FileUploadDo(FileInUploadDo), // Plugin PluginEntry(PluginInEntry), @@ -23,7 +22,7 @@ pub(crate) enum TaskIn { impl_from_in! { // File - FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload), FileUploadDo(FileInUploadDo), + FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUploadDo(FileInUploadDo), // Plugin PluginEntry(PluginInEntry), // Prework @@ -40,7 +39,6 @@ impl TaskIn { Self::FileDelete(r#in) => r#in.id, 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, diff --git a/yazi-scheduler/src/process/in.rs b/yazi-scheduler/src/process/in.rs index 41ea3b68..ac88d157 100644 --- a/yazi-scheduler/src/process/in.rs +++ b/yazi-scheduler/src/process/in.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use tokio::sync::mpsc; -use yazi_shared::{Id, url::{UrlBuf, UrlCow}}; +use yazi_shared::{Id, url::UrlCow}; use super::ShellOpt; @@ -9,7 +9,7 @@ use super::ShellOpt; #[derive(Debug)] pub(crate) struct ProcessInBlock { pub(crate) id: Id, - pub(crate) cwd: UrlBuf, + pub(crate) cwd: UrlCow<'static>, pub(crate) cmd: OsString, pub(crate) args: Vec>, } @@ -24,7 +24,7 @@ impl From for ShellOpt { #[derive(Debug)] pub(crate) struct ProcessInOrphan { pub(crate) id: Id, - pub(crate) cwd: UrlBuf, + pub(crate) cwd: UrlCow<'static>, pub(crate) cmd: OsString, pub(crate) args: Vec>, } @@ -39,7 +39,7 @@ impl From for ShellOpt { #[derive(Debug)] pub(crate) struct ProcessInBg { pub(crate) id: Id, - pub(crate) cwd: UrlBuf, + pub(crate) cwd: UrlCow<'static>, pub(crate) cmd: OsString, pub(crate) args: Vec>, pub(crate) cancel: mpsc::Receiver<()>, diff --git a/yazi-scheduler/src/process/shell.rs b/yazi-scheduler/src/process/shell.rs index 24f04366..25d1bfe3 100644 --- a/yazi-scheduler/src/process/shell.rs +++ b/yazi-scheduler/src/process/shell.rs @@ -3,10 +3,10 @@ use std::{ffi::OsString, process::Stdio}; use anyhow::Result; use tokio::process::{Child, Command}; use yazi_fs::{Cwd, FsUrl}; -use yazi_shared::url::{AsUrl, UrlBuf, UrlCow}; +use yazi_shared::url::{AsUrl, UrlCow}; pub(crate) struct ShellOpt { - pub(crate) cwd: UrlBuf, + pub(crate) cwd: UrlCow<'static>, pub(crate) cmd: OsString, pub(crate) args: Vec>, pub(crate) piped: bool, diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index 2460c61a..fd5bc69e 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -282,8 +282,8 @@ impl Scheduler { } for rx in wg { - if rx.await != Ok(true) { - return false; + if rx.await != Ok(false) { + return false; // Canceled or error } } true @@ -409,7 +409,6 @@ 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(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), diff --git a/yazi-sftp/src/operator.rs b/yazi-sftp/src/operator.rs index 595d6db8..1620d4f4 100644 --- a/yazi-sftp/src/operator.rs +++ b/yazi-sftp/src/operator.rs @@ -146,6 +146,21 @@ impl Operator { status.into() } + pub async fn rename_posix<'a, F, T>(&self, from: F, to: T) -> Result<(), Error> + where + F: ToByteStr<'a>, + T: ToByteStr<'a>, + { + if self.extensions.lock().get("posix-rename@openssh.com").is_none_or(|s| s != "1") { + return Err(Error::Unsupported); + } + + let data = requests::ExtendedRename::new(from, to)?; + let status: responses::Status = + self.send(requests::Extended::new("posix-rename@openssh.com", data)).await?; + status.into() + } + pub async fn readlink<'a, P>(&self, path: P) -> Result where P: ToByteStr<'a>, diff --git a/yazi-sftp/src/packet.rs b/yazi-sftp/src/packet.rs index 70545f89..197b7639 100644 --- a/yazi-sftp/src/packet.rs +++ b/yazi-sftp/src/packet.rs @@ -25,6 +25,7 @@ pub enum Packet<'a> { Rename(requests::Rename<'a>), Readlink(requests::Readlink<'a>), Symlink(requests::Symlink<'a>), + ExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>), ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>), ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>), ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>), @@ -59,6 +60,7 @@ impl_from_packet! { Rename(requests::Rename<'a>), Readlink(requests::Readlink<'a>), Symlink(requests::Symlink<'a>), + ExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>), ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>), ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>), ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>), @@ -105,6 +107,7 @@ impl Packet<'_> { Self::Rename(_) => 18, Self::Readlink(_) => 19, Self::Symlink(_) => 20, + Self::ExtendedRename(_) => 200, Self::ExtendedFsync(_) => 200, Self::ExtendedHardlink(_) => 200, Self::ExtendedLimits(_) => 200, @@ -141,6 +144,7 @@ impl Packet<'_> { Self::Rename(v) => v.id, Self::Readlink(v) => v.id, Self::Symlink(v) => v.id, + Self::ExtendedRename(v) => v.id, Self::ExtendedFsync(v) => v.id, Self::ExtendedHardlink(v) => v.id, Self::ExtendedLimits(v) => v.id, @@ -177,6 +181,7 @@ impl Packet<'_> { Self::Rename(v) => v.id = id, Self::Readlink(v) => v.id = id, Self::Symlink(v) => v.id = id, + Self::ExtendedRename(v) => v.id = id, Self::ExtendedFsync(v) => v.id = id, Self::ExtendedHardlink(v) => v.id = id, Self::ExtendedLimits(v) => v.id = id, @@ -215,6 +220,7 @@ impl Packet<'_> { Self::Rename(v) => type_len + v.len(), Self::Readlink(v) => type_len + v.len(), Self::Symlink(v) => type_len + v.len(), + Self::ExtendedRename(v) => type_len + v.len(), Self::ExtendedFsync(v) => type_len + v.len(), Self::ExtendedHardlink(v) => type_len + v.len(), Self::ExtendedLimits(v) => type_len + v.len(), diff --git a/yazi-sftp/src/requests/extended.rs b/yazi-sftp/src/requests/extended.rs index 93943481..7b310b03 100644 --- a/yazi-sftp/src/requests/extended.rs +++ b/yazi-sftp/src/requests/extended.rs @@ -27,6 +27,27 @@ pub trait ExtendedData: Debug + Serialize + for<'de> Deserialize<'de> { fn len(&self) -> usize; } +// --- POSIX Rename +#[derive(Debug, Deserialize, Serialize)] +pub struct ExtendedRename<'a> { + pub from: ByteStr<'a>, + pub to: ByteStr<'a>, +} + +impl<'a> ExtendedRename<'a> { + pub fn new(from: F, to: T) -> Result + where + F: ToByteStr<'a>, + T: ToByteStr<'a>, + { + Ok(Self { from: from.to_byte_str()?, to: to.to_byte_str()? }) + } +} + +impl ExtendedData for ExtendedRename<'_> { + fn len(&self) -> usize { 4 + self.from.len() + 4 + self.to.len() } +} + // --- Fsync #[derive(Debug, Deserialize, Serialize)] pub struct ExtendedFsync<'a> { diff --git a/yazi-vfs/src/fns.rs b/yazi-vfs/src/fns.rs index 39a4512b..d6b035e4 100644 --- a/yazi-vfs/src/fns.rs +++ b/yazi-vfs/src/fns.rs @@ -1,7 +1,7 @@ use std::{ffi::OsString, io}; use tokio::{select, sync::{mpsc, oneshot}}; -use yazi_fs::cha::Cha; +use yazi_fs::provider::Attrs; use yazi_macro::ok_or_not_found; use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike}; @@ -63,22 +63,27 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result { Ok(url) } -pub fn copy_with_progress(from: U, to: V, cha: Cha) -> mpsc::Receiver> +pub fn copy_with_progress( + from: U, + to: V, + attrs: A, +) -> mpsc::Receiver> where U: AsUrl, V: AsUrl, + A: Into, { - _copy_with_progress(from.as_url(), to.as_url(), cha) + _copy_with_progress(from.as_url(), to.as_url(), attrs.into()) } -fn _copy_with_progress(from: Url, to: Url, cha: Cha) -> mpsc::Receiver> { +fn _copy_with_progress(from: Url, to: Url, attrs: Attrs) -> mpsc::Receiver> { let (prog_tx, prog_rx) = mpsc::channel(1); let (done_tx, mut done_rx) = oneshot::channel(); tokio::spawn({ let (from, to) = (from.to_owned(), to.to_owned()); async move { - done_tx.send(provider::copy(from, to, cha).await).ok(); + done_tx.send(provider::copy(from, to, attrs).await).ok(); } }); diff --git a/yazi-vfs/src/provider/gate.rs b/yazi-vfs/src/provider/gate.rs index ec2c2a1a..8031e902 100644 --- a/yazi-vfs/src/provider/gate.rs +++ b/yazi-vfs/src/provider/gate.rs @@ -1,6 +1,6 @@ use std::{io, path::Path}; -use yazi_fs::{cha::Cha, provider::FileBuilder}; +use yazi_fs::provider::{Attrs, FileBuilder}; use yazi_shared::scheme::SchemeRef; pub enum Gate { @@ -27,10 +27,10 @@ impl FileBuilder for Gate { self } - fn cha(&mut self, cha: Cha) -> &mut Self { + fn attrs(&mut self, attrs: Attrs) -> &mut Self { match self { - Self::Local(g) => _ = g.cha(cha), - Self::Sftp(g) => _ = g.cha(cha), + Self::Local(g) => _ = g.attrs(attrs), + Self::Sftp(g) => _ = g.attrs(attrs), }; self } diff --git a/yazi-vfs/src/provider/provider.rs b/yazi-vfs/src/provider/provider.rs index 6628cb8f..1ddddc99 100644 --- a/yazi-vfs/src/provider/provider.rs +++ b/yazi-vfs/src/provider/provider.rs @@ -1,7 +1,7 @@ use std::{io, path::{Path, PathBuf}}; -use tokio::io::{BufReader, BufWriter}; -use yazi_fs::{cha::Cha, provider::{Provider, local::Local}}; +use tokio::io::{AsyncWriteExt, BufReader, BufWriter}; +use yazi_fs::{cha::Cha, provider::{Attrs, Provider, local::Local}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlBuf, UrlCow}}; use super::{Providers, ReadDir, RwFile}; @@ -57,7 +57,7 @@ where }) } -pub async fn copy(from: U, to: V, cha: Cha) -> io::Result +pub async fn copy(from: U, to: V, attrs: Attrs) -> io::Result where U: AsUrl, V: AsUrl, @@ -65,9 +65,9 @@ where let (from, to) = (from.as_url(), to.as_url()); match (from.as_path(), to.as_path()) { - (Some(from), Some(to)) => Local.copy(from, to, cha).await, + (Some(from), Some(to)) => Local.copy(from, to, attrs).await, (None, None) if from.scheme.covariant(to.scheme) => { - Providers::new(from).await?.copy(from.loc, to.loc, cha).await + Providers::new(from).await?.copy(from.loc, to.loc, attrs).await } (Some(_), None) | (None, Some(_)) | (None, None) => { let src = Providers::new(from).await?.open(from.loc).await?; @@ -75,10 +75,11 @@ where let mut reader = BufReader::with_capacity(524288, src); let mut writer = BufWriter::with_capacity(524288, dist); - let written = tokio::io::copy(&mut reader, &mut writer).await?; - writer.into_inner().set_cha(cha).await.ok(); + writer.flush().await?; + writer.get_ref().set_attrs(attrs).await.ok(); + writer.shutdown().await.ok(); Ok(written) } } diff --git a/yazi-vfs/src/provider/providers.rs b/yazi-vfs/src/provider/providers.rs index 80806270..aac8c07e 100644 --- a/yazi-vfs/src/provider/providers.rs +++ b/yazi-vfs/src/provider/providers.rs @@ -1,7 +1,7 @@ use std::{io, path::{Path, PathBuf}, sync::Arc}; use yazi_config::vfs::{ProviderSftp, Vfs}; -use yazi_fs::{cha::Cha, provider::{Provider, local::Local}}; +use yazi_fs::{cha::Cha, provider::{Attrs, Provider, local::Local}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, Url, UrlCow}}; pub(super) struct Providers<'a>(Inner<'a>); @@ -62,14 +62,14 @@ impl Provider for Providers<'_> { } } - async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result + async fn copy(&self, from: P, to: Q, attrs: Attrs) -> io::Result where P: AsRef, Q: AsRef, { match self.0 { - Inner::Regular | Inner::Search(_) => Local.copy(from, to, cha).await, - Inner::Sftp((p, _)) => p.copy(from, to, cha).await, + Inner::Regular | Inner::Search(_) => Local.copy(from, to, attrs).await, + Inner::Sftp((p, _)) => p.copy(from, to, attrs).await, } } diff --git a/yazi-vfs/src/provider/rw_file.rs b/yazi-vfs/src/provider/rw_file.rs index a4a0174b..8f8cb1df 100644 --- a/yazi-vfs/src/provider/rw_file.rs +++ b/yazi-vfs/src/provider/rw_file.rs @@ -1,7 +1,7 @@ use std::{io, pin::Pin}; use tokio::io::{AsyncRead, AsyncWrite}; -use yazi_fs::cha::Cha; +use yazi_fs::provider::Attrs; pub enum RwFile { Tokio(tokio::fs::File), @@ -17,19 +17,21 @@ impl From for RwFile { } impl RwFile { - pub async fn set_cha(&self, cha: Cha) -> io::Result<()> { + pub async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> { match self { Self::Tokio(f) => { let std = f.try_clone().await?.into_std().await; tokio::task::spawn_blocking(move || { #[cfg(unix)] - std.set_permissions(cha.into()).ok(); - std.set_times(cha.into()).ok(); + if let Some(mode) = attrs.mode { + std.set_permissions(mode.into()).ok(); + } + std.set_times(attrs.into()).ok(); }) .await?; } Self::Sftp(f) => { - f.fsetstat(&super::sftp::Cha(cha).into()).await?; + f.fsetstat(&super::sftp::Attrs(attrs).into()).await?; } } diff --git a/yazi-vfs/src/provider/sftp/gate.rs b/yazi-vfs/src/provider/sftp/gate.rs index 88e73247..0f10ecb5 100644 --- a/yazi-vfs/src/provider/sftp/gate.rs +++ b/yazi-vfs/src/provider/sftp/gate.rs @@ -1,15 +1,15 @@ use std::{io, path::Path}; use yazi_config::vfs::{ProviderSftp, Vfs}; -use yazi_fs::{cha::Cha, provider::FileBuilder}; -use yazi_sftp::fs::{Attrs, Flags}; +use yazi_fs::provider::{Attrs, FileBuilder}; +use yazi_sftp::fs::Flags; use yazi_shared::scheme::SchemeRef; pub struct Gate { sftp: super::Sftp, append: bool, - cha: Option, + attrs: Attrs, create: bool, create_new: bool, read: bool, @@ -25,8 +25,8 @@ impl FileBuilder for Gate { self } - fn cha(&mut self, cha: Cha) -> &mut Self { - self.cha = Some(cha); + fn attrs(&mut self, attrs: Attrs) -> &mut Self { + self.attrs = attrs; self } @@ -50,7 +50,7 @@ impl FileBuilder for Gate { sftp, append: false, - cha: None, + attrs: Attrs::default(), create: false, create_new: false, read: false, @@ -83,7 +83,7 @@ impl FileBuilder for Gate { flags |= Flags::WRITE; } - let attrs = self.cha.map(super::Cha).map_or(Attrs::default(), Attrs::from); + let attrs = super::Attrs(self.attrs).into(); Ok(self.sftp.op().await?.open(&path, flags, &attrs).await?) } diff --git a/yazi-vfs/src/provider/sftp/metadata.rs b/yazi-vfs/src/provider/sftp/metadata.rs index 8b3baaf8..5e2282a1 100644 --- a/yazi-vfs/src/provider/sftp/metadata.rs +++ b/yazi-vfs/src/provider/sftp/metadata.rs @@ -2,22 +2,26 @@ use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}}; use yazi_fs::cha::ChaKind; -pub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha); +// --- Attrs +pub(crate) struct Attrs(pub(crate) yazi_fs::provider::Attrs); -impl From for yazi_sftp::fs::Attrs { - fn from(cha: Cha) -> Self { +impl From for yazi_sftp::fs::Attrs { + fn from(attrs: Attrs) -> Self { Self { - size: Some(cha.0.len), - uid: Some(cha.0.uid), - gid: Some(cha.0.gid), - perm: Some(cha.0.mode.bits() as u32), - atime: cha.0.atime_dur().ok().map(|d| d.as_secs() as u32), - mtime: cha.0.mtime_dur().ok().map(|d| d.as_secs() as u32), + size: None, + uid: None, + gid: None, + perm: attrs.0.mode.map(|m| m.bits() as u32), + atime: attrs.0.atime_dur().map(|d| d.as_secs() as u32), + mtime: attrs.0.mtime_dur().map(|d| d.as_secs() as u32), extended: Default::default(), } } } +// --- Cha +pub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha); + impl TryFrom<&yazi_sftp::fs::DirEntry> for Cha { type Error = io::Error; diff --git a/yazi-vfs/src/provider/sftp/sftp.rs b/yazi-vfs/src/provider/sftp/sftp.rs index d91f5e00..560b4308 100644 --- a/yazi-vfs/src/provider/sftp/sftp.rs +++ b/yazi-vfs/src/provider/sftp/sftp.rs @@ -1,7 +1,7 @@ use std::{io, path::{Path, PathBuf}, sync::Arc}; use russh::keys::PrivateKeyWithHashAlg; -use tokio::io::{BufReader, BufWriter}; +use tokio::io::{AsyncWriteExt, BufReader, BufWriter}; use yazi_config::vfs::ProviderSftp; use yazi_fs::provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local}; use yazi_sftp::fs::{Attrs, Flags}; @@ -77,12 +77,12 @@ impl Provider for Sftp { similar.map(|n| parent.join(n)).ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) } - async fn copy(&self, from: P, to: Q, cha: yazi_fs::cha::Cha) -> io::Result + async fn copy(&self, from: P, to: Q, attrs: yazi_fs::provider::Attrs) -> io::Result where P: AsRef, Q: AsRef, { - let attrs = Attrs::from(Cha(cha)); + let attrs = Attrs::from(super::Attrs(attrs)); let op = self.op().await?; let from = op.open(&from, Flags::READ, &Attrs::default()).await?; @@ -90,10 +90,11 @@ impl Provider for Sftp { let mut reader = BufReader::with_capacity(524288, from); let mut writer = BufWriter::with_capacity(524288, to); - let written = tokio::io::copy(&mut reader, &mut writer).await?; - writer.into_inner().fsetstat(&attrs).await.ok(); + writer.flush().await?; + writer.get_ref().fsetstat(&attrs).await.ok(); + writer.shutdown().await.ok(); Ok(written) } @@ -158,7 +159,16 @@ impl Provider for Sftp { P: AsRef, Q: AsRef, { - Ok(self.op().await?.rename(&from, &to).await?) + let op = self.op().await?; + match op.rename_posix(&from, &to).await { + Ok(()) => {} + Err(yazi_sftp::Error::Unsupported) => { + op.remove(&to).await?; + op.rename(&from, &to).await?; + } + Err(e) => Err(e)?, + } + Ok(()) } async fn symlink(&self, original: P, link: Q, _is_dir: F) -> io::Result<()> diff --git a/yazi-watcher/src/local/local.rs b/yazi-watcher/src/local/local.rs index fe263109..2525e59c 100644 --- a/yazi-watcher/src/local/local.rs +++ b/yazi-watcher/src/local/local.rs @@ -2,7 +2,7 @@ use std::{path::Path, time::Duration}; use anyhow::Result; use hashbrown::HashSet; -use notify::{ErrorKind::WatchNotFound, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher}; +use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Watcher}; use tokio::{pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_fs::{File, FilesOp, provider}; @@ -34,13 +34,17 @@ impl Local { } pub(crate) fn watch(&mut self, path: &Path) -> Result<()> { - Ok(self.0.watch(path, RecursiveMode::NonRecursive)?) + match self.0.watch(path, RecursiveMode::NonRecursive) { + Ok(()) => Ok(()), + Err(e) if matches!(e.kind, notify::ErrorKind::PathNotFound) => Ok(()), + Err(e) => Err(e)?, + } } pub(crate) fn unwatch(&mut self, path: &Path) -> Result<()> { match self.0.unwatch(path) { Ok(()) => Ok(()), - Err(e) if matches!(e.kind, WatchNotFound) => Ok(()), + Err(e) if matches!(e.kind, notify::ErrorKind::WatchNotFound) => Ok(()), Err(e) => Err(e)?, } }