gstk_port_handler.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 462 行

ERL
462
字号
%% ``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 via the world wide web 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.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %%     $Id$%%%% ------------------------------------------------------------%%%% This is a driver for the 'gstk' application modified to %% handle events for gs. 'gstk' is a modified standalone wish.%% %% FIXME%% mkdir tcl ; cd tcl%% ( cd /usr/local/pgm/tcl-8.3.3 ; tar -cf - * ) | tar -xf -%% ( cd /usr/local/pgm/tk-8.3.3 ; tar -cf - * ) | tar -xf -%% rm -fr include man bin/tclsh%% cd ..%% tar -cf tcl.tar *%%%% -------------------------------------------------------------module(gstk_port_handler).-include("gstk.hrl").% The executable can have many names. There is not always% a plain "wish" program. % FIXME There has to be a better solution....% FIXME Add option in app file or environmen  variable.-define(WISHNAMES, ["wish84","wish8.4",		    "wish83","wish8.3",		    "wish82","wish8.2",		    "wish"]).%% ------------------------------------------------------------%%                  DEBUG FUNCTIONS %% -------------------------------------------------------------export([exec/1,call/2,	 start_link/1,init/2,ping/1,stop/1]).-export([wait_for_connection/2]).-define(START_TIMEOUT , 1000 * 30).-define(ACCEPT_TIMEOUT, 1000 * 20).-define(DEBUGLEVEL, 4).-ifdef(DEBUG).-define(DBG(DbgLvl,Format, Data),dbg(DbgLvl, Format, Data)).-define(DBG_STR(DbgLvl, What, Str),dbg_str(DbgLvl, What, Str)).dbg(DbgLvl, Format, Data) when DbgLvl =< ?DEBUGLEVEL ->    ok = io:format("DBG: " ++ Format, Data);dbg(_DbgLvl, _Format, _Data) -> ok.dbg_str(DbgLvl, What, Str) when DbgLvl =< ?DEBUGLEVEL ->    ok = io:format("DBG: ~s~s\n", [What,dbg_s(Str)]);dbg_str(_DbgLvl, _What, _Data) -> ok.dbg_s([]) ->    [];dbg_s([C | Str]) when list(C) ->    [dbg_s(C) | dbg_s(Str)];dbg_s([C | Str]) when C >= 20, C < 255 ->    [C | dbg_s(Str)];dbg_s([$\n | Str]) ->    ["\\n" | dbg_s(Str)];dbg_s([$\r | Str]) ->    ["\\r" | dbg_s(Str)];dbg_s([$\t | Str]) ->    ["\\t" | dbg_s(Str)];dbg_s([C | Str]) when integer(C) ->    [io_lib:format("\\~.3.0w",[C]) | dbg_s(Str)].-else.-define(DBG(DbgLvl,Format, Data), true).-define(DBG_STR(DbgLvl, What, Str), true).-endif.%% ------------------------------------------------------------%%                  INTERFACE FUNCTIONS %% ------------------------------------------------------------% Note: gs is not a true application so this doesn't work :-(% Communication protocol between Erlang backend and wish program% that can be set in the application environment, e.i. tested% with "erl -gs backend_comm socket"%%   backend_comm = socket | port%% We fake reading the application variables from the command line.% Note that multiple -gs arguments can't be used.get_env(App, KeyAtom) ->    KeyStr = atom_to_list(KeyAtom),    ?DBG(1,"Result from init:get_argument(~w): ~p\n",	[KeyAtom,init:get_argument(App)]),    case init:get_argument(App) of	{ok,[[KeyStr,ValStr]]} ->	    {ok,list_to_atom(ValStr)};	_ ->	    undefined    end.start_link(Gstk) ->    ?DBG(1, "start_link(~w)~n", [Gstk]),%    io:format("STARTS ~p\n",[erlang:localtime()]),    Mode = 	% FIXME: Want to use application:get_env() if we where an true app	case {os:type(),get_env(gs,backend_comm)} of	    {{win32,_Flavor},undefined} ->		use_socket;	    {_OS,undefined} ->		use_port;	    {_OS,{ok,socket}} ->		use_socket;	    {_OS,{ok,port}} ->		use_port	end,    ?DBG(1,"We use mode: ~w (~w)\n",[Mode,get_env(gs,backend_comm)]),    Pid = spawn_link(gstk_port_handler, init, [Gstk,Mode]),    receive	{Pid, ok} ->	    {ok, Pid};	{Pid, error, Reason} ->	    {error, Reason}    after ?START_TIMEOUT ->	    {error, timeout}    end.call(PortHandler, Cmd) ->    PortHandler ! {call, ["erlcall {",Cmd,$}]},    receive        {result, Result}         ->             ?DBG(1, "call reply: ~p~n", [Result]),            {result,     Result};        {bad_result, Bad_Result} ->            ?DBG(1, "bad call reply: ~p~n", [Bad_Result]),            {bad_result, Bad_Result}    end.ping(PortHandler) ->    ?DBG(1, "ping~n", []),    PortHandler ! {ping, self()},    receive	{pong,_From,PortOrSock} -> {ok,PortOrSock}    end.stop(PortHandler) ->    ?DBG(1, "stop~n", []),    PortHandler ! {stop,self()},    receive	{stopped,PortHandler} -> ok    end.%% Purpose: asyncron call to tk %% too expensive % FIXME   exec(Cmd) ->    get(port_handler) ! {exec, ["erlexec {",Cmd,$}]},    ok.% in gstk context, but I don't want "ifndef nt40" in other % modules than this one.%exec(Cmd) ->%    ?DBG_STR(1, "", ["erlexec {",Cmd,"}"]),%    case get(port) of%	{socket,Sock} ->%	    gen_tcp:send(Sock, ["erlexec {",Cmd,$}]);%	{port,Port} ->%	    Port ! {get(port_handler),{command,["erlexec {",Cmd,$}]}}%    end,%    ok.%% ===========================================================================%% The server%% ===========================================================================%% ---------------------------------------------------------------------%% We initiate by starting the wish port program and use the pipe%% or a socket to communicate with it.%%%% gstk: is the pid of the gstk process that started me. %%      all my input (from the port) is forwarded to it.%%-----------------------------------------------------------------------record(state,{out,gstk}).init(Gstk, Mode) ->    process_flag(trap_exit,true),    % ------------------------------------------------------------    % Set up paths    % ------------------------------------------------------------    PrivDir = code:priv_dir(gs),    TclDir = filename:join(PrivDir,"tcl"),    TclBinDir = filename:join(TclDir,"bin"),    TclLibDir = filename:join(TclDir,"lib"),    InitScript = filename:nativename(filename:join(PrivDir,"gstk.tcl")),    ?DBG(1, "TclBinDir  : ~s\n", [TclBinDir]),    ?DBG(1, "TclLibDir  : ~s\n", [TclLibDir]),    ?DBG(1, "InitScript : ~s\n", [InitScript]),    % ------------------------------------------------------------    % Search for wish in priv and in system search path    % ------------------------------------------------------------    {Wish,Options} = 	case filelib:wildcard(filename:join(TclBinDir,"wish*")) of	    % If more than one wish in priv we assume they are the same	    [PrivWish | _] ->		% ------------------------------------------------		% We have to set TCL_LIBRARY and TK_LIBRARY because else		% 'wish' will search in the original installation directory		% for 'tclIndex' and this may be an incompatible version on		% the host we run on.		% ------------------------------------------------		[TclLibrary] =		    filelib:wildcard(filename:join(PrivDir,						   "tcl/lib/tcl[1-9]*")),		[TkLibrary]  =		    filelib:wildcard(filename:join(PrivDir,						   "tcl/lib/tk[1-9]*")),		Opts = [{env,[{"TCL_LIBRARY", TclLibrary},			      {"TK_LIBRARY", TkLibrary},			      {"LD_LIBRARY_PATH",TclLibDir}]},		       {packet,4}],		{PrivWish,Opts};	    _ ->		% We use the system wish program		{search_wish(?WISHNAMES, Gstk),[{packet,4}]}	end,    ?DBG(1, "Wish       : ~s\n", [Wish]),    Cmd =	case Mode of	    use_socket ->		% ------------------------------------------------------------		% Set up a listening socket and call accept in another process		% ------------------------------------------------------------		SocketOpts =		    [		     {nodelay, true},		     {packet,4},		     {reuseaddr,true}		    ],		% Let OS pick a number		{ok,ListenSocket} = gen_tcp:listen(0, SocketOpts),		{ok,ListenPort} = inet:port(ListenSocket),		% Wait in another process		spawn_link(?MODULE,wait_for_connection,[self(),ListenSocket]),		lists:concat([Wish," ",InitScript," -- ",PrivDir," ",			      ListenPort]);	    use_port ->		lists:concat([Wish," ",InitScript," -- ",PrivDir])	end,    ?DBG(1, "Port opts  :\n~p\n", [Options]),    % FIXME remove timing if not debugging    Port =	case timer:tc(erlang,open_port,[{spawn, Cmd}, Options]) of	    {_T,Port1} when is_port(Port1) ->		?DBG(1,"open_port takes ~p milliseconds\n",[_T/1000]),		link(Port1),		Port1;	    {_T,{error,_Reason1}} ->		% FIXME: Why throw away reason?!		?DBG(1,"ERROR: ~p\n",[_Reason1]),		Gstk ! {self(), error, backend_died},		exit(normal)	end,    State =	case Mode of	    use_socket ->		% ------------------------------------------------------------		% Wait for a connection		% ------------------------------------------------------------		Sock =		    receive			{connected,Socket} ->			    Socket;			% FIXME: Why throw away reason?!			{'EXIT', _Pid, _Reason2} ->			    Gstk ! {self(), error, backend_died},			    exit(normal)		    end,		?DBG(1,"Got socket ~p~n",[Sock]),		#state{out={socket,Sock}, gstk=Gstk};	    use_port ->		#state{out={port,Port}, gstk=Gstk}	end,    Gstk ! {self(), ok},			% Tell caller we are prepared    idle(State).search_wish([], Gstk) ->    Gstk ! {self(), error, backend_died},    exit(normal);search_wish([WishName | WishNames], Gstk) ->    case os:find_executable(WishName) of	false ->	    search_wish(WishNames, Gstk);	Wish ->	    Wish    end.	%%----------------------------------------------------------------------%% If we use sockets we wait for connection from port prog%%----------------------------------------------------------------------wait_for_connection(CallerPid, ListenSocket) ->    {ok,Sock} = gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT),    ?DBG(1,"Got accept ~p~p~n",[self(),Sock]),    ok = gen_tcp:controlling_process(Sock,CallerPid),    CallerPid ! {connected,Sock}.%% ===========================================================================%% The main loop%% ===========================================================================idle(State) ->    ?DBG(1, "idle~n", []),%    io:format("IDLE ~p\n",[erlang:localtime()]),    receive	{call, Cmd} ->	    output(State, Cmd),	    idle(State);	{exec, Cmd} ->	    collect_exec_calls(Cmd, [], 0, State),	    idle(State);	{_Port, {data, Input}} ->	    ?DBG_STR(2, "INPUT[port]: ", [Input]),	    handle_input(State, Input),	    idle(State);	{tcp, _Sock, Input} ->	    ?DBG_STR(2, "INPUT[sock]: ", [Input]),	    handle_input(State, Input),	    idle(State);	{ping,From} ->	    From ! {pong,self(),State#state.out},	    idle(State);	{stop,From} ->	    From ! {stopped,self()};	% FIXME: We are we not to terminate if watforsocket	% terminated but what about the port???????	{'EXIT',_Pid,normal} ->	    ?DBG(1, "EXIT[~w]: normal~n", [_Pid]),	    idle(State);	{'EXIT',Pid,Reason} ->	    %%io:format("Port died when in idle loop!~n"),	    ?DBG(1,"EXIT[~w]~n~p~n",[Pid,Reason]),	    exit({port_handler,Pid,Reason});	Other ->	    ?DBG(1,"OTHER: ~p~n",[Other]),	    gs:error("gstk_port_handler: got other: ~w~n",[Other]),	    idle(State)    end.%% -----------------------------------------------------------------------define(MAXQUEUE, 4).				% FIXME find value...collect_exec_calls(Cmd, Queue, QueueLen, State) when QueueLen < ?MAXQUEUE ->    receive	{exec, NewCmd} ->%	    io:format("collect~p~n", [NewCmd]),	    collect_exec_calls(NewCmd, [Cmd | Queue], QueueLen+1, State)    after 0 ->	    if		QueueLen == 0 ->		    output(State, Cmd);		true ->		    output(State, join_cmd_reverse(Cmd, Queue, []))	    end    end;collect_exec_calls(Cmd, Queue, _QueueLen, State) -> % Queue is full, output    String = join_cmd_reverse(Cmd, Queue, []),%    io:format("queue full: ~p~n", [String]),    output(State, String).join_cmd_reverse(Cmd, [], DeepStr) ->    [DeepStr | Cmd];join_cmd_reverse(Cmd, [Cmd1 | Cmds], DeepStr) ->    join_cmd_reverse(Cmd, Cmds, [Cmd1,$; | DeepStr]).%% ----------------------------------------------------------------------%%%% Handle incoming data%% 1 - Event%% 2 - Reply from call%% 3 - Bad reply from call%% 4 - Error%% 5 - End of message%% handle_input(State,[Type | Data]) ->    GstkPid = State#state.gstk,    case Type of	1 ->	    handle_event(GstkPid,Data);	2 ->	    GstkPid ! {result, Data};	3 ->	    GstkPid ! {bad_result, Data};	4 ->	    gs:error("gstk_port_handler: error in input : ~s~n",[Data])    end.%% ----------------------------------------------------------------------%% output a command to the port%% buffer several incoming execs%%output(#state{out = {socket,Sock}}, Cmd) ->    ?DBG_STR(1, "OUTPUT[sock]: ", [Cmd]),    ok = gen_tcp:send(Sock, Cmd);output(#state{out = {port,Port}}, Cmd) ->    ?DBG_STR(1, "OUTPUT[port]: ", [Cmd]),    Port ! {self(), {command, Cmd}}.% FIXME why test list?handle_event(GstkPid, Bytes) when is_list(Bytes) ->    Event = tcl2erl:parse_event(Bytes),    ?DBG(1,"Event = ~p\n",[Event]),    gstk:event(GstkPid, Event). %% Event is {ID, Etag, Args}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?