From 4120311bf62e48e6a08ba322aad4e292ee36f434 Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Wed, 16 Sep 2026 19:32:06 +0200 Subject: [PATCH] build(remotefs): migrate to version 1 (#449) * build(remotefs): migrate to version 1 * fix(remote): preserve POSIX paths on Windows --- Cargo.lock | 1421 +++++++---------- Cargo.toml | 22 +- src/explorer.rs | 80 +- src/explorer/formatter.rs | 486 +++--- src/filetransfer.rs | 1 + src/filetransfer/remotefs_builder.rs | 95 +- src/filetransfer/wrapper.rs | 154 ++ src/host.rs | 4 +- src/host/bridge.rs | 221 ++- src/host/localhost.rs | 26 +- src/host/remote_bridged.rs | 327 ++-- src/host/remote_bridged/temp_mapped_file.rs | 13 + .../activities/filetransfer/actions/edit.rs | 25 +- .../filetransfer/actions/file_size.rs | 43 +- .../filetransfer/actions/newfile.rs | 2 +- .../activities/filetransfer/actions/open.rs | 8 + .../components/popups/file_info.rs | 24 +- .../activities/filetransfer/lib/transfer.rs | 68 +- .../filetransfer/session/transfer.rs | 135 +- src/utils/test_helpers.rs | 11 +- 20 files changed, 1760 insertions(+), 1406 deletions(-) create mode 100644 src/filetransfer/wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index ad3b854..5cb8f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -80,6 +93,23 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.104" @@ -167,6 +197,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "arrayvec" version = "0.7.8" @@ -181,7 +217,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -198,9 +234,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" +checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -265,9 +301,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -293,9 +329,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.144.0" +version = "1.146.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30dc8bf6baaf7d46336a0ca2c69f223d9b90d7a801fb3e28f7ea17b00dc6b1de" +checksum = "2cd651b4400d4011b8927b83a9552bf90ff11e6e5da0b9f0a7583247aceec971" dependencies = [ "arc-swap", "aws-credential-types", @@ -330,9 +366,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.108.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15301b04372832947916607983b114b3374b9db0be058a00fb7513800de1f05" +checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" dependencies = [ "arc-swap", "aws-credential-types", @@ -356,9 +392,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.110.0" +version = "1.111.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cc2c205cb27108183cf1856333f7d584c2ba0f505421b4209ca5828f9ea899" +checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" dependencies = [ "arc-swap", "aws-credential-types", @@ -382,9 +418,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.113.0" +version = "1.114.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" +checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" dependencies = [ "arc-swap", "aws-credential-types", @@ -468,9 +504,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.61.2" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6de526c7b567420a31bc283657a7921b45c4cafe0827fdf2490713dcc770c28f" +checksum = "80c2051c2f1016fb8e6548dd07b8bc2ac9c3fe583721444b92f515e856d31609" dependencies = [ "aws-smithy-types", "bytes", @@ -501,16 +537,16 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfd138fac0337cee7516c352757ea73b9f2266e57d0bcb5bc70e9547e45aef1" +checksum = "7bd25384a4e437aa8d8f339afad4b69e786b936a7cb10db668a7aaf66717b1a8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2 0.4.19", + "h2", "http 1.5.0", - "hyper 1.11.1", + "hyper", "hyper-rustls", "hyper-util", "pin-project-lite", @@ -519,15 +555,15 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tower 0.5.3", + "tower", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.63.0" +version = "0.63.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" +checksum = "3385d469edbe8b60cc72002784652b5efca39178192aa9cc4b44c9875c6bdc18" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -545,9 +581,9 @@ dependencies = [ [[package]] name = "aws-smithy-query" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512346c7212ab7436df2d77a16d976a468ae44a418835511d2a69269810aaf62" +checksum = "f1d1d71f6562be974caa85442ecd90194c40fdb5df045f182a6c2e872ce95056" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -558,9 +594,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.14.0" +version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" +checksum = "3296253d3a91b3f938a3f2bcce4daebadcb4aa4228153fcec90f9d23532b4484" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -584,9 +620,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.15.0" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" +checksum = "6d881a7b7ad179fd6611680c9de89f716fb00ab40299a9a7b8c6913e8f7511a8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -613,9 +649,9 @@ dependencies = [ [[package]] name = "aws-smithy-schema" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" +checksum = "e8f395d93304280b64b7632fea798d177e74897fe7f063416ce627cd6fa24829" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -624,9 +660,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.2" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" +checksum = "0b791f3ac597193fe1d08b82366986eb1f5bc31f2ac6c194c0855276116c76cd" dependencies = [ "base64-simd", "bytes", @@ -650,9 +686,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce84f71c72fee2cbbadde6e7d082f5fb466e3a84733855295fa7aafd1b31b7d8" +checksum = "0b932c8d6dc127fc980eecd78f8694ae9b9551b69a93a7def2a199c1c0033daf" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -662,9 +698,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" +checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -687,12 +723,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -740,9 +770,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] @@ -902,9 +932,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.4" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" dependencies = [ "find-msvc-tools", "jobserver", @@ -1055,16 +1085,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1081,6 +1101,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpubits" version = "0.1.1" @@ -1126,9 +1152,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] @@ -1141,9 +1167,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1151,18 +1177,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crossterm" @@ -1170,7 +1196,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "crossterm_winapi", "derive_more", "document-features", @@ -1314,37 +1340,14 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - [[package]] name = "darling" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core 0.24.1", - "darling_macro 0.24.1", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", + "darling_core", + "darling_macro", ] [[package]] @@ -1357,18 +1360,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 3.0.4", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1377,9 +1369,9 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core 0.24.1", + "darling_core", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1402,6 +1394,40 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +[[package]] +name = "dav-xml" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4570b88a7b7aa9dd517e234c6071e38fb5e13d2f032118237ba74218d8d25fca" +dependencies = [ + "bytes", + "bytestring", + "http 1.5.0", + "httpdate", + "indexmap 2.14.2", + "mime", + "nonempty", + "quick-xml 0.42.0", + "thiserror", + "time", +] + +[[package]] +name = "dav-xml-client" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c498054ec2499aa0d316222c95a47433042b99021190613fbb40e95cea7027" +dependencies = [ + "dav-xml", + "http 1.5.0", + "httpdate", + "mime", + "reqwest 0.13.5", + "thiserror", + "time", + "ureq", +] + [[package]] name = "dbus" version = "0.9.12" @@ -1470,7 +1496,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -1497,9 +1523,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" dependencies = [ "const-oid 0.10.2", "pem-rfc7468 1.0.0", @@ -1596,6 +1622,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -1614,7 +1649,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", ] @@ -1626,7 +1661,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1670,7 +1705,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ - "der 0.8.1", + "der 0.8.2", "digest 0.11.3", "elliptic-curve 0.14.1", "rfc6979 0.6.0", @@ -1796,11 +1831,26 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" dependencies = [ "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", ] [[package]] @@ -1881,9 +1931,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flate2" @@ -2016,7 +2066,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2090,9 +2140,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -2125,7 +2177,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "libgit2-sys", "log", @@ -2168,14 +2220,14 @@ dependencies = [ "http 1.5.0", "jiff", "jsonwebtoken", - "reqwest 0.13.4", + "reqwest 0.13.5", "rustc_version", "rustls", "rustls-pki-types", "serde", "serde_json", "sha2 0.11.0", - "thiserror 2.0.20", + "thiserror", "time", "tokio", "url", @@ -2196,7 +2248,7 @@ dependencies = [ "rand 0.10.2", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-stream", ] @@ -2213,11 +2265,11 @@ dependencies = [ "google-cloud-gax", "google-cloud-rpc", "google-cloud-wkt", - "h2 0.4.19", + "h2", "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper", "lazy_static", "opentelemetry", "opentelemetry-semantic-conventions", @@ -2226,16 +2278,16 @@ dependencies = [ "pin-project", "prost", "prost-types", - "reqwest 0.13.4", + "reqwest 0.13.5", "rustc_version", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-stream", "tonic", "tonic-prost", - "tower 0.5.3", + "tower", "tracing", "tracing-opentelemetry", ] @@ -2337,7 +2389,7 @@ dependencies = [ "serde_json", "serde_with", "sha2 0.11.0", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -2369,11 +2421,21 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.20", + "thiserror", "time", "url", ] +[[package]] +name = "granit-parser" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d03f81ad4732830d85cfd417a9f62cde6dadda4354d37d078a6084a19560aa2d" +dependencies = [ + "arraydeque", + "smallvec", +] + [[package]] name = "group" version = "0.13.0" @@ -2396,25 +2458,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.14.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.19" @@ -2427,7 +2470,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.1", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2468,30 +2511,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64 0.22.1", - "bytes", - "headers-core", - "http 1.5.0", - "httpdate", - "mime", - "sha1 0.10.7", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.5.0", -] - [[package]] name = "heck" version = "0.5.0" @@ -2624,9 +2643,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "ctutils", "subtle", @@ -2634,30 +2653,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.11.1" @@ -2668,7 +2663,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.19", + "h2", "http 1.5.0", "http-body 1.1.0", "httparse", @@ -2679,25 +2674,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-http-proxy" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1d471ea2f65ba45eddb1d6ab7d58ac2671d2d4ac14da5c6516ae1d97e1327a" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http 1.5.0", - "hyper 1.11.1", - "hyper-rustls", - "hyper-util", - "pin-project-lite", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "hyper-rustls" version = "0.27.9" @@ -2705,7 +2681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.5.0", - "hyper 1.11.1", + "hyper", "hyper-util", "log", "rustls", @@ -2722,7 +2698,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.11.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -2731,15 +2707,18 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.32", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -2754,12 +2733,12 @@ dependencies = [ "futures-util", "http 1.5.0", "http-body 1.1.0", - "hyper 1.11.1", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2", "tokio", "tower-service", "tracing", @@ -2811,11 +2790,32 @@ checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + [[package]] name = "icu_normalizer" version = "2.3.0" @@ -2865,6 +2865,8 @@ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -2872,6 +2874,28 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + [[package]] name = "ident_case" version = "1.0.1" @@ -2922,9 +2946,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2960,7 +2984,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "inotify-sys", "libc", ] @@ -3000,30 +3024,18 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" dependencies = [ - "darling 0.24.1", + "darling", "indoc", "proc-macro2", "quote", - "syn 3.0.4", -] - -[[package]] -name = "internal-russh-num-bigint" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8" -dependencies = [ - "num-integer", - "num-traits", - "rand 0.10.2", - "rand_core 0.10.1", + "syn 3.0.5", ] [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is-docker" @@ -3061,9 +3073,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ "defmt", "jiff-core", @@ -3078,18 +3090,19 @@ dependencies = [ [[package]] name = "jiff-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ "defmt", + "log", ] [[package]] name = "jiff-static" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" dependencies = [ "jiff-core", "proc-macro2", @@ -3124,7 +3137,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.20", + "thiserror", "walkdir", "windows-link 0.2.1", ] @@ -3173,9 +3186,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -3184,24 +3197,22 @@ dependencies = [ [[package]] name = "jsonpath-rust" -version = "0.5.1" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d8fe85bd70ff715f31ce8c739194b423d79811a19602115d611a3ec85d6200" +checksum = "d23892d4caa103325a558250e9f4e406d81cf64f7c01679ae7a64e0413321e51" dependencies = [ - "lazy_static", - "once_cell", "pest", "pest_derive", "regex", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] name = "jsonwebtoken" -version = "11.0.0" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881733cbc631fc9e472e24447ce32a64bedf2da498d6d8570b08edc87de71f65" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" dependencies = [ "aws-lc-rs", "base64 0.22.1", @@ -3215,14 +3226,13 @@ dependencies = [ [[package]] name = "k8s-openapi" -version = "0.22.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19501afb943ae5806548bc3ebd7f3374153ca057a38f480ef30adfde5ef09755" +checksum = "d9c6922f6afe80418dd6019818af5d0d34584c371780ff09b9752370c25b4abb" dependencies = [ "base64 0.22.1", - "chrono", + "jiff", "serde", - "serde-value", "serde_json", ] @@ -3234,7 +3244,7 @@ checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -3282,15 +3292,15 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", ] [[package]] name = "kube" -version = "0.92.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "231c5a5392d9e2a9b0d923199760d3f1dd73b95288f2871d16c7c90ba4954506" +checksum = "208d7fe1380066abb194812a8a8e303cb896a33bb3d7b3177b71bd03ff39bf18" dependencies = [ "k8s-openapi", "kube-client", @@ -3299,57 +3309,56 @@ dependencies = [ [[package]] name = "kube-client" -version = "0.92.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4bf54135062ff60e2a0dfb3e7a9c8e931fc4a535b4d6bd561e0a1371321c61" +checksum = "31e940a73033a7c5c7918b5ece7851d84571b58be25e937198da37fa13f116d3" dependencies = [ "base64 0.22.1", "bytes", - "chrono", "either", "futures", - "home", "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", - "hyper-http-proxy", + "hyper", "hyper-rustls", "hyper-timeout", "hyper-util", + "jiff", "jsonpath-rust", "k8s-openapi", "kube-core", "pem", - "rand 0.8.8", "rustls", - "rustls-pemfile 2.2.0", + "rustls-platform-verifier", "secrecy", "serde", + "serde-saphyr", "serde_json", - "serde_yaml", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-tungstenite", "tokio-util", - "tower 0.4.13", - "tower-http 0.5.2", + "tower", + "tower-http", "tracing", ] [[package]] name = "kube-core" -version = "0.92.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fb9bd8141cbc0fe6b0d9112d371679b4cb607b45c31dd68d92e40864a12975" +checksum = "a9d2353c118cf3462c352ee0b5bd5b0cf17990af456dfd8662d136ff9812eeb4" dependencies = [ - "chrono", + "derive_more", "form_urlencoded", "http 1.5.0", + "jiff", "k8s-openapi", "serde", + "serde-value", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -3418,9 +3427,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" dependencies = [ "libc", ] @@ -3443,7 +3452,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3496,9 +3505,9 @@ dependencies = [ [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "mac-notification-sys" @@ -3599,6 +3608,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" version = "0.2.18" @@ -3622,17 +3658,23 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", ] [[package]] -name = "nonempty" -version = "0.9.0" +name = "nohash-hasher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995defdca0a589acfdd1bd2e8e3b896b4d4f7675a31fd14c32611440c7f608e6" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" [[package]] name = "notify" @@ -3640,7 +3682,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "fsevent-sys", "inotify", "kqueue", @@ -3671,7 +3713,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3701,7 +3743,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-complex", "num-integer", "num-iter", @@ -3719,6 +3761,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.10.2", + "rand_core 0.10.1", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -3759,7 +3813,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-integer", "num-traits", ] @@ -3803,7 +3857,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dispatch2", "objc2", ] @@ -3820,7 +3874,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2", "libc", "objc2", @@ -3844,9 +3898,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "open" -version = "5.4.3" +version = "5.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c603ab8300cf18bc3b14146b19fe3dfcc4843ae5a400cd0e7a30b95aa366634" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" dependencies = [ "is-wsl", "libc", @@ -3858,7 +3912,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "foreign-types", "libc", @@ -3921,7 +3975,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.20", + "thiserror", "tracing", ] @@ -3944,7 +3998,7 @@ dependencies = [ "percent-encoding", "portable-atomic", "rand 0.9.5", - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -4023,9 +4077,9 @@ dependencies = [ [[package]] name = "pageant" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3adadc44070da6f464b0918655a12f5792c156e088d8c4082d13e27d94c3e791" +checksum = "6d8eab09a361a4afe0b1668be978cd97e4f052e927a92b0b608cf902965d49ce" dependencies = [ "base16ct 1.0.0", "byteorder", @@ -4035,7 +4089,7 @@ dependencies = [ "log", "rand 0.10.2", "sha2 0.11.0", - "thiserror 2.0.20", + "thiserror", "tokio", "windows 0.62.2", "windows-strings 0.5.1", @@ -4129,7 +4183,7 @@ dependencies = [ "libc", "log", "pavao-sys", - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -4201,9 +4255,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" dependencies = [ "memchr", "ucd-trie", @@ -4211,9 +4265,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" dependencies = [ "pest", "pest_generator", @@ -4221,9 +4275,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" dependencies = [ "pest", "pest_meta", @@ -4234,9 +4288,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" dependencies = [ "pest", ] @@ -4289,7 +4343,7 @@ version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der 0.8.1", + "der 0.8.2", "spki 0.8.0", ] @@ -4302,7 +4356,7 @@ dependencies = [ "aes 0.9.3", "aes-gcm", "cbc 0.2.1", - "der 0.8.1", + "der 0.8.2", "pbkdf2", "rand_core 0.10.1", "scrypt", @@ -4326,7 +4380,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.1", + "der 0.8.2", "pkcs5", "rand_core 0.10.1", "spki 0.8.0", @@ -4369,9 +4423,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -4382,6 +4436,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -4487,15 +4543,6 @@ dependencies = [ "prost", ] -[[package]] -name = "quick-xml" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.37.5" @@ -4506,10 +4553,19 @@ dependencies = [ ] [[package]] -name = "quinn" -version = "0.11.11" +name = "quick-xml" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ "bytes", "cfg_aliases", @@ -4518,8 +4574,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.5", - "thiserror 2.0.20", + "socket2", + "thiserror", "tokio", "tracing", "web-time", @@ -4527,9 +4583,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" dependencies = [ "aws-lc-rs", "bytes", @@ -4542,7 +4598,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.20", + "thiserror", "tinyvec", "tracing", "web-time", @@ -4557,7 +4613,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.5", + "socket2", "tracing", "windows-sys 0.61.2", ] @@ -4583,24 +4639,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -4615,16 +4660,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -4687,7 +4722,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "compact_str", "critical-section", "hashbrown 0.17.1", @@ -4697,7 +4732,7 @@ dependencies = [ "palette", "serde", "strum", - "thiserror 2.0.20", + "thiserror", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -4721,7 +4756,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "hashbrown 0.17.1", "indoc", "instability", @@ -4761,18 +4796,17 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -4792,7 +4826,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4832,36 +4866,42 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "remotefs" -version = "0.3.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b229ef0bf00ce8ae4cbe349bd8d71f3473e26de30efa637cfc53e08eaf6867f0" +checksum = "b561d031228e045c8a3de5446fc17cfa37bf494431f9267f8eb4e4d95a727b3c" dependencies = [ + "async-trait", + "futures-io", "log", - "thiserror 1.0.69", + "thiserror", + "tokio", + "tokio-util", "wildmatch", ] [[package]] name = "remotefs-aws-s3" -version = "0.4.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35899af55d8a847ee9ffb101677f42a3ff33d0ec21e33679e47fbfefa9dfc68c" +checksum = "03ec776079f71351bd8512413c4a0b4dba8e0ace57a56fb4ee21b56629b1eac2" dependencies = [ "aws-config", "aws-credential-types", "aws-sdk-s3", + "aws-smithy-types", + "bytes", + "futures-io", "log", "path-slash", "remotefs", - "thiserror 2.0.20", "tokio", ] [[package]] name = "remotefs-ftp" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af3d379199986cf5435240a0a4b159548897b6301536f33e25f81ffe6f5b2fb" +checksum = "740e877ddf267526b172c0ac28873ae3744a2516233150c81906389234180c74" dependencies = [ "log", "path-slash", @@ -4871,46 +4911,43 @@ dependencies = [ [[package]] name = "remotefs-gcs" -version = "0.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa6054c3e3da035b241a62f10d069928be5fbd7afcd0762cbf7aa0a8e90884" +checksum = "0dc8139f4881a8daee13ddc95bbc626f3007f6b2758de0eb0f1bc64fd941b47e" dependencies = [ "bytes", + "futures-io", "google-cloud-auth", "google-cloud-gax", "google-cloud-storage", "log", "path-slash", "remotefs", + "tempfile", "tokio", ] [[package]] name = "remotefs-kube" -version = "0.4.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ca6df262715fd3c88b58bcc9d219ed46ace67bf73e4216b9fa9b73eafd8426" +checksum = "78de0bbe43be847e1fdc595a92d202f129ce6855c6e29921f8dc4527f69815a2" dependencies = [ "chrono", - "futures-util", + "futures-io", "k8s-openapi", "kube", "lazy-regex", "log", - "path-slash", "remotefs", - "tar", - "tempfile", - "thiserror 1.0.69", "tokio", - "tokio-util", ] [[package]] name = "remotefs-smb" -version = "0.5.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d9b82c3ca50c6a7cbc923b286192178a6c675476802524e37b41a27c414c57" +checksum = "0cefcdd8d4061d679adcb7b3a47543fe7992a267d27b8d48f416103b3aaf6ec5" dependencies = [ "filetime", "libc", @@ -4922,82 +4959,36 @@ dependencies = [ [[package]] name = "remotefs-ssh" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db5bde9ec8d2f0154745200dc8fc9e6319ef5befd22860ca94c7321f413888" +checksum = "ac4c22b5d9dda14562e028c1acfe32117d85ec7d7114d34b54aac3c4af6d8be4" dependencies = [ "chrono", + "futures-io", "if-addrs", "lazy-regex", "log", - "path-slash", "remotefs", "russh", "russh-sftp", - "socket2 0.6.5", + "socket2", "ssh2-config", "tokio", ] [[package]] name = "remotefs-webdav" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2b34f3ac2069b106c65b2d0c91b9b4b24a0b5fe9585b49f50fff4135f2cff8" +checksum = "e86912166cdee378f4fd421a0d3027d6802f38bc41801cb91e5dab9510861f3f" dependencies = [ - "bytes", - "bytestring", + "dav-xml-client", + "futures", + "futures-io", "http 1.5.0", - "httpdate", - "indexmap 2.14.1", "log", - "mime", - "nonempty", - "quick-xml 0.31.0", "remotefs", - "rustydav", - "thiserror 1.0.69", - "time", -] - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", ] [[package]] @@ -5011,11 +5002,11 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.19", + "h2", "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper", "hyper-rustls", "hyper-util", "js-sys", @@ -5028,11 +5019,11 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-rustls", - "tower 0.5.3", - "tower-http 0.6.11", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -5043,23 +5034,26 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "futures-core", "futures-util", + "h2", "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", "mime_guess", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -5069,12 +5063,13 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", - "tower 0.5.3", - "tower-http 0.6.11", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -5159,13 +5154,13 @@ dependencies = [ [[package]] name = "russh" -version = "0.63.1" +version = "0.63.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bab1b87d915817d5d9cc352637cd40d5f0b298a48c6309af9156a4addc3031" +checksum = "036204edbd199552a5b3832f63c60dcdf395dc44c7f06b4af1c0e8139cc11bce" dependencies = [ "aes 0.9.3", "aws-lc-rs", - "bitflags 2.13.1", + "bitflags 2.13.2", "block-padding 0.4.2", "byteorder", "bytes", @@ -5176,7 +5171,7 @@ dependencies = [ "curve25519-dalek 5.0.0", "data-encoding", "delegate", - "der 0.8.1", + "der 0.8.2", "digest 0.11.3", "ecdsa 0.17.0", "ed25519-dalek 3.0.0", @@ -5190,13 +5185,12 @@ dependencies = [ "hex-literal", "hmac 0.13.0", "inout 0.2.2", - "internal-russh-num-bigint", "keccak", "log", "md5", "ml-kem", "module-lattice", - "num-bigint", + "num-bigint 0.5.1", "p256 0.14.0", "p384", "p521", @@ -5222,7 +5216,7 @@ dependencies = [ "ssh-encoding", "ssh-key", "subtle", - "thiserror 2.0.20", + "thiserror", "tokio", "typenum", "universal-hash", @@ -5247,7 +5241,7 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de67aace74530a29086db0671fa200c470a58eb380081f28ad512ffb0c5356b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "chrono", "dashmap", @@ -5255,7 +5249,7 @@ dependencies = [ "log", "serde", "serde_bytes", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-util", "wasm-bindgen-futures", @@ -5294,7 +5288,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5307,7 +5301,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5316,9 +5310,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "log", @@ -5342,24 +5336,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -5376,7 +5352,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -5415,15 +5391,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "rustydav" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4c86c47126ac8bfc573084610e93f4ca8726f3ae7bf6c64bd60476731b6e42" -dependencies = [ - "reqwest 0.11.27", -] - [[package]] name = "ryu" version = "1.0.23" @@ -5522,7 +5489,7 @@ checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct 1.0.0", "ctutils", - "der 0.8.1", + "der 0.8.2", "hybrid-array", "subtle", "zeroize", @@ -5530,11 +5497,10 @@ dependencies = [ [[package]] name = "secrecy" -version = "0.8.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ - "serde", "zeroize", ] @@ -5544,8 +5510,8 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", + "bitflags 2.13.2", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5580,7 +5546,7 @@ checksum = "d832c086ece0dacc29fb2947bb4219b8f6e12fe9e40b7108f9e57c4224e47b5c" dependencies = [ "either", "flate2", - "hyper 1.11.1", + "hyper", "indicatif", "log", "quick-xml 0.37.5", @@ -5612,6 +5578,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-saphyr" +version = "0.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4" +dependencies = [ + "ahash", + "annotate-snippets", + "base64 0.22.1", + "encoding_rs_io", + "getrandom 0.3.4", + "granit-parser", + "nohash-hasher", + "num-traits", + "serde_core", + "smallvec", + "zmij", +] + [[package]] name = "serde-value" version = "0.7.0" @@ -5649,7 +5634,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5688,16 +5673,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.1", + "indexmap 2.14.2", "jiff", "schemars 0.9.0", "schemars 1.2.2", @@ -5709,27 +5694,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.14.1", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "syn 3.0.5", ] [[package]] @@ -5764,7 +5736,7 @@ checksum = "a22144e767da4ddd8416dbf383700542ffd8a5dc493dfecedfe1fe3ad03c98ae" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5847,7 +5819,7 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -5948,9 +5920,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.0" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smawk" @@ -5958,16 +5930,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.5" @@ -6001,7 +5963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.1", + "der 0.8.2", ] [[package]] @@ -6075,11 +6037,11 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d2108aade44855426db8417ee923f1cfc0c4b38b4bb4bd26d936f0d0f79e0f" dependencies = [ - "bitflags 2.13.1", - "dirs", + "bitflags 2.13.2", + "dirs 6.0.0", "glob", "log", - "thiserror 2.0.20", + "thiserror", "wildmatch", ] @@ -6130,15 +6092,15 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "suppaftp" -version = "11.0.0" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5095831abc0d7944a2d50d6ec6abcd75b9d165d9377deb3e45798cae2343a" +checksum = "9c6978f1b04fc86f122f767b88a9d1d89e80cfe3dc97d670598507d713c4ce48" dependencies = [ "chrono", "lazy-regex", "log", "native-tls", - "thiserror 2.0.20", + "thiserror", ] [[package]] @@ -6154,21 +6116,15 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -6180,34 +6136,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "syn 3.0.5", ] [[package]] @@ -6227,7 +6162,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" dependencies = [ - "thiserror 2.0.20", + "thiserror", "windows 0.61.3", "windows-version", ] @@ -6263,14 +6198,14 @@ dependencies = [ "apple-native-keyring-store", "argh", "base64 0.23.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytesize", "cbc 0.2.1", "cfg_aliases", "chrono", "content_inspector", "dbus-secret-service-keyring-store", - "dirs", + "dirs 7.0.0", "edit", "filetime", "keyring-core", @@ -6302,7 +6237,7 @@ dependencies = [ "simplelog", "ssh2-config", "tempfile", - "thiserror 2.0.20", + "thiserror", "tokio", "toml", "tui-realm-stdlib", @@ -6317,42 +6252,22 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" dependencies = [ + "icu_segmenter", "smawk", - "unicode-linebreak", "unicode-width", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -6363,7 +6278,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -6414,23 +6329,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" @@ -6443,7 +6350,7 @@ dependencies = [ "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.5", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -6456,7 +6363,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -6471,9 +6378,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ "rustls", "tokio", @@ -6493,9 +6400,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -6511,6 +6418,7 @@ checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "futures-util", "libc", @@ -6520,11 +6428,11 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.5+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "serde_core", "serde_spanned", "toml_datetime", @@ -6568,17 +6476,17 @@ dependencies = [ "http 1.5.0", "http-body 1.1.0", "http-body-util", - "hyper 1.11.1", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "rustls-native-certs", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-rustls", "tokio-stream", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -6595,23 +6503,6 @@ dependencies = [ "tonic", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -6620,10 +6511,10 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.14.1", + "indexmap 2.14.2", "pin-project-lite", "slab", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-util", "tower-layer", @@ -6631,40 +6522,24 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-http" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" -dependencies = [ - "base64 0.21.7", - "bitflags 2.13.1", - "bytes", - "http 1.5.0", - "http-body 1.1.0", - "http-body-util", - "mime", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower-http" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "base64 0.22.1", + "bitflags 2.13.2", "bytes", "futures-util", "http 1.5.0", "http-body 1.1.0", + "mime", "pin-project-lite", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", + "tracing", "url", ] @@ -6772,12 +6647,12 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7d70af0dff16882cfeb423cbec374055e941ac25b205a31f9f5607e831ebac" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "crossterm", "dyn-clone", "lazy-regex", "ratatui", - "thiserror 2.0.20", + "thiserror", "tuirealm_derive", ] @@ -6794,20 +6669,18 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.23.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ - "byteorder", "bytes", "data-encoding", "http 1.5.0", "httparse", "log", - "rand 0.8.8", + "rand 0.9.5", "sha1 0.10.7", - "thiserror 1.0.69", - "utf-8", + "thiserror", ] [[package]] @@ -6834,12 +6707,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - [[package]] name = "unicode-segmentation" version = "1.13.3" @@ -6873,12 +6740,6 @@ dependencies = [ "ctutils", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.7.1" @@ -6891,6 +6752,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64 0.23.1", + "der 0.8.2", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64 0.23.1", + "http 1.5.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -6910,10 +6800,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] -name = "utf-8" -version = "0.7.6" +name = "utf8-zero" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "utf8_iter" @@ -6923,9 +6813,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.26.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -7041,9 +6931,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -7054,9 +6944,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -7064,9 +6954,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7074,22 +6964,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -7109,9 +6999,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -7401,15 +7291,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -7446,21 +7327,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -7521,12 +7387,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -7539,12 +7399,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -7557,12 +7411,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -7587,12 +7435,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -7605,12 +7447,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -7623,12 +7459,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -7641,12 +7471,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -7665,16 +7489,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.57.1" @@ -7683,13 +7497,14 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "wnaf" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" dependencies = [ "ff 0.14.0", "group 0.14.0", "hybrid-array", + "primefield", ] [[package]] @@ -7733,30 +7548,30 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -7774,13 +7589,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "synstructure", ] @@ -7813,6 +7628,7 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] @@ -7821,6 +7637,7 @@ version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -7834,7 +7651,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -7848,9 +7665,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.14.1", + "indexmap 2.14.2", "memchr", - "thiserror 2.0.20", + "thiserror", "time", "zopfli", ] @@ -7863,14 +7680,14 @@ checksum = "dba6063ff82cdbd9a765add16d369abe81e520f836054e997c2db217ceca40c0" dependencies = [ "base64 0.22.1", "ed25519-dalek 2.2.0", - "thiserror 2.0.20", + "thiserror", ] [[package]] name = "zlib-rs" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index c07a0d1..a191731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ bytesize = "2" cbc = { version = "0.2", features = ["alloc"] } chrono = "0.4" content_inspector = "0.2" -dirs = "6" +dirs = "7" edit = "0.1" filetime = "0.2" keyring-core = "1" @@ -59,13 +59,13 @@ nucleo = "0.5" open = "5" rand = "0.10" regex = "1" -remotefs = "0.3" -remotefs-aws-s3 = "0.4" -remotefs-gcs = "0.1" -remotefs-kube = "0.4" -remotefs-smb = { version = "0.5", default-features = false, optional = true, features = ["find", "pavao"] } -remotefs-ssh = { version = "0.9", default-features = false, features = ["russh"] } -remotefs-webdav = "0.2" +remotefs = "1" +remotefs-aws-s3 = { version = "1", features = ["tokio"] } +remotefs-gcs = { version = "1", features = ["tokio"] } +remotefs-kube = { version = "1", features = ["tokio"] } +remotefs-smb = { version = "1", default-features = false, optional = true, features = ["find", "pavao"] } +remotefs-ssh = { version = "1", default-features = false, features = ["russh"] } +remotefs-webdav = { version = "1", features = ["tokio"] } rpassword = "7" self_update = { version = "0.42", default-features = false, features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate", "rustls"] } semver = "1" @@ -76,7 +76,7 @@ simplelog = "0.12" ssh2-config = "0.8" tempfile = "3" thiserror = "2" -tokio = { version = "1", features = ["rt"] } +tokio = { version = "1", features = ["rt-multi-thread"] } toml = "1" tui-realm-stdlib = "4" tui-term = "0.3" @@ -89,11 +89,11 @@ wildmatch = "2" dbus-secret-service-keyring-store = { version = "1", features = ["crypto-rust", "vendored"] } [target."cfg(target_family = \"unix\")".dependencies] -remotefs-ftp = { version = "0.4", features = ["native-tls", "native-tls-vendored"] } +remotefs-ftp = { version = "1", features = ["native-tls", "native-tls-vendored"] } uzers = "0.12" [target."cfg(target_family = \"windows\")".dependencies] -remotefs-ftp = { version = "0.4", features = ["native-tls"] } +remotefs-ftp = { version = "1", features = ["native-tls"] } [target."cfg(target_os = \"macos\")".dependencies] apple-native-keyring-store = { version = "1", features = ["keychain"] } diff --git a/src/explorer.rs b/src/explorer.rs index 862f817..0ebc332 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -565,20 +565,18 @@ mod tests { let explorer: FileExplorer = FileExplorer::default(); // Create fs entry let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = File::new( + "/bar.txt", + Metadata::default() + .accessed(t) + .created(t) + .modified(t) + .file_type(FileType::File) + .uid(0) + .gid(0) + .mode(UnixPex::from(0o644)) + .size(8192), + ); #[cfg(posix)] assert_eq!( explorer.fmt_file(&entry), @@ -690,47 +688,37 @@ mod tests { fn make_fs_entry(name: &str, is_dir: bool) -> File { let t: SystemTime = SystemTime::now(); - let metadata = Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: if is_dir { + let metadata = Metadata::default() + .accessed(t) + .created(t) + .modified(t) + .file_type(if is_dir { FileType::Directory } else { FileType::File - }, - symlink: None, - gid: Some(0), - uid: Some(0), - mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), - size: 64, - }; - File { - path: PathBuf::from(name), - metadata, - } + }) + .gid(0) + .uid(0) + .mode(UnixPex::from(if is_dir { 0o755 } else { 0o644 })) + .size(64); + File::new(name, metadata) } fn make_fs_entry_with_size(name: &str, is_dir: bool, size: usize) -> File { let t: SystemTime = SystemTime::now(); - let metadata = Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: if is_dir { + let metadata = Metadata::default() + .accessed(t) + .created(t) + .modified(t) + .file_type(if is_dir { FileType::Directory } else { FileType::File - }, - symlink: None, - gid: Some(0), - uid: Some(0), - mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), - size: size as u64, - }; - File { - path: PathBuf::from(name), - metadata, - } + }) + .gid(0) + .uid(0) + .mode(UnixPex::from(if is_dir { 0o755 } else { 0o644 })) + .size(size as u64); + File::new(name, metadata) } } diff --git a/src/explorer/formatter.rs b/src/explorer/formatter.rs index e37f320..c783290 100644 --- a/src/explorer/formatter.rs +++ b/src/explorer/formatter.rs @@ -363,8 +363,10 @@ impl Formatter { ) -> String { if fsentry.is_file() { // Get byte size - let size: ByteSize = ByteSize(fsentry.metadata().size); - let mut fmt = size.display().si().to_string(); + let mut fmt = fsentry.metadata().size.map_or_else( + || String::from("?"), + |size| ByteSize(size).display().si().to_string(), + ); // pad with up to len 10 let pad = 10usize.saturating_sub(fmt.len()); for _ in 0..pad { @@ -534,7 +536,6 @@ impl Formatter { #[cfg(test)] mod tests { - use std::path::PathBuf; use std::time::SystemTime; use pretty_assertions::assert_eq; @@ -542,26 +543,61 @@ mod tests { use super::*; + #[expect( + clippy::too_many_arguments, + reason = "The fixture parameters mirror the optional remotefs metadata fields." + )] + fn fixture( + path: &str, + file_type: FileType, + size: Option, + symlink: Option<&str>, + uid: Option, + gid: Option, + mode: Option, + timestamp: Option, + ) -> File { + let mut metadata = Metadata::default().file_type(file_type); + if let Some(size) = size { + metadata = metadata.size(size); + } + if let Some(symlink) = symlink { + metadata = metadata.symlink(symlink); + } + if let Some(uid) = uid { + metadata = metadata.uid(uid); + } + if let Some(gid) = gid { + metadata = metadata.gid(gid); + } + if let Some(mode) = mode { + metadata = metadata.mode(mode); + } + if let Some(timestamp) = timestamp { + metadata = metadata + .accessed(timestamp) + .created(timestamp) + .modified(timestamp); + } + File::new(path, metadata) + } + #[test] fn test_fs_explorer_formatter_callchain() { // Make a dummy formatter let dummy_formatter: Formatter = Formatter::new(""); // Make a dummy entry let t: SystemTime = SystemTime::now(); - let dummy_entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: Some(UnixPex::from(0o644)), - }, - }; + let dummy_entry = fixture( + "/bar.txt", + FileType::File, + Some(8192), + None, + Some(0), + Some(0), + Some(UnixPex::from(0o644)), + Some(t), + ); let prefix: String = String::from("h"); let mut callchain: CallChainBlock = CallChainBlock::new(dummy_fmt, prefix, None, None); assert!(callchain.next_block.is_none()); @@ -589,20 +625,16 @@ mod tests { let formatter: Formatter = Formatter::default(); // Experiments :D let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/bar.txt", + FileType::File, + Some(8192), + None, + Some(0), + Some(0), + Some(UnixPex::from(0o644)), + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -620,20 +652,16 @@ mod tests { ) ); // Elide name - let entry = File { - path: PathBuf::from("/piroparoporoperoperupupu.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/piroparoporoperoperupupu.txt", + FileType::File, + Some(8192), + None, + Some(0), + Some(0), + Some(UnixPex::from(0o644)), + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -651,20 +679,16 @@ mod tests { ) ); // No pex - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: None, - }, - }; + let entry = fixture( + "/bar.txt", + FileType::File, + Some(8192), + None, + Some(0), + Some(0), + None, + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -682,20 +706,16 @@ mod tests { ) ); // No user - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: Some(0), - mode: None, - }, - }; + let entry = fixture( + "/bar.txt", + FileType::File, + Some(8192), + None, + None, + Some(0), + None, + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -720,20 +740,16 @@ mod tests { let formatter: Formatter = Formatter::default(); // Experiments :D let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/home/cvisintin/projects"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Directory, - size: 4096, - symlink: None, - uid: Some(0), - gid: Some(0), - mode: Some(UnixPex::from(0o755)), - }, - }; + let entry = fixture( + "/home/cvisintin/projects", + FileType::Directory, + Some(4096), + None, + Some(0), + Some(0), + Some(UnixPex::from(0o755)), + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -751,20 +767,16 @@ mod tests { ) ); // No pex, no user - let entry = File { - path: PathBuf::from("/home/cvisintin/projects"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Directory, - size: 4096, - symlink: None, - uid: None, - gid: Some(0), - mode: None, - }, - }; + let entry = fixture( + "/home/cvisintin/projects", + FileType::Directory, + Some(4096), + None, + None, + Some(0), + None, + Some(t), + ); #[cfg(posix)] assert_eq!( formatter.fmt(&entry), @@ -790,20 +802,16 @@ mod tests { ); // Directory (with symlink) let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/home/cvisintin/projects"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Symlink, - size: 4096, - symlink: Some(PathBuf::from("project.info")), - uid: None, - gid: None, - mode: Some(UnixPex::from(0o755)), - }, - }; + let entry = fixture( + "/home/cvisintin/projects", + FileType::Symlink, + Some(4096), + Some("project.info"), + None, + None, + Some(UnixPex::from(0o755)), + Some(t), + ); assert_eq!( formatter.fmt(&entry), format!( @@ -814,20 +822,16 @@ mod tests { ) ); // Directory without symlink - let entry = File { - path: PathBuf::from("/home/cvisintin/projects"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Directory, - size: 4096, - symlink: None, - uid: None, - gid: None, - mode: Some(UnixPex::from(0o755)), - }, - }; + let entry = fixture( + "/home/cvisintin/projects", + FileType::Directory, + Some(4096), + None, + None, + None, + Some(UnixPex::from(0o755)), + Some(t), + ); assert_eq!( formatter.fmt(&entry), format!( @@ -838,20 +842,16 @@ mod tests { ) ); // File with symlink - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Symlink, - size: 8192, - symlink: Some(PathBuf::from("project.info")), - uid: None, - gid: None, - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/bar.txt", + FileType::Symlink, + Some(8192), + Some("project.info"), + None, + None, + Some(UnixPex::from(0o644)), + Some(t), + ); assert_eq!( formatter.fmt(&entry), format!( @@ -862,20 +862,16 @@ mod tests { ) ); // File without symlink - let entry = File { - path: PathBuf::from("/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/bar.txt", + FileType::File, + Some(8192), + None, + None, + None, + Some(UnixPex::from(0o644)), + Some(t), + ); assert_eq!( formatter.fmt(&entry), format!( @@ -891,20 +887,16 @@ mod tests { #[cfg(posix)] fn should_fmt_path() { let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/tmp/a/b/c/bar.txt"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Symlink, - size: 8192, - symlink: Some(PathBuf::from("project.info")), - uid: None, - gid: None, - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/tmp/a/b/c/bar.txt", + FileType::Symlink, + Some(8192), + Some("project.info"), + None, + None, + Some(UnixPex::from(0o644)), + Some(t), + ); let formatter: Formatter = Formatter::new("File path: {PATH}"); assert_eq!( formatter.fmt(&entry).as_str(), @@ -923,20 +915,16 @@ mod tests { #[cfg(posix)] fn should_fmt_utf8_path() { let t: SystemTime = SystemTime::now(); - let entry = File { - path: PathBuf::from("/tmp/a/b/c/россия"), - metadata: Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: FileType::Symlink, - size: 8192, - symlink: Some(PathBuf::from("project.info")), - uid: None, - gid: None, - mode: Some(UnixPex::from(0o644)), - }, - }; + let entry = fixture( + "/tmp/a/b/c/россия", + FileType::Symlink, + Some(8192), + Some("project.info"), + None, + None, + Some(UnixPex::from(0o644)), + Some(t), + ); let formatter: Formatter = Formatter::new("File path: {PATH}"); assert_eq!( formatter.fmt(&entry).as_str(), @@ -948,100 +936,88 @@ mod tests { #[test] fn should_fmt_short_ascii_name() { - let entry = File { - path: PathBuf::from("/tmp/foo.txt"), - metadata: Metadata { - accessed: None, - created: None, - modified: None, - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: None, - }, - }; + let entry = fixture( + "/tmp/foo.txt", + FileType::File, + Some(8192), + None, + None, + None, + None, + None, + ); let formatter: Formatter = Formatter::new("{NAME:8}"); assert_eq!(formatter.fmt(&entry).as_str(), "foo.txt "); } #[test] fn should_fmt_exceeding_length_ascii_name() { - let entry = File { - path: PathBuf::from("/tmp/christian-visintin.txt"), - metadata: Metadata { - accessed: None, - created: None, - modified: None, - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: None, - }, - }; + let entry = fixture( + "/tmp/christian-visintin.txt", + FileType::File, + Some(8192), + None, + None, + None, + None, + None, + ); let formatter: Formatter = Formatter::new("{NAME:8}"); assert_eq!(formatter.fmt(&entry).as_str(), "christi…"); } #[test] fn should_fmt_short_utf8_name() { - let entry = File { - path: PathBuf::from("/tmp/россия"), - metadata: Metadata { - accessed: None, - created: None, - modified: None, - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: None, - }, - }; + let entry = fixture( + "/tmp/россия", + FileType::File, + Some(8192), + None, + None, + None, + None, + None, + ); let formatter: Formatter = Formatter::new("{NAME:8}"); assert_eq!(formatter.fmt(&entry).as_str(), "россия "); } #[test] fn should_fmt_long_utf8_name() { - let entry = File { - path: PathBuf::from("/tmp/喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵"), - metadata: Metadata { - accessed: None, - created: None, - modified: None, - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: None, - }, - }; + let entry = fixture( + "/tmp/喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵", + FileType::File, + Some(8192), + None, + None, + None, + None, + None, + ); let formatter: Formatter = Formatter::new("{NAME:8}"); assert_eq!(formatter.fmt(&entry).as_str(), "喵喵喵喵喵喵喵…"); } + #[test] + fn should_fmt_unknown_file_size() { + let entry = File::new("/tmp/unknown", Metadata::default()); + let formatter = Formatter::new("{SIZE}"); + + assert_eq!(formatter.fmt(&entry), "? "); + } + #[test] fn should_ignore_unknown_formatter_keys() { - let entry = File { - path: PathBuf::from("/tmp/foo.txt"), - metadata: Metadata { - accessed: None, - created: None, - modified: None, - file_type: FileType::File, - size: 8192, - symlink: None, - uid: None, - gid: None, - mode: None, - }, - }; + let entry = fixture( + "/tmp/foo.txt", + FileType::File, + Some(8192), + None, + None, + None, + None, + None, + ); let formatter: Formatter = Formatter::new("before {UNKNOWN:12} after {NAME:8}"); assert_eq!(formatter.fmt(&entry).as_str(), "before after foo.txt "); diff --git a/src/filetransfer.rs b/src/filetransfer.rs index c1208bd..142818b 100644 --- a/src/filetransfer.rs +++ b/src/filetransfer.rs @@ -5,6 +5,7 @@ mod host_bridge_builder; pub mod params; mod remotefs_builder; +mod wrapper; // -- export types pub use host_bridge_builder::HostBridgeBuilder; diff --git a/src/filetransfer/remotefs_builder.rs b/src/filetransfer/remotefs_builder.rs index 5d712a7..f147f38 100644 --- a/src/filetransfer/remotefs_builder.rs +++ b/src/filetransfer/remotefs_builder.rs @@ -3,14 +3,13 @@ //! Remotefs client builder use std::path::PathBuf; -use std::sync::Arc; use remotefs::RemoteFs; use remotefs_aws_s3::AwsS3Fs; use remotefs_ftp::FtpFs; use remotefs_gcs::credentials::service_account; use remotefs_gcs::{GoogleCloudStorageCredentials, GoogleCloudStorageFs}; -use remotefs_kube::KubeMultiPodFs as KubeFs; +use remotefs_kube::KubeMultiPodFs; #[cfg(smb_unix)] use remotefs_smb::{ PavaoSmbCredentials as SmbCredentials, PavaoSmbFs as SmbFs, PavaoSmbOptions as SmbOptions, @@ -19,8 +18,7 @@ use remotefs_smb::{ #[cfg(smb_windows)] use remotefs_smb::{WNetSmbCredentials as SmbCredentials, WNetSmbFs as SmbFs}; use remotefs_ssh::{ - NoCheckServerKey, RusshSession as SshSession, ScpFs, SftpFs, SshAgentIdentity, - SshConfigParseRule, SshOpts, + NoCheckServerKey, RusshScpFs, RusshSftpFs, SshAgentIdentity, SshConfigParseRule, SshOpts, }; use remotefs_webdav::WebDAVFs; @@ -31,6 +29,7 @@ use super::params::{AwsS3Params, GenericProtocolParams, GoogleCloudStorageParams #[cfg(smb)] use super::params::{AwsS3Params, GenericProtocolParams, GoogleCloudStorageParams, SmbParams}; use super::params::{KubeProtocolParams, WebDAVProtocolParams}; +use super::wrapper::RuntimeRemoteFs; use super::{FileTransferProtocol, ProtocolParams}; use crate::system::config_client::ConfigClient; use crate::system::sshkey_storage::SshKeyStorage; @@ -72,7 +71,7 @@ impl RemoteFsBuilder { Ok(Box::new(Self::smb_client(params)?)) } (FileTransferProtocol::WebDAV, ProtocolParams::WebDAV(params)) => { - Ok(Box::new(Self::webdav_client(params))) + Ok(Box::new(Self::webdav_client(params)?)) } (protocol, params) => { error!("Invalid params for protocol '{:?}'", protocol); @@ -84,16 +83,11 @@ impl RemoteFsBuilder { } /// Build aws s3 client from parameters - fn aws_s3_client(params: AwsS3Params) -> Result { - let rt = Arc::new( - tokio::runtime::Builder::new_current_thread() - .worker_threads(1) - .enable_all() - .build() - .map_err(|e| format!("Unable to create tokio runtime: {e}"))?, - ); - let mut client = - AwsS3Fs::new(params.bucket_name, &rt).new_path_style(params.new_path_style); + fn aws_s3_client(params: AwsS3Params) -> Result { + let runtime = Self::tokio_runtime()?; + + let mut client = AwsS3Fs::new(params.bucket_name).new_path_style(params.new_path_style); + if let Some(region) = params.region { client = client.region(region); } @@ -115,14 +109,15 @@ impl RemoteFsBuilder { if let Some(session_token) = params.session_token { client = client.session_token(session_token); } - Ok(client) + let client = client.into_blocking(runtime.handle().clone()); + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Build a Google Cloud Storage client from parameters. - fn gcs_client(params: GoogleCloudStorageParams) -> Result { + fn gcs_client(params: GoogleCloudStorageParams) -> Result { let runtime = Self::tokio_runtime()?; let mut client = match params.service_account_key { - None => GoogleCloudStorageFs::new(params.bucket_name, &runtime), + None => GoogleCloudStorageFs::new(params.bucket_name), Some(path) => { let raw = std::fs::read_to_string(&path).map_err(|error| { format!("Unable to read GCS service-account file '{path}': {error}") @@ -140,12 +135,12 @@ impl RemoteFsBuilder { GoogleCloudStorageFs::with_credentials( params.bucket_name, GoogleCloudStorageCredentials::custom(credentials), - &runtime, ) } }; client = client.endpoint(params.endpoint); - Ok(client) + let client = client.into_blocking(runtime.handle().clone()); + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Build ftp client from parameters @@ -164,34 +159,42 @@ impl RemoteFsBuilder { } /// Build kube client - fn kube_client(params: KubeProtocolParams) -> Result { - let rt = Self::tokio_runtime()?; - let kube_fs = KubeFs::new(&rt); - if let Some(config) = params.config() { - Ok(kube_fs.config(config)) + fn kube_client(params: KubeProtocolParams) -> Result { + let runtime = Self::tokio_runtime()?; + let kube_fs = if let Some(config) = params.config() { + KubeMultiPodFs::new().config(config) } else { - Ok(kube_fs) - } + KubeMultiPodFs::new() + }; + + let client = kube_fs.into_blocking(runtime.handle().clone()); + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Build scp client fn scp_client( params: GenericProtocolParams, config_client: &ConfigClient, - ) -> Result>, String> { + ) -> Result { let opts = Self::build_ssh_opts(params, config_client); - let rt = Self::tokio_runtime()?; - Ok(ScpFs::russh(opts, rt)) + let runtime = Self::tokio_runtime()?; + let client = + RusshScpFs::::new(opts).into_blocking(runtime.handle().clone()); + + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Build sftp client fn sftp_client( params: GenericProtocolParams, config_client: &ConfigClient, - ) -> Result>, String> { + ) -> Result { let opts = Self::build_ssh_opts(params, config_client); - let rt = Self::tokio_runtime()?; - Ok(SftpFs::russh(opts, rt)) + let runtime = Self::tokio_runtime()?; + let client = + RusshSftpFs::::new(opts).into_blocking(runtime.handle().clone()); + + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Maps the user-facing SMB family to inclusive remotefs dialect bounds. @@ -251,8 +254,16 @@ impl RemoteFsBuilder { Ok(SmbFs::new(credentials)) } - fn webdav_client(params: WebDAVProtocolParams) -> WebDAVFs { - WebDAVFs::new(¶ms.username, ¶ms.password, ¶ms.uri) + fn webdav_client(params: WebDAVProtocolParams) -> Result { + let runtime = Self::tokio_runtime()?; + let client = WebDAVFs::new( + ¶ms.uri, + remotefs_webdav::Auth::basic(params.username, params.password), + ) + .map_err(|e| format!("failed to create WebDAV client: {e}"))?; + + let client = client.into_blocking(runtime.handle().clone()); + Ok(RuntimeRemoteFs::new(client, runtime)) } /// Build ssh options from generic protocol params and client configuration @@ -288,14 +299,12 @@ impl RemoteFsBuilder { } /// Create tokio runtime to run async code for remotefs - fn tokio_runtime() -> Result, String> { - Ok(Arc::new( - tokio::runtime::Builder::new_current_thread() - .worker_threads(1) - .enable_all() - .build() - .map_err(|e| format!("Unable to create tokio runtime: {e}"))?, - )) + fn tokio_runtime() -> Result { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .map_err(|e| format!("Unable to create tokio runtime: {e}")) } } diff --git a/src/filetransfer/wrapper.rs b/src/filetransfer/wrapper.rs new file mode 100644 index 0000000..c3ee3cd --- /dev/null +++ b/src/filetransfer/wrapper.rs @@ -0,0 +1,154 @@ +//! Runtime-owning wrapper for asynchronous remote filesystem adapters. + +use std::io::{Read, Write}; +use std::path::Path; + +use remotefs::fs::{ + Capabilities, ExecOutput, ReadOptions, ReadStream, SetMetadata, UnixPex, WriteOptions, + WriteStream, +}; +use remotefs::{File, RemoteFs, RemoteResult}; +use tokio::runtime::Runtime; + +pub(super) struct RuntimeRemoteFs { + // Fields are dropped in declaration order, so the client cannot outlive its runtime. + remote: Box, + _runtime: Runtime, +} + +impl RuntimeRemoteFs { + pub(super) fn new(remote: impl RemoteFs + 'static, runtime: Runtime) -> Self { + Self { + remote: Box::new(remote), + _runtime: runtime, + } + } +} + +impl RemoteFs for RuntimeRemoteFs { + fn connect(&mut self) -> RemoteResult<()> { + self.remote.connect() + } + + fn disconnect(&mut self) -> RemoteResult<()> { + self.remote.disconnect() + } + + fn is_connected(&self) -> bool { + self.remote.is_connected() + } + + fn capabilities(&self) -> Capabilities { + self.remote.capabilities() + } + + fn list_dir(&self, path: &Path) -> RemoteResult> { + self.remote.list_dir(path) + } + + fn stat(&self, path: &Path) -> RemoteResult { + self.remote.stat(path) + } + + fn exists(&self, path: &Path) -> RemoteResult { + self.remote.exists(path) + } + + fn set_metadata(&self, path: &Path, metadata: &SetMetadata) -> RemoteResult<()> { + self.remote.set_metadata(path, metadata) + } + + fn create_dir(&self, path: &Path, mode: Option) -> RemoteResult<()> { + self.remote.create_dir(path, mode) + } + + fn remove_file(&self, path: &Path) -> RemoteResult<()> { + self.remote.remove_file(path) + } + + fn remove_dir(&self, path: &Path) -> RemoteResult<()> { + self.remote.remove_dir(path) + } + + fn remove_dir_all(&self, path: &Path) -> RemoteResult<()> { + self.remote.remove_dir_all(path) + } + + fn rename(&self, src: &Path, dest: &Path) -> RemoteResult<()> { + self.remote.rename(src, dest) + } + + fn copy(&self, src: &Path, dest: &Path) -> RemoteResult<()> { + self.remote.copy(src, dest) + } + + fn symlink(&self, path: &Path, target: &Path) -> RemoteResult<()> { + self.remote.symlink(path, target) + } + + fn open(&self, path: &Path, opts: &ReadOptions) -> RemoteResult { + self.remote.open(path, opts) + } + + fn create(&self, path: &Path, opts: &WriteOptions) -> RemoteResult { + self.remote.create(path, opts) + } + + fn append(&self, path: &Path, opts: &WriteOptions) -> RemoteResult { + self.remote.append(path, opts) + } + + fn read_file( + &self, + path: &Path, + opts: &ReadOptions, + dest: &mut (dyn Write + Send), + ) -> RemoteResult { + self.remote.read_file(path, opts, dest) + } + + fn write_file( + &self, + path: &Path, + opts: &WriteOptions, + src: &mut (dyn Read + Send), + ) -> RemoteResult { + self.remote.write_file(path, opts, src) + } + + fn append_file( + &self, + path: &Path, + opts: &WriteOptions, + src: &mut (dyn Read + Send), + ) -> RemoteResult { + self.remote.append_file(path, opts, src) + } + + fn exec(&self, cmd: &str) -> RemoteResult { + self.remote.exec(cmd) + } +} + +#[cfg(test)] +mod tests { + use remotefs_ftp::FtpFs; + + use super::*; + + #[test] + fn runtime_remote_fs_keeps_runtime_alive() { + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .unwrap(); + let handle = runtime.handle().clone(); + let remote = RuntimeRemoteFs::new(FtpFs::new("127.0.0.1", 21), runtime); + + let task = handle.spawn(async { 42 }); + + assert_eq!(handle.block_on(task).unwrap(), 42); + drop(remote); + } +} diff --git a/src/host.rs b/src/host.rs index 8479f0f..fab7f4d 100644 --- a/src/host.rs +++ b/src/host.rs @@ -11,7 +11,8 @@ use std::path::{Path, PathBuf}; use thiserror::Error; // Locals -pub use self::bridge::HostBridge; +#[doc(inline)] +pub use self::bridge::{HostBridge, HostReader, HostWriter}; pub use self::localhost::Localhost; pub use self::remote_bridged::RemoteBridged; @@ -48,6 +49,7 @@ pub enum HostErrorType { /// HostError is a wrapper for the error type and the exact io error #[derive(Debug, Error)] pub struct HostError { + #[source] pub error: HostErrorType, ioerr: Option, path: Option, diff --git a/src/host/bridge.rs b/src/host/bridge.rs index d85f2ed..9db8b39 100644 --- a/src/host/bridge.rs +++ b/src/host/bridge.rs @@ -3,7 +3,8 @@ //! Defines the host abstraction used to expose localhost and bridged remote //! filesystems through a shared interface. -use std::io::{Read, Write}; +use std::fmt; +use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use remotefs::File; @@ -11,6 +12,111 @@ use remotefs::fs::{Metadata, UnixPex}; use super::HostResult; +enum HostReaderInner { + Io(Box), + Remote(remotefs::fs::ReadStream), +} + +/// An owned host reader that optionally retains a remote transfer finalizer. +pub struct HostReader(HostReaderInner); + +impl HostReader { + pub(crate) fn io(reader: T) -> Self + where + T: Read + Send + 'static, + { + Self(HostReaderInner::Io(Box::new(reader))) + } + + pub(crate) fn remote(reader: remotefs::fs::ReadStream) -> Self { + Self(HostReaderInner::Remote(reader)) + } + + /// Completes the remote read and consumes this reader. + /// + /// # Errors + /// + /// Returns the remote stream finalization error, if the reader is backed + /// by a remote stream. + pub fn finish(self) -> super::HostResult<()> { + match self.0 { + HostReaderInner::Io(_) => Ok(()), + HostReaderInner::Remote(reader) => reader.finish().map_err(Into::into), + } + } +} + +impl fmt::Debug for HostReader { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.debug_struct("HostReader").finish_non_exhaustive() + } +} + +impl Read for HostReader { + fn read(&mut self, buffer: &mut [u8]) -> io::Result { + match &mut self.0 { + HostReaderInner::Io(reader) => reader.read(buffer), + HostReaderInner::Remote(reader) => reader.read(buffer), + } + } +} + +enum HostWriterInner { + Io(Box), + Remote(remotefs::fs::WriteStream), +} + +/// An owned host writer that optionally retains a remote transfer finalizer. +pub struct HostWriter(HostWriterInner); + +impl HostWriter { + pub(crate) fn io(writer: T) -> Self + where + T: Write + Send + 'static, + { + Self(HostWriterInner::Io(Box::new(writer))) + } + + pub(crate) fn remote(writer: remotefs::fs::WriteStream) -> Self { + Self(HostWriterInner::Remote(writer)) + } + + /// Completes the remote write and consumes this writer. + /// + /// # Errors + /// + /// Returns the remote stream finalization error, if the writer is backed + /// by a remote stream. + pub fn finish(self) -> super::HostResult<()> { + match self.0 { + HostWriterInner::Io(_) => Ok(()), + HostWriterInner::Remote(writer) => writer.finish().map_err(Into::into), + } + } +} + +impl fmt::Debug for HostWriter { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.debug_struct("HostWriter").finish_non_exhaustive() + } +} + +impl Write for HostWriter { + fn write(&mut self, buffer: &[u8]) -> io::Result { + match &mut self.0 { + HostWriterInner::Io(writer) => writer.write(buffer), + HostWriterInner::Remote(writer) => writer.write(buffer), + } + } + + fn flush(&mut self) -> io::Result<()> { + match &mut self.0 { + HostWriterInner::Io(writer) => writer.flush(), + HostWriterInner::Remote(writer) => writer.flush(), + } + } +} + /// Trait to bridge a remote filesystem to the host filesystem /// /// In case of `Localhost` this should be effortless, while for remote hosts this should @@ -75,15 +181,114 @@ pub trait HostBridge { fn chmod(&mut self, path: &Path, pex: UnixPex) -> HostResult<()>; /// Open file for reading - fn open_file(&mut self, file: &Path) -> HostResult>; + fn open_file(&mut self, file: &Path) -> HostResult; /// Open file for writing - fn create_file( - &mut self, - file: &Path, - metadata: &Metadata, - ) -> HostResult>; + fn create_file(&mut self, file: &Path, metadata: &Metadata) -> HostResult; /// Finalize write operation - fn finalize_write(&mut self, writer: Box) -> HostResult<()>; + fn finalize_write(&mut self, writer: HostWriter) -> HostResult<()>; +} + +#[cfg(test)] +mod test { + use std::io::Cursor; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + + use remotefs::fs::{ReadStream, RemoteRead, RemoteWrite, WriteStream}; + + use super::*; + + struct TrackedReader { + reader: Cursor>, + finishes: Arc, + } + + impl Read for TrackedReader { + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + self.reader.read(buffer) + } + } + + impl RemoteRead for TrackedReader { + fn finish(self: Box) -> remotefs::RemoteResult<()> { + self.finishes.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + } + + struct TrackedWriter { + writer: Cursor>, + finishes: Arc, + } + + impl Write for TrackedWriter { + fn write(&mut self, buffer: &[u8]) -> std::io::Result { + self.writer.write(buffer) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() + } + } + + impl RemoteWrite for TrackedWriter { + fn finish(self: Box) -> remotefs::RemoteResult<()> { + self.finishes.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + } + + #[test] + fn remote_reader_finishes_exactly_once_when_consumed() { + let finishes = Arc::new(AtomicUsize::new(0)); + let reader = HostReader::remote(ReadStream::new(TrackedReader { + reader: Cursor::new(Vec::new()), + finishes: finishes.clone(), + })); + + reader.finish().unwrap(); + + assert_eq!(finishes.load(Ordering::SeqCst), 1); + } + + #[test] + fn remote_writer_finishes_exactly_once_when_consumed() { + let finishes = Arc::new(AtomicUsize::new(0)); + let writer = HostWriter::remote(WriteStream::new(TrackedWriter { + writer: Cursor::new(Vec::new()), + finishes: finishes.clone(), + })); + + writer.finish().unwrap(); + + assert_eq!(finishes.load(Ordering::SeqCst), 1); + } + + #[test] + fn unfinished_remote_reader_is_dropped_without_finalizing() { + let finishes = Arc::new(AtomicUsize::new(0)); + let reader = HostReader::remote(ReadStream::new(TrackedReader { + reader: Cursor::new(Vec::new()), + finishes: finishes.clone(), + })); + + drop(reader); + + assert_eq!(finishes.load(Ordering::SeqCst), 0); + } + + #[test] + fn unfinished_remote_writer_is_dropped_without_finalizing() { + let finishes = Arc::new(AtomicUsize::new(0)); + let writer = HostWriter::remote(WriteStream::new(TrackedWriter { + writer: Cursor::new(Vec::new()), + finishes: finishes.clone(), + })); + + drop(writer); + + assert_eq!(finishes.load(Ordering::SeqCst), 0); + } } diff --git a/src/host/localhost.rs b/src/host/localhost.rs index 63e0ef3..fb9aadc 100644 --- a/src/host/localhost.rs +++ b/src/host/localhost.rs @@ -4,7 +4,7 @@ //! filesystem. use std::fs::{self, OpenOptions}; -use std::io::{Read, Write}; +use std::io::Write; #[cfg(posix)] use std::os::unix::fs::PermissionsExt as _; use std::path::{Path, PathBuf}; @@ -13,7 +13,7 @@ use filetime::FileTime; use remotefs::File; use remotefs::fs::{FileType, Metadata, UnixPex}; -use super::{HostBridge, HostResult}; +use super::{HostBridge, HostReader, HostResult, HostWriter}; use crate::host::{HostError, HostErrorType}; use crate::utils::path; @@ -368,7 +368,7 @@ impl HostBridge for Localhost { metadata.file_type = FileType::Symlink; } // Match dir / file - Ok(File { path, metadata }) + Ok(File::new(path, metadata)) } fn exists(&mut self, path: &Path) -> HostResult { @@ -520,7 +520,7 @@ impl HostBridge for Localhost { Err(HostError::from(HostErrorType::NotImplemented)) } - fn open_file(&mut self, file: &std::path::Path) -> HostResult> { + fn open_file(&mut self, file: &std::path::Path) -> HostResult { let file: PathBuf = self.to_path(file); info!("Opening file {} for read", file.display()); if !self.exists(file.as_path())? { @@ -537,7 +537,7 @@ impl HostBridge for Localhost { .write(false) .open(file.as_path()) { - Ok(f) => Ok(Box::new(f)), + Ok(f) => Ok(HostReader::io(f)), Err(err) => { error!("Could not open file for read: {}", err); Err(HostError::new( @@ -549,11 +549,7 @@ impl HostBridge for Localhost { } } - fn create_file( - &mut self, - file: &Path, - _metadata: &Metadata, - ) -> HostResult> { + fn create_file(&mut self, file: &Path, _metadata: &Metadata) -> HostResult { let file: PathBuf = self.to_path(file); info!("Opening file {} for write", file.display()); match OpenOptions::new() @@ -562,7 +558,7 @@ impl HostBridge for Localhost { .truncate(true) .open(file.as_path()) { - Ok(f) => Ok(Box::new(f)), + Ok(f) => Ok(HostWriter::io(f)), Err(err) => { error!("Failed to open file: {}", err); match self.exists(file.as_path())? { @@ -581,9 +577,11 @@ impl HostBridge for Localhost { } } - fn finalize_write(&mut self, _writer: Box) -> HostResult<()> { - // no-op - Ok(()) + fn finalize_write(&mut self, mut writer: HostWriter) -> HostResult<()> { + writer.flush().map_err(|error| { + HostError::new(HostErrorType::FileNotAccessible, Some(error), Path::new("")) + })?; + writer.finish() } } diff --git a/src/host/remote_bridged.rs b/src/host/remote_bridged.rs index 58a0a33..d32d555 100644 --- a/src/host/remote_bridged.rs +++ b/src/host/remote_bridged.rs @@ -5,19 +5,19 @@ mod temp_mapped_file; -use std::io::{Read, Write}; +use std::io::Write; use std::path::{Component, Path, PathBuf}; -use remotefs::fs::{Metadata, UnixPex}; +use remotefs::fs::{Capabilities, Metadata, ReadOptions, SetMetadata, UnixPex, WriteOptions}; use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs}; use self::temp_mapped_file::TempMappedFile; -use super::{HostBridge, HostError, HostResult}; +use super::{HostBridge, HostError, HostErrorType, HostReader, HostResult, HostWriter}; use crate::utils::path::normalize; struct WriteStreamOp { path: PathBuf, - metadata: Metadata, + options: WriteOptions, tempfile: TempMappedFile, } @@ -25,30 +25,81 @@ struct WriteStreamOp { pub struct RemoteBridged { /// Remote fs client remote: Box, + /// Consumer-owned remote working directory used to resolve relative paths + wrkdir: PathBuf, /// Reminder used to finalize write stream write_stream_op: Option, } impl RemoteBridged { - fn open_file_from_temp(&mut self, file: &Path) -> HostResult> { + fn open_file_from_temp(&mut self, file: &Path) -> HostResult { let mut temp_file = TempMappedFile::new()?; self.remote - .open_file(file, Box::new(temp_file.clone())) + .read_file(file, &ReadOptions::default(), &mut temp_file) .map_err(HostError::from)?; // Sync changes temp_file.sync()?; // now return as read - Ok(Box::new(temp_file)) + Ok(HostReader::io(temp_file)) } } +fn resolve_remote_path(wrkdir: &Path, target: &Path) -> HostResult { + let resolved = if remotefs::path::ensure_absolute(target).is_ok() { + target.to_path_buf() + } else { + wrkdir.join(target) + }; + remotefs::path::ensure_absolute(&resolved).map_err(HostError::from)?; + Ok(resolved) +} + +fn write_options(metadata: &Metadata) -> WriteOptions { + let mut options = WriteOptions::default(); + if let Some(size) = metadata.size { + options = options.size_hint(size); + } + if let Some(mode) = metadata.mode { + options = options.mode(mode); + } + if let Some(modified) = metadata.modified { + options = options.modified(modified); + } + options +} + +fn set_metadata_options(metadata: &Metadata) -> SetMetadata { + let mut options = SetMetadata::default(); + if let Some(mode) = metadata.mode { + options = options.mode(mode); + } + if let Some(uid) = metadata.uid { + options = options.uid(uid); + } + if let Some(gid) = metadata.gid { + options = options.gid(gid); + } + if let Some(accessed) = metadata.accessed { + options = options.accessed(accessed); + } + if let Some(modified) = metadata.modified { + options = options.modified(modified); + } + options +} + +fn map_io_error(error: std::io::Error) -> HostError { + HostError::new(HostErrorType::FileNotAccessible, Some(error), Path::new("")) +} + impl From> for RemoteBridged { fn from(remote: Box) -> Self { RemoteBridged { remote, + wrkdir: PathBuf::from("/"), write_stream_op: None, } } @@ -56,7 +107,7 @@ impl From> for RemoteBridged { impl HostBridge for RemoteBridged { fn connect(&mut self) -> HostResult<()> { - self.remote.connect().map(|_| ()).map_err(HostError::from) + self.remote.connect().map_err(HostError::from) } fn disconnect(&mut self) -> HostResult<()> { @@ -73,71 +124,85 @@ impl HostBridge for RemoteBridged { fn pwd(&mut self) -> HostResult { debug!("Getting working directory"); - self.remote.pwd().map_err(HostError::from) + Ok(self.wrkdir.clone()) } fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult { debug!("Changing working directory to {:?}", new_dir); - self.remote.change_dir(new_dir).map_err(HostError::from) + let new_dir = resolve_remote_path(&self.wrkdir, new_dir)?; + let entry = self.remote.stat(&new_dir).map_err(HostError::from)?; + if !entry.is_dir() { + return Err(HostError::from(RemoteError::new(RemoteErrorType::BadFile))); + } + self.wrkdir = new_dir; + Ok(self.wrkdir.clone()) } fn mkdir_ex(&mut self, dir_name: &Path, ignore_existing: bool) -> HostResult<()> { debug!("Creating directory {:?}", dir_name); - match self.remote.create_dir(dir_name, UnixPex::from(0o755)) { - Ok(_) => Ok(()), - Err(remotefs::RemoteError { - kind: RemoteErrorType::DirectoryAlreadyExists, - .. - }) if ignore_existing => Ok(()), - Err(e) => Err(HostError::from(e)), + let path = resolve_remote_path(&self.wrkdir, dir_name)?; + match self.remote.create_dir(&path, Some(UnixPex::from(0o755))) { + Ok(()) => Ok(()), + Err(error) if ignore_existing && error.kind() == RemoteErrorType::AlreadyExists => { + Ok(()) + } + Err(error) => Err(HostError::from(error)), } } fn remove(&mut self, entry: &File) -> HostResult<()> { debug!("Removing {:?}", entry.path()); + let path = resolve_remote_path(&self.wrkdir, entry.path())?; if entry.is_dir() { - self.remote - .remove_dir_all(entry.path()) - .map_err(HostError::from) + self.remote.remove_dir_all(&path).map_err(HostError::from) } else { - self.remote - .remove_file(entry.path()) - .map_err(HostError::from) + self.remote.remove_file(&path).map_err(HostError::from) } } fn rename(&mut self, entry: &File, dst_path: &Path) -> HostResult<()> { debug!("Renaming {:?} to {:?}", entry.path(), dst_path); + let source = resolve_remote_path(&self.wrkdir, entry.path())?; + let destination = resolve_remote_path(&self.wrkdir, dst_path)?; self.remote - .mov(entry.path(), dst_path) + .rename(&source, &destination) .map_err(HostError::from) } fn copy(&mut self, entry: &File, dst: &Path) -> HostResult<()> { debug!("Copying {:?} to {:?}", entry.path(), dst); - self.remote.copy(entry.path(), dst).map_err(HostError::from) + let source = resolve_remote_path(&self.wrkdir, entry.path())?; + let destination = resolve_remote_path(&self.wrkdir, dst)?; + self.remote + .copy(&source, &destination) + .map_err(HostError::from) } fn stat(&mut self, path: &Path) -> HostResult { debug!("Statting {:?}", path); - self.remote.stat(path).map_err(HostError::from) + let path = resolve_remote_path(&self.wrkdir, path)?; + self.remote.stat(&path).map_err(HostError::from) } fn exists(&mut self, path: &Path) -> HostResult { debug!("Checking existence of {:?}", path); - self.remote.exists(path).map_err(HostError::from) + let path = resolve_remote_path(&self.wrkdir, path)?; + self.remote.exists(&path).map_err(HostError::from) } fn list_dir(&mut self, path: &Path) -> HostResult> { debug!("Listing directory {:?}", path); - let entries = self.remote.list_dir(path).map_err(HostError::from)?; - Ok(filter_self_refs(path, entries)) + let path = resolve_remote_path(&self.wrkdir, path)?; + let entries = self.remote.list_dir(&path).map_err(HostError::from)?; + Ok(filter_self_refs(&path, entries)) } fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()> { debug!("Setting metadata for {:?}", path); + let path = resolve_remote_path(&self.wrkdir, path)?; + let options = set_metadata_options(metadata); self.remote - .setstat(path, metadata.clone()) + .set_metadata(&path, &options) .map_err(HostError::from) } @@ -145,78 +210,108 @@ impl HostBridge for RemoteBridged { debug!("Executing command: {}", cmd); self.remote .exec(cmd) - .map(|(_, stdout)| stdout) + .map(|output| output.stdout) .map_err(HostError::from) } fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()> { debug!("Creating symlink from {:?} to {:?}", src, dst); - self.remote.symlink(src, dst).map_err(HostError::from) + let path = resolve_remote_path(&self.wrkdir, src)?; + let target = resolve_remote_path(&self.wrkdir, dst)?; + self.remote.symlink(&path, &target).map_err(HostError::from) } fn chmod(&mut self, path: &Path, pex: UnixPex) -> HostResult<()> { debug!("Changing permissions of {:?} to {:?}", path, pex); - let stat = self.remote.stat(path).map_err(HostError::from)?; - let mut metadata = stat.metadata.clone(); - metadata.mode = Some(pex); - - self.setstat(path, &metadata) + let path = resolve_remote_path(&self.wrkdir, path)?; + self.remote + .set_metadata(&path, &SetMetadata::default().mode(pex)) + .map_err(HostError::from) } - fn open_file(&mut self, file: &Path) -> HostResult> { - // try to use stream, otherwise download to a temporary file and return a reader - match self.remote.open(file) { - Ok(stream) => Ok(Box::new(stream)), - Err(RemoteError { - kind: RemoteErrorType::UnsupportedFeature, - .. - }) => self.open_file_from_temp(file), - Err(e) => Err(HostError::from(e)), - } - } - - fn create_file( - &mut self, - file: &Path, - metadata: &Metadata, - ) -> HostResult> { - // try to use stream, otherwise download to a temporary file and return a reader - match self.remote.create(file, metadata) { - Ok(stream) => Ok(Box::new(stream)), - Err(RemoteError { - kind: RemoteErrorType::UnsupportedFeature, - .. - }) => { - let tempfile = TempMappedFile::new()?; - self.write_stream_op = Some(WriteStreamOp { - path: file.to_path_buf(), - metadata: metadata.clone(), - tempfile: tempfile.clone(), - }); - - Ok(Box::new(tempfile)) + fn open_file(&mut self, file: &Path) -> HostResult { + let path = resolve_remote_path(&self.wrkdir, file)?; + if self + .remote + .capabilities() + .contains(Capabilities::STREAM_READ) + { + match self.remote.open(&path, &ReadOptions::default()) { + Ok(stream) => Ok(HostReader::remote(stream)), + Err(error) if error.kind() == RemoteErrorType::UnsupportedFeature => { + self.open_file_from_temp(&path) + } + Err(error) => Err(HostError::from(error)), } - Err(e) => Err(HostError::from(e)), + } else { + self.open_file_from_temp(&path) } } - fn finalize_write(&mut self, _writer: Box) -> HostResult<()> { + fn create_file(&mut self, file: &Path, metadata: &Metadata) -> HostResult { + let path = resolve_remote_path(&self.wrkdir, file)?; + let options = write_options(metadata); + self.write_stream_op = None; + if self + .remote + .capabilities() + .contains(Capabilities::STREAM_WRITE) + { + match self.remote.create(&path, &options) { + Ok(stream) => Ok(HostWriter::remote(stream)), + Err(error) + if matches!( + error.kind(), + RemoteErrorType::SizeRequired | RemoteErrorType::UnsupportedFeature + ) => + { + self.create_file_from_temp(path, options) + } + Err(error) => Err(HostError::from(error)), + } + } else { + self.create_file_from_temp(path, options) + } + } + + fn finalize_write(&mut self, mut writer: HostWriter) -> HostResult<()> { + writer.flush().map_err(map_io_error)?; + writer.finish()?; if let Some(WriteStreamOp { path, - metadata, + mut options, mut tempfile, }) = self.write_stream_op.take() { // sync tempfile.sync()?; + options = options.size_hint(tempfile.len()?); // write file self.remote - .create_file(&path, &metadata, Box::new(tempfile))?; + .write_file(&path, &options, &mut tempfile) + .map_err(HostError::from)?; } Ok(()) } } +impl RemoteBridged { + fn create_file_from_temp( + &mut self, + path: PathBuf, + options: WriteOptions, + ) -> HostResult { + let tempfile = TempMappedFile::new()?; + self.write_stream_op = Some(WriteStreamOp { + path, + options, + tempfile: tempfile.clone(), + }); + + Ok(HostWriter::io(tempfile)) + } +} + /// Drop entries that refer to the directory being listed. /// /// Some non-compliant FTP servers (e.g. LiteSpeed) include a self-reference @@ -236,31 +331,85 @@ fn filter_self_refs(path: &Path, entries: Vec) -> Vec { #[cfg(test)] mod test { - use std::path::PathBuf; - use std::time::SystemTime; + use std::path::Path; + use std::time::{SystemTime, UNIX_EPOCH}; use pretty_assertions::assert_eq; - use remotefs::fs::{FileType, Metadata}; + use remotefs::fs::{FileType, Metadata, UnixPex}; use super::*; fn file(path: &str, file_type: FileType) -> File { - File { - path: PathBuf::from(path), - metadata: Metadata { - accessed: Some(SystemTime::UNIX_EPOCH), - created: Some(SystemTime::UNIX_EPOCH), - modified: Some(SystemTime::UNIX_EPOCH), - file_type, - gid: None, - mode: None, - size: 0, - symlink: None, - uid: None, - }, + File::new( + path, + Metadata::default() + .accessed(UNIX_EPOCH) + .created(UNIX_EPOCH) + .modified(UNIX_EPOCH) + .file_type(file_type) + .size(0), + ) + } + + #[test] + fn resolves_relative_paths_from_consumer_working_directory() { + assert_eq!( + resolve_remote_path(Path::new("/home/user"), Path::new("docs/report.txt")).unwrap(), + Path::new("/home/user/docs/report.txt") + ); + } + + #[test] + fn preserves_absolute_remote_paths() { + for path in ["/srv/data", r"C:\data\file", r"\\server\share\file"] { + assert_eq!( + resolve_remote_path(Path::new("/ignored"), Path::new(path)).unwrap(), + Path::new(path) + ); } } + #[test] + fn maps_metadata_to_write_options() { + let modified = SystemTime::UNIX_EPOCH; + let mode = UnixPex::from(0o640); + let options = write_options(&Metadata::default().size(42).mode(mode).modified(modified)); + + assert_eq!(options.size_hint, Some(42)); + assert_eq!(options.mode, Some(mode)); + assert_eq!(options.modified, Some(modified)); + } + + #[test] + fn maps_settable_metadata_fields() { + let accessed = SystemTime::UNIX_EPOCH; + let modified = accessed + std::time::Duration::from_secs(1); + let mode = UnixPex::from(0o640); + let options = set_metadata_options( + &Metadata::default() + .accessed(accessed) + .gid(20) + .mode(mode) + .modified(modified) + .uid(10), + ); + + assert_eq!(options.accessed, Some(accessed)); + assert_eq!(options.gid, Some(20)); + assert_eq!(options.mode, Some(mode)); + assert_eq!(options.modified, Some(modified)); + assert_eq!(options.uid, Some(10)); + } + + #[test] + fn default_metadata_maps_to_empty_options() { + assert_eq!(write_options(&Metadata::default()), Default::default()); + assert_eq!( + set_metadata_options(&Metadata::default()), + Default::default() + ); + } + #[test] fn filter_self_refs_drops_entry_matching_listed_dir() { let entries = vec![ diff --git a/src/host/remote_bridged/temp_mapped_file.rs b/src/host/remote_bridged/temp_mapped_file.rs index 142942e..6337ab6 100644 --- a/src/host/remote_bridged/temp_mapped_file.rs +++ b/src/host/remote_bridged/temp_mapped_file.rs @@ -82,6 +82,19 @@ impl TempMappedFile { Ok(()) } + /// Returns the number of bytes currently stored in the temporary file. + pub fn len(&self) -> HostResult { + std::fs::metadata(self.tempfile.path()) + .map(|metadata| metadata.len()) + .map_err(|error| { + HostError::new( + HostErrorType::FileNotAccessible, + Some(error), + self.tempfile.path(), + ) + }) + } + fn write_hnd(&mut self) -> io::Result> { let mut lock = self.lock_handle()?; if lock.is_none() { diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index 7a6ca15..e08f236 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -3,7 +3,7 @@ //! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall use std::fs::OpenOptions; -use std::io::Read; +use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::SystemTime; @@ -107,10 +107,12 @@ impl FileTransferActivity { } }; - let new_file_size = match std::io::copy(&mut reader, &mut writer) { - Err(err) => return Err(format!("Could not write file: {err}")), - Ok(size) => size, - }; + if let Err(err) = std::io::copy(&mut reader, &mut writer) { + return Err(format!("Could not write file: {err}")); + } + reader + .finish() + .map_err(|err| format!("Could not finish file read: {err}"))?; // edit file @@ -126,10 +128,11 @@ impl FileTransferActivity { }; let mut writer = match self.browser.local_pane_mut().fs.create_file( entry.path(), - &Metadata { - size: new_file_size, - ..Default::default() - }, + &Metadata::default().size( + std::fs::metadata(tempfile.as_path()) + .map_err(|err| format!("Could not stat edited file: {err}"))? + .len(), + ), ) { Ok(writer) => writer, Err(err) => { @@ -141,6 +144,10 @@ impl FileTransferActivity { return Err(format!("Could not write file: {err}")); } + writer + .flush() + .map_err(|err| format!("Could not write file: {err}"))?; + self.browser .local_pane_mut() .fs diff --git a/src/ui/activities/filetransfer/actions/file_size.rs b/src/ui/activities/filetransfer/actions/file_size.rs index 80bd239..58fb1c3 100644 --- a/src/ui/activities/filetransfer/actions/file_size.rs +++ b/src/ui/activities/filetransfer/actions/file_size.rs @@ -2,6 +2,12 @@ use remotefs::File; use super::{FileTransferActivity, LogLevel}; +fn aggregate_sizes(sizes: impl IntoIterator>) -> Option { + sizes + .into_iter() + .try_fold(0_u64, |total, size| total.checked_add(size?)) +} + impl FileTransferActivity { /// Calculate and display the total size of the selected file(s) via the active tab's pane. pub(crate) fn action_get_file_size(&mut self) { @@ -11,17 +17,23 @@ impl FileTransferActivity { let total_size = self.get_files_size(files); self.umount_wait(); - self.mount_info(format!( - "Total file size: {size}", - size = bytesize::ByteSize::b(total_size) - )); + let message = total_size.map_or_else( + || String::from("Total file size: Unknown"), + |size| { + format!( + "Total file size: {size}", + size = bytesize::ByteSize::b(size) + ) + }, + ); + self.mount_info(message); } - fn get_files_size(&mut self, files: Vec) -> u64 { - files.into_iter().map(|f| self.get_file_size(f)).sum() + fn get_files_size(&mut self, files: Vec) -> Option { + aggregate_sizes(files.into_iter().map(|file| self.get_file_size(file))) } - fn get_file_size(&mut self, file: File) -> u64 { + fn get_file_size(&mut self, file: File) -> Option { if let Some(symlink) = &file.metadata().symlink { match self.browser.fs_pane_mut().fs.stat(symlink) { Ok(stat) => stat.metadata().size, @@ -33,12 +45,12 @@ impl FileTransferActivity { path = symlink.display(), ), ); - 0 + None } } } else if file.is_dir() { match self.browser.fs_pane_mut().fs.list_dir(&file.path) { - Ok(list) => list.into_iter().map(|f| self.get_file_size(f)).sum(), + Ok(list) => self.get_files_size(list), Err(err) => { self.log( LogLevel::Error, @@ -47,7 +59,7 @@ impl FileTransferActivity { path = file.path.display(), ), ); - 0 + None } } } else { @@ -55,3 +67,14 @@ impl FileTransferActivity { } } } + +#[cfg(test)] +mod test { + use super::aggregate_sizes; + + #[test] + fn aggregates_only_known_sizes() { + assert_eq!(aggregate_sizes([Some(2), Some(3)]), Some(5)); + assert_eq!(aggregate_sizes([Some(2), None, Some(3)]), None); + } +} diff --git a/src/ui/activities/filetransfer/actions/newfile.rs b/src/ui/activities/filetransfer/actions/newfile.rs index 09543e2..3c35e1f 100644 --- a/src/ui/activities/filetransfer/actions/newfile.rs +++ b/src/ui/activities/filetransfer/actions/newfile.rs @@ -28,7 +28,7 @@ impl FileTransferActivity { .browser .fs_pane_mut() .fs - .create_file(file_path.as_path(), &Metadata::default()) + .create_file(file_path.as_path(), &Metadata::default().size(0)) { Ok(f) => f, Err(err) => { diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index f81ded1..a6a1add 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -131,6 +131,14 @@ impl FileTransferActivity { return; } + if let Err(err) = reader.finish() { + self.log( + LogLevel::Error, + format!("Failed to finish bridged file read: {err}"), + ); + return; + } + if tmpfile.exists() { self.open_path_with(tmpfile.as_path(), open_with); } diff --git a/src/ui/activities/filetransfer/components/popups/file_info.rs b/src/ui/activities/filetransfer/components/popups/file_info.rs index 18980aa..b7c226b 100644 --- a/src/ui/activities/filetransfer/components/popups/file_info.rs +++ b/src/ui/activities/filetransfer/components/popups/file_info.rs @@ -43,11 +43,10 @@ impl FileInfoPopup { .add_col(SpanStatic::from("File type: ")) .add_col(SpanStatic::raw(filetype.clone()).fg(Color::LightGreen)); } - let (bsize, size): (ByteSize, u64) = (ByteSize(file.metadata().size), file.metadata().size); texts .add_row() .add_col(SpanStatic::from("Size: ")) - .add_col(SpanStatic::raw(format!("{bsize} ({size})")).fg(Color::Cyan)); + .add_col(SpanStatic::raw(format_file_size(file)).fg(Color::Cyan)); let atime: String = fmt_time( file.metadata().accessed.unwrap_or(UNIX_EPOCH), "%b %d %Y %H:%M:%S", @@ -117,6 +116,13 @@ impl FileInfoPopup { } } +fn format_file_size(file: &File) -> String { + match file.metadata().size { + Some(size) => format!("{bsize} ({size})", bsize = ByteSize(size), size = size), + None => String::from("Unknown"), + } +} + impl AppComponent for FileInfoPopup { fn on(&mut self, ev: &Event) -> Option { match ev { @@ -128,3 +134,17 @@ impl AppComponent for FileInfoPopup { } } } + +#[cfg(test)] +mod test { + use remotefs::fs::Metadata; + + use super::*; + + #[test] + fn formats_unknown_file_size_as_unknown() { + let file = File::new("/tmp/unknown", Metadata::default()); + + assert_eq!(format_file_size(&file), "Unknown"); + } +} diff --git a/src/ui/activities/filetransfer/lib/transfer.rs b/src/ui/activities/filetransfer/lib/transfer.rs index 31b47cb..9e73f6c 100644 --- a/src/ui/activities/filetransfer/lib/transfer.rs +++ b/src/ui/activities/filetransfer/lib/transfer.rs @@ -16,7 +16,7 @@ use bytesize::ByteSize; pub struct TransferProgress { files_total: usize, files_completed: usize, - cur_file_size: usize, + cur_file_size: Option, cur_file_written: usize, total_bytes_written: usize, pub(crate) started: Instant, @@ -27,7 +27,7 @@ impl Default for TransferProgress { Self { files_total: 0, files_completed: 0, - cur_file_size: 0, + cur_file_size: None, cur_file_written: 0, total_bytes_written: 0, started: Instant::now(), @@ -41,13 +41,15 @@ impl fmt::Display for TransferProgress { 0 => String::from("--:--"), seconds => format!("{:02}:{:02}", seconds / 60, seconds % 60), }; + let total = self.cur_file_size.map_or_else( + || String::from("?"), + |size| ByteSize(size as u64).to_string(), + ); write!( f, - "{} / {} — {:.1}% — ETA {} ({}/s)", + "{} / {total} — {:.1}% — ETA {eta} ({}/s)", ByteSize(self.cur_file_written as u64), - ByteSize(self.cur_file_size as u64), self.calc_partial_progress() * 100.0, - eta, ByteSize(self.calc_bytes_per_second()), ) } @@ -58,15 +60,15 @@ impl TransferProgress { pub fn init(&mut self, files_total: usize) { self.files_total = files_total; self.files_completed = 0; - self.cur_file_size = 0; + self.cur_file_size = None; self.cur_file_written = 0; self.total_bytes_written = 0; self.started = Instant::now(); } - /// Begin a new file with a known size. - pub fn start_file(&mut self, size: usize) { - self.cur_file_size = size; + /// Begin a new file with an optional advertised size. + pub fn start_file(&mut self, size: Option) { + self.cur_file_size = size.and_then(|size| usize::try_from(size).ok()); self.cur_file_written = 0; } @@ -83,7 +85,7 @@ impl TransferProgress { /// fraction in [`Self::calc_full_progress`]. pub fn finish_file(&mut self) { self.files_completed += 1; - self.cur_file_size = 0; + self.cur_file_size = None; self.cur_file_written = 0; } @@ -92,25 +94,26 @@ impl TransferProgress { self.files_completed += 1; } - /// Fraction of the current file written (0.0..=1.0). Zero-byte file => 1.0. + /// Fraction of the current file written (0.0..=1.0). pub fn calc_partial_progress(&self) -> f64 { - if self.cur_file_size == 0 { - return 1.0; + match self.cur_file_size { + None => 0.0, + Some(0) => 1.0, + Some(size) => (self.cur_file_written as f64 / size as f64).min(1.0), } - (self.cur_file_written as f64 / self.cur_file_size as f64).min(1.0) } /// Overall progress (0.0..=1.0): file-weighted with intra-file interpolation. /// /// The current file only contributes a fraction while it is genuinely in - /// progress (`cur_file_size > 0` and not all files completed). A finished - /// file clears `cur_file_size` (see [`Self::finish_file`]) so it is counted + /// progress (a known size and not all files completed). A finished file + /// clears `cur_file_size` (see [`Self::finish_file`]) so it is counted /// exactly once via `files_completed`. pub fn calc_full_progress(&self) -> f64 { if self.files_total == 0 { return 0.0; } - let cur_fraction = if self.cur_file_size == 0 || self.files_completed >= self.files_total { + let cur_fraction = if self.files_completed >= self.files_total { 0.0 } else { self.calc_partial_progress() @@ -234,7 +237,7 @@ mod test { assert!(progress.is_single_file()); assert_eq!(progress.calc_full_progress(), 0.0); - progress.start_file(1024); + progress.start_file(Some(1024)); assert_eq!(progress.calc_partial_progress(), 0.0); assert_eq!(progress.calc_full_progress(), 0.0); @@ -258,13 +261,13 @@ mod test { assert!(!progress.is_single_file()); // File 1 fully transferred => full ≈ 0.25 - progress.start_file(1000); + progress.start_file(Some(1000)); progress.add_bytes(1000); progress.finish_file(); assert!((progress.calc_full_progress() - 0.25).abs() < 1e-9); // File 2 half transferred => partial ≈ 0.5, full ≈ 0.375 - progress.start_file(1000); + progress.start_file(Some(1000)); progress.add_bytes(500); assert!((progress.calc_partial_progress() - 0.5).abs() < 1e-9); assert!((progress.calc_full_progress() - 0.375).abs() < 1e-9); @@ -276,7 +279,7 @@ mod test { progress.init(2); // One file actually transferred. - progress.start_file(100); + progress.start_file(Some(100)); progress.add_bytes(100); progress.finish_file(); @@ -294,15 +297,34 @@ mod test { let mut progress = TransferProgress::default(); progress.init(1); - progress.start_file(0); + progress.start_file(Some(0)); assert!((progress.calc_partial_progress() - 1.0).abs() < 1e-9); } + #[test] + fn test_transfer_progress_unknown_size() { + let mut progress = TransferProgress::default(); + progress.init(1); + progress.start_file(None); + progress.add_bytes(256); + + assert_eq!(progress.calc_partial_progress(), 0.0); + assert_eq!(progress.calc_full_progress(), 0.0); + assert_eq!(progress.calc_eta(), 0); + assert_eq!(progress.total_bytes_written(), 256); + assert!(progress.to_string().contains("?")); + assert!(!progress.to_string().contains("0 B")); + + progress.finish_file(); + assert_eq!(progress.files_completed(), 1); + assert_eq!(progress.calc_full_progress(), 1.0); + } + #[test] fn test_transfer_progress_timing() { let mut progress = TransferProgress::default(); progress.init(1); - progress.start_file(1024); + progress.start_file(Some(1024)); progress.started = progress .started diff --git a/src/ui/activities/filetransfer/session/transfer.rs b/src/ui/activities/filetransfer/session/transfer.rs index 83666a2..504768a 100644 --- a/src/ui/activities/filetransfer/session/transfer.rs +++ b/src/ui/activities/filetransfer/session/transfer.rs @@ -10,7 +10,7 @@ use bytesize::ByteSize; use remotefs::fs::File; use thiserror::Error; -use crate::host::HostError; +use crate::host::{HostError, HostReader, HostWriter}; use crate::ui::activities::filetransfer::{FileTransferActivity, LogLevel}; use crate::utils::fmt::fmt_millis; @@ -457,8 +457,8 @@ impl FileTransferActivity { host: &File, remote: &Path, file_name: String, - mut reader: Box, - mut writer: Box, + mut reader: HostReader, + mut writer: HostWriter, ) -> Result<(), TransferErrorReason> { // Write file let file_size = self @@ -467,18 +467,16 @@ impl FileTransferActivity { .fs .stat(host.path()) .map_err(TransferErrorReason::HostError) - .map(|x| x.metadata().size as usize)?; + .map(|x| x.metadata().size)?; // Init transfer self.transfer.progress.start_file(file_size); let file_started = Instant::now(); // Write remote file - let mut total_bytes_written: usize = 0; let mut last_redraw: Instant = Instant::now(); let mut last_input_event_fetch: Option = None; - // While the entire file hasn't been completely written, - // Or filetransfer has been aborted - while total_bytes_written < file_size && !self.transfer.aborted() { + // Read until EOF or until the transfer is aborted. + while !self.transfer.aborted() { // Handle input events (each 500ms) or if never fetched before if last_input_event_fetch.is_none() || last_input_event_fetch @@ -494,33 +492,17 @@ impl FileTransferActivity { } // Read till you can let mut buffer: [u8; BUFSIZE] = [0; BUFSIZE]; - let delta: usize = match reader.read(&mut buffer) { - Ok(bytes_read) => { - total_bytes_written += bytes_read; - if bytes_read == 0 { - continue; - } else { - let mut delta: usize = 0; - while delta < bytes_read { - // Write bytes - match writer.write(&buffer[delta..bytes_read]) { - Ok(bytes) => { - delta += bytes; - } - Err(err) => { - return Err(TransferErrorReason::RemoteIoError(err)); - } - } - } - delta - } - } - Err(err) => { - return Err(TransferErrorReason::HostIoError(err)); - } - }; + let bytes_read = reader + .read(&mut buffer) + .map_err(TransferErrorReason::HostIoError)?; + if bytes_read == 0 { + break; + } + writer + .write_all(&buffer[..bytes_read]) + .map_err(TransferErrorReason::RemoteIoError)?; // Increase progress - self.transfer.progress.add_bytes(delta); + self.transfer.progress.add_bytes(bytes_read); // Redraw at most every 100ms to keep UI responsive for large files if last_redraw.elapsed().as_millis() >= 100 { self.update_progress_bar(format!("Uploading \"{file_name}\"…")); @@ -528,12 +510,14 @@ impl FileTransferActivity { last_redraw = Instant::now(); } } - // Finalize stream - handle_remote_finalize_result(self.browser.remote_pane_mut().fs.finalize_write(writer))?; // if upload was abrupted, return error if self.transfer.aborted() { return Err(TransferErrorReason::Abrupted); } + writer.flush().map_err(TransferErrorReason::RemoteIoError)?; + reader.finish().map_err(TransferErrorReason::HostError)?; + // Finalize stream + handle_remote_finalize_result(self.browser.remote_pane_mut().fs.finalize_write(writer))?; // set stat if let Err(err) = self .browser @@ -812,21 +796,17 @@ impl FileTransferActivity { host_bridge: &Path, remote: &File, file_name: String, - mut reader: Box, - mut writer: Box, + mut reader: HostReader, + mut writer: HostWriter, ) -> Result<(), TransferErrorReason> { - let mut total_bytes_written: usize = 0; // Init transfer - self.transfer - .progress - .start_file(remote.metadata.size as usize); + self.transfer.progress.start_file(remote.metadata.size); let file_started = Instant::now(); // Write host_bridge file let mut last_redraw: Instant = Instant::now(); let mut last_input_event_fetch: Option = None; - // While the entire file hasn't been completely read, - // Or filetransfer has been aborted - while total_bytes_written < remote.metadata.size as usize && !self.transfer.aborted() { + // Read until EOF or until the transfer is aborted. + while !self.transfer.aborted() { // Handle input events (each 500 ms) or is None if last_input_event_fetch.is_none() || last_input_event_fetch @@ -842,31 +822,17 @@ impl FileTransferActivity { } // Read till you can let mut buffer: [u8; BUFSIZE] = [0; BUFSIZE]; - let delta: usize = match reader.read(&mut buffer) { - Ok(bytes_read) => { - total_bytes_written += bytes_read; - if bytes_read == 0 { - continue; - } else { - let mut delta: usize = 0; - while delta < bytes_read { - // Write bytes - match writer.write(&buffer[delta..bytes_read]) { - Ok(bytes) => delta += bytes, - Err(err) => { - return Err(TransferErrorReason::HostIoError(err)); - } - } - } - delta - } - } - Err(err) => { - return Err(TransferErrorReason::RemoteIoError(err)); - } - }; + let bytes_read = reader + .read(&mut buffer) + .map_err(TransferErrorReason::RemoteIoError)?; + if bytes_read == 0 { + break; + } + writer + .write_all(&buffer[..bytes_read]) + .map_err(TransferErrorReason::HostIoError)?; // Set progress - self.transfer.progress.add_bytes(delta); + self.transfer.progress.add_bytes(bytes_read); // Redraw at most every 100ms to keep UI responsive for large files if last_redraw.elapsed().as_millis() >= 100 { self.update_progress_bar(format!("Downloading \"{file_name}\"")); @@ -879,6 +845,10 @@ impl FileTransferActivity { return Err(TransferErrorReason::Abrupted); } + writer.flush().map_err(TransferErrorReason::HostIoError)?; + reader + .finish() + .map_err(TransferErrorReason::RemoteHostError)?; // Finalize write self.browser .local_pane_mut() @@ -953,25 +923,20 @@ mod worklist_test { // equal (`File` derives `PartialEq` over its full metadata, timestamps // included), allowing direct equality assertions against the worklist. let t = SystemTime::UNIX_EPOCH; - let metadata = Metadata { - accessed: Some(t), - created: Some(t), - modified: Some(t), - file_type: if is_dir { + let metadata = Metadata::default() + .accessed(t) + .created(t) + .modified(t) + .file_type(if is_dir { FileType::Directory } else { FileType::File - }, - symlink: None, - gid: Some(0), - uid: Some(0), - mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), - size: 64, - }; - File { - path: PathBuf::from(path), - metadata, - } + }) + .gid(0) + .uid(0) + .mode(UnixPex::from(if is_dir { 0o755 } else { 0o644 })) + .size(64); + File::new(path, metadata) } #[test] diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs index 5d1ddce..32f8473 100644 --- a/src/utils/test_helpers.rs +++ b/src/utils/test_helpers.rs @@ -14,10 +14,7 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) { // Write let tmpfile = create_sample_file(); ( - File { - path: tmpfile.path().to_path_buf(), - metadata: Metadata::default(), - }, + File::new(tmpfile.path().to_path_buf(), Metadata::default()), tmpfile, ) } @@ -58,14 +55,14 @@ pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> { /// Create a File at specified path pub fn make_fsentry>(path: P, is_dir: bool) -> File { let path: PathBuf = path.as_ref().to_path_buf(); - File { + File::new( path, - metadata: Metadata::default().file_type(if is_dir { + Metadata::default().file_type(if is_dir { FileType::Directory } else { FileType::File }), - } + ) } /// Open a file with two handlers, the first is to read, the second is to write