~gpanders/pushbroom

16d2aa1dcbc3717547de519cf3bd2de3f2b215bf — Greg Anders 2 years ago 94b0c9c
Rename project to pushbroom
8 files changed, 81 insertions(+), 143 deletions(-)

M README.md
D install.sh
D poetry.lock
R janitor.conf => pushbroom.conf
M pyproject.toml
R src/{janitor/__init__.py => pushbroom/__init__.py}
R src/{janitor/console.py => pushbroom/console.py}
R src/{janitor/sweep.py => pushbroom/sweep.py}
M README.md => README.md +23 -20
@@ 1,35 1,38 @@
# Janitor
Bash script to automate deleting old files in a specified directory
# Pushbroom
Keep select filesystem paths free of clutter

## Installation
    git clone git@github.com:gpanders/Janitor.git
    cd Janitor
    ./install.sh

By default, Janitor will monitor `$HOME/Downloads` and move any file older than
30 days to your Trash folder (`$HOME/.Trash`). These settings can be changed using
the installation script (see below).
Directly from source:

## Options
    git clone https://github.com/gpanders/pushbroom
    pip install ./pushbroom

**Target directory**
Or from PyPI:

  Specify which directory to monitor
    pip install pushbroom

    -d <directory>
Pushbroom comes with an example configuration file `pushbroom.conf`. You can
copy this to either `$XDG_CONFIG_HOME/pushbroom/config` or `$HOME/.pushbroomrc` and
modify it to your needs.

**Trash directory**
## Configuration

  Specify where to move files after deletion (if `-x` option is not set)
The following configuration items are recognized in `pushbroom.conf`:

    -t <directory>
**Path**

**Number of days to keep files**
Specify which directory to monitor

    -n <integer>
**Trash**

**Hard delete files**
Specify where to move files after deletion. If this option is not provided,
files will simply be deleted.

  Flag indicating to not move files to another directory, but rather permanently delete them
**NumDays**

    -x
Number of days to keep files in `Path` before they are removed.

**Ignore**

Regular expression pattern of files or directories to ignore.

D install.sh => install.sh +0 -70
@@ 1,70 0,0 @@
#!/usr/bin/env bash

# Set defaults
TARGET_DIR=$HOME/Downloads
NUM_DAYS=30
TRASH_DIR=$HOME/.Trash

function usage() {
    echo "Usage: ./install.sh [-x] [-d <target directory>] [-t <trash directory>] [-n <integer>]"
}

while getopts "xd:t:n:" opt; do
    case $opt in
        d)
            TARGET_DIR="$OPTARG"
            ;;
        t)
            TRASH_DIR="$OPTARG"
            ;;
        x)
            TRASH_DIR=
            ;;
        n)
            NUM_DAYS="$OPTARG"
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
done

echo "Target directory is $TARGET_DIR"
echo "Deleting files older than $NUM_DAYS days"
if [ -z "$TRASH_DIR" ];  then
    echo "Hard deleting files - not using a Trash directory"
else
    echo "Trash direcotry is $TRASH_DIR"
fi

function create_crontab() {
    echo "# Begin Janitor job"
    echo "0  */6  *  *  * $(pwd)/bin/janitor \"$1\" \"$2\" \"$3\""
    echo "# End Janitor job"
}

function check_crontab() {
    if [[ $1 == *"# Begin Janitor job"* && \
          $1 == *"# End Janitor job"* ]]; then
        return 1
    else
        return 0
    fi
}

# Create crontab
if check_crontab "$(crontab -l 2>/dev/null)"; then
    # Crontab exists but does not already contain ours
    echo "Creating crontab entry."
    entry=$(create_crontab "$TARGET_DIR" "$NUM_DAYS" "$TRASH_DIR")
    (crontab -l 2>/dev/null; [[ $? -eq 0 ]] && echo " " ; echo "$entry") | crontab -
else
    echo "Janitor cronjob already exists. Remove the current job (using crontab -e) and re-run this installation script."
    exit 1
fi

mkdir -p logs

echo "Installation complete."
exit 0

D poetry.lock => poetry.lock +0 -7
@@ 1,7 0,0 @@
package = []

[metadata]
content-hash = "7ba5937a13359cece2ea91521dd10c13fb5e097b75e8a55458fc9d5570ab67b7"
python-versions = "^3.4"

[metadata.hashes]

R janitor.conf => pushbroom.conf +2 -1
@@ 1,3 1,4 @@
[~/Downloads]
[Downloads]
Path = ~/Downloads
Trash = ~/.Trash
NumDays = 30

M pyproject.toml => pyproject.toml +5 -5
@@ 1,18 1,18 @@
[tool.poetry]
name = "janitor"
name = "pushbroom"
version = "0.1.0"
description = "Clean up your filesystem"
authors = ["Greg Anders <greg@gpanders.com>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/gpanders/janitor"
include = ["janitor.conf"]
repository = "https://github.com/gpanders/pushbroom"
include = ["pushbroom.conf"]
packages = [
    { include = "janitor", from = "src" },
    { include = "pushbroom", from = "src" },
]

[tool.poetry.scripts]
janitor = "janitor.console:run"
pushbroom = "pushbroom.console:run"

[tool.poetry.dependencies]
python = "^3.4"

R src/janitor/__init__.py => src/pushbroom/__init__.py +0 -0
R src/janitor/console.py => src/pushbroom/console.py +39 -27
@@ 5,27 5,14 @@ import logging
import os
import sys

import janitor

LOG_FILE = os.path.expanduser("~/.cache/janitor/janitor.log")
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    filename=LOG_FILE,
)
import pushbroom


def run():
    """Run janitor"""
    """Run pushbroom"""
    parser = argparse.ArgumentParser(description="Clean up your filesystem.")
    parser.add_argument(
        "-c",
        "--config",
        type=str,
        help="path to config file",
    )
    parser.add_argument("-c", "--config", type=str, help="path to config file")
    parser.add_argument("-v", "--verbose", action="store_true", help="verbose output")
    parser.add_argument(
        "-n",
        "--dry-run",


@@ 35,29 22,54 @@ def run():

    args = parser.parse_args()

    # Set up logging
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    ch = logging.StreamHandler()
    ch.setFormatter(logging.Formatter("%(message)s"))
    ch.setLevel(logging.ERROR)

    if not args.dry_run:
        # If not doing a dry run log to a file
        log_file = os.path.expanduser("~/.cache/pushbroom/pushbroom.log")
        os.makedirs(os.path.dirname(log_file), exist_ok=True)
        fh = logging.FileHandler(log_file)
        fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
        fh.setLevel(logging.INFO)
        logger.addHandler(fh)

    if args.verbose or args.dry_run:
        # If verbose or doing a dry run print info to console
        ch.setLevel(logging.INFO)

    logger.addHandler(ch)

    if not args.config:
        # Look under XDG_CONFIG_HOME first, then look for ~/.janitorrc
        xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
        args.config = os.path.join(xdg_config_home, "janitor", "config")
        # Look under XDG_CONFIG_HOME first, then look for ~/.pushbroomrc
        xdg_config_home = os.environ.get(
            "XDG_CONFIG_HOME", os.path.expanduser("~/.config")
        )
        args.config = os.path.join(xdg_config_home, "pushbroom", "config")
        if not os.path.exists(args.config):
            args.config = os.path.expanduser("~/.janitorrc")
            args.config = os.path.expanduser("~/.pushbroomrc")

    config = configparser.ConfigParser()
    try:
        with open(args.config, "r") as f:
            config.read_file(f)
    except FileNotFoundError:
        print("Configuration file {} not found".format(args.config))
        logging.error("Configuration file {} not found".format(args.config))
        sys.exit(1)

    for path in config.sections():
    for section in config.sections():
        path = config.get(section, "path")
        fullpath = os.path.abspath(os.path.expanduser(path))
        if not os.path.isdir(fullpath):
            logging.error("No such directory: %s", fullpath)
        else:
            num_days = config.getint(path, "numdays")
            trash = config.get(path, "trash", fallback=None)
            ignore = config.get(path, "ignore", fallback="").split(",")
            num_days = config.getint(section, "numdays")
            trash = config.get(section, "trash", fallback=None)
            ignore = config.get(section, "ignore", fallback="").split(",")
            ignored = r"|".join([fnmatch.translate(x) for x in ignore])

            if trash:


@@ 65,4 77,4 @@ def run():
                if not os.path.isdir(trash):
                    logging.error("No such directory %s", trash)

            janitor.sweep(fullpath, num_days, ignored, trash, args.dry_run)
            pushbroom.sweep(fullpath, num_days, ignored, trash, args.dry_run)

R src/janitor/sweep.py => src/pushbroom/sweep.py +12 -13
@@ 17,7 17,7 @@ def sweep(path, num_days, ignored, trash=None, dry_run=False):

    """
    now = time.time()
    logging.info("Starting janitor")
    logging.info("Starting pushbroom")
    num_seconds = num_days * SECONDS_PER_DAY
    thresh = now - num_seconds
    for root, dirs, files in os.walk(path):


@@ 26,16 26,15 @@ def sweep(path, num_days, ignored, trash=None, dry_run=False):
        files = [f for f in files if not re.match(ignored, f)]
        for file in files:
            fpath = os.path.join(root, file)
            if os.stat(fpath).st_mtime < thresh:
                if trash:
                    if dry_run:
                        print("Moving {} to {}".format(file, trash))
            try:
                if os.stat(fpath).st_mtime < thresh:
                    if trash:
                        logging.info("Moving %s to %s", fpath, trash)
                        if not dry_run:
                            os.rename(fpath, os.path.join(trash, file))
                    else:
                        logging.info("Moving %s to %s", file, trash)
                        os.rename(fpath, os.path.join(trash, file))
                else:
                    if dry_run:
                        print("Deleting {}".format(file))
                    else:
                        logging.info("Deleting %s", file)
                        os.remove(fpath)
                        logging.info("Deleting %s", fpath)
                        if not dry_run:
                            os.remove(fpath)
            except FileNotFoundError:
                pass