%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2012. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% %% %% ------------------------------------------------------------ %% Erlang Graphics Interface front-end server %% ------------------------------------------------------------ %% -module(gs_frontend). -compile([{nowarn_deprecated_function,{gs,assq,2}}, {nowarn_deprecated_function,{gs,error,2}}]). -export([create/2, config/2, read/2, destroy/2, info/1, start/1, stop/0, init/1, event/3]). -include("gstk.hrl"). %%---------------------------------------------------------------------- %% The ets contains: {Obj,lives}|{Obj,{Name,Pid}} %% new obj is {Int,Node} %% {{Name,Pid},Obj} %%---------------------------------------------------------------------- -record(state, {db,user,user_count,kernel,kernel_count,self}). %%---------------------------------------------------------------------- %% The interface. %%---------------------------------------------------------------------- create(GsPid,Args) -> request(GsPid,{create,Args}). config(GsPid,Args) -> request(GsPid,{config, Args}). read(GsPid,Args) -> request(GsPid,{read, Args}). destroy(GsPid,IdOrName) -> request(GsPid,{destroy, IdOrName}). info(Option) -> request(gs_frontend,{info,Option}). %%---------------------------------------------------------------------- %% Comment: Frontend is only locally registered. These functions are called %% by any backend. %%---------------------------------------------------------------------- event(FrontEnd,ToOwner,EventMsg) -> FrontEnd ! {event, ToOwner,EventMsg}. request(GsPid,Msg) -> GsPid ! {self(),Msg}, receive {gs_reply,R} -> R end. %%---------------------------------------------------------------------- %% The server %%---------------------------------------------------------------------- start(Opts) -> case whereis(gs_frontend) of undefined -> P = spawn_link(gs_frontend,init,[Opts]), case catch register(gs_frontend, P) of true -> request(gs_frontend,{instance, backend_name(Opts), Opts}); {'EXIT', _} -> exit(P,kill), % a raise... and I lost this time start(Opts) end; P -> request(P,{instance,backend_name(Opts),Opts}) end. backend_name(Opts) -> case gs:assq(kernel,Opts) of {value,true} -> kernel; _ -> user end. stop() -> request(gs_frontend,stop). %% ------------------------------------------------------------ %% THE FRONT END SERVER %% ------------------------------------------------------------ %% Initialize %% init(_Opts) -> process_flag(trap_exit, true), DB=ets:new(gs_names,[set,public]), loop(#state{db=DB,self=self()}). loop(State) -> receive X -> % io:format("frontend received: ~p~n",[X]), case catch (doit(X,State)) of done -> loop(State); NewState when is_record(NewState,state) -> loop(NewState); stop -> stop; Reason -> io:format("GS frontend. Last mgs in was:~p~n",[X]), io:format("exit:~p~n",[X]), io:format("Reason: ~p~n", [Reason]), terminate(Reason,State), exit(Reason) end end. reply(To,Msg) -> To ! {gs_reply,Msg}, done. doit({FromOwner,{config, Args}},State) -> {IdOrName, Opts} = Args, #state{db=DB} = State, case idOrName_to_id(DB,IdOrName,FromOwner) of undefined -> reply(FromOwner,{error,{no_such_object,IdOrName}}); Obj -> reply(FromOwner,gstk:config(backend(State,Obj),{Obj,Opts})) end; doit({event,ToOwner,{gs,Obj,Etype,Data,Args}}, #state{db=DB,self=Self}) -> case ets:lookup(DB,Obj) of [{_,{Name,ToOwner}}] -> ToOwner ! {gs,Name,Etype,Data,Args}; _ -> ToOwner ! {gs,{Obj,Self},Etype,Data,Args} end, done; doit({FromOwner,{create,Args}}, State) -> {Objtype, Name, Parent, Opts} = Args, #state{db=DB} = State, NameOccupied = case {Name, ets:lookup(DB,{Name,FromOwner})} of {undefined,_} -> false; {_, []} -> false; _ -> true end, if NameOccupied == true -> reply(FromOwner, {error,{name_occupied,Name}}); true -> case idOrName_to_id(DB,Parent,FromOwner) of undefined -> reply(FromOwner, {error,{no_such_parent,Parent}}); ParentObj -> {Id,NewState} = inc(ParentObj,State), case gstk:create(backend(State,ParentObj), {FromOwner,{Objtype,Id,ParentObj,Opts}}) of ok -> link(FromOwner), if Name == undefined -> ets:insert(DB,{Id,lives}), reply(FromOwner, Id), NewState; true -> % it's a real name, register it NamePid = {Name,FromOwner}, ets:insert(DB,{NamePid,Id}), ets:insert(DB,{Id,NamePid}), reply(FromOwner,Id), NewState end; Err -> reply(FromOwner,Err) end end end; doit({FromOwner,{read, Args}}, State) -> #state{db=DB} = State, {IdOrName, Opt} = Args, case idOrName_to_id(DB,IdOrName,FromOwner) of undefined -> reply(FromOwner,{error,{no_such_object,IdOrName}}); Obj -> reply(FromOwner,gstk:read(backend(State,Obj),{Obj,Opt})) end; doit({'EXIT', UserBackend, Reason}, State) when State#state.user == UserBackend -> gs:error("user backend died reason ~w~n", [Reason]), remove_user_objects(State#state.db), State#state{user=undefined}; doit({'EXIT', KernelBackend, Reason}, State) when State#state.kernel == KernelBackend -> gs:error("kernel backend died reason ~w~n", [Reason]), exit({gs_kernel_died,Reason}); doit({'EXIT', Pid, _Reason}, #state{kernel=K,user=U,db=DB}) -> %% io:format("Pid ~w died reason ~w~n", [Pid, _Reason]), if is_pid(U) -> DeadObjU = gstk:pid_died(U,Pid), remove_objs(DB,DeadObjU); true -> ok end, if is_pid(K) -> DeadObjK = gstk:pid_died(K,Pid), remove_objs(DB,DeadObjK); true -> true end, done; doit({FromOwner,{destroy, IdOrName}}, State) -> #state{db=DB} = State, case idOrName_to_id(DB,IdOrName,FromOwner) of undefined -> reply(FromOwner, {error,{no_such_object,IdOrName}}); Obj -> DeadObj = gstk:destroy(backend(State,Obj),Obj), remove_objs(DB,DeadObj), reply(FromOwner,done) end; doit({From,{instance,user,Opts}},State) -> #state{db=DB, self=Self, user_count=UC} = State, case ets:lookup(DB,1) of [_] -> reply(From, {1,Self}); [] -> ets:insert(DB,{1,lives}), % parent of all user gs objs case gstk:start_link(1, Self, Self, Opts) of {ok, UserBackend} -> reply(From, {1, Self}), case UC of undefined -> State#state{user_count=1, user=UserBackend}; _N -> State#state{user_count=UC+2, user=UserBackend} end; {error, Reason} -> reply(From, {error, Reason}), stop end end; doit({From,{instance,kernel,Opts}},State) -> #state{db=DB,self=Self} = State, case ets:lookup(DB,0) of [_] -> reply(From, {0,Self}); [] -> ets:insert(DB,{0,lives}), % parent of all user gs objs case gstk:start_link(0,Self,Self,Opts) of {ok, KernelBackend} -> reply(From, {0,Self}), State#state{kernel_count=0,kernel=KernelBackend}; {error, Reason} -> reply(From, {error,Reason}), stop end end; doit({From,stop}, State) -> #state{kernel=K,user=U} = State, if is_pid(U) -> gstk:stop(U); true -> true end, if is_pid(K) -> gstk:stop(K); true -> true end, reply(From,stopped), stop; doit({From,{gstk,user,Msg}},State) -> reply(From,gstk:request(State#state.user,Msg)); doit({From,{gstk,kernel,Msg}},State) -> reply(From,gstk:request(State#state.kernel,Msg)); doit({From,{info,gs_db}},State) -> io:format("gs_db:~p~n",[ets:tab2list(State#state.db)]), reply(From,State); doit({From,{info,kernel_db}},State) -> reply(From,gstk:request(State#state.kernel,dump_db)); doit({From,{info,user_db}},State) -> reply(From,gstk:request(State#state.user,dump_db)); doit({From,{info,Unknown}},_State) -> io:format("gs: unknown info option '~w', use one of 'gs_db', 'kernel_db' or 'user_db'~n",[Unknown]), reply(From,ok). terminate(_Reason,#state{db=DB}) -> if DB==undefined -> ok; true -> % io:format("frontend db:~p~n",[ets:tab2list(DB)]) ok end. backend(#state{user=Upid,kernel=Kpid},Obj) -> if Obj rem 2 == 0 -> Kpid; true -> Upid end. %%---------------------------------------------------------------------- %% Returns: {NewId,NewState} %%---------------------------------------------------------------------- inc(ParInt,State) when ParInt rem 2 == 1 -> X=State#state.user_count+2, {X,State#state{user_count=X}}; inc(ParInt,State) when ParInt rem 2 == 0 -> X=State#state.kernel_count+2, {X,State#state{kernel_count=X}}. remove_user_objects(DB) -> DeadObj = find_user_obj(ets:first(DB),DB), remove_objs(DB,DeadObj). find_user_obj(Int,DB) when is_integer(Int) -> if Int rem 2 == 0 -> %% a kernel obj find_user_obj(ets:next(DB,Int),DB); true -> %% a user obj [Int|find_user_obj(ets:next(DB,Int),DB)] end; find_user_obj('$end_of_table',_DB) -> []; find_user_obj(OtherKey,DB) -> find_user_obj(ets:next(DB,OtherKey),DB). remove_objs(DB,[Obj|Objs]) -> case ets:lookup(DB, Obj) of [{_,NamePid}] -> ets:delete(DB,Obj), ets:delete(DB,NamePid); [] -> backend_only end, remove_objs(DB,Objs); remove_objs(_DB,[]) -> done. idOrName_to_id(DB,IdOrName,Pid) when is_atom(IdOrName) -> case ets:lookup(DB,{IdOrName,Pid}) of [{_,Obj}] -> Obj; _ -> undefined end; idOrName_to_id(DB,Obj,_Pid) -> case ets:lookup(DB,Obj) of [_] -> Obj; _ -> undefined end. %% ---------------------------------------- %% done