%%%------------------------------------------------------------------- %%% @author cheese %%%------------------------------------------------------------------- -module(kv_storage). -author("cheese"). -include_lib("kernel/include/logger.hrl"). -behaviour(gen_server). %% API -export([start_link/2, add/3, get/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, search/4, internal_clear_old_messages/4, print_all_messages/1]). -define(SERVER, ?MODULE). -define(TIMEOUT_CHECK_STATE, 120000). -record(one_message, {key, value, time}). -record(state, {lifetime_seconds, server_name, log_file, messages :: [#one_message{}]}). %%%=================================================================== %%% API %%%=================================================================== start_link(LifeTimeSeconds, ServerName) -> gen_server:start_link({local, ServerName}, ?MODULE, [LifeTimeSeconds, ServerName], []). add(Key, Value, ServerName) -> gen_server:call(ServerName, {add, Key, Value}). get(Key, RemoveValue, ServerName) -> gen_server:call(ServerName, {get, Key, RemoveValue}). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([LifeTimeSeconds, ServerName]) -> ?LOG_INFO("Start key-value storage ~w. LifeTimeSeconds: ~w~n", [ServerName, LifeTimeSeconds]), State = #state{ messages = [], lifetime_seconds = LifeTimeSeconds, server_name = ServerName}, erlang:send_after(?TIMEOUT_CHECK_STATE, self(), check_state), {ok, State}. handle_call({add, Key, Value}, _From, #state{ messages = OldMessages} = State) -> NewMessage = #one_message{key = Key, value = Value, time = calendar:local_time()}, ?LOG_INFO("Add ~p~n", [Key]), {reply, ok, State#state{messages = [NewMessage | OldMessages]}}; handle_call({get, Key, RemoveValue}, _From, #state{ messages = OldMessages} = State) -> case search(Key, RemoveValue, OldMessages, []) of {ok, Value, NewMessages} -> ?LOG_INFO("Get ~p ok~n", [Key]), {reply, {ok, Value}, State#state{messages = NewMessages}}; {error, Reason} -> ?LOG_INFO("Get ~p error. ~w~n", [Key, Reason]), {reply, {error, Reason}, State} end; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Request, State) -> {noreply, State}. handle_info(check_state, #state{ messages = Messages, lifetime_seconds = LifetimeSeconds} = State) -> ?LOG_INFO("Start check_state for requests length=~w~n", [length(Messages)]), AllSeconds = time_utilites:get_seconds(calendar:local_time()), NewMessages = internal_clear_old_messages(AllSeconds, LifetimeSeconds, Messages, []), ?LOG_INFO("New requests length=~w~n", [length(NewMessages)]), erlang:send_after(?TIMEOUT_CHECK_STATE, self(), check_state), {noreply, State#state{ messages = NewMessages}}; handle_info(state, #state{ messages = Messages} = State) -> ?LOG_INFO("****************************************************~n", []), print_all_messages(Messages), ?LOG_INFO("****************************************************~n", []), {noreply, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== search(_Key, _RemoveValue, [], _MessagesTail) -> {error, not_found_message}; search(Key, RemoveValue, [#one_message{key = Key, value = Value} = OneMessage | Tail], MessagesTail) -> case RemoveValue of true -> {ok, Value, MessagesTail ++ Tail}; false -> {ok, Value, [OneMessage | MessagesTail] ++ Tail} end; search(Key, RemoveValue, [ OneMessage | Tail], MessagesTail) -> search(Key, RemoveValue, Tail, [OneMessage | MessagesTail]). internal_clear_old_messages(_AllSeconds, _LifeTimeSeconds, [], OtherMessage) -> OtherMessage; internal_clear_old_messages(AllSeconds, LifeTimeSeconds, [#one_message{time = DateCreate, key = Key} = Client | Tail], OtherMessage) -> SubSeconds = AllSeconds - time_utilites:get_seconds(DateCreate) - LifeTimeSeconds, case SubSeconds > 0 of true -> ?LOG_INFO("Delete request [Key: ~p] [date: ~w] [seconds sub: ~w]~n", [Key, DateCreate, SubSeconds]), internal_clear_old_messages(AllSeconds, LifeTimeSeconds, Tail, OtherMessage); false -> internal_clear_old_messages(AllSeconds, LifeTimeSeconds, Tail, [Client | OtherMessage]) end. print_all_messages([]) -> ok; print_all_messages([#one_message{time = DateCreate, key = Key, value = Value} | Tail]) -> ?LOG_INFO("* [Key: ~p] [Value: ~p] [Date: ~p]~n", [Key, Value, DateCreate]), print_all_messages(Tail).