Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/test-screenshot-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: Test Screenshot Cleanup Script

'on':
push:
paths:
- 'scripts/screenshot_cleanup'
- 'tests/test_screenshot_cleanup.bats'
- '.github/workflows/test-screenshot-cleanup.yml'
pull_request:
paths:
- 'scripts/screenshot_cleanup'
- 'tests/test_screenshot_cleanup.bats'
- '.github/workflows/test-screenshot-cleanup.yml'

permissions:
contents: read

jobs:
test-macos:
name: Test on macOS
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install dependencies
run: |
# Install bats-core
brew install bats-core

# Install trash (optional but recommended)
brew install trash || true

- name: Run screenshot cleanup tests
env:
TERM: xterm
run: |
bats -p --print-output-on-failure tests/test_screenshot_cleanup.bats

- name: Test script execution
env:
DRY_RUN: true
run: |
# Test that the script can run without errors
./scripts/screenshot_cleanup

test-ubuntu:
name: Test on Ubuntu
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup Bats and bats libs
id: setup-bats
uses: bats-core/bats-action@42fcc8700f773c075a16a90eb11674c0318ad507 # 3.0.1

- name: Install dependencies
run: |
# Install trash-cli (Linux version of trash)
sudo apt-get update --yes
DEBIAN_FRONTEND=noninteractive sudo apt-get install --yes --no-install-recommends trash-cli || true

- name: Run screenshot cleanup tests
env:
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
TERM: xterm
run: |
bats -p --print-output-on-failure tests/test_screenshot_cleanup.bats

- name: Test script execution
env:
DRY_RUN: true
run: |
# Test that the script can run without errors
./scripts/screenshot_cleanup
293 changes: 293 additions & 0 deletions scripts/screenshot_cleanup
Comment thread
colindean marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
#!/usr/bin/env bash

set -euo pipefail

# Configuration
DRY_RUN=${DRY_RUN:-false}
SCREENSHOT_PREFIX="Screenshot"

# Logging functions copied from _hejmo_stdlib_helpers.sh
CHECKMARK="\u2713"
CROSSMARK="\u2717"
EXCLAMATION="\u26A0"
INFOMARK="\u2139"
INTERROBANG="\u203D"

BLUE_COLOR="\033[0;34m"
RED_COLOR="\033[0;31m"
YELLOW_COLOR="\033[0;33m"
GRAY_COLOR="\033[0;37m"
RESET_COLOR="\033[0m"
BOLD_COLOR="\033[1m"

SUCCESS_COLOR="${BLUE_COLOR}"
FAILURE_COLOR="${RED_COLOR}"
INFO_COLOR="${BLUE_COLOR}"
WARNING_COLOR="${YELLOW_COLOR}"
DEBUG_COLOR="${GRAY_COLOR}"

SUCCESS_TEXT="${SUCCESS_COLOR}${BOLD_COLOR}[${CHECKMARK} SUCC]${RESET_COLOR}"
FAILURE_TEXT="${FAILURE_COLOR}[${CROSSMARK} FAIL]${RESET_COLOR}"
INFO_TEXT="${INFO_COLOR}[${INFOMARK} INFO]${RESET_COLOR}"
WARNING_TEXT="${WARNING_COLOR}[${EXCLAMATION} WARN]${RESET_COLOR}"
DEBUG_TEXT="${DEBUG_COLOR}[${INTERROBANG} DBUG]${RESET_COLOR}"

stderr_log() {
echo -e "${*}" >&2
}

log_success() {
stderr_log "${SUCCESS_TEXT} ${BOLD_COLOR}${*}${RESET_COLOR}"
}
log_failure() {
stderr_log "${FAILURE_TEXT} ${*}"
}
log_info() {
stderr_log "${INFO_TEXT} ${*}"
}
log_warning() {
stderr_log "${WARNING_TEXT} ${*}"
}
log_debug() {
if [[ -n "${DEBUG:-}" ]]; then
stderr_log "${DEBUG_TEXT} ${*}"
fi
}

# Get screenshot directory based on OS
get_screenshot_dir() {
local os
os=$(uname -s)

case "${os}" in
Darwin)
# macOS: Use defaults command to get screenshot location
defaults read com.apple.screencapture location 2>/dev/null || echo "${HOME}/Desktop"
;;
Linux)
# Linux/GNOME: Try to get from gsettings, fallback to default
if command -v gsettings &>/dev/null; then
local gnome_dir
gnome_dir=$(gsettings get org.gnome.gnome-screenshot auto-save-directory 2>/dev/null | tr -d "'")
if [[ -n "${gnome_dir}" ]]; then
# Expand ~ to $HOME if present
gnome_dir="${gnome_dir/#\~/${HOME}}"
echo "${gnome_dir}"
else
echo "${HOME}/Pictures"
fi
else
echo "${HOME}/Pictures"
fi
;;
*)
echo "${HOME}/Pictures"
;;
esac
}

# Extract timestamp from filename
extract_timestamp() {
local filename="$1"
local basename_only
basename_only=$(basename "${filename}")
local os
os=$(uname -s)

case "${os}" in
Darwin)
# macOS: "Screenshot 2024-01-15 at 10.30.45 AM.png"
if [[ ${basename_only} =~ ([0-9]{4}-[0-9]{2}-[0-9]{2})\ at\ ([0-9]{1,2}\.[0-9]{2}\.[0-9]{2})(\ [AP]M)? ]]; then
local date="${BASH_REMATCH[1]}"
local time="${BASH_REMATCH[2]//./:}"
echo "${date} ${time}"
else
log_failure "Unable to extract timestamp from filename: ${basename_only}"
return 1
fi
;;
Linux)
# GNOME: "Screenshot from 2024-01-15 10-30-45.png"
if [[ ${basename_only} =~ ([Ff]rom )?([0-9]{4}-[0-9]{2}-[0-9]{2})\ ([0-9]{2}-[0-9]{2}-[0-9]{2}) ]]; then
local date="${BASH_REMATCH[2]}"
local time="${BASH_REMATCH[3]//-/:}"
echo "${date} ${time}"
else
log_failure "Unable to extract timestamp from filename: ${basename_only}"
return 1
fi
;;
*)
log_failure "Unsupported OS: ${os}"
return 1
;;
esac
}

# Calculate age in days
age_in_days() {
local timestamp="$1"
local now
local file_time
local os
os=$(uname -s)

now=$(date +%s)

case "${os}" in
Darwin)
# macOS: Use -j flag with BSD date
file_time=$(date -j -f "%Y-%m-%d %H:%M:%S" "${timestamp}" +%s 2>/dev/null)
;;
Linux)
# Linux: Use GNU date
file_time=$(date -d "${timestamp}" +%s 2>/dev/null)
;;
*)
log_failure "Unsupported OS: ${os}"
return 1
;;
esac

if [[ -z "${file_time}" ]]; then
log_failure "Unable to parse timestamp: ${timestamp}"
return 1
fi

echo $(((now - file_time) / 86400))
}

# Trash a file
trash() {
local file="$1"
log_info "🗑 ⬅ $(basename "${file}")"
if [[ "${DRY_RUN}" != "true" ]]; then
if command -v trash &>/dev/null; then
command trash "${file}"
else
log_warning "trash command not found, using rm instead"
rm "${file}"
fi
fi
}

# Archive a file
archive() {
local file="$1"
local archive_dir="$2"
log_info "🗄 ⬅ $(basename "${file}")"
if [[ ! -d "${archive_dir}" ]]; then
log_info "Creating ${archive_dir}"
if [[ "${DRY_RUN}" != "true" ]]; then
mkdir -p "${archive_dir}"
fi
fi
local new_name
new_name="${archive_dir}/$(basename "${file}")"
if [[ "${DRY_RUN}" != "true" ]]; then
mv "${file}" "${new_name}"
fi
}

# Process archive files
process_archive() {
local archive_dir="$1"
local max_age="${2:-15}"

log_info "Looking in ${archive_dir} for deletion…"

if [[ ! -d "${archive_dir}" ]]; then
log_info "Archive directory does not exist: ${archive_dir}"
return 0
fi

find "${archive_dir}" -type f -name "${SCREENSHOT_PREFIX}*" 2>/dev/null | while read -r file; do
local timestamp
local age

# shellcheck disable=SC2310
if timestamp=$(extract_timestamp "${file}" 2>/dev/null); then
# shellcheck disable=SC2310
if age=$(age_in_days "${timestamp}" 2>/dev/null); then
if ((age > max_age)); then
trash "${file}"
else
log_debug "🗄 ⬇ $(basename "${file}")"
fi
else
log_warning "Could not calculate age for: $(basename "${file}")"
fi
else
log_warning "Could not extract timestamp from: $(basename "${file}")"
fi
done
}

# Process screenshot files
process_screenshots() {
local screenshot_dir="$1"
local archive_dir="$2"
local max_age="${3:-7}"

log_info "Looking in ${screenshot_dir} for archival…"

if [[ ! -d "${screenshot_dir}" ]]; then
log_info "Screenshot directory does not exist: ${screenshot_dir}"
return 0
fi

find "${screenshot_dir}" -maxdepth 1 -type f -name "${SCREENSHOT_PREFIX}*" 2>/dev/null | while read -r file; do
local timestamp
local age

# shellcheck disable=SC2310
if timestamp=$(extract_timestamp "${file}" 2>/dev/null); then
# shellcheck disable=SC2310
if age=$(age_in_days "${timestamp}" 2>/dev/null); then
if ((age > max_age)); then
archive "${file}" "${archive_dir}"
else
log_debug "📂 ⬇ $(basename "${file}")"
fi
else
log_warning "Could not calculate age for: $(basename "${file}")"
fi
else
log_warning "Could not extract timestamp from: $(basename "${file}")"
fi
done
}

# Main function
main() {
local screenshot_dir
local archive_dir

# Get screenshot directory based on OS
screenshot_dir=$(get_screenshot_dir)

# Allow override via environment variable
screenshot_dir="${SCREENSHOT_DIR:-${screenshot_dir}}"

# Set archive directory
archive_dir="${screenshot_dir}/Screenshot Archive"
archive_dir="${ARCHIVE_DIR:-${archive_dir}}"

log_info "Screenshot directory: ${screenshot_dir}"
log_info "Archive directory: ${archive_dir}"
log_info "Dry run: ${DRY_RUN}"
echo ""

# Process archived screenshots (delete after 15 days)
process_archive "${archive_dir}" 15

echo ""

# Process screenshots (archive after 7 days)
process_screenshots "${screenshot_dir}" "${archive_dir}" 7
}

# Run main function if script is executed (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Loading
Loading