Compare commits
4 Commits
flags_impr
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be9867a007 | ||
| e76c8726a2 | |||
| 657f14d95d | |||
|
|
b6c6fcc14d |
24
README.md
24
README.md
@@ -7,11 +7,23 @@ It moves files to a per-user _trash_ instead of permanently deleting them, while
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Move files and directories to a **trash folder** instead of permanent deletion
|
- Move files and directories to a **Trash folder** instead of permanent deletion
|
||||||
- Restore deleted files by **short ID or exact basename**
|
- Restore deleted files by **short ID or exact basename**
|
||||||
- Empty trash safely
|
- Empty trash safely
|
||||||
- Supports `-r`, `-f`, `-i`, `--perma` options
|
- Supports `-r`, `-f`, `-i`, `--skip-trash` options
|
||||||
- Works with `sudo` for root-owned files
|
- Works with `sudo` for root-owned files
|
||||||
|
- Automatically prunes Trash entries older than `$RESRM_TRASH_LIFE` days (default **7**, minimum **1**)
|
||||||
|
> Note: if you need immediate deletion, use the regular `rm` command instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To control how long trashed files are kept, add this line to your shell configuration (e.g. `~/.bashrc`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export RESRM_TRASH_LIFE=10
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -57,7 +69,7 @@ resrm -f file
|
|||||||
resrm -i file
|
resrm -i file
|
||||||
|
|
||||||
# Permanent delete (bypass trash)
|
# Permanent delete (bypass trash)
|
||||||
resrm --perma file
|
resrm --skip-trash file
|
||||||
|
|
||||||
# List trash entries
|
# List trash entries
|
||||||
resrm -l
|
resrm -l
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "resrm"
|
name = "resrm"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
description = "drop-in replacement for rm with undo/restore built-in."
|
description = "drop-in replacement for rm with undo/restore built-in."
|
||||||
authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
|
authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|||||||
@@ -53,6 +53,42 @@ def get_trash_paths() -> tuple[Path, Path]:
|
|||||||
TRASH_DIR, META_FILE = get_trash_paths()
|
TRASH_DIR, META_FILE = get_trash_paths()
|
||||||
DATEFMT = "%Y-%m-%d %H:%M"
|
DATEFMT = "%Y-%m-%d %H:%M"
|
||||||
|
|
||||||
|
def prune_old_trash():
|
||||||
|
"""Remove trash entries older than RESRM_TRASH_LIFE days (default 7)."""
|
||||||
|
try:
|
||||||
|
life_days = int(os.environ.get("RESRM_TRASH_LIFE", "7"))
|
||||||
|
except ValueError:
|
||||||
|
life_days = 7
|
||||||
|
|
||||||
|
if life_days < 1:
|
||||||
|
life_days = 1
|
||||||
|
|
||||||
|
cutoff = datetime.datetime.now() - datetime.timedelta(days=life_days)
|
||||||
|
removed = 0
|
||||||
|
|
||||||
|
for entry in list(meta): # make copy since we'll modify meta
|
||||||
|
try:
|
||||||
|
ts = datetime.datetime.fromisoformat(entry["timestamp"])
|
||||||
|
except Exception:
|
||||||
|
continue # skip malformed entries
|
||||||
|
|
||||||
|
if ts < cutoff:
|
||||||
|
f = TRASH_DIR / entry["id"]
|
||||||
|
try:
|
||||||
|
if f.exists():
|
||||||
|
if f.is_dir():
|
||||||
|
shutil.rmtree(f, ignore_errors=True)
|
||||||
|
else:
|
||||||
|
f.unlink(missing_ok=True)
|
||||||
|
meta.remove(entry)
|
||||||
|
removed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to prune {f}: {e}")
|
||||||
|
|
||||||
|
if removed > 0:
|
||||||
|
save_meta(meta)
|
||||||
|
print(f"Pruned {removed} trash entr{'y' if removed == 1 else 'ies'} older than {life_days} da{'y' if life_days == 1 else 'ys'}.")
|
||||||
|
|
||||||
def load_meta() -> List[Dict]:
|
def load_meta() -> List[Dict]:
|
||||||
if META_FILE.exists():
|
if META_FILE.exists():
|
||||||
try:
|
try:
|
||||||
@@ -304,6 +340,7 @@ def move_to_trash(path: Path, interactive: bool, force: bool, skip_trash: bool):
|
|||||||
def main(argv: Optional[List[str]] = None):
|
def main(argv: Optional[List[str]] = None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
prune_old_trash()
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
parser.add_argument("paths", nargs="*", help="files to remove")
|
parser.add_argument("paths", nargs="*", help="files to remove")
|
||||||
parser.add_argument("-r", action="store_true", help="recursive")
|
parser.add_argument("-r", action="store_true", help="recursive")
|
||||||
|
|||||||
Reference in New Issue
Block a user