use crate::{error::Error, secret::FtpSecret};
use ftp::{
FtpStream,
openssl::ssl::{SslContext, SslMethod},
types::FileType,
};
use std::convert::TryFrom;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct FtpEndpoint {
connection: Arc<Mutex<FtpStream>>,
root_directory: String,
}
impl TryFrom<&FtpSecret> for FtpEndpoint {
type Error = Error;
fn try_from(secret: &FtpSecret) -> Result<Self, Error> {
let connection = Self::connect(secret)?;
let connection = Arc::new(Mutex::new(connection));
Ok(Self {
connection,
root_directory: secret
.root_directory
.clone()
.unwrap_or_else(|| "/".to_string()),
})
}
}
impl FtpEndpoint {
fn connect(secret: &FtpSecret) -> Result<FtpStream, Error> {
let mut ftp_stream = FtpStream::connect((secret.hostname(), secret.port()))?;
if secret.is_secure() {
let builder = SslContext::builder(SslMethod::tls())?;
let context = builder.build();
ftp_stream = ftp_stream.into_secure(context)?;
}
if let (Some(username), Some(password)) = (secret.username(), secret.password()) {
ftp_stream.login(username.as_str(), password.as_str())?;
}
ftp_stream.transfer_type(FileType::Binary)?;
Ok(ftp_stream)
}
pub fn connection(&self) -> Arc<Mutex<FtpStream>> {
self.connection.clone()
}
pub fn absolute_path(&self, path: &str) -> String {
format!(
"{}/{}",
self.root_directory.trim_end_matches('/'),
path.trim_start_matches('/')
)
}
}
impl Drop for FtpEndpoint {
fn drop(&mut self) {
if let Err(_error) = self.connection.lock().unwrap().quit() {
log::debug!("FTP connection already closed.");
} else {
log::debug!("FTP connection closed properly.");
}
}
}