//! Trash umbrella: `rm` (soft-delete), `restore`, `purge` (permanent), //! `trash list` / `trash empty`. use anyhow::Result; use crate::TrashAction; pub fn cmd_rm(query: String) -> Result<()> { let vault = crate::session::UnlockedVault::unlock_interactive()?; let mut manifest = vault.load_manifest()?; let entry = super::resolve_query(&manifest, &query)?; let id = entry.id.clone(); let _ = entry; let mut item = vault.load_item(&id)?; item.soft_delete(); vault.save_item(&item)?; manifest.upsert(&item); vault.after_manifest_change(&manifest)?; super::commit_paths(&vault, &format!("trash: {} ({})", crate::helpers::sanitize_for_commit(&item.title), item.id.as_str()), &[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?; eprintln!("Moved to trash: {}", item.title); Ok(()) } pub fn cmd_restore(query: String) -> Result<()> { let vault = crate::session::UnlockedVault::unlock_interactive()?; let mut manifest = vault.load_manifest()?; let entry = super::resolve_query(&manifest, &query)?; let id = entry.id.clone(); let _ = entry; let mut item = vault.load_item(&id)?; item.restore(); vault.save_item(&item)?; manifest.upsert(&item); vault.after_manifest_change(&manifest)?; super::commit_paths(&vault, &format!("restore: {} ({})", crate::helpers::sanitize_for_commit(&item.title), item.id.as_str()), &[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?; eprintln!("Restored: {}", item.title); Ok(()) } /// Filesystem-only purge: removes the item.enc, attachments//, and updates /// the manifest in memory. Returns the relative paths the caller must stage /// via `git rm` after the loop. Does NOT invoke any git commands — the caller /// batches them. pub(super) fn purge_item_filesystem( vault: &crate::session::UnlockedVault, manifest: &mut relicario_core::Manifest, id: &relicario_core::ItemId, title: &str, ) -> Result> { use std::{fs, io::ErrorKind}; let item_rel = format!("items/{}.enc", id.as_str()); let att_rel = format!("attachments/{}", id.as_str()); let ignore_missing = |r: std::io::Result<()>| -> Result<()> { match r { Ok(()) => Ok(()), Err(e) if e.kind() == ErrorKind::NotFound => Ok(()), Err(e) => Err(e.into()), } }; ignore_missing(fs::remove_file(vault.item_path(id)))?; ignore_missing(fs::remove_dir_all(vault.root().join("attachments").join(id.as_str())))?; manifest.remove(id); eprintln!("Purged: {title}"); Ok(vec![item_rel, att_rel]) } pub fn cmd_purge(query: String) -> Result<()> { let vault = crate::session::UnlockedVault::unlock_interactive()?; let mut manifest = vault.load_manifest()?; let entry = super::resolve_query(&manifest, &query)?; let id = entry.id.clone(); let title = entry.title.clone(); let _ = entry; let paths = purge_item_filesystem(&vault, &mut manifest, &id, &title)?; vault.after_manifest_change(&manifest)?; let purge_ctx = format!("purge \"{}\" ({})", title, id.as_str()); crate::helpers::git_rm(vault.root(), &paths, &format!("{purge_ctx}: git rm"))?; crate::helpers::git_run( vault.root(), &["add", "manifest.enc"], &format!("{purge_ctx}: git add manifest.enc"), )?; crate::helpers::git_run( vault.root(), &["commit", "-m", &format!("purge: {} ({})", title, id.as_str())], &format!("{purge_ctx}: git commit"), )?; Ok(()) } pub fn cmd_trash(action: TrashAction) -> Result<()> { match action { TrashAction::List => super::list::cmd_list(None, None, None, true), TrashAction::Empty => cmd_trash_empty(), } } pub fn cmd_trash_empty() -> Result<()> { use relicario_core::time::now_unix; let vault = crate::session::UnlockedVault::unlock_interactive()?; let mut manifest = vault.load_manifest()?; let settings = vault.load_settings()?; let now = now_unix(); let purgeable: Vec<_> = manifest.items.values() .filter(|e| match e.trashed_at { Some(t) => settings.trash_retention.should_purge(t, now), None => false, }) .map(|e| (e.id.clone(), e.title.clone())) .collect(); if purgeable.is_empty() { eprintln!("nothing past retention window"); return Ok(()); } let mut all_paths: Vec = Vec::new(); let purged_count = purgeable.len(); for (id, title) in purgeable { let mut paths = purge_item_filesystem(&vault, &mut manifest, &id, &title)?; all_paths.append(&mut paths); } vault.after_manifest_change(&manifest)?; crate::helpers::git_rm(vault.root(), &all_paths, "trash empty: git rm")?; crate::helpers::git_run( vault.root(), &["add", "manifest.enc"], "trash empty: git add manifest.enc", )?; crate::helpers::git_run( vault.root(), &["commit", "-m", &format!("trash empty: purged {} item(s)", purged_count)], "trash empty: git commit", )?; eprintln!("Emptied trash: {} item(s)", purged_count); Ok(()) }