1use std::path::PathBuf;
4
5use tokio::sync::mpsc;
6
7use crate::conflict::ConflictResolution;
8use crate::copy::{start_copy, CopyOptions, CopyResult};
9use crate::create::{start_create_directory, start_create_file, CreateResult};
10use crate::move_op::{start_move, MoveOptions, MoveResult};
11use crate::progress::{OperationComplete, OperationProgress};
12use crate::rename::{start_rename, RenameResult};
13use crate::undo::UndoableOperation;
14use crate::{Conflict, OPERATION_CHANNEL_SIZE};
15
16#[derive(Debug)]
18pub enum OperationResult {
19 Progress(OperationProgress),
21 Conflict(Conflict),
23 Complete(OperationComplete),
25}
26
27#[derive(Debug, Default)]
29pub struct OperationExecutor {
30 pub default_resolution: Option<ConflictResolution>,
32 pub use_trash: bool,
34}
35
36impl OperationExecutor {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn with_trash() -> Self {
44 Self {
45 use_trash: true,
46 ..Default::default()
47 }
48 }
49
50 pub fn with_resolution(mut self, resolution: ConflictResolution) -> Self {
52 self.default_resolution = Some(resolution);
53 self
54 }
55
56 pub fn copy(
58 &self,
59 sources: Vec<PathBuf>,
60 destination: PathBuf,
61 ) -> mpsc::Receiver<OperationResult> {
62 let options = CopyOptions {
63 conflict_resolution: self.default_resolution,
64 preserve_timestamps: true,
65 };
66
67 let copy_rx = start_copy(sources, destination, options);
68 Self::adapt_copy_results(copy_rx)
69 }
70
71 pub fn move_to(
73 &self,
74 sources: Vec<PathBuf>,
75 destination: PathBuf,
76 ) -> mpsc::Receiver<OperationResult> {
77 let options = MoveOptions {
78 conflict_resolution: self.default_resolution,
79 };
80
81 let move_rx = start_move(sources, destination, options);
82 Self::adapt_move_results(move_rx)
83 }
84
85 pub fn rename(&self, source: PathBuf, new_name: String) -> mpsc::Receiver<OperationResult> {
87 let rename_rx = start_rename(source, new_name);
88 Self::adapt_rename_results(rename_rx)
89 }
90
91 pub fn create_file(&self, path: PathBuf) -> mpsc::Receiver<OperationResult> {
93 let create_rx = start_create_file(path);
94 Self::adapt_create_results(create_rx)
95 }
96
97 pub fn create_directory(&self, path: PathBuf) -> mpsc::Receiver<OperationResult> {
99 let create_rx = start_create_directory(path);
100 Self::adapt_create_results(create_rx)
101 }
102
103 fn adapt_copy_results(mut rx: mpsc::Receiver<CopyResult>) -> mpsc::Receiver<OperationResult> {
105 let (tx, result_rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
106
107 tokio::spawn(async move {
108 while let Some(result) = rx.recv().await {
109 let unified = match result {
110 CopyResult::Progress(p) => OperationResult::Progress(p),
111 CopyResult::Conflict(c) => OperationResult::Conflict(c),
112 CopyResult::Complete(c) => OperationResult::Complete(c),
113 };
114 if tx.send(unified).await.is_err() {
115 break;
116 }
117 }
118 });
119
120 result_rx
121 }
122
123 fn adapt_move_results(mut rx: mpsc::Receiver<MoveResult>) -> mpsc::Receiver<OperationResult> {
125 let (tx, result_rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
126
127 tokio::spawn(async move {
128 while let Some(result) = rx.recv().await {
129 let unified = match result {
130 MoveResult::Progress(p) => OperationResult::Progress(p),
131 MoveResult::Conflict(c) => OperationResult::Conflict(c),
132 MoveResult::Complete(c) => OperationResult::Complete(c),
133 };
134 if tx.send(unified).await.is_err() {
135 break;
136 }
137 }
138 });
139
140 result_rx
141 }
142
143 fn adapt_rename_results(
145 mut rx: mpsc::Receiver<RenameResult>,
146 ) -> mpsc::Receiver<OperationResult> {
147 let (tx, result_rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
148
149 tokio::spawn(async move {
150 while let Some(result) = rx.recv().await {
151 let unified = match result {
152 RenameResult::Progress(p) => OperationResult::Progress(p),
153 RenameResult::Complete(c) => OperationResult::Complete(c),
154 };
155 if tx.send(unified).await.is_err() {
156 break;
157 }
158 }
159 });
160
161 result_rx
162 }
163
164 fn adapt_create_results(
166 mut rx: mpsc::Receiver<CreateResult>,
167 ) -> mpsc::Receiver<OperationResult> {
168 let (tx, result_rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
169
170 tokio::spawn(async move {
171 while let Some(result) = rx.recv().await {
172 let unified = match result {
173 CreateResult::Progress(p) => OperationResult::Progress(p),
174 CreateResult::Complete(c) => OperationResult::Complete(c),
175 };
176 if tx.send(unified).await.is_err() {
177 break;
178 }
179 }
180 });
181
182 result_rx
183 }
184}
185
186pub fn execute_undo(entry: crate::UndoEntry) -> mpsc::Receiver<OperationResult> {
190 let (tx, rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
191
192 tokio::spawn(async move {
193 match entry.operation {
194 UndoableOperation::FilesMoved { moves } => {
195 let reverse_moves: Vec<(PathBuf, PathBuf)> =
197 moves.into_iter().map(|(from, to)| (to, from)).collect();
198
199 let options = MoveOptions {
200 conflict_resolution: Some(ConflictResolution::Overwrite),
201 };
202
203 let sources: Vec<PathBuf> = reverse_moves.iter().map(|(from, _)| from.clone()).collect();
204 let mut move_rx = start_move(
205 sources,
206 reverse_moves
207 .first()
208 .and_then(|(_, to)| to.parent())
209 .map(|p| p.to_path_buf())
210 .unwrap_or_default(),
211 options,
212 );
213
214 while let Some(result) = move_rx.recv().await {
215 let unified = match result {
216 MoveResult::Progress(p) => OperationResult::Progress(p),
217 MoveResult::Conflict(c) => OperationResult::Conflict(c),
218 MoveResult::Complete(c) => OperationResult::Complete(c),
219 };
220 if tx.send(unified).await.is_err() {
221 break;
222 }
223 }
224 }
225 UndoableOperation::FilesCopied { created } => {
226 use crate::progress::OperationType;
228 use std::fs;
229
230 let mut progress = OperationProgress::new(OperationType::Delete, created.len(), 0);
231 let mut succeeded = 0;
232 let mut failed = 0;
233
234 for path in created {
235 progress.set_current_file(Some(path.clone()));
236 let _ = tx.send(OperationResult::Progress(progress.clone())).await;
237
238 let result = tokio::task::spawn_blocking(move || {
239 if path.is_dir() {
240 fs::remove_dir_all(&path)
241 } else {
242 fs::remove_file(&path)
243 }
244 })
245 .await;
246
247 match result {
248 Ok(Ok(())) => {
249 succeeded += 1;
250 progress.complete_file(0);
251 }
252 _ => {
253 failed += 1;
254 }
255 }
256 }
257
258 let _ = tx
259 .send(OperationResult::Complete(OperationComplete {
260 operation_type: OperationType::Delete,
261 succeeded,
262 failed,
263 bytes_processed: 0,
264 errors: progress.errors,
265 }))
266 .await;
267 }
268 UndoableOperation::FilesDeleted { trash_entries } => {
269 use crate::progress::OperationType;
271
272 if trash_entries.is_empty() {
273 let _ = tx
274 .send(OperationResult::Complete(OperationComplete {
275 operation_type: OperationType::Move,
276 succeeded: 0,
277 failed: 0,
278 bytes_processed: 0,
279 errors: vec![crate::OperationError::new(
280 PathBuf::new(),
281 "Cannot undo permanent deletion".to_string(),
282 )],
283 }))
284 .await;
285 return;
286 }
287
288 let mut progress = OperationProgress::new(OperationType::Move, trash_entries.len(), 0);
290 let mut succeeded = 0;
291 let mut failed = 0;
292
293 for (original, trash) in trash_entries {
294 progress.set_current_file(Some(trash.clone()));
295 let _ = tx.send(OperationResult::Progress(progress.clone())).await;
296
297 let result = tokio::task::spawn_blocking(move || std::fs::rename(&trash, &original))
298 .await;
299
300 match result {
301 Ok(Ok(())) => {
302 succeeded += 1;
303 progress.complete_file(0);
304 }
305 _ => {
306 failed += 1;
307 }
308 }
309 }
310
311 let _ = tx
312 .send(OperationResult::Complete(OperationComplete {
313 operation_type: OperationType::Move,
314 succeeded,
315 failed,
316 bytes_processed: 0,
317 errors: progress.errors,
318 }))
319 .await;
320 }
321 UndoableOperation::FileRenamed {
322 path,
323 old_name,
324 new_name: _,
325 } => {
326 let mut rename_rx = start_rename(path, old_name);
328
329 while let Some(result) = rename_rx.recv().await {
330 let unified = match result {
331 RenameResult::Progress(p) => OperationResult::Progress(p),
332 RenameResult::Complete(c) => OperationResult::Complete(c),
333 };
334 if tx.send(unified).await.is_err() {
335 break;
336 }
337 }
338 }
339 UndoableOperation::FileCreated { path } | UndoableOperation::DirectoryCreated { path } => {
340 use crate::progress::OperationType;
342 use std::fs;
343
344 let mut progress = OperationProgress::new(OperationType::Delete, 1, 0);
345 progress.set_current_file(Some(path.clone()));
346 let _ = tx.send(OperationResult::Progress(progress.clone())).await;
347
348 let path_clone = path.clone();
349 let result = tokio::task::spawn_blocking(move || {
350 if path_clone.is_dir() {
351 fs::remove_dir_all(&path_clone)
352 } else {
353 fs::remove_file(&path_clone)
354 }
355 })
356 .await;
357
358 let (succeeded, failed) = match result {
359 Ok(Ok(())) => (1, 0),
360 _ => (0, 1),
361 };
362
363 let _ = tx
364 .send(OperationResult::Complete(OperationComplete {
365 operation_type: OperationType::Delete,
366 succeeded,
367 failed,
368 bytes_processed: 0,
369 errors: progress.errors,
370 }))
371 .await;
372 }
373 }
374 });
375
376 rx
377}