Automatically cleanup your downloads

My downloads folder is a mess. I rarely clean it, it hogs up enormous amounts of disk space, and I really only need about a fraction of the files in there to be kept.

I wrote a script to automatically clean the folder daily, it moves files between four different folders, Last week, Old, and Deleting soon before finally permanently erasing them after 30 days while leaving anything in Keep untouched.

It's a nice, quick, and neat way to keep things clean. 🙂

Before you copy it you should read the rules...

The rules

ConditionAction
New files < 7 daysNothing happens. Very recent files are kept as they are.
> 7 daysIt will be moved into the Last week directory.
> 14 daysMoved into the Old folder
> 23 daysMoved into the Deleting soon folder
>= 30 daysAutomatically erased from your computer
Files in KeepAnything placed into the Keep folder is ignored by the script

The script

import os
import shutil
import argparse
import subprocess
from datetime import datetime, timedelta
from pathlib import Path

def send_macos_notification(title, message):
    applescript_cmd = f'display notification "{message}" with title "{title}"'
    subprocess.run(['osascript', '-e', applescript_cmd])

def create_log_file():
    logs_folder = Path("~/.logs/downloads_cleaner").expanduser()
    logs_folder.mkdir(parents=True, exist_ok=True)
    log_file = logs_folder / f"{datetime.now().strftime('%Y-%m-%d')}_downloads_cleaner.log"
    return log_file

def remove_old_log_files(max_age_days):
    logs_folder = Path("~/.logs/downloads_cleaner").expanduser()
    now = datetime.now()

    for log_file in logs_folder.iterdir():
        if log_file.is_file():
            file_age = now - datetime.fromtimestamp(log_file.stat().st_mtime)
            if file_age > timedelta(days=max_age_days):
                log_file.unlink()

# Command-line arguments
parser = argparse.ArgumentParser(description='Sort downloads folder by date downloaded.')
parser.add_argument('--dry-run', action='store_true', help='Perform a dry run without actually moving or deleting files')
args = parser.parse_args()

downloads_folder = Path("~/Downloads").expanduser()
last_week_folder = downloads_folder / "Last week"
old_folder = downloads_folder / "Old"
deleting_soon_folder = downloads_folder / "Deleting soon"
keep_folder = downloads_folder / "Keep"

# Create folders if they don't exist
last_week_folder.mkdir(exist_ok=True)
old_folder.mkdir(exist_ok=True)
deleting_soon_folder.mkdir(exist_ok=True)
keep_folder.mkdir(exist_ok=True)

log_file = create_log_file()
remove_old_log_files(30)

deletions = 0
moved = 0

with log_file.open('a') as log:
    now = datetime.now()

    # Delete files in the "Deleting soon" folder if older than 30 days
    for item in deleting_soon_folder.iterdir():
        if item.is_file():
            file_age = now - datetime.fromtimestamp(item.stat().st_mtime)
            if file_age > timedelta(days=30):
                deletions += 1
                if args.dry_run:
                    print(f'[DRY RUN] Would delete: {item}')
                else:
                    log.write(f"{datetime.now()} - Deleting: {item}\n")
                    item.unlink()

    # Move files to respective folders based on their age
    for item in downloads_folder.iterdir():
        if item.is_file():
            # Check if the file is inside the "Keep" folder
            if item.parent == keep_folder:
                continue

            # Calculate the age of the file
            file_age = now - datetime.fromtimestamp(item.stat().st_mtime)
            destination = None

            if file_age > timedelta(days=22):
                destination = deleting_soon_folder
            elif file_age > timedelta(days=14):
                destination = old_folder
            elif file_age > timedelta(days=7):
                destination = last_week_folder

            if destination:
                moved += 1
                if args.dry_run:
                    print(f'[DRY RUN] Would move: {item} to {destination / item.name}')
                else:
                    log.write(f"{datetime.now()} - Moving: {item} to {destination / item.name}\n")
                    shutil.move(str(item), destination / item.name)

    # Send macOS notification
    notification_message = f"{deletions} deleted, {moved} moved."
    send_macos_notification("Downloads cleaner", notification_message)

How to use it

You'll need Python, the script has also been written for my mac so Windows and Linux users will need to tweak the location of their downloads and snip out the notification function.

To test the script you can pass the --dry-run parameter to see what it would have done without touching anything.

python3 downloads_cleaner.py --dry-run

To check on any files that have been moved or deleted you can check the log file written to ~/.logs/downloads_cleaner.

cat ~/.logs/downloads_cleaner/2021-04-30_downloads_cleaner.log

Automating it

The next step is to automate this thing, if you're on unix then the obvious choice is a cron job. I've set mine to run daily at 9am.

crontab -e
0 9 * * * python3 ~/scripts/downloads_cleaner.py

If you're on Windows, Task Scheduler is your friend.

Mac users

If you're on macOS may wake up the next morning to find a message for you waiting in your terminal, you'll need to give cron access to your downloads folder. In order to do this...

  1. Go to System Preferences > Security & Privacy > Privacy > Full Disk Access
  2. Click the lock icon to make changes
  3. Click the + icon and add crontab to the list
  4. Restart your computer

And that's it. Thanks for reading. 🙂