Current section

Files

Jump to
quantum lib quantum.ex
Raw

lib/quantum.ex

defmodule Quantum do
use GenServer
import Process, only: [send_after: 3]
@days ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
@months ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__] ++ options)
end
# Public functions ------------------------------------------------------------------------------
def cron("@yearly", fun), do: GenServer.cast(__MODULE__, {"0 0 1 1 *", fun})
def cron("@monthly", fun), do: GenServer.cast(__MODULE__, {"0 0 1 * *", fun})
def cron("@weekly", fun), do: GenServer.cast(__MODULE__, {"0 0 * * 0", fun})
def cron("@daily", fun), do: GenServer.cast(__MODULE__, {"0 0 * * *", fun})
def cron("@hourly", fun), do: GenServer.cast(__MODULE__, {"0 * * * *", fun})
def cron(expression, fun), do: GenServer.cast(__MODULE__, {expression, fun})
def reset, do: GenServer.cast(__MODULE__, :reset)
# Private functions -----------------------------------------------------------------------------
def init(_) do
send_after(self, :tick, 1000)
{:ok, %{jobs: [], d: nil, h: nil, m: nil, w: nil}}
end
def handle_info(:tick, state) do
send_after(self, :tick, 1000)
{d, {h, m, _}} = :calendar.now_to_universal_time(:os.timestamp)
if state.d != d do
state = %{state | w: rem(:calendar.day_of_the_week(d), 7)}
end
if state.m != m do
state = %{state | d: d, h: h, m: m}
Enum.each(state.jobs, fn({e, fun}) -> Task.start(__MODULE__, :execute, [e, fun, state]) end)
end
{:noreply, state}
end
def handle_info(_, state), do: {:noreply, state}
def handle_cast(:reset, state) do
{:noreply, %{state | jobs: []}}
end
def handle_cast({ e, fun }, state) do
{:noreply, %{state | jobs: ["#{e |> String.downcase |> translate}": fun] ++ state.jobs}}
end
def execute("* * * * *", fun, _), do: fun.()
def execute("0 * * * *", fun, %{m: 0}), do: fun.()
def execute("0 0 * * *", fun, %{m: 0, h: 0}), do: fun.()
def execute("0 0 1 * *", fun, %{m: 0, h: 0, d: {_, _, 1}}), do: fun.()
def execute("0 0 1 1 *", fun, %{m: 0, h: 0, d: {_, 1, 1}}), do: fun.()
def execute(e, fun, state) do
[m, h, d, n, w] = e |> String.split(" ")
{_, cur_mon, cur_day} = state.d
cond do
!match(m, state.m, 0, 59) -> false
!match(h, state.h, 0, 59) -> false
!match(d, cur_day, 1, 31) -> false
!match(n, cur_mon, 1, 12) -> false
!match(w, state.w, 0, 6) -> false
true -> fun.()
end
end
defp translate(e) do
{e,_} = List.foldl(@days, {e,0}, fn(x, acc) -> translate(acc, x) end)
{e,_} = List.foldl(@months, {e,1}, fn(x, acc) -> translate(acc, x) end)
e
end
defp translate({e, i}, term), do: {String.replace(e, term, "#{i}"), i+1}
defp match("*", _, _, _), do: true
defp match([], _, _, _), do: false
defp match([e|t], v, min, max), do: Enum.any?(parse(e, min, max), &(&1 == v)) or match(t, v, min, max)
defp match(e, v, min, max), do: match(e |> String.split(","), v, min, max)
defp parse("*/" <> _ = e, min, max) do
[_,i] = e |> String.split("/")
{x,_} = i |> Integer.parse
Enum.reject(min..max, &(rem(&1, x) != 0))
end
defp parse(e, min, max) do
[r|i] = e |> String.split("/")
[x|y] = r |> String.split("-")
{v,_} = x |> Integer.parse
parse(v, y, i, min, max) |> Enum.reject(&((&1 < min) or (&1 > max)))
end
defp parse(v, [], [], _, _), do: [v]
defp parse(v, [], i, _, _) do
{x,_} = i |> Integer.parse
[rem(v,i)]
end
defp parse(v, y, [], min, max) do
{t,_} = y |> Integer.parse
if v < t do
Enum.to_list(v..t)
else
Enum.to_list(v..max) ++ Enum.to_list(min..t)
end
end
defp parse(v, y, i, min, max) do
{x, _} = i |> Integer.parse
parse(v, y, [], min, max) |> Enum.reject(&(rem(&1, x) != 0))
end
end