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:
Christian Visintin
2023-05-13 15:00:16 +02:00
committed by GitHub
parent a13663e5e9
commit b7369162d2
54 changed files with 1256 additions and 154 deletions

View File

@@ -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(&params.share);
#[cfg(unix)]
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
}
}

View File

@@ -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),
)
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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,
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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");
}

View File

@@ -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()

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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(

View File

@@ -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() {

View File

@@ -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,
}),
}
}

View File

@@ -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 {

View File

@@ -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);