Files
rust-imager/src/services/import.rs
lychang 82653c1a6c
Some checks failed
Rust CI / Build Release (push) Has been cancelled
Rust CI / Cargo Check (push) Has been cancelled
update
2026-07-19 15:04:22 +08:00

405 lines
14 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.

use std::fs;
use std::path::Path;
use std::sync::Arc;
use ::image::GenericImageView;
use tokio::task;
use uuid::Uuid;
use crate::config::Config;
use crate::db::Database;
use crate::models::*;
/// 批量插入图片并设置封面的辅助函数
async fn batch_insert_images_and_set_cover(
db: &Database,
images_to_insert: Vec<CreateImage>,
album_id: Uuid,
_album_path: &Path,
_folder_name: &str,
) -> Result<usize, Box<dyn std::error::Error>> {
if images_to_insert.is_empty() {
return Ok(0);
}
println!(" 批量插入 {} 张图片...", images_to_insert.len());
match db.add_images(images_to_insert).await {
Ok(ids) => {
println!(" 批量插入成功,共插入 {} 张图片", ids.len());
if let Some(first_image_id) = ids.first() {
println!(" 设置第一张图片为封面图片 (ID: {})...", first_image_id);
if let Err(e) = db.update_album_cover(album_id, *first_image_id).await {
eprintln!(" 更新相册封面失败: {}", e);
}
if let Ok(Some(first_image)) = db.get_image(*first_image_id).await {
println!(" 封面图片已设置: {}", first_image.filename);
}
}
Ok(ids.len())
}
Err(e) => {
eprintln!(" 批量插入失败: {}", e);
Ok(0)
}
}
}
pub async fn import_existing_data(config: &Config, db: Arc<Database>, _task_queue: Option<Arc<crate::queue::TaskQueue>>) -> Result<(), Box<dyn std::error::Error>> {
println!("开始扫描图片目录并导入新相册...");
let albums_dir = Path::new(&config.storage.images_dir);
if !albums_dir.exists() {
println!("albums目录不存在,跳过导入");
return Ok(());
}
let existing_albums = db.get_all_albums().await?;
let existing_titles: Vec<String> = existing_albums.iter()
.map(|album| album.document.to_lowercase())
.collect();
println!("数据库中现有相册数量: {}", existing_albums.len());
let mut reimported_count = 0;
let mut deleted_count = 0;
for album in &existing_albums {
let album_folder_path = albums_dir.join(&album.document);
if !album_folder_path.exists() {
println!("🗑️ 相册文件夹不存在,删除相册: {}", album.document);
if let Err(e) = db.delete_album(album.id).await {
eprintln!("❌ 删除相册失败: {}", e);
} else {
deleted_count += 1;
}
continue;
}
let image_count = db.get_album_image_count(album.id).await.unwrap_or(0);
if image_count == 0 {
println!("🔄 重新导入相册: {} (图片数量: 0)", album.document);
match reimport_single_album(&db, &album.document, &album_folder_path, album.id).await {
Ok(count) => {
if count > 0 {
reimported_count += 1;
println!("✅ 相册 {} 重新导入成功,导入 {} 张图片", album.document, count);
} else {
println!("⚠️ 相册 {} 重新导入但没有找到图片", album.document);
}
}
Err(e) => {
eprintln!("❌ 相册 {} 重新导入失败: {}", album.document, e);
}
}
}
}
if reimported_count > 0 || deleted_count > 0 {
println!("🎯 重新导入完成: 重新导入 {} 个相册,删除 {} 个相册", reimported_count, deleted_count);
}
let entries: Vec<_> = fs::read_dir(albums_dir)?.collect::<Result<Vec<_>, _>>()?;
let mut album_folders = Vec::new();
for entry in entries {
let path = entry.path();
if path.is_dir() {
if let Some(folder_name) = path.file_name().and_then(|s| s.to_str()) {
if existing_titles.contains(&folder_name.to_lowercase()) {
continue;
}
album_folders.push((folder_name.to_string(), path));
}
}
}
println!("发现 {} 个新相册需要导入", album_folders.len());
if album_folders.is_empty() {
println!("没有新相册需要导入");
return Ok(());
}
println!("📁 开始导入新相册...");
let mut total_imported = 0;
let mut successful_albums = 0;
for (folder_name, path) in album_folders {
println!("📁 开始导入相册: {}", folder_name);
match import_single_album(&db, &folder_name, &path).await {
Ok(count) => {
if count > 0 {
successful_albums += 1;
total_imported += count;
println!("✅ 相册 {} 导入完成,共导入 {} 张图片", folder_name, count);
} else {
println!("⚠️ 相册 {} 没有图片需要导入", folder_name);
}
}
Err(e) => {
eprintln!("❌ 相册 {} 导入失败: {}", folder_name, e);
}
}
}
println!("🎉 导入完成!成功导入 {} 个相册,共 {} 张图片", successful_albums, total_imported);
Ok(())
}
async fn import_single_album(db: &Database, folder_name: &str, path: &Path) -> Result<usize, Box<dyn std::error::Error>> {
println!("📁 开始导入相册: {}", folder_name);
let index_file_path = path.join("index.json");
let album_title = if index_file_path.exists() {
match fs::read_to_string(&index_file_path) {
Ok(content) => {
match serde_json::from_str::<AlbumIndex>(&content) {
Ok(index) => {
println!(" 从index.json读取相册标题: {}", index.title);
index.title
}
Err(e) => {
println!(" 解析index.json失败: {}, 使用文件夹名作为标题", e);
folder_name.to_string()
}
}
}
Err(e) => {
println!(" 读取index.json失败: {}, 使用文件夹名作为标题", e);
folder_name.to_string()
}
}
} else {
println!(" index.json不存在,使用文件夹名作为标题");
folder_name.to_string()
};
let new_album = CreateAlbum {
title: album_title.clone(),
document: folder_name.to_string(),
description: Some(format!("从文件夹 {} 自动导入的相册", folder_name)),
};
let album_id = db.create_album(new_album).await?;
println!(" 创建相册: {} (ID: {})", album_title, album_id);
let image_urls = if index_file_path.exists() {
match fs::read_to_string(&index_file_path) {
Ok(content) => {
match serde_json::from_str::<AlbumIndex>(&content) {
Ok(index) => {
println!(" 从index.json读取到 {} 个图片URL", index.images.len());
Some(index.images)
}
Err(_) => None
}
}
Err(_) => None
}
} else {
None
};
let image_files = scan_image_files(path, folder_name)?;
if image_files.is_empty() {
println!(" 相册 {} 中没有找到图片文件", folder_name);
return Ok(0);
}
println!(" 发现 {} 张图片,开始处理...", image_files.len());
let mut image_tasks = Vec::new();
for (index, (filename, image_path)) in image_files.iter().enumerate() {
let image_url = image_urls.as_ref().and_then(|urls| {
if urls.len() > index {
Some(urls[index].clone())
} else {
None
}
});
let filename_clone = filename.clone();
let image_path_clone = image_path.clone();
let album_id_clone = album_id;
let folder_name_clone = folder_name.to_string();
let task = task::spawn_blocking(move || {
let (file_size, width, height) = match get_image_info(&image_path_clone) {
Ok(info) => info,
Err(e) => {
eprintln!("获取图片信息失败: {} - {}", image_path_clone.display(), e);
(0, 0, 0)
}
};
CreateImage {
album_id: album_id_clone,
filename: format!("{}/{}", folder_name_clone, filename_clone),
original_url: image_url,
title: Some(format!("图片 {}", index + 1)),
description: Some(format!("从文件夹 {} 导入的图片", folder_name_clone)),
file_size,
width,
height,
mime_type: match image_path_clone.extension().and_then(|s| s.to_str()).unwrap_or("jpg").to_lowercase().as_str() {
"jpg" | "jpeg" => "image/jpeg".to_string(),
"png" => "image/png".to_string(),
"gif" => "image/gif".to_string(),
"webp" => "image/webp".to_string(),
"bmp" => "image/bmp".to_string(),
_ => "image/jpeg".to_string(),
},
}
});
image_tasks.push(task);
}
let mut images_to_insert = Vec::new();
for task in image_tasks {
match task.await {
Ok(image) => images_to_insert.push(image),
Err(e) => eprintln!("图片处理任务失败: {}", e),
}
}
batch_insert_images_and_set_cover(&db, images_to_insert, album_id, path, folder_name).await
}
pub(crate) fn scan_image_files(path: &Path, _folder_name: &str) -> Result<Vec<(String, std::path::PathBuf)>, Box<dyn std::error::Error>> {
let mut image_files = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let image_path = entry.path();
if image_path.is_file() {
if let Some(extension) = image_path.extension().and_then(|s| s.to_str()) {
let valid_extensions = ["jpg", "jpeg", "png", "gif", "webp", "bmp"];
if valid_extensions.contains(&extension.to_lowercase().as_str()) {
let filename = image_path.file_name()
.and_then(|s| s.to_str())
.unwrap_or("unknown.jpg")
.to_string();
image_files.push((filename, image_path));
}
}
}
}
image_files.sort_by(|a, b| {
let num_a = a.0.chars()
.take_while(|c| c.is_ascii_digit())
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
let num_b = b.0.chars()
.take_while(|c| c.is_ascii_digit())
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
num_a.cmp(&num_b)
});
Ok(image_files)
}
fn get_image_info(image_path: &Path) -> Result<(i64, i32, i32), Box<dyn std::error::Error>> {
let metadata = fs::metadata(image_path)?;
let file_size = metadata.len() as i64;
let img = ::image::open(image_path)?;
let (width, height) = img.dimensions();
Ok((file_size, width as i32, height as i32))
}
async fn reimport_single_album(
db: &Database,
folder_name: &str,
path: &Path,
album_id: Uuid,
) -> Result<usize, Box<dyn std::error::Error>> {
println!("📁 重新导入相册: {} (ID: {})", folder_name, album_id);
let index_file_path = path.join("index.json");
let album_title = if index_file_path.exists() {
match fs::read_to_string(&index_file_path) {
Ok(content) => {
match serde_json::from_str::<AlbumIndex>(&content) {
Ok(index) => index.title,
Err(_) => folder_name.to_string(),
}
}
Err(_) => folder_name.to_string(),
}
} else {
folder_name.to_string()
};
println!(" 相册标题: {}", album_title);
let mut images_to_insert = Vec::new();
let image_extensions = ["jpg", "jpeg", "png", "gif", "webp", "bmp"];
let entries: Vec<_> = fs::read_dir(path)?.collect::<Result<Vec<_>, _>>()?;
for entry in entries {
let file_path = entry.path();
if file_path.is_file() {
if let Some(extension) = file_path.extension().and_then(|ext| ext.to_str()) {
if image_extensions.contains(&extension.to_lowercase().as_str()) {
if let Some(filename) = file_path.file_name().and_then(|s| s.to_str()) {
if filename == "index.json" {
continue;
}
let (file_size, width, height) = match get_image_info(&file_path) {
Ok(info) => info,
Err(e) => {
eprintln!(" 获取图片信息失败 {}: {}", filename, e);
continue;
}
};
let image = CreateImage {
album_id,
filename: format!("{}/{}", folder_name, filename),
original_url: Some(format!("/images/{}/{}", folder_name, filename)),
title: Some(filename.to_string()),
description: Some(String::new()),
file_size,
width,
height,
mime_type: match extension.to_lowercase().as_str() {
"jpg" | "jpeg" => "image/jpeg".to_string(),
"png" => "image/png".to_string(),
"gif" => "image/gif".to_string(),
"webp" => "image/webp".to_string(),
"bmp" => "image/bmp".to_string(),
_ => "application/octet-stream".to_string(),
},
};
images_to_insert.push(image);
}
}
}
}
}
println!(" 发现 {} 张图片需要导入", images_to_insert.len());
batch_insert_images_and_set_cover(&db, images_to_insert, album_id, path, folder_name).await
}