Packages

A memcached-like Elixir cache.

Current section

Files

Jump to
lazy_cache lib lazy_cache.ex
Raw

lib/lazy_cache.ex

defmodule LazyCache do
use GenServer
@moduledoc """
Documentation for LazyCache.
"""
@doc """
Start scheduling the works for clearing the cache.
This method should be called before performing any operation.
Returns `{:ok, PID}`.
"""
def start do
GenServer.start_link(__MODULE__, %{})
end
@doc """
Store anything for a certain amount of time.
Returns a boolean indicating if element has been correctly inserted.
"""
def insert(key, value, keepAliveInMillis) do
if not check_valid_keep_alive(keepAliveInMillis) do
{:error,
"Keep Alive Time is not valid. Should be a positive Integer or :keep_alive_forever."}
else
is_inserted =
:ets.insert(
:buckets_registry,
{key, value, get_keepalive(keepAliveInMillis)}
)
get_keyset()
|> check_if_key_already_in_keyset(key)
if is_inserted do
append_to_keyset(get_keyset(), key)
end
is_inserted
end
end
@doc """
Store anything forever.
Returns a boolean indicating if element has been correctly inserted.
"""
def insert(key, value) do
insert(key, value, :keep_alive_forever)
end
@doc """
Retrieve anything by its key.
Returns `[{your_stored_tuple}]`.
"""
def lookup(key) do
:ets.lookup(:buckets_registry, key)
end
@doc """
Delete anything by its key.
Returns a boolean indicating if element has been correctly deleted.
"""
def delete(key) do
is_deleted = :ets.delete(:buckets_registry, key)
if is_deleted do
delete_from_keyset(get_keyset(), key)
end
is_deleted
end
@doc """
Obtain the number of elements stored in the cache.
Returns an integer equals or bigger than zero.
"""
def size() do
length(get_keyset())
end
@doc """
Delete everything in the cache.
Returns a boolean indicating if cache has been correctly cleared.
"""
def clear() do
if size() == 0 do
false
else
for key <- get_keyset(), do: delete(key)
true
end
end
defp get_keepalive(keepAliveInMillis) do
if keepAliveInMillis == :keep_alive_forever do
:keep_alive_forever
else
:os.system_time(:milli_seconds) + keepAliveInMillis
end
end
defp check_valid_keep_alive(keepAliveInMillis) do
keepAliveInMillis != nil and
(keepAliveInMillis == :keep_alive_forever or
(is_integer(keepAliveInMillis) and keepAliveInMillis > 0))
end
defp update_key_set(key_set) do
:ets.delete(:key_set, :keys)
:ets.insert(:key_set, {:keys, key_set})
end
defp check_if_key_already_in_keyset(key_set, key) do
if Enum.member?(key_set, key) do
delete_from_keyset(key_set, key)
end
end
defp delete_from_keyset(key_set, key) do
List.delete(key_set, key)
|> update_key_set()
end
defp append_to_keyset(key_set, key) do
(key_set ++ [key])
|> update_key_set()
end
defp purge(key) do
[element] = lookup(key)
if(elem(element, 2) <= :os.system_time(:milli_seconds)) do
delete(key)
end
end
defp purge_cache() do
if size() > 0 do
for key <- get_keyset(), do: purge(key)
end
end
defp get_keyset() do
:ets.lookup(:key_set, :keys)[:keys]
end
@impl true
def init(state) do
# Schedule work to be performed on start
schedule_work()
# Create table
:ets.new(:buckets_registry, [:set, :public, :named_table])
:ets.new(:key_set, [:set, :public, :named_table])
:ets.insert(:key_set, {:keys, []})
{:ok, state}
end
@impl true
def handle_info(:work, state) do
# Do the desired work here
purge_cache()
# Reschedule once more
schedule_work()
{:noreply, state}
end
defp schedule_work() do
# Each second
Process.send_after(self(), :work, 1000)
end
end