-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnet.rs
More file actions
89 lines (77 loc) · 3.11 KB
/
Copy pathnet.rs
File metadata and controls
89 lines (77 loc) · 3.11 KB
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
//! File uploading functionality for Cordyceps.
//!
//! This module provides the core logic for uploading files to a remote server,
//! encapsulated in the asynchronous `upload_file` function. It handles reading
//! the file from the local filesystem, sanitizing its name for compatibility,
//! constructing the multipart form, and sending the HTTP POST request.
//!
//! The `reqwest` crate is used to manage HTTP communication, including
//! multipart form uploads. For asynchronous file I/O, the module relies on
//! `tokio::fs`, ensuring non-blocking performance in a Tokio runtime.
//!
//! Errors encountered during file reading, request creation, or network
//! communication are unified under the central `AppError` type. This provides
//! consistent and streamlined error handling throughout the application.
//!
//! This abstraction simplifies integration of file upload capabilities and
//! reduces boilerplate, while promoting reliable and maintainable network I/O.
use log::info;
use reqwest::{Client, multipart};
use std::{io, path::Path};
use tokio::fs;
use crate::error::AppError;
/// Uploads a single file to the server.
///
/// # Arguments
/// - `client`: An HTTP client instance.
/// - `base_url`: The server's base address (e.g., `http://127.0.0.1:2673`).
/// - `local_path`: The full path to the local file to upload.
///
/// # Returns
/// Returns the HTTP status code on success or an `AppError` on failure.
///
/// # TODO
/// 1. Open the file as a stream and pass that to reqwest to avoid loading
/// the whole file into memory at once.
/// 2. Properly use `async` and `tokio` by implementing parallelism in
/// `core::sporulate`--it's forward-looking architectural decision.
pub async fn upload_file(
client: &Client,
base_url: &str,
local_path: &Path,
) -> Result<u16, AppError> {
let file_name = local_path
.file_name()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("File name not found for path: {:?}", local_path),
)
})?
.to_string_lossy();
let url = format!("{}/upload", base_url.trim_end_matches('/'));
// ASCII-only filename for maximum compatibility
let mut sanitized_file_name = String::with_capacity(file_name.len());
for c in file_name.chars() {
if c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_') {
sanitized_file_name.push(c);
}
}
let final_file_name = if sanitized_file_name.is_empty() {
"upload".to_string()
} else {
sanitized_file_name
};
info!("Uploading {:?} -> {}", local_path, url);
// Read the entire file into memory. For large files, a streaming
// solution would be preferable.
let file_content = fs::read(local_path).await?;
let file_part = multipart::Part::bytes(file_content)
.file_name(final_file_name)
.mime_str("application/octet-stream")?;
let form = multipart::Form::new().part("files", file_part);
let response = client.post(&url).multipart(form).send().await?;
let status = response.status();
response.error_for_status()?;
Ok(status.as_u16())
}