Class: Clacky::Server::Scheduler
- Inherits:
-
Object
- Object
- Clacky::Server::Scheduler
- Defined in:
- lib/clacky/server/scheduler.rb
Overview
Scheduler reads ~/.clacky/schedules.yml and runs tasks on a cron-like schedule.
It starts a background thread that ticks every 60 seconds, checks all configured schedules, and fires any task whose cron expression matches the current time.
Schedule file format (~/.clacky/schedules.yml):
- name: daily_report
task: daily_report # references ~/.clacky/tasks/daily_report.md
cron: "0 9 * * 1-5" # standard 5-field cron expression
enabled: true # optional, defaults to true
Cron field order: minute hour day-of-month month day-of-week
Constant Summary collapse
- SCHEDULES_FILE =
File.("~/.clacky/schedules.yml")
- TASKS_DIR =
File.("~/.clacky/tasks")
Instance Method Summary collapse
-
#add_schedule(name:, task:, cron:, enabled: true) ⇒ Object
Add or update a schedule entry in schedules.yml.
-
#create_cron_task(name:, content:, cron:, enabled: true) ⇒ Object
Create a task file and its schedule in one step.
-
#delete_cron_task(name) ⇒ Object
Delete a cron-task: remove both the task file and its schedule.
-
#delete_task(task_name) ⇒ Object
Delete a task file and remove all schedules that reference it.
-
#initialize(session_registry:, session_builder:) ⇒ Scheduler
constructor
A new instance of Scheduler.
-
#list_cron_tasks ⇒ Object
Return a merged list of cron-tasks (task content + schedule metadata).
-
#list_tasks ⇒ Object
List all existing task names.
-
#read_task(task_name) ⇒ Object
Read the prompt content of a named task.
-
#remove_schedule(name) ⇒ Object
Remove a schedule entry by name.
- #running? ⇒ Boolean
-
#schedules ⇒ Object
Return all schedules from the config file.
-
#start ⇒ Object
Start the background scheduler thread.
-
#stop ⇒ Object
Stop the background scheduler thread gracefully.
-
#task_file_path(task_name) ⇒ Object
Return the file path for a task.
-
#update_cron_task(name, content: nil, cron: nil, enabled: nil) ⇒ Object
Update a cron-task: optionally update content and/or schedule fields.
-
#update_schedule(name, cron: nil, enabled: nil) ⇒ Object
Update an existing schedule entry (cron and/or enabled).
-
#write_task(task_name, content) ⇒ Object
Write the prompt content for a named task.
Constructor Details
#initialize(session_registry:, session_builder:) ⇒ Scheduler
Returns a new instance of Scheduler.
26 27 28 29 30 31 32 |
# File 'lib/clacky/server/scheduler.rb', line 26 def initialize(session_registry:, session_builder:) @registry = session_registry @session_builder = session_builder # callable: (name:, working_dir:) -> session_id @thread = nil @running = false @mutex = Mutex.new end |
Instance Method Details
#add_schedule(name:, task:, cron:, enabled: true) ⇒ Object
Add or update a schedule entry in schedules.yml.
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/clacky/server/scheduler.rb', line 66 def add_schedule(name:, task:, cron:, enabled: true) list = load_schedules # Remove existing entry with the same name list.reject! { |s| s["name"] == name } list << { "name" => name, "task" => task, "cron" => cron, "enabled" => enabled } save_schedules(list) end |
#create_cron_task(name:, content:, cron:, enabled: true) ⇒ Object
Create a task file and its schedule in one step.
104 105 106 107 |
# File 'lib/clacky/server/scheduler.rb', line 104 def create_cron_task(name:, content:, cron:, enabled: true) write_task(name, content) add_schedule(name: name, task: name, cron: cron, enabled: enabled) end |
#delete_cron_task(name) ⇒ Object
Delete a cron-task: remove both the task file and its schedule.
118 119 120 121 122 |
# File 'lib/clacky/server/scheduler.rb', line 118 def delete_cron_task(name) removed_schedule = remove_schedule(name) removed_task = delete_task(name) removed_schedule || removed_task end |
#delete_task(task_name) ⇒ Object
Delete a task file and remove all schedules that reference it. Returns true if the task file existed and was deleted, false otherwise.
170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/clacky/server/scheduler.rb', line 170 def delete_task(task_name) path = task_file_path(task_name) return false unless File.exist?(path) File.delete(path) # Remove all schedules referencing this task load_schedules.select { |s| s["task"] == task_name }.each do |s| remove_schedule(s["name"]) end true end |
#list_cron_tasks ⇒ Object
Return a merged list of cron-tasks (task content + schedule metadata).
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/clacky/server/scheduler.rb', line 125 def list_cron_tasks schedule_map = load_schedules.each_with_object({}) do |s, h| h[s["task"]] = s if s.is_a?(Hash) end list_tasks.map do |task_name| content = begin; read_task(task_name); rescue StandardError; ""; end schedule = schedule_map[task_name] || {} { "name" => task_name, "content" => content, "cron" => schedule["cron"], "enabled" => schedule.fetch("enabled", true), "scheduled" => !schedule.empty? } end end |
#list_tasks ⇒ Object
List all existing task names.
160 161 162 163 164 165 166 |
# File 'lib/clacky/server/scheduler.rb', line 160 def list_tasks return [] unless Dir.exist?(TASKS_DIR) Dir.glob(File.join(TASKS_DIR, "*.md")).map do |path| File.basename(path, ".md") end.sort end |
#read_task(task_name) ⇒ Object
Read the prompt content of a named task.
146 147 148 149 150 151 |
# File 'lib/clacky/server/scheduler.rb', line 146 def read_task(task_name) path = task_file_path(task_name) raise "Task not found: #{task_name} (expected #{path})" unless File.exist?(path) File.read(path) end |
#remove_schedule(name) ⇒ Object
Remove a schedule entry by name.
80 81 82 83 84 85 86 |
# File 'lib/clacky/server/scheduler.rb', line 80 def remove_schedule(name) list = load_schedules before_count = list.size list.reject! { |s| s["name"] == name } save_schedules(list) list.size < before_count end |
#running? ⇒ Boolean
54 55 56 |
# File 'lib/clacky/server/scheduler.rb', line 54 def running? @running end |
#schedules ⇒ Object
Return all schedules from the config file.
59 60 61 |
# File 'lib/clacky/server/scheduler.rb', line 59 def schedules load_schedules end |
#start ⇒ Object
Start the background scheduler thread.
35 36 37 38 39 40 41 42 43 |
# File 'lib/clacky/server/scheduler.rb', line 35 def start @mutex.synchronize do return if @running @running = true @thread = Thread.new { run_loop } @thread.name = "clacky-scheduler" end end |
#stop ⇒ Object
Stop the background scheduler thread gracefully. NOTE: intentionally avoids Mutex here so it is safe to call from a signal trap context (Ruby disallows Mutex#synchronize inside traps).
48 49 50 51 52 |
# File 'lib/clacky/server/scheduler.rb', line 48 def stop @running = false @thread&.wakeup rescue nil @thread&.join(5) end |
#task_file_path(task_name) ⇒ Object
Return the file path for a task.
183 184 185 |
# File 'lib/clacky/server/scheduler.rb', line 183 def task_file_path(task_name) File.join(TASKS_DIR, "#{task_name}.md") end |
#update_cron_task(name, content: nil, cron: nil, enabled: nil) ⇒ Object
Update a cron-task: optionally update content and/or schedule fields.
110 111 112 113 114 115 |
# File 'lib/clacky/server/scheduler.rb', line 110 def update_cron_task(name, content: nil, cron: nil, enabled: nil) raise "Cron task not found: #{name}" unless list_tasks.include?(name) write_task(name, content) unless content.nil? update_schedule(name, cron: cron, enabled: enabled) if cron || !enabled.nil? end |
#update_schedule(name, cron: nil, enabled: nil) ⇒ Object
Update an existing schedule entry (cron and/or enabled). Returns false if the schedule does not exist.
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/clacky/server/scheduler.rb', line 90 def update_schedule(name, cron: nil, enabled: nil) list = load_schedules entry = list.find { |s| s["name"] == name } return false unless entry entry["cron"] = cron unless cron.nil? entry["enabled"] = enabled unless enabled.nil? save_schedules(list) true end |
#write_task(task_name, content) ⇒ Object
Write the prompt content for a named task.
154 155 156 157 |
# File 'lib/clacky/server/scheduler.rb', line 154 def write_task(task_name, content) FileUtils.mkdir_p(TASKS_DIR) File.write(task_file_path(task_name), content) end |