Beispiel #1
0
class ChannelTracker(propertied.Propertied):
    """Track open channels on the Asterisk server"""
    channels = common.DictionaryProperty(
        "channels",
        """Set of open channels on the system""",
    )
    thresholdCount = common.IntegerProperty(
        "thresholdCount",
        """Storage of threshold below which we don't warn user""",
        defaultValue=20,
    )

    def main(self):
        """Main operation for the channel-tracking demo"""
        APPLICATION.amiSpecifier.login().addCallback(self.onAMIConnect)

    def onAMIConnect(self, ami):
        ami.status().addCallback(self.onStatus, ami=ami)
        ami.registerEvent('Hangup', self.onChannelHangup)
        ami.registerEvent('Newchannel', self.onChannelNew)

    def onStatus(self, events, ami=None):
        """Integrate the current status into our set of channels"""
        log.debug("""Initial channel status retrieved""")
        for event in events:
            self.onChannelNew(ami, event)

    def onChannelNew(self, ami, event):
        """Handle creation of a new channel"""
        log.debug("""Start on channel %s""", event)
        opening = event['uniqueid'] not in self.channels
        self.channels[event['uniqueid']] = event
        if opening:
            self.onChannelChange(ami, event, opening=opening)

    def onChannelHangup(self, ami, event):
        """Handle hangup of an existing channel"""
        try:
            del self.channels[event['uniqueid']]
        except KeyError as err:
            log.warn("""Hangup on unknown channel %s""", event)
        else:
            log.debug("""Hangup on channel %s""", event)
        self.onChannelChange(ami, event, opening=False)

    def onChannelChange(self, ami, event, opening=False):
        """Channel count has changed, do something useful like enforcing limits"""
        if opening and len(self.channels) > self.thresholdCount:
            log.warn("""Current channel count: %s""", len(self.channels))
        else:
            log.info("""Current channel count: %s""", len(self.channels))
Beispiel #2
0
class Tag(propertied.Propertied):
    """Represents a particular tag within a document"""
    name = common.StringProperty(
        "name",
        "The name of the tag",
        defaultValue="",
    )
    attributes = common.DictionaryProperty(
        "attributes",
        """The in-tag attributes of the tag""",
        defaultFunction=lambda x, y: {},
    )
    content = common.ListProperty(
        "content",
        """The content (children) of the tag""",
        setDefaultOnGet=1,
        defaultFunction=lambda x, y: [],
    )

    def __cmp__(self, other):
        """Compare this tag to another"""
        if not isinstance(other, Tag):
            return -1
        if other.name != self.name:
            return cmp(self.name, other.name)
        if other.attributes != self.attributes:
            return cmp(self.attributes, other.attributes)
        if other.content != self.content:
            return cmp(self.content, other.content)
        return 0

    def __repr__(self):
        """Create a decent representation of this tag"""
        fragments = []
        name = self.name.decode().encode('utf-8')
        fragments.append("<" + name)
        for key, value in self.attributes.items():
            fragments.append("""%s=%r""" % (key, value))
        fragments = [" ".join(fragments)]
        if self.content:
            fragments.append(">")
            for item in self.content:
                if isinstance(item, str):
                    fragments.append(item)
                else:
                    fragments.append(repr(item))
            fragments.append("</%s>" % (name))
        else:
            fragments.append("/>")
        return "".join(fragments)
Beispiel #3
0
class WithProps(object):
    currentState = common.IntegerProperty(
        "currentState",
        """The current state of this instance""",
        defaultValue=3,
    )
    someSource = common.DictionaryProperty(
        "someSource",
        """Source for properties in state 4""",
    )

    someProp = StateBasedProperty(
        "someProp",
        """A state-aware generic property""",
        defaultFunction=lambda prop, client: client.__class__.__name__,
        setDefaultOnGet=0,
    )
    someOtherProp = DictStateBasedProperty(
        "someOtherProp",
        """A state-aware dictionary property (with automatic default""",
    )
class Simple(propertied.Propertied):
    count = common.IntegerProperty(
        "count",
        """Count some value for us""",
        defaultValue=0,
    )
    names = common.StringsProperty(
        "names",
        """Some names as a list of strings""",
    )
    mapping = common.DictionaryProperty(
        "mapping",
        """Mapping from name to number""",
        defaultValue=[
            ('tim', 3),
            ('tom', 4),
            ('bryan', 5),
        ],
    )

    def __repr__(self):
        className = self.__class__.__name__

        def clean(value):
            value = value.splitlines()[0]
            if len(value) > 30:
                value = value[:27] + '...'
            return value

        props = ", ".join([
            '%s=%s' % (prop.name, repr(prop.__get__(self)))
            for prop in self.getProperties() if hasattr(self, prop.name)
        ])
        return '<%(className)s %(props)s>' % locals()

    __str__ = __repr__
Beispiel #5
0
class Application(utilapplication.UtilApplication):
    """Services provided at the application level"""
    surveys = common.DictionaryProperty(
        "surveys",
        """Set of surveys indexed by survey/extension number""",
    )
Beispiel #6
0
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
Beispiel #7
0
class ChannelTracker(propertied.Propertied):
    """Track open channels on the Asterisk server"""
    channels = common.DictionaryProperty(
        "channels",
        """Set of open channels on the system""",
    )
    thresholdCount = common.IntegerProperty(
        "thresholdCount",
        """Storage of threshold below which we don't warn user""",
        defaultValue=20,
    )

    def main(self):
        """Main operation for the channel-tracking demo"""
        amiDF = APPLICATION.amiSpecifier.login().addCallback(self.onAMIConnect)
        # XXX do something useful on failure to login...
    def onAMIConnect(self, ami):
        """Register for AMI events"""
        # XXX should do an initial query to populate channels...
        # XXX should handle asterisk reboots (at the moment the AMI
        # interface will just stop generating events), not a practical
        # problem at the moment, but should have a periodic check to be sure
        # the interface is still up, and if not, should close and restart
        log.debug('onAMIConnect')
        ami.status().addCallback(self.onStatus, ami=ami)
        ami.registerEvent('Hangup', self.onChannelHangup)
        ami.registerEvent('Newchannel', self.onChannelNew)

    def interestingEvent(self, event, ami=None):
        """Decide whether this channel event is interesting 
		
		Real-world application would want to take only Zap channels, or only
		channels from a given context, or whatever other filter you want in 
		order to capture *just* the scarce resource (such as PRI lines).
		
		Keep in mind that an "interesting" event must show up as interesting 
		for *both* Newchannel and Hangup events or you will leak 
		references/channels or have unknown channels hanging up.
		"""
        return True

    def onStatus(self, events, ami=None):
        """Integrate the current status into our set of channels"""
        log.debug("""Initial channel status retrieved""")
        for event in events:
            self.onChannelNew(ami, event)

    def onChannelNew(self, ami, event):
        """Handle creation of a new channel"""
        log.debug("""Start on channel %s""", event)
        if self.interestingEvent(event, ami):
            opening = not self.channels.has_key(event['uniqueid'])
            self.channels[event['uniqueid']] = event
            if opening:
                self.onChannelChange(ami, event, opening=opening)

    def onChannelHangup(self, ami, event):
        """Handle hangup of an existing channel"""
        if self.interestingEvent(event, ami):
            try:
                del self.channels[event['uniqueid']]
            except KeyError, err:
                log.warn("""Hangup on unknown channel %s""", event)
            else:
                log.debug("""Hangup on channel %s""", event)
            self.onChannelChange(ami, event, opening=False)
Beispiel #8
0
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 = ('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) as 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 as err:
            try:
                callback = self.extensionHandlers[extension]
            except KeyError as err:
                try:
                    callback = self.extensionHandlers[None]
                except KeyError as err:
                    log.warn("""Unexpected connection to extension %r: %s""",
                             extension, agi.variables)
                    agi.finish()
                    return
            try:
                return callback(agi)
            except Exception as 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