mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
SMB support (#184)
* feat: smb client * fix: smb connection * fix: smbclient deps * feat: SMB mentions to user manual * feat: changelog * dlib macos * fix: removed smb support from macos :( * fix: restored libsmbclient build * fix: strange lint message * fix: macos build smb * fix: macos build smb * fix: macos tests * fix: macos lint * feat: SMB windows support * fix: windows tests
This commit is contained in:
committed by
GitHub
parent
a13663e5e9
commit
b7369162d2
@@ -4,7 +4,7 @@
|
||||
|
||||
// Locals
|
||||
use super::{AuthActivity, FileTransferParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
|
||||
|
||||
impl AuthActivity {
|
||||
/// Delete bookmark
|
||||
@@ -157,6 +157,7 @@ impl AuthActivity {
|
||||
match bookmark.params {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,4 +179,15 @@ impl AuthActivity {
|
||||
self.mount_s3_session_token(params.session_token.as_deref().unwrap_or(""));
|
||||
self.mount_s3_new_path_style(params.new_path_style);
|
||||
}
|
||||
|
||||
fn load_bookmark_smb_into_gui(&mut self, params: SmbParams) {
|
||||
self.mount_address(params.address.as_str());
|
||||
#[cfg(unix)]
|
||||
self.mount_port(params.port);
|
||||
self.mount_username(params.username.as_deref().unwrap_or(""));
|
||||
self.mount_password(params.password.as_deref().unwrap_or(""));
|
||||
self.mount_smb_share(¶ms.share);
|
||||
#[cfg(unix)]
|
||||
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
use crate::ui::activities::auth::{
|
||||
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
|
||||
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
|
||||
};
|
||||
|
||||
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
|
||||
|
||||
// -- protocol
|
||||
@@ -26,7 +31,11 @@ impl ProtocolRadio {
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3"])
|
||||
.choices(if cfg!(smb) {
|
||||
&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"]
|
||||
} else {
|
||||
&["SFTP", "SCP", "FTP", "FTPS", "S3"]
|
||||
})
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("Protocol", Alignment::Left)
|
||||
@@ -37,10 +46,11 @@ impl ProtocolRadio {
|
||||
/// Convert radio index for protocol into a `FileTransferProtocol`
|
||||
fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol {
|
||||
match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SCP => FileTransferProtocol::Scp,
|
||||
RADIO_PROTOCOL_FTP => FileTransferProtocol::Ftp(false),
|
||||
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
|
||||
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
}
|
||||
}
|
||||
@@ -48,11 +58,12 @@ impl ProtocolRadio {
|
||||
/// Convert `FileTransferProtocol` enum into radio group index
|
||||
fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize {
|
||||
match protocol {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP,
|
||||
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
|
||||
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
|
||||
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
|
||||
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
|
||||
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -673,3 +684,72 @@ fn handle_input_ev(
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputSmbShare {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputSmbShare {
|
||||
pub fn new(host: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Share", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputSmbShare {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
Msg::Ui(UiMsg::SmbShareBlurDown),
|
||||
Msg::Ui(UiMsg::SmbShareBlurUp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputSmbWorkgroup {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl InputSmbWorkgroup {
|
||||
pub fn new(host: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Workgroup", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Component<Msg, NoUserEvent> for InputSmbWorkgroup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
Msg::Ui(UiMsg::SmbWorkgroupDown),
|
||||
Msg::Ui(UiMsg::SmbWorkgroupUp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ pub use bookmarks::{
|
||||
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
|
||||
RecentsList,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
pub use form::InputSmbWorkgroup;
|
||||
pub use form::{
|
||||
InputAddress, InputPassword, InputPort, InputRemoteDirectory, InputS3AccessKey, InputS3Bucket,
|
||||
InputS3Endpoint, InputS3Profile, InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken,
|
||||
InputS3SessionToken, InputUsername, ProtocolRadio, RadioS3NewPathStyle,
|
||||
InputS3SessionToken, InputSmbShare, InputUsername, ProtocolRadio, RadioS3NewPathStyle,
|
||||
};
|
||||
pub use popup::{
|
||||
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
|
||||
|
||||
@@ -14,6 +14,7 @@ impl AuthActivity {
|
||||
FileTransferProtocol::Sftp | FileTransferProtocol::Scp => 22,
|
||||
FileTransferProtocol::Ftp(_) => 21,
|
||||
FileTransferProtocol::AwsS3 => 22, // Doesn't matter, since not used
|
||||
FileTransferProtocol::Smb => 445,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +37,10 @@ impl AuthActivity {
|
||||
pub(super) fn collect_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
match self.protocol {
|
||||
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(),
|
||||
protocol => self.collect_generic_host_params(protocol),
|
||||
FileTransferProtocol::Smb => self.collect_smb_host_params(),
|
||||
FileTransferProtocol::Ftp(_)
|
||||
| FileTransferProtocol::Scp
|
||||
| FileTransferProtocol::Sftp => self.collect_generic_host_params(self.protocol),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +76,25 @@ impl AuthActivity {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn collect_smb_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
let params = self.get_smb_params_input();
|
||||
if params.address.is_empty() {
|
||||
return Err("Invalid address");
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if params.port == 0 {
|
||||
return Err("Invalid port");
|
||||
}
|
||||
if params.share.is_empty() {
|
||||
return Err("Invalid share");
|
||||
}
|
||||
Ok(FileTransferParams {
|
||||
protocol: FileTransferProtocol::Smb,
|
||||
params: ProtocolParams::Smb(params),
|
||||
entry_directory: self.get_input_remote_directory(),
|
||||
})
|
||||
}
|
||||
|
||||
// -- update install
|
||||
|
||||
/// If enabled in configuration, check for updates from Github
|
||||
|
||||
@@ -23,6 +23,14 @@ use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||
use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
|
||||
// radio
|
||||
const RADIO_PROTOCOL_SFTP: usize = 0;
|
||||
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;
|
||||
|
||||
// -- components
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum Id {
|
||||
@@ -55,6 +63,9 @@ pub enum Id {
|
||||
S3SecretAccessKey,
|
||||
S3SecurityToken,
|
||||
S3SessionToken,
|
||||
SmbShare,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroup,
|
||||
Subtitle,
|
||||
Title,
|
||||
Username,
|
||||
@@ -125,6 +136,12 @@ pub enum UiMsg {
|
||||
S3SecurityTokenBlurUp,
|
||||
S3SessionTokenBlurDown,
|
||||
S3SessionTokenBlurUp,
|
||||
SmbShareBlurDown,
|
||||
SmbShareBlurUp,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroupDown,
|
||||
#[cfg(unix)]
|
||||
SmbWorkgroupUp,
|
||||
BookmarkNameBlur,
|
||||
SaveBookmarkPasswordBlur,
|
||||
ShowDeleteBookmarkPopup,
|
||||
@@ -143,6 +160,7 @@ pub enum UiMsg {
|
||||
enum InputMask {
|
||||
Generic,
|
||||
AwsS3,
|
||||
Smb,
|
||||
}
|
||||
|
||||
// Store keys
|
||||
@@ -218,6 +236,7 @@ impl AuthActivity {
|
||||
FileTransferProtocol::Ftp(_)
|
||||
| FileTransferProtocol::Scp
|
||||
| FileTransferProtocol::Sftp => InputMask::Generic,
|
||||
FileTransferProtocol::Smb => InputMask::Smb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -79,6 +80,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -113,7 +115,12 @@ impl AuthActivity {
|
||||
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
||||
match msg {
|
||||
UiMsg::AddressBlurDown => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
||||
&Id::SmbShare
|
||||
} else {
|
||||
&Id::Port
|
||||
};
|
||||
assert!(self.app.active(id).is_ok());
|
||||
}
|
||||
UiMsg::AddressBlurUp => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
@@ -155,13 +162,30 @@ impl AuthActivity {
|
||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||
}
|
||||
UiMsg::PasswordBlurDown => {
|
||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::RemoteDirectory,
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => &Id::SmbWorkgroup,
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => &Id::RemoteDirectory,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::PasswordBlurUp => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
UiMsg::PortBlurDown => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Username,
|
||||
InputMask::Smb => &Id::SmbShare,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (port on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::PortBlurUp => {
|
||||
assert!(self.app.active(&Id::Address).is_ok());
|
||||
@@ -171,6 +195,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Address,
|
||||
InputMask::Smb => &Id::Address,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -189,6 +214,10 @@ impl AuthActivity {
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => &Id::SmbWorkgroup,
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3NewPathStyle,
|
||||
})
|
||||
.is_ok());
|
||||
@@ -247,6 +276,25 @@ impl AuthActivity {
|
||||
UiMsg::S3NewPathStyleBlurUp => {
|
||||
assert!(self.app.active(&Id::S3SessionToken).is_ok());
|
||||
}
|
||||
UiMsg::SmbShareBlurDown => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
UiMsg::SmbShareBlurUp => {
|
||||
let id = if cfg!(windows) && self.input_mask() == InputMask::Smb {
|
||||
&Id::Address
|
||||
} else {
|
||||
&Id::Port
|
||||
};
|
||||
assert!(self.app.active(id).is_ok());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
UiMsg::SmbWorkgroupDown => {
|
||||
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
UiMsg::SmbWorkgroupUp => {
|
||||
assert!(self.app.active(&Id::Password).is_ok());
|
||||
}
|
||||
UiMsg::SaveBookmarkPasswordBlur => {
|
||||
assert!(self.app.active(&Id::BookmarkName).is_ok());
|
||||
}
|
||||
@@ -272,7 +320,14 @@ impl AuthActivity {
|
||||
assert!(self.app.active(&Id::Password).is_ok());
|
||||
}
|
||||
UiMsg::UsernameBlurUp => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Port,
|
||||
InputMask::Smb => &Id::SmbShare,
|
||||
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
UiMsg::WindowResized => {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ 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};
|
||||
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
|
||||
@@ -56,6 +56,9 @@ impl AuthActivity {
|
||||
self.mount_s3_security_token("");
|
||||
self.mount_s3_session_token("");
|
||||
self.mount_s3_new_path_style(false);
|
||||
self.mount_smb_share("");
|
||||
#[cfg(unix)]
|
||||
self.mount_smb_workgroup("");
|
||||
// Version notice
|
||||
if let Some(version) = self
|
||||
.context()
|
||||
@@ -150,7 +153,7 @@ impl AuthActivity {
|
||||
InputMask::Generic => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // host
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // port
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
@@ -160,6 +163,36 @@ impl AuthActivity {
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
#[cfg(unix)]
|
||||
InputMask::Smb => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // port
|
||||
Constraint::Length(3), // share
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
Constraint::Length(3), // workgroup
|
||||
Constraint::Length(3), // remote directory
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
#[cfg(windows)]
|
||||
InputMask::Smb => Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // address
|
||||
Constraint::Length(3), // share
|
||||
Constraint::Length(3), // username
|
||||
Constraint::Length(3), // password
|
||||
Constraint::Length(3), // remote directory
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(auth_chunks[4]),
|
||||
};
|
||||
// Create bookmark chunks
|
||||
let bookmark_chunks = Layout::default()
|
||||
@@ -188,6 +221,13 @@ impl AuthActivity {
|
||||
self.app.view(&view_ids[2], f, input_mask[2]);
|
||||
self.app.view(&view_ids[3], f, input_mask[3]);
|
||||
}
|
||||
InputMask::Smb => {
|
||||
let view_ids = self.get_smb_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]);
|
||||
@@ -713,6 +753,31 @@ impl AuthActivity {
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
pub(crate) fn mount_smb_share(&mut self, share: &str) {
|
||||
let color = self.theme().auth_password;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::SmbShare,
|
||||
Box::new(components::InputSmbShare::new(share, color)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn mount_smb_workgroup(&mut self, workgroup: &str) {
|
||||
let color = self.theme().auth_address;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::SmbWorkgroup,
|
||||
Box::new(components::InputSmbWorkgroup::new(workgroup, color)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
// -- query
|
||||
|
||||
/// Collect input values from view
|
||||
@@ -748,6 +813,37 @@ impl AuthActivity {
|
||||
.new_path_style(new_path_style)
|
||||
}
|
||||
|
||||
/// Collect s3 input values from view
|
||||
#[cfg(unix)]
|
||||
pub(super) fn get_smb_params_input(&self) -> SmbParams {
|
||||
let share: String = self.get_input_smb_share();
|
||||
let workgroup: Option<String> = self.get_input_smb_workgroup();
|
||||
|
||||
let address: String = self.get_input_addr();
|
||||
let port: u16 = self.get_input_port();
|
||||
let username = self.get_input_username();
|
||||
let password = self.get_input_password();
|
||||
|
||||
SmbParams::new(address, share)
|
||||
.port(port)
|
||||
.username(username)
|
||||
.password(password)
|
||||
.workgroup(workgroup)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(super) fn get_smb_params_input(&self) -> SmbParams {
|
||||
let share: String = self.get_input_smb_share();
|
||||
|
||||
let address: String = self.get_input_addr();
|
||||
let username = self.get_input_username();
|
||||
let password = self.get_input_password();
|
||||
|
||||
SmbParams::new(address, share)
|
||||
.username(username)
|
||||
.password(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() => {
|
||||
@@ -851,6 +947,21 @@ impl AuthActivity {
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn get_input_smb_share(&self) -> String {
|
||||
match self.app.state(&Id::SmbShare) {
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) fn get_input_smb_workgroup(&self) -> Option<String> {
|
||||
match self.app.state(&Id::SmbWorkgroup) {
|
||||
Ok(State::One(StateValue::String(x))) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get new bookmark params
|
||||
pub(super) fn get_new_bookmark(&self) -> (String, bool) {
|
||||
let name = match self.app.state(&Id::BookmarkName) {
|
||||
@@ -874,6 +985,7 @@ impl AuthActivity {
|
||||
match self.input_mask() {
|
||||
InputMask::AwsS3 => 12,
|
||||
InputMask::Generic => 12,
|
||||
InputMask::Smb => 12,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,6 +1025,25 @@ impl AuthActivity {
|
||||
protocol, username, params.address, params.port
|
||||
)
|
||||
}
|
||||
#[cfg(unix)]
|
||||
ProtocolParams::Smb(params) => {
|
||||
let username: String = match params.username {
|
||||
None => String::default(),
|
||||
Some(u) => format!("{u}@"),
|
||||
};
|
||||
format!(
|
||||
"\\\\{username}{}:{}\\{}",
|
||||
params.address, params.port, params.share
|
||||
)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
ProtocolParams::Smb(params) => {
|
||||
let username: String = match params.username {
|
||||
None => String::default(),
|
||||
Some(u) => format!("{u}@"),
|
||||
};
|
||||
format!("\\\\{username}{}\\{}", params.address, params.share)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,6 +1097,40 @@ impl AuthActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_smb_view(&self) -> [Id; 4] {
|
||||
match self.app.focus() {
|
||||
Some(&Id::Address | &Id::Port | &Id::SmbShare | &Id::Username) => {
|
||||
[Id::Address, Id::Port, Id::SmbShare, Id::Username]
|
||||
}
|
||||
Some(&Id::Password) => [Id::Port, Id::SmbShare, Id::Username, Id::Password],
|
||||
Some(&Id::SmbWorkgroup) => [Id::SmbShare, Id::Username, Id::Password, Id::SmbWorkgroup],
|
||||
Some(&Id::RemoteDirectory) => [
|
||||
Id::Username,
|
||||
Id::Password,
|
||||
Id::SmbWorkgroup,
|
||||
Id::RemoteDirectory,
|
||||
],
|
||||
_ => [Id::Address, Id::Port, Id::SmbShare, Id::Username],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_smb_view(&self) -> [Id; 4] {
|
||||
match self.app.focus() {
|
||||
Some(&Id::Address | &Id::Password | &Id::SmbShare | &Id::Username) => {
|
||||
[Id::Address, Id::SmbShare, Id::Username, Id::Password]
|
||||
}
|
||||
Some(&Id::RemoteDirectory) => [
|
||||
Id::SmbShare,
|
||||
Id::Username,
|
||||
Id::Password,
|
||||
Id::RemoteDirectory,
|
||||
],
|
||||
_ => [Id::Address, Id::SmbShare, Id::Username, Id::Password],
|
||||
}
|
||||
}
|
||||
|
||||
fn init_global_listener(&mut self) {
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
assert!(self
|
||||
|
||||
@@ -3,7 +3,7 @@ use remotefs::fs::UnixPex;
|
||||
use super::{FileTransferActivity, LogLevel};
|
||||
|
||||
impl FileTransferActivity {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn action_local_chmod(&mut self, mode: UnixPex) {
|
||||
let files = self.get_local_selected_entries().get_files();
|
||||
|
||||
@@ -51,7 +51,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn action_find_local_chmod(&mut self, mode: UnixPex) {
|
||||
let files = self.get_found_selected_entries().get_files();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{FileTransferActivity, LogLevel, SelectedFile};
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Create symlink on localhost
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn action_local_symlink(&mut self, name: String) {
|
||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||
match self
|
||||
@@ -33,7 +33,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn action_local_symlink(&mut self, _name: String) {
|
||||
self.mount_error("Symlinks are not supported on Windows hosts");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use tuirealm::props::{
|
||||
Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TableBuilder, TextSpan,
|
||||
};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
use super::super::Browser;
|
||||
@@ -445,7 +445,7 @@ impl FileInfoPopup {
|
||||
.add_col(TextSpan::from("Last access time: "))
|
||||
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
|
||||
// User
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let username: String = match file.metadata().uid {
|
||||
Some(uid) => match get_user_by_uid(uid) {
|
||||
Some(user) => user.name().to_string_lossy().to_string(),
|
||||
@@ -453,10 +453,10 @@ impl FileInfoPopup {
|
||||
},
|
||||
None => String::from("0"),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let username: String = format!("{}", file.metadata().uid.unwrap_or(0));
|
||||
// Group
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
let group: String = match file.metadata().gid {
|
||||
Some(gid) => match get_group_by_gid(gid) {
|
||||
Some(group) => group.name().to_string_lossy().to_string(),
|
||||
@@ -464,7 +464,7 @@ impl FileInfoPopup {
|
||||
},
|
||||
None => String::from("0"),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
|
||||
texts
|
||||
.add_row()
|
||||
|
||||
@@ -111,6 +111,7 @@ impl FileTransferActivity {
|
||||
match &ft_params.params {
|
||||
ProtocolParams::Generic(params) => params.address.clone(),
|
||||
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
||||
ProtocolParams::Smb(params) => params.address.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +134,13 @@ impl FileTransferActivity {
|
||||
);
|
||||
format!("Connecting to {}…", params.bucket_name)
|
||||
}
|
||||
ProtocolParams::Smb(params) => {
|
||||
info!(
|
||||
"Client is not connected to remote; connecting to {}:{}",
|
||||
params.address, params.share
|
||||
);
|
||||
format!("Connecting to \\\\{}\\{}…", params.address, params.share)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,8 @@ pub struct FileTransferActivity {
|
||||
cache: Option<TempDir>,
|
||||
/// Fs watcher
|
||||
fswatcher: Option<FsWatcher>,
|
||||
/// conncted once
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -252,6 +254,7 @@ impl FileTransferActivity {
|
||||
None
|
||||
}
|
||||
},
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,14 +375,12 @@ impl Activity for FileTransferActivity {
|
||||
return;
|
||||
}
|
||||
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||
if !self.client.is_connected() && !self.app.mounted(&Id::FatalPopup) {
|
||||
if (!self.client.is_connected() || !self.connected) && !self.app.mounted(&Id::FatalPopup) {
|
||||
let ftparams = self.context().ft_params().unwrap();
|
||||
// print params
|
||||
let msg: String = Self::get_connection_msg(&ftparams.params);
|
||||
// Set init state to connecting popup
|
||||
self.mount_wait(msg.as_str());
|
||||
// Force ui draw
|
||||
self.view();
|
||||
self.mount_blocking_wait(msg.as_str());
|
||||
// Connect to remote
|
||||
self.connect();
|
||||
// Redraw
|
||||
|
||||
@@ -57,6 +57,7 @@ impl FileTransferActivity {
|
||||
// Connect to remote
|
||||
match self.client.connect() {
|
||||
Ok(Welcome { banner, .. }) => {
|
||||
self.connected = true;
|
||||
if let Some(banner) = banner {
|
||||
// Log welcome
|
||||
self.log(
|
||||
|
||||
@@ -36,13 +36,13 @@ impl FileTransferActivity {
|
||||
self.umount_chmod();
|
||||
self.mount_blocking_wait("Applying new file mode…");
|
||||
match self.browser.tab() {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::Local => self.action_local_chmod(mode),
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::FindLocal => self.action_find_local_chmod(mode),
|
||||
FileExplorerTab::Remote => self.action_remote_chmod(mode),
|
||||
FileExplorerTab::FindRemote => self.action_find_remote_chmod(mode),
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => {}
|
||||
}
|
||||
self.umount_wait();
|
||||
@@ -441,13 +441,13 @@ impl FileTransferActivity {
|
||||
}
|
||||
UiMsg::ShowChmodPopup => {
|
||||
let selected_file = match self.browser.tab() {
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::Local => self.get_local_selected_entries(),
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
FileExplorerTab::FindLocal => self.get_found_selected_entries(),
|
||||
FileExplorerTab::Remote => self.get_remote_selected_entries(),
|
||||
FileExplorerTab::FindRemote => self.get_found_selected_entries(),
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => SelectedFile::None,
|
||||
};
|
||||
if let Some(mode) = selected_file.unix_pex() {
|
||||
|
||||
@@ -11,6 +11,10 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use super::{ConfigMsg, Msg};
|
||||
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,
|
||||
};
|
||||
use crate::utils::parser::parse_bytesize;
|
||||
|
||||
// -- components
|
||||
@@ -63,16 +67,17 @@ impl DefaultProtocol {
|
||||
.color(Color::Cyan)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3"])
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"])
|
||||
.foreground(Color::Cyan)
|
||||
.rewind(true)
|
||||
.title("Default protocol", Alignment::Left)
|
||||
.value(match protocol {
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP,
|
||||
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
|
||||
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
|
||||
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
|
||||
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
|
||||
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ use crate::config::themes::Theme;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::theme_provider::ThemeProvider;
|
||||
|
||||
// radio
|
||||
const RADIO_PROTOCOL_SFTP: usize = 0;
|
||||
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;
|
||||
|
||||
// -- components
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
enum Id {
|
||||
|
||||
@@ -13,6 +13,10 @@ use tuirealm::{State, StateValue};
|
||||
use super::{components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout};
|
||||
use crate::explorer::GroupDirs;
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::ui::activities::setup::{
|
||||
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
|
||||
RADIO_PROTOCOL_SMB,
|
||||
};
|
||||
use crate::utils::fmt::fmt_bytes;
|
||||
|
||||
impl SetupActivity {
|
||||
@@ -268,10 +272,11 @@ impl SetupActivity {
|
||||
self.app.state(&Id::Config(IdConfig::DefaultProtocol))
|
||||
{
|
||||
let protocol: FileTransferProtocol = match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SCP => FileTransferProtocol::Scp,
|
||||
RADIO_PROTOCOL_FTP => FileTransferProtocol::Ftp(false),
|
||||
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
|
||||
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
|
||||
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
};
|
||||
self.config_mut().set_default_protocol(protocol);
|
||||
|
||||
Reference in New Issue
Block a user