From a408a38ffb78c4f22820a505aa4d37c7fbcccaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Wed, 7 Aug 2024 00:09:00 +0200 Subject: [PATCH 1/2] feat: add database --- lib/todo/cache.ex | 3 +- lib/todo/database.ex | 51 +++++++++++++++++++++++++ lib/todo/server.ex | 90 ++++++++++++++++++++++---------------------- 3 files changed, 97 insertions(+), 47 deletions(-) create mode 100644 lib/todo/database.ex diff --git a/lib/todo/cache.ex b/lib/todo/cache.ex index 724b539..9e29fc6 100644 --- a/lib/todo/cache.ex +++ b/lib/todo/cache.ex @@ -3,6 +3,7 @@ defmodule Todo.Cache do @impl GenServer def init(_init_args) do + Todo.Database.start() {:ok, %{}} end @@ -13,7 +14,7 @@ defmodule Todo.Cache do {:reply, process, state} :error -> - new_process = Todo.Server.start() + {:ok, new_process} = Todo.Server.start(name) new_state = Map.put(state, name, new_process) diff --git a/lib/todo/database.ex b/lib/todo/database.ex new file mode 100644 index 0000000..07862fd --- /dev/null +++ b/lib/todo/database.ex @@ -0,0 +1,51 @@ +defmodule Todo.Database do + use GenServer + + @db_folder "./persist" + + def start do + GenServer.start(__MODULE__, nil, name: __MODULE__) + end + + def store(key, data) do + GenServer.cast(__MODULE__, {:store, key, data}) + end + + def get(key) do + GenServer.call(__MODULE__, {:get, key}) + end + + @impl GenServer + def init(_) do + File.mkdir_p!(@db_folder) + + {:ok, nil} + end + + @impl GenServer + def handle_cast({:store, key, data}, state) do + key + |> file_name() + |> File.write!(:erlang.term_to_binary(data)) + + {:noreply, state} + end + + @impl GenServer + def handle_call({:get, key}, _, state) do + data = + case File.read(file_name(key)) do + {:ok, contents} -> + :erlang.binary_to_term(contents) + + _ -> + nil + end + + {:reply, data, state} + end + + def file_name(key) do + Path.join(@db_folder, to_string(key)) + end +end diff --git a/lib/todo/server.ex b/lib/todo/server.ex index 09e75b7..2688873 100644 --- a/lib/todo/server.ex +++ b/lib/todo/server.ex @@ -1,52 +1,8 @@ defmodule Todo.Server do use GenServer - @impl GenServer - def init(%_{} = todo_list) do - {:ok, todo_list} - end - - @impl GenServer - def init(entries) do - {:ok, Todo.List.new(entries)} - end - - @impl GenServer - def handle_call({:entries, date}, _from, todo_list) do - entries = Todo.List.entries(todo_list, date) - - {:reply, entries, todo_list} - end - - @impl GenServer - def handle_cast({:add, entry}, todo_list) do - new_todo_list = Todo.List.add(todo_list, entry) - - {:noreply, new_todo_list} - end - - @impl GenServer - def handle_cast({:update, id, update_fun}, todo_list) do - new_todo_list = Todo.List.update(todo_list, id, update_fun) - - {:noreply, new_todo_list} - end - - @impl GenServer - def handle_cast({:delete, id}, todo_list) do - new_todo_list = Todo.List.delete(todo_list, id) - - {:noreply, new_todo_list} - end - - def start(entries \\ []) - - def start(%_{} = todo_list) do - GenServer.start(__MODULE__, todo_list) - end - - def start(entries) do - GenServer.start(__MODULE__, entries) + def start(name) do + GenServer.start(__MODULE__, name) end def add(pid, entry) do @@ -64,4 +20,46 @@ defmodule Todo.Server do def delete(pid, id) do GenServer.cast(pid, {:delete, id}) end + + @impl GenServer + def init(name) do + {:ok, {name, nil}, {:continue, :init}} + end + + @impl GenServer + def handle_continue(:init, {name, nil}) do + todo_list = Todo.Database.get(name) || Todo.List.new() + {:noreply, {name, todo_list}} + end + + @impl GenServer + def handle_call({:entries, date}, _from, {name, todo_list}) do + entries = Todo.List.entries(todo_list, date) + + {:reply, entries, {name, todo_list}} + end + + @impl GenServer + def handle_cast({:add, entry}, {name, todo_list}) do + new_todo_list = Todo.List.add(todo_list, entry) + Todo.Database.store(name, new_todo_list) + + {:noreply, {name, new_todo_list}} + end + + @impl GenServer + def handle_cast({:update, id, update_fun}, {name, todo_list}) do + new_todo_list = Todo.List.update(todo_list, id, update_fun) + Todo.Database.store(name, new_todo_list) + + {:noreply, {name, new_todo_list}} + end + + @impl GenServer + def handle_cast({:delete, id}, {name, todo_list}) do + new_todo_list = Todo.List.delete(todo_list, id) + Todo.Database.store(name, new_todo_list) + + {:noreply, {name, new_todo_list}} + end end From 1550e896a072869fdd966d511ebc68d1b408ae50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Wed, 7 Aug 2024 00:48:49 +0200 Subject: [PATCH 2/2] feat: add database workers --- lib/todo/database.ex | 50 ++++++++++++++++++++++++------------ lib/todo/database_worker.ex | 48 ++++++++++++++++++++++++++++++++++ persist/a | Bin 0 -> 198 bytes 3 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 lib/todo/database_worker.ex create mode 100644 persist/a diff --git a/lib/todo/database.ex b/lib/todo/database.ex index 07862fd..b67dfb8 100644 --- a/lib/todo/database.ex +++ b/lib/todo/database.ex @@ -1,7 +1,9 @@ defmodule Todo.Database do + alias Todo.DatabaseWorker use GenServer @db_folder "./persist" + @num_of_workers 3 def start do GenServer.start(__MODULE__, nil, name: __MODULE__) @@ -19,33 +21,47 @@ defmodule Todo.Database do def init(_) do File.mkdir_p!(@db_folder) - {:ok, nil} + {:ok, nil, {:continue, :init}} end @impl GenServer - def handle_cast({:store, key, data}, state) do - key - |> file_name() - |> File.write!(:erlang.term_to_binary(data)) + def handle_continue(:init, nil) do + File.mkdir_p!(@db_folder) - {:noreply, state} + workers = + 0..(@num_of_workers - 1) + |> Enum.map(fn i -> + {:ok, pid} = Todo.DatabaseWorker.start(@db_folder) + {i, pid} + end) + |> Map.new() + + IO.inspect(workers) + {:noreply, workers} end @impl GenServer - def handle_call({:get, key}, _, state) do + def handle_cast({:store, key, data}, workers) do + workers + |> get_worker(key) + |> DatabaseWorker.store(key, data) + + {:noreply, workers} + end + + @impl GenServer + def handle_call({:get, key}, _, workers) do data = - case File.read(file_name(key)) do - {:ok, contents} -> - :erlang.binary_to_term(contents) + workers + |> get_worker(key) + |> DatabaseWorker.get(key) - _ -> - nil - end - - {:reply, data, state} + {:reply, data, workers} end - def file_name(key) do - Path.join(@db_folder, to_string(key)) + defp choose_worker(workers, key) do + id = :erlang.phash2(key, @num_of_workers) + + Map.fetch!(workers, id) end end diff --git a/lib/todo/database_worker.ex b/lib/todo/database_worker.ex new file mode 100644 index 0000000..8a2e265 --- /dev/null +++ b/lib/todo/database_worker.ex @@ -0,0 +1,48 @@ +defmodule Todo.DatabaseWorker do + use GenServer + + def start(db_folder) do + GenServer.start(__MODULE__, db_folder) + end + + def store(pid, key, data) do + GenServer.cast(pid, {:store, key, data}) + end + + def get(pid, key) do + GenServer.call(pid, {:get, key}) + end + + @impl GenServer + def init(db_folder) do + {:ok, db_folder} + end + + @impl GenServer + def handle_cast({:store, key, data}, db_folder) do + {db_folder, key} + |> file_name() + |> File.write!(:erlang.term_to_binary(data)) + + {:noreply, db_folder} + end + + @impl GenServer + def handle_call({:get, key}, _, db_folder) do + data = + case File.read(file_name({db_folder, key})) do + {:ok, contents} -> + :erlang.binary_to_term(contents) + + _ -> + nil + end + + {:reply, data, db_folder} + end + + def file_name({db_folder, key}) do + Path.join(db_folder, to_string(key)) + end + +end diff --git a/persist/a b/persist/a new file mode 100644 index 0000000000000000000000000000000000000000..dca63f37477f53429f5e68755f2497b2cc76cffd GIT binary patch literal 198 zcmZ9G!3x4K5JaanrA04-|IwdN4}#!9?_t|rXdq1?U7mHF1L`lMbWBBp>eS1azM0O`kTwNb)FxQ|07EP}dH?_b literal 0 HcmV?d00001