소스 제외(인덱스에서 제거) 기능 추가

- 사이드바 소스 행 hover 시 ✕ 버튼 → 확인 후 인덱스/폴더/태그/썸네일
  캐시 제거. 디스크의 실제 파일은 삭제하지 않음.
- 로컬: 파일 워처 해제. 원격: 연결 해제 + 자격증명(키체인/볼트) 삭제.
- maintenance::remove_source (defer_foreign_keys로 자기참조 FK 처리),
  스캔 중 제외 방지, 제외한 소스를 보던 중이면 전체 뷰로 전환.

Rust 테스트: 실제 파일 보존 + 인덱스/썸네일 제거 검증 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
강 한
2026-07-19 13:41:15 +09:00
co-authored by Claude Opus 4.8
parent 6bff43c4e9
commit b4b1ef3241
7 changed files with 190 additions and 2 deletions
+44
View File
@@ -112,6 +112,50 @@ pub fn add_local_source(state: tauri::State<'_, AppState>, path: String) -> CmdR
})
}
/// 소스를 인덱스에서 제외한다 — **디스크의 실제 파일은 삭제하지 않는다.**
/// 워처 해제, (원격이면) 연결 해제·자격증명 삭제, 썸네일 캐시 정리 포함.
#[tauri::command]
pub async fn remove_source(
app: tauri::AppHandle,
state: tauri::State<'_, AppState>,
source_id: i64,
) -> CmdResult<u64> {
if state.scan_running.load(Ordering::SeqCst) {
return Err("스캔이 진행 중입니다. 완료 후 다시 시도하세요".into());
}
let kind: String = state
.db
.with_read(|conn| {
conn.query_row("SELECT kind FROM sources WHERE id = ?1", [source_id], |r| r.get(0))
})
.map_err(|_| "소스를 찾을 수 없습니다".to_string())?;
if kind == "local" {
if let Some(w) = state.watchers.get() {
w.remove(source_id);
}
} else {
state.remote.disconnect(source_id).await;
let key = remote::cred_key(&kind, source_id);
if let Err(e) = state.creds.lock().unwrap().delete(&key) {
tracing::warn!("자격증명 삭제 실패: {e}");
}
}
let db = state.db.clone();
let thumb_root = state.data_dir.join("thumbs");
let removed = tauri::async_runtime::spawn_blocking(move || {
archive_indexer::maintenance::remove_source(&db, &thumb_root, source_id)
})
.await
.map_err(err_str)?
.map_err(err_str)?;
tracing::info!(source_id, removed, "소스 제외 완료");
let _ = app.emit("library-changed", source_id);
Ok(removed)
}
// ── 스캔 ────────────────────────────────────────────────
#[tauri::command]