From c17a0a29f7b2b088dbcc65a56d76a0d7c5a54a4e Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Wed, 28 Jan 2009 00:15:05 +0000 Subject: [PATCH] Add freeswitch.erl - An erlang module to make talking to mod_erlang_event more painless git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@11525 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- .../mod_erlang_event/freeswitch.erl | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 src/mod/event_handlers/mod_erlang_event/freeswitch.erl diff --git a/src/mod/event_handlers/mod_erlang_event/freeswitch.erl b/src/mod/event_handlers/mod_erlang_event/freeswitch.erl new file mode 100644 index 0000000000..f3b21e856d --- /dev/null +++ b/src/mod/event_handlers/mod_erlang_event/freeswitch.erl @@ -0,0 +1,265 @@ +%% 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 +%% @copyright 2008-2009 Andrew Thompson +%% @doc A module for interfacing with FreeSWITCH using mod_erlang_event. + +-module(freeswitch). + +-export([send/2, api/3, bgapi/3, event/2, + nixevent/2, noevents/1, close/1, + get_event_header/2, get_event_body/1, + get_event_name/1, start_fetch_handler/4, + start_log_handler/3, start_event_handler/3]). +-define(TIMEOUT, 10000). + +%% @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 + X -> X + after ?TIMEOUT -> + timeout + end. + +%% @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 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 + X -> X + 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 + X -> X + after ?TIMEOUT -> + timeout + end. + +%% @doc Close the connection to `Node'. +close(Node) -> + {close, Node} ! exit, + receive + X -> X + 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 + X -> X + 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 + X -> X + 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 + X -> X + 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, Process) -> + {handlecall, Node} ! {handlecall, Process}, + receive + X -> X + 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).