feat(cli): trash ops — rm / restore / purge / trash empty
Soft-delete sets trashed_at via Item::soft_delete; restore clears it. Purge deletes item + attachment dir and removes manifest entry. Trash empty scans for items past settings.trash_retention.
This commit is contained in:
@@ -958,10 +958,105 @@ fn push_history(
|
|||||||
replaced_at: now_unix(),
|
replaced_at: now_unix(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fn cmd_rm(_query: String) -> Result<()> { bail!("not yet implemented"); }
|
fn cmd_rm(query: String) -> Result<()> {
|
||||||
fn cmd_restore(_query: String) -> Result<()> { bail!("not yet implemented"); }
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||||
fn cmd_purge(_query: String) -> Result<()> { bail!("not yet implemented"); }
|
let mut manifest = vault.load_manifest()?;
|
||||||
fn cmd_trash(_action: TrashAction) -> Result<()> { bail!("not yet implemented"); }
|
let entry = 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.save_manifest(&manifest)?;
|
||||||
|
commit_paths(&vault, &format!("trash: {} ({})", item.title, item.id.as_str()),
|
||||||
|
&[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?;
|
||||||
|
eprintln!("Moved to trash: {}", item.title);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_restore(query: String) -> Result<()> {
|
||||||
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||||
|
let mut manifest = vault.load_manifest()?;
|
||||||
|
let entry = 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.save_manifest(&manifest)?;
|
||||||
|
commit_paths(&vault, &format!("restore: {} ({})", item.title, item.id.as_str()),
|
||||||
|
&[&format!("items/{}.enc", item.id.as_str()), "manifest.enc"])?;
|
||||||
|
eprintln!("Restored: {}", item.title);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_purge(query: String) -> Result<()> {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||||
|
let mut manifest = vault.load_manifest()?;
|
||||||
|
let entry = resolve_query(&manifest, &query)?;
|
||||||
|
let id = entry.id.clone();
|
||||||
|
let title = entry.title.clone();
|
||||||
|
let _ = entry;
|
||||||
|
|
||||||
|
// Remove the item file, its attachments directory, and drop the manifest entry.
|
||||||
|
let item_path = vault.item_path(&id);
|
||||||
|
if item_path.exists() { fs::remove_file(&item_path)?; }
|
||||||
|
let att_dir = vault.root().join("attachments").join(id.as_str());
|
||||||
|
if att_dir.exists() { fs::remove_dir_all(&att_dir)?; }
|
||||||
|
manifest.remove(&id);
|
||||||
|
vault.save_manifest(&manifest)?;
|
||||||
|
|
||||||
|
// `git rm -rf --ignore-unmatch` stages the deletions. Then add manifest and commit.
|
||||||
|
let _ = crate::helpers::git_command(vault.root(), &["rm", "-rf", "--ignore-unmatch",
|
||||||
|
&format!("items/{}.enc", id.as_str()),
|
||||||
|
&format!("attachments/{}", id.as_str()),
|
||||||
|
]).status()?;
|
||||||
|
let status = crate::helpers::git_command(vault.root(), &["add", "manifest.enc"]).status()?;
|
||||||
|
if !status.success() { anyhow::bail!("git add manifest.enc failed"); }
|
||||||
|
let status = crate::helpers::git_command(vault.root(),
|
||||||
|
&["commit", "-m", &format!("purge: {} ({})", title, id.as_str())]).status()?;
|
||||||
|
if !status.success() { anyhow::bail!("git commit failed"); }
|
||||||
|
eprintln!("Purged: {title}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_trash(action: TrashAction) -> Result<()> {
|
||||||
|
match action {
|
||||||
|
TrashAction::List => cmd_list(None, None, None, true),
|
||||||
|
TrashAction::Empty => cmd_trash_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_trash_empty() -> Result<()> {
|
||||||
|
use relicario_core::time::now_unix;
|
||||||
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||||
|
let 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(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, title) in purgeable {
|
||||||
|
cmd_purge(id.as_str().to_string())?;
|
||||||
|
eprintln!(" purged {title}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn cmd_attach(_q: String, _file: PathBuf) -> Result<()> { bail!("not yet implemented"); }
|
fn cmd_attach(_q: String, _file: PathBuf) -> Result<()> { bail!("not yet implemented"); }
|
||||||
fn cmd_attachments(_q: String) -> Result<()> { bail!("not yet implemented"); }
|
fn cmd_attachments(_q: String) -> Result<()> { bail!("not yet implemented"); }
|
||||||
fn cmd_extract(_q: String, _aid: String, _out: Option<PathBuf>) -> Result<()> { bail!("not yet implemented"); }
|
fn cmd_extract(_q: String, _aid: String, _out: Option<PathBuf>) -> Result<()> { bail!("not yet implemented"); }
|
||||||
|
|||||||
Reference in New Issue
Block a user