trash: a delete can be undone

`x`, `dd`, `:delete` and `:rmspace` no longer end anything. What they
delete is moved, whole, under one hidden root, and `u` puts the last of it
back where it was. `:trash` lists everything deleted, the last first:
Enter or `r` restores, `x` deletes one for good and `:trash purge` empties
it, each asking first. Something whose folder has gone since comes back to
the root of its space; something whose space is in the trash too waits for
the space. The daily sweep purges what has waited WORKSPACE_TRASH_DAYS (30),
and only then are its files deleted.

While it waits, a trashed node is out of the tree, the search, the agenda,
the mails, the SFTP mount, the export and the readable files/ view, and a
shared document answers 404 until it comes back with the same link. `rm`
over the mount trashes too; a rename onto a taken name still replaces what
was there, so editor saves do not fill the trash.
This commit is contained in:
nvrl committed 2026-10-20 18:15:22 +02:00
1 parent 439d5b083b
commit 8ffad6350f
22 files changed
+1154 -78

No files matched your search

+8
View File
@@ -22,6 +22,8 @@ pub struct Config {
pub secure_cookie: bool,
/// How long the vault stays open with nothing using it.
pub vault_idle: std::time::Duration,
/// How many days a deleted node waits in the trash before it is purged.
pub trash_days: i64,
pub backup: BackupConfig,
pub mail: MailConfig,
pub sftp: SftpConfig,
@@ -41,6 +43,7 @@ impl std::fmt::Debug for Config {
.field("max_upload_bytes", &self.max_upload_bytes)
.field("secure_cookie", &self.secure_cookie)
.field("vault_idle", &self.vault_idle)
.field("trash_days", &self.trash_days)
.field("backup", &self.backup)
.field("mail", &self.mail)
.field("sftp", &self.sftp)
@@ -287,6 +290,9 @@ impl Config {
// A vault that locks the moment it opens could not be used; one that
// stays open for more than a day is not much of a vault.
let vault_idle = Duration::from_secs(number("VAULT_IDLE_MIN", 15, 1..=24 * 60)? * 60);
// A day at least, so an undo is never raced by the sweep; ten years
// at most, as for the sessions.
let trash_days = number("TRASH_DAYS", 30, 1..=3650)? as i64;
let secure_cookie = flag("SECURE_COOKIE")?;
Ok(Self {
@@ -299,6 +305,7 @@ impl Config {
max_upload_bytes,
secure_cookie,
vault_idle,
trash_days,
backup: BackupConfig::from_env()?,
mail: MailConfig::from_env()?,
sftp: SftpConfig::from_env()?,
@@ -336,6 +343,7 @@ impl Config {
max_upload_bytes: 1,
secure_cookie: false,
vault_idle: std::time::Duration::from_secs(60),
trash_days: 30,
backup: BackupConfig {
device_name: "narl-diary".into(),
interval: None,