| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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. |
| |
| use super::backend::*; |
| use super::core::FtpCore; |
| use super::core::Manager; |
| use super::core::format_ftp_error; |
| use super::lister::FtpLister; |
| use super::writer::FtpWriter; |
| use bytes::BytesMut; |
| use fastpool::bounded; |
| use futures::AsyncRead; |
| use futures::AsyncReadExt; |
| use opendal_core::raw::*; |
| use opendal_core::*; |
| use std::sync::Arc; |
| use suppaftp::FtpError; |
| use suppaftp::Status; |
| use suppaftp::types::Response; |
| |
| pub struct FtpReadStream { |
| /// Keep the connection alive while data stream is alive. |
| _ftp_stream: bounded::Object<Manager>, |
| |
| data_stream: Box<dyn AsyncRead + Sync + Send + Unpin + 'static>, |
| chunk: usize, |
| buf: BytesMut, |
| } |
| |
| /// # Safety |
| /// |
| /// We only have `&mut self` for FtpReadStream. |
| unsafe impl Sync for FtpReadStream {} |
| |
| impl FtpReadStream { |
| pub async fn new( |
| mut ftp_stream: bounded::Object<Manager>, |
| path: String, |
| range: BytesRange, |
| ) -> Result<Self> { |
| let (offset, size) = (range.offset(), range.size().unwrap_or(u64::MAX)); |
| if offset != 0 { |
| ftp_stream |
| .resume_transfer(offset as usize) |
| .await |
| .map_err(format_ftp_error)?; |
| } |
| let ds = ftp_stream |
| .retr_as_stream(path) |
| .await |
| .map_err(format_ftp_error)? |
| .take(size as _); |
| |
| Ok(Self { |
| _ftp_stream: ftp_stream, |
| |
| data_stream: Box::new(ds), |
| chunk: 1024 * 1024, |
| buf: BytesMut::new(), |
| }) |
| } |
| } |
| |
| impl oio::ReadStream for FtpReadStream { |
| async fn read(&mut self) -> Result<Buffer> { |
| self.buf.resize(self.chunk, 0); |
| let n = self |
| .data_stream |
| .read(&mut self.buf) |
| .await |
| .map_err(new_std_io_error)?; |
| Ok(Buffer::from(self.buf.split_to(n).freeze())) |
| } |
| } |
| |
| /// Reader returned by this backend. |
| pub struct FtpReader { |
| backend: FtpBackend, |
| path: String, |
| } |
| |
| impl FtpReader { |
| pub(super) fn new(backend: FtpBackend, path: &str, _: OpRead) -> Self { |
| Self { |
| backend, |
| path: path.to_string(), |
| } |
| } |
| } |
| |
| pub struct FtpLazyWriter { |
| core: Arc<FtpCore>, |
| path: String, |
| append: bool, |
| inner: Option<FtpWriter>, |
| } |
| |
| impl FtpLazyWriter { |
| pub(super) fn new(core: Arc<FtpCore>, path: &str, op: OpWrite) -> Self { |
| Self { |
| core, |
| path: path.to_string(), |
| append: op.append(), |
| inner: None, |
| } |
| } |
| |
| async fn inner(&mut self) -> Result<&mut FtpWriter> { |
| if self.inner.is_none() { |
| let parent = get_parent(&self.path); |
| let paths: Vec<&str> = parent.split('/').collect(); |
| |
| // TODO: we can optimize this by checking dir existence first. |
| let mut ftp_stream = self.core.ftp_connect(Operation::Write).await?; |
| let mut curr_path = String::new(); |
| |
| for path in paths { |
| if path.is_empty() { |
| continue; |
| } |
| curr_path.push_str(path); |
| curr_path.push('/'); |
| match ftp_stream.mkdir(&curr_path).await { |
| // Do nothing if status is FileUnavailable or OK(()) is return. |
| Err(FtpError::UnexpectedResponse(Response { |
| status: Status::FileUnavailable, |
| .. |
| })) |
| | Ok(()) => (), |
| Err(e) => { |
| return Err(format_ftp_error(e)); |
| } |
| } |
| } |
| |
| let tmp_path = (!self.append).then_some(build_tmp_path_of(&self.path)); |
| let w = FtpWriter::new(ftp_stream, self.path.clone(), tmp_path); |
| self.inner = Some(w); |
| } |
| |
| Ok(self.inner.as_mut().expect("ftp writer must be initialized")) |
| } |
| } |
| |
| impl oio::Write for FtpLazyWriter { |
| async fn write(&mut self, bs: Buffer) -> Result<()> { |
| self.inner().await?.write(bs).await |
| } |
| |
| async fn close(&mut self) -> Result<Metadata> { |
| match &mut self.inner { |
| Some(w) => w.close().await, |
| None => Ok(Metadata::default()), |
| } |
| } |
| |
| async fn abort(&mut self) -> Result<()> { |
| match &mut self.inner { |
| Some(w) => w.abort().await, |
| None => Err(Error::new( |
| ErrorKind::Unsupported, |
| "FtpWriter doesn't support abort", |
| )), |
| } |
| } |
| } |
| |
| pub struct FtpLazyLister { |
| core: Arc<FtpCore>, |
| path: String, |
| inner: Option<FtpLister>, |
| } |
| |
| impl FtpLazyLister { |
| pub(super) fn new(core: Arc<FtpCore>, path: &str) -> Self { |
| Self { |
| core, |
| path: path.to_string(), |
| inner: None, |
| } |
| } |
| |
| async fn inner(&mut self) -> Result<&mut FtpLister> { |
| if self.inner.is_none() { |
| let mut ftp_stream = self.core.ftp_connect(Operation::List).await?; |
| |
| let pathname = if self.path == "/" { |
| None |
| } else { |
| Some(self.path.as_str()) |
| }; |
| let files = ftp_stream.list(pathname).await.map_err(format_ftp_error)?; |
| |
| self.inner = Some(FtpLister::new( |
| if self.path == "/" { "" } else { &self.path }, |
| files, |
| )); |
| } |
| |
| Ok(self.inner.as_mut().expect("ftp lister must be initialized")) |
| } |
| } |
| |
| impl oio::List for FtpLazyLister { |
| async fn next(&mut self) -> Result<Option<oio::Entry>> { |
| self.inner().await?.next().await |
| } |
| } |
| |
| impl oio::StreamRead for FtpReader { |
| async fn open(&self, range: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> { |
| let backend = &self.backend; |
| let path = self.path.as_str(); |
| |
| let ftp_stream = backend.core.ftp_connect(Operation::Read).await?; |
| let rp = RpRead::default(); |
| let stream = FtpReadStream::new(ftp_stream, path.to_string(), range).await?; |
| |
| Ok((rp, Box::new(stream) as Box<dyn oio::ReadStreamDyn>)) |
| } |
| } |