defmodule Quantum.Job do
@moduledoc """
This Struct defines a Job.
## Usage
The struct should never be defined by hand. Use `c:Quantum.new_job/1` to create a new job and use the setters mentioned
below to mutate the job.
This is to ensure type safety.
"""
alias Crontab.CronExpression
@enforce_keys [:name, :run_strategy, :overlap, :timezone]
defstruct [
:run_strategy,
:overlap,
:timezone,
:name,
schedule: nil,
task: nil,
state: :active
]
@type name :: atom | reference()
@type state :: :active | :inactive
@type task :: {atom, atom, [any]} | (() -> any)
@type timezone :: :utc | String.t()
@type schedule :: Crontab.CronExpression.t()
@type t :: %__MODULE__{
name: name,
schedule: schedule | nil,
task: task | nil,
state: state,
run_strategy: Quantum.RunStrategy.NodeList,
overlap: boolean,
timezone: timezone
}
# Takes some config from a scheduler and returns a new job
# Do not use directly, use `Scheduler.new_job/1` instead.
@doc false
@spec new(config :: Keyword.t()) :: t
def new(config) do
Enum.reduce(
[
{&set_name/2, Keyword.get(config, :name, make_ref())},
{&set_overlap/2, Keyword.fetch!(config, :overlap)},
{&set_run_strategy/2, Keyword.fetch!(config, :run_strategy)},
{&set_schedule/2, Keyword.get(config, :schedule)},
{&set_state/2, Keyword.fetch!(config, :state)},
{&set_task/2, Keyword.get(config, :task)},
{&set_timezone/2, Keyword.fetch!(config, :timezone)}
],
%__MODULE__{name: nil, run_strategy: nil, overlap: nil, timezone: nil},
fn
{_fun, nil}, acc -> acc
{fun, value}, acc -> fun.(acc, value)
end
)
end
@doc """
Sets a job's name.
### Parameters
1. `job` - The job struct to modify
2. `name` - The name to set
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_name(:name)
...> |> Map.get(:name)
:name
"""
@spec set_name(t, atom) :: t
def set_name(%__MODULE__{} = job, name) when is_atom(name), do: Map.put(job, :name, name)
def set_name(%__MODULE__{} = job, name) when is_reference(name), do: Map.put(job, :name, name)
@doc """
Sets a job's schedule.
### Parameters
1. `job` - The job struct to modify
2. `schedule` - The schedule to set. May only be of type `%Crontab.CronExpression{}`
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_schedule(Crontab.CronExpression.Parser.parse!("*/7"))
...> |> Map.get(:schedule)
Crontab.CronExpression.Parser.parse!("*/7")
"""
@spec set_schedule(t, CronExpression.t()) :: t
def set_schedule(%__MODULE__{} = job, %CronExpression{} = schedule),
do: %{job | schedule: schedule}
@doc """
Sets a job's task.
### Parameters
1. `job` - The job struct to modify
2. `task` - The function to be performed, ex: `{Heartbeat, :send, []}` or `fn -> :something end`
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_task({Backup, :backup, []})
...> |> Map.get(:task)
{Backup, :backup, []}
"""
@spec set_task(t, task) :: t
def set_task(%__MODULE__{} = job, {module, function, args} = task)
when is_atom(module) and is_atom(function) and is_list(args),
do: Map.put(job, :task, task)
def set_task(%__MODULE__{} = job, task) when is_function(task, 0), do: Map.put(job, :task, task)
@doc """
Sets a job's state.
### Parameters
1. `job` - The job struct to modify
2. `state` - The state to set
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_state(:active)
...> |> Map.get(:state)
:active
"""
@spec set_state(t, state) :: t
def set_state(%__MODULE__{} = job, :active), do: Map.put(job, :state, :active)
def set_state(%__MODULE__{} = job, :inactive), do: Map.put(job, :state, :inactive)
@doc """
Sets a job's run strategy.
### Parameters
1. `job` - The job struct to modify
2. `run_strategy` - The run strategy to set
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.run_strategy(%Quantum.RunStrategy.All{nodes: [:one, :two]})
...> |> Map.get(:run_strategy)
[:one, :two]
"""
@spec set_run_strategy(t, Quantum.RunStrategy.NodeList) :: t
def set_run_strategy(%__MODULE__{} = job, run_strategy),
do: Map.put(job, :run_strategy, run_strategy)
@doc """
Sets a job's overlap.
### Parameters
1. `job` - The job struct to modify
2. `overlap` - Enable / Disable Overlap
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_overlap(false)
...> |> Map.get(:overlap)
false
"""
@spec set_overlap(t, boolean) :: t
def set_overlap(%__MODULE__{} = job, overlap?) when is_boolean(overlap?),
do: Map.put(job, :overlap, overlap?)
@doc """
Sets a job's timezone.
### Parameters
1. `job` - The job struct to modify
2. `timezone` - The timezone to set.
### Examples
iex> Acme.Scheduler.new_job()
...> |> Quantum.Job.set_timezone("Europe/Zurich")
...> |> Map.get(:timezone)
"Europe/Zurich"
"""
@spec set_timezone(t, String.t() | :utc) :: t
def set_timezone(%__MODULE__{} = job, timezone), do: Map.put(job, :timezone, timezone)
end