agentmux_srv\backend/
rpc_fileutil.rs1#![allow(dead_code)]
2use serde::{Deserialize, Serialize};
13
14use super::storage::filestore::{FileMeta, FileOpts, WaveFile};
15
16pub const FILE_PATH_PATTERN: &str = "wavefile://";
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct FileInfo {
23 pub path: String,
25
26 pub name: String,
28
29 pub size: i64,
31
32 #[serde(default, skip_serializing_if = "is_zero")]
34 pub modts: i64,
35
36 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
38 pub isdir: bool,
39
40 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub mimetype: Option<String>,
43
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub opts: Option<FileOpts>,
47
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub meta: Option<FileMeta>,
51
52 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
54 pub readonly: bool,
55}
56
57fn is_zero(v: &i64) -> bool {
58 *v == 0
59}
60
61pub fn wave_file_to_file_info(file: &WaveFile) -> FileInfo {
63 let path = format!(
64 "{}{}/{}",
65 FILE_PATH_PATTERN, file.zoneid, file.name
66 );
67 FileInfo {
68 path,
69 name: file.name.clone(),
70 size: file.size,
71 modts: file.modts,
72 isdir: false,
73 mimetype: None,
74 opts: Some(file.opts.clone()),
75 meta: if file.meta.is_empty() {
76 None
77 } else {
78 Some(file.meta.clone())
79 },
80 readonly: false,
81 }
82}
83
84pub fn wave_file_list_to_file_info_list(files: &[WaveFile]) -> Vec<FileInfo> {
86 files.iter().map(wave_file_to_file_info).collect()
87}
88
89pub fn parse_wave_file_path(path: &str) -> Option<(String, String)> {
92 let rest = path.strip_prefix(FILE_PATH_PATTERN)?;
93 let slash_pos = rest.find('/')?;
94 let zone_id = &rest[..slash_pos];
95 let name = &rest[slash_pos + 1..];
96 if zone_id.is_empty() || name.is_empty() {
97 return None;
98 }
99 Some((zone_id.to_string(), name.to_string()))
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use std::collections::HashMap;
106
107 fn make_test_file() -> WaveFile {
108 WaveFile {
109 zoneid: "zone-abc".to_string(),
110 name: "test.txt".to_string(),
111 size: 1024,
112 createdts: 1700000000000,
113 modts: 1700000001000,
114 opts: FileOpts::default(),
115 meta: HashMap::new(),
116 }
117 }
118
119 #[test]
120 fn test_wave_file_to_file_info() {
121 let file = make_test_file();
122 let info = wave_file_to_file_info(&file);
123 assert_eq!(info.path, "wavefile://zone-abc/test.txt");
124 assert_eq!(info.name, "test.txt");
125 assert_eq!(info.size, 1024);
126 assert_eq!(info.modts, 1700000001000);
127 assert!(!info.isdir);
128 assert!(!info.readonly);
129 assert!(info.meta.is_none()); }
131
132 #[test]
133 fn test_wave_file_to_file_info_with_meta() {
134 let mut file = make_test_file();
135 file.meta
136 .insert("custom".to_string(), serde_json::json!("value"));
137 let info = wave_file_to_file_info(&file);
138 assert!(info.meta.is_some());
139 assert_eq!(
140 info.meta.unwrap().get("custom"),
141 Some(&serde_json::json!("value"))
142 );
143 }
144
145 #[test]
146 fn test_wave_file_to_file_info_with_opts() {
147 let mut file = make_test_file();
148 file.opts.circular = true;
149 file.opts.maxsize = 65536;
150 let info = wave_file_to_file_info(&file);
151 let opts = info.opts.unwrap();
152 assert!(opts.circular);
153 assert_eq!(opts.maxsize, 65536);
154 }
155
156 #[test]
157 fn test_wave_file_list_to_file_info_list() {
158 let files = vec![make_test_file(), make_test_file()];
159 let infos = wave_file_list_to_file_info_list(&files);
160 assert_eq!(infos.len(), 2);
161 }
162
163 #[test]
164 fn test_wave_file_list_empty() {
165 let infos = wave_file_list_to_file_info_list(&[]);
166 assert!(infos.is_empty());
167 }
168
169 #[test]
170 fn test_file_info_serialization() {
171 let info = FileInfo {
172 path: "wavefile://z1/f1".to_string(),
173 name: "f1".to_string(),
174 size: 100,
175 modts: 0,
176 isdir: false,
177 mimetype: None,
178 opts: None,
179 meta: None,
180 readonly: false,
181 };
182 let json = serde_json::to_string(&info).unwrap();
183 assert!(!json.contains("modts"));
185 assert!(!json.contains("isdir"));
187 assert!(!json.contains("opts"));
189 }
190
191 #[test]
192 fn test_file_info_deserialization() {
193 let json = r#"{"path":"wavefile://z/f","name":"f","size":50,"modts":123,"isdir":false}"#;
194 let info: FileInfo = serde_json::from_str(json).unwrap();
195 assert_eq!(info.path, "wavefile://z/f");
196 assert_eq!(info.size, 50);
197 assert_eq!(info.modts, 123);
198 }
199
200 #[test]
201 fn test_parse_wave_file_path() {
202 let result = parse_wave_file_path("wavefile://zone-abc/test.txt");
203 assert_eq!(result, Some(("zone-abc".to_string(), "test.txt".to_string())));
204 }
205
206 #[test]
207 fn test_parse_wave_file_path_nested() {
208 let result = parse_wave_file_path("wavefile://zone/dir/file.txt");
209 assert_eq!(
210 result,
211 Some(("zone".to_string(), "dir/file.txt".to_string()))
212 );
213 }
214
215 #[test]
216 fn test_parse_wave_file_path_invalid() {
217 assert!(parse_wave_file_path("").is_none());
218 assert!(parse_wave_file_path("file:///tmp/foo").is_none());
219 assert!(parse_wave_file_path("wavefile://").is_none());
220 assert!(parse_wave_file_path("wavefile://zone/").is_none());
221 assert!(parse_wave_file_path("wavefile:///file").is_none());
222 }
223}