mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: timeouts for SFTP operations (#3384)
This commit is contained in:
parent
81ccdd8b64
commit
ade1025a74
7 changed files with 90 additions and 34 deletions
|
|
@ -59,6 +59,10 @@ impl From<tokio::sync::oneshot::error::RecvError> for Error {
|
||||||
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { Self::custom("channel closed") }
|
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { Self::custom("channel closed") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<tokio::time::error::Elapsed> for Error {
|
||||||
|
fn from(_: tokio::time::error::Elapsed) -> Self { Self::Timeout }
|
||||||
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{io, pin::Pin, sync::Arc, task::{Context, Poll, ready}, time::Duration};
|
use std::{io, pin::Pin, sync::Arc, task::{Context, Poll, ready}, time::Duration};
|
||||||
|
|
||||||
use tokio::{io::{AsyncRead, AsyncWrite, ReadBuf}, sync::oneshot, time::{Timeout, timeout}};
|
use tokio::{io::{AsyncRead, AsyncWrite, ReadBuf}, time::{Timeout, timeout}};
|
||||||
|
|
||||||
use crate::{Error, Operator, Packet, Session, fs::Attrs};
|
use crate::{Error, Operator, Packet, Receiver, Session, fs::Attrs};
|
||||||
|
|
||||||
pub struct File {
|
pub struct File {
|
||||||
session: Arc<Session>,
|
session: Arc<Session>,
|
||||||
|
|
@ -10,10 +10,10 @@ pub struct File {
|
||||||
|
|
||||||
closed: bool,
|
closed: bool,
|
||||||
cursor: u64,
|
cursor: u64,
|
||||||
close_rx: Option<Timeout<oneshot::Receiver<Packet<'static>>>>,
|
close_rx: Option<Timeout<Receiver>>,
|
||||||
read_rx: Option<oneshot::Receiver<Packet<'static>>>,
|
read_rx: Option<Receiver>,
|
||||||
write_rx: Option<(oneshot::Receiver<Packet<'static>>, usize)>,
|
write_rx: Option<(Receiver, usize)>,
|
||||||
flush_rx: Option<Timeout<oneshot::Receiver<Packet<'static>>>>,
|
flush_rx: Option<Timeout<Receiver>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Unpin for File {}
|
impl Unpin for File {}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod macros;
|
||||||
mod operator;
|
mod operator;
|
||||||
mod packet;
|
mod packet;
|
||||||
mod path;
|
mod path;
|
||||||
|
mod receiver;
|
||||||
mod ser;
|
mod ser;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
|
|
@ -18,5 +19,6 @@ pub(crate) use id::*;
|
||||||
pub use operator::*;
|
pub use operator::*;
|
||||||
pub use packet::*;
|
pub use packet::*;
|
||||||
pub use path::*;
|
pub use path::*;
|
||||||
|
pub use receiver::*;
|
||||||
pub(crate) use ser::*;
|
pub(crate) use ser::*;
|
||||||
pub use session::*;
|
pub use session::*;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use russh::{ChannelStream, client::Msg};
|
use russh::{ChannelStream, client::Msg};
|
||||||
use tokio::sync::oneshot;
|
|
||||||
use typed_path::UnixPathBuf;
|
use typed_path::UnixPathBuf;
|
||||||
|
|
||||||
use crate::{AsSftpPath, Error, Packet, Session, SftpPath, fs::{Attrs, File, Flags, ReadDir}, requests, responses};
|
use crate::{AsSftpPath, Error, Receiver, Session, SftpPath, fs::{Attrs, File, Flags, ReadDir}, requests, responses};
|
||||||
|
|
||||||
pub struct Operator(Arc<Session>);
|
pub struct Operator(Arc<Session>);
|
||||||
|
|
||||||
impl Deref for Operator {
|
impl Deref for Operator {
|
||||||
type Target = Session;
|
type Target = Arc<Session>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.0 }
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
@ -36,25 +35,15 @@ impl Operator {
|
||||||
Ok(File::new(&self.0, handle.handle))
|
Ok(File::new(&self.0, handle.handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
pub fn close(&self, handle: &str) -> Result<Receiver, Error> {
|
||||||
self.send_sync(requests::Close::new(handle))
|
self.send_sync(requests::Close::new(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(
|
pub fn read(&self, handle: &str, offset: u64, len: u32) -> Result<Receiver, Error> {
|
||||||
&self,
|
|
||||||
handle: &str,
|
|
||||||
offset: u64,
|
|
||||||
len: u32,
|
|
||||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
|
||||||
self.send_sync(requests::Read::new(handle, offset, len))
|
self.send_sync(requests::Read::new(handle, offset, len))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(
|
pub fn write(&self, handle: &str, offset: u64, data: &[u8]) -> Result<Receiver, Error> {
|
||||||
&self,
|
|
||||||
handle: &str,
|
|
||||||
offset: u64,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
|
||||||
self.send_sync(requests::Write::new(handle, offset, data))
|
self.send_sync(requests::Write::new(handle, offset, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +172,7 @@ impl Operator {
|
||||||
status.into()
|
status.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fsync(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
pub fn fsync(&self, handle: &str) -> Result<Receiver, Error> {
|
||||||
if self.extensions.lock().get("fsync@openssh.com").is_none_or(|s| s != "1") {
|
if self.extensions.lock().get("fsync@openssh.com").is_none_or(|s| s != "1") {
|
||||||
return Err(Error::Unsupported);
|
return Err(Error::Unsupported);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
yazi-sftp/src/receiver.rs
Normal file
47
yazi-sftp/src/receiver.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use std::{pin::Pin, sync::Arc, task::Poll};
|
||||||
|
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
use crate::{Packet, Session};
|
||||||
|
|
||||||
|
pub struct Receiver {
|
||||||
|
rx: oneshot::Receiver<Packet<'static>>,
|
||||||
|
received: bool,
|
||||||
|
|
||||||
|
session: Arc<Session>,
|
||||||
|
id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Receiver {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.received {
|
||||||
|
self.session.callback.lock().remove(&self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Receiver {
|
||||||
|
pub(crate) fn new(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
id: u32,
|
||||||
|
rx: oneshot::Receiver<Packet<'static>>,
|
||||||
|
) -> Self {
|
||||||
|
Self { rx, received: false, session: session.clone(), id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for Receiver {
|
||||||
|
type Output = Result<Packet<'static>, oneshot::error::RecvError>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let me = self.get_mut();
|
||||||
|
match Pin::new(&mut me.rx).poll(cx) {
|
||||||
|
Poll::Ready(Ok(packet)) => {
|
||||||
|
me.received = true;
|
||||||
|
Poll::Ready(Ok(packet))
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,15 @@ impl Status {
|
||||||
pub fn is_ok(&self) -> bool { self.code == StatusCode::Ok }
|
pub fn is_ok(&self) -> bool { self.code == StatusCode::Ok }
|
||||||
|
|
||||||
pub fn is_eof(&self) -> bool { self.code == StatusCode::Eof }
|
pub fn is_eof(&self) -> bool { self.code == StatusCode::Eof }
|
||||||
|
|
||||||
|
pub(crate) fn connection_lost(id: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
code: StatusCode::ConnectionLost,
|
||||||
|
message: "connection lost".to_owned(),
|
||||||
|
language: "en".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
use std::{any::TypeId, collections::HashMap, io::{self, ErrorKind}, sync::Arc};
|
use std::{any::TypeId, collections::HashMap, io::{self, ErrorKind}, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use russh::{ChannelStream, client::Msg};
|
use russh::{ChannelStream, client::Msg};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}, select, sync::{mpsc, oneshot}};
|
use tokio::{io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}, select, sync::{mpsc, oneshot}, time::timeout};
|
||||||
|
|
||||||
use crate::{Error, Id, Packet, responses};
|
use crate::{Error, Id, Packet, Receiver, responses};
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
tx: mpsc::UnboundedSender<Vec<u8>>,
|
tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||||
id: Id,
|
id: Id,
|
||||||
callback: Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>,
|
pub(super) callback: Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>,
|
||||||
pub(super) extensions: Mutex<HashMap<String, String>>,
|
pub(super) extensions: Mutex<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +43,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let me_ = me.clone();
|
||||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(data) = rx.recv().await {
|
while let Some(data) = rx.recv().await {
|
||||||
|
|
@ -51,6 +52,9 @@ impl Session {
|
||||||
{
|
{
|
||||||
rx.close();
|
rx.close();
|
||||||
writer.shutdown().await.ok();
|
writer.shutdown().await.ok();
|
||||||
|
for (id, cb) in me_.callback.lock().drain() {
|
||||||
|
cb.send(responses::Status::connection_lost(id).into()).ok();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,12 +88,12 @@ impl Session {
|
||||||
me
|
me
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send<'a, I, O>(&self, input: I) -> Result<O, Error>
|
pub async fn send<'a, I, O>(self: &Arc<Self>, input: I) -> Result<O, Error>
|
||||||
where
|
where
|
||||||
I: Into<Packet<'a>> + Serialize,
|
I: Into<Packet<'a>> + Serialize,
|
||||||
O: TryFrom<Packet<'static>, Error = Error> + 'static,
|
O: TryFrom<Packet<'static>, Error = Error> + 'static,
|
||||||
{
|
{
|
||||||
match self.send_sync(input)?.await? {
|
match timeout(Duration::from_secs(30), self.send_sync(input)?).await?? {
|
||||||
Packet::Status(status) if TypeId::of::<O>() != TypeId::of::<responses::Status>() => {
|
Packet::Status(status) if TypeId::of::<O>() != TypeId::of::<responses::Status>() => {
|
||||||
Err(Error::Status(status))
|
Err(Error::Status(status))
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +101,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_sync<'a, I>(&self, input: I) -> Result<oneshot::Receiver<Packet<'static>>, Error>
|
pub fn send_sync<'a, I>(self: &Arc<Self>, input: I) -> Result<Receiver, Error>
|
||||||
where
|
where
|
||||||
I: Into<Packet<'a>> + Serialize,
|
I: Into<Packet<'a>> + Serialize,
|
||||||
{
|
{
|
||||||
|
|
@ -106,12 +110,13 @@ impl Session {
|
||||||
request = request.with_id(self.id.next());
|
request = request.with_id(self.id.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let id = request.id();
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.callback.lock().insert(request.id(), tx);
|
|
||||||
self.tx.send(crate::to_bytes(request)?)?;
|
|
||||||
|
|
||||||
Ok(rx)
|
self.callback.lock().insert(id, tx);
|
||||||
|
self.tx.send(crate::to_bytes(request)?)?;
|
||||||
|
Ok(Receiver::new(self, id, rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_closed(&self) -> bool { self.tx.is_closed() }
|
pub fn is_closed(self: &Arc<Self>) -> bool { self.tx.is_closed() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue