update code level
This commit is contained in:
@@ -267,24 +267,30 @@ impl Database {
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// 查找重名相册,返回 (keep_id, [duplicate_ids])
|
||||
pub async fn find_duplicate_albums(&self) -> Vec<(Uuid, Vec<Uuid>)> {
|
||||
/// 查找重名相册,返回结构:每个元素为 (keep_id, keep_document, [(dup_id, dup_document)])
|
||||
pub async fn find_duplicate_albums(&self) -> Vec<(Uuid, String, Vec<(Uuid, String)>)> {
|
||||
let rows = sqlx::query(
|
||||
r#"SELECT id, title FROM albums WHERE title != '' ORDER BY created_at ASC"#
|
||||
r#"SELECT id, title, document FROM albums WHERE title != '' ORDER BY created_at ASC"#
|
||||
).fetch_all(&self.pool).await.unwrap_or_default();
|
||||
|
||||
let mut title_map: std::collections::HashMap<String, Vec<Uuid>> = std::collections::HashMap::new();
|
||||
// title → [(id, document)]
|
||||
let mut title_map: std::collections::HashMap<String, Vec<(Uuid, String)>> = std::collections::HashMap::new();
|
||||
for r in &rows {
|
||||
let title: String = r.get("title");
|
||||
let id_str: String = r.get("id");
|
||||
let doc: String = r.get("document");
|
||||
if let Ok(id) = Uuid::parse_str(&id_str) {
|
||||
title_map.entry(title).or_default().push(id);
|
||||
title_map.entry(title).or_default().push((id, doc));
|
||||
}
|
||||
}
|
||||
|
||||
title_map.into_iter()
|
||||
.filter(|(_, ids)| ids.len() > 1)
|
||||
.map(|(_, ids)| (ids[0], ids[1..].to_vec()))
|
||||
.filter(|(_, items)| items.len() > 1)
|
||||
.map(|(_, items)| {
|
||||
let keep = items[0].clone(); // (keep_id, keep_document)
|
||||
let dups = items[1..].to_vec();
|
||||
(keep.0, keep.1, dups)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -266,15 +266,33 @@ pub async fn phase_delete_empty_albums(db: &Database) -> usize {
|
||||
// Phase Z1:去重同名相册
|
||||
// ════════════════════════════════════════════════
|
||||
|
||||
/// 查找同名的相册,只保留第一个(最早创建的),删除其余
|
||||
pub async fn phase_dedup_albums(db: &Database) -> usize {
|
||||
/// 查找同名的相册,只保留第一个(最早创建的),删除其余(包括文件夹和 search.db 数据)
|
||||
pub async fn phase_dedup_albums(db: &Database, search_db: &SearchDb) -> usize {
|
||||
let dups = db.find_duplicate_albums().await;
|
||||
let images_dir = &db.config.storage.images_dir;
|
||||
let mut total_deleted = 0usize;
|
||||
for (keep, to_delete) in &dups {
|
||||
let deleted = db.delete_albums_batch(to_delete).await;
|
||||
total_deleted += deleted;
|
||||
for id in to_delete {
|
||||
println!("[管线·PhaseZ1] 删除重复相册 {}(保留 {})", id, keep);
|
||||
for (keep_id, keep_document, to_delete) in &dups {
|
||||
let dup_ids: Vec<uuid::Uuid> = to_delete.iter().map(|(id, _)| *id).collect();
|
||||
// 删除数据库记录
|
||||
db.delete_albums_batch(&dup_ids).await;
|
||||
// 删除 search.db 数据
|
||||
for (id, _) in to_delete {
|
||||
search_db.delete_album(&id.to_string()).await.ok();
|
||||
}
|
||||
// 删除磁盘文件夹
|
||||
for (_, doc) in to_delete {
|
||||
let folder = images_dir.join(doc);
|
||||
if folder.exists() {
|
||||
if let Err(e) = std::fs::remove_dir_all(&folder) {
|
||||
eprintln!("[管线·PhaseZ1] 删除文件夹失败 {}: {}", folder.display(), e);
|
||||
} else {
|
||||
println!("[管线·PhaseZ1] 删除文件夹 {}", folder.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
total_deleted += to_delete.len();
|
||||
for (id, doc) in to_delete {
|
||||
println!("[管线·PhaseZ1] 删除重复相册 {} ({}),保留 {} ({})", id, doc, keep_id, keep_document);
|
||||
}
|
||||
}
|
||||
if total_deleted == 0 {
|
||||
@@ -316,12 +334,12 @@ pub struct PipelineResult {
|
||||
/// Phase ③ 增量解析脏集 → 只扫脏集
|
||||
/// Phase ④ 人物合并 + 重计 → 1次人物扫描
|
||||
/// Phase ⑤ 后校验标签中的人名
|
||||
pub async fn run_pipeline(db: &Database) -> PipelineResult {
|
||||
pub async fn run_pipeline(db: &Database, search_db: &SearchDb) -> PipelineResult {
|
||||
// Phase Z0:删除空相册
|
||||
let empty_albums_deleted = phase_delete_empty_albums(db).await;
|
||||
|
||||
// Phase Z1:去重同名相册
|
||||
let dup_albums_deleted = phase_dedup_albums(db).await;
|
||||
// Phase Z1:去重同名相册(含删除文件夹和 search.db 数据)
|
||||
let dup_albums_deleted = phase_dedup_albums(db, search_db).await;
|
||||
|
||||
// Phase ①:合并预处理(1 次相册扫描)
|
||||
let pre = phase_preprocess_combined(db).await;
|
||||
|
||||
16
src/main.rs
16
src/main.rs
@@ -122,8 +122,16 @@ async fn main() -> std::io::Result<()> {
|
||||
Err(e) => error!("[启动] 导入失败: {}", e),
|
||||
}
|
||||
|
||||
// ② 查找新相册 / 半成品
|
||||
info!("[启动] ② 查找新相册...");
|
||||
// ② 清理:删除空相册 + 去重
|
||||
info!("[启动] ② 清理空相册和重复相册...");
|
||||
let empty = engine::pipeline::phase_delete_empty_albums(&boot_db).await;
|
||||
let dedup = engine::pipeline::phase_dedup_albums(&boot_db, &boot_search_db).await;
|
||||
if empty > 0 || dedup > 0 {
|
||||
info!("[启动] 清理完成:删除空相册 {},去重删除 {}", empty, dedup);
|
||||
}
|
||||
|
||||
// ③ 查找新相册 / 半成品
|
||||
info!("[启动] ③ 查找新相册...");
|
||||
let dirty = engine::pipeline::phase_collect_new_albums(&boot_db).await;
|
||||
let new_count = dirty.len();
|
||||
if new_count > 0 {
|
||||
@@ -134,8 +142,8 @@ async fn main() -> std::io::Result<()> {
|
||||
info!("[启动] 没有新相册");
|
||||
}
|
||||
|
||||
// ③ 刷新所有内存缓存(从 search.db 读取)
|
||||
info!("[启动] ③ 刷新所有内存缓存...");
|
||||
// ④ 刷新所有内存缓存(从 search.db 读取)
|
||||
info!("[启动] ④ 刷新所有内存缓存...");
|
||||
cache::refresh_all_caches(&boot_db, &boot_search_db, &boot_albums_cache, &boot_persons_cache, &boot_tags_cache).await;
|
||||
info!("[启动] 启动管线执行完毕(新相册={})", new_count);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ pub async fn pipeline_run(
|
||||
db: web::Data<Arc<Database>>,
|
||||
search_db: web::Data<Arc<SearchDb>>,
|
||||
) -> impl Responder {
|
||||
let result = engine::pipeline::run_pipeline(db.get_ref()).await;
|
||||
let result = engine::pipeline::run_pipeline(db.get_ref(), search_db.get_ref()).await;
|
||||
let synced = engine::pipeline::sync_albums_to_search_db(db.get_ref(), search_db.get_ref()).await;
|
||||
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user