Initial checkin of the POE::Filter::FSSocket module and fsconsole.pl which is a curses, multi-window console for freeswitch mod_event_socket. See http://search.cpan.org/~ptinsley/POE-Filter-FSSocket-0.04/lib/POE/Filter/FSSocket.pm for more info on the perl module.

git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3116 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
Paul Tinsley 2006-10-20 06:20:39 +00:00
parent 3a32d9e53c
commit bb5b51dfbe
11 changed files with 1219 additions and 0 deletions

View File

@ -0,0 +1,50 @@
=========================
2006-10-14 00:08:00 v0_05
=========================
2006-10-14 00:08:00 (r5) by ptinsley
Added some "bullet proofing" in the module to handle bad/unknown input
Updated the curses example (fsconsole.pl)
Added debug and strict parsing as part of the bullet proofing
to enable debug add debug => 1 in your new() and if you want the module
to croak when it doesn't properly parse something use strict => 1 in the
new(). Example Poe::Filter::FSSocket->new(debug => 1, strict => 1)
The default for debug and strict is 0
=========================
2006-10-14 00:08:00 v0_04
=========================
2006-10-14 00:08:00 (r4) by ptinsley
Fixed support for log/data
Added an example that is a quick curses console for freeswitch
=========================
2006-09-18 21:08:00 v0_03
=========================
2006-09-18 21:08:00 (r3) by ptinsley
Added support for log/data
=========================
2006-09-17 23:57:00 v0_02
=========================
2006-09-17 23:57:00 (r2) by ptinsley
Added support for api/response type, data ends up in api-response variable.
=========================
2006-09-17 22:19:20 v0_01
=========================
2006-09-17 22:19:20 (r1) by ptinsley
Initial package of the module.
==============
End of Excerpt
==============

View File

@ -0,0 +1,4 @@
perl Makefile.PL
make
make test
make install

View File

@ -0,0 +1,18 @@
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.
The Original Code is POE::Filter::FSSocket.
The Initial Developer of the Original Code is Paul Tinsley <pdt@jackhammer.org>.
Portions created by the Initial Developer are Copyright (C) 2006
the Initial Developer. All Rights Reserved.
Contributor(s):
Paul Tinsley <pdt@jackhammer.org>

View File

@ -0,0 +1,22 @@
#!/usr/bin/perl
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'POE::Filter::FSSocket',
AUTHOR => 'Paul Tinsley <pdt@jackhammer.org>',
ABSTRACT => 'POE filter for getting events out of FreeSWITCH',
VERSION_FROM => 'lib/POE/Filter/FSSocket.pm',
PM => { 'lib/POE/Filter/FSSocket.pm' => '$(INST_LIBDIR)/FSSocket.pm' },
PREREQ_PM => {
POE => 0.3101,
Test::More => 0,
POE::Component::Client::TCP => 0,
POE::Filter::Line => 0,
},
dist => {
COMPRESS => 'gzip -9f',
SUFFIX => 'gz',
},
);

View File

@ -0,0 +1,3 @@
A POE filter for FreeSWITCH (http://www.freeswitch.org) that parses event/log/etc... messages for you. You must ask for events in plain mode.
perldoc POE::Filter::FSSocket for more info and an example.

View File

@ -0,0 +1,5 @@
- Support bgapi output
- more example scripts
- more documentation
- sister component that uses this filter
- reconnect handling

View File

@ -0,0 +1,361 @@
#!/usr/bin/perl
use strict;
use warnings;
sub POE::Kernel::ASSERT_DEFAULT () { 1 };
sub Term::Visual::DEBUG () { 1 }
sub Term::Visual::DEBUG_FILE () { 'test.log' }
use IO::Socket;
use POE qw/Filter::FSSocket Component::Client::TCP/;
use Data::Dumper;
use Term::Visual;
local *D;
if (Term::Visual::DEBUG) {
*D = *Term::Visual::ERRS;
}
#local *ERROR = *STDERR;
$SIG{__DIE__} = sub {
if (Term::Visual::DEBUG) {
print Term::Visual::ERRS "Died: @_\n";
}
};
###############################################################################
## BEGIN Globals ##############################################################
###############################################################################
our $server_address = "127.0.0.1";
our $server_port = "8021";
our $server_secret = "ClueCon";
#this is where you can customize the color scheme
our %Pallet = (
'warn_bullet' => 'bold yellow',
'err_bullet' => 'bold red',
'out_bullet' => 'bold green',
'access' => 'bright red on blue',
'current' => 'bright yellow on blue',
);
our $terminal;
my %sockets;
my %windows;
my %unread_count;
my %commands = (
'window' => 1,
'w' => 1,
'win' => 1,
);
###############################################################################
## END Globals ##############################################################
###############################################################################
#setup our session
POE::Session->create(
'inline_states' => {
'_start' => \&handle_start, #session start
'_stop' => \&handle_stop, #session stop
'curses_input' => \&handle_curses_input, #input from the keyboard
'update_time' => \&handle_update_time, #update the status line clock
'quit' => \&handle_quit, #handler to do any cleanup
'server_input' => \&handle_server_input,
'_default' => \&handle_default,
},
'heap' => {
'terminal' => undef,
'freeswitch' => undef,
},
);
#start the kernel a chugging along
$poe_kernel->run;
###############################################################################
## BEGIN Handlers #############################################################
###############################################################################
#handles any startup functions for our session
sub handle_default {
}
sub handle_start {
my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
#setup our terminal
$heap->{'terminal'} = Term::Visual->new(
'Alias' => 'terminal', #poe alias for this
'History_Size' => 300, #number of things to keep in history
'Common_Input' => 1, #all windows share input and history
'Tab_Complete' => \&tab_complete,
);
$terminal = $heap->{'terminal'};
#setup the color palette
$terminal->set_palette(%Pallet);
#create a base window
my $window_id = $terminal->create_window(
'Window_Name' => 'console',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Console',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'console'} = $window_id;
$window_id = $terminal->create_window(
'Window_Name' => 'log',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Logs',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'log'} = $window_id;
$window_id = $terminal->create_window(
'Window_Name' => 'event',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Event',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'event'} = $window_id;
#tell the terminal what to call when there is input from the keyboard
$kernel->post('terminal' => 'send_me_input' => 'curses_input');
$terminal->change_window(0);
$kernel->delay_set('update_time' => 1);
$terminal->set_status_field(0, 'time' => scalar(localtime));
new_message('destination_window' => 0, 'message' => "
Welcome to the FreeSWITCH POE Curses Console!
The console is split into three windows:
- 'console' for api response messages
- 'log' for freeswitch log output (simply send the log level you want
to start seeing events eg: 'log all')
- 'event' for freeswitch event output (must subscribe in plain format
eg: 'event plain all')
To switch between windows type 'w <windowname' so 'w log' for example.
Coming soon:
- Tab completion
- command history
- window status in the bar (messages added since last view, etc...)
Send any bug reports or comments to jackhammer\@gmail.com
Thanks,
Paul\n");
$terminal->set_status_field($terminal->current_window, 'window_status' => format_window_status());
#connect to freeswitch
$heap->{'freeswitch'} = POE::Component::Client::TCP->new(
'RemoteAddress' => $server_address,
'RemotePort' => $server_port,
'ServerInput' => \&handle_server_input,
'Connected' => \&handle_fs_connected,
'ServerError' => \&handle_server_error,
'Disconnected' => \&handle_server_disconnect,
'Domain' => AF_INET,
'Filter' => POE::Filter::FSSocket->new(),
);
}
#called when users enter commands in a window
sub handle_curses_input {
my ($kernel, $heap, $input, $context) = @_[KERNEL, HEAP, ARG0, ARG1];
#get the id of the window that is responsible for the input
my $window = $heap->{'terminal'}->current_window;
open(ERROR, ">>error.log");
if($input eq "quit") {
$kernel->yield('quit');
} elsif ($input =~ /^w\ (.*)$/) {
#get the id of the requested window
eval {
my $window_id = $windows{$1};
#see if it's real
if(defined($window_id)) {
$unread_count{$window_id} = 0;
$terminal->change_window($window_id);
$terminal->set_status_field($window_id, 'window_status' => &format_window_status());
}
};
if($@) {
print ERROR "put error: $@\n";
}
} else {
#see if we got connected at some point
if(defined($sockets{'localhost'})) {
#send the command
$sockets{'localhost'}->put($input);
}
}
}
sub handle_fs_connected {
my ($kernel, $heap) = @_[KERNEL, HEAP];
eval {
$sockets{'localhost'} = $heap->{'server'};
}
}
#this is responsible for doing any cleanup and returning the terminal to the previous
#state before we mucked with it
sub handle_quit {
my ($kernel, $heap) = @_[KERNEL, HEAP];
#tell curses to clean up it's crap
$kernel->post('terminal' => 'shutdown');
#there is probably a more elegant way, but this works for now
exit;
}
#data from freeswitch
sub handle_server_input {
my ($kernel,$heap,$input) = @_[KERNEL,HEAP,ARG0];
eval {
#terminal HATES null
if(defined($input->{'__DATA__'})) {
$input->{'__DATA__'} =~ s/[\x00]//g;
}
#handle the login
if($input->{'Content-Type'} eq "auth/request") {
$heap->{'server'}->put("auth $server_secret");
} elsif ($input->{'Content-Type'} eq "api/response") {
new_message('destination_window' => 0, 'message' => 'Response: ');
new_message('destination_window' => 0, 'message' => $input->{'__DATA__'});
} elsif ($input->{'Content-Type'} eq "log/data") {
new_message('destination_window' => 1, 'message' => $input->{'__DATA__'});
} elsif ($input->{'Content-Type'} eq "text/event-plain") {
new_message('destination_window' => 2, 'message' => Dumper $input);
}
};
if($@) {
open(ERROR, ">>error.log");
print ERROR "died: $@\n";
print ERROR Dumper $heap;
close(ERROR);
}
}
sub handle_server_error {
}
sub handle_server_disconnect {
}
sub tab_complete {
my $left = shift;
my @return;
if(defined($commands{$left})) {
return [$left . " "];
#} elsif () {
}
}
sub handle_update_time {
my ($kernel, $heap) = @_[KERNEL, HEAP];
$terminal->set_status_field($terminal->current_window, 'time' => scalar(localtime));
$kernel->delay_set('update_time' => 1);
}
###############################################################################
## END Handlers #############################################################
###############################################################################
sub new_message {
my %args = @_;
my $message = $args{'message'};
my $destination_window = $args{'destination_window'};
my $status_field;
#see if we are on the window being updated
if($terminal->current_window != $destination_window) {
#increment the unread count for the window
#FIXME, should we count messages or lines?
$unread_count{$destination_window}++;
#update the status bar
eval {
$terminal->set_status_field($terminal->current_window, 'window_status' => &format_window_status());
};
if($@) {
print $@;
}
}
#deliver the message
$terminal->print($destination_window, $message);
}
sub format_window_status {
my $status_field;
#put all the windows in the bar with their current unread count
foreach my $window (sort {$windows{$a} <=> $windows{$b}} keys %windows) {
#see if we are printing the current window
if($terminal->current_window == $windows{$window}) {
$status_field .= "[\0(current)$window\0(st_frames)";
} else {
$status_field .= "[$window";
}
if($unread_count{$windows{$window}}) {
$status_field .= " (" . $unread_count{$windows{$window}} . ")";
}
$status_field .= "] ";
}
return $status_field;
}

View File

@ -0,0 +1,42 @@
#!/usr/bin/perl
use warnings;
use strict;
use POE qw(Component::Client::TCP Filter::FSSocket);
use Data::Dumper;
my $auth_sent = 0;
my $password = "ClueCon";
POE::Component::Client::TCP->new(
'RemoteAddress' => '127.0.0.1',
'RemotePort' => '8021',
'ServerInput' => \&handle_server_input,
'Filter' => POE::Filter::FSSocket->new(),
);
POE::Kernel->run();
exit;
sub handle_server_input {
my ($heap,$input) = @_[HEAP,ARG0];
print Dumper $input;
if($input->{'Content-Type'} eq "auth/request") {
$auth_sent = 1;
$heap->{'server'}->put("auth $password");
} elsif ($input->{'Content-Type'} eq "command/reply") {
if($auth_sent == 1) {
$auth_sent = -1;
#do post auth stuff
$heap->{'server'}->put("events plain all");
$heap->{'server'}->put("log");
$heap->{'server'}->put("api show channels");
}
}
}

View File

@ -0,0 +1,343 @@
=head1 NAME
POE::Filter::FSSocket - a POE filter that parses FreeSWITCH events into hashes
=head1 SYNOPSIS
#!/usr/bin/perl
use warnings;
use strict;
use POE qw(Component::Client::TCP Filter::FSSocket);
use Data::Dumper;
my $auth_sent = 0;
my $password = "ClueCon";
POE::Component::Client::TCP->new(
'RemoteAddress' => '127.0.0.1',
'RemotePort' => '8021',
'ServerInput' => \&handle_server_input,
'Filter' => 'POE::Filter::FSSocket',
);
POE::Kernel->run();
exit;
sub handle_server_input {
my ($heap,$input) = @_[HEAP,ARG0];
print Dumper $input;
if($input->{'Content-Type'} eq "auth/request") {
$auth_sent = 1;
$heap->{'server'}->put("auth $password");
} elsif ($input->{'Content-Type'} eq "command/reply") {
if($auth_sent == 1) {
$auth_sent = -1;
#do post auth stuff
$heap->{'server'}->put("events plain all");
}
}
}
=head1 EXAMPLES
See examples in the examples directory of the distribution.
=head1 DESCRIPTION
POE::Filter::FSSocket parses output from FreeSWITCH into hashes. FreeSWITCH
events have a very wide range of keys, the only consistant one being
Content-Type. The keys are dependant on the type of events. You must use the
plain event type as that is what the filter knows how to parse. You can ask for
as many event types as you like or all for everything. You specify a list of
event types by putting spaces between them ex: "events plain api log talk"
Currently known event types (Event-Name):
CUSTOM
CHANNEL_CREATE
CHANNEL_DESTROY
CHANNEL_STATE
CHANNEL_ANSWER
CHANNEL_HANGUP
CHANNEL_EXECUTE
CHANNEL_BRIDGE
CHANNEL_UNBRIDGE
CHANNEL_PROGRESS
CHANNEL_OUTGOING
CHANNEL_PARK
CHANNEL_UNPARK
API
LOG
INBOUND_CHAN
OUTBOUND_CHAN
STARTUP
SHUTDOWN
PUBLISH
UNPUBLISH
TALK
NOTALK
SESSION_CRASH
MODULE_LOAD
DTMF
MESSAGE
CODEC
BACKGROUND_JOB
ALL
Currently handled FreeSWITCH messages (Content-Type):
auth/request
command/response
text/event-plain
api/response (data in __DATA__ variable)
log/data (data in __DATA__ variable)
=cut
package POE::Filter::FSSocket;
use warnings;
use strict;
use Carp qw(carp croak);
use vars qw($VERSION);
use base qw(POE::Filter);
$VERSION = '0.05';
use POE::Filter::Line;
use Data::Dumper;
#self array
sub LINE_FILTER() {1}
sub PARSER_STATE() {2}
sub PARSER_STATENEXT() {3}
sub PARSED_RECORD() {4}
sub CURRENT_LENGTH() {5}
sub STRICT_PARSE() {6}
sub DEBUG_LEVEL() {7}
#states of the parser
sub STATE_WAITING() {1} #looking for new input
sub STATE_CLEANUP() {2} #wipe out record separators
sub STATE_GETDATA() {3} #have header, get data
sub STATE_FLUSH() {4} #puts us back in wait state and tells us to kill the parsed_record
sub STATE_TEXTRESPONSE() {5} #used for api output
sub new {
my $class = shift;
my %args = @_;
my $strict = 0;
my $debug = 0;
if(defined($args{'debug'})) {
$debug = $args{'debug'};
}
if(defined($args{'strict'}) && $args{'strict'} == 1) {
$strict = $args{'strict'};
}
#our filter is line based, don't reinvent the wheel
my $line_filter = POE::Filter::Line->new();
my $self = bless [
"", #not used by me but the baseclass clone wants it here
$line_filter, #LINE_FILTER
STATE_WAITING, #PARSER_STATE
undef, #PARSER_STATE
{}, #PARSED_RECORD
0, #length tracking (for Content-Length when needed)
$strict, #whether we should bail on a bad parse or try and save the session
$debug, #debug level
], $class;
return $self;
}
sub get_one_start {
my ($self, $stream) = @_;
$self->[LINE_FILTER]->get_one_start($stream);
}
sub get_one {
my $self = shift;
while(1) {
#grab a line from the filter
my $line = $self->[LINE_FILTER]->get_one();
#quit if we can't get any lines
return [] unless @$line;
#get the actual line
$line = $line->[0];
if(($self->[PARSER_STATE] == STATE_WAITING) || ($self->[PARSER_STATE] == STATE_FLUSH)) {
#see if we need to wipe out the parsed_record info
if($self->[PARSER_STATE] == STATE_FLUSH) {
delete $self->[PARSED_RECORD];
$self->[CURRENT_LENGTH] = 0;
$self->[PARSER_STATE] = STATE_WAITING;
}
if($line =~ /Content-Length:\ (\d+)$/) {
#store the length
$self->[PARSED_RECORD]{'Content-Length'} = $1;
#see if we had a place to go from here (we should)
if(defined($self->[PARSER_STATENEXT])) {
$self->[PARSER_STATE] = $self->[PARSER_STATENEXT];
$self->[PARSER_STATENEXT] = undef;
}
} elsif($line =~ /Content-Type:\ (.*)$/) {
#store the type of request
$self->[PARSED_RECORD]{'Content-Type'} = $1;
if($1 eq "auth/request") {
$self->[PARSER_STATE] = STATE_CLEANUP;
$self->[PARSER_STATENEXT] = STATE_FLUSH;
return [ $self->[PARSED_RECORD] ];
} elsif ($1 eq "command/reply") { #do something with this later
$self->[PARSER_STATE] = STATE_GETDATA;
} elsif ($1 eq "text/event-plain") {
$self->[PARSER_STATE] = STATE_CLEANUP;
$self->[PARSER_STATENEXT] = STATE_GETDATA;
} elsif ($1 eq "api/response") {
$self->[PARSER_STATENEXT] = STATE_TEXTRESPONSE;
} elsif ($1 eq "log/data") {
$self->[PARSER_STATENEXT] = STATE_TEXTRESPONSE;
} else { #unexpected input
croak ref($self) . " unknown input [" . $self->[PARSER_STATE] . "] (" . $line . ")";
}
} else {
#already in wait state, if we are not in strict, keep going
if($self->[STRICT_PARSE]) {
croak ref($self) . " unknown input [STATE_WAITING] (" . $line . ")";
}
}
} elsif ($self->[PARSER_STATE] == STATE_CLEANUP) {
if($line eq "") {
if(defined($self->[PARSER_STATENEXT])) {
$self->[PARSER_STATE] = $self->[PARSER_STATENEXT];
$self->[PARSER_STATENEXT] = undef;
} else {
$self->[PARSER_STATE] = STATE_WAITING;
}
} else {
#see if we should bail
if($self->[STRICT_PARSE]) {
croak ref($self) . " unknown input [STATE_CLEANUP] (" . $line . ")";
} else {
#we are not supposed to bail so try and save our session...
#since we are think we should be cleaning up, flush it all away
$self->[PARSER_STATE] = STATE_FLUSH;
#parser fail should be considered critical, if any debug at all, print dump
if($self->[DEBUG_LEVEL]) {
print STDERR "Parse failed on ($line) in STATE_CLEANUP:\n";
print STDERR Dumper $self->[PARSED_RECORD];
}
}
}
} elsif ($self->[PARSER_STATE] == STATE_GETDATA) {
if($line =~ /^([^:]+):\ (.*)$/) {
$self->[PARSED_RECORD]{$1} = $2;
} elsif ($line eq "") { #end of event
$self->[PARSER_STATE] = STATE_FLUSH;
return [ $self->[PARSED_RECORD] ];
} else {
if($self->[STRICT_PARSE]) {
croak ref($self) . " unknown input [STATE_GETDATA] (" . $line . ")";
} else {
#flush and run
$self->[PARSER_STATE] = STATE_FLUSH;
#parser fail should be considered critical, if any debug at all, print dump
if($self->[DEBUG_LEVEL]) {
print STDERR "Parse failed on ($line) in STATE_GETDATA:\n";
print STDERR Dumper $self->[PARSED_RECORD];
}
}
}
} elsif ($self->[PARSER_STATE] == STATE_TEXTRESPONSE) {
if($self->[CURRENT_LENGTH] == -1) {
$self->[CURRENT_LENGTH] = 0;
next;
}
$self->[CURRENT_LENGTH] += (length($line) + 1);
if(($self->[CURRENT_LENGTH] - 1) == $self->[PARSED_RECORD]{'Content-Length'}) {
$self->[PARSER_STATE] = STATE_FLUSH;
$self->[PARSED_RECORD]{'__DATA__'} .= $line;
return [$self->[PARSED_RECORD]];
} else {
$self->[PARSED_RECORD]{'__DATA__'} .= $line . "\n";
}
}
}
}
sub put {
my ($self, $lines) = @_;
my @row;
foreach my $line (@$lines) {
push @row, $line . "\n\n";
}
return \@row;
}
sub get_pending {
my $self = shift;
return $self->[LINE_FILTER]->get_pending();
}
sub get {
my ($self, $stream) = @_;
my @return;
$self->get_one_start($stream);
while(1) {
my $next = $self->get_one();
last unless @$next;
push @return, @$next;
}
return \@return;
}
1;
=head1 SEE ALSO
FreeSWITCH - http://www.freeswitch.org/
=head1 AUTHORS
POE::Filter::FSSocket is written by Paul Tinsley. You can reach him by e-mail
at pdt@jackhammer.org.
=head1 COPYRIGHT
Copyright 2006, Paul Tinsley. All rights are reserved.
POE::Filter::FSSocket is free software; it is currently licensed under the MPL
license version 1.1.
=cut

View File

@ -0,0 +1,10 @@
#!/usr/bin/env perl
use warnings;
use strict;
use Test::More tests => 1;
use_ok("POE::Filter::FSSocket");
exit;

361
scripts/socket/fsconsole.pl Normal file
View File

@ -0,0 +1,361 @@
#!/usr/bin/perl
use strict;
use warnings;
sub POE::Kernel::ASSERT_DEFAULT () { 1 };
sub Term::Visual::DEBUG () { 1 }
sub Term::Visual::DEBUG_FILE () { 'test.log' }
use IO::Socket;
use POE qw/Filter::FSSocket Component::Client::TCP/;
use Data::Dumper;
use Term::Visual;
local *D;
if (Term::Visual::DEBUG) {
*D = *Term::Visual::ERRS;
}
#local *ERROR = *STDERR;
$SIG{__DIE__} = sub {
if (Term::Visual::DEBUG) {
print Term::Visual::ERRS "Died: @_\n";
}
};
###############################################################################
## BEGIN Globals ##############################################################
###############################################################################
our $server_address = "127.0.0.1";
our $server_port = "8021";
our $server_secret = "ClueCon";
#this is where you can customize the color scheme
our %Pallet = (
'warn_bullet' => 'bold yellow',
'err_bullet' => 'bold red',
'out_bullet' => 'bold green',
'access' => 'bright red on blue',
'current' => 'bright yellow on blue',
);
our $terminal;
my %sockets;
my %windows;
my %unread_count;
my %commands = (
'window' => 1,
'w' => 1,
'win' => 1,
);
###############################################################################
## END Globals ##############################################################
###############################################################################
#setup our session
POE::Session->create(
'inline_states' => {
'_start' => \&handle_start, #session start
'_stop' => \&handle_stop, #session stop
'curses_input' => \&handle_curses_input, #input from the keyboard
'update_time' => \&handle_update_time, #update the status line clock
'quit' => \&handle_quit, #handler to do any cleanup
'server_input' => \&handle_server_input,
'_default' => \&handle_default,
},
'heap' => {
'terminal' => undef,
'freeswitch' => undef,
},
);
#start the kernel a chugging along
$poe_kernel->run;
###############################################################################
## BEGIN Handlers #############################################################
###############################################################################
#handles any startup functions for our session
sub handle_default {
}
sub handle_start {
my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
#setup our terminal
$heap->{'terminal'} = Term::Visual->new(
'Alias' => 'terminal', #poe alias for this
'History_Size' => 300, #number of things to keep in history
'Common_Input' => 1, #all windows share input and history
'Tab_Complete' => \&tab_complete,
);
$terminal = $heap->{'terminal'};
#setup the color palette
$terminal->set_palette(%Pallet);
#create a base window
my $window_id = $terminal->create_window(
'Window_Name' => 'console',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Console',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'console'} = $window_id;
$window_id = $terminal->create_window(
'Window_Name' => 'log',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Logs',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'log'} = $window_id;
$window_id = $terminal->create_window(
'Window_Name' => 'event',
'Buffer_Size' => 3000,
'Title' => 'FreeSWITCH Event',
'Status' => {
'0' => {
'format' => '%s',
'fields' => ['time'],
},
'1' => {
'format' => '%s',
'fields' => ['window_status'],
},
},
);
$windows{'event'} = $window_id;
#tell the terminal what to call when there is input from the keyboard
$kernel->post('terminal' => 'send_me_input' => 'curses_input');
$terminal->change_window(0);
$kernel->delay_set('update_time' => 1);
$terminal->set_status_field(0, 'time' => scalar(localtime));
new_message('destination_window' => 0, 'message' => "
Welcome to the FreeSWITCH POE Curses Console!
The console is split into three windows:
- 'console' for api response messages
- 'log' for freeswitch log output (simply send the log level you want
to start seeing events eg: 'log all')
- 'event' for freeswitch event output (must subscribe in plain format
eg: 'event plain all')
To switch between windows type 'w <windowname' so 'w log' for example.
Coming soon:
- Tab completion
- command history
- window status in the bar (messages added since last view, etc...)
Send any bug reports or comments to jackhammer\@gmail.com
Thanks,
Paul\n");
$terminal->set_status_field($terminal->current_window, 'window_status' => format_window_status());
#connect to freeswitch
$heap->{'freeswitch'} = POE::Component::Client::TCP->new(
'RemoteAddress' => $server_address,
'RemotePort' => $server_port,
'ServerInput' => \&handle_server_input,
'Connected' => \&handle_fs_connected,
'ServerError' => \&handle_server_error,
'Disconnected' => \&handle_server_disconnect,
'Domain' => AF_INET,
'Filter' => POE::Filter::FSSocket->new(),
);
}
#called when users enter commands in a window
sub handle_curses_input {
my ($kernel, $heap, $input, $context) = @_[KERNEL, HEAP, ARG0, ARG1];
#get the id of the window that is responsible for the input
my $window = $heap->{'terminal'}->current_window;
open(ERROR, ">>error.log");
if($input eq "quit") {
$kernel->yield('quit');
} elsif ($input =~ /^w\ (.*)$/) {
#get the id of the requested window
eval {
my $window_id = $windows{$1};
#see if it's real
if(defined($window_id)) {
$unread_count{$window_id} = 0;
$terminal->change_window($window_id);
$terminal->set_status_field($window_id, 'window_status' => &format_window_status());
}
};
if($@) {
print ERROR "put error: $@\n";
}
} else {
#see if we got connected at some point
if(defined($sockets{'localhost'})) {
#send the command
$sockets{'localhost'}->put($input);
}
}
}
sub handle_fs_connected {
my ($kernel, $heap) = @_[KERNEL, HEAP];
eval {
$sockets{'localhost'} = $heap->{'server'};
}
}
#this is responsible for doing any cleanup and returning the terminal to the previous
#state before we mucked with it
sub handle_quit {
my ($kernel, $heap) = @_[KERNEL, HEAP];
#tell curses to clean up it's crap
$kernel->post('terminal' => 'shutdown');
#there is probably a more elegant way, but this works for now
exit;
}
#data from freeswitch
sub handle_server_input {
my ($kernel,$heap,$input) = @_[KERNEL,HEAP,ARG0];
eval {
#terminal HATES null
if(defined($input->{'__DATA__'})) {
$input->{'__DATA__'} =~ s/[\x00]//g;
}
#handle the login
if($input->{'Content-Type'} eq "auth/request") {
$heap->{'server'}->put("auth $server_secret");
} elsif ($input->{'Content-Type'} eq "api/response") {
new_message('destination_window' => 0, 'message' => 'Response: ');
new_message('destination_window' => 0, 'message' => $input->{'__DATA__'});
} elsif ($input->{'Content-Type'} eq "log/data") {
new_message('destination_window' => 1, 'message' => $input->{'__DATA__'});
} elsif ($input->{'Content-Type'} eq "text/event-plain") {
new_message('destination_window' => 2, 'message' => Dumper $input);
}
};
if($@) {
open(ERROR, ">>error.log");
print ERROR "died: $@\n";
print ERROR Dumper $heap;
close(ERROR);
}
}
sub handle_server_error {
}
sub handle_server_disconnect {
}
sub tab_complete {
my $left = shift;
my @return;
if(defined($commands{$left})) {
return [$left . " "];
#} elsif () {
}
}
sub handle_update_time {
my ($kernel, $heap) = @_[KERNEL, HEAP];
$terminal->set_status_field($terminal->current_window, 'time' => scalar(localtime));
$kernel->delay_set('update_time' => 1);
}
###############################################################################
## END Handlers #############################################################
###############################################################################
sub new_message {
my %args = @_;
my $message = $args{'message'};
my $destination_window = $args{'destination_window'};
my $status_field;
#see if we are on the window being updated
if($terminal->current_window != $destination_window) {
#increment the unread count for the window
#FIXME, should we count messages or lines?
$unread_count{$destination_window}++;
#update the status bar
eval {
$terminal->set_status_field($terminal->current_window, 'window_status' => &format_window_status());
};
if($@) {
print $@;
}
}
#deliver the message
$terminal->print($destination_window, $message);
}
sub format_window_status {
my $status_field;
#put all the windows in the bar with their current unread count
foreach my $window (sort {$windows{$a} <=> $windows{$b}} keys %windows) {
#see if we are printing the current window
if($terminal->current_window == $windows{$window}) {
$status_field .= "[\0(current)$window\0(st_frames)";
} else {
$status_field .= "[$window";
}
if($unread_count{$windows{$window}}) {
$status_field .= " (" . $unread_count{$windows{$window}} . ")";
}
$status_field .= "] ";
}
return $status_field;
}