LP#1375043: Support for in-A/T telephony configuration
authorMike Rylander <mrylander@gmail.com>
Sun, 28 Sep 2014 20:37:33 +0000 (16:37 -0400)
committerGalen Charlton <gmc@esilibrary.com>
Wed, 2 Mar 2016 22:24:04 +0000 (17:24 -0500)
The AstCall reactor module creates a callfile for Asterisk, given a
template describing the message and an environment defining
necessary information for contacting the Asterisk server and scheduling
a call with it.

If you have only one SIP server, you can set it up like this in the
opensrf.xml configuration file:

    <telephony>
        <!-- replace all values below when telephony server is configured -->
        <enabled>0</enabled>
        <driver>SIP</driver> <!-- SIP (default) or multi -->
        <channels> <!-- explicit list of channels used if multi -->
            <!-- A channel specifies technology/resource -->
            <channel>Zap/1</channel>
            <channel>Zap/2</channel>
            <channel>IAX/user:secret@widgets.biz</channel>
        </channels>
        <host>localhost</host>
        <port>10080</port>
        <user>evergreen</user>
        <pw>evergreen</pw>
        <!--
            The overall composition of callfiles is determined by the
            relevant template, but this section can be invoked for callfile
            configs common to all outbound calls.
            callfile_lines will be inserted into ALL generated callfiles
            after the Channel line. This content mat be overridden
            (in whole) by the org unit setting callfile_lines.
            Warning: Invalid syntax may break ALL outbound calls.
        -->
        <!-- <callfile_lines>
            MaxRetries: 3
            RetryTime: 60
            WaitTime: 30
            Archive: 1
            Extension: 10
        </callfile_lines> -->
    </telephony>

To support more than one SIP server, say, per library, you can use
Action/Trigger parameters like these, which model the same information
as above:

    enabled = 0
    driver = "SIP"
    channels = ["Zap/1", "Zap/2", "IAX/user:secret@widgets.biz"]
    host = "localhost"
    port = "10080"
    user = "evergreen"
    pw = "evergreen"
    callfile_lines = ["MaxRetries: 3", "RetryTime: 60", "WaitTime: 30", "Archive: 1", "Extension: 10"]

Co-author credit goes to Steve Callender, who helped build this patch.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/AstCall.pm

index dea456b..7ea0198 100644 (file)
@@ -14,71 +14,147 @@ $Data::Dumper::Indent = 0;
 
 my $U = 'OpenILS::Application::AppUtils';
 
-# $last_channel_used is:
+# %last_channel_used is, per event def with params or the config file:
 # ~ index (not literal value) of last channel used in a callfile
-# ~ index is of position in @channels (zero-based)
+# ~ index is of position in the array of channels (zero-based)
 # ~ cached at package level
 # ~ typically for Zap (PSTN), not VOIP
 
-our @channels;
-our $last_channel_used = 0;
+our %last_channel_used = ();
 our $telephony;
 
 sub ABOUT {
-    return <<ABOUT;
+    return <<'ABOUT';
 
     The AstCall reactor module creates a callfile for Asterisk, given a
     template describing the message and an environment defining
     necessary information for contacting the Asterisk server and scheduling
     a call with it.
 
+    If you have only one SIP server, you can set it up like this in the
+    opensrf.xml configuration file:
+
+        <telephony>
+            <!-- replace all values below when telephony server is configured -->
+            <enabled>0</enabled>
+            <driver>SIP</driver>    <!-- SIP (default) or multi -->
+            <channels>              <!-- explicit list of channels used if multi -->
+                                    <!-- A channel specifies technology/resource -->
+                <channel>Zap/1</channel>
+                <channel>Zap/2</channel>
+                <channel>IAX/user:secret@widgets.biz</channel>
+            </channels>
+            <host>localhost</host>
+            <port>10080</port>
+            <user>evergreen</user>
+            <pw>evergreen</pw>
+            <!--
+                The overall composition of callfiles is determined by the
+                relevant template, but this section can be invoked for callfile
+                configs common to all outbound calls.
+                callfile_lines will be inserted into ALL generated callfiles
+                after the Channel line.  This content mat be overridden
+                (in whole) by the org unit setting callfile_lines.
+                Warning: Invalid syntax may break ALL outbound calls.
+            -->
+            <!-- <callfile_lines>
+                MaxRetries: 3
+                RetryTime: 60
+                WaitTime: 30
+                Archive: 1
+                Extension: 10
+            </callfile_lines> -->
+        </telephony>
+
+    To support more than one SIP server, say, per library, you can use
+    Action/Trigger parameters like these, which model the same information
+    as above:
+
+            enabled = 0
+            driver = "SIP"
+            channels = ["Zap/1", "Zap/2", "IAX/user:secret@widgets.biz"]
+            host = "localhost"
+            port = "10080"
+            user = "evergreen"
+            pw = "evergreen"
+            callfile_lines = ["MaxRetries: 3", "RetryTime: 60", "WaitTime: 30", "Archive: 1", "Extension: 10"]
+
 ABOUT
 }
 
 sub get_conf {
+    my $part = shift;
+    my $env = shift;
+
+    # get the part they want from the environment, if we have it
+    return $env->{params}{$part} if ( $part && $env && exists $env->{params}{$part}); 
    # $logger->info(__PACKAGE__ . ": get_conf()");
-    $telephony and return $telephony;
-    my $config = OpenSRF::Utils::SettingsClient->new;
-    # config object cached by package
-    $telephony = $config->config_value('notifications', 'telephony');
+
+    # failing all of that, just fetch the config file if we don't have it
+    if (!$telephony) {
+        my $config = OpenSRF::Utils::SettingsClient->new;
+        # config object cached by package
+        $telephony = $config->config_value('notifications', 'telephony');
+    }
+
+    # if they want a part, and we have the config file data, return that
+    return $$telephony{$part} if ( $part && $telephony && exists $$telephony{$part}); 
+
+    # but if they don't want a part, and we have the whole config file thing, return it
     return $telephony;
 }
 
+sub channels_from {
+    my $env = shift;
+
+    # report the event def id if we get the channels from params
+    return $env->{EventProcessor}{event}->event_def->id
+        if ( exists $env->{params}{channels}); 
+
+    # else just say '*'
+    return '*';
+}
+
 sub get_channels {
-    @channels and return @channels;
-    my $config = get_conf();    # populated $telephony object
-    @channels = @{ $config->{channels} };
-    return @channels;
+    my $env = shift;
+    @{ get_conf( channels => $env ) };
 }
 
 sub next_channel {
+    my $env = shift;
     # Increments $last_channel_used, or resets it to zero, as necessary.
     # Returns appropriate value from channels array.
-    my @chans = get_channels();
+    my $source = channels_from($env);
+    my @chans = get_channels($env);
     unless(@chans) {
         $logger->error(__PACKAGE__ . ": Cannot build call using " .
             (shift ||'driver') .
             ", no notifications.telephony.channels found in config!");
         return;
     }
-    if (++$last_channel_used > $#chans) {
-        $last_channel_used = 0;
+    if (++$last_channel_used{$source} > $#chans) {
+        $last_channel_used{$source} = 0;
     }
-    return $chans[$last_channel_used];     # say, 'Zap/1' or 'Zap/12'
+    return $chans[$last_channel_used{$source}];     # say, 'Zap/1' or 'Zap/12'
 }
 
 sub channel {
-    my $tech = get_conf()->{driver} || 'SIP';
+    my $env = shift;
+    my $tech = get_conf( driver => $env ) || 'SIP';
     if ($tech !~ /^SIP/) {
-        return next_channel($tech);
+        return next_channel($env, $tech);
     }
     return $tech;                          #  say, 'SIP' or 'SIP/ubab33'
 }
 
 sub get_extra_lines {
-    my $lines = get_conf()->{callfile_lines} or return '';
+    my $env = shift;
+    my $lines = get_conf( callfile_lines => $env ) or return '';
+    return '' if (ref($lines) && (ref($lines) !~ /ARRAY/));
+    $lines = [ split "\n", $lines ] unless (ref($lines));
+
     my @fixed;
-    foreach (split "\n", $lines) {
+    foreach (@$lines) {
         s/^\s*//g;      # strip leading spaces
         /\S/ or next;   # skip empty lines
         push @fixed, $_;
@@ -88,18 +164,26 @@ sub get_extra_lines {
 }
 
 sub host_string {
-    my $conf = get_conf();
-    my $host = $conf->{host};
+    my $env = shift;
+    my $host = get_conf( host => $env );
+    my $port = get_conf( port => $env );
+
     unless ($host) {
         $logger->error(__PACKAGE__ . ": No telephony/host in config.");
         return;
     }
+    $logger->info(__PACKAGE__ . ": host [$host], port [$port]");
 
     # prepend http:// if no protocol specified
-    $host =~ /^\S+:\/\// or $host  = 'http://' . $host;
+    if ($host !~ /^\S+:\/\//) {
+        $host  = 'http://' . $host;
+    }
     # append port number if specified
-    $conf->{port} and $host .= ":" . $conf->{port};
+    if ($port) {
+       $host .= ":" . $port;
+    }
 
+    $logger->info(__PACKAGE__ . ": final host string [$host]");
     return $host;
 }
 sub rpc_client {
@@ -117,12 +201,12 @@ sub handler {
     $logger->info(__PACKAGE__ . ": entered handler");
 
     # assignment, not comparison
-    unless ($env->{channel_prefix} = channel()) {
+    unless ($env->{channel_prefix} = channel($env)) {
         $logger->error(__PACKAGE__ . ": Cannot find tech/resource in config");
         return 0;
     }
 
-    $env->{extra_lines} = get_extra_lines() || '';
+    $env->{extra_lines} = get_extra_lines($env) || '';
     my $tmpl_output = $self->run_TT($env);
     if (not $tmpl_output) {
         $logger->error(__PACKAGE__ . ": no template input");
@@ -160,7 +244,7 @@ sub handler {
 
     # TODO: add scheduling intelligence and use it here... or not if
     # relying only on crontab
-    my $client = rpc_client();
+    my $client = rpc_client(host_string($env));
     my $resp = $client->send_request(
         'inject', $tmpl_output, $filename_fragment, 0
     ); # FIXME: 0 could be seconds-from-epoch UTC if deferred call needed
@@ -302,8 +386,8 @@ sub cleanup {
 }
 
 sub retrieve {
-    $logger->info("retrieve() not implemented. how'd we get here?"); # XXX
-    return;
+       $logger->info("retrieve() not implemented. how'd we get here?"); # XXX
+       return;
 }
 
 #sub retrieve {