|
@@ -1,4 +1,7 @@
|
|
|
#! /usr/bin/env python3
|
|
#! /usr/bin/env python3
|
|
|
|
|
+""" Move files and directories to custom trash directories, taking the
|
|
|
|
|
+corresponding mount points into account.
|
|
|
|
|
+"""
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
import argparse
|
|
import argparse
|
|
@@ -6,7 +9,7 @@ import logging
|
|
|
import sys
|
|
import sys
|
|
|
import os
|
|
import os
|
|
|
import json
|
|
import json
|
|
|
-
|
|
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
|
|
|
DEFAULT_CONFIG = {
|
|
DEFAULT_CONFIG = {
|
|
|
'trashes': [
|
|
'trashes': [
|
|
@@ -16,8 +19,10 @@ DEFAULT_CONFIG = {
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args():
|
|
def parse_args():
|
|
|
- parser = argparse.ArgumentParser()
|
|
|
|
|
- parser.add_argument('names', nargs='+')
|
|
|
|
|
|
|
+ parser = argparse.ArgumentParser(description=__doc__)
|
|
|
|
|
+ parser.add_argument('names', nargs='*')
|
|
|
|
|
+ parser.add_argument('-d', '--dry-run', action='store_true')
|
|
|
|
|
+ parser.add_argument('-l', '--list', action='store_true')
|
|
|
return parser.parse_args()
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
@@ -29,7 +34,7 @@ def lowest_mount(path: Path) -> Path:
|
|
|
return path
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
-def getconfig():
|
|
|
|
|
|
|
+def getconfig() -> dict:
|
|
|
config_file = Path('~/.config/trash.json').expanduser()
|
|
config_file = Path('~/.config/trash.json').expanduser()
|
|
|
config = DEFAULT_CONFIG
|
|
config = DEFAULT_CONFIG
|
|
|
if config_file.is_file():
|
|
if config_file.is_file():
|
|
@@ -38,22 +43,16 @@ def getconfig():
|
|
|
return config
|
|
return config
|
|
|
|
|
|
|
|
|
|
|
|
|
-def main():
|
|
|
|
|
- args = parse_args()
|
|
|
|
|
- logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
|
|
|
|
-
|
|
|
|
|
- config = getconfig()
|
|
|
|
|
- logging.debug(config)
|
|
|
|
|
-
|
|
|
|
|
- trashes = []
|
|
|
|
|
|
|
+def get_trashes(config):
|
|
|
|
|
+ trashes: list[Path] = []
|
|
|
for trashdir in config['trashes']:
|
|
for trashdir in config['trashes']:
|
|
|
trashdir = Path(os.path.expandvars(trashdir)).expanduser().resolve()
|
|
trashdir = Path(os.path.expandvars(trashdir)).expanduser().resolve()
|
|
|
- if not trashdir.is_dir():
|
|
|
|
|
- logging.info(f'new trash created at {trashdir}.')
|
|
|
|
|
- trashdir.mkdir()
|
|
|
|
|
trashes.append(trashdir)
|
|
trashes.append(trashdir)
|
|
|
|
|
+ return trashes
|
|
|
|
|
+
|
|
|
|
|
|
|
|
- for name in args.names:
|
|
|
|
|
|
|
+def trash(names, config, dry_run=False):
|
|
|
|
|
+ for name in names:
|
|
|
name = Path(os.path.expandvars(name))
|
|
name = Path(os.path.expandvars(name))
|
|
|
|
|
|
|
|
if not (name.is_file() or name.is_dir()):
|
|
if not (name.is_file() or name.is_dir()):
|
|
@@ -63,10 +62,16 @@ def main():
|
|
|
name = name.absolute()
|
|
name = name.absolute()
|
|
|
|
|
|
|
|
trashed = False
|
|
trashed = False
|
|
|
- for trashdir in trashes:
|
|
|
|
|
|
|
+ for trashdir in get_trashes(config):
|
|
|
try:
|
|
try:
|
|
|
if lowest_mount(trashdir) == lowest_mount(name):
|
|
if lowest_mount(trashdir) == lowest_mount(name):
|
|
|
- logging.info(f'Will move {name} to {trashdir}')
|
|
|
|
|
|
|
+ newname = (trashdir / datetime.now().isoformat(
|
|
|
|
|
+ timespec='seconds').replace(':', '_') /
|
|
|
|
|
+ '/'.join(name.parts[1:]))
|
|
|
|
|
+ newname.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
+ logging.debug(f'Will move {name} to {newname}')
|
|
|
|
|
+ if not dry_run:
|
|
|
|
|
+ name.rename(newname)
|
|
|
trashed = True
|
|
trashed = True
|
|
|
break
|
|
break
|
|
|
except ValueError:
|
|
except ValueError:
|
|
@@ -76,5 +81,37 @@ def main():
|
|
|
logging.warning(f'Unable to trash {name}')
|
|
logging.warning(f'Unable to trash {name}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def trashed_sort_key(trashed_path: Path) -> int:
|
|
|
|
|
+ # length of the used ISO format is 19!
|
|
|
|
|
+ date_string = trashed_path.parts[-1][-19:].replace('_', ':')
|
|
|
|
|
+ logging.debug(f'Extracted {date_string} from {trashed_path}')
|
|
|
|
|
+ return datetime.fromisoformat(date_string).timestamp()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def list_trashed(config):
|
|
|
|
|
+ """ Yield trashed entries for trashdirs of given config
|
|
|
|
|
+ """
|
|
|
|
|
+ for trashdir in get_trashes(config):
|
|
|
|
|
+ for entry in sorted(trashdir.iterdir(), key=trashed_sort_key):
|
|
|
|
|
+ yield entry
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ args = parse_args()
|
|
|
|
|
+ logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
|
|
|
|
+
|
|
|
|
|
+ config = getconfig()
|
|
|
|
|
+ logging.debug(config)
|
|
|
|
|
+
|
|
|
|
|
+ if args.list:
|
|
|
|
|
+ print(*list(list_trashed(config)), sep='\n')
|
|
|
|
|
+ sys.exit()
|
|
|
|
|
+
|
|
|
|
|
+ trash(args.names, config, dry_run=args.dry_run)
|
|
|
|
|
+
|
|
|
|
|
+ if args.dry_run:
|
|
|
|
|
+ logging.info('Dry run. Nothin happened. I guess.')
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
|
main()
|
|
main()
|