1mod atomic;
7mod case_only;
8
9use crate::log::{LogRecord, LogSink, NullLogSink};
10use crate::plan_types::ItemKind;
11use crate::{ExecutionReport, FrenError, RenamePlan};
12use chrono::Utc;
13use uuid::Uuid;
14
15pub trait ProgressSink {
24 fn on_rename(&mut self, plan: &RenamePlan);
26}
27
28pub struct NullProgressSink;
30
31impl ProgressSink for NullProgressSink {
32 fn on_rename(&mut self, _plan: &RenamePlan) {}
33}
34
35pub fn execute(plans: &[RenamePlan]) -> Result<ExecutionReport, FrenError> {
40 let mut sink = NullLogSink;
41 execute_with_log(plans, &mut sink)
42}
43
44pub fn execute_with_log(
46 plans: &[RenamePlan],
47 log_sink: &mut dyn LogSink,
48) -> Result<ExecutionReport, FrenError> {
49 execute_with_progress(plans, log_sink, &mut NullProgressSink)
50}
51
52pub fn execute_with_progress(
58 plans: &[RenamePlan],
59 log_sink: &mut dyn LogSink,
60 progress: &mut dyn ProgressSink,
61) -> Result<ExecutionReport, FrenError> {
62 let batch_id = plans.first().map(|p| p.batch_id).unwrap_or_else(Uuid::nil);
63
64 let mut applied = 0usize;
65 let mut errors: Vec<FrenError> = Vec::new();
66
67 for plan in plans {
68 let from = &plan.original_path;
69 let to_path = plan.parent.join(&plan.new_name);
70
71 let outcome = match atomic::rename(from, &to_path) {
72 Ok(()) => Ok(()),
73 Err(FrenError::TargetExists(_)) if case_only::is_case_only_rename(from, &to_path) => {
74 case_only::rename_via_temp(from, &to_path)
75 }
76 Err(e) => Err(e),
77 };
78
79 match outcome {
80 Ok(()) => {
81 applied += 1;
82 progress.on_rename(plan);
83 let kind = match plan.kind {
84 ItemKind::File => "file",
85 ItemKind::Dir => "dir",
86 ItemKind::Symlink => "symlink",
87 };
88 let record = LogRecord::Rename {
89 v: 1,
90 ts: Utc::now().to_rfc3339(),
91 from: from.clone(),
92 to: to_path,
93 kind: kind.to_string(),
94 };
95 if let Err(e) = log_sink.append(&record) {
97 errors.push(e);
98 }
99 }
100 Err(e) => {
101 errors.push(e);
102 break; }
104 }
105 }
106
107 let log_path = log_sink.path().map(|p| p.to_path_buf());
108
109 Ok(ExecutionReport {
110 applied,
111 skipped: 0,
112 errors,
113 batch_id,
114 log_path,
115 })
116}