Initial commit

This commit is contained in:
unknown
2026-05-22 02:52:15 +02:00
commit 125321c418
55 changed files with 9231 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
[package]
name = "cgcx-storage"
version.workspace = true
edition.workspace = true
[dependencies]
cgcx-core = { path = "../cgcx-core" }
cgcx-config = { path = "../cgcx-config" }
tokio = { version = "1", features = ["fs", "io-util"] }
tracing = "0.1"
tempfile = "3"

View File

@@ -0,0 +1,86 @@
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())
}
}