1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum OperationType {
9 Encrypt,
11 Decrypt,
13 HybridEncrypt,
15 HybridDecrypt,
17}
18
19impl std::fmt::Display for OperationType {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 Self::Encrypt => write!(f, "Encrypt"),
23 Self::Decrypt => write!(f, "Decrypt"),
24 Self::HybridEncrypt => write!(f, "Hybrid Encrypt"),
25 Self::HybridDecrypt => write!(f, "Hybrid Decrypt"),
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub enum TargetType {
33 File,
35 Directory,
37}
38
39impl std::fmt::Display for TargetType {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::File => write!(f, "File"),
43 Self::Directory => write!(f, "Directory"),
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct OperationParams {
51 pub operation: OperationType,
53 pub target_type: TargetType,
55 pub source: PathBuf,
57 pub destination: Option<PathBuf>,
59 pub compress: bool,
61 pub show_progress: bool,
63 pub buffer_size: usize,
65 pub preserve_filename: bool,
67 pub delete_source: bool,
69 pub verify_checksum: bool,
71 pub public_key_path: Option<PathBuf>,
73 pub private_key_path: Option<PathBuf>,
75}
76
77impl OperationParams {
78 pub fn new(
80 operation: OperationType,
81 target_type: TargetType,
82 source: PathBuf,
83 ) -> Self {
84 Self {
85 operation,
86 target_type,
87 source,
88 destination: None,
89 compress: false,
90 show_progress: true,
91 buffer_size: 64 * 1024, preserve_filename: true, delete_source: false, verify_checksum: true, public_key_path: None, private_key_path: None, }
98 }
99
100 pub fn with_destination(mut self, destination: PathBuf) -> Self {
102 self.destination = Some(destination);
103 self
104 }
105
106 pub fn with_compression(mut self, compress: bool) -> Self {
108 self.compress = compress;
109 self
110 }
111
112 pub fn with_progress(mut self, show_progress: bool) -> Self {
114 self.show_progress = show_progress;
115 self
116 }
117
118 pub fn with_buffer_size(mut self, buffer_size: usize) -> Self {
120 self.buffer_size = buffer_size;
121 self
122 }
123
124 pub fn with_preserve_filename(mut self, preserve_filename: bool) -> Self {
126 self.preserve_filename = preserve_filename;
127 self
128 }
129
130 pub fn with_delete_source(mut self, delete_source: bool) -> Self {
132 self.delete_source = delete_source;
133 self
134 }
135
136 pub fn with_verify_checksum(mut self, verify_checksum: bool) -> Self {
138 self.verify_checksum = verify_checksum;
139 self
140 }
141
142 pub fn with_public_key_path(mut self, public_key_path: PathBuf) -> Self {
144 self.public_key_path = Some(public_key_path);
145 self
146 }
147
148 pub fn with_private_key_path(mut self, private_key_path: PathBuf) -> Self {
150 self.private_key_path = Some(private_key_path);
151 self
152 }
153
154 pub fn get_destination(&self) -> PathBuf {
156 if let Some(dest) = &self.destination {
157 dest.clone()
158 } else {
159 match self.operation {
160 OperationType::Encrypt => {
161 if self.preserve_filename {
162 let original_ext = self.source.extension()
164 .and_then(|s| s.to_str())
165 .unwrap_or("")
166 .to_string();
167
168 if self.compress {
169 if original_ext.is_empty() {
170 self.source.with_extension("sf")
171 } else {
172 self.source.with_extension(format!("{}.sf", original_ext))
173 }
174 } else {
175 if original_ext.is_empty() {
176 self.source.with_extension("sf")
177 } else {
178 self.source.with_extension(format!("{}.sf", original_ext))
179 }
180 }
181 } else {
182 if self.compress {
184 self.source.with_extension("sf.gz")
185 } else {
186 self.source.with_extension("sf")
187 }
188 }
189 }
190 OperationType::HybridEncrypt => {
191 if self.preserve_filename {
193 let original_ext = self.source.extension()
194 .and_then(|s| s.to_str())
195 .unwrap_or("")
196 .to_string();
197
198 if original_ext.is_empty() {
199 self.source.with_extension("hsf")
200 } else {
201 self.source.with_extension(format!("{}.hsf", original_ext))
202 }
203 } else {
204 self.source.with_extension("hsf")
205 }
206 }
207 OperationType::Decrypt => {
208 if self.preserve_filename {
209 let source_str = self.source.to_string_lossy();
212 if source_str.ends_with(".sf.gz") {
213 PathBuf::from(source_str.trim_end_matches(".sf.gz"))
214 } else if source_str.ends_with(".sf") {
215 PathBuf::from(source_str.trim_end_matches(".sf"))
216 } else {
217 self.source.with_extension("decrypted")
218 }
219 } else {
220 let source_str = self.source.to_string_lossy();
222 if source_str.ends_with(".sf.gz") {
223 PathBuf::from(source_str.trim_end_matches(".sf.gz"))
224 } else if source_str.ends_with(".sf") {
225 PathBuf::from(source_str.trim_end_matches(".sf"))
226 } else {
227 self.source.with_extension("decrypted")
228 }
229 }
230 }
231 OperationType::HybridDecrypt => {
232 let source_str = self.source.to_string_lossy();
234 if source_str.ends_with(".hsf") {
235 PathBuf::from(source_str.trim_end_matches(".hsf"))
236 } else {
237 self.source.with_extension("decrypted")
238 }
239 }
240 }
241 }
242 }
243}
244
245#[derive(Debug, Clone)]
247pub struct OperationResult {
248 pub success: bool,
250 pub source: PathBuf,
252 pub destination: PathBuf,
254 pub bytes_processed: u64,
256 pub error: Option<String>,
258 pub operation: OperationType,
260 pub compressed: bool,
262 pub original_filename: Option<String>,
264 pub checksum_verified: Option<bool>,
266}
267
268impl OperationResult {
269 pub fn success(
271 source: PathBuf,
272 destination: PathBuf,
273 bytes_processed: u64,
274 operation: OperationType,
275 compressed: bool,
276 ) -> Self {
277 Self {
278 success: true,
279 source,
280 destination,
281 bytes_processed,
282 error: None,
283 operation,
284 compressed,
285 original_filename: None,
286 checksum_verified: None,
287 }
288 }
289
290 pub fn success_with_metadata(
292 source: PathBuf,
293 destination: PathBuf,
294 bytes_processed: u64,
295 operation: OperationType,
296 compressed: bool,
297 original_filename: Option<String>,
298 checksum_verified: Option<bool>,
299 ) -> Self {
300 Self {
301 success: true,
302 source,
303 destination,
304 bytes_processed,
305 error: None,
306 operation,
307 compressed,
308 original_filename,
309 checksum_verified,
310 }
311 }
312
313 pub fn failure(
315 source: PathBuf,
316 operation: OperationType,
317 error: String,
318 ) -> Self {
319 Self {
320 success: false,
321 source,
322 destination: PathBuf::new(),
323 bytes_processed: 0,
324 error: Some(error),
325 operation,
326 compressed: false,
327 original_filename: None,
328 checksum_verified: None,
329 }
330 }
331}
332
333impl std::fmt::Display for OperationResult {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 if self.success {
336 let mut result = format!(
337 "✓ {} {} -> {} ({} bytes{})",
338 self.operation,
339 self.source.display(),
340 self.destination.display(),
341 self.bytes_processed,
342 if self.compressed { ", compressed" } else { "" }
343 );
344
345 if let Some(filename) = &self.original_filename {
346 result.push_str(&format!(", original: {}", filename));
347 }
348
349 if let Some(verified) = self.checksum_verified {
350 result.push_str(&format!(", checksum: {}", if verified { "✓" } else { "✗" }));
351 }
352
353 write!(f, "{}", result)
354 } else {
355 write!(
356 f,
357 "✗ {} {} failed: {}",
358 self.operation,
359 self.source.display(),
360 self.error.as_ref().unwrap_or(&"Unknown error".to_string())
361 )
362 }
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn test_operation_params() {
372 let params = OperationParams::new(
373 OperationType::Encrypt,
374 TargetType::File,
375 PathBuf::from("test.txt"),
376 );
377
378 assert_eq!(params.operation, OperationType::Encrypt);
379 assert_eq!(params.target_type, TargetType::File);
380 assert_eq!(params.source, PathBuf::from("test.txt"));
381 assert!(!params.compress);
382 assert!(params.show_progress);
383 }
384
385 #[test]
386 fn test_destination_generation() {
387 let params = OperationParams::new(
389 OperationType::Encrypt,
390 TargetType::File,
391 PathBuf::from("test.txt"),
392 );
393 assert_eq!(params.get_destination(), PathBuf::from("test.txt.sf"));
394
395 let params = params.with_compression(true);
397 assert_eq!(params.get_destination(), PathBuf::from("test.txt.sf"));
398
399 let params = params.with_preserve_filename(false);
401 assert_eq!(params.get_destination(), PathBuf::from("test.sf.gz"));
402
403 let params = OperationParams::new(
405 OperationType::Decrypt,
406 TargetType::File,
407 PathBuf::from("test.txt.sf"),
408 );
409 assert_eq!(params.get_destination(), PathBuf::from("test.txt"));
410
411 let params = OperationParams::new(
413 OperationType::Decrypt,
414 TargetType::File,
415 PathBuf::from("test.sf.gz"),
416 );
417 assert_eq!(params.get_destination(), PathBuf::from("test"));
418 }
419
420 #[test]
421 fn test_operation_result() {
422 let success = OperationResult::success(
423 PathBuf::from("source.txt"),
424 PathBuf::from("dest.sf"),
425 1024,
426 OperationType::Encrypt,
427 false,
428 );
429
430 assert!(success.success);
431 assert_eq!(success.bytes_processed, 1024);
432 assert!(success.error.is_none());
433
434 let failure = OperationResult::failure(
435 PathBuf::from("source.txt"),
436 OperationType::Encrypt,
437 "Test error".to_string(),
438 );
439
440 assert!(!failure.success);
441 assert_eq!(failure.bytes_processed, 0);
442 assert_eq!(failure.error.as_ref().unwrap(), "Test error");
443 }
444}