87 lines
2.7 KiB
Rust
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())
|
|
}
|
|
}
|