defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmEvent do alias Farmbot.{ Asset, Asset.FarmEvent, Asset.Regimen, Asset.Sequence, } require Logger use GenServer defstruct [:farm_event, :datetime] alias __MODULE__, as: State @checkup_time_ms Application.get_env(:farmbot_core, __MODULE__)[:checkup_time_ms] @checkup_time_ms || Mix.raise(""" config :farmbot_core, #{__MODULE__}, checkup_time_ms: 10_000 """) def start_link(farm_event) do GenServer.start_link(__MODULE__, [farm_event]) end def init([farm_event]) do Logger.disable(self()) ensure_executable!(farm_event) now = DateTime.utc_now() state = %State{ farm_event: farm_event, datetime: farm_event.last_executed || DateTime.utc_now() } # check if now is _before_ start_time case DateTime.compare(now, farm_event.start_time) do :lt -> init_event_started(state, now) _ -> # check if now is _after_ end_time case DateTime.compare(now, farm_event.end_time) do :gt -> init_event_completed(state, now) _ -> init_event_started(state, now) end end end defp init_event_completed(_, _) do Logger.warn "No future events" :ignore end def init_event_started(%State{} = state, _now) do {:ok, state, 0} end def handle_info(:timeout, %State{} = state) do Logger.info "build_calendar" next = FarmEvent.build_calendar(state.farm_event, state.datetime) if next do # positive if the first date/time comes after the second. diff = DateTime.compare(next, DateTime.utc_now()) # if next_event is more than 0 milliseconds away, schedule that event. case diff do :gt -> Logger.info "Event is still in the future" {:noreply, state, @checkup_time_ms} diff when diff in [:lt, :eq] -> Logger.info "Event should be executed: #{Timex.from_now(next)}" executable = ensure_executable!(state.farm_event) event = ensure_executed!(state.farm_event, executable, next) {:noreply, %{state | farm_event: event, datetime: DateTime.utc_now()}, @checkup_time_ms} end else Logger.warn "No more future events to execute." {:stop, :normal, state} end end defp ensure_executed!(%FarmEvent{last_executed: nil} = event, %Sequence{} = exe, next_dt) do # positive if the first date/time comes after the second. comp = Timex.diff(DateTime.utc_now(), next_dt, :minutes) cond do # now is more than 2 minutes past expected execution time comp > 2 -> Logger.warn "Sequence: #{inspect exe} too late: #{comp} minutes difference." event true -> Logger.warn "Sequence: #{inspect exe} has not run before: #{comp} minutes difference." Farmbot.Core.CeleryScript.sequence(exe, fn(_) -> :ok end) Asset.update_farm_event!(event, %{last_executed: next_dt}) end end defp ensure_executed!(%FarmEvent{} = event, %Sequence{} = exe, next_dt) do # positive if the first date/time comes after the second. comp = Timex.compare(event.last_executed, :minutes) cond do comp > 2 -> Logger.warn("Sequence: #{inspect exe} needs executing") Farmbot.Core.CeleryScript.sequence(exe, fn(_) -> :ok end) Asset.update_farm_event!(event, %{last_executed: next_dt}) 0 -> Logger.warn("Sequence: #{inspect exe} already executed: #{Timex.from_now(next_dt)}") event end end defp ensure_executed!(%FarmEvent{last_executed: nil} = event, %Regimen{} = exe, next_dt) do Logger.warn "Regimen: #{inspect exe} has not run before. Executing it." Asset.upsert_persistent_regimen(exe, event, %{started_at: next_dt}) Asset.update_farm_event!(event, %{last_executed: next_dt}) end defp ensure_executed!(%FarmEvent{} = event, %Regimen{} = exe, _next_dt) do Asset.upsert_persistent_regimen(exe, event) event end defp ensure_executable!(%FarmEvent{executable_type: "Sequence", executable_id: id}) do Asset.get_sequence!(id: id) end defp ensure_executable!(%FarmEvent{executable_type: "Regimen", executable_id: id}) do Asset.get_regimen!(id: id) end end