I fixed the typo in the XML, but we're still getting a lockup. Also, my log files are indicating that the request for sofia.conf is being issued _twice_ now. I'm attaching all the source code from our freeswitch module. I believe freeswitch.erl is just a copy of the shared source for integrating freeswitch. freeswitch_bind.erl is the one that does the bindings for the configs; it invokes and binds freeswitch_callback.erl. freeswitch_monitor.erl is the process binding for "attaching" the FS node to ERLang. Andrew Thompson wrote:
Mark,
I just noticed your original version had a typo for the global_settings closing tab. If I fix that I do indeed get a temporary hang because of the STUN stuff, but once that times out the module loads successfully. Have you tried the re-factored module I attached in my previous email (you'll want to fix that typo)?
Also, please don't send HTML-only email (like your last reply) it's a pain for me to read :)
Andrew
_______________________________________________ FreeSWITCH-dev mailing list FreeSWITCH-dev@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-dev UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-dev http://www.freeswitch.org
%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (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.mozilla.org/MPL/ %% %% 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. %% %% @author Andrew Thompson <andrew AT hijacked DOT us> %% @copyright 2008-2009 Andrew Thompson %% @doc A module for interfacing with FreeSWITCH using mod_erlang_event. -module(freeswitch). -export([send/2, api/3, api/2, bgapi/3, bgapi/4, event/2, nixevent/2, noevents/1, close/1, get_event_header/2, get_event_body/1, get_event_name/1, getpid/1, sendmsg/3, sendevent/3, handlecall/2, handlecall/3, start_fetch_handler/4, start_log_handler/3, start_event_handler/3]). -define(TIMEOUT, 5000). %% @doc Return the value for a specific header in an event or `{error,notfound}'. get_event_header([], _Needle) -> {error, notfound}; get_event_header({event, Headers}, Needle) when is_list(Headers) -> get_event_header(Headers, Needle); get_event_header([undefined | Headers], Needle) -> get_event_header(Headers, Needle); get_event_header([UUID | Headers], Needle) when is_list(UUID) -> get_event_header(Headers, Needle); get_event_header([{Key,Value} | Headers], Needle) -> case Key of Needle -> Value; _ -> get_event_header(Headers, Needle) end. %% @doc Return the name of the event. get_event_name(Event) -> get_event_header(Event, "Event-Name"). %% @doc Return the body of the event or `{error, notfound}' if no event body. get_event_body(Event) -> get_event_header(Event, "body"). %% @doc Send a raw term to FreeSWITCH. Returns the reply or `timeout' on a %% timeout. send(Node, Term) -> {send, Node} ! Term, receive Response -> Response after ?TIMEOUT -> timeout end. %% @doc Make a blocking API call to FreeSWITCH. The result of the API call is %% returned or `timeout' if FreeSWITCH fails to respond. api(Node, Cmd, Args) -> {api, Node} ! {api, Cmd, Args}, receive {ok, X} -> {ok, X}; {error, X} -> {error, X} after ?TIMEOUT -> timeout end. %% @doc Same as @link{api/3} except there's no additional arguments. api(Node, Cmd) -> api(Node, Cmd, ""). %% @doc Make a backgrounded API call to FreeSWITCH. The asynchronous reply is %% sent to calling process after it is received. This function %% returns the result of the initial bgapi call or `timeout' if FreeSWITCH fails %% to respond. bgapi(Node, Cmd, Args) -> Self = self(), % spawn a new process so that both responses go here instead of directly to % the calling process. spawn(fun() -> {bgapi, Node} ! {bgapi, Cmd, Args}, receive {error, Reason} -> % send the error condition to the calling process Self ! {api, {error, Reason}}; {ok, JobID} -> % send the reply to the calling process Self ! {api, ok}, receive % wait for the job's reply {bgok, JobID, Reply} -> % send the actual command output back to the calling process Self ! {bgok, Reply}; {bgerror, JobID, Reply} -> Self ! {bgerror, Reply} end after ?TIMEOUT -> % send a timeout to the calling process Self ! {api, timeout} end end), % get the initial result of the command, NOT the asynchronous response, and % return it receive {api, X} -> X end. %% @doc Make a backgrounded API call to FreeSWITCH. The asynchronous reply is %% passed as the argument to `Fun' after it is received. This function %% returns the result of the initial bgapi call or `timeout' if FreeSWITCH fails %% to respond. bgapi(Node, Cmd, Args, Fun) -> Self = self(), % spawn a new process so that both responses go here instead of directly to % the calling process. spawn(fun() -> {bgapi, Node} ! {bgapi, Cmd, Args}, receive {error, Reason} -> % send the error condition to the calling process Self ! {api, {error, Reason}}; {ok, JobID} -> % send the reply to the calling process Self ! {api, ok}, receive % wait for the job's reply {bgok, JobID, Reply} -> % Call the function with the reply Fun(ok, Reply); {bgerror, JobID, Reply} -> Fun(error, Reply) end after ?TIMEOUT -> % send a timeout to the calling process Self ! {api, timeout} end end), % get the initial result of the command, NOT the asynchronous response, and % return it receive {api, X} -> X end. %% @doc Request to receive any events in the list `List'. event(Node, Events) when is_list(Events) -> {event, Node} ! list_to_tuple(lists:append([event], Events)), receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end; event(Node, Event) when is_atom(Event) -> event(Node, [Event]). %% @doc Stop receiving any events in the list `Events' from `Node'. nixevent(Node, Events) when is_list(Events) -> {nixevent, Node} ! list_to_tuple(lists:append([nixevent], Events)), receive X -> X after ?TIMEOUT -> timeout end; nixevent(Node, Event) when is_atom(Event) -> nixevent(Node, [Event]). %% @doc Stop receiving any events from `Node'. noevents(Node) -> {noevents, Node} ! noevents, receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end. %% @doc Close the connection to `Node'. close(Node) -> {close, Node} ! exit, receive ok -> ok after ?TIMEOUT -> timeout end. %% @doc Send an event to FreeSWITCH. `EventName' is the name of the event and %% `Headers' is a list of `{Key, Value}' string tuples. See the mod_event_socket %% documentation for more information. sendevent(Node, EventName, Headers) -> {sendevent, Node} ! {sendevent, EventName, Headers}, receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end. %% @doc Send a message to the call identified by `UUID'. `Headers' is a list of %% `{Key, Value}' string tuples. sendmsg(Node, UUID, Headers) -> {sendmsg, Node} ! {sendmsg, UUID, Headers}, receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end. %% @doc Get the fake pid of the FreeSWITCH node at `Node'. This can be helpful %% for linking to the process. Returns `{ok, Pid}' or `timeout'. getpid(Node) -> {getpid, Node} ! getpid, receive {ok, Pid} when is_pid(Pid) -> {ok, Pid} after ?TIMEOUT -> timeout end. %% @doc Request that FreeSWITCH send any events pertaining to call `UUID' to %% `Process' where process is a registered process name. handlecall(Node, UUID, Process) -> {handle_call, Node} ! {handle_call, UUID, Process}, io:format("Freeswitch.erl received call from ~w.~n", Node), receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end. %% @doc Request that FreeSWITCH send any events pertaining to call `UUID' to %% the calling process. handlecall(Node, UUID) -> {handle_call, Node} ! {handle_call, UUID}, receive ok -> ok; {error, Reason} -> {error, Reason} after ?TIMEOUT -> timeout end. %% @private start_handler(Node, Type, Module, Function) -> Self = self(), spawn(fun() -> monitor_node(Node, true), {foo, Node} ! Type, receive ok -> Self ! {Type, {ok, self()}}, apply(Module, Function, [Node]); {error,Reason} -> Self ! {Type, {error, Reason}} after ?TIMEOUT -> Self ! {Type, timeout} end end), receive {Type, X} -> X end. %% @todo Notify the process if it gets replaced by a new log handler. %% @doc Spawn `Module':`Function' as a log handler. The process will receive %% messages of the form `{log, [{level, LogLevel}, {text_channel, TextChannel}, {file, FileName}, {func, FunctionName}, {line, LineNumber}, {data, LogMessage}]}' %% or `{nodedown, Node}' if the FreesSWITCH node at `Node' exits. %% %% The function specified by `Module':`Function' should be tail recursive and is %% passed one argument; the name of the FreeSWITCH node. %% %% Subsequent calls to this function for the same node replaces the %% previous event handler with the newly spawned one. %% %% This function returns either `{ok, Pid}' where `Pid' is the pid of the newly %% spawned process, `{error, Reason}' or the atom `timeout' if FreeSWITCH did %% not respond. start_log_handler(Node, Module, Function) -> start_handler(Node, register_log_handler, Module, Function). %% @todo Notify the process if it gets replaced with a new event handler. %% @doc Spawn Module:Function as an event handler. The process will receive %% messages of the form `{event, [UniqueID, {Key, Value}, {...}]}' where %% `UniqueID' is either a FreeSWITCH call ID or `undefined' or %% `{nodedown, Node}' if the FreeSWITCH node at `Node' exits. %% %% The function specified by `Module':`Function' should be tail recursive and is %% passed one argument; the name of the FreeSWITCH node. %% %% Subsequent calls to this function for the same node replaces the %% previous event handler with the newly spawned one. %% %% This function returns either `{ok, Pid}' where `Pid' is the pid of the newly %% spawned process, `{error, Reason}' or the atom `timeout' if FreeSWITCH did %% not respond. start_event_handler(Node, Module, Function) -> start_handler(Node, register_event_handler, Module, Function). %% @doc Spawn Module:Function as an XML config fetch handler for configs of type %% `Section'. See the FreeSWITCH documentation for mod_xml_rpc for more %% information on sections. The process will receive messages of the form %% `{fetch, Section, Tag, Key, Value, ID, Data}' or `{nodedown, Node}' if the %% FreeSWITCH node at `Node' exits. %% %% The function specified by `Module':`Function' should be tail recursive and is %% passed one argument; the name of the FreeSWITCH node. The function should %% send tuples back to FreeSWITCH of the form `{fetch_reply, ID, XML}' where %%`ID' is the ID received in the request tuple and `XML' is XML in string or %% binary form of the form noted in the mod_xml_rpc documentation. %% %% Subsequent calls to this function for the same node and section will yield %% undefined behaviour. %% %% This function returns either `{ok, Pid}' where `Pid' is the pid of the newly %% spawned process, `{error, Reason}' or the atom `timeout' if FreeSWITCH did %% not respond. start_fetch_handler(Node, Section, Module, Function) -> start_handler(Node, {bind, Section}, Module, Function). -module(freeswitch_app). -author('fig@marketelsystems.com'). -behaviour(supervisor). -export([start/2, stop/1, init/1, go/0]). -define(MAX_RESTART, 5). -define(MAX_TIME, 60). %%---------------------------------------------------------------------- %% Application behaviour callbacks %%---------------------------------------------------------------------- start(_Type, _Args) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [freeswitch_monitor]). stop(_S) -> ok. go() -> Apps = [tcp_server, figure, storage], lists:foreach(fun (App) -> application:stop(App) end, lists:reverse(Apps)), lists:foreach(fun (App) -> application:start(App) end, Apps). %%---------------------------------------------------------------------- %% Supervisor behaviour callbacks %%---------------------------------------------------------------------- init([Module]) -> {ok, {_SupFlags = {one_for_one, ?MAX_RESTART, ?MAX_TIME}, [ % FreeSWITCH supervisor. { Module, % Id = internal id {Module,start_link,[]}, % StartFun = {M, F, A} permanent, % Restart = permanent | transient | temporary 15000, % Shutdown = brutal_kill | int() >= 0 | infinity worker, % Type = worker | supervisor [Module] % Modules = [Module] | dynamic } ] } }. -module(freeswitch_bind). -behaviour(gen_server). -record(st, {fsnode, pbxpid, configpid, dirpid, dialpid}). -export([start/3, terminate/2, code_change/3, init/1, handle_call/3, handle_cast/2, handle_info/2]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% gen_server methods start(Node, Section, Pid) -> gen_server:start(?MODULE, [Node, Section, Pid], []). init([Node, Section, Pid]) -> io:format( "freeswitch_bind:init( [Node=~w, Section=~w, Pid=~w])~n", [Node, Section, Pid] ), {api, Node} ! {bind, Section}, receive ok -> {ok, ConfigurationPid } = freeswitch:start_fetch_handler( Node, configuration, freeswitch_callback, fetch_handler ), {ok, DirectoryPid } = freeswitch:start_fetch_handler( Node, directory, freeswitch_callback, fetch_handler ), {ok, DialplanPid } = freeswitch:start_fetch_handler( Node, dialplan, freeswitch_callback, fetch_handler ), {ok, #st{fsnode=Node, pbxpid=Pid, configpid=ConfigurationPid, dirpid=DirectoryPid, dialpid=DialplanPid}}; {error, Reason} -> {stop, {error, {freeswitch_error, Reason}}} after 5000 -> {stop, {error, freeswitch_timeout}} end. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% %% If the request isn't recognized, just log it and do nothing. %% handle_call(Request, _From, State) -> io:format("freeswitch_bind:handle_call( ~w, _From, State) unrecognized request~n", [Request]), {reply, {error, unrecognized_request}, State}. handle_cast(Message, State) -> error_logger:error_msg("~p received unrecognized cast ~p~n", [self(), Message]), {noreply, State}. handle_info({fetch, Section, Tag, Key, Value, FetchID, Params}, #st{fsnode=Node, pbxpid=Pid}=State) -> {ok, Xml} = freeswitch_callback:xml_fetch( {fetch, Section, Tag, Key, Value, Params} ), %% {ok, XML} = gen_server:call(Pid, {fetch, Section, Tag, Key, Value, Params}), {api, Node} ! {fetch_reply, FetchID, Xml}, receive ok -> {noreply, State}; {error, Reason} -> {stop, {error, Reason}, State} end; handle_info(Info, State) -> io:format( "freeswitch_bind:handle_info() Info=~w~n", [Info]), {noreply, State}. %% Author: mark %% Created: Sep 15, 2009 %% Description: TODO: Add description to freeswitch_callback -module(freeswitch_callback). -behaviour(gen_server). -record(st, {fsnode, pbxpid}). -export([start/3, terminate/2, code_change/3, init/1, handle_call/3, handle_cast/2, handle_info/2, fetch_handler/1, xml_fetch/1]). start(Node, Section, Pid) -> gen_server:start(?MODULE, [Node, Section, Pid], []). init([Node, Section, Pid]) -> io:format( "freeswitch_callback:init( [Node=~w, Section=~w, Pid=~w])~n", [Node, Section, Pid] ), {ok, #st{fsnode=Node, pbxpid=Pid}}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% %% Callback for freeswitch:start_fetch_handler() called in freeswitch_bind:init() %% fetch_handler( FreeswitchNode ) -> receive { nodedown, Node } -> io:format( "freeswitch_callback:fetch_handler() Node ~w is down~n", [Node] ), ok; { fetch, Section, Tag, Key, Value, FetchId, Params } -> io:format( "freeswitch_callback:fetch_handler() Invoking xml_fetch()~n" ), {ok, Xml} = xml_fetch( {fetch, Section, Tag, Key, Value, Params} ), io:format( "freeswitch_callback:fetch_handler() Sending reply to FreeswitchNode ~w: ~s~n", [FreeswitchNode, Xml] ), {api, FreeswitchNode} ! {fetch_reply, FetchId, Xml}, %% FreeswitchNode ! { fetch_reply, FetchId, Xml }, io:format( "freeswitch_callback:fetch_handler() Reply sent~n" ) %% io:format( "freeswitch_callback:fetch_handler() Waiting for confirmation reply was received~n"), %% receive %% ok -> %% io:format( "freeswitch_callback:fetch_handler() Reply receipt confirmed~n" ), %% ok; %% {error, Reason} -> %% io:format( "freeswitch_callback:fetch_handler() Reply receipt failed due to error ~s~n", [Reason] ), %% {error, Reason} %% end end, { ok } = fetch_handler( FreeswitchNode ), { ok }. dumpParams( Params ) -> case Params of [] -> ok; [ H | T ] -> case H of undefined -> io:format( " undefined~n" ); { Key, Value } -> io:format( " ~s=\"~s\"~n", [Key, Value ] ) end, dumpParams( T ) end. %% %% Configuration handler for mod_sofia. %% xml_fetch({fetch, configuration, "configuration", "name", "sofia.conf", Params}) -> io:format( "freeswitch_callback:handle_call( {fetch, configuration, Tag=\"configuration\", Key=\"name\", Value=\"sofia.conf\", Params=...} )~n"), dumpParams( Params ), Xml = "<document type=\"freeswitch/xml\"> <section name=\"configuration\"> <configuration name=\"sofia.conf\" description=\"sofia Endpoint\"> <global_settings> <param name=\"log-level\" value=\"0\" /> <param name=\"auto-restart\" value=\"false\" /> <param name=\"debug-presence\" value=\"0\" /> </global_settings> <settings> <!-- ADD parameters here --> </settings> <profiles> <profile name=\"external\"> <gateways> <!-- Add gateways here --> <gateway name=\"asterlink.com\"> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> <param name=\"\" value=\"\" /> </gateway> </gateways> <aliases> <alias name=\"outbound\" /> <alias name=\"nat\" /> </aliases> <domains> <domain name=\"all\" alias=\"false\" parse=\"true\" /> </domains> <settings> <param name=\"debug\" value=\"0\" /> <param name=\"sip-trace\" value=\"no\" /> <param name=\"rfc2833-pt\" value=\"101\" /> <param name=\"sip-port\" value=\"6080\" /> <param name=\"dialplan\" value=\"XML\" /> <param name=\"context\" value=\"public\" /> <param name=\"dtmf-duration\" value=\"100\" /> <param name=\"codec-prefs\" value=\"PCMU,PCMA,GSM\" /> <param name=\"hold-music\" value=\"local_stream://moh\" /> <param name=\"rtp-timer-name\" value=\"soft\" /> <!--param name=\"enable-100rel\" value=\"true\" / --> <param name=\"manage-presence\" value=\"false\" /> <!-- param name=\"dbname\" value=\"share_presence\" / --> <!-- param name=\"presence-hosts\" value=\"10.77.0.254\" / --> <!-- param name=\"force-register-domain\" value=\"10.77.0.254\" / --> <!-- param name=\"force-register-db-domain\" value=\"10.77.0.254\" / --> <!-- param name=\"aggressive-nat-detection\" value=\"true\" / --> <param name=\"inbound-codec-negotiation\" value=\"generous\" /> <param name=\"nonce-ttl\" value=\"60\" /> <param name=\"auth-calls\" value=\"false\" /> <param name=\"rtp-timeout-sec\" value=\"1800\" /> <param name=\"rtp-ip\" value=\"10.77.0.254\" /> <param name=\"sip-ip\" value=\"10.77.0.254\" /> <param name=\"ext-rtp-ip\" value=\"stun:stun.freeswitch.org\" /> <param name=\"ext-sip-ip\" value=\"stun:stun.freeswitch.org\" /> <param name=\"rtp-timout-sec\" value=\"300\" /> <param name=\"rtp-hold-timeout-sec\" value=\"1800\" /> <!-- param name=\"enable-3pcc\" value=\"true\" / --> <param name=\"tls-bind-params\" value=\"transport=tls\" /> <param name=\"tls-sip-port\" value=\"6081\" /> <param name=\"tls-cert-dir\" value=\"/opt/freeswitch/conf/ssl\" /> <param name=\"tls-version\" value=\"tlsv1\" /> <!-- param name=\"rtp-autoflush-during-bridge\" value=\"false\" / --> <!-- param name=\"rtp-rewrite-timestamp\" value=\"true\" / --> <!-- param name=\"pass-rfc2833\" value=\"true\" / --> <!-- param name=\"odbc-dsn\" value=\"dsn:user:pass\" / --> <!-- param name=\"inbound-bypass-media\" value=\"true\" / --> <!-- param name=\"inbound-proxy-media\" value=\"true\" / --> <!-- param name=\"inbound-late-negotiation\" value=\"true\" / --> <!-- param name=\"accept-blind-reg\" value=\"true\" / --> <!-- param name=\"accept-blind-auth\" value=\"true\" / --> <!-- param name=\"suppress-cng\" value=\"true\" / --> <param name=\"nonce-ttl\" value=\"60\" /> <!-- param name=\"disable-transcoding\" value=\"true\" / --> <!-- param name=\"disable-transfer\" value=\"true\" / --> <!-- param name=\"NDLB-broken-auth-hash\" value=\"true\" / --> <!-- param name=\"NDLB-received-in-nat-reg-contact\" value=\"true\" / --> <param name=\"auth-calls\" value=\"true\" /> <param name=\"inbound-reg-force-matching-username\" value=\"true\" /> <param name=\"auth-all-packets\" value=\"false\" /> <!-- param name=\"ext-rtp-ip\" value=\"stun.freeswitch.org\" / --> <!-- param name=\"ext-sip-ip\" value=\"stun.freeswitch.org\" / --> <param name=\"rtp-timeout-sec\" value=\"300\" /> <param name=\"rtp-hold-timeout-sec\" value=\"1800\" /> <!-- param name=\"vad\" value=\"in\" / --> <!-- param name=\"vad\" value=\"out\" / --> <!-- param name=\"vad\" value=\"both\" / --> <!-- param name=\"alias\" value=\"sip:10.77.0.231:5555\" / --> <param name=\"force-register-domain\" value=\"10.77.0.231\" /> <param name=\"force-register-db-domain\" value=\"10.77.0.231\" /> <!-- param name=\"force-subscription-expires\" value=\"60\" / --> <!-- param name=\"disable-transfer\" value=\"true\" / --> <!-- param name=\"disable-register\" value=\"true\" / --> <!-- param name=\"enable-3pcc\" value=\"true\" / --> <!-- param name=\"NDLB-force-rport\" value=\"true\" / --> <param name=\"challenge-realm\" value=\"auto_from\" /> <!-- param name=\"disable-rtp-auto-adjust\" value=\"true\" / --> <!-- param name=\"inbound-use-callid-as-uuid\" value=\"true\" / --> <!-- param name=\"outbound-use-uuid-as-callid\" value=\"true\" / --> <!-- param name=\"rtp-autofix-timing\" value=\"false\" / --> <!-- param name=\"auto-rtp-bugs\" data=\"clear\" / --> <!-- param name=\"disable-srv\" value=\"false\" / --> <!-- param name=\"disable-naptr\" value=\"false\" / --> <!-- param name=\"timer-T1\" value=\"800\" / --> <!-- param name=\"timer-T1X64\" value=\"32000\" / --> <!-- param name=\"timer-T2\" value=\"4000\" / --> <!-- param name=\"timer-T4\" value=\"4000\" / --> </settings> </profile> </profiles> </configuration> </section> </document>", {ok, Xml }; %% %% Configuration handler replies that the requested document section, tag, and key are not %% found. %% xml_fetch({fetch, configuration, Tag, Key, Value, Params}) -> io:format( "freeswitch_callback:handle_call( {fetch, configuration, Tag=~s, Key=~s, Value=~s, Params=...} )~n", [Tag, Key, Value]), dumpParams( Params ), Xml = "<document type=\"freeswitch/xml\"> <section name=\"result\"> <result status=\"not found\" /> </section> </document>", {ok, Xml }; %% %% Directory handler replies that the requested document section, tag, and key are not %% found. %% xml_fetch({fetch, directory, Tag, Key, Value, Params}) -> io:format( "freeswitch_callback:xml_fetch( {fetch, directory, Tag=~s, Key=~s, Value=~s, Params=...} )~n", [Tag, Key, Value]), dumpParams( Params ), Xml = "<document type=\"freeswitch/xml\"> <section name=\"result\"> <result status=\"not found\" /> </section> </document>", {ok, Xml }; %% %% Dialplan handler replies that the requested document section, tag, and key are not %% found. %% xml_fetch({fetch, dialplan, Tag, Key, Value, Params}) -> io:format( "freeswitch_callback:xml_fetch( {fetch, dialplan, Tag=~s, Key=~s, Value=~s, Params=...} )~n", [Tag, Key, Value]), dumpParams( Params ), Xml = "<document type=\"freeswitch/xml\"> <section name=\"result\"> <result status=\"not found\" /> </section> </document>", {ok, Xml }; %% %% Default handler replies that the requested document section, tag, and key are not %% found. %% xml_fetch({fetch, Section, Tag, Key, Value, Params}) -> io:format( "freeswitch_callback:xml_fetch( {fetch, Section=~w, Tag=~s, Key=~s, Value=~s, Params=...} )~n", [Section, Tag, Key, Value]), dumpParams( Params ), Xml = "<document type=\"freeswitch/xml\"> <section name=\"result\"> <result status=\"not found\" /> </section> </document>", {ok, Xml }; %% %% If the request isn't recognized, just log it. %% xml_fetch( Request ) -> io:format( "freeswitch_callback:xml_fetch( Request=~w ) not recognized~n", [Request]), Xml = "<document type=\"freeswitch/xml\"> <section name=\"result\"> <result status=\"not found\" /> </section> </document>", {ok, Xml }. %% %% If the request isn't recognized, just log it and do nothing. %% handle_call(Request, _From, State) -> io:format("freeswitch_callback:handle_call( ~w, _From, State) unrecognized request~n", [Request]), {reply, {error, unrecognized_request}, State}. handle_cast(Message, State) -> error_logger:error_msg("~p received unrecognized cast ~p~n", [self(), Message]), {noreply, State}. handle_info(Info, State) -> io:format( "freeswitch_callback:handle_info() Info=~w~n", [Info]), {noreply, State}. %%handle_info({fetch, Section, Tag, Key, Value, FetchID, Params}, #st{fsnode=Node, pbxpid=Pid}=State) -> %% {ok, XML} = gen_server:call(Pid, {fetch, Section, Tag, Key, Value, Params}), %% {api, Node} ! {fetch_reply, FetchID, XML}, %% receive %% ok -> %% {noreply, State}; %% {error, Reason} -> %% {stop, {error, Reason}, State} %% end. -module(freeswitch_monitor). -author('fig@marketelsystems.com'). -behaviour(gen_server). -define(PID_ANNOUNCE, "run_freeswitch.pid="). -define(PREFIX, "freeswitch< "). -define(PROG, "./run_freeswitch"). -record(state, {port, prefix=?PREFIX, pid, fspid, sync=[], trigger, node}). -export([start_link/0, terminate/2, code_change/3, init/1, handle_call/3, handle_cast/2, handle_info/2]). -export([send/1]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% gen_server methods start_link() -> process_flag(trap_exit, true), SName = lists:takewhile(fun ($.) -> false; (_C) -> true end, net_adm:localhost()), FsNode = list_to_atom("freeswitch@" ++ SName), io:format("FsNode = ~w calling freeswitch:start_link()~n", [FsNode]), gen_server:start_link({global, freeswitch}, ?MODULE, FsNode, []). init(FsNode) -> %% Have our terminate/2 function run when our supervisor shuts us down. io:format("init(~w) called~n",[FsNode]), State = #state{port=starconf:start_ext(?MODULE, ?PROG), node=FsNode}, io:format("State = ~w~n", [State]), {ok, try_getpid(1000, State)}. terminate(Reason, State) -> case Reason of {process_exited, _} -> ok; _ -> Port = State#state.port, catch port_command(Port, "shutdown\n"), receive {Port, {exit_status, _}} -> ok after 5000 -> starconf:stop_ext(State#state.port, State#state.pid) end end, ok. code_change(OldVsn, State, _Extra) -> starconf:stop_ext(State#state.port, State#state.pid), {ok, #state{port=starconf:start_ext(?MODULE, OldVsn, ?PROG)}}. handle_call(sync, From, #state{fspid=undefined}=State) -> io:format("freeswitch_monitor reached to handle sync call 1.~n"), {foo, State#state.node} ! getpid, {noreply, State#state{sync=[From | State#state.sync]}}; handle_call(sync, _From, State) -> %% We are synced, so return our Pid to link to. io:format("freeswitch_monitor reached to handle sync call 2 and is successful.~n"), {reply, {ok, self()}, State}; handle_call(_, _From, #state{fspid=undefined}=State) -> io:format("Freeswitch_monitor is not ready and throw error msg.~n"), {reply, {error, not_ready}, State}; handle_call({pbx_api, Pid, Tag, Cmd, Args}, _From, State) -> %% We background the call so we can continue, but reply to %% the original caller from our child process. CPid = spawn(fun () -> link(State#state.fspid), {api, State#state.node} ! {api, Cmd, Args}, receive {error, Reason} -> Pid ! {Tag, {error, Reason}}; {ok, Reply} -> Pid ! {Tag, {ok, Reply}} end end), {reply, {ok, CPid}, State}; handle_call({pbx_bind, Section, Pid}, _From, State) -> Status = freeswitch_bind:start(State#state.node, Section, Pid), {reply, Status, State}; handle_call(Request, _From, State) -> error_logger:error_msg("~p:~p received unrecognized request ~p~n", [?MODULE, self(), Request]), {reply, {error, unrecognized_request}, State}. handle_cast({pbx_api, _Pid, _Tag, _Cmd, _Args}=Request, State) -> handle_call(Request, undefined, State), {noreply, State}; handle_cast({send, Cmd}, State) -> catch port_command(State#state.port, [Cmd, "\n"]), {noreply, State}; handle_cast(Message, State) -> error_logger:error_msg("~p:~p received unrecognized cast ~p~n", [?MODULE, self(), Message]), {noreply, State}. handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) -> case Status of 0 -> {stop, normal, State}; _N -> {stop, {process_exited, Status}, State} end; handle_info({try_getpid, Timeout}, State) -> {noreply, try_getpid(Timeout, State)}; handle_info({ok, Pid}, #state{sync=Sync}=State) when is_pid(Pid) -> error_logger:info_msg("~p:~p got ~p pid ~p~n", [?MODULE, self(), State#state.node, Pid]), case State#state.trigger of undefined -> ok; Trigger -> timer:cancel(Trigger), ok end, lists:foreach(fun (From) -> gen_server:reply(From, {ok, self()}) end, Sync), {noreply, State#state{trigger=undefined, fspid=Pid, sync=[]}}; handle_info({Port, {data, {noeol, Output}}}, #state{port=Port}=State) -> io:format("~s~s", [State#state.prefix, Output]), {noreply, State#state{prefix=""}}; handle_info({Port, {data, {eol, Output}}}, #state{port=Port}=State) -> io:format("~s~s~n", [State#state.prefix, Output]), Len = string:len(?PID_ANNOUNCE), Pid = case string:substr(Output, 1, Len) of ?PID_ANNOUNCE -> string:substr(Output, Len + 1); _ -> State#state.pid end, {noreply, State#state{prefix=?PREFIX, pid=Pid}}; handle_info(Message, State) -> error_logger:error_msg("~p:~p: Got info ~p~n", [?MODULE, self(), Message]), {noreply, State}. try_getpid(Timeout, #state{fspid=undefined}=State) -> error_logger:info_msg("~p:~p trying to get ~p pid~n", [?MODULE, self(), State#state.node]), {foo, State#state.node} ! getpid, {ok, Trigger} = timer:send_after(Timeout, {try_getpid, Timeout}), State#state{trigger=Trigger}; try_getpid(_Timeout, State) -> State. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% API send(Cmd) -> gen_server:cast({global, freeswitch}, {send, Cmd}).