blob: 30141cc30071f86282fc0a55bd947bb269e64e3e [file]
// 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>))
}
}