| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- #! /usr/bin/env python3
- """ Move files and directories to custom trash directories, taking the
- corresponding mount points into account.
- """
- from pathlib import Path
- import argparse
- import logging
- import sys
- import os
- import json
- from datetime import datetime
- DEFAULT_CONFIG = {
- 'trashes': [
- '$HOME/trash/',
- ]
- }
- def parse_args():
- 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()
- def lowest_mount(path: Path) -> Path:
- """Find lowest level mount point of given path"""
- while not path.is_mount() and path.parent != path:
- return lowest_mount(path.parent)
- return path
- def getconfig() -> dict:
- config_file = Path('~/.config/trash.json').expanduser()
- config = DEFAULT_CONFIG
- if config_file.is_file():
- with open(config_file) as f:
- config.update(json.load(f))
- return config
- def get_trashes(config):
- trashes: list[Path] = []
- for trashdir in config['trashes']:
- trashdir = Path(os.path.expandvars(trashdir)).expanduser().resolve()
- trashes.append(trashdir)
- return trashes
- def trash(names, config, dry_run=False):
- for name in names:
- name = Path(os.path.expandvars(name))
- if not (name.is_file() or name.is_dir()):
- logging.warning(f'{name} is not a file nor directory')
- continue
- name = name.absolute()
- trashed = False
- for trashdir in get_trashes(config):
- try:
- if lowest_mount(trashdir) == lowest_mount(name):
- 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
- break
- except ValueError:
- pass
- if not trashed:
- 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__':
- main()
|