Files
yaoayanui/src-tauri/src/commands/material.rs
fengchuanhn@gmail.com 2abc2134a8 11
2026-05-22 18:33:46 +08:00

144 lines
4.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 画中画/字幕素材SQLite 元数据 + 本地文件管理
use std::path::{Component, Path, PathBuf};
use chrono::Local;
use serde_json::json;
use tauri::{AppHandle, Manager, State};
use uuid::Uuid;
use crate::material_db::MaterialDb;
use crate::material_store::MaterialStore;
const ALLOWED_EXTS: &[&str] = &[
"mp4", "mov", "avi", "mkv", "jpg", "jpeg", "png", "gif", "bmp", "webp",
];
fn normalize_path(path: &str) -> PathBuf {
Path::new(path)
.components()
.filter(|c| !matches!(c, Component::ParentDir))
.collect()
}
fn materials_dir(app: &AppHandle) -> Result<PathBuf, String> {
let dir = app
.path()
.app_data_dir()
.map_err(|e| format!("无法获取应用数据目录: {e}"))?
.join("video-material");
Ok(dir)
}
fn is_under_materials_dir(root: &Path, target: &Path) -> bool {
if let (Ok(root), Ok(canonical)) = (root.canonicalize(), target.canonicalize()) {
return canonical.starts_with(&root);
}
let root_s = root.to_string_lossy().to_lowercase();
let target_s = target.to_string_lossy().to_lowercase();
target_s.contains("video-material") && target_s.starts_with(&root_s)
}
#[tauri::command]
pub async fn list_materials(store: State<'_, MaterialStore>) -> Result<Vec<crate::material_db::MaterialRecord>, String> {
store.list().await
}
#[tauri::command]
pub async fn update_material_name(
store: State<'_, MaterialStore>,
id: i64,
name: String,
) -> Result<(), String> {
store.update_name(id, name).await
}
#[tauri::command]
pub async fn delete_material(
app: AppHandle,
store: State<'_, MaterialStore>,
id: i64,
) -> Result<(), String> {
let file_path = store.delete(id).await?;
if let Some(path) = file_path {
delete_material_file_if_allowed(&app, &path).await?;
}
Ok(())
}
/// 将用户选择的媒体文件复制到应用数据目录并写入数据库
#[tauri::command]
pub async fn import_material_files(
app: AppHandle,
store: State<'_, MaterialStore>,
source_paths: Vec<String>,
) -> Result<Vec<crate::material_db::MaterialRecord>, String> {
let mut imported = Vec::new();
for raw in source_paths {
let src = normalize_path(raw.trim());
if !src.is_file() {
return Err(format!("文件不存在: {}", src.display()));
}
let ext = src
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
if !ALLOWED_EXTS.contains(&ext.as_str()) {
return Err(format!("不支持的文件格式: .{ext}"));
}
let saved = copy_to_materials_dir(&app, &src, &ext).await?;
let now = chrono::Utc::now().timestamp_millis();
let name = MaterialDb::material_name_from_path(raw.trim());
let material_type = MaterialDb::material_type_from_path(raw.trim());
let info = json!({ "sourcePath": raw.trim() });
let record = store
.insert(
name,
saved,
material_type,
info,
now,
now,
)
.await?;
imported.push(record);
}
Ok(imported)
}
async fn copy_to_materials_dir(
app: &AppHandle,
src: &Path,
ext: &str,
) -> Result<String, String> {
let root = materials_dir(app)?;
let date = Local::now().format("%Y%m%d").to_string();
let dir = root.join(&date);
tokio::fs::create_dir_all(&dir)
.await
.map_err(|e| format!("创建素材目录失败: {e}"))?;
let file_name = format!("{}_{}.{}", Uuid::new_v4(), Local::now().format("%H%M%S"), ext);
let dest = dir.join(file_name);
tokio::fs::copy(src, &dest)
.await
.map_err(|e| format!("复制素材失败: {e}"))?;
Ok(dest.to_string_lossy().to_string())
}
async fn delete_material_file_if_allowed(app: &AppHandle, path: &str) -> Result<(), String> {
let target = normalize_path(path);
let dir = materials_dir(app)?;
if !is_under_materials_dir(&dir, &target) {
return Err("不允许删除该路径下的文件".into());
}
if target.is_file() {
tokio::fs::remove_file(&target)
.await
.map_err(|e| format!("删除文件失败: {e}"))?;
}
Ok(())
}