use crate::{util::run_ssh2_fn, Error};
use futures::prelude::*;
use smol::Async;
use ssh2::{self, FileStat, OpenFlags, OpenType};
use std::{
io::{self, Read, Seek, Write},
net::TcpStream,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
pub struct Sftp {
inner: ssh2::Sftp,
stream: Arc<Async<TcpStream>>,
}
pub struct File {
inner: ssh2::File,
stream: Arc<Async<TcpStream>>,
}
impl Sftp {
pub(crate) fn new(sftp: ssh2::Sftp, stream: Arc<Async<TcpStream>>) -> Self {
Self {
inner: sftp,
stream,
}
}
pub async fn open_mode(
&self,
filename: &Path,
flags: ssh2::OpenFlags,
mode: i32,
open_type: ssh2::OpenType,
) -> Result<File, Error> {
let file = run_ssh2_fn(&self.stream, || {
self.inner.open_mode(filename, flags, mode, open_type)
})
.await?;
Ok(File::new(file, self.stream.clone()))
}
pub async fn open(&self, filename: &Path) -> Result<File, Error> {
self.open_mode(filename, OpenFlags::READ, 0o644, OpenType::File)
.await
}
pub async fn create(&self, filename: &Path) -> Result<File, Error> {
self.open_mode(
filename,
OpenFlags::WRITE | OpenFlags::TRUNCATE,
0o644,
OpenType::File,
)
.await
}
pub async fn opendir(&self, dirname: &Path) -> Result<File, Error> {
self.open_mode(dirname, OpenFlags::READ, 0, OpenType::Dir)
.await
}
pub async fn readdir(&self, dirname: &Path) -> Result<Vec<(PathBuf, FileStat)>, Error> {
let mut dir = self.opendir(dirname).await?;
let mut ret = Vec::new();
loop {
match dir.readdir().await {
Ok((filename, stat)) => {
if &*filename == Path::new(".") || &*filename == Path::new("..") {
continue;
}
ret.push((dirname.join(&filename), stat))
}
Err(Error::SSH2(ref e)) if e.code() == -16 => {
break;
}
Err(e) => {
return Err(e);
}
}
}
Ok(ret)
}
pub async fn mkdir(&self, filename: &Path, mode: i32) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.mkdir(filename, mode)).await
}
pub async fn rmdir(&self, filename: &Path) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.rmdir(filename)).await
}
pub async fn stat(&self, filename: &Path) -> Result<ssh2::FileStat, Error> {
run_ssh2_fn(&self.stream, || self.inner.stat(filename)).await
}
pub async fn lstat(&self, filename: &Path) -> Result<ssh2::FileStat, Error> {
run_ssh2_fn(&self.stream, || self.inner.lstat(filename)).await
}
pub async fn setstat(&self, filename: &Path, stat: ssh2::FileStat) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.setstat(filename, stat.clone())).await
}
pub async fn symlink(&self, path: &Path, target: &Path) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.symlink(path, target)).await
}
pub async fn readlink(&self, path: &Path) -> Result<PathBuf, Error> {
run_ssh2_fn(&self.stream, || self.inner.readlink(path)).await
}
pub async fn realpath(&self, path: &Path) -> Result<PathBuf, Error> {
run_ssh2_fn(&self.stream, || self.inner.realpath(path)).await
}
pub async fn rename(
&self,
src: &Path,
dst: &Path,
flags: Option<ssh2::RenameFlags>,
) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.rename(src, dst, flags)).await
}
pub async fn unlink(&self, file: &Path) -> Result<(), Error> {
run_ssh2_fn(&self.stream, || self.inner.unlink(file)).await
}
pub async fn shutdown(mut self) -> Result<(), Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.shutdown()).await
}
}
impl File {
pub(crate) fn new(file: ssh2::File, stream: Arc<Async<TcpStream>>) -> Self {
Self {
inner: file,
stream,
}
}
pub async fn setstat(&mut self, stat: FileStat) -> Result<(), Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.setstat(stat.clone())).await
}
pub async fn stat(&mut self) -> Result<FileStat, Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.stat()).await
}
#[allow(missing_docs)]
pub async fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.readdir()).await
}
pub async fn fsync(&mut self) -> Result<(), Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.fsync()).await
}
pub async fn close(mut self) -> Result<(), Error> {
run_ssh2_fn(&self.stream.clone(), || self.inner.close()).await
}
}
impl AsyncRead for File {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.stream
.clone()
.with(|_s| self.inner.read(buf))
.boxed()
.poll_unpin(cx)
}
}
impl AsyncWrite for File {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
self.stream
.clone()
.with(|_s| self.inner.write(buf))
.boxed()
.poll_unpin(cx)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
self.stream
.clone()
.with(|_s| self.inner.flush())
.boxed()
.poll_unpin(cx)
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
impl Seek for File {
fn seek(&mut self, how: std::io::SeekFrom) -> std::result::Result<u64, std::io::Error> {
self.inner.seek(how)
}
}