feat: WebDAV support (#235)

This commit is contained in:
Christian Visintin
2024-03-02 19:23:27 +01:00
committed by GitHub
parent 5dfee2cbd9
commit c7469b8594
33 changed files with 656 additions and 85 deletions

View File

@@ -4,7 +4,9 @@
// Locals
use super::{AuthActivity, FileTransferParams};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
};
impl AuthActivity {
/// Delete bookmark
@@ -164,6 +166,7 @@ impl AuthActivity {
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(params),
ProtocolParams::WebDAV(params) => self.load_bookmark_webdav_into_gui(params),
}
}
@@ -196,4 +199,10 @@ impl AuthActivity {
#[cfg(unix)]
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
}
fn load_bookmark_webdav_into_gui(&mut self, params: WebDAVProtocolParams) {
self.mount_webdav_uri(&params.uri);
self.mount_username(&params.username);
self.mount_password(&params.password);
}
}

View File

@@ -11,7 +11,7 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
use crate::ui::activities::auth::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
// -- protocol
@@ -31,9 +31,9 @@ impl ProtocolRadio {
.modifiers(BorderType::Rounded),
)
.choices(if cfg!(smb) {
&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV", "SMB"]
} else {
&["SFTP", "SCP", "FTP", "FTPS", "S3"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV"]
})
.foreground(color)
.rewind(true)
@@ -50,6 +50,7 @@ impl ProtocolRadio {
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,
_ => FileTransferProtocol::Sftp,
}
}
@@ -63,6 +64,7 @@ impl ProtocolRadio {
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,
}
}
}
@@ -788,3 +790,40 @@ impl Component<Msg, NoUserEvent> for InputSmbWorkgroup {
)
}
}
#[derive(MockComponent)]
pub struct InputWebDAVUri {
component: Input,
}
impl InputWebDAVUri {
pub fn new(host: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder(
"http://localhost:8080",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("HTTP url", Alignment::Left)
.input_type(InputType::Text)
.value(host),
}
}
}
impl Component<Msg, NoUserEvent> for InputWebDAVUri {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::WebDAVUriBlurDown),
Msg::Ui(UiMsg::WebDAVUriBlurUp),
)
}
}

View File

@@ -19,7 +19,7 @@ pub use form::{
InputAddress, InputLocalDirectory, InputPassword, InputPort, InputRemoteDirectory,
InputS3AccessKey, InputS3Bucket, InputS3Endpoint, InputS3Profile, InputS3Region,
InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken, InputSmbShare,
InputUsername, ProtocolRadio, RadioS3NewPathStyle,
InputUsername, InputWebDAVUri, ProtocolRadio, RadioS3NewPathStyle,
};
pub use popup::{
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,

View File

@@ -15,6 +15,7 @@ impl AuthActivity {
FileTransferProtocol::Ftp(_) => 21,
FileTransferProtocol::AwsS3 => 22, // Doesn't matter, since not used
FileTransferProtocol::Smb => 445,
FileTransferProtocol::WebDAV => 80, // Doesn't matter, since not used
}
}
@@ -41,6 +42,7 @@ impl AuthActivity {
FileTransferProtocol::Ftp(_)
| FileTransferProtocol::Scp
| FileTransferProtocol::Sftp => self.collect_generic_host_params(self.protocol),
FileTransferProtocol::WebDAV => self.collect_webdav_host_params(),
}
}
@@ -98,6 +100,19 @@ impl AuthActivity {
})
}
pub(super) fn collect_webdav_host_params(&self) -> Result<FileTransferParams, &'static str> {
let params = self.get_webdav_params_input();
if params.uri.is_empty() {
return Err("Invalid URI");
}
Ok(FileTransferParams {
protocol: FileTransferProtocol::WebDAV,
params: ProtocolParams::WebDAV(params),
local_path: self.get_input_local_directory(),
remote_path: self.get_input_remote_directory(),
})
}
// -- update install
/// If enabled in configuration, check for updates from Github

View File

@@ -29,7 +29,8 @@ const RADIO_PROTOCOL_SCP: usize = 1;
const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_SMB: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 5;
const RADIO_PROTOCOL_SMB: usize = 6;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
@@ -71,6 +72,7 @@ pub enum Id {
Title,
Username,
WaitPopup,
WebDAVUri,
WindowSizeError,
}
@@ -155,6 +157,8 @@ pub enum UiMsg {
ShowSaveBookmarkPopup,
UsernameBlurDown,
UsernameBlurUp,
WebDAVUriBlurDown,
WebDAVUriBlurUp,
WindowResized,
}
@@ -164,6 +168,7 @@ enum InputMask {
Generic,
AwsS3,
Smb,
WebDAV,
}
// Store keys
@@ -240,6 +245,7 @@ impl AuthActivity {
| FileTransferProtocol::Scp
| FileTransferProtocol::Sftp => InputMask::Generic,
FileTransferProtocol::Smb => InputMask::Smb,
FileTransferProtocol::WebDAV => InputMask::WebDAV,
}
}
}

View File

@@ -70,6 +70,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -82,6 +83,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -177,6 +179,7 @@ impl AuthActivity {
#[cfg(windows)]
InputMask::Smb => &Id::RemoteDirectory,
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
InputMask::WebDAV => &Id::RemoteDirectory,
})
.is_ok());
}
@@ -189,7 +192,8 @@ impl AuthActivity {
.active(match self.input_mask() {
InputMask::Generic => &Id::Username,
InputMask::Smb => &Id::SmbShare,
InputMask::AwsS3 => panic!("this shouldn't happen (port on s3)"),
InputMask::AwsS3 | InputMask::WebDAV =>
panic!("this shouldn't happen (port on s3)"),
})
.is_ok());
}
@@ -203,6 +207,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Address,
InputMask::Smb => &Id::Address,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::WebDAVUri,
})
.is_ok());
}
@@ -225,6 +230,7 @@ impl AuthActivity {
#[cfg(windows)]
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3NewPathStyle,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -332,9 +338,16 @@ impl AuthActivity {
InputMask::Generic => &Id::Port,
InputMask::Smb => &Id::SmbShare,
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
InputMask::WebDAV => &Id::WebDAVUri,
})
.is_ok());
}
UiMsg::WebDAVUriBlurDown => {
assert!(self.app.active(&Id::Username).is_ok());
}
UiMsg::WebDAVUriBlurUp => {
assert!(self.app.active(&Id::Protocol).is_ok());
}
UiMsg::WindowResized => {
self.redraw = true;
}

View File

@@ -12,7 +12,9 @@ use tuirealm::tui::widgets::Clear;
use tuirealm::{State, StateValue, Sub, SubClause, SubEventClause};
use super::{components, AuthActivity, Context, FileTransferProtocol, Id, InputMask};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
};
use crate::filetransfer::FileTransferParams;
use crate::utils::ui::{Popup, Size};
@@ -61,6 +63,7 @@ impl AuthActivity {
self.mount_smb_share("");
#[cfg(unix)]
self.mount_smb_workgroup("");
self.mount_webdav_uri("");
// Version notice
if let Some(version) = self
.context()
@@ -195,6 +198,18 @@ impl AuthActivity {
)
.direction(Direction::Vertical)
.split(auth_chunks[4]),
InputMask::WebDAV => Layout::default()
.constraints(
[
Constraint::Length(3), // uri
Constraint::Length(3), // username
Constraint::Length(3), // password
Constraint::Length(3), // dir
]
.as_ref(),
)
.direction(Direction::Vertical)
.split(auth_chunks[4]),
};
// Create bookmark chunks
let bookmark_chunks = Layout::default()
@@ -230,6 +245,13 @@ impl AuthActivity {
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
InputMask::WebDAV => {
let view_ids = self.get_webdav_view();
self.app.view(&view_ids[0], f, input_mask[0]);
self.app.view(&view_ids[1], f, input_mask[1]);
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
}
// Bookmark chunks
self.app.view(&Id::BookmarksList, f, bookmark_chunks[0]);
@@ -794,6 +816,18 @@ impl AuthActivity {
.is_ok());
}
pub(super) fn mount_webdav_uri(&mut self, uri: &str) {
let addr_color = self.theme().auth_address;
assert!(self
.app
.remount(
Id::WebDAVUri,
Box::new(components::InputWebDAVUri::new(uri, addr_color)),
vec![]
)
.is_ok());
}
// -- query
/// Collect input values from view
@@ -860,6 +894,18 @@ impl AuthActivity {
.password(password)
}
pub(super) fn get_webdav_params_input(&self) -> WebDAVProtocolParams {
let uri: String = self.get_webdav_uri();
let username = self.get_input_username().unwrap_or_default();
let password = self.get_input_password().unwrap_or_default();
WebDAVProtocolParams {
uri,
username,
password,
}
}
pub(super) fn get_input_remote_directory(&self) -> Option<PathBuf> {
match self.app.state(&Id::RemoteDirectory) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => {
@@ -878,6 +924,13 @@ impl AuthActivity {
}
}
pub(super) fn get_webdav_uri(&self) -> String {
match self.app.state(&Id::WebDAVUri) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
}
}
pub(super) fn get_input_addr(&self) -> String {
match self.app.state(&Id::Address) {
Ok(State::One(StateValue::String(x))) => x,
@@ -1011,6 +1064,7 @@ impl AuthActivity {
InputMask::AwsS3 => 12,
InputMask::Generic => 12,
InputMask::Smb => 12,
InputMask::WebDAV => 12,
}
}
@@ -1069,6 +1123,7 @@ impl AuthActivity {
};
format!("\\\\{username}{}\\{}", params.address, params.share)
}
ProtocolParams::WebDAV(params) => params.uri,
}
}
@@ -1180,6 +1235,23 @@ impl AuthActivity {
}
}
fn get_webdav_view(&self) -> [Id; 4] {
match self.app.focus() {
Some(&Id::LocalDirectory) => [
Id::Username,
Id::Password,
Id::RemoteDirectory,
Id::LocalDirectory,
],
_ => [
Id::WebDAVUri,
Id::Username,
Id::Password,
Id::RemoteDirectory,
],
}
}
fn init_global_listener(&mut self) {
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
assert!(self

View File

@@ -112,6 +112,7 @@ impl FileTransferActivity {
ProtocolParams::Generic(params) => params.address.clone(),
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
ProtocolParams::Smb(params) => params.address.clone(),
ProtocolParams::WebDAV(params) => params.uri.clone(),
}
}
@@ -141,6 +142,13 @@ impl FileTransferActivity {
);
format!("Connecting to \\\\{}\\{}", params.address, params.share)
}
ProtocolParams::WebDAV(params) => {
info!(
"Client is not connected to remote; connecting to {}",
params.uri
);
format!("Connecting to {}", params.uri)
}
}
}

View File

@@ -13,7 +13,7 @@ use crate::explorer::GroupDirs as GroupDirsEnum;
use crate::filetransfer::FileTransferProtocol;
use crate::ui::activities::setup::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
use crate::utils::parser::parse_bytesize;
@@ -67,7 +67,7 @@ impl DefaultProtocol {
.color(Color::Cyan)
.modifiers(BorderType::Rounded),
)
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"])
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB", "WebDAV"])
.foreground(Color::Cyan)
.rewind(true)
.title("Default protocol", Alignment::Left)
@@ -78,6 +78,7 @@ impl DefaultProtocol {
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,
}),
}
}

View File

@@ -31,6 +31,7 @@ const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_SMB: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 6;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]

View File

@@ -10,7 +10,9 @@ use std::path::PathBuf;
use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::{State, StateValue};
use super::{components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout};
use super::{
components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout, RADIO_PROTOCOL_WEBDAV,
};
use crate::explorer::GroupDirs;
use crate::filetransfer::FileTransferProtocol;
use crate::ui::activities::setup::{
@@ -277,6 +279,7 @@ impl SetupActivity {
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,
_ => FileTransferProtocol::Sftp,
};
self.config_mut().set_default_protocol(protocol);