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 let metadata = fs::symlink_metadata(&path)?;
241 if metadata.is_symlink() {
242 fs::remove_file(&path)
244 } else if metadata.is_dir() {
245 fs::remove_dir_all(&path)
246 } else {
247 fs::remove_file(&path)
248 }
249 })
250 .await;
251
252 match result {
253 Ok(Ok(())) => {
254 succeeded += 1;
255 progress.complete_file(0);
256 }
257 _ => {
258 failed += 1;
259 }
260 }
261 }
262
263 let _ = tx
264 .send(OperationResult::Complete(OperationComplete {
265 operation_type: OperationType::Delete,
266 succeeded,
267 failed,
268 bytes_processed: 0,
269 errors: progress.errors,
270 }))
271 .await;
272 }
273 UndoableOperation::FilesDeleted { trash_entries } => {
274 use crate::progress::OperationType;
276
277 if trash_entries.is_empty() {
278 let _ = tx
279 .send(OperationResult::Complete(OperationComplete {
280 operation_type: OperationType::Move,
281 succeeded: 0,
282 failed: 0,
283 bytes_processed: 0,
284 errors: vec![crate::OperationError::new(
285 PathBuf::new(),
286 "Cannot undo permanent deletion".to_string(),
287 )],
288 }))
289 .await;
290 return;
291 }
292
293 let mut progress = OperationProgress::new(OperationType::Move, trash_entries.len(), 0);
295 let mut succeeded = 0;
296 let mut failed = 0;
297
298 for (original, trash) in trash_entries {
299 progress.set_current_file(Some(trash.clone()));
300 let _ = tx.send(OperationResult::Progress(progress.clone())).await;
301
302 let result = tokio::task::spawn_blocking(move || std::fs::rename(&trash, &original))
303 .await;
304
305 match result {
306 Ok(Ok(())) => {
307 succeeded += 1;
308 progress.complete_file(0);
309 }
310 _ => {
311 failed += 1;
312 }
313 }
314 }
315
316 let _ = tx
317 .send(OperationResult::Complete(OperationComplete {
318 operation_type: OperationType::Move,
319 succeeded,
320 failed,
321 bytes_processed: 0,
322 errors: progress.errors,
323 }))
324 .await;
325 }
326 UndoableOperation::FileRenamed {
327 path,
328 old_name,
329 new_name: _,
330 } => {
331 let mut rename_rx = start_rename(path, old_name);
333
334 while let Some(result) = rename_rx.recv().await {
335 let unified = match result {
336 RenameResult::Progress(p) => OperationResult::Progress(p),
337 RenameResult::Complete(c) => OperationResult::Complete(c),
338 };
339 if tx.send(unified).await.is_err() {
340 break;
341 }
342 }
343 }
344 UndoableOperation::FileCreated { path } | UndoableOperation::DirectoryCreated { path } => {
345 use crate::progress::OperationType;
347 use std::fs;
348
349 let mut progress = OperationProgress::new(OperationType::Delete, 1, 0);
350 progress.set_current_file(Some(path.clone()));
351 let _ = tx.send(OperationResult::Progress(progress.clone())).await;
352
353 let path_clone = path.clone();
354 let result = tokio::task::spawn_blocking(move || {
355 let metadata = fs::symlink_metadata(&path_clone)?;
357 if metadata.is_symlink() {
358 fs::remove_file(&path_clone)
360 } else if metadata.is_dir() {
361 fs::remove_dir_all(&path_clone)
362 } else {
363 fs::remove_file(&path_clone)
364 }
365 })
366 .await;
367
368 let (succeeded, failed) = match result {
369 Ok(Ok(())) => (1, 0),
370 _ => (0, 1),
371 };
372
373 let _ = tx
374 .send(OperationResult::Complete(OperationComplete {
375 operation_type: OperationType::Delete,
376 succeeded,
377 failed,
378 bytes_processed: 0,
379 errors: progress.errors,
380 }))
381 .await;
382 }
383 }
384 });
385
386 rx
387}