| % Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| % use this file except in compliance with the License. You may obtain a copy of |
| % the License at |
| % |
| % http://www.apache.org/licenses/LICENSE-2.0 |
| % |
| % Unless required by applicable law or agreed to in writing, software |
| % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| % License for the specific language governing permissions and limitations under |
| % the License. |
| |
| -module(couch_epi_module_keeper). |
| |
| |
| -behaviour(gen_server). |
| |
| %% ------------------------------------------------------------------ |
| %% API Function Exports |
| %% ------------------------------------------------------------------ |
| |
| -export([start_link/3, stop/1]). |
| -export([reload/1]). |
| |
| |
| %% ------------------------------------------------------------------ |
| %% gen_server Function Exports |
| %% ------------------------------------------------------------------ |
| |
| -export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
| terminate/2, code_change/3]). |
| |
| -record(state, { |
| codegen, module, key, type, handle, hash, kind, |
| timer = {undefined, undefined}}). |
| |
| %% ------------------------------------------------------------------ |
| %% API Function Definitions |
| %% ------------------------------------------------------------------ |
| |
| start_link(Type, Key, Codegen) -> |
| Handle = Codegen:get_handle(Key), |
| gen_server:start_link( |
| {local, Handle}, ?MODULE, [Type, Codegen, Key, Handle], []). |
| |
| stop(Server) -> |
| catch gen_server:call(Server, stop). |
| |
| reload(Server) -> |
| gen_server:call(Server, reload). |
| |
| %% ------------------------------------------------------------------ |
| %% gen_server Function Definitions |
| %% ------------------------------------------------------------------ |
| |
| init([Kind, Codegen, Key, Handle]) -> |
| Type = type(Kind), |
| State = #state{ |
| codegen = Codegen, |
| key = Key, |
| type = Type, |
| handle = Handle, |
| kind = Kind |
| }, |
| compile_module(State). |
| |
| handle_call(reload, _From, State0) -> |
| {Reply, State1} = reload_if_updated(State0), |
| {reply, Reply, State1}; |
| handle_call(_Request, _From, State) -> |
| {reply, ok, State}. |
| |
| handle_cast(_Msg, State) -> |
| {noreply, State}. |
| |
| handle_info(tick, State0) -> |
| {_Res, State1} = reload_if_updated(State0), |
| {noreply, State1}; |
| handle_info(_Info, State) -> |
| {noreply, State}. |
| |
| terminate(_Reason, _State) -> |
| ok. |
| |
| code_change(_OldVsn, State0, _Extra) -> |
| {_Res, State1} = reload_if_updated(State0), |
| {ok, State1}. |
| |
| %% ------------------------------------------------------------------ |
| %% Internal Function Definitions |
| %% ------------------------------------------------------------------ |
| |
| type(data_providers) -> couch_epi_data; |
| type(providers) -> couch_epi_functions; |
| type(services) -> couch_epi_functions. |
| |
| reload_if_updated(#state{handle = Module} = State) -> |
| case couch_epi_util:module_exists(Module) of |
| true -> |
| do_reload_if_updated(State); |
| false -> |
| {ok, State} |
| end. |
| |
| compile_module(State) -> |
| do_reload_if_updated(State). |
| |
| do_reload_if_updated(#state{} = State0) -> |
| #state{ |
| hash = OldHash, |
| type = Type, |
| key = Key, |
| kind = Kind |
| } = State0, |
| Defs = couch_epi_plugin:definitions(Kind, Key), |
| case Type:data(Defs) of |
| {ok, OldHash, _Data} -> |
| {ok, State0}; |
| {ok, Hash, Data} -> |
| {ok, OldData, State1} = safe_set(Hash, Data, State0), |
| notify(Key, OldData, Data, Defs), |
| State2 = update_interval(Type:interval(Defs), State1), |
| {ok, State2}; |
| Else -> |
| {Else, State0} |
| end. |
| |
| update_interval(undefined, #state{timer = Timer} = State) -> |
| State#state{timer = cancel_timer(Timer)}; |
| update_interval(Interval, #state{timer = Timer} = State) -> |
| State#state{timer = start_timer(Interval, Timer)}. |
| |
| start_timer(Interval, {undefined, undefined}) -> |
| {ok, Timer} = timer:send_interval(Interval, self(), tick), |
| {Timer, Interval}; |
| start_timer(Interval, {Timer, _Interval}) -> |
| start_timer(Interval, cancel_timer(Timer)). |
| |
| cancel_timer({undefined, undefined}) -> |
| {undefined, undefined}; |
| cancel_timer({Timer, _Interval}) -> |
| timer:cancel(Timer), |
| {undefined, undefined}. |
| |
| safe_set(Hash, Data, #state{} = State) -> |
| #state{ |
| handle = Handle, |
| codegen = CodeGen |
| } = State, |
| try |
| OldData = CodeGen:get_current_definitions(Handle), |
| ok = CodeGen:generate(Handle, Data), |
| {ok, OldData, State#state{hash = Hash}} |
| catch Class:Reason -> |
| {{Class, Reason}, State} |
| end. |
| |
| notify(Key, OldData, NewData, Defs) -> |
| Specs = [Spec || {_App, Spec} <- Defs], |
| couch_epi_plugin:notify(Key, OldData, NewData, Specs), |
| ok. |