%%% @copyright Erlware, LLC. All Rights Reserved. %%% %%% This file is provided to you under the BSD License; you may not use %%% this file except in compliance with the License. -module(erlcron). -export([validate/1, cron/1, cron/2, cron/3, at/2, at/3, at/4, daily/2, daily/3, daily/4, weekly/3, weekly/4, weekly/5, monthly/3,monthly/4, monthly/5, cancel/1, epoch/0, epoch_seconds/0, datetime/0, ref_datetime/0, set_datetime/1, set_datetime/2, reset_datetime/0, get_all_jobs/0, multi_set_datetime/1, multi_set_datetime/2]). -export_type([job/0, job_ref/0, job_opts/0, cron_opts/0, job_start/0, job_end/0, run_when/0, callable/0, dow/0, dom/0, period/0, duration/0, constraint/0, cron_time/0, seconds/0, milliseconds/0]). %%%=================================================================== %%% Types %%%=================================================================== -type seconds() :: integer(). -type milliseconds():: integer(). -type cron_time() :: {integer(), am | pm} | {integer(), integer(), am | pm} | calendar:time(). -type constraint() :: {between, cron_time(), cron_time()}. -type duration() :: {integer(), hr | h | min | m | sec | s}. -type period() :: cron_time() | {every, duration()} | {every, duration(), constraint()}. -type dom() :: integer(). -type dow_day() :: mon | tue | wed | thu | fri | sat | sun. -type dow() :: dow_day() | [dow_day()]. -type callable() :: {M :: module(), F :: atom(), A :: [term()]} | fun(() -> term()) | fun((JobRef::job_ref(), calendar:datetime()) -> term()). -type run_when() :: {once, cron_time()} | {once, seconds()} | {daily, period()} | {weekly, dow(), period()} | {monthly, dom()|[dom()], period()}. -type job() :: {run_when(), callable()} | {run_when(), callable(), job_opts()} | #{id => job_ref(), interval => run_when(), execute => callable(), _ => any()}. %% should be opaque but dialyzer does not allow it -type job_ref() :: reference() | atom() | binary(). %% A job reference. -type job_start() :: fun((JobRef::job_ref()) -> ignore | any()). %% A function to be called before a job is started. If it returns the `ignore' %% atom, the job function will not be executed. -type job_end() :: fun((JobRef::job_ref(), Res :: {ok, term()} | {error, {Reason::term(), Stack::list()}}) -> term()). %% A function to be called after a job ended. The function is passed the %% job's result. -type job_opts() :: #{hostnames => [binary()|string()], id => term(), on_job_start => {Mod::atom(), Fun::atom()} | job_start(), on_job_end => {Mod::atom(), Fun::atom()} | job_end() }. %% Job options: %%
%%
hostnames => [Hostname]
%%
List of hostnames where the job is allowed to run
%%
id => ID
%%
An identifier of the job passed to `on_job_start' and `on_job_end' %% callbacks. It can be any term.
%%
on_job_start => {Mod, Fun} | fun(JobRef, ID) -> any()
%%
`Mod:Fun(Ref::job_ref(), ID::any())' function to call on job start. %% The result is ignored.
%%
{on_job_start, {Mod, Fun}} | fun(JobRef, ID, Result) -> term()
%%
`Mod:Fun(Ref::job_ref(), ID::any(), Result)' function to call after %% a job has ended. `Result' is `{ok, JobResult::term()}' or %% `{error, `{Reason, StackTrace}}' if there is an exception.
%%
-type cron_opts() :: job_opts(). %% Cron default options applicable to all jobs. See job_opts(). %%%=================================================================== %%% API %%%=================================================================== %% @doc %% Check that the spec specified is valid or invalid -spec validate(run_when()) -> ok | {error, term()}. validate(Spec) -> ecrn_agent:validate(Spec). %% @doc %% Adds a new job to the cron system. Jobs are described in the job() %% spec. It returns the JobRef that can be used to manipulate the job %% after it is created. -spec cron(job()) -> job_ref() | ignored | already_started | {error, term()}. cron(Job) -> cron(make_ref(Job), Job, #{}). -spec cron(job()|job_ref(), job()|cron_opts()) -> job_ref() | ignored | already_started | {error, term()}. cron(Job, DefOpts) when is_map(DefOpts) -> cron(make_ref(Job), Job, DefOpts); cron(JobRef, Job) when (is_atom(JobRef) orelse is_reference(JobRef) orelse is_binary(JobRef)) -> cron(JobRef, Job, #{}). %% @doc Schedule a job identified by a `JobRef'. %% A job reference can be a reference, atom, or binary. If it is %% an atom, a job will be locally registered by that name. %% Returns false if the job is not permitted to run on the current host -spec cron(job_ref(), job(), job_opts()) -> job_ref() | ignored | already_started | {error, term()}. cron(JobRef, Job, Opts) when (is_atom(JobRef) orelse is_reference(JobRef) orelse is_binary(JobRef)) , is_map(Opts) -> ecrn_cron_sup:add_job(JobRef, Job, Opts). %% @doc %% Convenience method to specify a job to run once at the given time %% or after the amount of time specified. -spec at(cron_time() | seconds(), callable()) -> job_ref() | ignored | already_started | {error, term()}. at(When, Fun) -> at(make_ref(), When, Fun). -spec at(job_ref(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. at(JobRef, When, Fun) -> cron(JobRef, {{once, When}, Fun}). -spec at(job_ref(), cron_time() | seconds(), function(), job_opts()) -> job_ref() | ignored | already_started | {error, term()}. at(JobRef, When, Fun, #{} = Opts) -> cron(JobRef, {{once, When}, Fun}, Opts). %% @doc %% Convenience method to specify a job run to run on a daily basis %% at a specific time. -spec daily(cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. daily(When, Fun) -> daily(make_ref(), When, Fun). -spec daily(job_ref(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. daily(JobRef, When, Fun) -> cron(JobRef, {{daily, When}, Fun}). -spec daily(job_ref(), cron_time() | seconds(), function(), job_opts()) -> job_ref() | ignored | already_started | {error, term()}. daily(JobRef, When, Fun, #{} = Opts) -> cron(JobRef, {{daily, When}, Fun}, Opts). %% @doc %% Convenience method to specify a job run to run on a weekly basis %% at a specific time. -spec weekly(dow(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. weekly(DOW, When, Fun) -> weekly(make_ref(), DOW, When, Fun). -spec weekly(job_ref(), dow(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. weekly(JobRef, DOW, When, Fun) -> cron(JobRef, {{weekly, DOW, When}, Fun}). -spec weekly(job_ref(), dow(), cron_time() | seconds(), function(), job_opts()) -> job_ref() | ignored | already_started | {error, term()}. weekly(JobRef, DOW, When, Fun, #{} = Opts) -> cron(JobRef, {{weekly, DOW, When}, Fun}, Opts). %% @doc %% Convenience method to specify a job run to run on a weekly basis %% at a specific time. -spec monthly(dom(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. monthly(DOM, When, Fun) -> monthly(make_ref(), DOM, When, Fun). -spec monthly(job_ref(), dom(), cron_time() | seconds(), function()) -> job_ref() | ignored | already_started | {error, term()}. monthly(JobRef, DOM, When, Fun) -> cron(JobRef, {{monthly, DOM, When}, Fun}). -spec monthly(job_ref(), dom(), cron_time() | seconds(), function(), job_opts()) -> job_ref() | ignored | already_started | {error, term()}. monthly(JobRef, DOM, When, Fun, #{} = Opts) -> cron(JobRef, {{monthly, DOM, When}, Fun}, Opts). %% @doc %% Cancel the job specified by the jobref. -spec cancel(job_ref()) -> boolean(). cancel(JobRef) -> ecrn_control:cancel(JobRef). %% @doc %% Get the current date time in seconds millisince epoch. -spec epoch() -> milliseconds(). epoch() -> ecrn_util:epoch_milliseconds(). %% @doc %% Get the current date time in seconds since epoch. -spec epoch_seconds() -> seconds(). epoch_seconds() -> ecrn_util:epoch_seconds(). %% @doc %% Get the current date time of the erlcron system adjusted to reference. %% -spec datetime() -> {calendar:datetime(), milliseconds()}. datetime() -> ecrn_control:datetime(). %% @doc %% Get the reference date time of the erlcron system. %% -spec ref_datetime() -> {calendar:datetime(), milliseconds()}. ref_datetime() -> ecrn_control:ref_datetime(). %% @doc %% Set the current date time of the running erlcron system using local time. -spec set_datetime(calendar:datetime()) -> ok. set_datetime(DateTime) -> set_datetime(DateTime, local). %% @doc %% Set the current date time of the running erlcron system using either local %% or universal time. The `TZ` parameter must contain an atom `local|universal'. -spec set_datetime(calendar:datetime(), local|universal) -> ok. set_datetime({D,T} = DateTime, TZ) when tuple_size(D)==3, tuple_size(T)==3 -> ecrn_control:set_datetime(DateTime, TZ). %% @doc %% Reset the reference datetime of erlcron system to current epoch time. -spec reset_datetime() -> ok. reset_datetime() -> ecrn_control:reset_datetime(). %% Get references of all running jobs -spec get_all_jobs() -> [job_ref()]. get_all_jobs() -> ecrn_reg:get_all_refs(). %% @doc %% Set the current date time of the erlcron system running on different nodes. -spec multi_set_datetime(calendar:datetime()) -> {Replies, BadNodes} when Replies :: [{node(), ok | {error, term()}}], BadNodes :: [node()]. multi_set_datetime({D,T} = DateTime) when tuple_size(D)==3, tuple_size(T)==3 -> ecrn_control:multi_set_datetime([node()|nodes()], DateTime). %% @doc %% Set the current date time of the erlcron system running on the %% specified nodes -spec multi_set_datetime([node()], calendar:datetime()) -> {Replies, BadNodes} when Replies :: [{node(), ok | {error, term()}}], BadNodes :: [node()]. multi_set_datetime(Nodes, DateTime) when is_list(Nodes), tuple_size(DateTime)==2 -> ecrn_control:multi_set_datetime(Nodes, DateTime). make_ref(#{id := ID}) when is_atom(ID); is_binary(ID); is_reference(ID) -> ID; make_ref({_When, _Callable, #{id := ID}}) when is_atom(ID); is_binary(ID); is_reference(ID) -> ID; make_ref({_When, _Callable, #{}}) -> make_ref(); make_ref({_When, _Callable}) -> make_ref().