Skip to content
Open
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
16 changes: 10 additions & 6 deletions plugins/stash-scheduler/stash-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,28 @@ settings:
displayName: Scan Frequency
description: >
How often to run the library scan.
Valid values: hourly, daily, weekly.
Hourly ignores the Time of Day setting.
Simple values: hourly, daily, weekly. Hourly ignores the Time of Day
setting. For more control, enter a standard 5-field crontab expression
instead (e.g. "10 7,17 * * *" fires at 7:10 AM and 5:10 PM every day;
"0 3 * * 1" fires at 3:00 AM every Monday). When a crontab expression
is entered, the Time of Day and Day of Week settings below are ignored.
Cron field order: minute hour day-of-month month day-of-week.
type: STRING

time_of_day:
displayName: Time of Day (HH:MM)
description: >
The time to run the scan in 24-hour HH:MM format. Used for Daily and
Weekly schedules; ignored for Hourly. Examples: 02:00, 14:30, 20:45.
Defaults to 02:00 if not set.
Weekly schedules; ignored for Hourly and when Frequency is a crontab
expression. Examples: 02:00, 14:30, 20:45. Defaults to 02:00 if not set.
type: STRING

day_of_week:
displayName: Day of Week (Weekly only)
description: >
The day of the week on which to run the scan when Frequency is set to
Weekly. Valid values: mon, tue, wed, thu, fri, sat, sun.
Defaults to sun if not set.
Weekly. Valid values: mon, tue, wed, thu, fri, sat, sun. Ignored when
Frequency is a crontab expression. Defaults to sun if not set.
type: STRING

timezone:
Expand Down
54 changes: 49 additions & 5 deletions plugins/stash-scheduler/stash_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,47 @@ def _parse_time_of_day(raw, warn):
return 2, 0


def _is_cron_expr(value):
"""Return True if value looks like a 5-field crontab expression."""
import re as _re
parts = _re.split(r'\s+', str(value).strip())
return len(parts) == 5 and any(c in value for c in (' ', '\t'))
Comment on lines +186 to +190


def validate_and_coerce_settings(settings, warn):
VALID_FREQUENCIES = {"hourly", "daily", "weekly"}
VALID_DAYS = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"}

freq = str(settings.get("frequency", "daily")).strip().lower()
if freq not in VALID_FREQUENCIES:
warn(f"[Stash Scheduler] Invalid frequency {freq!r} — defaulting to 'daily'.")
freq = "daily"
settings["frequency"] = freq
raw_freq = str(settings.get("frequency", "daily")).strip()

if _is_cron_expr(raw_freq):
# Validate the cron expression via APScheduler if available; otherwise
Comment on lines +197 to +200
# trust the structural check and let the daemon catch any errors at start.
cron_valid = True
try:
from apscheduler.triggers.cron import CronTrigger
CronTrigger.from_crontab(raw_freq)
except ImportError:
pass # APScheduler not installed yet; daemon will catch invalid exprs at start
except Exception as exc:
cron_valid = False
warn(
f"[Stash Scheduler] Invalid cron expression {raw_freq!r} ({exc})"
" — defaulting to 'daily'."
)

if cron_valid:
settings["frequency"] = "cron"
settings["cron_expr"] = raw_freq
else:
raw_freq = "daily"

if settings.get("frequency") not in ("cron",):
freq = raw_freq.lower()
if freq not in VALID_FREQUENCIES:
warn(f"[Stash Scheduler] Invalid frequency {freq!r} — defaulting to 'daily'.")
freq = "daily"
settings["frequency"] = freq

raw_time = settings.get("time_of_day", "02:00")
hour, minute = _parse_time_of_day(raw_time, warn)
Expand Down Expand Up @@ -604,6 +636,16 @@ def scheduled_job():
if frequency == "hourly":
scheduler.add_job(trigger="cron", minute=0, **job_kwargs)
log.info(f"Schedule: every hour at :00 ({timezone})")
elif frequency == "cron":
from apscheduler.triggers.cron import CronTrigger
cron_expr = settings["cron_expr"]
try:
cron_trigger = CronTrigger.from_crontab(cron_expr, timezone=timezone)
except Exception as exc:
log.error(f"Invalid cron expression {cron_expr!r}: {exc}. Daemon exiting.")
sys.exit(1)
scheduler.add_job(trigger=cron_trigger, **job_kwargs)
log.info(f"Schedule: crontab '{cron_expr}' ({timezone})")
elif frequency == "weekly":
scheduler.add_job(
trigger="cron", day_of_week=day_of_week, hour=hour, minute=minute, **job_kwargs
Expand Down Expand Up @@ -691,6 +733,8 @@ def task_start_scheduler(stash, server_connection, settings):
tz = settings.get("timezone", "UTC")
if freq == "hourly":
schedule_desc = f"every hour at :00 ({tz})"
elif freq == "cron":
schedule_desc = f"crontab '{settings['cron_expr']}' ({tz})"
elif freq == "weekly":
schedule_desc = f"weekly on {dow.upper()} at {time_str} ({tz})"
else:
Expand Down