1use std::path::Path;
7
8use tracing::{info, warn};
9
10#[derive(Debug)]
12pub struct Par2Result {
13 pub success: bool,
14 pub blocks_needed: u32,
15 pub blocks_available: u32,
16 pub repaired: bool,
17 pub message: String,
18}
19
20pub fn par2_repair_blocking(par2_file: &Path) -> anyhow::Result<Par2Result> {
25 let dir = par2_file.parent().unwrap_or(Path::new("."));
26
27 let file_set = rust_par2::parse(par2_file).map_err(|e| anyhow::anyhow!("PAR2 parse: {e}"))?;
28 let verify_result = rust_par2::verify(&file_set, dir);
29
30 if verify_result.all_correct() {
31 return Ok(Par2Result {
32 success: true,
33 blocks_needed: 0,
34 blocks_available: verify_result.recovery_blocks_available,
35 repaired: false,
36 message: "All files correct".to_string(),
37 });
38 }
39
40 let blocks_needed = verify_result.blocks_needed();
41 let blocks_available = verify_result.recovery_blocks_available;
42
43 info!(
44 blocks_needed,
45 blocks_available,
46 damaged = verify_result.damaged.len(),
47 missing = verify_result.missing.len(),
48 "Damage detected, attempting native repair"
49 );
50
51 match rust_par2::repair_from_verify(&file_set, dir, &verify_result) {
52 Ok(repair_result) => {
53 info!(
54 blocks_repaired = repair_result.blocks_repaired,
55 files_repaired = repair_result.files_repaired,
56 "Native PAR2 repair complete"
57 );
58 Ok(Par2Result {
59 success: repair_result.success,
60 blocks_needed,
61 blocks_available,
62 repaired: repair_result.success,
63 message: repair_result.message,
64 })
65 }
66 Err(e) => {
67 warn!(error = %e, "Native PAR2 repair failed");
68 Ok(Par2Result {
69 success: false,
70 blocks_needed,
71 blocks_available,
72 repaired: false,
73 message: format!("{e}"),
74 })
75 }
76 }
77}
78
79pub async fn par2_repair(par2_file: &Path) -> anyhow::Result<Par2Result> {
81 let par2_file = par2_file.to_path_buf();
82 tokio::task::spawn_blocking(move || par2_repair_blocking(&par2_file)).await?
83}
84
85#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[tokio::test]
94 async fn test_repair_nonexistent_file() {
95 let result = par2_repair(Path::new("/nonexistent/file.par2")).await;
96 assert!(result.is_err() || !result.unwrap().success);
97 }
98
99 #[test]
100 fn test_repair_blocking_nonexistent() {
101 let result = par2_repair_blocking(Path::new("/no/such/file.par2"));
102 assert!(result.is_err());
103 }
104
105 #[test]
106 fn test_par2_result_fields() {
107 let result = Par2Result {
108 success: true,
109 blocks_needed: 0,
110 blocks_available: 10,
111 repaired: false,
112 message: "All files correct".to_string(),
113 };
114 assert!(result.success);
115 assert!(!result.repaired);
116 assert_eq!(result.blocks_needed, 0);
117 assert_eq!(result.blocks_available, 10);
118 }
119}