FsEntry::*::symlink is now a Option<Box<FsEntry>>; this improved symlinks, which gave errors some times

This commit is contained in:
ChristianVisintin
2020-12-12 16:26:03 +01:00
parent 23ac5089a7
commit f73e43304e
8 changed files with 108 additions and 88 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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