ssh_commander_core/
file_entry.rs1use serde::Serialize;
9
10pub const FILE_TRANSFER_CHUNK_SIZE: usize = 32 * 1024;
13
14#[derive(Debug, Clone, Serialize)]
17pub struct FileEntry {
18 pub name: String,
19 pub size: u64,
20 pub modified: Option<String>,
22 pub modified_unix: Option<i64>,
27 pub permissions: Option<String>,
28 pub owner: Option<String>,
29 pub group: Option<String>,
30 pub file_type: FileEntryType,
31}
32
33pub type RemoteFileEntry = FileEntry;
35
36#[derive(Debug, Clone, Serialize, PartialEq)]
37pub enum FileEntryType {
38 File,
39 Directory,
40 Symlink,
41}
42
43pub fn format_unix_timestamp(secs: i64) -> String {
47 chrono::DateTime::<chrono::Utc>::from_timestamp(secs, 0)
48 .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
49 .unwrap_or_else(|| "invalid-timestamp".to_string())
50}
51
52#[allow(dead_code)]
57pub(crate) fn format_permissions(mode: u32) -> String {
58 let mut s = String::with_capacity(9);
59 let flags = [
60 (0o400, 'r'),
61 (0o200, 'w'),
62 (0o100, 'x'),
63 (0o040, 'r'),
64 (0o020, 'w'),
65 (0o010, 'x'),
66 (0o004, 'r'),
67 (0o002, 'w'),
68 (0o001, 'x'),
69 ];
70 for (bit, ch) in flags.iter() {
71 if mode & bit != 0 {
72 s.push(*ch);
73 } else {
74 s.push('-');
75 }
76 }
77 s
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_format_permissions_full() {
86 assert_eq!(format_permissions(0o777), "rwxrwxrwx");
87 }
88
89 #[test]
90 fn test_format_permissions_none() {
91 assert_eq!(format_permissions(0o000), "---------");
92 }
93
94 #[test]
95 fn test_format_permissions_typical_file() {
96 assert_eq!(format_permissions(0o644), "rw-r--r--");
97 }
98
99 #[test]
100 fn test_format_permissions_typical_dir() {
101 assert_eq!(format_permissions(0o755), "rwxr-xr-x");
102 }
103
104 #[test]
105 fn test_format_permissions_write_only() {
106 assert_eq!(format_permissions(0o200), "-w-------");
107 }
108
109 #[test]
110 fn format_unix_timestamp_epoch() {
111 assert_eq!(format_unix_timestamp(0), "1970-01-01 00:00:00");
112 }
113
114 #[test]
115 fn format_unix_timestamp_known_date() {
116 assert_eq!(format_unix_timestamp(1704067200), "2024-01-01 00:00:00");
118 }
119
120 #[test]
121 fn format_unix_timestamp_with_time() {
122 assert_eq!(format_unix_timestamp(961068645), "2000-06-15 11:30:45");
124 }
125
126 #[test]
127 fn format_unix_timestamp_post_2106() {
128 assert_eq!(format_unix_timestamp(7258118400), "2200-01-01 00:00:00");
131 }
132
133 #[test]
134 fn test_file_entry_type_serialization() {
135 let entry = RemoteFileEntry {
136 name: "test.txt".to_string(),
137 size: 1024,
138 modified: Some("2024-01-01 00:00:00".to_string()),
139 modified_unix: Some(1_704_067_200),
140 permissions: Some("rw-r--r--".to_string()),
141 owner: Some("501".to_string()),
142 group: Some("20".to_string()),
143 file_type: FileEntryType::File,
144 };
145 let json = serde_json::to_string(&entry).unwrap();
146 assert!(json.contains("\"name\":\"test.txt\""));
147 assert!(json.contains("\"size\":1024"));
148 assert!(json.contains("File"));
149 }
150
151 #[test]
152 fn test_directory_entry_serialization() {
153 let entry = RemoteFileEntry {
154 name: "mydir".to_string(),
155 size: 4096,
156 modified: None,
157 modified_unix: None,
158 permissions: Some("rwxr-xr-x".to_string()),
159 owner: None,
160 group: None,
161 file_type: FileEntryType::Directory,
162 };
163 let json = serde_json::to_string(&entry).unwrap();
164 assert!(json.contains("Directory"));
165 assert!(json.contains("\"modified\":null"));
166 }
167
168 #[test]
169 fn test_symlink_entry_serialization() {
170 let entry = RemoteFileEntry {
171 name: "link".to_string(),
172 size: 0,
173 modified: None,
174 modified_unix: None,
175 permissions: None,
176 owner: None,
177 group: None,
178 file_type: FileEntryType::Symlink,
179 };
180 let json = serde_json::to_string(&entry).unwrap();
181 assert!(json.contains("Symlink"));
182 }
183}