-module(db).
-behavior(gen_server).
-export([start_link/1]).
% gen_server things
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]).
%% server things
init(Args) ->
process_flag(trap_exit, true),
[{dbpath, Path}, _] = Args,
open_db(Path).
open_db(Path) ->
{ok, Db} = sqlite3:open(Path),
ok = init_tables(Db),
{ok, [{sql, Db}, {dbPath, Path}]}.
start_link(DbPath) ->
gen_server:start_link({local, cabaldb}, ?MODULE, [{dbpath, DbPath}], []).
handle_call({addchannel, Name}, _From, State = [{sql, Db}, _]) ->
io:format("INSERTing channel: ~p~n", [Name]),
sqlite3:write(Db, channels, [{name, Name}]),
{reply, ok, State};
handle_call(listchannels, _From, State = [{sql, Db}, _]) ->
[_, {rows, Rows}] = sqlite3:read_all(Db, channels),
Chans = lists:map(fun({_Id, Name}) ->
binary_to_list(Name)
end, Rows),
{reply, {ok, Chans}, State}.
handle_cast(Msg, State) ->
io:format("Unexpected async call: ~p~n", [Msg]),
{noreply, State}.
handle_info(Msg, State) ->
io:format("Unexpected message: ~p~n", [Msg]),
{noreply, State}.
terminate(normal, _State = [{sql, Db, _}]) ->
sqlite3:close(Db),
ok.
code_change(_OldVsn, State, _Extra) ->
% let's re-open the Db, just in case the driver changed.
[{sql, Db}, {dbPath, Path}] = State,
sqlite3:close(Db),
open_db(Path).
%% private helpers
init_tables(Db) ->
ColId = {id, integer, [{primary_key, [asc, autoincrement]}]},
Tables = #{
channels => [ColId, {name, text, not_null}],
users => [ColId, {pubkey, blob, not_null}, {name, text, not_null}],
posts => [ColId, {pubkey, blob, not_null}, {type, integer, not_null}]
},
case tables_exist(Db, maps:keys(Tables)) of
missing ->
CreateTable = fun(T, Cols) ->
ok = sqlite3:create_table(Db, T, Cols),
io:format("Created table: ~p~n", [T])
end,
maps:foreach(CreateTable, Tables);
ok ->
ok
end.
tables_exist(Db, [T | Rest]) ->
case lists:member(T, sqlite3:list_tables(Db)) of
true -> tables_exist(Db, Rest);
false -> missing
end;
tables_exist(_, []) ->
ok.