diff --git a/core/Cargo.toml b/core/Cargo.toml index 3817a64b938..6d1f0e9972e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -135,12 +135,7 @@ services-s3 = [ "reqsign?/services-aws", "reqsign?/reqwest_request", ] -services-sftp = [ - "dep:openssh", - "dep:openssh-sftp-client", - "dep:dirs", - "futures/executor", -] +services-sftp = ["dep:openssh", "dep:openssh-sftp-client", "dep:dirs"] services-sled = ["dep:sled"] services-supabase = [] services-vercel-artifacts = [] diff --git a/core/src/services/sftp/backend.rs b/core/src/services/sftp/backend.rs index bfdd98215fe..4fb0de5a6f0 100644 --- a/core/src/services/sftp/backend.rs +++ b/core/src/services/sftp/backend.rs @@ -56,7 +56,7 @@ use crate::*; /// - [x] create_dir /// - [x] delete /// - [ ] copy -/// - [ ] rename +/// - [x] rename /// - [x] list /// - [ ] ~~scan~~ /// - [ ] ~~presign~~ @@ -276,8 +276,10 @@ impl Accessor for SftpBackend { stat: true, read: true, + read_with_range: true, write: true, + write_without_content_length: true, create_dir: true, delete: true, @@ -285,6 +287,8 @@ impl Accessor for SftpBackend { list_with_limit: true, list_with_delimiter_slash: true, + rename: true, + ..Default::default() }); @@ -352,14 +356,7 @@ impl Accessor for SftpBackend { Ok((RpRead::new(end - start), r)) } - async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> { - if args.content_length().is_none() { - return Err(Error::new( - ErrorKind::Unsupported, - "write without content length is not supported", - )); - } - + async fn write(&self, path: &str, _: OpWrite) -> Result<(RpWrite, Self::Writer)> { if let Some((dir, _)) = path.rsplit_once('/') { self.create_dir(dir, OpCreateDir::default()).await?; } @@ -375,6 +372,40 @@ impl Accessor for SftpBackend { Ok((RpWrite::new(), SftpWriter::new(file))) } + async fn copy(&self, from: &str, to: &str, _: OpCopy) -> Result { + let client = self.connect().await?; + + let mut fs = client.fs(); + fs.set_cwd(&self.root); + + if let Some((dir, _)) = to.rsplit_once('/') { + self.create_dir(dir, OpCreateDir::default()).await?; + } + + let src = fs.canonicalize(from).await?; + let dst = fs.canonicalize(to).await?; + let mut src_file = client.open(&src).await?; + let mut dst_file = client.create(dst).await?; + + src_file.copy_all_to(&mut dst_file).await?; + + Ok(RpCopy::default()) + } + + async fn rename(&self, from: &str, to: &str, _: OpRename) -> Result { + let client = self.connect().await?; + + let mut fs = client.fs(); + fs.set_cwd(&self.root); + + if let Some((dir, _)) = to.rsplit_once('/') { + self.create_dir(dir, OpCreateDir::default()).await?; + } + fs.rename(from, to).await?; + + Ok(RpRename::default()) + } + async fn stat(&self, path: &str, _: OpStat) -> Result { let client = self.connect().await?; let mut fs = client.fs();