feat(sftp): introduce helper functions for directory listing and stat commands

- Added `list_dir_command` and `stat_command` functions to streamline the creation of shell commands for listing directories and retrieving file statistics with numeric owner and group information.
- Updated `stat_remote_properties` and `list_dir` methods to utilize the new command functions, improving code readability and maintainability.
- Enhanced unit tests to validate the new command functions and ensure correct parsing of directory entries.
This commit is contained in:
Kang committed 2026-09-01 14:23:59 +08:00
1 parent 8605ae7b25
commit f0267a6538
1 file changed
+65 -12
+65 -12
View File
@@ -323,6 +323,14 @@ fn split_ls_fields(line: &str) -> Option<(Vec<&str>, &str)> {
(cursor < bytes.len()).then(|| (fields, &line[cursor..]))
}
fn list_dir_command(path: &str) -> String {
format!("LC_ALL=C ls -la -n -- {}", sh_quote(path))
}
fn stat_command(path: &str) -> String {
format!("LC_ALL=C ls -lad -n -- {}", sh_quote(path))
}
fn parse_ls_line(line: &str) -> Option<FileEntry> {
let line = line.trim_start();
if line.trim_end().is_empty() || line.starts_with("total ") {
@@ -442,11 +450,7 @@ async fn stat_remote_properties(
ssh_handle: &Arc<SshConnectionHandles>,
path: &str,
) -> AppResult<FileProperties> {
let result = exec_command(
ssh_handle,
&format!("LC_ALL=C ls -lad -- {}", sh_quote(path)),
)
.await?;
let result = exec_command(ssh_handle, &stat_command(path)).await?;
if result.exit_code != Some(0) {
let stderr_text = String::from_utf8_lossy(&result.stderr);
return Err(AppError::Channel(format!(
@@ -888,9 +892,7 @@ impl RemoteFs for ScpNormalBackend {
async fn list_dir(&self, path: &str) -> AppResult<Vec<FileEntry>> {
let listing_path = remote_dir_listing_path(path);
let output = self
.exec_ok(&format!("LC_ALL=C ls -la -- {}", sh_quote(&listing_path)))
.await?;
let output = self.exec_ok(&list_dir_command(&listing_path)).await?;
let mut entries = Vec::new();
for line in output.lines() {
if let Some(mut entry) = parse_ls_line(line) {
@@ -908,9 +910,7 @@ impl RemoteFs for ScpNormalBackend {
}
async fn stat(&self, path: &str) -> AppResult<FileProperties> {
let output = self
.exec_ok(&format!("LC_ALL=C ls -lad -- {}", sh_quote(path)))
.await?;
let output = self.exec_ok(&stat_command(path)).await?;
let line = output
.lines()
.find(|l| !l.trim().is_empty() && !l.starts_with("total "))
@@ -1558,7 +1558,60 @@ impl RemoteFs for ScpNormalBackend {
#[cfg(test)]
mod tests {
use super::parse_ls_line_to_properties;
use super::{list_dir_command, parse_ls_line, parse_ls_line_to_properties, stat_command};
#[test]
fn ls_parser_handles_numeric_owner_and_group() {
let entry = parse_ls_line("-rw-r--r-- 1 1000 100513 203 Aug 3 16:37 .zshrc").unwrap();
assert_eq!(entry.name, ".zshrc");
assert_eq!(entry.owner, "1000");
assert_eq!(entry.group, "100513");
assert_eq!(entry.size, 203);
assert!(!entry.is_dir);
}
#[test]
fn ls_parser_keeps_hidden_directory_name() {
let entry = parse_ls_line("drwx------ 4 1000 100513 4096 Sep 1 09:31 .copilot").unwrap();
assert_eq!(entry.name, ".copilot");
assert!(entry.is_dir);
}
#[test]
fn ls_parser_keeps_spaces_in_file_name() {
let entry =
parse_ls_line("-rw-r--r-- 1 1000 100513 123 Sep 1 09:31 hello world.txt").unwrap();
assert_eq!(entry.name, "hello world.txt");
assert!(!entry.is_dir);
}
#[test]
fn ls_parser_keeps_spaces_in_directory_name() {
let entry = parse_ls_line("drwxr-xr-x 2 1000 100513 4096 Sep 1 09:31 My Folder").unwrap();
assert_eq!(entry.name, "My Folder");
assert!(entry.is_dir);
}
#[test]
fn ls_parser_strips_symlink_target_from_name() {
let entry =
parse_ls_line("lrwxrwxrwx 1 1000 100513 11 Aug 29 12:00 current -> releases/v2")
.unwrap();
assert_eq!(entry.name, "current");
assert!(entry.is_symlink);
}
#[test]
fn ls_commands_request_numeric_owner_and_group() {
assert_eq!(
list_dir_command("/home/user/My Folder"),
"LC_ALL=C ls -la -n -- '/home/user/My Folder'"
);
assert_eq!(
stat_command("/home/user/My Folder"),
"LC_ALL=C ls -lad -n -- '/home/user/My Folder'"
);
}
#[test]
fn ls_properties_parser_keeps_relative_symlink_target() {