%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%%-------------------------------------------------------------------
%%% File : wxe_server.erl
%%% Author : Dan Gudmundsson <[email protected]>
%%% Description : Application server monitors the application and handles
%%% callbacks, some cleaning if processes dies.
%%% The interface functions is found in wxe_util.erl
%%% Created : 17 Jan 2007 by Dan Gudmundsson <[email protected]>
%%%-------------------------------------------------------------------
%% @hidden
-module(wxe_server).
-behaviour(gen_server).
%% API
-export([start/0, stop/0, register_me/1, set_debug/2, invoke_callback/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {port,cb_port,users,cleaners=[],cb,cb_cnt}).
-record(user, {objects=[], events=[], evt_handler}).
-record(event, {object, callback, cb_handler}).
-define(APPLICATION, wxe).
-define(log(S,A), log(?MODULE,?LINE,S,A)).
-include("wxe.hrl").
-include("../include/wx.hrl").
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start() -> #wx_env{}
%% Description: Starts the server
%%--------------------------------------------------------------------
start() ->
case get(?WXE_IDENTIFIER) of
undefined ->
case gen_server:start(?MODULE, [], []) of
{ok, Pid} ->
{ok, Port} = gen_server:call(Pid, get_port, infinity),
wx:set_env(Env = #wx_env{port=Port,sv=Pid}),
Env;
{error, {Reason, _Stack}} ->
erlang:error(Reason)
end;
Env = #wx_env{sv=Pid} ->
case erlang:is_process_alive(Pid) of
true ->
Env;
false -> %% Ok we got an old wx env, someone forgot
erase(?WXE_IDENTIFIER), %% to call wx:destroy()
start()
end
end.
stop() ->
#wx_env{sv=Pid} = get(?WXE_IDENTIFIER),
catch gen_server:call(Pid, stop, infinity),
ok.
register_me(Pid) ->
ok = gen_server:call(Pid, register_me, infinity).
set_debug(Pid, Level) ->
gen_server:cast(Pid, {debug, Level}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
{Port,CBPort} = wxe_master:init_port(),
put(?WXE_IDENTIFIER, #wx_env{port=Port,sv=self()}),
{ok,#state{port=Port, cb_port=CBPort,
users=gb_trees:empty(), cb=gb_trees:empty(), cb_cnt=1}}.
%% Register process
handle_call(register_me, {From,_}, State=#state{users=Users}) ->
erlang:monitor(process, From),
case gb_trees:is_defined(From, Users) of
true ->
{reply, ok, State};
false ->
New = gb_trees:insert(From, #user{}, Users),
{reply, ok, State#state{users=New}}
end;
%% Port request
handle_call(get_port, _, State=#state{port=Port}) ->
{reply, {ok,Port}, State};
%% Connect callback
handle_call({connect_cb,Obj,Msg},{From,_},State) ->
handle_connect(Obj,Msg, From, State);
handle_call({disconnect_cb,Obj,Msg},{From,_},State) ->
handle_disconnect(Obj,Msg, From, State);
handle_call(stop,{_From,_},State = #state{users=Users0, cleaners=Cs0}) ->
Env = get(?WXE_IDENTIFIER),
Users = gb_trees:to_list(Users0),
Cs = lists:map(fun({Pid,User}) ->
spawn_link(fun() -> cleanup(Env,Pid,[User]) end)
end, Users),
{noreply, State#state{users=gb_trees:empty(), cleaners=Cs ++ Cs0}};
handle_call({register_cb, Fun}, _, State0) ->
{FunId, State} = attach_fun(Fun,State0),
{reply, FunId, State};
%% Error
handle_call(_Request, _From, State) ->
?log("Unknown request ~p sent to ~p from ~p ~n",[_Request, ?MODULE, _From]),
Reply = ok,
{reply, Reply, State}.
%%%%%%%%%%%% Cast's
handle_cast({cleaned, From}, State=#state{users=Users,cleaners=Cs0}) ->
Cs = lists:delete(From,Cs0),
case Cs =:= [] andalso gb_trees:is_empty(Users) of
true -> {stop, normal, State#state{cleaners=Cs}};
false -> {noreply,State#state{cleaners=Cs}}
end;
handle_cast({debug, Level}, State) ->
Env = get(?WXE_IDENTIFIER),
put(?WXE_IDENTIFIER, Env#wx_env{debug=Level}),
{noreply, State};
handle_cast(_Msg, State) ->
?log("Unknown message ~p sent to ~p~n",[_Msg, ?MODULE]),
{noreply, State}.
%%%% Info
%% Callback request from driver
handle_info(Cb = {_, _, '_wx_invoke_cb_'}, State) ->
invoke_cb(Cb, State),
{noreply, State};
handle_info({wx_delete_cb, FunId}, State0 = #state{cb=CB}) when is_integer(FunId) ->
case get(FunId) of
undefined ->
{noreply, State0};
Fun ->
erase(FunId),
{noreply, State0#state{cb=gb_trees:delete(Fun, CB)}}
end;
handle_info({'DOWN',_,process,Pid,_}, State=#state{users=Users0,cleaners=Cs}) ->
try
User = gb_trees:get(Pid,Users0),
Users = gb_trees:delete(Pid,Users0),
Env = wx:get_env(),
Cleaner = spawn_link(fun() -> cleanup(Env,Pid,[User]) end),
{noreply, State#state{users=Users,cleaners=[Cleaner|Cs]}}
catch _E:_R ->
%% ?log("Error: ~p ~p", [_E,_R]),
{noreply, State}
end;
handle_info(Msg = {'_wxe_destroy_', Pid}, State) ->
case erlang:is_process_alive(Pid) of
true ->
Pid ! Msg,
ok;
false ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
?log("Unknown message ~p sent to ~p~n",[_Info, ?MODULE]),
{noreply, State}.
terminate(_Reason, _State) ->
%% erlang:display({?MODULE, killed, process_info(self(),trap_exit),_Reason}),
%% timer:sleep(250), %% Give driver a chance to clean up
shutdown.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
log(Mod,Line,Str,Args) ->
error_logger:format("~p:~p: " ++ Str, [Mod,Line|Args]).
handle_connect(Object, EvData, From, State0 = #state{users=Users}) ->
User0 = #user{events=Evs0,evt_handler=Handler0} = gb_trees:get(From, Users),
Callback = wxEvtHandler:get_callback(EvData),
case Handler0 of
#wx_ref{} when Callback =:= 0 ->
CBHandler = Handler0,
Handler = Handler0;
undefined when Callback =:= 0 ->
Handler = new_evt_listener(State0),
CBHandler = Handler;
_ ->
CBHandler = new_evt_listener(State0),
Handler = Handler0
end,
Evs = [#event{object=Object,callback=Callback, cb_handler=CBHandler}|Evs0],
User = User0#user{events=Evs, evt_handler=Handler},
State1 = State0#state{users=gb_trees:update(From, User, Users)},
if is_function(Callback) orelse is_pid(Callback) ->
{FunId, State} = attach_fun(Callback,State1),
Res = wxEvtHandler:connect_impl(CBHandler,Object,
wxEvtHandler:replace_fun_with_id(EvData,FunId)),
case Res of
ok -> {reply,Res,State};
_Error -> {reply,Res,State0}
end;
true ->
Res = {call_impl, connect_cb, CBHandler},
{reply, Res, State1}
end.
invoke_cb({{Ev=#wx{}, Ref=#wx_ref{}}, FunId,_}, _S) ->
%% Event callbacks
case get(FunId) of
Fun when is_function(Fun) ->
invoke_callback(fun() -> Fun(Ev, Ref), <<>> end);
Pid when is_pid(Pid) -> %% wx_object sync event
invoke_callback(Pid, Ev, Ref);
Err ->
?log("Internal Error ~p~n",[Err])
end;
invoke_cb({FunId, Args, _}, _S) when is_list(Args), is_integer(FunId) ->
%% Overloaded functions
case get(FunId) of
Fun when is_function(Fun) ->
invoke_callback(fun() -> Fun(Args) end);
Err ->
?log("Internal Error ~p ~p ~p~n",[Err, FunId, Args])
end.
invoke_callback(Fun) ->
Env = get(?WXE_IDENTIFIER),
CB = fun() ->
wx:set_env(Env),
wxe_util:cast(?WXE_CB_START, <<>>),
Res = try
Return = Fun(),
true = is_binary(Return),
Return
catch _:Reason ->
?log("Callback fun crashed with {'EXIT, ~p, ~p}~n",
[Reason, erlang:get_stacktrace()]),
<<>>
end,
wxe_util:cast(?WXE_CB_RETURN, Res)
end,
spawn(CB),
ok.
invoke_callback(Pid, Ev, Ref) ->
Env = get(?WXE_IDENTIFIER),
CB = fun() ->
wx:set_env(Env),
wxe_util:cast(?WXE_CB_START, <<>>),
try
case get_wx_object_state(Pid) of
ignore ->
%% Ignore early events
wxEvent:skip(Ref);
{Mod, State} ->
case Mod:handle_sync_event(Ev, Ref, State) of
ok -> ok;
noreply -> ok;
Return -> exit({bad_return, Return})
end
end
catch _:Reason ->
wxEvent:skip(Ref),
?log("Callback fun crashed with {'EXIT, ~p, ~p}~n",
[Reason, erlang:get_stacktrace()])
end,
wxe_util:cast(?WXE_CB_RETURN, <<>>)
end,
spawn(CB),
ok.
get_wx_object_state(Pid) ->
case process_info(Pid, dictionary) of
{dictionary, Dict} ->
case lists:keysearch('_wx_object_',1,Dict) of
{value, {'_wx_object_', {_Mod, '_wx_init_'}}} -> ignore;
{value, {'_wx_object_', Value}} -> Value;
_ -> ignore
end;
_ -> ignore
end.
new_evt_listener(State) ->
#wx_env{port=Port} = wx:get_env(),
_ = erlang:port_control(Port,98,<<>>),
get_result(State).
get_result(_State) ->
receive
{'_wxe_result_', Res} -> Res;
{'_wxe_error_', Op, Error} ->
erlang:error({Error, {wxEvtHandler, {internal_installer, Op}}})
end.
attach_fun(Fun, S = #state{cb=CB,cb_cnt=Next}) ->
case gb_trees:lookup(Fun,CB) of
{value, ID} ->
{ID,S};
none ->
put(Next,Fun),
{Next,S#state{cb=gb_trees:insert(Fun,Next,CB),cb_cnt=Next+1}}
end.
handle_disconnect(Object, Evh, From, State0 = #state{users=Users0}) ->
User0 = #user{events=Evs0, evt_handler=PidH} = gb_trees:get(From, Users0),
Fun = wxEvtHandler:get_callback(Evh),
case find_handler(Evs0, Object, Fun) of
[] ->
{reply, false, State0};
Handlers ->
case disconnect(Object,Evh, Handlers) of
Ev = #event{callback=CB, cb_handler=Handler} ->
case is_function(CB) of
true -> wxEvtHandler:destroy_evt_listener(Handler);
false -> ignore
end,
User = case lists:delete(Ev,Evs0) of
[] when PidH =/= undefined ->
wxEvtHandler:destroy_evt_listener(PidH),
User0#user{events=[], evt_handler=undefined};
Evs ->
User0#user{events=Evs}
end,
{reply, true, State0#state{users=gb_trees:update(From,User,Users0)}};
Result ->
{reply, Result, State0}
end
end.
disconnect(Object,Evh,[Ev=#event{cb_handler=Handler}|Evs]) ->
case wxEvtHandler:disconnect_impl(Handler,Object,Evh) of
true -> Ev;
false -> disconnect(Object, Evh, Evs);
Error -> Error
end;
disconnect(_, _, []) -> false.
find_handler(Evs, Object, Fun) ->
find_handler(Evs, Object, Fun, []).
find_handler([Ev =#event{object=Object,callback=FunReg}|Evs],Object,Search,Acc) ->
case FunReg =:= Search of
true -> find_handler(Evs,Object,Search,[Ev|Acc]);
false when is_function(FunReg), Search =:= 0 ->
find_handler(Evs,Object,Search,[Ev|Acc]);
_ ->
find_handler(Evs,Object,Search,Acc)
end;
find_handler([_|Evs],Object,Fun,Res) ->
find_handler(Evs,Object,Fun,Res);
find_handler([],_Object,_Fun,Res) ->
Res.
%% Cleanup
%% The server handles callbacks from driver so every other wx call must
%% be called from another process, therefore the cleaning must be spawned.
%%
cleanup(Env, _Pid, Data) ->
put(?WXE_IDENTIFIER, Env),
lists:foreach(fun cleanup/1, Data),
gen_server:cast(Env#wx_env.sv, {cleaned, self()}),
normal.
cleanup(#user{objects=_Os,events=Evs, evt_handler=Handler}) ->
lists:foreach(fun(#event{object=_O, callback=CB, cb_handler=CbH}) ->
%%catch wxEvtHandler:disconnect_impl(CbH,O),
case is_function(CB) of
true ->
wxEvtHandler:destroy_evt_listener(CbH);
false ->
ignore
end
end, Evs),
case Handler of
undefined -> ignore;
_ -> wxEvtHandler:destroy_evt_listener(Handler)
end,
ok.