1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
use std::path::{Path, PathBuf};
use crate::types::*;
/// Main rename logic integration
impl NameExchange {
/// Initialize structure for storing all information
///
/// Create a new NameExchange instance with two default initialized FileInfos
pub fn new() -> NameExchange {
NameExchange {
f1: FileInfos {
..Default::default()
},
f2: FileInfos {
..Default::default()
},
}
}
/// Get temporary filename and renamed filename
///
/// Generate temporary file path and final file path based on directory path, filename, and extension
///
/// ### Parameters
/// * `dir` - Directory path where file is located
/// * `other_name` - Target filename (without extension)
/// * `f1_ext` - File 1 extension (including leading dot ".")
/// * `f2_ext` - File 2 extension (including leading dot ".")
/// * `preserve_ext` - Should make new name with/without original ext
///
/// ### Return Value
/// Returns tuple `(temporary file path, final file path)`
pub fn make_name(
dir: &Path,
other_name: impl ToString,
f1_ext: impl ToString,
f2_ext: impl ToString,
preserve_ext: bool,
) -> (PathBuf, PathBuf) {
let other_name = other_name.to_string();
let ext = if preserve_ext {
f1_ext.to_string()
} else {
f2_ext.to_string()
};
let mut final_path = dir.to_path_buf();
// Generate unique temporary filename, avoid conflicts with existing files
let base_temp = crate::types::GUID;
let mut temp_path = dir.join(format!("{}{}", base_temp, ext));
let mut counter = 0u64;
while temp_path.exists() {
counter += 1;
temp_path = dir.join(format!("{}_{}{}", base_temp, counter, ext));
}
let final_component = if ext.is_empty() {
other_name
} else {
format!("{}{}", other_name, ext)
};
if !final_component.is_empty() {
final_path.push(final_component);
}
(temp_path, final_path)
}
/// Rename execution part
///
/// Execute rename operation based on file type and nesting relationship
///
/// ### Parameters
/// * `is_nested` - Whether there is a nesting relationship (such as parent-child directories)
/// * `file1_first` - Whether to rename the first file first
///
/// ### Return Value
/// Returns `Ok(())` for success, `Err(RenameError)` for corresponding failure reason
pub fn rename_each(&self, is_nested: bool, file1_first: bool) -> Result<(), RenameError> {
// Prepare path variables according to rename order
let mut path1 = self.f2.exchange.original_path.clone();
let mut final_name1 = self.f2.exchange.new_path.clone();
let mut path2 = self.f1.exchange.original_path.clone();
let mut final_name2 = self.f1.exchange.new_path.clone();
let mut tmp_name2 = self.f1.exchange.pre_path.clone();
if file1_first {
path1 = self.f1.exchange.original_path.clone();
final_name1 = self.f1.exchange.new_path.clone();
path2 = self.f2.exchange.original_path.clone();
final_name2 = self.f2.exchange.new_path.clone();
tmp_name2 = self.f2.exchange.pre_path.clone();
}
if is_nested {
Self::handle_rename(&path1, &final_name1)?;
if let Err(e) = Self::handle_rename(&path2, &final_name2) {
// Rollback step 1
if let Err(rollback_err) = Self::handle_rename(&final_name1, &path1) {
if DEBUG_MODE {
eprintln!(
"rollback failed: {}. Files may be inconsistent: {} -> {}",
rollback_err,
final_name1.display(),
path1.display()
);
}
}
return Err(e);
}
Ok(())
} else {
Self::handle_rename(&path2, &tmp_name2)?;
if let Err(e) = Self::handle_rename(&path1, &final_name1) {
// Rollback step 1: restore path2
if let Err(rollback_err) = Self::handle_rename(&tmp_name2, &path2) {
if DEBUG_MODE {
eprintln!(
"rollback failed: {}. Files may be inconsistent: {} -> {}",
rollback_err,
tmp_name2.display(),
path2.display()
);
}
}
return Err(e);
}
if let Err(e) = Self::handle_rename(&tmp_name2, &final_name2) {
// Rollback steps 1 & 2: restore both files
if let Err(rollback_err) = Self::handle_rename(&final_name1, &path1) {
if DEBUG_MODE {
eprintln!(
"rollback failed: {}. Files may be inconsistent: {} -> {}",
rollback_err,
final_name1.display(),
path1.display()
);
}
}
if let Err(rollback_err) = Self::handle_rename(&tmp_name2, &path2) {
if DEBUG_MODE {
eprintln!(
"rollback failed: {}. Files may be inconsistent: {} -> {}",
rollback_err,
tmp_name2.display(),
path2.display()
);
}
}
return Err(e);
}
Ok(())
}
}
/// Handle single rename operation and process possible errors
///
/// ### Parameters
/// * `from` - Original file path
/// * `to` - Target file path
///
/// ### Return Value
/// Returns `Ok(())` for success, `Err(RenameError)` for specific error
fn handle_rename(from: &Path, to: &Path) -> Result<(), RenameError> {
match std::fs::rename(from, to) {
Ok(_) => Ok(()),
Err(e) => Err(RenameError::from(e)),
}
}
}