405 lines
14 KiB
Rust
405 lines
14 KiB
Rust
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
|
||
} |