Current section
Files
Jump to
Current section
Files
lib/poppy_server.ex
defmodule ExPoppyServer do
use GenServer
@moduledoc """
Documentation for `ExPoppyServer`.
ExPoppyServer is a genserver holding the reference to a `ExPoppy` bloom filter in memory.
The main purpose of this construct is to avoid reloading a filter when not necessary.
Here is how one would add ExPoppyServer to an application's Supervisor to give access to a filter called `host.bloom`:
`{ExPoppy.ExPoppyServer, name: :poppyserver, path: Path.join([:code.priv_dir(:myapp), "host.bloom"])}`
"""
@moduledoc since: "0.1.0"
# Client API
@doc """
Starts genserver.
Process name can be set in the options.
## Examples
iex(1)> {:ok, pid} = ExPoppyServer.start_link([])
{:ok, #PID<0.188.0>}
iex(4)> {:ok, pid2} = ExPoppyServer.start_link([])
** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.188.0>}}
"""
def start_link(opts \\ []) do
{name, opts} = Keyword.pop(opts, :name)
case name do
nil ->
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
name when is_atom(name) ->
IO.puts("registering name")
GenServer.start_link(__MODULE__, opts, name: name)
end
end
@doc """
Inserts a `string` into the bloomfilter.
Returns `:ok` or `{:error, "a message describing the error}`
## Examples
iex(4)> ExPoppyServer.insert(pid, "toto")
:ok
"""
def insert(bf_pid, string) when is_pid(bf_pid) and is_binary(string) do
GenServer.call(bf_pid, {:insert, string})
end
@doc """
Checks whether a `string` may be preesnt in the bloom filter.
Returns `true` or `false`
## Examples
iex(5)> ExPoppyServer.contains(pid, "toto")
true
iex(6)> ExPoppyServer.contains(pid, "tata")
false
"""
def contains(bf_pid, string) when is_pid(bf_pid) and is_binary(string) do
GenServer.call(bf_pid, {:contains, string})
end
@doc """
Returns the filter's version
## Examples
iex(7)> ExPoppyServer.version(pid)
2
"""
def version(bf_pid) when is_pid(bf_pid) do
GenServer.call(bf_pid, {:version})
end
@doc """
Returns the filter's capacity
## Examples
iex(8)> ExPoppyServer.capacity(pid)
10000
"""
def capacity(bf_pid) when is_pid(bf_pid) do
GenServer.call(bf_pid, {:capacity})
end
@doc """
Returns the filter's false positive rate
## Examples
iex(8)> ExPoppyServer.fpp(pid)
0.001
"""
def fpp(bf_pid) when is_pid(bf_pid) do
GenServer.call(bf_pid, {:fpp})
end
@doc """
Returns the estimate count of elements in the bloom filter
## Examples
iex(2)> ExPoppyServer.insert(pid, "toto")
:ok
iex(3)> ExPoppyServer.insert(pid, "tata")
:ok
iex(4)> ExPoppyServer.count_estimate(pid)
2
"""
def count_estimate(bf_pid) when is_pid(bf_pid) do
GenServer.call(bf_pid, {:count_estimate})
end
@doc """
Loads the bloom filter located at `path`.
Returns `:ok` or `{:error, "a message describing the error"}`
# Examples
iex(5)> ExPoppyServer.load(pid, "toto")
:ok
iex(6)> ExPoppyServer.load(pid, "/root/toto")
{:error, "IO error: Permission denied (os error 13)"}
iex(8)> ExPoppyServer.load(pid, "/home/jlouis/tata")
{:error, "IO error: No such file or directory (os error 2)"}
"""
def load(bf_pid, path) when is_pid(bf_pid) and is_binary(path) do
GenServer.call(bf_pid, {:load, path})
end
@doc """
Saves the bloom filter to a file located at `path`
Returns `:ok` or `{:error, "a message describing the error"}`
## Examples
iex(4)> ExPoppyServer.save(pid, "toto")
:ok
iex(6)> ExPoppyServer.save(pid, "/root/toto")
{:error, "IO error: Permission denied (os error 13)"}
"""
def save(bf_pid, path) when is_pid(bf_pid) and is_binary(path) do
GenServer.call(bf_pid, {:save, path})
end
# Server callbacks
@doc false
def init(opts) do
opts =
opts
|> Keyword.put_new(:capacity, 10000)
|> Keyword.put_new(:fpp, 0.001)
|> Keyword.put_new(:version, 2)
# loading can take time so we return init to activate the mailbox
# and continue
{:ok, opts, {:continue, :load_or_create_bloom_filter}}
end
@doc false
def handle_continue(:load_or_create_bloom_filter, opts) do
case load_or_create_bloom_filter(opts) do
{:error, reason} -> {:stop, reason}
bloom_filter -> {:noreply, bloom_filter}
end
end
@doc false
def handle_call({:insert, string}, _from, bloom_filter) do
case ExPoppy.insert_str(bloom_filter, string) do
true -> {:reply, :ok, bloom_filter}
{:error, _} -> {:reply, :error, bloom_filter}
end
end
@doc false
def handle_call({:contains, string}, _from, bloom_filter) do
case ExPoppy.contains_str(bloom_filter, string) do
true -> {:reply, true, bloom_filter}
false -> {:reply, false, bloom_filter}
end
end
@doc false
def handle_call({:version}, _from, bloom_filter) do
{:reply, ExPoppy.version(bloom_filter), bloom_filter}
end
@doc false
def handle_call({:capacity}, _from, bloom_filter) do
{:reply, ExPoppy.capacity(bloom_filter), bloom_filter}
end
@doc false
def handle_call({:fpp}, _from, bloom_filter) do
{:reply, ExPoppy.fpp(bloom_filter), bloom_filter}
end
@doc false
def handle_call({:count_estimate}, _from, bloom_filter) do
{:reply, ExPoppy.count_estimate(bloom_filter), bloom_filter}
end
@doc false
def handle_call({:load, path}, _from, bloom_filter) do
case ExPoppy.load_filter(path) do
{:error, msg} -> {:reply, {:error, msg}, bloom_filter}
bloom_filter -> {:reply, :ok, bloom_filter}
end
end
@doc false
def handle_call({:save, path}, _from, bloom_filter) do
case ExPoppy.save(bloom_filter, path) do
{} -> {:reply, :ok, bloom_filter}
{:error, msg} -> {:reply, {:error, msg}, bloom_filter}
end
end
@doc false
defp load_or_create_bloom_filter(opts) do
case Keyword.pop(opts, :path) do
{nil, opts} ->
case Keyword.pop(opts, :parameter) do
{nil, opts} ->
ExPoppy.with_version(opts[:version], opts[:capacity], opts[:fpp])
{parameter, opts} ->
ExPoppy.with_params(opts[:version], opts[:capacity], opts[:fpp], parameter)
end
{path, _} ->
ExPoppy.load_filter(path)
end
end
end