Current section

Files

Jump to
onetime lib onetime.ex
Raw

lib/onetime.ex

defmodule Onetime do
@moduledoc """
This is a onetime key-value store.
This can be used for such as storing tokens for authentication.
"""
use GenServer
alias Timex.Date, as: Date
@type result :: {:ok, any} | :error
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, %{}, options)
end
@doc """
Registers the key with specific time.
"""
@spec register(any, any, any, number) :: any
def register(name, key, value, time) do
GenServer.cast(name, {:register, {key, value, time}})
end
@doc """
Registers the key.
"""
@spec register(any, any, any) :: any
def register(name, key, value) do
register(name, key, value, Date.now())
end
@doc """
Drops the key.
"""
@spec drop(any, any) :: any
def drop(name, key) do
GenServer.cast(name, {:drop, key})
end
@doc """
Checks whether the key exists or not.
"""
@spec has?(any, any, number) :: boolean
def has?(name, key, secs \\ :infinity) do
case get(name, key, secs) do
{:ok, _} -> true
_ -> false
end
end
@doc """
Returns the value for a given key.
"""
@spec get(any, any, number) :: result
def get(name, key, secs \\ :infinity) do
GenServer.call(name, {:get, {key, secs}})
end
@doc """
Returns the value for a given key and removes them.
"""
@spec pop(any, any, number) :: result
def pop(name, key, secs \\ :infinity) do
GenServer.call(name, {:pop, {key, secs}})
end
@doc """
Returns the value for a given key and changes the key.
"""
@spec get_and_update(any, any, any, number) :: result
def get_and_update(name, key, new_key, secs \\ :infinity) do
GenServer.call(name, {:pop, {key, new_key, secs}})
end
@doc """
Returns the all keys and values.
"""
@spec get_all(any, number) :: map
def get_all(name, secs \\ :infinity) do
GenServer.call(name, {:get_all, secs})
end
@doc """
Remove the old keys and values.
"""
@spec clear(any, number) :: map
def clear(name, secs) do
GenServer.cast(name, {:clear, secs})
end
defp get_from_map(map, key, now, secs) do
case Map.get(map, key) do
{value, time} ->
if secs != :infinity && Date.diff(time, now, :secs) > secs do
:error
else
{:ok, value}
end
_ -> :error
end
end
# Callbacks
def handle_cast({:register, {key, value, time}}, map) do
{:noreply, Map.put(map, key, {value, time})}
end
def handle_cast({:drop, key}, map) do
{:noreply, Map.delete(map, key)}
end
def handle_cast({:clear, secs}, map) do
map = if secs != :infinity do
now = Date.now()
Enum.filter(map, fn {_key, {_value, time}} -> Date.diff(time, now, :secs) < secs end)
else map end
{:noreply, map}
end
def handle_call({:get, {key, secs}}, _from, map) do
reply = get_from_map(map, key, Date.now(), secs)
{:reply, reply, map}
end
def handle_call({:pop, {key, secs}}, _from, map) do
reply = get_from_map(map, key, Date.now(), secs)
{:reply, reply, Map.delete(map, key)}
end
def handle_call({:pop, {key, new_key, secs}}, _from, map) do
now = Date.now()
reply = get_from_map(map, key, now, secs)
map = if reply != :error do
Map.delete(map, key)
|> Map.put(new_key, {elem(reply, 1), now})
else
Map.delete(map, key)
end
{:reply, reply, map}
end
def handle_call({:get_all, secs}, _from, map) do
map = if secs != :infinity do
now = Date.now()
Enum.filter(map, fn {_key, {_value, time}} -> Date.diff(time, now, :secs) < secs end) |> Enum.into(%{})
else map end
reply = Enum.map(map, fn {key, {value, _time}} -> {key, value} end) |> Enum.into(%{})
{:reply, reply, map}
end
end