/usr/lib/python2.7/dist-packages/examples/calldurationcallback.py is in python-starpy 1.0.1.0.git.20140806-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | #! /usr/bin/env python
"""Sample application to read call duration back to user
Implemented as an AGI and a manager connection, send
those who want to time the call to the AGI, we will wait
for the end of the call, then call them back with the
duration message.
"""
from twisted.application import service, internet
from twisted.internet import reactor, defer
from starpy import manager, fastagi
import utilapplication
import menu
import os, logging, pprint, time
log = logging.getLogger( 'callduration' )
class Application( utilapplication.UtilApplication ):
"""Application for the call duration callback mechanism"""
def onS( self, agi ):
"""Incoming AGI connection to the "s" extension (start operation)"""
log.info( """New call tracker""" )
c = CallTracker()
return c.recordChannelInfo( agi ).addErrback(
agi.jumpOnError, difference=100,
)
class CallTracker( object ):
"""Object which tracks duration of a single call
This object encapsulates the entire interaction with the user, from
the initial incoming FastAGI that records the channel ID and account
number through the manager watching for the disconnect to the new call
setup and the FastAGI that plays back the results...
Requires a context 'callduration' with 's' mapping to this AGI, as well
as all numeric extensions.
"""
ourContext = 'callduration'
def __init__( self ):
"""Initialise the tracker object"""
self.uniqueChannelId = None
self.currentChannel = None
self.callbackChannel = None
self.account = None
self.cancelled = False
self.ami = None
self.startTime = None
self.stopTime = None
def recordChannelInfo( self, agi ):
"""Records relevant channel information, creates manager watcher"""
self.uniqueChannelId = agi.variables['agi_uniqueid']
self.currentChannel = currentChannel = agi.variables['agi_channel']
# XXX everything up to the last - is normally our local caller's "address"
# this is not, however, a great way to decide who to call back...
self.callbackChannel = currentChannel.rsplit( '-', 1)[0]
# Ask user for the account number...
df = menu.CollectDigits(
soundFile = 'your-account',
maxDigits = 7,
minDigits = 3,
timeout = 5,
)( agi ).addCallback(
self.onAccountInput,agi=agi,
)
# XXX handle AMI login failure...
amiDF = APPLICATION.amiSpecifier.login(
).addCallback( self.onAMIConnect )
dl = defer.DeferredList( [df, amiDF] )
return dl.addCallback( self.onConnectAndAccount )
def onAccountInput( self, result, agi, retries=2):
"""Allow user to enter again if timed out"""
self.account = result[0][1]
self.startTime = time.time()
agi.finish() # let the user go about their business...
return agi
def cleanUp( self, agi=None ):
"""Cleanup on error as much as possible"""
items = []
if self.ami:
items.append( self.ami.logoff())
self.ami = None
if items:
return defer.DeferredList( items )
else:
return defer.succeed( False )
def onAMIConnect( self, ami ):
"""We have successfully connected to the AMI"""
log.debug( "AMI login complete" )
if not self.cancelled:
self.ami = ami
return ami
else:
return self.ami.logoff()
def onConnectAndAccount( self, results ):
"""We have connected and retrieved an account"""
log.info( """AMI Connected and account information gathered: %s""", self.uniqueChannelId )
df = defer.Deferred()
def onChannelHangup( ami, event ):
"""Deal with the hangup of an event"""
if event['uniqueid'] == self.uniqueChannelId:
log.info( """AMI Detected close of our channel: %s""", self.uniqueChannelId )
self.stopTime = time.time()
# give the user a few seconds to put down the hand-set
reactor.callLater( 2, df.callback, event )
self.ami.deregisterEvent( 'Hangup', onChannelHangup )
log.debug( 'event:', event )
if not self.cancelled:
self.ami.registerEvent( 'Hangup', onChannelHangup )
return df.addCallback( self.onHangup, callbacks=5 )
def onHangup( self, event, callbacks=5 ):
"""Okay, the call is finished, time to inform the user"""
log.debug( 'onHangup %s %s', event, callbacks )
def ignoreResult( result ):
"""Since we're using an equal timeout waiting for a connect
we don't care *how* this fails/succeeds"""
pass
self.ami.originate(
self.callbackChannel,
self.ourContext, id(self), 1,
timeout = 15,
).addCallbacks( ignoreResult, ignoreResult )
df = APPLICATION.waitForCallOn( id(self), 15 )
df.addCallbacks(
self.onUserReconnected, self.onUserReconnectFail,
errbackKeywords = { 'event': event, 'callbacks': callbacks-1 },
)
def onUserReconnectFail( self, reason, event, callbacks ):
"""Wait for bit, then retry..."""
if callbacks:
# XXX really want something like a decaying back-off in frequency
# with final values of e.g. an hour...
log.info( """Failure connecting: will retry in 30 seconds""" )
reactor.callLater( 30, self.onHangup, event, callbacks )
else:
log.error( """Unable to connect to user, giving up""" )
return self.cleanUp( None )
def onUserReconnected( self, agi ):
"""Handle the user interaction after they've re-connected"""
log.info( """Connection re-established with the user""" )
# XXX should handle unexpected failures in here...
delta = self.stopTime - self.startTime
minutes, seconds = divmod( delta, 60 )
seconds = int(seconds)
hours, minutes = divmod( minutes, 60 )
duration = []
if hours:
duration.append( '%s hour%s'%(hours,['','s'][hours!=1]))
if minutes:
duration.append( '%s second%s'%(minutes,['','s'][minutes!=1]))
if seconds:
duration.append( '%s second%s'%(seconds,['','s'][seconds!=1]))
if not duration:
duration = '0'
else:
duration = " ".join( duration )
seq = fastagi.InSequence( )
seq.append( agi.wait, 1 )
seq.append( agi.execute, "Festival", "Call to account %r took %s"%(self.account,duration) )
seq.append( agi.wait, 1 )
seq.append( agi.execute, "Festival", "Repeating, call to account %r took %s"%(self.account,duration) )
seq.append( agi.wait, 1 )
seq.append( agi.finish )
def logSuccess( ):
log.debug( """Finished successfully!""" )
return defer.succeed( True )
seq.append( logSuccess )
seq.append( self.cleanUp, agi )
return seq()
APPLICATION = Application()
if __name__ == "__main__":
logging.basicConfig()
log.setLevel( logging.DEBUG )
#manager.log.setLevel( logging.DEBUG )
#fastagi.log.setLevel( logging.DEBUG )
APPLICATION.handleCallsFor( 's', APPLICATION.onS )
APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
from twisted.internet import reactor
reactor.run()
|