Current section
Files
Jump to
Current section
Files
lib/commanded/scheduler/schedule/schedule.ex
defmodule Commanded.Scheduler.Schedule do
@moduledoc false
alias Commanded.Scheduler.{
ScheduleBatch,
CancelSchedule,
ScheduleCancelled,
ScheduledOnce,
ScheduledRecurring,
ScheduleOnce,
ScheduleRecurring,
ScheduleTriggered,
TriggerSchedule
}
alias Commanded.Scheduler.Schedule
alias Commanded.Aggregate.Multi
defstruct [
:schedule_uuid,
scheduled: %{}
]
# public API
# Schedule a one-off command
def execute(%Schedule{schedule_uuid: nil} = schedule, %ScheduleOnce{} = once) do
schedule_once(schedule, Map.from_struct(once))
end
def execute(%Schedule{}, %ScheduleOnce{}), do: {:error, :already_scheduled}
# Schedule a recurring command
def execute(%Schedule{schedule_uuid: nil} = schedule, %ScheduleRecurring{} = recurring) do
schedule_recurring(schedule, Map.from_struct(recurring))
end
def execute(%Schedule{}, %ScheduleRecurring{}), do: {:error, :already_scheduled}
# Schedule a batch of commands
def execute(%Schedule{schedule_uuid: nil} = schedule, %ScheduleBatch{} = batch) do
%ScheduleBatch{
schedule_uuid: schedule_uuid,
schedule_once: schedule_once,
schedule_recurring: schedule_recurring
} = batch
multi = Multi.new(schedule)
multi =
schedule_once
|> Enum.map(fn once ->
once |> Map.from_struct() |> Map.put(:schedule_uuid, schedule_uuid)
end)
|> Enum.reduce(multi, fn once, multi ->
Multi.execute(multi, &schedule_once(&1, once))
end)
multi =
schedule_recurring
|> Enum.map(fn recurring ->
recurring |> Map.from_struct() |> Map.put(:schedule_uuid, schedule_uuid)
end)
|> Enum.reduce(multi, fn recurring, multi ->
Multi.execute(multi, &schedule_recurring(&1, recurring))
end)
multi
end
def execute(%Schedule{}, %ScheduleBatch{}), do: {:error, :already_scheduled}
def execute(%Schedule{schedule_uuid: nil}, %TriggerSchedule{}), do: {:error, :no_schedule}
# Trigger a scheduled command
def execute(%Schedule{} = schedule, %TriggerSchedule{name: name}) do
%Schedule{schedule_uuid: schedule_uuid, scheduled: scheduled} = schedule
case Map.get(scheduled, name) do
nil ->
{:error, :no_schedule}
{command, command_type} ->
%ScheduleTriggered{
schedule_uuid: schedule_uuid,
name: name,
command: command,
command_type: command_type
}
end
end
def execute(%Schedule{schedule_uuid: nil}, %CancelSchedule{}), do: {:error, :no_schedule}
# Cancel all scheduled commands
def execute(%Schedule{scheduled: scheduled} = schedule, %CancelSchedule{name: nil}) do
scheduled
|> Map.keys()
|> case do
[] ->
{:error, :no_schedule}
names ->
Enum.reduce(names, Multi.new(schedule), fn name, multi ->
Multi.execute(multi, &cancel(&1, name))
end)
end
end
# Cancel named scheduled command
def execute(%Schedule{} = schedule, %CancelSchedule{name: name}), do: cancel(schedule, name)
# state mutators
def apply(%Schedule{scheduled: scheduled} = schedule, %ScheduledOnce{} = once) do
%ScheduledOnce{
schedule_uuid: schedule_uuid,
name: name,
command: command,
command_type: command_type
} = once
%Schedule{
schedule
| schedule_uuid: schedule_uuid,
scheduled: Map.put(scheduled, name, {command, command_type})
}
end
def apply(%Schedule{scheduled: scheduled} = schedule, %ScheduledRecurring{} = recurring) do
%ScheduledRecurring{
schedule_uuid: schedule_uuid,
name: name,
command: command,
command_type: command_type
} = recurring
%Schedule{
schedule
| schedule_uuid: schedule_uuid,
scheduled: Map.put(scheduled, name, {command, command_type})
}
end
def apply(%Schedule{scheduled: scheduled} = schedule, %ScheduleTriggered{name: name}) do
%Schedule{schedule | scheduled: Map.delete(scheduled, name)}
end
def apply(%Schedule{scheduled: scheduled} = schedule, %ScheduleCancelled{name: name}) do
%Schedule{schedule | scheduled: Map.delete(scheduled, name)}
end
# private helpers
defp schedule_once(%Schedule{} = schedule, once) when is_map(once) do
schedule(schedule, ScheduledOnce, once)
end
defp schedule_recurring(%Schedule{} = schedule, recurring) when is_map(recurring) do
schedule(schedule, ScheduledRecurring, recurring)
end
defp schedule(%Schedule{} = schedule, schedule_type, once_or_recurring) do
with {:ok, name} <- ensure_default_name(schedule, once_or_recurring),
:ok <- validate_scheduled(schedule, name) do
schedule_type
|> struct(once_or_recurring)
|> Map.put(:name, name)
|> Map.put(:command_type, command_type(once_or_recurring))
else
reply -> reply
end
end
# Ensure a valid schedule name is provided, or set a default if `nil`.
defp ensure_default_name(%Schedule{scheduled: scheduled}, schedule) do
case Map.get(schedule, :name) do
nil ->
# use a default name (e.g. `@default0`)
{:ok, "@default#{scheduled |> Map.keys() |> length()}"}
name when is_bitstring(name) ->
case String.length(name) do
0 -> {:error, :invalid_schedule}
_ -> {:ok, name}
end
_ ->
{:error, :invalid_schedule}
end
end
# Ensure the schedule name is unique
defp validate_scheduled(%Schedule{scheduled: scheduled}, name) do
case Map.has_key?(scheduled, name) do
true ->
{:error, :already_scheduled}
false ->
:ok
end
end
defp command_type(%{command: command} = schedule) when is_map(schedule) do
Atom.to_string(command.__struct__)
end
defp cancel(%Schedule{} = schedule, name) do
%Schedule{schedule_uuid: schedule_uuid, scheduled: scheduled} = schedule
case Map.get(scheduled, name) do
nil ->
{:error, :no_schedule}
_ ->
%ScheduleCancelled{
schedule_uuid: schedule_uuid,
name: name
}
end
end
end