mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat: new yazi-sftp crate (#3166)
This commit is contained in:
parent
f0502a46b7
commit
4a3710b1c4
52 changed files with 3963 additions and 94 deletions
1262
Cargo.lock
generated
1262
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -32,9 +32,9 @@ foldhash = "0.2.0"
|
|||
futures = "0.3.31"
|
||||
globset = "0.4.16"
|
||||
hashbrown = { version = "0.16.0", features = [ "serde" ] }
|
||||
indexmap = { version = "2.11.0", features = [ "serde" ] }
|
||||
indexmap = { version = "2.11.1", features = [ "serde" ] }
|
||||
libc = "0.2.175"
|
||||
lru = "0.16.0"
|
||||
lru = "0.16.1"
|
||||
mlua = { version = "0.11.3", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
|
||||
objc = "0.2.7"
|
||||
ordered-float = { version = "5.0.0", features = [ "serde" ] }
|
||||
|
|
@ -42,6 +42,7 @@ parking_lot = "0.12.4"
|
|||
paste = "1.0.15"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
regex = "1.11.2"
|
||||
russh = { version = "0.54.3", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_json = "1.0.143"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ libc = { workspace = true }
|
|||
uzers = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.60.2", features = [ "Win32_Storage_FileSystem" ] }
|
||||
windows-sys = { version = "0.61.0", features = [ "Win32_Storage_FileSystem" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation-sys = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ libc = { workspace = true }
|
|||
uzers = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
windows-sys = { version = "0.60.2", features = [ "Win32_System_JobObjects" ] }
|
||||
windows-sys = { version = "0.61.0", features = [ "Win32_System_JobObjects" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
19
yazi-sftp/Cargo.toml
Normal file
19
yazi-sftp/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "yazi-sftp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "AspectUnk", "sxyazi <sxyazi@gmail.com>" ]
|
||||
description = "Yazi SFTP client"
|
||||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
russh = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
os_str_bytes = { version = "7.1.1", default-features = false, features = [ "conversions" ] }
|
||||
201
yazi-sftp/LICENSE
Normal file
201
yazi-sftp/LICENSE
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
15
yazi-sftp/README.md
Normal file
15
yazi-sftp/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
## yazi-sftp
|
||||
|
||||
A fork of [`russh-sftp`](https://github.com/AspectUnk/russh-sftp) used by Yazi, with some changes:
|
||||
|
||||
- Supports paths containing invalid UTF-8
|
||||
- Supports retrieving file nlink, username, and group
|
||||
- Uses generic return parameters for a more idiomatic API, e.g.:
|
||||
```rust
|
||||
let attrs: responses::Attrs = session.send(requests::Stat::new(path)).await?;
|
||||
```
|
||||
- Reduced dependencies
|
||||
- Performance optimizations:
|
||||
- Copy-on-write for all packets to avoid unnecessary memory allocation
|
||||
- Packet lengths are precomputed to avoid secondary allocations
|
||||
- Avoids cloning buffers in `AsyncRead` and `AsyncWrite` implementations
|
||||
100
yazi-sftp/src/byte_str.rs
Normal file
100
yazi-sftp/src/byte_str.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
use std::{borrow::Cow, ffi::{OsStr, OsString}, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct ByteStr<'a>(Cow<'a, [u8]>);
|
||||
|
||||
impl Deref for ByteStr<'_> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ByteStr<'a> {
|
||||
fn from(value: &'a str) -> Self { ByteStr(Cow::Borrowed(value.as_bytes())) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ByteStr<'a>> for ByteStr<'a> {
|
||||
fn from(value: &'a ByteStr) -> Self { ByteStr(Cow::Borrowed(&value.0)) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a OsStr> for ByteStr<'a> {
|
||||
fn from(value: &'a OsStr) -> Self {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
ByteStr(Cow::Borrowed(value.as_bytes()))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use os_str_bytes::OsStrBytes;
|
||||
ByteStr(value.to_raw_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Path> for ByteStr<'a> {
|
||||
fn from(value: &'a Path) -> Self { ByteStr::from(value.as_os_str()) }
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for ByteStr<'_> {
|
||||
fn eq(&self, other: &&str) -> bool { self.0 == other.as_bytes() }
|
||||
}
|
||||
|
||||
impl<'a> ByteStr<'a> {
|
||||
pub fn to_os_str(&self) -> Cow<'_, OsStr> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
OsStr::from_bytes(&self.0).into()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use os_str_bytes::OsStrBytes;
|
||||
OsStr::assert_from_raw_bytes(self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_os_string(self) -> OsString {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
OsString::from_vec(self.0.into_owned())
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use os_str_bytes::OsStrBytes;
|
||||
OsStr::assert_from_raw_bytes(self.0).into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_path(&self) -> Cow<'_, Path> {
|
||||
match self.to_os_str() {
|
||||
Cow::Borrowed(s) => Path::new(s).into(),
|
||||
Cow::Owned(s) => PathBuf::from(s).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_path(self) -> PathBuf {
|
||||
match self.0 {
|
||||
Cow::Borrowed(_) => self.to_path().into_owned(),
|
||||
Cow::Owned(_) => self.into_os_string().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(&self, other: impl Into<ByteStr<'a>>) -> PathBuf {
|
||||
let other = other.into();
|
||||
match self.to_path() {
|
||||
Cow::Borrowed(p) => p.join(other.to_path()),
|
||||
Cow::Owned(mut p) => {
|
||||
p.push(other.to_path());
|
||||
p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe fn from_str_bytes_unchecked(bytes: &'a [u8]) -> Self {
|
||||
Self(Cow::Borrowed(bytes))
|
||||
}
|
||||
}
|
||||
375
yazi-sftp/src/de.rs
Normal file
375
yazi-sftp/src/de.rs
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
use serde::{Deserializer as _, de::{EnumAccess, MapAccess, SeqAccess, VariantAccess, value::U32Deserializer}};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub(super) struct Deserializer<'a> {
|
||||
input: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Deserializer<'a> {
|
||||
pub(super) fn once<'de, T>(input: &'de [u8]) -> Result<T, Error>
|
||||
where
|
||||
T: serde::Deserialize<'de>,
|
||||
{
|
||||
let mut de = Deserializer { input };
|
||||
let t = T::deserialize(&mut de)?;
|
||||
if !de.input.is_empty() {
|
||||
return Err(Error::serde("trailing bytes"));
|
||||
}
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserializer<'de> for &mut Deserializer<'de> {
|
||||
type Error = Error;
|
||||
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let len = self.input.len();
|
||||
visitor.visit_seq(SeqDeserializer { de: self, remaining: len })
|
||||
}
|
||||
|
||||
fn deserialize_bool<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("bool not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_i8<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("i8 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_i16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("i16 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_i32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("i32 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_i64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("i64 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let b = *self.input.first().ok_or(Error::serde("u8 not enough"))?;
|
||||
|
||||
self.input = &self.input[1..];
|
||||
visitor.visit_u8(b)
|
||||
}
|
||||
|
||||
fn deserialize_u16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("u16 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let b: [u8; 4] = self.input.get(..4).ok_or(Error::serde("u32 not enough"))?.try_into().unwrap();
|
||||
|
||||
self.input = &self.input[4..];
|
||||
visitor.visit_u32(u32::from_be_bytes(b))
|
||||
}
|
||||
|
||||
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let b: [u8; 8] = self.input.get(..8).ok_or(Error::serde("u64 not enough"))?.try_into().unwrap();
|
||||
|
||||
self.input = &self.input[8..];
|
||||
visitor.visit_u64(u64::from_be_bytes(b))
|
||||
}
|
||||
|
||||
fn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("f32 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_f64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("f64 not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_char<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("char not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let len: [u8; 4] =
|
||||
self.input.get(..4).ok_or(Error::serde("invalid string length"))?.try_into().unwrap();
|
||||
let len = u32::from_be_bytes(len) as usize;
|
||||
|
||||
self.input = &self.input[4..];
|
||||
let b = self.input.get(..len).ok_or(Error::serde("string not enough"))?;
|
||||
|
||||
self.input = &self.input[len..];
|
||||
visitor.visit_str(str::from_utf8(b).map_err(|e| Error::serde(e.to_string()))?)
|
||||
}
|
||||
|
||||
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
self.deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let len: [u8; 4] =
|
||||
self.input.get(..4).ok_or(Error::serde("invalid bytes length"))?.try_into().unwrap();
|
||||
let len = u32::from_be_bytes(len) as usize;
|
||||
let b = self.input.get(4..4 + len).ok_or(Error::serde("bytes not enough"))?;
|
||||
|
||||
self.input = &self.input[4 + len..];
|
||||
visitor.visit_bytes(b)
|
||||
}
|
||||
|
||||
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
self.deserialize_bytes(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("option not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
let len: [u8; 4] =
|
||||
self.input.get(..4).ok_or(Error::serde("invalid seq length"))?.try_into().unwrap();
|
||||
|
||||
self.input = &self.input[4..];
|
||||
visitor.visit_seq(SeqDeserializer { de: self, remaining: u32::from_be_bytes(len) as _ })
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(SeqDeserializer { de: self, remaining: len })
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
self.deserialize_tuple(len, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_map(MapDeserializer { de: self })
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
self.deserialize_tuple(fields.len(), visitor)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(self)
|
||||
}
|
||||
|
||||
fn deserialize_identifier<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("identifier not supported"))
|
||||
}
|
||||
|
||||
fn deserialize_ignored_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
Err(Error::serde("ignored any not supported"))
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool { false }
|
||||
}
|
||||
|
||||
struct SeqDeserializer<'a, 'de: 'a> {
|
||||
de: &'a mut Deserializer<'de>,
|
||||
remaining: usize,
|
||||
}
|
||||
|
||||
impl<'de> SeqAccess<'de> for SeqDeserializer<'_, 'de> {
|
||||
type Error = Error;
|
||||
|
||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
|
||||
where
|
||||
T: serde::de::DeserializeSeed<'de>,
|
||||
{
|
||||
if self.remaining == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.remaining -= 1;
|
||||
seed.deserialize(&mut *self.de).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> Option<usize> { Some(self.remaining) }
|
||||
}
|
||||
|
||||
struct MapDeserializer<'a, 'de: 'a> {
|
||||
de: &'a mut Deserializer<'de>,
|
||||
}
|
||||
|
||||
impl<'de> MapAccess<'de> for MapDeserializer<'_, 'de> {
|
||||
type Error = Error;
|
||||
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
||||
where
|
||||
K: serde::de::DeserializeSeed<'de>,
|
||||
{
|
||||
if self.de.input.is_empty() { Ok(None) } else { seed.deserialize(&mut *self.de).map(Some) }
|
||||
}
|
||||
|
||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::DeserializeSeed<'de>,
|
||||
{
|
||||
seed.deserialize(&mut *self.de)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> VariantAccess<'de> for &mut Deserializer<'de> {
|
||||
type Error = Error;
|
||||
|
||||
fn unit_variant(self) -> Result<(), Self::Error> { Ok(()) }
|
||||
|
||||
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
|
||||
where
|
||||
T: serde::de::DeserializeSeed<'de>,
|
||||
{
|
||||
seed.deserialize(self)
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
use serde::Deserializer;
|
||||
self.deserialize_tuple(len, visitor)
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
self,
|
||||
fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: serde::de::Visitor<'de>,
|
||||
{
|
||||
self.deserialize_tuple(fields.len(), visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> EnumAccess<'de> for &mut Deserializer<'de> {
|
||||
type Error = Error;
|
||||
type Variant = Self;
|
||||
|
||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
||||
where
|
||||
V: serde::de::DeserializeSeed<'de>,
|
||||
{
|
||||
let b: [u8; 4] =
|
||||
self.input.get(..4).ok_or(Error::serde("enum not enough"))?.try_into().unwrap();
|
||||
self.input = &self.input[4..];
|
||||
|
||||
let de = U32Deserializer::<Self::Error>::new(u32::from_be_bytes(b));
|
||||
Ok((seed.deserialize(de)?, self))
|
||||
}
|
||||
}
|
||||
66
yazi-sftp/src/error.rs
Normal file
66
yazi-sftp/src/error.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::responses;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IO(std::io::Error),
|
||||
Serde(Cow<'static, str>),
|
||||
Status(responses::Status),
|
||||
Packet(&'static str),
|
||||
Timeout,
|
||||
Unsupported,
|
||||
Custom(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(super) fn serde(s: impl Into<Cow<'static, str>>) -> Self { Self::Serde(s.into()) }
|
||||
|
||||
pub(super) fn custom(s: impl Into<Cow<'static, str>>) -> Self { Self::Custom(s.into()) }
|
||||
}
|
||||
|
||||
impl serde::ser::Error for Error {
|
||||
fn custom<T: std::fmt::Display>(msg: T) -> Self { Self::serde(msg.to_string()) }
|
||||
}
|
||||
|
||||
impl serde::de::Error for Error {
|
||||
fn custom<T: std::fmt::Display>(msg: T) -> Self { Self::serde(msg.to_string()) }
|
||||
}
|
||||
|
||||
impl From<Error> for std::io::Error {
|
||||
fn from(err: Error) -> Self {
|
||||
match err {
|
||||
Error::IO(e) => e,
|
||||
Error::Serde(_) => Self::new(std::io::ErrorKind::InvalidData, err),
|
||||
Error::Status(_) => Self::other(err),
|
||||
Error::Packet(_) => Self::new(std::io::ErrorKind::InvalidData, err),
|
||||
Error::Timeout => Self::new(std::io::ErrorKind::TimedOut, err),
|
||||
Error::Unsupported => Self::new(std::io::ErrorKind::Unsupported, err),
|
||||
Error::Custom(_) => Self::other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
|
||||
fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self { Self::custom("channel closed") }
|
||||
}
|
||||
|
||||
impl From<tokio::sync::oneshot::error::RecvError> for Error {
|
||||
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { Self::custom("channel closed") }
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::IO(e) => write!(f, "IO error: {e}"),
|
||||
Self::Serde(s) => write!(f, "Serde error: {s}"),
|
||||
Self::Status(s) => write!(f, "Status error: {s:?}"),
|
||||
Self::Packet(s) => write!(f, "Unexpected packet: {s}"),
|
||||
Self::Timeout => write!(f, "Operation timed out"),
|
||||
Self::Unsupported => write!(f, "Operation not supported"),
|
||||
Self::Custom(s) => write!(f, "{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
146
yazi-sftp/src/fs/attrs.rs
Normal file
146
yazi-sftp/src/fs/attrs.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::Visitor, ser::SerializeStruct};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Attrs {
|
||||
pub size: Option<u64>,
|
||||
pub uid: Option<u32>,
|
||||
pub gid: Option<u32>,
|
||||
pub perm: Option<u32>,
|
||||
pub atime: Option<u32>,
|
||||
pub mtime: Option<u32>,
|
||||
pub extended: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn len(&self) -> usize {
|
||||
let mut len = 4;
|
||||
if let Some(size) = self.size {
|
||||
len += size_of_val(&size);
|
||||
}
|
||||
if self.uid.is_some() || self.gid.is_some() {
|
||||
len += 4 + 4;
|
||||
}
|
||||
if let Some(perm) = self.perm {
|
||||
len += size_of_val(&perm);
|
||||
}
|
||||
if self.atime.is_some() || self.mtime.is_some() {
|
||||
len += 4 + 4;
|
||||
}
|
||||
if !self.extended.is_empty() {
|
||||
len += 4 + self.extended.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>();
|
||||
}
|
||||
len
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Attrs {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut flags: u32 = 0;
|
||||
let mut len = 1;
|
||||
|
||||
if self.size.is_some() {
|
||||
flags |= 0x1;
|
||||
len += 1;
|
||||
}
|
||||
if self.uid.is_some() || self.gid.is_some() {
|
||||
flags |= 0x2;
|
||||
len += 2;
|
||||
}
|
||||
if self.perm.is_some() {
|
||||
flags |= 0x4;
|
||||
len += 1;
|
||||
}
|
||||
if self.atime.is_some() || self.mtime.is_some() {
|
||||
flags |= 0x8;
|
||||
len += 2;
|
||||
}
|
||||
if !self.extended.is_empty() {
|
||||
flags |= 0x80000000;
|
||||
len += 1 + self.extended.len() * 2;
|
||||
}
|
||||
|
||||
let mut seq = serializer.serialize_struct("Attrs", len)?;
|
||||
seq.serialize_field("flags", &flags)?;
|
||||
if let Some(size) = self.size {
|
||||
seq.serialize_field("size", &size)?;
|
||||
}
|
||||
if self.uid.is_some() || self.gid.is_some() {
|
||||
seq.serialize_field("uid", &self.uid.unwrap_or(0))?;
|
||||
seq.serialize_field("gid", &self.gid.unwrap_or(0))?;
|
||||
}
|
||||
if let Some(perm) = self.perm {
|
||||
seq.serialize_field("perm", &perm)?;
|
||||
}
|
||||
if self.atime.is_some() || self.mtime.is_some() {
|
||||
seq.serialize_field("atime", &self.atime.unwrap_or(0))?;
|
||||
seq.serialize_field("mtime", &self.mtime.unwrap_or(0))?;
|
||||
}
|
||||
if !self.extended.is_empty() {
|
||||
let count = self.extended.len() as u32;
|
||||
seq.serialize_field("extended_count", &count)?;
|
||||
for (k, v) in self.extended.iter().take(count as usize) {
|
||||
seq.serialize_field("extended_key", k)?;
|
||||
seq.serialize_field("extended_value", v)?;
|
||||
}
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Attrs {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct AttrsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for AttrsVisitor {
|
||||
type Value = Attrs;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("attributes")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let b = seq.next_element::<u32>()?.unwrap_or(0);
|
||||
let mut attrs = Attrs::default();
|
||||
|
||||
if b & 0x1 != 0 {
|
||||
attrs.size = seq.next_element()?;
|
||||
}
|
||||
if b & 0x2 != 0 {
|
||||
attrs.uid = seq.next_element()?;
|
||||
attrs.gid = seq.next_element()?;
|
||||
}
|
||||
if b & 0x4 != 0 {
|
||||
attrs.perm = seq.next_element()?;
|
||||
}
|
||||
if b & 0x8 != 0 {
|
||||
attrs.atime = seq.next_element()?;
|
||||
attrs.mtime = seq.next_element()?;
|
||||
}
|
||||
if b & 0x80000000 != 0 {
|
||||
let count: u32 = seq.next_element()?.unwrap_or(0);
|
||||
for _ in 0..count {
|
||||
attrs.extended.insert(
|
||||
seq.next_element()?.unwrap_or_default(),
|
||||
seq.next_element()?.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(AttrsVisitor)
|
||||
}
|
||||
}
|
||||
46
yazi-sftp/src/fs/dir_entry.rs
Normal file
46
yazi-sftp/src/fs/dir_entry.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, path::PathBuf};
|
||||
|
||||
use crate::{ByteStr, fs::Attrs};
|
||||
|
||||
pub struct DirEntry<'a> {
|
||||
pub(super) dir: ByteStr<'a>,
|
||||
pub(super) name: ByteStr<'a>,
|
||||
pub(super) long_name: ByteStr<'a>,
|
||||
pub(super) attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> DirEntry<'a> {
|
||||
pub fn path(&self) -> PathBuf { self.dir.join(&self.name) }
|
||||
|
||||
pub fn name(&self) -> Cow<'_, OsStr> { self.name.to_os_str() }
|
||||
|
||||
pub fn long_name(&self) -> Cow<'_, OsStr> { self.long_name.to_os_str() }
|
||||
|
||||
pub fn attrs(&self) -> &Attrs { &self.attrs }
|
||||
|
||||
pub fn nlink(&self) -> Option<u64> { str::from_utf8(self.long_name_field(1)?).ok()?.parse().ok() }
|
||||
|
||||
pub fn user(&self) -> Option<Cow<'_, OsStr>> {
|
||||
let b = self.long_name_field(2)?;
|
||||
Some(unsafe {
|
||||
match ByteStr::from_str_bytes_unchecked(b).to_os_str() {
|
||||
Cow::Borrowed(_) => OsStr::from_encoded_bytes_unchecked(b).into(),
|
||||
Cow::Owned(s) => s.into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn group(&self) -> Option<Cow<'_, OsStr>> {
|
||||
let b = self.long_name_field(3)?;
|
||||
Some(unsafe {
|
||||
match ByteStr::from_str_bytes_unchecked(b).to_os_str() {
|
||||
Cow::Borrowed(_) => OsStr::from_encoded_bytes_unchecked(b).into(),
|
||||
Cow::Owned(s) => s.into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn long_name_field(&self, n: usize) -> Option<&[u8]> {
|
||||
self.long_name.split(|b| b.is_ascii_whitespace()).filter(|s| !s.is_empty()).nth(n)
|
||||
}
|
||||
}
|
||||
165
yazi-sftp/src/fs/file.rs
Normal file
165
yazi-sftp/src/fs/file.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use std::{io, pin::Pin, task::{Context, Poll, ready}, time::Duration};
|
||||
|
||||
use tokio::{io::{AsyncRead, AsyncWrite, ReadBuf}, sync::oneshot, time::{Timeout, timeout}};
|
||||
|
||||
use crate::{Error, Packet, Session, fs::Attrs};
|
||||
|
||||
pub struct File<'a> {
|
||||
session: &'a Session,
|
||||
handle: String,
|
||||
|
||||
closed: bool,
|
||||
cursor: u64,
|
||||
close_rx: Option<Timeout<oneshot::Receiver<Packet<'static>>>>,
|
||||
read_rx: Option<oneshot::Receiver<Packet<'static>>>,
|
||||
write_rx: Option<(oneshot::Receiver<Packet<'static>>, usize)>,
|
||||
flush_rx: Option<Timeout<oneshot::Receiver<Packet<'static>>>>,
|
||||
}
|
||||
|
||||
impl Unpin for File<'_> {}
|
||||
|
||||
impl Drop for File<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !self.closed {
|
||||
self.session.close(&self.handle).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> File<'a> {
|
||||
pub(crate) fn new(session: &'a Session, handle: impl Into<String>) -> Self {
|
||||
Self {
|
||||
session,
|
||||
handle: handle.into(),
|
||||
|
||||
closed: false,
|
||||
cursor: 0,
|
||||
close_rx: None,
|
||||
read_rx: None,
|
||||
write_rx: None,
|
||||
flush_rx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fstat(&self) -> Result<Attrs, Error> { self.session.fstat(&self.handle).await }
|
||||
|
||||
pub async fn fsetstat(&self, attrs: Attrs) -> Result<(), Error> {
|
||||
self.session.fsetstat(&self.handle, attrs).await
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for File<'_> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
if me.read_rx.is_none() {
|
||||
let max = buf.remaining().min(261120) as u32;
|
||||
me.read_rx = Some(me.session.read(&me.handle, me.cursor, max)?);
|
||||
}
|
||||
|
||||
let result = ready!(Pin::new(me.read_rx.as_mut().unwrap()).poll(cx));
|
||||
me.read_rx = None;
|
||||
|
||||
Poll::Ready(match result {
|
||||
Ok(Packet::Data(data)) => {
|
||||
let len = buf.remaining().min(data.data.len());
|
||||
me.cursor += len as u64;
|
||||
buf.put_slice(&data.data[..len]);
|
||||
Ok(())
|
||||
}
|
||||
Ok(Packet::Status(status)) if status.is_eof() => Ok(()),
|
||||
Ok(Packet::Status(status)) => Err(Error::Status(status).into()),
|
||||
Ok(_) => Err(Error::Packet("not a Data or Status").into()),
|
||||
Err(e) => Err(Error::from(e).into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for File<'_> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
let (rx, len) = match &mut me.write_rx {
|
||||
Some((rx, len)) => (rx, *len),
|
||||
None => {
|
||||
let max = buf.len().min(261120);
|
||||
let rx = me.session.write(&me.handle, me.cursor, &buf[..max])?;
|
||||
(&mut me.write_rx.get_or_insert((rx, max)).0, max)
|
||||
}
|
||||
};
|
||||
|
||||
let result = ready!(Pin::new(rx).poll(cx));
|
||||
me.write_rx = None;
|
||||
|
||||
Poll::Ready(match result {
|
||||
Ok(Packet::Status(status)) if status.is_ok() => {
|
||||
me.cursor += len as u64;
|
||||
Ok(len)
|
||||
}
|
||||
Ok(Packet::Status(status)) => Err(Error::Status(status).into()),
|
||||
Ok(_) => Err(Error::Packet("not a Status").into()),
|
||||
Err(e) => Err(Error::from(e).into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
if me.flush_rx.is_none() {
|
||||
match me.session.fsync(&me.handle) {
|
||||
Ok(rx) => me.flush_rx = Some(timeout(Duration::from_secs(10), rx)),
|
||||
Err(Error::Unsupported) => return Poll::Ready(Ok(())),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
}
|
||||
|
||||
let rx = unsafe { Pin::new_unchecked(me.flush_rx.as_mut().unwrap()) };
|
||||
let result = ready!(rx.poll(cx));
|
||||
me.flush_rx = None;
|
||||
|
||||
let Ok(result) = result else {
|
||||
return Poll::Ready(Err(Error::Timeout.into()));
|
||||
};
|
||||
|
||||
Poll::Ready(match result {
|
||||
Ok(Packet::Status(status)) if status.is_ok() => Ok(()),
|
||||
Ok(Packet::Status(status)) => Err(Error::Status(status).into()),
|
||||
Ok(_) => Err(Error::Packet("not a Status").into()),
|
||||
Err(e) => Err(Error::from(e).into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
if me.close_rx.is_none() {
|
||||
me.close_rx = Some(timeout(Duration::from_secs(10), me.session.close(&me.handle)?));
|
||||
}
|
||||
|
||||
let rx = unsafe { Pin::new_unchecked(me.close_rx.as_mut().unwrap()) };
|
||||
let result = ready!(rx.poll(cx));
|
||||
me.close_rx = None;
|
||||
|
||||
let Ok(result) = result else {
|
||||
return Poll::Ready(Err(Error::Timeout.into()));
|
||||
};
|
||||
|
||||
Poll::Ready(match result {
|
||||
Ok(Packet::Status(status)) if status.is_ok() => {
|
||||
me.closed = true;
|
||||
Ok(())
|
||||
}
|
||||
Ok(Packet::Status(status)) => Err(Error::Status(status).into()),
|
||||
Ok(_) => Err(Error::Packet("not a Status").into()),
|
||||
Err(e) => Err(Error::from(e).into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
16
yazi-sftp/src/fs/flags.rs
Normal file
16
yazi-sftp/src/fs/flags.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Flags(u32);
|
||||
|
||||
bitflags! {
|
||||
impl Flags: u32 {
|
||||
const READ = 0b000001;
|
||||
const WRITE = 0b000010;
|
||||
const APPEND = 0b000100;
|
||||
const CREATE = 0b001000;
|
||||
const TRUNCATE = 0b010000;
|
||||
const EXCLUDE = 0b100000;
|
||||
}
|
||||
}
|
||||
11
yazi-sftp/src/fs/mod.rs
Normal file
11
yazi-sftp/src/fs/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod attrs;
|
||||
mod dir_entry;
|
||||
mod file;
|
||||
mod flags;
|
||||
mod read_dir;
|
||||
|
||||
pub use attrs::*;
|
||||
pub use dir_entry::*;
|
||||
pub use file::*;
|
||||
pub use flags::*;
|
||||
pub use read_dir::*;
|
||||
56
yazi-sftp/src/fs/read_dir.rs
Normal file
56
yazi-sftp/src/fs/read_dir.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::{ByteStr, Error, Session, fs::DirEntry, requests, responses};
|
||||
|
||||
pub struct ReadDir<'a> {
|
||||
dir: ByteStr<'a>,
|
||||
handle: String,
|
||||
session: &'a Session,
|
||||
|
||||
name: responses::Name<'a>,
|
||||
cursor: usize,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl<'a> ReadDir<'a> {
|
||||
pub(crate) fn new(session: &'a Session, dir: ByteStr<'a>, handle: String) -> Self {
|
||||
Self { dir, handle, session, name: Default::default(), cursor: 0, done: false }
|
||||
}
|
||||
|
||||
pub async fn next(&mut self) -> Result<Option<DirEntry<'_>>, Error> {
|
||||
loop {
|
||||
self.fetch().await?;
|
||||
let Some(item) = self.name.items.get_mut(self.cursor).map(mem::take) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
self.cursor += 1;
|
||||
if item.name != "." && item.name != ".." {
|
||||
return Ok(Some(DirEntry {
|
||||
dir: ByteStr::from(&self.dir),
|
||||
name: item.name,
|
||||
long_name: item.long_name,
|
||||
attrs: item.attrs,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch(&mut self) -> Result<(), Error> {
|
||||
if self.cursor < self.name.items.len() || self.done {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.name = match self.session.send(requests::ReadDir::new(&self.handle)).await {
|
||||
Ok(resp) => resp,
|
||||
Err(Error::Status(status)) if status.is_eof() => {
|
||||
self.done = true;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
self.cursor = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
18
yazi-sftp/src/id.rs
Normal file
18
yazi-sftp/src/id.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub(super) struct Id(AtomicU32);
|
||||
|
||||
impl Default for Id {
|
||||
fn default() -> Self { Self(AtomicU32::new(1)) }
|
||||
}
|
||||
|
||||
impl Id {
|
||||
pub(super) fn next(&self) -> u32 {
|
||||
loop {
|
||||
let old = self.0.fetch_add(1, Ordering::Relaxed);
|
||||
if old != 0 {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
yazi-sftp/src/lib.rs
Normal file
22
yazi-sftp/src/lib.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#![allow(clippy::len_without_is_empty)]
|
||||
|
||||
pub mod fs;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
|
||||
mod byte_str;
|
||||
mod de;
|
||||
mod error;
|
||||
mod id;
|
||||
mod macros;
|
||||
mod packet;
|
||||
mod ser;
|
||||
mod session;
|
||||
|
||||
pub use byte_str::*;
|
||||
pub(crate) use de::*;
|
||||
pub use error::*;
|
||||
pub(crate) use id::*;
|
||||
pub use packet::*;
|
||||
pub(crate) use ser::*;
|
||||
pub use session::*;
|
||||
30
yazi-sftp/src/macros.rs
Normal file
30
yazi-sftp/src/macros.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#[macro_export]
|
||||
macro_rules! impl_from_packet {
|
||||
($($variant:ident($type:ty)),* $(,)?) => {
|
||||
$(
|
||||
impl<'a> From<$type> for $crate::Packet<'a> {
|
||||
fn from(value: $type) -> Self {
|
||||
Self::$variant(value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_try_from_packet {
|
||||
($($variant:ident($type:ty)),* $(,)?) => {
|
||||
$(
|
||||
impl<'a> TryFrom<$crate::Packet<'a>> for $type {
|
||||
type Error = $crate::Error;
|
||||
|
||||
fn try_from(value: $crate::Packet<'a>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
$crate::Packet::$variant(v) => Ok(v),
|
||||
_ => Err($crate::Error::Packet(concat!("not a ", stringify!($variant)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
265
yazi-sftp/src/packet.rs
Normal file
265
yazi-sftp/src/packet.rs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::de::Deserializer;
|
||||
use crate::{Error, impl_from_packet, impl_try_from_packet, requests, responses};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Packet<'a> {
|
||||
Init(requests::Init),
|
||||
Open(requests::Open<'a>),
|
||||
Close(requests::Close<'a>),
|
||||
Read(requests::Read<'a>),
|
||||
Write(requests::Write<'a>),
|
||||
Lstat(requests::Lstat<'a>),
|
||||
Fstat(requests::Fstat<'a>),
|
||||
SetStat(requests::SetStat<'a>),
|
||||
FSetStat(requests::FSetStat<'a>),
|
||||
OpenDir(requests::OpenDir<'a>),
|
||||
ReadDir(requests::ReadDir<'a>),
|
||||
Remove(requests::Remove<'a>),
|
||||
Mkdir(requests::Mkdir<'a>),
|
||||
Rmdir(requests::Rmdir<'a>),
|
||||
Realpath(requests::Realpath<'a>),
|
||||
Stat(requests::Stat<'a>),
|
||||
Rename(requests::Rename<'a>),
|
||||
Readlink(requests::Readlink<'a>),
|
||||
Symlink(requests::Symlink<'a>),
|
||||
ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),
|
||||
ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),
|
||||
ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),
|
||||
|
||||
// Responses
|
||||
Version(responses::Version),
|
||||
Status(responses::Status),
|
||||
Handle(responses::Handle),
|
||||
Data(responses::Data),
|
||||
Name(responses::Name<'a>),
|
||||
Attrs(responses::Attrs),
|
||||
ExtendedReply(responses::Extended<'a>),
|
||||
}
|
||||
|
||||
impl_from_packet! {
|
||||
Init(requests::Init),
|
||||
Open(requests::Open<'a>),
|
||||
Close(requests::Close<'a>),
|
||||
Read(requests::Read<'a>),
|
||||
Write(requests::Write<'a>),
|
||||
Lstat(requests::Lstat<'a>),
|
||||
Fstat(requests::Fstat<'a>),
|
||||
SetStat(requests::SetStat<'a>),
|
||||
FSetStat(requests::FSetStat<'a>),
|
||||
OpenDir(requests::OpenDir<'a>),
|
||||
ReadDir(requests::ReadDir<'a>),
|
||||
Remove(requests::Remove<'a>),
|
||||
Mkdir(requests::Mkdir<'a>),
|
||||
Rmdir(requests::Rmdir<'a>),
|
||||
Realpath(requests::Realpath<'a>),
|
||||
Stat(requests::Stat<'a>),
|
||||
Rename(requests::Rename<'a>),
|
||||
Readlink(requests::Readlink<'a>),
|
||||
Symlink(requests::Symlink<'a>),
|
||||
ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),
|
||||
ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),
|
||||
ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),
|
||||
|
||||
// Responses
|
||||
Version(responses::Version),
|
||||
Status(responses::Status),
|
||||
Handle(responses::Handle),
|
||||
Data(responses::Data),
|
||||
Name(responses::Name<'a>),
|
||||
Attrs(responses::Attrs),
|
||||
ExtendedReply(responses::Extended<'a>),
|
||||
}
|
||||
|
||||
impl_try_from_packet! {
|
||||
Version(responses::Version),
|
||||
Status(responses::Status),
|
||||
Handle(responses::Handle),
|
||||
Data(responses::Data),
|
||||
Name(responses::Name<'a>),
|
||||
Attrs(responses::Attrs),
|
||||
ExtendedReply(responses::Extended<'a>),
|
||||
}
|
||||
|
||||
impl Packet<'_> {
|
||||
fn kind(&self) -> u8 {
|
||||
match self {
|
||||
Self::Init(_) => 1,
|
||||
Self::Open(_) => 3,
|
||||
Self::Close(_) => 4,
|
||||
Self::Read(_) => 5,
|
||||
Self::Write(_) => 6,
|
||||
Self::Lstat(_) => 7,
|
||||
Self::Fstat(_) => 8,
|
||||
Self::SetStat(_) => 9,
|
||||
Self::FSetStat(_) => 10,
|
||||
Self::OpenDir(_) => 11,
|
||||
Self::ReadDir(_) => 12,
|
||||
Self::Remove(_) => 13,
|
||||
Self::Mkdir(_) => 14,
|
||||
Self::Rmdir(_) => 15,
|
||||
Self::Realpath(_) => 16,
|
||||
Self::Stat(_) => 17,
|
||||
Self::Rename(_) => 18,
|
||||
Self::Readlink(_) => 19,
|
||||
Self::Symlink(_) => 20,
|
||||
Self::ExtendedFsync(_) => 200,
|
||||
Self::ExtendedHardlink(_) => 200,
|
||||
Self::ExtendedLimits(_) => 200,
|
||||
|
||||
// Responses
|
||||
Self::Version(_) => 2,
|
||||
Self::Status(_) => 101,
|
||||
Self::Handle(_) => 102,
|
||||
Self::Data(_) => 103,
|
||||
Self::Name(_) => 104,
|
||||
Self::Attrs(_) => 105,
|
||||
Self::ExtendedReply(_) => 201,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
match self {
|
||||
Self::Init(_) => 0,
|
||||
Self::Open(v) => v.id,
|
||||
Self::Close(v) => v.id,
|
||||
Self::Read(v) => v.id,
|
||||
Self::Write(v) => v.id,
|
||||
Self::Lstat(v) => v.id,
|
||||
Self::Fstat(v) => v.id,
|
||||
Self::SetStat(v) => v.id,
|
||||
Self::FSetStat(v) => v.id,
|
||||
Self::OpenDir(v) => v.id,
|
||||
Self::ReadDir(v) => v.id,
|
||||
Self::Remove(v) => v.id,
|
||||
Self::Mkdir(v) => v.id,
|
||||
Self::Rmdir(v) => v.id,
|
||||
Self::Realpath(v) => v.id,
|
||||
Self::Stat(v) => v.id,
|
||||
Self::Rename(v) => v.id,
|
||||
Self::Readlink(v) => v.id,
|
||||
Self::Symlink(v) => v.id,
|
||||
Self::ExtendedFsync(v) => v.id,
|
||||
Self::ExtendedHardlink(v) => v.id,
|
||||
Self::ExtendedLimits(v) => v.id,
|
||||
|
||||
// Responses
|
||||
Self::Version(_) => 0,
|
||||
Self::Status(v) => v.id,
|
||||
Self::Handle(v) => v.id,
|
||||
Self::Data(v) => v.id,
|
||||
Self::Name(v) => v.id,
|
||||
Self::Attrs(v) => v.id,
|
||||
Self::ExtendedReply(v) => v.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: u32) -> Self {
|
||||
match &mut self {
|
||||
Self::Init(_) => {}
|
||||
Self::Open(v) => v.id = id,
|
||||
Self::Close(v) => v.id = id,
|
||||
Self::Read(v) => v.id = id,
|
||||
Self::Write(v) => v.id = id,
|
||||
Self::Lstat(v) => v.id = id,
|
||||
Self::Fstat(v) => v.id = id,
|
||||
Self::SetStat(v) => v.id = id,
|
||||
Self::FSetStat(v) => v.id = id,
|
||||
Self::OpenDir(v) => v.id = id,
|
||||
Self::ReadDir(v) => v.id = id,
|
||||
Self::Remove(v) => v.id = id,
|
||||
Self::Mkdir(v) => v.id = id,
|
||||
Self::Rmdir(v) => v.id = id,
|
||||
Self::Realpath(v) => v.id = id,
|
||||
Self::Stat(v) => v.id = id,
|
||||
Self::Rename(v) => v.id = id,
|
||||
Self::Readlink(v) => v.id = id,
|
||||
Self::Symlink(v) => v.id = id,
|
||||
Self::ExtendedFsync(v) => v.id = id,
|
||||
Self::ExtendedHardlink(v) => v.id = id,
|
||||
Self::ExtendedLimits(v) => v.id = id,
|
||||
|
||||
// Responses
|
||||
Self::Version(_) => {}
|
||||
Self::Status(v) => v.id = id,
|
||||
Self::Handle(v) => v.id = id,
|
||||
Self::Data(v) => v.id = id,
|
||||
Self::Name(v) => v.id = id,
|
||||
Self::Attrs(v) => v.id = id,
|
||||
Self::ExtendedReply(v) => v.id = id,
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
let type_len = 1;
|
||||
match self {
|
||||
Self::Init(v) => type_len + v.len(),
|
||||
Self::Open(v) => type_len + v.len(),
|
||||
Self::Close(v) => type_len + v.len(),
|
||||
Self::Read(v) => type_len + v.len(),
|
||||
Self::Write(v) => type_len + v.len(),
|
||||
Self::Lstat(v) => type_len + v.len(),
|
||||
Self::Fstat(v) => type_len + v.len(),
|
||||
Self::SetStat(v) => type_len + v.len(),
|
||||
Self::FSetStat(v) => type_len + v.len(),
|
||||
Self::OpenDir(v) => type_len + v.len(),
|
||||
Self::ReadDir(v) => type_len + v.len(),
|
||||
Self::Remove(v) => type_len + v.len(),
|
||||
Self::Mkdir(v) => type_len + v.len(),
|
||||
Self::Rmdir(v) => type_len + v.len(),
|
||||
Self::Realpath(v) => type_len + v.len(),
|
||||
Self::Stat(v) => type_len + v.len(),
|
||||
Self::Rename(v) => type_len + v.len(),
|
||||
Self::Readlink(v) => type_len + v.len(),
|
||||
Self::Symlink(v) => type_len + v.len(),
|
||||
Self::ExtendedFsync(v) => type_len + v.len(),
|
||||
Self::ExtendedHardlink(v) => type_len + v.len(),
|
||||
Self::ExtendedLimits(v) => type_len + v.len(),
|
||||
|
||||
// Responses
|
||||
Self::Version(v) => type_len + v.len(),
|
||||
Self::Status(v) => type_len + v.len(),
|
||||
Self::Handle(v) => type_len + v.len(),
|
||||
Self::Data(v) => type_len + v.len(),
|
||||
Self::Name(v) => type_len + v.len(),
|
||||
Self::Attrs(v) => type_len + v.len(),
|
||||
Self::ExtendedReply(v) => type_len + v.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes<'a, T>(value: T) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
T: Into<Packet<'a>> + Serialize,
|
||||
{
|
||||
let packet: Packet = value.into();
|
||||
|
||||
let len = u32::try_from(packet.len()).map_err(|_| Error::serde("packet too large"))?;
|
||||
|
||||
let mut output = Vec::with_capacity(4 + len as usize);
|
||||
output.extend_from_slice(&len.to_be_bytes());
|
||||
output.push(packet.kind());
|
||||
|
||||
let mut serializer = crate::Serializer { output };
|
||||
packet.serialize(&mut serializer)?;
|
||||
Ok(serializer.output)
|
||||
}
|
||||
|
||||
// TODO: use Vec<u8>
|
||||
pub fn from_bytes(mut bytes: &[u8]) -> Result<Packet<'static>, Error> {
|
||||
let kind = *bytes.first().ok_or(Error::serde("empty packet"))?;
|
||||
bytes = &bytes[1..];
|
||||
|
||||
Ok(match kind {
|
||||
2 => Packet::Version(Deserializer::once(bytes)?),
|
||||
101 => Packet::Status(Deserializer::once(bytes)?),
|
||||
102 => Packet::Handle(Deserializer::once(bytes)?),
|
||||
103 => Packet::Data(Deserializer::once(bytes)?),
|
||||
104 => Packet::Name(Deserializer::once(bytes)?),
|
||||
201 => Packet::ExtendedReply(Deserializer::once(bytes)?),
|
||||
_ => return Err(Error::Packet("unknown packet kind")),
|
||||
})
|
||||
}
|
||||
15
yazi-sftp/src/requests/close.rs
Normal file
15
yazi-sftp/src/requests/close.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Close<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> Close<'a> {
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { id: 0, handle: handle.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }
|
||||
}
|
||||
71
yazi-sftp/src/requests/extended.rs
Normal file
71
yazi-sftp/src/requests/extended.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Extended<'a, D> {
|
||||
pub id: u32,
|
||||
pub request: Cow<'a, str>,
|
||||
pub data: D,
|
||||
}
|
||||
|
||||
impl<D: ExtendedData> Extended<'_, D> {
|
||||
pub fn new<'a, R>(request: R, data: D) -> Extended<'a, D>
|
||||
where
|
||||
R: Into<Cow<'a, str>>,
|
||||
{
|
||||
Extended { id: 0, request: request.into(), data }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.request.len() + self.data.len() }
|
||||
}
|
||||
|
||||
// --- Data
|
||||
pub trait ExtendedData: Debug + Serialize + for<'de> Deserialize<'de> {
|
||||
fn len(&self) -> usize;
|
||||
}
|
||||
|
||||
// --- Fsync
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ExtendedFsync<'a> {
|
||||
pub handle: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> ExtendedFsync<'a> {
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { handle: handle.into() } }
|
||||
}
|
||||
|
||||
impl ExtendedData for ExtendedFsync<'_> {
|
||||
fn len(&self) -> usize { 4 + self.handle.len() }
|
||||
}
|
||||
|
||||
// --- Hardlink
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ExtendedHardlink<'a> {
|
||||
pub original: ByteStr<'a>,
|
||||
pub link: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ExtendedHardlink<'a> {
|
||||
pub fn new<O, L>(original: O, link: L) -> Self
|
||||
where
|
||||
O: Into<ByteStr<'a>>,
|
||||
L: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { original: original.into(), link: link.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedData for ExtendedHardlink<'_> {
|
||||
fn len(&self) -> usize { 4 + self.original.len() + 4 + self.link.len() }
|
||||
}
|
||||
|
||||
// --- Limits
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ExtendedLimits;
|
||||
|
||||
impl ExtendedData for ExtendedLimits {
|
||||
fn len(&self) -> usize { 0 }
|
||||
}
|
||||
15
yazi-sftp/src/requests/fstat.rs
Normal file
15
yazi-sftp/src/requests/fstat.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Fstat<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> Fstat<'a> {
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { id: 0, handle: handle.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }
|
||||
}
|
||||
22
yazi-sftp/src/requests/init.rs
Normal file
22
yazi-sftp/src/requests/init.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Init {
|
||||
pub version: u32,
|
||||
pub extensions: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Init {
|
||||
pub fn new(extensions: HashMap<String, String>) -> Self { Self { version: 3, extensions } }
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.version)
|
||||
+ self.extensions.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Init {
|
||||
fn default() -> Self { Self::new(HashMap::new()) }
|
||||
}
|
||||
15
yazi-sftp/src/requests/lstat.rs
Normal file
15
yazi-sftp/src/requests/lstat.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Lstat<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl Lstat<'_> {
|
||||
pub fn new<'a>(path: impl Into<ByteStr<'a>>) -> Lstat<'a> { Lstat { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
21
yazi-sftp/src/requests/mkdir.rs
Normal file
21
yazi-sftp/src/requests/mkdir.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ByteStr, fs::Attrs};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Mkdir<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> Mkdir<'a> {
|
||||
pub fn new<P>(path: P, attrs: Attrs) -> Self
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { id: 0, path: path.into(), attrs }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() }
|
||||
}
|
||||
39
yazi-sftp/src/requests/mod.rs
Normal file
39
yazi-sftp/src/requests/mod.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
mod close;
|
||||
mod extended;
|
||||
mod fstat;
|
||||
mod init;
|
||||
mod lstat;
|
||||
mod mkdir;
|
||||
mod open;
|
||||
mod open_dir;
|
||||
mod read;
|
||||
mod read_dir;
|
||||
mod readlink;
|
||||
mod realpath;
|
||||
mod remove;
|
||||
mod rename;
|
||||
mod rmdir;
|
||||
mod set_stat;
|
||||
mod stat;
|
||||
mod symlink;
|
||||
mod write;
|
||||
|
||||
pub use close::*;
|
||||
pub use extended::*;
|
||||
pub use fstat::*;
|
||||
pub use init::*;
|
||||
pub use lstat::*;
|
||||
pub use mkdir::*;
|
||||
pub use open::*;
|
||||
pub use open_dir::*;
|
||||
pub use read::*;
|
||||
pub use read_dir::*;
|
||||
pub use readlink::*;
|
||||
pub use realpath::*;
|
||||
pub use remove::*;
|
||||
pub use rename::*;
|
||||
pub use rmdir::*;
|
||||
pub use set_stat::*;
|
||||
pub use stat::*;
|
||||
pub use symlink::*;
|
||||
pub use write::*;
|
||||
24
yazi-sftp/src/requests/open.rs
Normal file
24
yazi-sftp/src/requests/open.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ByteStr, fs::{Attrs, Flags}};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Open<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
pub flags: Flags,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> Open<'a> {
|
||||
pub fn new<P>(path: P, flags: Flags, attrs: Attrs) -> Self
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { id: 0, path: path.into(), flags, attrs }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id) + 4 + self.path.len() + size_of_val(&self.flags) + self.attrs.len()
|
||||
}
|
||||
}
|
||||
15
yazi-sftp/src/requests/open_dir.rs
Normal file
15
yazi-sftp/src/requests/open_dir.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct OpenDir<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> OpenDir<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
27
yazi-sftp/src/requests/read.rs
Normal file
27
yazi-sftp/src/requests/read.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Read<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
pub offset: u64,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
impl<'a> Read<'a> {
|
||||
pub fn new<H>(handle: H, offset: u64, len: u32) -> Self
|
||||
where
|
||||
H: Into<Cow<'a, str>>,
|
||||
{
|
||||
Self { id: 0, handle: handle.into(), offset, len }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id)
|
||||
+ 4 + self.handle.len()
|
||||
+ size_of_val(&self.offset)
|
||||
+ size_of_val(&self.len)
|
||||
}
|
||||
}
|
||||
15
yazi-sftp/src/requests/read_dir.rs
Normal file
15
yazi-sftp/src/requests/read_dir.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ReadDir<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> ReadDir<'a> {
|
||||
pub fn new(handle: &'a str) -> Self { Self { id: 0, handle: handle.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }
|
||||
}
|
||||
15
yazi-sftp/src/requests/readlink.rs
Normal file
15
yazi-sftp/src/requests/readlink.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Readlink<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Readlink<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
15
yazi-sftp/src/requests/realpath.rs
Normal file
15
yazi-sftp/src/requests/realpath.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Realpath<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Realpath<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
15
yazi-sftp/src/requests/remove.rs
Normal file
15
yazi-sftp/src/requests/remove.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Remove<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Remove<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
22
yazi-sftp/src/requests/rename.rs
Normal file
22
yazi-sftp/src/requests/rename.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Rename<'a> {
|
||||
pub id: u32,
|
||||
pub from: ByteStr<'a>,
|
||||
pub to: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Rename<'a> {
|
||||
pub fn new<F, T>(from: F, to: T) -> Self
|
||||
where
|
||||
F: Into<ByteStr<'a>>,
|
||||
T: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { id: 0, from: from.into(), to: to.into() }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.from.len() + 4 + self.to.len() }
|
||||
}
|
||||
15
yazi-sftp/src/requests/rmdir.rs
Normal file
15
yazi-sftp/src/requests/rmdir.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Rmdir<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Rmdir<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
35
yazi-sftp/src/requests/set_stat.rs
Normal file
35
yazi-sftp/src/requests/set_stat.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ByteStr, fs::Attrs};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SetStat<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> SetStat<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>, attrs: Attrs) -> Self {
|
||||
Self { id: 0, path: path.into(), attrs }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() }
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FSetStat<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> FSetStat<'a> {
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>, attrs: Attrs) -> Self {
|
||||
Self { id: 0, handle: handle.into(), attrs }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() + self.attrs.len() }
|
||||
}
|
||||
15
yazi-sftp/src/requests/stat.rs
Normal file
15
yazi-sftp/src/requests/stat.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Stat<'a> {
|
||||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Stat<'a> {
|
||||
pub fn new(path: impl Into<ByteStr<'a>>) -> Self { Self { id: 0, path: path.into() } }
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }
|
||||
}
|
||||
24
yazi-sftp/src/requests/symlink.rs
Normal file
24
yazi-sftp/src/requests/symlink.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ByteStr;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Symlink<'a> {
|
||||
pub id: u32,
|
||||
pub link: ByteStr<'a>,
|
||||
pub original: ByteStr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Symlink<'a> {
|
||||
pub fn new<L, O>(link: L, original: O) -> Self
|
||||
where
|
||||
L: Into<ByteStr<'a>>,
|
||||
O: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { id: 0, link: link.into(), original: original.into() }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id) + 4 + self.link.len() + 4 + self.original.len()
|
||||
}
|
||||
}
|
||||
25
yazi-sftp/src/requests/write.rs
Normal file
25
yazi-sftp/src/requests/write.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Write<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
pub offset: u64,
|
||||
pub data: Cow<'a, [u8]>,
|
||||
}
|
||||
|
||||
impl Write<'_> {
|
||||
pub fn new<'a, H, D>(handle: H, offset: u64, data: D) -> Write<'a>
|
||||
where
|
||||
H: Into<Cow<'a, str>>,
|
||||
D: Into<Cow<'a, [u8]>>,
|
||||
{
|
||||
Write { id: 0, handle: handle.into(), offset, data: data.into() }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id) + 4 + self.handle.len() + size_of_val(&self.offset) + 4 + self.data.len()
|
||||
}
|
||||
}
|
||||
13
yazi-sftp/src/responses/attrs.rs
Normal file
13
yazi-sftp/src/responses/attrs.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::fs;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Attrs {
|
||||
pub id: u32,
|
||||
pub attrs: fs::Attrs,
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + self.attrs.len() }
|
||||
}
|
||||
11
yazi-sftp/src/responses/data.rs
Normal file
11
yazi-sftp/src/responses/data.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Data {
|
||||
pub id: u32,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.data.len() }
|
||||
}
|
||||
85
yazi-sftp/src/responses/extended.rs
Normal file
85
yazi-sftp/src/responses/extended.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::{borrow::Cow, fmt, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Serialize, de::Visitor, ser::SerializeSeq};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Extended<'a> {
|
||||
pub id: u32,
|
||||
pub data: ExtendedData<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Extended<'a> {
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + self.data.len() }
|
||||
}
|
||||
|
||||
// --- Data
|
||||
#[derive(Debug)]
|
||||
pub struct ExtendedData<'a>(Cow<'a, [u8]>);
|
||||
|
||||
impl Deref for ExtendedData<'_> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Serialize for ExtendedData<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
for b in self.0.as_ref() {
|
||||
seq.serialize_element(b)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ExtendedData<'_> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct ExtendedDataVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ExtendedDataVisitor {
|
||||
type Value = Vec<u8>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("extended data")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
|
||||
while let Some(b) = seq.next_element()? {
|
||||
bytes.push(b);
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ExtendedDataVisitor).map(Cow::Owned).map(ExtendedData)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Limits
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ExtendedLimits {
|
||||
pub packet_len: u64,
|
||||
pub read_len: u64,
|
||||
pub write_len: u64,
|
||||
pub open_handles: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<Extended<'_>> for ExtendedLimits {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Extended<'_>) -> Result<Self, Self::Error> {
|
||||
crate::Deserializer::once(&value.data)
|
||||
}
|
||||
}
|
||||
11
yazi-sftp/src/responses/handle.rs
Normal file
11
yazi-sftp/src/responses/handle.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Handle {
|
||||
pub id: u32,
|
||||
pub handle: String,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }
|
||||
}
|
||||
15
yazi-sftp/src/responses/mod.rs
Normal file
15
yazi-sftp/src/responses/mod.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
mod attrs;
|
||||
mod data;
|
||||
mod extended;
|
||||
mod handle;
|
||||
mod name;
|
||||
mod status;
|
||||
mod version;
|
||||
|
||||
pub use attrs::*;
|
||||
pub use data::*;
|
||||
pub use extended::*;
|
||||
pub use handle::*;
|
||||
pub use name::*;
|
||||
pub use status::*;
|
||||
pub use version::*;
|
||||
26
yazi-sftp/src/responses/name.rs
Normal file
26
yazi-sftp/src/responses/name.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ByteStr, fs::Attrs};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Name<'a> {
|
||||
pub id: u32,
|
||||
pub items: Vec<NameItem<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct NameItem<'a> {
|
||||
pub name: ByteStr<'a>,
|
||||
pub long_name: ByteStr<'a>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl Name<'_> {
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id) + 4 + self.items.iter().map(|v| v.len()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
|
||||
impl NameItem<'_> {
|
||||
pub fn len(&self) -> usize { 4 + self.name.len() + 4 + self.long_name.len() + self.attrs.len() }
|
||||
}
|
||||
43
yazi-sftp/src/responses/status.rs
Normal file
43
yazi-sftp/src/responses/status.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Status {
|
||||
pub id: u32,
|
||||
pub code: StatusCode,
|
||||
pub message: String,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
impl From<Status> for Result<(), Error> {
|
||||
fn from(status: Status) -> Self {
|
||||
if status.is_ok() { Ok(()) } else { Err(Error::Status(status)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.id)
|
||||
+ size_of_val(&(self.code as u32))
|
||||
+ 4 + self.message.len()
|
||||
+ 4 + self.language.len()
|
||||
}
|
||||
|
||||
pub fn is_ok(&self) -> bool { self.code == StatusCode::Ok }
|
||||
|
||||
pub fn is_eof(&self) -> bool { self.code == StatusCode::Eof }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||
pub enum StatusCode {
|
||||
Ok = 0,
|
||||
Eof = 1,
|
||||
NoSuchFile = 2,
|
||||
PermissionDenied = 3,
|
||||
Failure = 4,
|
||||
BadMessage = 5,
|
||||
NoConnection = 6,
|
||||
ConnectionLost = 7,
|
||||
OpUnsupported = 8,
|
||||
}
|
||||
16
yazi-sftp/src/responses/version.rs
Normal file
16
yazi-sftp/src/responses/version.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Version {
|
||||
pub version: u32,
|
||||
pub extensions: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn len(&self) -> usize {
|
||||
size_of_val(&self.version)
|
||||
+ self.extensions.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
286
yazi-sftp/src/ser.rs
Normal file
286
yazi-sftp/src/ser.rs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub(super) struct Serializer {
|
||||
pub(super) output: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> serde::Serializer for &'a mut Serializer {
|
||||
type Error = crate::Error;
|
||||
type Ok = ();
|
||||
type SerializeMap = &'a mut Serializer;
|
||||
type SerializeSeq = &'a mut Serializer;
|
||||
type SerializeStruct = &'a mut Serializer;
|
||||
type SerializeStructVariant = &'a mut Serializer;
|
||||
type SerializeTuple = &'a mut Serializer;
|
||||
type SerializeTupleStruct = &'a mut Serializer;
|
||||
type SerializeTupleVariant = &'a mut Serializer;
|
||||
|
||||
fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("bool not supported"))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("i8 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("i16 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("i32 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("i64 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
|
||||
self.output.push(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("u16 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
|
||||
self.output.extend_from_slice(&v.to_be_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
|
||||
self.output.extend_from_slice(&v.to_be_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("f32 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("f64 not supported"))
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("char not supported"))
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
|
||||
self.serialize_bytes(v.as_bytes())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
let len = u32::try_from(v.len()).map_err(|_| Error::serde("bytes too long"))?;
|
||||
self.output.extend_from_slice(&len.to_be_bytes());
|
||||
self.output.extend_from_slice(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
|
||||
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::serde("unit not supported"))
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
variant_index: u32,
|
||||
_variant: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
self.serialize_u32(variant_index)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
if let Some(len) = len {
|
||||
self.serialize_u32(len.try_into().map_err(|_| Error::serde("sequence too long"))?)?;
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> { Ok(self) }
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||
Err(Error::serde("struct variant not supported"))
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool { false }
|
||||
}
|
||||
|
||||
impl SerializeMap for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_key<T>(&mut self, key: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
key.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn serialize_value<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeSeq for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeStruct for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeStructVariant for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeTuple for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeTupleStruct for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl SerializeTupleVariant for &mut Serializer {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
|
||||
}
|
||||
265
yazi-sftp/src/session.rs
Normal file
265
yazi-sftp/src/session.rs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
use std::{any::TypeId, collections::HashMap, io::ErrorKind, path::PathBuf, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use russh::{ChannelStream, client::Msg};
|
||||
use serde::Serialize;
|
||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, select, sync::{mpsc, oneshot}};
|
||||
|
||||
use crate::{ByteStr, Error, Id, Packet, fs::{Attrs, File, Flags, ReadDir}, requests, responses};
|
||||
|
||||
pub struct Session {
|
||||
tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
pub(crate) id: Id,
|
||||
callback: Arc<Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>>,
|
||||
extensions: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn make(stream: ChannelStream<Msg>) -> Self {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Vec<u8>>();
|
||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||
let callback = Arc::new(Mutex::new(HashMap::<_, oneshot::Sender<_>>::new()));
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(data) = rx.recv().await {
|
||||
if data.is_empty() {
|
||||
rx.close();
|
||||
writer.shutdown().await.ok();
|
||||
break;
|
||||
} else {
|
||||
writer.write_all(&data).await.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (tx_, callback_) = (tx.clone(), callback.clone());
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
select! {
|
||||
Ok(len) = reader.read_u32() => {
|
||||
let mut buf = vec![0; len as usize];
|
||||
if let Err(e) = reader.read_exact(&mut buf).await && e.kind() == ErrorKind::UnexpectedEof {
|
||||
tx_.send(vec![]).ok();
|
||||
break;
|
||||
}
|
||||
if let Ok(packet) = crate::from_bytes(&buf)
|
||||
&& let Some(cb) = callback_.lock().remove(&packet.id())
|
||||
{
|
||||
cb.send(packet).ok();
|
||||
}
|
||||
}
|
||||
_ = tx_.closed() => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self { tx, id: Id::default(), callback, extensions: HashMap::new() }
|
||||
}
|
||||
|
||||
pub async fn init(&mut self) -> Result<(), Error> {
|
||||
let version: responses::Version = self.send(requests::Init::default()).await?;
|
||||
self.extensions = version.extensions;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: Attrs) -> Result<File<'_>, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)).await?;
|
||||
|
||||
Ok(File::new(self, handle.handle))
|
||||
}
|
||||
|
||||
pub fn close(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Close::new(handle))
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
len: u32,
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Read::new(handle, offset, len))
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Write::new(handle, offset, data))
|
||||
}
|
||||
|
||||
pub async fn lstat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Lstat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn fstat(&self, handle: &str) -> Result<Attrs, Error> {
|
||||
let attrs: responses::Attrs = self.send(requests::Fstat::new(handle)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn setstat<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::SetStat::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, handle: &str, attrs: Attrs) -> Result<(), Error> {
|
||||
let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn read_dir<'a>(&'a self, dir: impl Into<ByteStr<'a>>) -> Result<ReadDir<'a>, Error> {
|
||||
let dir: ByteStr = dir.into();
|
||||
let handle: responses::Handle = self.send(requests::OpenDir::new(&dir)).await?;
|
||||
|
||||
Ok(ReadDir::new(self, dir, handle.handle))
|
||||
}
|
||||
|
||||
pub async fn remove<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Remove::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn mkdir<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Mkdir::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn rmdir<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rmdir::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn realpath<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Realpath::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("realpath returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Stat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn rename<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>
|
||||
where
|
||||
F: Into<ByteStr<'a>>,
|
||||
T: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rename::new(from, to)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn readlink<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Readlink::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("readlink returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn symlink<'a, L, O>(&self, link: L, original: O) -> Result<(), Error>
|
||||
where
|
||||
L: Into<ByteStr<'a>>,
|
||||
O: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Symlink::new(link, original)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub fn fsync(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
if self.extensions.get("fsync@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedFsync::new(handle);
|
||||
self.send_sync(requests::Extended::new("fsync@openssh.com", data))
|
||||
}
|
||||
|
||||
pub async fn hardlink<'a, O, L>(&self, original: O, link: L) -> Result<(), Error>
|
||||
where
|
||||
O: Into<ByteStr<'a>>,
|
||||
L: Into<ByteStr<'a>>,
|
||||
{
|
||||
if self.extensions.get("hardlink@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedHardlink::new(original, link);
|
||||
let status: responses::Status =
|
||||
self.send(requests::Extended::new("hardlink@openssh.com", data)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn limits(&self) -> Result<responses::ExtendedLimits, Error> {
|
||||
if self.extensions.get("limits@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let extended: responses::Extended =
|
||||
self.send(requests::Extended::new("limits@openssh.com", requests::ExtendedLimits)).await?;
|
||||
extended.try_into()
|
||||
}
|
||||
|
||||
pub async fn send<'a, I, O>(&self, input: I) -> Result<O, Error>
|
||||
where
|
||||
I: Into<Packet<'a>> + Serialize,
|
||||
O: TryFrom<Packet<'static>, Error = Error> + 'static,
|
||||
{
|
||||
match self.send_sync(input)?.await? {
|
||||
Packet::Status(status) if TypeId::of::<O>() != TypeId::of::<responses::Status>() => {
|
||||
Err(Error::Status(status))
|
||||
}
|
||||
response => response.try_into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_sync<'a, I>(&self, input: I) -> Result<oneshot::Receiver<Packet<'static>>, Error>
|
||||
where
|
||||
I: Into<Packet<'a>> + Serialize,
|
||||
{
|
||||
let mut request: Packet = input.into();
|
||||
if request.id() == 0 {
|
||||
request = request.with_id(self.id.next());
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.callback.lock().insert(request.id(), tx);
|
||||
self.tx.send(crate::to_bytes(request)?)?;
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ libc = { workspace = true }
|
|||
uzers = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.60.2", features = [ "Win32_UI_Shell" ] }
|
||||
windows-sys = { version = "0.61.0", features = [ "Win32_UI_Shell" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ libc = { workspace = true }
|
|||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.60.2", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] }
|
||||
windows-sys = { version = "0.61.0", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue