Asterisk-CoroManager

 view release on metacpan or  search on metacpan

lib/Asterisk/CoroManager.pm  view on Meta::CPAN

package Asterisk::CoroManager;

require 5.8.8;

use strict;
use warnings;
use warnings::register;
use utf8;

use Coro;
use Coro::AnyEvent;                # NB: I have only tested with Coro::EV
use Coro::Semaphore;
use Coro::Channel;
use Coro::Debug;

use Carp qw( croak longmess );
use IO::Socket;
use Digest::MD5;
use Data::Dumper::Simple;
use Time::HiRes qw ( time );

use constant DEFAULT_TIMEOUT => 3; # Default timeout to wait for
                                   # action response

=head1 NAME

Asterisk::CoroManager - Asterisk Manager Interface, Coro version

=head1 SYNOPSIS

  use Asterisk::CoroManager;

  my $astman = new Asterisk::CoroManager({
                                          user   => 'username',
                                          secret => 'test',
                                          host   => 'localhost',
                                        });

  $astman->connect || die "Could not connect to " . $astman->host . "!\n";

  my $ping = $astman->sendcommand({ Action => 'Ping' }, { timeout => '2' });

  if( $ping ) {
      # $ping->{Response} should be 'Pong'
      print "Yay, we're alive! We got ". $ping->{Response} ."\n";
  }
  else {
      print "Got no pong in 2 seconds :-(\n";
  }

  $astman->disconnect;

=head1 DESCRIPTION

This module provides a dependable, event-based interface to the
asterisk manager interface.
L<http://www.voip-info.org/wiki/view/Asterisk+manager+API>

This is done with L<Coro>, and continuations.  If you are unfamiliar
with L<Coro>, go read up on it!  Your program should 'use Coro' quite
at the beginning, and be aware of that it is asynchronous.  If you
wait for an answer to a sendcommand, other events will probably be
triggered in the meanwhile.

=head2 Logging / Error handling

Asterisk::CoroManager uses L<Log::Log4perl> if it is installed.  Read
L<Log::Log4perl>, or initialize a simple logger like this:

  use Log::Log4perl qw(:easy);
  Log::Log4perl->easy_init( { level => $DEBUG,
                              file => ">>test.log" } );

=cut

BEGIN {
    # Check for Log::Log4perl...
    eval { use Log::Log4perl qw(get_logger :nowarn); };
    if($@) {
	print "Log::Log4perl not installed - stubbing.\n";
	no strict qw(refs);
	*{__PACKAGE__."::$_"} = sub { } for qw(trace debug error fatal);
    }
    else {
	print "Log::Log4perl installed - logging enabled.\n";
	no strict qw(refs);
	foreach my $level (qw(trace debug error fatal)) {
	    *{__PACKAGE__."::$level"} =
	      sub {
		  $Log::Log4perl::caller_depth++;
		  my( $astman, $message ) = @_;
		  my $log = Log::Log4perl->get_logger();
		  $log->$level($astman->host .': '. $message);
		  $Log::Log4perl::caller_depth--;
		  return;
	      };
	}
    }
}


my $EOL = "\015\012";
my $BLANK = $EOL x 2;

use vars qw($VERSION); $VERSION = '0.11'; sub version { return $VERSION }

lib/Asterisk/CoroManager.pm  view on Meta::CPAN

Sends a command to asterisk.

If you are looking for a response, the command will wait for the
specific response from asterisk (identified with an ActionID).
Otherwise it returns immediately.

TODO: Implement timeout: Timeout is how long to wait for the response.
Defaults to 3 seconds.

Returns a hash or hash-ref on success (depending on wantarray), undef
on timeout.

=cut

sub sendcommand {
    my( $astman, $command, $args ) = @_;
    my $actionid = $command->{ActionID} || $ACTIONID_SEQ++;

    # Ping must be handled specially, since it doesn't return ActionID
    if( $command->{Action} and
	$command->{Action} eq 'Ping' ) {
	$actionid = 'Ping';
    }

    $command->{ActionID} ||= $actionid;

    $astman->trace("Sending command: ". Dumper($command));

    my $fh = $astman->{fh};
    my $cstring = make_packet(%$command);

    eval { # Send command to Asterisk
	$fh->send("$cstring$EOL");
    } or $astman->check_connection;

    if (defined wantarray) {
	$astman->debug("Waiting for response of command here.");
	$astman->trace(longmess());
	$astman->trace("-------------------------------------");
	$args ||= {};
	my $timeout = $args->{Timeout} || DEFAULT_TIMEOUT;
	my $response = new Coro::Channel;
	$astman->{action_cb}{$actionid} = sub{ $response->put(@_) };

	my $resp = $response->get; # Cede's until a response is gotten
	return unless( $resp );
	return wantarray ? %{$resp} : $resp;
	# TODO: Timeout!
    }

    return;
}


##############################################################################

=head2 check_connection

  $astman->check_connection();

Checks if the connection is still alive.  Tries to reconnect if it
isn't.

=cut

sub check_connection {
    my( $astman ) = @_;

    if( $astman->connected ) {
	$astman->debug("...connection appears to be fine...");
    }
    else {
	$astman->error("Lost connection to server!");
	$astman->error("Trying to reconnect...");

	$astman->{fh}->close if $astman->{fh}->connected;
	undef $astman->{fh};

	if( $astman->connect ) {
	    $astman->error("Succeeded in reconnect!  "
                           ."Continuing as if nothing happened.");
	}
	else {
	    $astman->fatal("Couldn't reconnect.  "
                           ."Dying here.  Good bye cruel world.");
	    croak "Couldn't reconnect.  Dying here.  "
              ."Good bye cruel world.";
	}
    }

    return;
}


##############################################################################

=head2 eventloop

  $astman->eventloop();

Will wait for events, until shut down.

=cut

sub eventloop {
    my ($astman) = @_;

    # Coro debug shell, if socket is available
    eval {
        my $shell = new_unix_server Coro::Debug "/tmp/myshell";
    };
    if($@) {
	$astman->error("Coro debug shell failed.\n");
    }

    $astman->{finished}->recv;

    return;
}




( run in 1.930 second using v1.01-cache-2.11-cpan-df04353d9ac )