Files
cg_api_secure-webshare/crates/cgcx-storage/src/lib.rs
2026-05-22 02:52:15 +02:00

87 lines
2.7 KiB
Rust

use cgcx_config::StoragePaths;
use cgcx_core::{ContentId, Result, CgcxError};
use std::path::{Path, PathBuf};
use tokio::fs;
#[derive(Clone)]
pub struct Storage {
paths: StoragePaths,
}
impl Storage {
pub fn new(paths: StoragePaths) -> Self {
Self { paths }
}
pub async fn ensure_dirs(&self) -> Result<()> {
for dir in [&self.paths.media, &self.paths.documents, &self.paths.text, &self.paths.temp] {
fs::create_dir_all(dir).await.map_err(|e| CgcxError::Storage(format!("create dir {:?}: {}", dir, e)))?;
}
Ok(())
}
pub fn media_dir(&self) -> &Path {
&self.paths.media
}
pub fn documents_dir(&self) -> &Path {
&self.paths.documents
}
pub fn text_dir(&self) -> &Path {
&self.paths.text
}
pub fn temp_dir(&self) -> &Path {
&self.paths.temp
}
pub fn content_dir(&self, content_id: &ContentId, mime_type: &str) -> PathBuf {
let base = if mime_type.starts_with("image/") || mime_type.starts_with("video/") || mime_type.starts_with("audio/") {
&self.paths.media
} else if mime_type.starts_with("text/") {
&self.paths.text
} else {
&self.paths.documents
};
base.join(content_id.as_str())
}
pub fn file_path(&self, content_id: &ContentId, file_index: u32, mime_type: &str) -> Result<PathBuf> {
let base = if mime_type.starts_with("image/") || mime_type.starts_with("video/") || mime_type.starts_with("audio/") {
&self.paths.media
} else if mime_type.starts_with("text/") {
&self.paths.text
} else {
&self.paths.documents
};
let dir = base.join(content_id.as_str());
let file_name = format!("{}_{:04}.enc", content_id.as_str(), file_index);
let path = dir.join(file_name);
if !path.starts_with(base) {
return Err(CgcxError::Storage("path traversal detected".into()));
}
Ok(path)
}
pub fn temp_file(&self) -> Result<tempfile::NamedTempFile> {
tempfile::NamedTempFile::new_in(&self.paths.temp)
.map_err(|e| CgcxError::Storage(format!("create temp file: {}", e)))
}
pub async fn delete_content_files(&self, content_id: &ContentId, mime_type: &str) -> Result<()> {
let dir = self.content_dir(content_id, mime_type);
if dir.exists() {
fs::remove_dir_all(&dir).await.map_err(|e| CgcxError::Storage(format!("remove dir {:?}: {}", dir, e)))?;
}
Ok(())
}
pub async fn file_size(&self, path: &Path) -> Result<u64> {
let meta = fs::metadata(path).await.map_err(|e| CgcxError::Storage(format!("metadata {:?}: {}", path, e)))?;
Ok(meta.len())
}
}