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::{OperationError, OPERATION_CHANNEL_SIZE};
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        if let Err(e) = validate_filename(name) {
53            progress.add_error(OperationError::new(path.clone(), e));
54            let _ = tx
55                .send(CreateResult::Complete(OperationComplete {
56                    operation_type: OperationType::CreateFile,
57                    succeeded: 0,
58                    failed: 1,
59                    bytes_processed: 0,
60                    errors: progress.errors,
61                }))
62                .await;
63            return;
64        }
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        if !parent.exists() {
88            if let Err(e) = fs::create_dir_all(parent) {
89                progress.add_error(OperationError::new(
90                    path.clone(),
91                    format!("Failed to create parent directory: {}", e),
92                ));
93                let _ = tx
94                    .send(CreateResult::Complete(OperationComplete {
95                        operation_type: OperationType::CreateFile,
96                        succeeded: 0,
97                        failed: 1,
98                        bytes_processed: 0,
99                        errors: progress.errors,
100                    }))
101                    .await;
102                return;
103            }
104        }
105    }
106
107    // Create the file
108    let path_clone = path.clone();
109    let result = tokio::task::spawn_blocking(move || File::create(&path_clone))
110        .await
111        .map_err(|e| format!("Task failed: {}", e));
112
113    match result {
114        Ok(Ok(_)) => {
115            progress.complete_file(0);
116            let _ = tx
117                .send(CreateResult::Complete(OperationComplete {
118                    operation_type: OperationType::CreateFile,
119                    succeeded: 1,
120                    failed: 0,
121                    bytes_processed: 0,
122                    errors: vec![],
123                }))
124                .await;
125        }
126        Ok(Err(e)) => {
127            progress.add_error(OperationError::new(path, format!("Failed to create file: {}", e)));
128            let _ = tx
129                .send(CreateResult::Complete(OperationComplete {
130                    operation_type: OperationType::CreateFile,
131                    succeeded: 0,
132                    failed: 1,
133                    bytes_processed: 0,
134                    errors: progress.errors,
135                }))
136                .await;
137        }
138        Err(e) => {
139            progress.add_error(OperationError::new(path, e));
140            let _ = tx
141                .send(CreateResult::Complete(OperationComplete {
142                    operation_type: OperationType::CreateFile,
143                    succeeded: 0,
144                    failed: 1,
145                    bytes_processed: 0,
146                    errors: progress.errors,
147                }))
148                .await;
149        }
150    }
151}
152
153/// Internal implementation of directory creation.
154async fn create_directory_impl(path: PathBuf, tx: mpsc::Sender<CreateResult>) {
155    let mut progress = OperationProgress::new(OperationType::CreateDirectory, 1, 0);
156    progress.set_current_file(Some(path.clone()));
157
158    let _ = tx.send(CreateResult::Progress(progress.clone())).await;
159
160    // Validate the directory name
161    if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
162        if let Err(e) = validate_filename(name) {
163            progress.add_error(OperationError::new(path.clone(), e));
164            let _ = tx
165                .send(CreateResult::Complete(OperationComplete {
166                    operation_type: OperationType::CreateDirectory,
167                    succeeded: 0,
168                    failed: 1,
169                    bytes_processed: 0,
170                    errors: progress.errors,
171                }))
172                .await;
173            return;
174        }
175    }
176
177    // Check if directory already exists
178    if path.exists() {
179        progress.add_error(OperationError::new(
180            path.clone(),
181            "Directory already exists".to_string(),
182        ));
183        let _ = tx
184            .send(CreateResult::Complete(OperationComplete {
185                operation_type: OperationType::CreateDirectory,
186                succeeded: 0,
187                failed: 1,
188                bytes_processed: 0,
189                errors: progress.errors,
190            }))
191            .await;
192        return;
193    }
194
195    // Create the directory
196    let path_clone = path.clone();
197    let result = tokio::task::spawn_blocking(move || fs::create_dir_all(&path_clone))
198        .await
199        .map_err(|e| format!("Task failed: {}", e));
200
201    match result {
202        Ok(Ok(())) => {
203            progress.complete_file(0);
204            let _ = tx
205                .send(CreateResult::Complete(OperationComplete {
206                    operation_type: OperationType::CreateDirectory,
207                    succeeded: 1,
208                    failed: 0,
209                    bytes_processed: 0,
210                    errors: vec![],
211                }))
212                .await;
213        }
214        Ok(Err(e)) => {
215            progress.add_error(OperationError::new(
216                path,
217                format!("Failed to create directory: {}", e),
218            ));
219            let _ = tx
220                .send(CreateResult::Complete(OperationComplete {
221                    operation_type: OperationType::CreateDirectory,
222                    succeeded: 0,
223                    failed: 1,
224                    bytes_processed: 0,
225                    errors: progress.errors,
226                }))
227                .await;
228        }
229        Err(e) => {
230            progress.add_error(OperationError::new(path, e));
231            let _ = tx
232                .send(CreateResult::Complete(OperationComplete {
233                    operation_type: OperationType::CreateDirectory,
234                    succeeded: 0,
235                    failed: 1,
236                    bytes_processed: 0,
237                    errors: progress.errors,
238                }))
239                .await;
240        }
241    }
242}