mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
FsEntry::*::symlink is now a Option<Box<FsEntry>>; this improved symlinks, which gave errors some times
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
Released on ??
|
Released on ??
|
||||||
|
|
||||||
- General performance and code improvements
|
- General performance and code improvements
|
||||||
|
- Improved symlinks management
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
|
|||||||
@@ -613,7 +613,7 @@ mod tests {
|
|||||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||||
assert_eq!(file.name, String::from("omar.txt"));
|
assert_eq!(file.name, String::from("omar.txt"));
|
||||||
assert_eq!(file.size, 8192);
|
assert_eq!(file.size, 8192);
|
||||||
assert_eq!(file.symlink, None);
|
assert!(file.symlink.is_none());
|
||||||
assert_eq!(file.user, None);
|
assert_eq!(file.user, None);
|
||||||
assert_eq!(file.group, None);
|
assert_eq!(file.group, None);
|
||||||
assert_eq!(file.unix_pex.unwrap(), (6, 6, 4));
|
assert_eq!(file.unix_pex.unwrap(), (6, 6, 4));
|
||||||
@@ -653,7 +653,7 @@ mod tests {
|
|||||||
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt"));
|
||||||
assert_eq!(file.name, String::from("omar.txt"));
|
assert_eq!(file.name, String::from("omar.txt"));
|
||||||
assert_eq!(file.size, 4096);
|
assert_eq!(file.size, 4096);
|
||||||
assert_eq!(file.symlink, None);
|
assert!(file.symlink.is_none());
|
||||||
assert_eq!(file.user, Some(0));
|
assert_eq!(file.user, Some(0));
|
||||||
assert_eq!(file.group, Some(9));
|
assert_eq!(file.group, Some(9));
|
||||||
assert_eq!(file.unix_pex.unwrap(), (7, 5, 5));
|
assert_eq!(file.unix_pex.unwrap(), (7, 5, 5));
|
||||||
@@ -692,7 +692,7 @@ mod tests {
|
|||||||
if let FsEntry::Directory(dir) = fs_entry {
|
if let FsEntry::Directory(dir) = fs_entry {
|
||||||
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs"));
|
||||||
assert_eq!(dir.name, String::from("docs"));
|
assert_eq!(dir.name, String::from("docs"));
|
||||||
assert_eq!(dir.symlink, None);
|
assert!(dir.symlink.is_none());
|
||||||
assert_eq!(dir.user, Some(0));
|
assert_eq!(dir.user, Some(0));
|
||||||
assert_eq!(dir.group, Some(9));
|
assert_eq!(dir.group, Some(9));
|
||||||
assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5));
|
assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5));
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ impl ScpFileTransfer {
|
|||||||
/// ### parse_ls_output
|
/// ### parse_ls_output
|
||||||
///
|
///
|
||||||
/// Parse a line of `ls -l` output and tokenize the output into a `FsEntry`
|
/// Parse a line of `ls -l` output and tokenize the output into a `FsEntry`
|
||||||
fn parse_ls_output(&self, path: &Path, line: &str) -> Result<FsEntry, ()> {
|
fn parse_ls_output(&mut self, path: &Path, line: &str) -> Result<FsEntry, ()> {
|
||||||
// Prepare list regex
|
// Prepare list regex
|
||||||
// NOTE: about this damn regex <https://stackoverflow.com/questions/32480890/is-there-a-regex-to-parse-the-values-from-an-ftp-directory-listing>
|
// NOTE: about this damn regex <https://stackoverflow.com/questions/32480890/is-there-a-regex-to-parse-the-values-from-an-ftp-directory-listing>
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -179,6 +179,14 @@ impl ScpFileTransfer {
|
|||||||
true => self.get_name_and_link(metadata.get(8).unwrap().as_str()),
|
true => self.get_name_and_link(metadata.get(8).unwrap().as_str()),
|
||||||
false => (String::from(metadata.get(8).unwrap().as_str()), None),
|
false => (String::from(metadata.get(8).unwrap().as_str()), None),
|
||||||
};
|
};
|
||||||
|
// Get symlink
|
||||||
|
let symlink: Option<Box<FsEntry>> = match symlink_path {
|
||||||
|
None => None,
|
||||||
|
Some(p) => match self.stat(p.as_path()) {
|
||||||
|
Ok(e) => Some(Box::new(e)),
|
||||||
|
Err(_) => None, // Ignore errors
|
||||||
|
}
|
||||||
|
};
|
||||||
// Check if file_name is '.' or '..'
|
// Check if file_name is '.' or '..'
|
||||||
if file_name.as_str() == "." || file_name.as_str() == ".." {
|
if file_name.as_str() == "." || file_name.as_str() == ".." {
|
||||||
return Err(());
|
return Err(());
|
||||||
@@ -199,7 +207,7 @@ impl ScpFileTransfer {
|
|||||||
last_access_time: mtime,
|
last_access_time: mtime,
|
||||||
creation_time: mtime,
|
creation_time: mtime,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
symlink: symlink_path,
|
symlink,
|
||||||
user: uid,
|
user: uid,
|
||||||
group: gid,
|
group: gid,
|
||||||
unix_pex: Some(unix_pex),
|
unix_pex: Some(unix_pex),
|
||||||
@@ -213,7 +221,7 @@ impl ScpFileTransfer {
|
|||||||
size: filesize,
|
size: filesize,
|
||||||
ftype: extension,
|
ftype: extension,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
symlink: symlink_path,
|
symlink,
|
||||||
user: uid,
|
user: uid,
|
||||||
group: gid,
|
group: gid,
|
||||||
unix_pex: Some(unix_pex),
|
unix_pex: Some(unix_pex),
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ impl SftpFileTransfer {
|
|||||||
/// ### make_fsentry
|
/// ### make_fsentry
|
||||||
///
|
///
|
||||||
/// Make fsentry from path and metadata
|
/// Make fsentry from path and metadata
|
||||||
fn make_fsentry(&self, path: &Path, metadata: &FileStat) -> FsEntry {
|
fn make_fsentry(&mut self, path: &Path, metadata: &FileStat) -> FsEntry {
|
||||||
// Get common parameters
|
// Get common parameters
|
||||||
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
||||||
let file_type: Option<String> = match path.extension() {
|
let file_type: Option<String> = match path.extension() {
|
||||||
@@ -149,11 +149,14 @@ impl SftpFileTransfer {
|
|||||||
.unwrap_or(SystemTime::UNIX_EPOCH);
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
// Check if symlink
|
// Check if symlink
|
||||||
let is_symlink: bool = metadata.file_type().is_symlink();
|
let is_symlink: bool = metadata.file_type().is_symlink();
|
||||||
let symlink: Option<PathBuf> = match is_symlink {
|
let symlink: Option<Box<FsEntry>> = match is_symlink {
|
||||||
true => {
|
true => {
|
||||||
// Read symlink
|
// Read symlink
|
||||||
match self.sftp.as_ref().unwrap().readlink(path) {
|
match self.sftp.as_ref().unwrap().readlink(path) {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => match self.stat(p.as_path()) {
|
||||||
|
Ok(entry) => Some(Box::new(entry)),
|
||||||
|
Err(_) => None, // Ignore errors
|
||||||
|
},
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub struct FsDirectory {
|
|||||||
pub last_access_time: SystemTime,
|
pub last_access_time: SystemTime,
|
||||||
pub creation_time: SystemTime,
|
pub creation_time: SystemTime,
|
||||||
pub readonly: bool,
|
pub readonly: bool,
|
||||||
pub symlink: Option<PathBuf>, // UNIX only
|
pub symlink: Option<Box<FsEntry>>, // UNIX only
|
||||||
pub user: Option<u32>, // UNIX only
|
pub user: Option<u32>, // UNIX only
|
||||||
pub group: Option<u32>, // UNIX only
|
pub group: Option<u32>, // UNIX only
|
||||||
pub unix_pex: Option<(u8, u8, u8)>, // UNIX only
|
pub unix_pex: Option<(u8, u8, u8)>, // UNIX only
|
||||||
@@ -77,12 +77,48 @@ pub struct FsFile {
|
|||||||
pub size: usize,
|
pub size: usize,
|
||||||
pub ftype: Option<String>, // File type
|
pub ftype: Option<String>, // File type
|
||||||
pub readonly: bool,
|
pub readonly: bool,
|
||||||
pub symlink: Option<PathBuf>, // UNIX only
|
pub symlink: Option<Box<FsEntry>>, // UNIX only
|
||||||
pub user: Option<u32>, // UNIX only
|
pub user: Option<u32>, // UNIX only
|
||||||
pub group: Option<u32>, // UNIX only
|
pub group: Option<u32>, // UNIX only
|
||||||
pub unix_pex: Option<(u8, u8, u8)>, // UNIX only
|
pub unix_pex: Option<(u8, u8, u8)>, // UNIX only
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FsEntry {
|
||||||
|
/// ### get_abs_path
|
||||||
|
///
|
||||||
|
/// Get absolute path from `FsEntry`
|
||||||
|
pub fn get_abs_path(&self) -> PathBuf {
|
||||||
|
match self {
|
||||||
|
FsEntry::Directory(dir) => dir.abs_path.clone(),
|
||||||
|
FsEntry::File(file) => file.abs_path.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### get_realfile
|
||||||
|
///
|
||||||
|
/// Return the real file pointed by a `FsEntry`
|
||||||
|
pub fn get_realfile(&self) -> Option<FsEntry> {
|
||||||
|
match self {
|
||||||
|
FsEntry::Directory(dir) => match &dir.symlink {
|
||||||
|
Some(symlink) => match symlink.get_realfile() {
|
||||||
|
// Recursive call
|
||||||
|
Some(e) => e.get_realfile(), // Recursive call
|
||||||
|
None => Some(*symlink.clone()),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
FsEntry::File(file) => match &file.symlink {
|
||||||
|
Some(symlink) => match symlink.get_realfile() {
|
||||||
|
// Recursive call
|
||||||
|
Some(e) => e.get_realfile(), // Recursive call
|
||||||
|
None => Some(*symlink.clone()),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for FsEntry {
|
impl std::fmt::Display for FsEntry {
|
||||||
/// ### fmt_ls
|
/// ### fmt_ls
|
||||||
///
|
///
|
||||||
@@ -279,3 +315,5 @@ impl std::fmt::Display for FsEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: tests
|
||||||
|
|||||||
@@ -281,7 +281,10 @@ impl Localhost {
|
|||||||
creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH),
|
creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH),
|
||||||
readonly: attr.permissions().readonly(),
|
readonly: attr.permissions().readonly(),
|
||||||
symlink: match fs::read_link(path) {
|
symlink: match fs::read_link(path) {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => match self.stat(p.as_path()) {
|
||||||
|
Ok(entry) => Some(Box::new(entry)),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
},
|
},
|
||||||
user: Some(attr.uid()),
|
user: Some(attr.uid()),
|
||||||
@@ -304,8 +307,11 @@ impl Localhost {
|
|||||||
size: attr.len() as usize,
|
size: attr.len() as usize,
|
||||||
ftype: extension,
|
ftype: extension,
|
||||||
symlink: match fs::read_link(path) {
|
symlink: match fs::read_link(path) {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => match self.stat(p.as_path()) {
|
||||||
Err(_) => None,
|
Ok(entry) => Some(Box::new(entry)),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
Err(_) => None, // Ignore errors
|
||||||
},
|
},
|
||||||
user: Some(attr.uid()),
|
user: Some(attr.uid()),
|
||||||
group: Some(attr.gid()),
|
group: Some(attr.gid()),
|
||||||
@@ -336,7 +342,10 @@ impl Localhost {
|
|||||||
creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH),
|
creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH),
|
||||||
readonly: attr.permissions().readonly(),
|
readonly: attr.permissions().readonly(),
|
||||||
symlink: match fs::read_link(path) {
|
symlink: match fs::read_link(path) {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => match self.stat(p.as_path()) {
|
||||||
|
Ok(entry) => Some(Box::new(entry)),
|
||||||
|
Err(_) => None, // Ignore errors
|
||||||
|
},
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
},
|
},
|
||||||
user: None,
|
user: None,
|
||||||
@@ -359,7 +368,10 @@ impl Localhost {
|
|||||||
size: attr.len() as usize,
|
size: attr.len() as usize,
|
||||||
ftype: extension,
|
ftype: extension,
|
||||||
symlink: match fs::read_link(path) {
|
symlink: match fs::read_link(path) {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => match self.stat(p.as_path()) {
|
||||||
|
Ok(entry) => Some(Box::new(entry)),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
},
|
},
|
||||||
user: None,
|
user: None,
|
||||||
@@ -618,7 +630,7 @@ mod tests {
|
|||||||
assert!(file_0.symlink.is_none());
|
assert!(file_0.symlink.is_none());
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*file_0.symlink.as_ref().unwrap(),
|
*file_0.symlink.as_ref().unwrap().get_abs_path(),
|
||||||
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -631,7 +643,7 @@ mod tests {
|
|||||||
FsEntry::File(file_1) => {
|
FsEntry::File(file_1) => {
|
||||||
if file_1.name == String::from("bar.txt") {
|
if file_1.name == String::from("bar.txt") {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*file_1.symlink.as_ref().unwrap(),
|
*file_1.symlink.as_ref().unwrap().get_abs_path(),
|
||||||
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -118,43 +118,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FsEntry::File(file) => {
|
FsEntry::File(file) => {
|
||||||
// Check if symlink
|
// Check if symlink
|
||||||
if let Some(realpath) = &file.symlink {
|
if let Some(symlink_entry) = &file.symlink {
|
||||||
// Stat realpath
|
// If symlink entry is a directory, go to directory
|
||||||
match self
|
if let FsEntry::Directory(dir) = &**symlink_entry {
|
||||||
.context
|
self.local_changedir(dir.abs_path.as_path(), true)
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.local
|
|
||||||
.stat(realpath.as_path())
|
|
||||||
{
|
|
||||||
Ok(real_file) => {
|
|
||||||
// If real file is a directory, enter directory
|
|
||||||
if let FsEntry::Directory(real_dir) = real_file {
|
|
||||||
self.local_changedir(
|
|
||||||
real_dir.abs_path.as_path(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.log(
|
|
||||||
LogLevel::Error,
|
|
||||||
format!(
|
|
||||||
"Failed to stat file \"{}\": {}",
|
|
||||||
realpath.display(),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
.as_ref(),
|
|
||||||
);
|
|
||||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
|
||||||
Color::Red,
|
|
||||||
format!(
|
|
||||||
"Failed to stat file \"{}\": {}",
|
|
||||||
realpath.display(),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,37 +286,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FsEntry::File(file) => {
|
FsEntry::File(file) => {
|
||||||
// Check if symlink
|
// Check if symlink
|
||||||
if let Some(realpath) = &file.symlink {
|
if let Some(symlink_entry) = &file.symlink {
|
||||||
// Stat realpath
|
// If symlink entry is a directory, go to directory
|
||||||
match self.client.stat(realpath.as_path()) {
|
if let FsEntry::Directory(dir) = &**symlink_entry {
|
||||||
Ok(real_file) => {
|
self.remote_changedir(dir.abs_path.as_path(), true)
|
||||||
// If real file is a directory, enter directory
|
|
||||||
if let FsEntry::Directory(real_dir) = real_file {
|
|
||||||
self.remote_changedir(
|
|
||||||
real_dir.abs_path.as_path(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.log(
|
|
||||||
LogLevel::Error,
|
|
||||||
format!(
|
|
||||||
"Failed to stat file \"{}\": {}",
|
|
||||||
realpath.display(),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
.as_ref(),
|
|
||||||
);
|
|
||||||
self.input_mode = InputMode::Popup(PopupType::Alert(
|
|
||||||
Color::Red,
|
|
||||||
format!(
|
|
||||||
"Failed to stat file \"{}\": {}",
|
|
||||||
realpath.display(),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -473,7 +473,16 @@ impl FileTransferActivity {
|
|||||||
Span::styled(
|
Span::styled(
|
||||||
match &dir.symlink {
|
match &dir.symlink {
|
||||||
Some(symlink) => {
|
Some(symlink) => {
|
||||||
format!("{} => {}", dir.abs_path.display(), symlink.display())
|
// Get symlink path
|
||||||
|
let symlink_path: &Path = match &**symlink {
|
||||||
|
FsEntry::Directory(s_dir) => s_dir.abs_path.as_path(),
|
||||||
|
FsEntry::File(s_file) => s_file.abs_path.as_path(),
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{} => {}",
|
||||||
|
dir.abs_path.display(),
|
||||||
|
symlink_path.display()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => dir.abs_path.to_string_lossy().to_string(),
|
None => dir.abs_path.to_string_lossy().to_string(),
|
||||||
},
|
},
|
||||||
@@ -560,7 +569,16 @@ impl FileTransferActivity {
|
|||||||
Span::styled(
|
Span::styled(
|
||||||
match &file.symlink {
|
match &file.symlink {
|
||||||
Some(symlink) => {
|
Some(symlink) => {
|
||||||
format!("{} => {}", file.abs_path.display(), symlink.display())
|
// Get symlink path
|
||||||
|
let symlink_path: &Path = match &**symlink {
|
||||||
|
FsEntry::Directory(s_dir) => s_dir.abs_path.as_path(),
|
||||||
|
FsEntry::File(s_file) => s_file.abs_path.as_path(),
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{} => {}",
|
||||||
|
file.abs_path.display(),
|
||||||
|
symlink_path.display()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => file.abs_path.to_string_lossy().to_string(),
|
None => file.abs_path.to_string_lossy().to_string(),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user