/usr/lib/python2.7/dist-packages/examples/utilapplication.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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | #
# StarPy -- Asterisk Protocols for Twisted
#
# Copyright (c) 2006, Michael C. Fletcher
#
# Michael C. Fletcher <mcfletch@vrplumber.com>
#
# See http://asterisk-org.github.com/starpy/ for more information about the
# StarPy project. Please do not directly contact any of the maintainers of this
# project for assistance; the project provides a web site, mailing lists and
# IRC channels for your use.
#
# This program is free software, distributed under the terms of the
# BSD 3-Clause License. See the LICENSE file at the top of the source tree for
# details.
"""Class providing utility applications with common support code"""
from basicproperty import common, propertied, basic, weak
from ConfigParser import ConfigParser
from starpy import fastagi, manager
from twisted.internet import defer, reactor
import logging,os
log = logging.getLogger( 'app' )
class UtilApplication( propertied.Propertied ):
"""Utility class providing simple application-level operations
FastAGI entry points are waitForCallOn and handleCallsFor, which allow
for one-shot and permanant handling of calls for an extension
(respectively), and agiSpecifier, which is loaded from configuration file
(as specified in self.configFiles).
"""
amiSpecifier = basic.BasicProperty(
"amiSpecifier", """AMI connection specifier for the application see AMISpecifier""",
defaultFunction = lambda prop,client: AMISpecifier()
)
agiSpecifier = basic.BasicProperty(
"agiSpecifier", """FastAGI server specifier for the application see AGISpecifier""",
defaultFunction = lambda prop,client: AGISpecifier()
)
extensionWaiters = common.DictionaryProperty(
"extensionWaiters", """Set of deferreds waiting for incoming extensions""",
)
extensionHandlers = common.DictionaryProperty(
"extensionHandlers", """Set of permanant callbacks waiting for incoming extensions""",
)
configFiles = configFiles=('starpy.conf','~/.starpy.conf')
def __init__( self ):
"""Initialise the application from options in configFile"""
self.loadConfigurations()
def loadConfigurations( self ):
parser = self._loadConfigFiles( self.configFiles )
self._copyPropertiesFrom( parser, 'AMI', self.amiSpecifier )
self._copyPropertiesFrom( parser, 'FastAGI', self.agiSpecifier )
return parser
def _loadConfigFiles( self, configFiles ):
"""Load options from configuration files given (if present)"""
parser = ConfigParser( )
filenames = [
os.path.abspath( os.path.expandvars( os.path.expanduser( file ) ))
for file in configFiles
]
log.info( "Possible configuration files:\n\t%s", "\n\t".join(filenames) or None)
filenames = [
file for file in filenames
if os.path.isfile(file)
]
log.info( "Actual configuration files:\n\t%s", "\n\t".join(filenames) or None)
parser.read( filenames )
return parser
def _copyPropertiesFrom( self, parser, section, client, properties=None ):
"""Copy properties from the config-parser's given section into client"""
if properties is None:
properties = client.getProperties()
for property in properties:
if parser.has_option( section, property.name ):
try:
value = parser.get( section, property.name )
setattr( client, property.name, value )
except (TypeError,ValueError,AttributeError,NameError), err:
log( """Unable to set property %r of %r to config-file value %r: %s"""%(
property.name, client, parser.get( section, property.name, 1), err,
))
return client
def dispatchIncomingCall( self, agi ):
"""Handle an incoming call (dispatch to the appropriate registered handler)"""
extension = agi.variables['agi_extension']
log.info( """AGI connection with extension: %r""", extension )
try:
df = self.extensionWaiters.pop( extension )
except KeyError, err:
try:
callback = self.extensionHandlers[ extension ]
except KeyError, err:
try:
callback = self.extensionHandlers[ None ]
except KeyError, err:
log.warn( """Unexpected connection to extension %r: %s""", extension, agi.variables )
agi.finish()
return
try:
return callback( agi )
except Exception, err:
log.error( """Failure during callback %s for agi %s: %s""", callback, agi.variables, err )
# XXX return a -1 here
else:
if not df.called:
df.callback( agi )
def waitForCallOn( self, extension, timeout=15 ):
"""Wait for an AGI call on extension given
extension -- string extension for which to wait
timeout -- duration in seconds to wait before defer.TimeoutError is
returned to the deferred.
Note that waiting callback overrides any registered handler; that is,
if you register one callback with waitForCallOn and another with
handleCallsFor, the first incoming call will trigger the waitForCallOn
handler.
returns deferred returning connected FastAGIProtocol or an error
"""
extension = str(extension)
log.info( 'Waiting for extension %r for %s seconds', extension, timeout )
df = defer.Deferred( )
self.extensionWaiters[ extension ] = df
def onTimeout( ):
if not df.called:
df.errback( defer.TimeoutError(
"""Timeout waiting for call on extension: %r"""%(extension,)
))
reactor.callLater( timeout, onTimeout )
return df
def handleCallsFor( self, extension, callback ):
"""Register permanant handler for given extension
extension -- string extension for which to wait or None to define
a default handler (that chosen if there is not explicit handler
or waiter)
callback -- callback function to be called for each incoming channel
to the given extension.
Note that waiting callback overrides any registered handler; that is,
if you register one callback with waitForCallOn and another with
handleCallsFor, the first incoming call will trigger the waitForCallOn
handler.
returns None
"""
if extension is not None:
extension = str(extension)
self.extensionHandlers[ extension ] = callback
class AMISpecifier( propertied.Propertied ):
"""Manager interface setup/specifier"""
username = common.StringLocaleProperty(
"username", """Login username for the manager interface""",
)
secret = common.StringLocaleProperty(
"secret", """Login secret for the manager interface""",
)
password = secret
server = common.StringLocaleProperty(
"server", """Server IP address to which to connect""",
defaultValue = '127.0.0.1',
)
port = common.IntegerProperty(
"port", """Server IP port to which to connect""",
defaultValue = 5038,
)
timeout = common.FloatProperty(
"timeout", """Timeout in seconds for an AMI connection timeout""",
defaultValue = 5.0,
)
def login( self ):
"""Login to the specified manager via the AMI"""
theManager = manager.AMIFactory(self.username, self.secret)
return theManager.login(self.server, self.port, timeout=self.timeout)
class AGISpecifier( propertied.Propertied ):
"""Specifier of where we send the user to connect to our AGI"""
port = common.IntegerProperty(
"port", """IP port on which to listen""",
defaultValue = 4573,
)
interface = common.StringLocaleProperty(
"interface", """IP interface on which to listen (local only by default)""",
defaultValue = '127.0.0.1',
)
context = common.StringLocaleProperty(
"context", """Asterisk context to which to connect incoming calls""",
defaultValue = 'survey',
)
def run( self, mainFunction ):
"""Start up the AGI server with the given mainFunction"""
f = fastagi.FastAGIFactory(mainFunction)
return reactor.listenTCP(self.port, f, 50, self.interface)
|