# fpc-cron — API reference Every callable the library exposes, with parameters, return values, and runnable examples. See [`architecture.md`](architecture.md) for the big picture and [`DEVELOPER_GUIDE.md`](DEVELOPER_GUIDE.md) for the consumer-oriented walkthrough. ## Contents - [Quick start](#quick-start) - [Types (`cron.types`)](#types) - [Events (`cron.events`)](#events) - [Schema specs (`cron.runner`)](#schema-specs) - [TCron](#tcron) - [Create](#tcroncreate) - [Destroy](#tcrondestroy) - [RegisterSystemTask](#tcronregistersystemtask) - [RefreshTasks](#tcronrefreshtasks) - [RunTaskNow](#tcronruntasknow) - [GetTasksJSON / GetTaskJSON](#tcrongettasksjson) - [UpdateTask](#tcronupdatetask) - [Running (property)](#tcronrunning) - [OnTaskStart / OnTaskComplete / OnTaskRegistered / OnPluginOrphaned / OnThreadStart / OnThreadStop](#tcronon) - [ParseCronField (class function)](#tcronparsecronfield) - [MatchesCron (class function)](#tcronmatchescron) - [Version constants](#version-constants) --- ## Quick start ```pascal uses Classes, SysUtils, fpjson, DateUtils, log.types, database.types, database.pool, cron.types, cron.events, cron.runner; var Pool: TDBPool; C: TCron; begin Pool := TDBPool.Create; Pool.Init(dbSQLite, '/tmp/cron.sqlite3'); try C := TCron.Create(Pool, @MyHost.RunTask, nil, @MyHost.HandleLog); try C.OnTaskComplete := @MyHost.HandleTaskComplete; C.RegisterSystemTask('cleanup', @MyHost.RunCleanup); C.Start; ... C.Terminate; finally C.Free; end; finally Pool.Free; end; end. ``` ## Types `cron.types`: ```pascal type TCronTaskKind = (stkInterval, stkCron); TCronTask = record ID: Integer; TaskName: string; PluginName: string; Description: string; Category: string; Kind: TCronTaskKind; IntervalSeconds: Integer; CronExpr: string; Enabled: Boolean; LastRun: TDateTime; NextRun: TDateTime; LastResult: string; LastError: string; RunCount: Integer; FailCount: Integer; UserModified: Boolean; IsRunning: Boolean; end; TRunTaskProc = procedure(const APluginName, ATaskName: string) of object; TGetExtraTasksFunc = function: TJSONArray of object; TSystemTaskProc = procedure(const ATaskName: string) of object; ``` The logger type is `log.types.TLogProc`, defined in fpc-log. ## Events `cron.events` defines the typed observer callbacks (same pattern as fpc-binkp's `bp.events` and fpc-comet's `cm.events`): ```pascal type TCronOnTaskStart = procedure(const APluginName, ATaskName: string) of object; TCronOnTaskComplete = procedure(const APluginName, ATaskName: string; ASuccess: Boolean; ADurationMs: Integer; const AError: string) of object; TCronOnTaskRegistered = procedure(const APluginName, ATaskName: string; AIntervalSeconds: Integer) of object; TCronOnPluginOrphaned = procedure(const APluginName: string) of object; TCronOnThreadStart = procedure of object; TCronOnThreadStop = procedure of object; ``` All callbacks are `of object`. `nil` is always valid (no-op). ## Schema specs ```pascal function BuildSystemSchedulerSpec(const ANowExpr: string): TDBTable; function BuildSchedulerLogSpec: TDBTable; ``` Return fpc-db `TDBTable` specs for the two runner-owned tables. Use these to declare schema independently of `TCron.Create`: ```pascal APool.DeclareTable(BuildSystemSchedulerSpec(APool.Dialect.NowExpr)); APool.DeclareTable(BuildSchedulerLogSpec); ``` The names mirror canonical Fastway `fw_schema.pas`'s `BuildSystemScheduler` / `BuildSchedulerLog` so a Fastway database is reusable without migration. ## TCron `TCron = class(TThread)`. Created suspended; caller calls `.Start` to begin the wake loop. ### `TCron.Create` ```pascal constructor Create(APool: TDBPool; ARunTask: TRunTaskProc = nil; AGetExtraTasks: TGetExtraTasksFunc = nil; ALogger: TLogProc = nil); ``` - `APool` — required. fpc-db pool the runner reads/writes its two tables on. Raises if `nil`. - `ARunTask` — invoked for every non-system task. `nil` means non-system tasks fail with `'No plugin task runner available'`. - `AGetExtraTasks` — invoked at `Create` and on `RefreshTasks` to discover plugin tasks. `nil` means `SyncPluginTasks` is a no-op. - `ALogger` — `log.types.TLogProc`. `nil` means silent. `Create` calls `Pool.DeclareTable` for both schema specs, runs `LoadTasksFromDB`, and runs `SyncPluginTasks`. Returns a *suspended* thread — caller must call `.Start`. ### `TCron.Destroy` If the wake loop is running, `Destroy` calls `Terminate`, fires the stop event, and `WaitFor`s the thread. Then frees the lock and the stop event. Safe to call from any thread. ### `TCron.RegisterSystemTask` ```pascal procedure RegisterSystemTask(const AName: string; AProc: TSystemTaskProc); ``` Register a callback for a `system/` task. Calling twice with the same name replaces the previous entry. Replaces canonical Fastway's hardcoded `case ATaskName of` table. If a `plugin_name='system'` task fires whose name isn't registered, the runner logs `'unknown system task: '` at `llWarn` and moves on. ### `TCron.RefreshTasks` ```pascal procedure RefreshTasks; ``` Reload from the DB and re-run `SyncPluginTasks`. Use after inserting tasks externally, or after the supplier callback's return value has changed. Recomputes `NextRun` for every enabled task. ### `TCron.RunTaskNow` ```pascal procedure RunTaskNow(ATaskID: Integer); ``` Synchronously run one task by row id. Updates `LastRun`, `RunCount`, `FailCount`, `NextRun`, writes a `scheduler_log` row, fires `OnTaskStart` + `OnTaskComplete`. Useful for "run now" buttons in admin UIs. ### `TCron.GetTasksJSON` ```pascal function GetTasksJSON: TJSONArray; function GetTaskJSON(ATaskID: Integer): TJSONObject; ``` Snapshot the in-memory task table. `GetTaskJSON` returns `nil` if the task ID isn't found. Caller owns the returned objects (must `Free` them). All `last_run` / `next_run` timestamps are formatted as `'yyyy-mm-dd hh:nn:ss'` (UTC); null when unset. ### `TCron.UpdateTask` ```pascal function UpdateTask(ATaskID: Integer; AUpdates: TJSONObject): Boolean; ``` Apply mutations to an existing task and persist to DB. Recognised keys in `AUpdates`: - `enabled` (Boolean) - `schedule_type` (`'interval'` or `'cron'`) - `interval_seconds` (Integer, must be > 0) - `cron_expr` (string) Sets `user_modified = 1` (which exempts the row from `SyncPluginTasks` orphan cleanup). Recomputes `NextRun`. Returns `True` if the task was found, `False` otherwise. ### `TCron.Running` ```pascal property Running: Boolean read FRunning; ``` `True` between `Execute` start and stop. ### `TCron.OnTaskStart` etc. ```pascal property OnTaskStart: TCronOnTaskStart ...; property OnTaskComplete: TCronOnTaskComplete ...; property OnTaskRegistered: TCronOnTaskRegistered ...; property OnPluginOrphaned: TCronOnPluginOrphaned ...; property OnThreadStart: TCronOnThreadStart ...; property OnThreadStop: TCronOnThreadStop ...; ``` Wire by assignment (`C.OnTaskStart := @Host.HandleStart`). Each callback is invoked synchronously on the runner thread. `nil` (the default) means no-op. ### `TCron.ParseCronField` ```pascal class function ParseCronField(const AField: string; AMin, AMax: Integer): TBits; ``` Parse one cron field (`*`, `*/N`, `a,b,c`, `a-b`, `a-b/N`, or a single literal). Returns a `TBits` of size `AMax+1` with the matched values set. Caller frees the result. Out-of-range literals are silently dropped. ### `TCron.MatchesCron` ```pascal class function MatchesCron(const ACronExpr: string; ATime: TDateTime): Boolean; ``` Test whether a 5-field cron expression matches the given `TDateTime`. Returns `False` if `ACronExpr` has fewer than 5 fields after splitting on whitespace. `ATime` is treated as local time (canonical Fastway behaviour); the canonical's `next_run` math converts to UTC after the match. ## Version constants In `cron.version`: ```pascal const CRON_VERSION_MAJOR = 0; CRON_VERSION_MINOR = 1; CRON_VERSION_PATCH = 0; CRON_VERSION_STRING = '0.1.0'; ``` Bumped together with the git tag. Pin downstream consumers by tag, not commit hash.