Skip to main content

gravityfile_ops/
create.rs

1//! File and directory creation operations.
2
3use std::fs::{self, File};
4use std::path::PathBuf;
5
6use tokio::sync::mpsc;
7
8use crate::progress::{OperationComplete, OperationProgress, OperationType};
9use crate::rename::validate_filename;
10use crate::{OPERATION_CHANNEL_SIZE, OperationError};
11
12/// Result sent through the channel during create operations.
13#[derive(Debug)]
14pub enum CreateResult {
15    /// Progress update.
16    Progress(OperationProgress),
17    /// The operation completed.
18    Complete(OperationComplete),
19}
20
21/// Start an async file creation operation.
22pub fn start_create_file(path: PathBuf) -> mpsc::Receiver<CreateResult> {
23    let (tx, rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
24
25    tokio::spawn(async move {
26        create_file_impl(path, tx).await;
27    });
28
29    rx
30}
31
32/// Start an async directory creation operation.
33pub fn start_create_directory(path: PathBuf) -> mpsc::Receiver<CreateResult> {
34    let (tx, rx) = mpsc::channel(OPERATION_CHANNEL_SIZE);
35
36    tokio::spawn(async move {
37        create_directory_impl(path, tx).await;
38    });
39
40    rx
41}
42
43/// Internal implementation of file creation.
44async fn create_file_impl(path: PathBuf, tx: mpsc::Sender<CreateResult>) {
45    let mut progress = OperationProgress::new(OperationType::CreateFile, 1, 0);
46    progress.set_current_file(Some(path.clone()));
47
48    let _ = tx.send(CreateResult::Progress(progress.clone())).await;
49
50    // Validate the filename
51    if let Some(name) = path.file_name().and_then(|n| n.to_str())
52        && let Err(e) = validate_filename(name)
53    {
54        progress.add_error(OperationError::new(path.clone(), e));
55        let _ = tx
56            .send(CreateResult::Complete(OperationComplete {
57                operation_type: OperationType::CreateFile,
58                succeeded: 0,
59                failed: 1,
60                bytes_processed: 0,
61                errors: progress.errors,
62            }))
63            .await;
64        return;
65    }
66
67    // Check if file already exists
68    if path.exists() {
69        progress.add_error(OperationError::new(
70            path.clone(),
71            "File already exists".to_string(),
72        ));
73        let _ = tx
74            .send(CreateResult::Complete(OperationComplete {
75                operation_type: OperationType::CreateFile,
76                succeeded: 0,
77                failed: 1,
78                bytes_processed: 0,
79                errors: progress.errors,
80            }))
81            .await;
82        return;
83    }
84
85    // Ensure parent directory exists
86    if let Some(parent) = path.parent()
87        && !parent.exists()
88        && let Err(e) = fs::create_dir_all(parent)
89    {
90        progress.add_error(OperationError::new(
91            path.clone(),
92            format!("Failed to create parent directory: {}", e),
93        ));
94        let _ = tx
95            .send(CreateResult::Complete(OperationComplete {
96                operation_type: OperationType::CreateFile,
97                succeeded: 0,
98                failed: 1,
99                bytes_processed: 0,
100                errors: progress.errors,
101            }))
102            .await;
103        return;
104    }
105
106    // Create the file
107    let path_clone = path.clone();
108    let result = tokio::task::spawn_blocking(move || File::create(&path_clone))
109        .await
110        .map_err(|e| format!("Task failed: {}", e));
111
112    match result {
113        Ok(Ok(_)) => {
114            progress.complete_file(0);
115            let _ = tx
116                .send(CreateResult::Complete(OperationComplete {
117                    operation_type: OperationType::CreateFile,
118                    succeeded: 1,
119                    failed: 0,
120                    bytes_processed: 0,
121                    errors: vec![],
122                }))
123                .await;
124        }
125        Ok(Err(e)) => {
126            progress.add_error(OperationError::new(
127                path,
128                format!("Failed to create file: {}", e),
129            ));
130            let _ = tx
131                .send(CreateResult::Complete(OperationComplete {
132                    operation_type: OperationType::CreateFile,
133                    succeeded: 0,
134                    failed: 1,
135                    bytes_processed: 0,
136                    errors: progress.errors,
137                }))
138                .await;
139        }
140        Err(e) => {
141            progress.add_error(OperationError::new(path, e));
142            let _ = tx
143                .send(CreateResult::Complete(OperationComplete {
144                    operation_type: OperationType::CreateFile,
145                    succeeded: 0,
146                    failed: 1,
147                    bytes_processed: 0,
148                    errors: progress.errors,
149                }))
150                .await;
151        }
152    }
153}
154
155/// Internal implementation of directory creation.
156async fn create_directory_impl(path: PathBuf, tx: mpsc::Sender<CreateResult>) {
157    let mut progress = OperationProgress::new(OperationType::CreateDirectory, 1, 0);
158    progress.set_current_file(Some(path.clone()));
159
160    let _ = tx.send(CreateResult::Progress(progress.clone())).await;
161
162    // Validate the directory name
163    if let Some(name) = path.file_name().and_then(|n| n.to_str())
164        && let Err(e) = validate_filename(name)
165    {
166        progress.add_error(OperationError::new(path.clone(), e));
167        let _ = tx
168            .send(CreateResult::Complete(OperationComplete {
169                operation_type: OperationType::CreateDirectory,
170                succeeded: 0,
171                failed: 1,
172                bytes_processed: 0,
173                errors: progress.errors,
174            }))
175            .await;
176        return;
177    }
178
179    // Check if directory already exists
180    if path.exists() {
181        progress.add_error(OperationError::new(
182            path.clone(),
183            "Directory already exists".to_string(),
184        ));
185        let _ = tx
186            .send(CreateResult::Complete(OperationComplete {
187                operation_type: OperationType::CreateDirectory,
188                succeeded: 0,
189                failed: 1,
190                bytes_processed: 0,
191                errors: progress.errors,
192            }))
193            .await;
194        return;
195    }
196
197    // Create the directory
198    let path_clone = path.clone();
199    let result = tokio::task::spawn_blocking(move || fs::create_dir_all(&path_clone))
200        .await
201        .map_err(|e| format!("Task failed: {}", e));
202
203    match result {
204        Ok(Ok(())) => {
205            progress.complete_file(0);
206            let _ = tx
207                .send(CreateResult::Complete(OperationComplete {
208                    operation_type: OperationType::CreateDirectory,
209                    succeeded: 1,
210                    failed: 0,
211                    bytes_processed: 0,
212                    errors: vec![],
213                }))
214                .await;
215        }
216        Ok(Err(e)) => {
217            progress.add_error(OperationError::new(
218                path,
219                format!("Failed to create directory: {}", e),
220            ));
221            let _ = tx
222                .send(CreateResult::Complete(OperationComplete {
223                    operation_type: OperationType::CreateDirectory,
224                    succeeded: 0,
225                    failed: 1,
226                    bytes_processed: 0,
227                    errors: progress.errors,
228                }))
229                .await;
230        }
231        Err(e) => {
232            progress.add_error(OperationError::new(path, e));
233            let _ = tx
234                .send(CreateResult::Complete(OperationComplete {
235                    operation_type: OperationType::CreateDirectory,
236                    succeeded: 0,
237                    failed: 1,
238                    bytes_processed: 0,
239                    errors: progress.errors,
240                }))
241                .await;
242        }
243    }
244}