[Pkg-voip-commits] [SCM] StarPy branch, master, updated. 40188c102f4f21bd7d44f823e0d9191a1aaa4b05

Paul Belanger paul.belanger at polybeacon.com
Sat Feb 11 18:46:44 UTC 2012


The following commit has been merged in the master branch:
commit b626b9261eb7a0721c91b36d717b4e2da8086b49
Author: Paul Belanger <paul.belanger at polybeacon.com>
Date:   Sat Feb 11 13:36:58 2012 -0500

    Removed starpy source code from package repo
    
    Signed-off-by: Paul Belanger <paul.belanger at polybeacon.com>

diff --git a/CHANGES.txt b/CHANGES.txt
deleted file mode 100644
index 7a6d470..0000000
--- a/CHANGES.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-CHANGE notes for starpy python module
-======================================
-
-- References to zaptel functions have been converted into
-  their DAHDI equivalents. For most of the functions, the 
-  first three letters of the old function names "zap", 
-  have been removed and replaced  with "dahdi".
-
-     Old Zaptel Func.  |  New Zaptel Func.
-    ======================================
-    zapDNDon           | dahdiDNDon
-    zapDNDoff          | dahdiDNDoff
-    zapDialOffHook     | dahdiDialOffHook
-    zapHangup          | dahdiHangup
-    zapshowchannels    | dahdiShowChannels
-    zaptransfers       | dahdiTransfers
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index aa3cb24..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,34 +0,0 @@
-Copyright (c) 2006, Michael C. Fletcher
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-	Redistributions of source code must retain the above copyright
-	notice, this list of conditions and the following disclaimer.
-
-	Redistributions in binary form must reproduce the above
-	copyright notice, this list of conditions and the following
-	disclaimer in the documentation and/or other materials
-	provided with the distribution.
-
-	The name of Michael C. Fletcher, or the name of any Contributor,
-	may not be used to endorse or promote products derived from this 
-	software without specific prior written permission.
-
-THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY
-SITUATION ENDANGERING HUMAN LIFE OR PROPERTY.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-OF THE POSSIBILITY OF SUCH DAMAGE. 
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 3457123..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,25 +0,0 @@
-include MANIFEST.in
-include LICENSE
-include *.py
-include doc/*
-include doc/style/*
-include doc/pydoc/*.py
-include doc/pydoc/*.html
-include examples/*.conf
-
-global-exclude starpy.conf
-global-exclude *CVS*
-global-exclude *Cvs*
-global-exclude *.pyc
-global-exclude *.pyo
-global-exclude *.pdb
-global-exclude *.db
-global-exclude *.max
-global-exclude *.gz
-global-exclude *.zip
-global-exclude *.bat
-global-exclude *.profile
-global-exclude *.directory
-global-exclude *.cvsignore
-global-exclude *.ttf
-global-exclude core.*
diff --git a/README b/README
deleted file mode 100644
index 421a265..0000000
--- a/README
+++ /dev/null
@@ -1 +0,0 @@
-StarPy
diff --git a/UPGRADE.txt b/UPGRADE.txt
deleted file mode 100644
index c4e5680..0000000
--- a/UPGRADE.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-UPGRADE notes for starpy python module
-======================================
-
-- References to zaptel functions have been converted into
-  their DAHDI equivalents. For most of the functions, the 
-  first three letters of the old function names "zap", 
-  have been removed and replaced  with "dahdi".
-
-  i.e. zapDNDoff is now dahdiDNDoff
-
-  See CHANGES.txt for a complete list.
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
index b8b9ac3..0000000
--- a/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""Twisted Protocols for Communication with the Asterisk PBX
-
-StarPy allows you to communicate with an Asterisk PBX using an
-Asterisk Manager Interface (AMI) client or a Fast Asterisk
-Gateway Interface (FastAGI) server.
-
-The protocols are designed to be included in applications that
-want to allow for multi-protocol communication using the Twisted
-protocol.  Their integration with Asterisk does not require any
-modification to the Asterisk source code (though a manager account
-is obviously required for the AMI interface, and you have to
-actually call the FastAGI server from the dialplan).
-"""
diff --git a/doc/index.html b/doc/index.html
deleted file mode 100644
index c821fe8..0000000
--- a/doc/index.html
+++ /dev/null
@@ -1,707 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html><head>
-
-
-  
-  
-  <link rel="stylesheet" type="text/css" href="style/sitestyle.css">
-
-
-  
-  
-  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
-
-
-  
-  
-  <title>StarPy Asterisk Protocols for Twisted</title></head>
-<body>
-
-
-<h1>StarPy
-Asterisk Protocols for Twisted<br>
-
-
-</h1>
-
-
-<p class="introduction">StarPy
-is a Python + Twisted
-protocol that provides access to the Asterisk PBX's Manager Interface
-(AMI) and Fast Asterisk Gateway Interface (FastAGI). Together these
-allow you write both command-and-control interfaces (used, for example
-to generate new calls) and to customise user interactions from the
-dial-plan.  You can readily write applications that use the AMI
-and FastAGI protocol together with any of the already-available Twisted
-protocols.</p>
-
-
-<p class="introduction">StarPy is primarily intended to allow Twisted
-developers to add Asterisk connectivity to their Twisted
-applications.  It isn't really targeted at the normal AGI-writing
-populace, as it requires understanding Twisted's asynchronous
-programming model.  That said, if you do know Twisted, it can
-readily be used to write stand-alone FastAGIs.<br>
-
-
-</p>
-
-
-<p class="technical">StarPy is Open Source, the we are interested in
-contributions, bug reports and feedback.  The contributors (listed
-below) may also be available for implementation and extension contracts.<br>
-
-
-</p>
-
-
-<h2>Installation</h2>
-
-
-<p>StarPy is a pure-Python
-distutils extension.  Simply unpack the
-<a href="https://sourceforge.net/project/showfiles.php?group_id=164040">source
-archive</a> to a temporary directory and run:</p>
-
-
-<pre>python setup.py install<br></pre>
-
-
-<p>You
-will need <a href="http://www.python.org/">Python</a> 2.3+ and
-<a href="http://twistedmatrix.com/">Twisted</a> (Core) installed.
-You'll need <a href="http://basicproperty.sourceforge.net/">BasicProperty</a>
-as well.  If you want to check out the SVN version instead of a
-released version, use:</p>
-
-
-<pre>svn co https://svn.sourceforge.net/svnroot/starpy/trunk starpy<br></pre>
-
-
-<p>On your PythonPath.</p>
-
-
-<p>The demonstration applications use the <a href="pydoc/starpy.utilapplication.html">utilapplication</a> module,
-which uses configuration-file-based setup of the AMI and FastAGI
-servers.  To use this, create a starpy.conf file for the current
-directory (directory from which to run an example script) or a
-~/.starpy.conf user-global file.  Content of the configuration
-file(s) looks like this:</p>
-
-
-<pre>[AMI]<br>username=AMIUSERNAME<br>secret=AMIPASSWORD<br>server=127.0.0.1<br>port=5038<br><br>[FastAGI]<br>port=4573<br>interface=127.0.0.1<br>context=survey<br></pre>
-
-
-<p>Keep in mind that FastAGI applications are neither encrypting nor
-authenticating; you probably should not expose them on any interface
-other than local (127.0.0.1)!</p>
-
-
-<h2>Asterisk Manager Interface
-(AMI) Usage</h2>
-
-
-<p><span class="technical"></span></p>
-
-
-<p>StarPy provides most of the
-hooks you want to use on the protocol instances.  The AMI
-client is created by a client factory, as is standard for Twisted
-operation.  You can create a factory manually like so:</p>
-
-
-<pre>from starpy import manager<br>f = manager.AMIFactory(sys.argv[1], sys.argv[2])<br>df = f.login('server',port)</pre>
-
-
-<p>The factory takes the username
-and secret (password) for the Asterisk manager interface (note: do not
-actually pass in these values on the command-line in a real
-application, as this would expose the username and password to anyone
-on the machine).  The deferred object returned from the login
-call will fire when the AMI connection has been established and
-authenticated.  You register callbacks on the deferred to
-accomplish those tasks you'd like to accomplish.</p>
-
-
-<p>You will need to configure
-Asterisk to have the AMI enabled and choose the username, password and
-allowed hosts in
-/etc/asterisk/manager.conf.  You will also need to be sure that
-the AMI user has sufficient permissions to carry out whatever AMI
-operations you want to perform:</p>
-
-
-<pre>[USERNAME]<br>secret=SECRETPASSWORD<br>permit=127.0.0.1<br>read = system,call,log,verbose,command,agent,user<br>write = system,call,log,verbose,command,agent,user</pre>
-
-
-<p>Please keep in mind that the AMI
-interface is not encrypted, so should never be run across an insecure
-network.  If you need to run across such a network, use ssh
-tunnelling or the like to prevent eavesdropping!  You will want to
-read up on the <a href="http://www.voip-info.org/wiki/view/Asterisk+manager+API">AMI</a>
-in the voip-info Wiki.<br>
-
-
-</p>
-
-
-<p>The return value for the
-login() deferred is an <a href="pydoc/starpy.manager.html#AMIProtocol">AMIProtocol</a>
-instance.  The various methods on the AMIProtocol generally
-handle the creation and interpretation of "Action ID" fields. 
-The return value for most methods is an event, message or list of
-events.  Messages and events are modeled as dictionaries with
-lower-case keys.</p>
-
-
-<p>Perhaps the most common task desired for use with the AMI Protocol
-is the creation of new calls.  Here's a snippet showing such
-generation:</p>
-
-
-<pre>self.ami.originate( <br>	self.callbackChannel,<br>	self.ourContext, id(self), 1,<br>	timeout = 15,<br>)<br></pre>
-
-
-<p>You will likely want to ignore the results of the originate, and
-instead use an equal timeout waiting for an AGI connection to determine
-whether you have connected (the AMI originate can "succeed" without a
-successful connection, and will not tell you what channel is
-created).  If you want to track whether you have returned from a
-particular call to originate, use a different extension for each
-originate call (you can use UtilApplication's <a href="pydoc/starpy.utilapplication.html#UtilApplication-waitForCallOn">waitForCallOn</a>
-method to register a one-shot handler if you are using UtilApplication).</p>
-
-
-<p>Another common task is watching for an event of a particular type,
-for instance a "Hangup" event.  The AMIProtocol instance has a
-method registerEvent that allows you to add a handler to be called
-whenever an event of a given type is observed.</p>
-
-
-<pre>def onChannelHangup( ami, event ):<br>	"""Deal with the hangup of an event"""<br>	if event['uniqueid'] == self.uniqueChannelId:<br>		log.info( """AMI Detected close of our channel: %s""", self.uniqueChannelId )<br>		self.stopTime = time.time()<br>		# give the user a few seconds to put down the hand-set<br>		reactor.callLater( 2, df.callback, event )<br>		self.ami.deregisterEvent( 'Hangup', onChannelHangup )<br>self.ami.registerEvent( 'Hangup', onChannelHangup )<br>return df.addCallback( self.onHangup, callbacks=5 )<br></pre>
-
-
-<p><span class="technical">Note that the registerEvent and
-deregisterEvent methods use object identity to manage the callbacks
-being stored, as a result, a method is not a good handler (since method
-objects are created and destroyed each time they are accessed) to
-choose.  A nested function that can be passed to deregisterHandler
-is generally a better choice.  Eventually we may use PyDispatcher
-for the registration as it has solved this problem already in a far
-more general way.</span></p>
-
-
-<p>See the
-examples/connecttoivr.py and examples/calldurationcallback.py scripts
-for sample usage of the AMIProtocol</p>
-
-
-<p><span class="technical">Note that StarPy uses floating-point seconds
-for all time values in all interfaces, </span></p>
-
-
-<span style="font-family: monospace;"></span>
-<h2>Fast Asterisk Gateway
-Interface (FastAGI) Usage</h2>
-
-
-<p>Again, most of the hooks you
-want to use are provided on the protocol instances.  FastAGI
-is a server, and is thus created by a (non-client) factory like so:</p>
-
-
-<pre>from starpy import fastagi<br>f = fastagi.FastAGIFactory(testFunction)<br>reactor.listenTCP( 4573, f, 50, '127.0.0.1')</pre>
-
-
-<p>testFunction in the example above is the
-operation to undertake when the Asterisk Server connects to the FastAGI
-server.  It takes a (connected) <a href="pydoc/starpy.fastagi.html#FastAGIProtocol">FastAGIProtocol</a>
-instance as its only argument.</p>
-
-
-<p>This FastAGI protocol has methods available which match those <a href="http://www.voip-info.org/wiki-Asterisk+AGI">AGI functions</a>
-documented in the voip-info wiki.  Each method has basic
-documentation in the automated reference linked above, but you will
-want to use the wiki documentation to understand the semantics of the
-calls.  Keep in mind that the <a href="pydoc/starpy.fastagi.html#FastAGIProtocol-execute">execute</a>
-method (known as exec (which is a Python keyword) in the AGI
-documentation) allows you to access <a href="http://www.voip-info.org/wiki/index.php?page=Asterisk+-+documentation+of+application+commands">Asterisk
-Applications</a> as well as AGI methods.</p>
-
-
-<p>You use a FastAGI application
-from your Dial Plan like this (note: arguments do not
-appear to be passed to FastAGI scripts in Asterisk 1.2.1, unlike
-regular AGI scripts):</p>
-
-
-<pre>exten => 1000,3,AGI(agi://127.0.0.1:4573)</pre>
-
-
-<p>Please keep in mind that the
-FastAGI interface is neither encrypted nor authenticating!  It
-should never be run across an insecure network and should never be run
-on a port that is accessible from a public network.  Also keep in
-mind that your FastAGI process must be running already when Asterisk
-tries to connect to it, you need to code your FastAGI process to be
-robust so that it is always available to Asterisk.<br>
-
-
-</p>
-
-
-<p>See the examples directory for
-examples of FastAGI scripts.</p>
-
-
-<p><span class="technical">Note that StarPy uses floating-point seconds
-for all time values in all interfaces, </span></p>
-
-
-<h3>Sequential Operations</h3>
-
-
-The <a href="pydoc/fastagi.html#InSequence">InSequence</a>
-class allows for easily setting up multiple chained deferred processes,
-for instance when you want to play 2 or 3 sound files
-sequentially. 
-It is used like this:
-<pre>sequence = fastagi.InSequence()<br>sequence.append( agi.setContext, agi.variables['agi_context'] )<br>sequence.append( agi.setExtension, agi.variables['agi_extension'] )<br>sequence.append( agi.setPriority, int(agi.variables['agi_priority'])+difference )<br>sequence.append( agi.finish )<br>return sequence()<br></pre>
-
-
-<p>Calling the populated sequence returns a deferred which fires when
-all elements finish, or any element fails (raise an
-exception/failure).  The InSequence class is a trivial convenience
-that avoids needing to define a new callable function for every
-operation of a many-step operation.<br>
-
-
-</p>
-
-
-<h3>Menu Objects Usage</h3>
-
-
-<p>The FastAGI interface includes basic support for creating hierarchic
-<a href="pydoc/starpy.menu.html">IVR menus</a>.  The purpose of
-the menuing system is to encapsulate common UI functionality at a
-higher level of abstraction than that seen in the raw FastAGI
-interface.  Menus are defined using "model" classes which describe
-the desired features of the menu.  An example <a href="pydoc/starpy.menu.html#Menu">Menu</a> using simple single-digit <a href="pydoc/starpy.menu.html#Option">Option</a> instances:</p>
-
-
-<pre>m = menu.Menu(<br>	tellInvalid = False, # don't report incorrect selections<br>	prompt = 'atlantic',<br>	options = [<br>		menu.Option( option='0' ),<br>		menu.Option( option='#' ),<br>		menu.ExitOn( option='*' ),<br>	],<br>	maxRepetitions = 5,<br>)<br></pre>
-
-
-<p>To invoke the menu, simply call it with a FastAGI protocol instance
-as its first argument.  The menu will repeat up to maxRepetitions
-times if an invalid or null entry is chosen.  If tellInvalid is
-True, the menu will play an "invalid entry" message of your choosing on
-an unrecognised entry, otherwise it will ignore invalid choices.</p>
-
-
-<p>If a callable option is specified, such as <a href="pydoc/starpy.menu.html#ExitOn">ExitOn</a> or <a href="pydoc/starpy.menu.html#SubMenu">SubMenu</a>, the result of
-calling that option with the AGI and the selected option will be
-returned.  This same mechanism allows for creating chained
-sub-menus like so:</p>
-
-
-<pre>menu.SubMenu( <br>	option='1',<br>	menu = menu.Menu(<br>		tellInvalid = False, # don't report incorrect selections<br>		prompt = ['atlantic',menu.DigitsPrompt(53),menu.DateTimePrompt(time.time())],<br>		options = [<br>			menu.Option( option='0' ),<br>			menu.Option( option='#' ),<br>			menu.ExitOn( option='*' ),<br>		],<br>	),<br>),</pre>
-
-
-<p>which can be used as an option within a higher-level menu.</p>
-
-
-<p>You can also specify an onSuccess callback in the Option, this will
-be called (and it's value returned) if and only if that specific Option
-is chosen by the user (it is called only if the Option is not itself
-callable (which regular Option instances are not)).</p>
-
-
-<p>The return value from a Menu is a chain of [ (option, digit), ... ]
-pairs for the final option selected from the lowest-level menu. 
-An ExitOn option triggers a return to a higher-level menu; this is not
-reported as a "final" option selection.</p>
-
-
-<p>The Menu module includes a <a href="pydoc/starpy.menu.html#CollectDigits">CollectDigits</a> class
-which may be used either as a top-level Menu or as a SubMenu-wrapped
-option in a higher-level menu:</p>
-
-
-<pre>menu.SubMenu(<br>	option='2',<br>	menu = menu.CollectDigits(<br>		soundFile = 'extension',<br>		maxDigits = 5,<br>		minDigits = 3,<br>	),<br>)<br></pre>
-
-
-<p>Eventually the CollectDigits class should support review/cancel
-options on completion.  It would also be nice to get it to use the
-prompt system, but as of yet I don't know of any way to make that work
-with multi-character entry during the various sayXXX functions.</p>
-
-
-<h3>Signalling Errors from FastAGI<br>
-
-
-</h3>
-
-
-<p>The FastAGIProtocol has a method <a href="pydoc/fastagi.html#FastAGIProtocol-jumpOnError">jumpOnError</a>
-which is intended to be used for implementing the common Asterisk
-application pattern of setting priority to some large value beyond the
-current value in order to indicate an error in the application. 
-Yes, it's an ugly way to signal errors, but there it is.  To use,
-add jumpOnError to a deferred where you want any uncaught exception to
-trigger a jump and finish the AGI connection.  This would normally
-be the overall deferred for your entire FastAGI operation.<br>
-
-
-</p>
-
-
-<pre>df.addErrback( agi.jumpOnError, 100 )<br></pre>
-
-
-<p>If you only want to cause a particular jump on a particular
-error/exception or set of exceptions, you can pass in a (tuple of)
-error classes in the forErrors argument to which to restrict the jump:</p>
-
-
-<pre>df.addErrback( agi.jumpOnError, 50, forErrors=error.OnUnknownUser )<br></pre>
-
-
-<h2>Secondary Services</h2>
-
-
-<p>The <a href="pydoc/starpy.utilapplication.html">utilapplication</a>
-module contains a few simple classes which provide common services for
-writing AMI/FastAGI applications.  This includes
-configuration-file setup of AMI and FastAGI services and an application
-instance that provides methods for registering to handle incoming
-FastAGI extensions.</p>
-
-
-<pre># map incoming calls to extension 's' to the given method onS<br>APPLICATION.handleCallsFor( 's', someObject.onS )</pre>
-
-
-<p>UtilApplication's agiSpecifier and amiSpecifier property point to
-automatically generated <a href="pydoc/starpy.utilapplication.html#AGISpecifier">AGISpecifier</a>
-and <a href="pydoc/starpy.utilapplication.html#AMISpecifier">AMISpecifier</a>
-instances whose parameters are loaded from configuration files. 
-The specifier instances provide methods for starting up instances
-configured by the specifier:</p>
-
-
-<pre># tell the application to run a FastAGI server which dispatches<br># to handlers registered with handleCallsFor (as above)<br>APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br><br># tell the application to log into the configured AMI server<br># to allow for further management operations<br>df = APPLICATION.amiSpecifier.login( <br>).addCallback( self.onAMIConnect )<br></pre>
-
-
-<h3>Simple FastAGI Application Example<br>
-
-
-</h3>
-
-
-<p>The following is the hellofastagiapp sample application, it uses the
-starpy.conf file in the current directory to control the FastAGI setup,
-and shows use of the utilapplication handleCallsFor method, which
-allows for a single FastAGI server handling many different FastAGI
-scripts (though in this case we only register a handler for one
-extension, 's'):</p>
-
-
-<pre>#! /usr/bin/env python<br>"""FastAGI server using starpy and the utility application framework<br><br>This is basically identical to hellofastagi, save that it uses the application<br>framework to allow for configuration-file-based setup of the AGI service.<br>"""<br>from twisted.internet import reactor<br>from starpy import fastagi, utilapplication<br>import logging, time<br><br>log = logging.getLogger( 'hellofastagi' )<br><br>def testFunction( agi ):<br>	"""Demonstrate simplistic use of the AGI interface with sequence of actions"""<br>	log.debug( 'testFunction' )<br>	sequence = fastagi.InSequence()<br>	sequence.append( agi.sayDateTime, time.time() )<br>	sequence.append( agi.finish )<br>	def onFailure( reason ):<br>		log.error( "Failure: %s", reason.getTraceback())<br>		agi.finish()<br>	return sequence().addErrback( onFailure )<br><br>if __name__ == "__main__":<br>	logging.basicConfig()<br>	fastagi.log.setLevel( logging.DEBUG )<br>	APPLICATION = utilapplication.UtilApplication()<br>	APPLICATION.handleCallsFor( 's', testFunction )<br>	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>	reactor.run()</pre>
-
-
-<h2>Changes</h2>
-
-
-<ul>
-
-
-</ul>
-
-
-<p>StarPy can be downloaded from the project's <a href="https://sourceforge.net/project/showfiles.php?group_id=164040">File
-Download</a> area.</p>
-
-
-<ul>
-
-
-  <li>1.0.0b1</li><ul><li>Provide download link in setup.py to allow easy-install to work</li></ul><li>1.0.0a13</li><ul><li>Godson's Asterisk 1.4.x interface updates</li></ul><li>1.0.0.a12</li>
-  <ul>
-    <li>Fix "recursion" bug in menu's onReadMenu</li>
-    <li>Fix bug in clientConnectionFailed parameters for AMI connections</li>
-  </ul>
-  <li>1.0.0.a11</li>
-
-  
-  <ul>
-
-    <li>Fix bug in fastagi setExtension</li>
-
-    <li>Add timeout to manager api</li>
-
-    <li>Allow for overriding utilapplication configuration loading (to add new sections, for instance)</li>
-
-  
-  </ul>
-
-  <li>1.0.0.a10</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Allow for registering a FastAGI handler for None, is used to
-provide a default handler for extensions which do not have an explicit
-waiter or handler registered</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0.a9</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Add multi-element prompt capability, so you can use sound
-files, numbers, read alpha and similar operations to define a compound
-prompt for a menu.<br>
-
-
-      <br>
-
-
-Note: This change breaks all previously defined menus, you need to
-change the "soundFile" property of your menus to be "prompt".  You
-may (but do not need to) wrap your sound file names in a
-menu.AudioPrompt() instance.</li>
-
-
-    <li>Minor bug in onStreamingComplete fixed (variable name shadowed
-the module)</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0.a8</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Add password-checking menu operation</li>
-
-
-    <li>Add ability to pass an "onSuccess" handler to a menu option; it
-is called before returning from selection of that option</li>
-
-
-    <li>Fix bug in AMI handling of multi-line command results that
-include ':' characters</li>
-
-
-    <li>Add example showing usage of ami.command(...)</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0.a7</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Fix bug introduced in a6
-where None could no longer be used to
-handle all events in AMI</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0.a6</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Fix bug in AMIProtocol.deregisterEvent, would remove all
-registrations in all instances<br>
-
-
-    </li>
-
-
-    <li>Add ability to register/deregister multiple events at once in
-AMIProtocol.registerEvent and deregisterEvent</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0a5</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Setup script bug-fix for placement of data files (one directory
-level too high)</li>
-
-
-    <li>Minor documentation enhancements<br>
-
-
-    </li>
-
-
-    <li>priexhaustion.py example application added (track total number
-of open channels)</li>
-
-
-    <li>Bug-fix in AGI getVariable (incorrect/incomplete parsing)</li>
-
-
-    <li>Trivial bug-fix in hellofastagiapp.py (editing problem during
-documentation creation)</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0a4</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Fixed naming error on setPriority</li>
-
-
-    <li>Added jumpOnError</li>
-
-
-    <li>Fixes for Call Duration Sample to work with newest code</li>
-
-
-    <li>More documentation</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0a3</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>FastAGI's getOption API change, should actually be useful now</li>
-
-
-    <li>IVR Menu and CollectDigits objects first release</li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0a2</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Slightly more mature release, a few minor applications have
-been built with the package to test out operation, a few bugs have been
-fixed.</li>
-
-
-    <li>Call Duration sample application added, note that this requires
-      <a href="http://basicproperty.sourceforge.net/">BasicProperty</a><br>
-
-
-    </li>
-
-
-  
-  
-  </ul>
-
-
-  <li>1.0.0a1</li>
-
-
-  
-  
-  <ul>
-
-
-    <li>Initial release of the
-StarPy package, much of the functionality is still untested, but the
-coverage of the APIs should be close to complete.</li>
-
-
-  
-  
-  </ul>
-
-
-</ul>
-
-
-<h2>License</h2>
-
-
-<p>StarPy is licensed under
-extremely liberal terms.<br>
-
-
-</p>
-
-
-<pre>Copyright (c) 2006, Michael C. Fletcher and Contributors<br>All rights reserved.<br><br>Redistribution and use in source and binary forms, with or without<br>modification, are permitted provided that the following conditions<br>are met:<br><br>	Redistributions of source code must retain the above copyright<br>	notice, this list of conditions and the following disclaimer.<br><br>	Redistributions in binary form must reproduce the above<br>	copyright notice, this list of conditions and the following<br>	disclaimer in the documentation and/or other materials<br>	provided with the distribution.<br><br>	The name of Michael C. Fletcher, or the name of any Contributor,<br>	may not be used to endorse or promote products derived from this <br>	software without specific prior written permission.<br><br>THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY<br>SITUATION ENDANGERING HUMAN LIFE OR PROPERTY.<br><br>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS<br>``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT<br>LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS<br>FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE<br>COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,<br>INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES<br>(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR<br>SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)<br>HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,<br>STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)<br>ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED<br>OF THE POSSIBILITY OF SUCH DAMAGE.<br></pre><p>Contributors include (contributors with an (*) after their name are generally available for consulting work):</p><ul><li><a href="mailto:mcfletch at vrplumber.com">Mike C. Fletcher</a> (VRPlumber Consulting Inc., Canada)</li><li><a href="mailto:godson.g at gmail.com">Godson Gera</a> </li></ul>
-
-
-<p class="footer"><a href="http://starpy.sourceforge.net/">StarPy</a> 
-is a <a href="http://sourceforge.net/"> <img alt="SourceForge.net Logo" style="border: 0px solid ; width: 88px; height: 31px;" src="http://sourceforge.net/sflogo.php?group_id=164040&type=1" align="middle"></a> Open-Source Project</p>
-
-
-</body></html>
\ No newline at end of file
diff --git a/doc/pydoc/builddocs.py b/doc/pydoc/builddocs.py
deleted file mode 100755
index 080f7c6..0000000
--- a/doc/pydoc/builddocs.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""Script to automatically generate PyTable documentation"""
-import pydoc2
-
-if __name__ == "__main__":
-	excludes = [
-		"Numeric",
-		"_tkinter",
-		"Tkinter",
-		"math",
-		"string",
-		"twisted",
-	]
-	stops = [
-	]
-
-	modules = [
-		'starpy',
-		'starpy.examples',
-		'__builtin__',
-	]	
-	pydoc2.PackageDocumentationGenerator(
-		baseModules = modules,
-		destinationDirectory = ".",
-		exclusions = excludes,
-		recursionStops = stops,
-	).process ()
-	
diff --git a/doc/pydoc/pydoc2.py b/doc/pydoc/pydoc2.py
deleted file mode 100644
index 8fc4d33..0000000
--- a/doc/pydoc/pydoc2.py
+++ /dev/null
@@ -1,463 +0,0 @@
-"""Pydoc sub-class for generating documentation for entire packages"""
-import pydoc, inspect, os, string
-import sys, imp, os, stat, re, types, inspect
-from repr import Repr
-from string import expandtabs, find, join, lower, split, strip, rfind, rstrip
-
-def classify_class_attrs(cls):
-	"""Return list of attribute-descriptor tuples.
-
-	For each name in dir(cls), the return list contains a 4-tuple
-	with these elements:
-
-		0. The name (a string).
-
-		1. The kind of attribute this is, one of these strings:
-			   'class method'    created via classmethod()
-			   'static method'   created via staticmethod()
-			   'property'        created via property()
-			   'method'          any other flavor of method
-			   'data'            not a method
-
-		2. The class which defined this attribute (a class).
-
-		3. The object as obtained directly from the defining class's
-		   __dict__, not via getattr.  This is especially important for
-		   data attributes:  C.data is just a data object, but
-		   C.__dict__['data'] may be a data descriptor with additional
-		   info, like a __doc__ string.
-	
-	Note: This version is patched to work with Zope Interface-bearing objects
-	"""
-
-	mro = inspect.getmro(cls)
-	names = dir(cls)
-	result = []
-	for name in names:
-		# Get the object associated with the name.
-		# Getting an obj from the __dict__ sometimes reveals more than
-		# using getattr.  Static and class methods are dramatic examples.
-		if name in cls.__dict__:
-			obj = cls.__dict__[name]
-		else:
-			try:
-				obj = getattr(cls, name)
-			except AttributeError, err:
-				continue
-
-		# Figure out where it was defined.
-		homecls = getattr(obj, "__objclass__", None)
-		if homecls is None:
-			# search the dicts.
-			for base in mro:
-				if name in base.__dict__:
-					homecls = base
-					break
-
-		# Get the object again, in order to get it from the defining
-		# __dict__ instead of via getattr (if possible).
-		if homecls is not None and name in homecls.__dict__:
-			obj = homecls.__dict__[name]
-
-		# Also get the object via getattr.
-		obj_via_getattr = getattr(cls, name)
-
-		# Classify the object.
-		if isinstance(obj, staticmethod):
-			kind = "static method"
-		elif isinstance(obj, classmethod):
-			kind = "class method"
-		elif isinstance(obj, property):
-			kind = "property"
-		elif (inspect.ismethod(obj_via_getattr) or
-			  inspect.ismethoddescriptor(obj_via_getattr)):
-			kind = "method"
-		else:
-			kind = "data"
-
-		result.append((name, kind, homecls, obj))
-
-	return result
-inspect.classify_class_attrs = classify_class_attrs
-
-
-class DefaultFormatter(pydoc.HTMLDoc):
-	def docmodule(self, object, name=None, mod=None, packageContext = None, *ignored):
-		"""Produce HTML documentation for a module object."""
-		name = object.__name__ # ignore the passed-in name
-		parts = split(name, '.')
-		links = []
-		for i in range(len(parts)-1):
-			links.append(
-				'<a href="%s.html"><font color="#ffffff">%s</font></a>' %
-				(join(parts[:i+1], '.'), parts[i]))
-		linkedname = join(links + parts[-1:], '.')
-		head = '<big><big><strong>%s</strong></big></big>' % linkedname
-		try:
-			path = inspect.getabsfile(object)
-			url = path
-			if sys.platform == 'win32':
-				import nturl2path
-				url = nturl2path.pathname2url(path)
-			filelink = '<a href="file:%s">%s</a>' % (url, path)
-		except TypeError:
-			filelink = '(built-in)'
-		info = []
-		if hasattr(object, '__version__'):
-			version = str(object.__version__)
-			if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
-				version = strip(version[11:-1])
-			info.append('version %s' % self.escape(version))
-		if hasattr(object, '__date__'):
-			info.append(self.escape(str(object.__date__)))
-		if info:
-			head = head + ' (%s)' % join(info, ', ')
-		result = self.heading(
-			head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
-
-		modules = inspect.getmembers(object, inspect.ismodule)
-
-		classes, cdict = [], {}
-		for key, value in inspect.getmembers(object, inspect.isclass):
-			if (inspect.getmodule(value) or object) is object:
-				classes.append((key, value))
-				cdict[key] = cdict[value] = '#' + key
-		for key, value in classes:
-			for base in value.__bases__:
-				key, modname = base.__name__, base.__module__
-				module = sys.modules.get(modname)
-				if modname != name and module and hasattr(module, key):
-					if getattr(module, key) is base:
-						if not cdict.has_key(key):
-							cdict[key] = cdict[base] = modname + '.html#' + key
-		funcs, fdict = [], {}
-		for key, value in inspect.getmembers(object, inspect.isroutine):
-			if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
-				funcs.append((key, value))
-				fdict[key] = '#-' + key
-				if inspect.isfunction(value): fdict[value] = fdict[key]
-		data = []
-		for key, value in inspect.getmembers(object, pydoc.isdata):
-			if key not in ['__builtins__', '__doc__']:
-				data.append((key, value))
-
-		doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict)
-		doc = doc and '<tt>%s</tt>' % doc
-		result = result + '<p>%s</p>\n' % doc
-
-		packageContext.clean ( classes, object )
-		packageContext.clean ( funcs, object )
-		packageContext.clean ( data, object )
-		
-		if hasattr(object, '__path__'):
-			modpkgs = []
-			modnames = []
-			for file in os.listdir(object.__path__[0]):
-				path = os.path.join(object.__path__[0], file)
-				modname = inspect.getmodulename(file)
-				if modname and modname not in modnames:
-					modpkgs.append((modname, name, 0, 0))
-					modnames.append(modname)
-				elif pydoc.ispackage(path):
-					modpkgs.append((file, name, 1, 0))
-			modpkgs.sort()
-			contents = self.multicolumn(modpkgs, self.modpkglink)
-##			result = result + self.bigsection(
-##				'Package Contents', '#ffffff', '#aa55cc', contents)
-			result = result + self.moduleSection( object, packageContext)
-		elif modules:
-			contents = self.multicolumn(
-				modules, lambda (key, value), s=self: s.modulelink(value))
-			result = result + self.bigsection(
-				'Modules', '#fffff', '#aa55cc', contents)
-
-		
-		if classes:
-##			print classes
-##			import pdb
-##			pdb.set_trace()
-			classlist = map(lambda (key, value): value, classes)
-			contents = [
-				self.formattree(inspect.getclasstree(classlist, 1), name)]
-			for key, value in classes:
-				contents.append(self.document(value, key, name, fdict, cdict))
-			result = result + self.bigsection(
-				'Classes', '#ffffff', '#ee77aa', join(contents))
-		if funcs:
-			contents = []
-			for key, value in funcs:
-				contents.append(self.document(value, key, name, fdict, cdict))
-			result = result + self.bigsection(
-				'Functions', '#ffffff', '#eeaa77', join(contents))
-		if data:
-			contents = []
-			for key, value in data:
-				try:
-					contents.append(self.document(value, key))
-				except Exception, err:
-					pass
-			result = result + self.bigsection(
-				'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))
-		if hasattr(object, '__author__'):
-			contents = self.markup(str(object.__author__), self.preformat)
-			result = result + self.bigsection(
-				'Author', '#ffffff', '#7799ee', contents)
-		if hasattr(object, '__credits__'):
-			contents = self.markup(str(object.__credits__), self.preformat)
-			result = result + self.bigsection(
-				'Credits', '#ffffff', '#7799ee', contents)
-
-		return result
-
-	def classlink(self, object, modname):
-		"""Make a link for a class."""
-		name, module = object.__name__, sys.modules.get(object.__module__)
-		if hasattr(module, name) and getattr(module, name) is object:
-			return '<a href="%s.html#%s">%s</a>' % (
-				module.__name__, name, name
-			)
-		return pydoc.classname(object, modname)
-	
-	def moduleSection( self, object, packageContext ):
-		"""Create a module-links section for the given object (module)"""
-		modules = inspect.getmembers(object, inspect.ismodule)
-		packageContext.clean ( modules, object )
-		packageContext.recurseScan( modules )
-
-		if hasattr(object, '__path__'):
-			modpkgs = []
-			modnames = []
-			for file in os.listdir(object.__path__[0]):
-				path = os.path.join(object.__path__[0], file)
-				modname = inspect.getmodulename(file)
-				if modname and modname not in modnames:
-					modpkgs.append((modname, object.__name__, 0, 0))
-					modnames.append(modname)
-				elif pydoc.ispackage(path):
-					modpkgs.append((file, object.__name__, 1, 0))
-			modpkgs.sort()
-			# do more recursion here...
-			for (modname, name, ya,yo) in modpkgs:
-				packageContext.addInteresting( join( (object.__name__, modname), '.'))
-			items = []
-			for (modname, name, ispackage,isshadowed) in modpkgs:
-				try:
-					# get the actual module object...
-##					if modname == "events":
-##						import pdb
-##						pdb.set_trace()
-					module = pydoc.safeimport( "%s.%s"%(name,modname) )
-					description, documentation = pydoc.splitdoc( inspect.getdoc( module ))
-					if description:
-						items.append(
-							"""%s -- %s"""% (
-								self.modpkglink( (modname, name, ispackage, isshadowed) ),
-								description,
-							)
-						)
-					else:
-						items.append(
-							self.modpkglink( (modname, name, ispackage, isshadowed) )
-						)
-				except:
-					items.append(
-						self.modpkglink( (modname, name, ispackage, isshadowed) )
-					)
-			contents = string.join( items, '<br>')
-			result = self.bigsection(
-				'Package Contents', '#ffffff', '#aa55cc', contents)
-		elif modules:
-			contents = self.multicolumn(
-				modules, lambda (key, value), s=self: s.modulelink(value))
-			result = self.bigsection(
-				'Modules', '#fffff', '#aa55cc', contents)
-		else:
-			result = ""
-		return result
-	
-	
-class AlreadyDone(Exception):
-	pass
-	
-
-
-class PackageDocumentationGenerator:
-	"""A package document generator creates documentation
-	for an entire package using pydoc's machinery.
-
-	baseModules -- modules which will be included
-		and whose included and children modules will be
-		considered fair game for documentation
-	destinationDirectory -- the directory into which
-		the HTML documentation will be written
-	recursion -- whether to add modules which are
-		referenced by and/or children of base modules
-	exclusions -- a list of modules whose contents will
-		not be shown in any other module, commonly
-		such modules as OpenGL.GL, wxPython.wx etc.
-	recursionStops -- a list of modules which will
-		explicitly stop recursion (i.e. they will never
-		be included), even if they are children of base
-		modules.
-	formatter -- allows for passing in a custom formatter
-		see DefaultFormatter for sample implementation.
-	"""
-	def __init__ (
-		self, baseModules, destinationDirectory = ".",
-		recursion = 1, exclusions = (),
-		recursionStops = (),
-		formatter = None
-	):
-		self.destinationDirectory = os.path.abspath( destinationDirectory)
-		self.exclusions = {}
-		self.warnings = []
-		self.baseSpecifiers = {}
-		self.completed = {}
-		self.recursionStops = {}
-		self.recursion = recursion
-		for stop in recursionStops:
-			self.recursionStops[ stop ] = 1
-		self.pending = []
-		for exclusion in exclusions:
-			try:
-				self.exclusions[ exclusion ]= pydoc.locate ( exclusion)
-			except pydoc.ErrorDuringImport, value:
-				self.warn( """Unable to import the module %s which was specified as an exclusion module"""% (repr(exclusion)))
-		self.formatter = formatter or DefaultFormatter()
-		for base in baseModules:
-			self.addBase( base )
-	def warn( self, message ):
-		"""Warnings are used for recoverable, but not necessarily ignorable conditions"""
-		self.warnings.append (message)
-	def info (self, message):
-		"""Information/status report"""
-		print message
-	def addBase(self, specifier):
-		"""Set the base of the documentation set, only children of these modules will be documented"""
-		try:
-			self.baseSpecifiers [specifier] = pydoc.locate ( specifier)
-			self.pending.append (specifier)
-		except pydoc.ErrorDuringImport, value:
-			self.warn( """Unable to import the module %s which was specified as a base module"""% (repr(specifier)))
-	def addInteresting( self, specifier):
-		"""Add a module to the list of interesting modules"""
-		if self.checkScope( specifier):
-##			print "addInteresting", specifier
-			self.pending.append (specifier)
-		else:
-			self.completed[ specifier] = 1
-	def checkScope (self, specifier):
-		"""Check that the specifier is "in scope" for the recursion"""
-		if not self.recursion:
-			return 0
-		items = string.split (specifier, ".")
-		stopCheck = items [:]
-		while stopCheck:
-			name = string.join(items, ".")
-			if self.recursionStops.get( name):
-				return 0
-			elif self.completed.get (name):
-				return 0
-			del stopCheck[-1]
-		while items:
-			if self.baseSpecifiers.get( string.join(items, ".")):
-				return 1
-			del items[-1]
-		# was not within any given scope
-		return 0
-
-	def process( self ):
-		"""Having added all of the base and/or interesting modules,
-		proceed to generate the appropriate documentation for each
-		module in the appropriate directory, doing the recursion
-		as we go."""
-		try:
-			while self.pending:
-				try:
-					if self.completed.has_key( self.pending[0] ):
-						raise AlreadyDone( self.pending[0] )
-					self.info( """Start %s"""% (repr(self.pending[0])))
-					object = pydoc.locate ( self.pending[0] )
-					self.info( """   ... found %s"""% (repr(object.__name__)))
-				except AlreadyDone:
-					pass
-				except pydoc.ErrorDuringImport, value:
-					self.info( """   ... FAILED %s"""% (repr( value)))
-					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
-				except (SystemError, SystemExit), value:
-					self.info( """   ... FAILED %s"""% (repr( value)))
-					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
-				except Exception, value:
-					self.info( """   ... FAILED %s"""% (repr( value)))
-					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
-				else:
-					page = self.formatter.page(
-						pydoc.describe(object),
-						self.formatter.docmodule(
-							object,
-							object.__name__,
-							packageContext = self,
-						)
-					)
-					file = open (
-						os.path.join(
-							self.destinationDirectory,
-							self.pending[0] + ".html",
-						),
-						'w',
-					)
-					file.write(page)
-					file.close()
-					self.completed[ self.pending[0]] = object
-				del self.pending[0]
-		finally:
-			for item in self.warnings:
-				print item
-			
-	def clean (self, objectList, object):
-		"""callback from the formatter object asking us to remove
-		those items in the key, value pairs where the object is
-		imported from one of the excluded modules"""
-		for key, value in objectList[:]:
-			for excludeObject in self.exclusions.values():
-				if hasattr( excludeObject, key ) and excludeObject is not object:
-					if (
-						getattr( excludeObject, key) is value or
-						(hasattr( excludeObject, '__name__') and
-						 excludeObject.__name__ == "Numeric"
-						 )
-					):
-						objectList[:] = [ (k,o) for k,o in objectList if k != key ]
-	def recurseScan(self, objectList):
-		"""Process the list of modules trying to add each to the
-		list of interesting modules"""
-		for key, value in objectList:
-			self.addInteresting( value.__name__ )
-
-
-
-if __name__ == "__main__":
-	excludes = [
-		"OpenGL.GL",
-		"OpenGL.GLU",
-		"OpenGL.GLUT",
-		"OpenGL.GLE",
-		"OpenGL.GLX",
-		"wxPython.wx",
-		"Numeric",
-		"_tkinter",
-		"Tkinter",
-	]
-
-	modules = [
-		"OpenGLContext.debug",
-##		"wxPython.glcanvas",
-##		"OpenGL.Tk",
-##		"OpenGL",
-	]	
-	PackageDocumentationGenerator(
-		baseModules = modules,
-		destinationDirectory = "z:\\temp",
-		exclusions = excludes,
-	).process ()
-		
diff --git a/doc/style/sitestyle.css b/doc/style/sitestyle.css
deleted file mode 100644
index 2db80f2..0000000
--- a/doc/style/sitestyle.css
+++ /dev/null
@@ -1,54 +0,0 @@
-h1,h2,h3 {
-	color: #000000;
-	background-color: #f0f0f0;
-	border-top-style: solid; 
-	border-top-width: 1 
-}
-.footer {
-	color: #000033;
-	background-color: #f0f0f0;
-	text-align: center;
-	border-bottom-style: solid; 
-	border-bottom-width: 1 
-}
-.introduction {
-	margin-left: 60;
-	margin-right: 60;
-	color: #555555;
-}
-.technical {
-	margin-left: 60;
-	margin-right: 60;
-	color: #775555;
-}
-p {
-	margin-left: 10;
-	margin-right: 10;
-}
-ul {
-	margin-left: 30;
-}
-pre {
-	background-color: #fffff0; 
-	margin-left: 60;
-}
-blockquote {
-	margin-left: 90;
-}
-body {
-	background-color: #FFFFFF; 
-	color: #000000;
-	font-family: Arial, Helvetica;
-}
-a:link {
-	color: #3333e0;
-	text-decoration: none;
-}
-a:visited {
-	color: #1111aa;
-	text-decoration: none;
-}
-a:active {
-	color: #111133;
-	text-decoration: none;
-}
diff --git a/doc/upload.sh b/doc/upload.sh
deleted file mode 100755
index 039c8e7..0000000
--- a/doc/upload.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-rsync -r -R -L -v -l -C -p --exclude=*.py --exclude=*.sh --exclude=digikam.xml --exclude=Thumbs.db --exclude=thumbs.db --exclude=*.in --rsh=ssh ./* mcfletch at shell.sourceforge.net:~/starpy/doc/
-ssh mcfletch at shell.sourceforge.net "/usr/local/bin/sfgrp starpy < ~/mvstarpysite.sh"
diff --git a/error.py b/error.py
deleted file mode 100644
index f3dfa3c..0000000
--- a/error.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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.
-
-"""Collection of StarPy-specific error classes"""
-
-class AMICommandFailure(Exception):
-    """AMI Command failure of some description"""
-
-class AGICommandFailure(Exception):
-    """AGI Command failure of some description"""
-
-class MenuFinished(Exception):
-    """Base class for reporting non-standard exits (i.e. not a choice) from a menu"""
-
-class MenuExit(MenuFinished):
-    """User exited from the menu voluntarily"""
-
-class MenuTimeout(MenuFinished):
-    """User didn't complete selection from menu in reasonable time period"""
-
-class MenuUnexpectedOption(MenuFinished):
-    """Somehow the user managed to select an option that doesn't exist?"""
diff --git a/examples/__init__.py b/examples/__init__.py
deleted file mode 100644
index d11c21c..0000000
--- a/examples/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Example applications for usage of StarPy with Asterisk"""
diff --git a/examples/amicommand.py b/examples/amicommand.py
deleted file mode 100644
index 232eb52..0000000
--- a/examples/amicommand.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#! /usr/bin/env python
-"""Test/sample to call "show database" command
-"""
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication, menu
-import os, logging, pprint, time
-
-log = logging.getLogger( 'callduration' )
-APPLICATION = utilapplication.UtilApplication()
-
-def main():
-    def onConnect( ami ):
-        def onResult( result ):
-            print 'Result', result
-            return ami.logoff()
-        def onError( reason ):
-            print reason.getTraceback()
-            return reason
-        def onFinished( result ):
-            reactor.stop()
-        df = ami.command( 'database show' )
-        df.addCallbacks( onResult, onError )
-        df.addCallbacks( onFinished, onFinished )
-        return df
-    amiDF = APPLICATION.amiSpecifier.login(
-    ).addCallback( onConnect )
-
-if __name__ == "__main__":
-    logging.basicConfig()
-    manager.log.setLevel( logging.DEBUG )
-    reactor.callWhenRunning( main )
-    reactor.run()
diff --git a/examples/autosurvey/extensions.conf b/examples/autosurvey/extensions.conf
deleted file mode 100644
index 4eb01dd..0000000
--- a/examples/autosurvey/extensions.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-; Extensions to allow the autosurvey example application
-; to run on the system... include into your extensions.conf
-; with a line like:
-; #include /home/mcfletch/pylive/starpy/examples/autosurvey/extensions.conf
-
-[survey]
-exten => _X.,1,Answer()
-exten => _X.,2,AGI(agi://localhost)
-exten => _X.,3,Hangup()
diff --git a/examples/autosurvey/frontend.py b/examples/autosurvey/frontend.py
deleted file mode 100644
index da50378..0000000
--- a/examples/autosurvey/frontend.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""Simple HTTP Server using twisted.web2"""
-from nevow import rend, appserver, inevow, tags, loaders
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication
-from basicproperty import common, basic, propertied, weak
-import os, logging, pprint, time
-
-log = logging.getLogger( 'autosurvey' )
-
-class Application( utilapplication.UtilApplication ):
-	"""Services provided at the application level"""
-	surveys = common.DictionaryProperty(
-		"surveys", """Set of surveys indexed by survey/extension number""",
-	)
-
-
-
-class Survey( propertied.Propertied ):
-	"""Models a single survey to be completed"""
-	surveyId = common.IntegerProperty(
-		"surveyId", """Unique identifier for this survey""",
-	)
-	owner = basic.BasicProperty(
-		"owner", """Owner's phone number to which to connect""",
-	)
-	questions = common.ListProperty(
-		"questions", """Set of questions which make up the survey""",
-	)
-	YOU_CURRENTLY_HAVE = 'vm-youhave'
-	QUESTIONS_IN_YOUR_SURVEY = 'vm-messages'
-	QUESTION_IN_YOUR_SURVEY = 'vm-message'
-	TO_LISTEN_TO_SURVEY_QUESTION = 'to-listen-to-it'
-	TO_RECORD_A_NEW_SURVEY_QUESTION = 'to-rerecord-it'
-	TO_FINISH_SURVEY_SETUP = 'vm-helpexit'
-	def setupSurvey( self, agi ):
-		"""AGI application to allow the user to set up the survey
-		
-		Screen 1:
-			You have # questions.
-			To listen to a question, press the number of the question.
-			To record a new question, press pound.
-			To finish setup, press star.
-		"""
-		seq = fastagi.InSequence( )
-		seq.append( agi.wait, 2 )
-		base = """You currently have %s question%s.
-		To listen to a question press the number of the question.
-		To record a new question, press pound.
-		To finish survey setup, press star.
-		"""%(
-			len(self.questions),
-			['','s'][len(self.questions)==1],
-		)
-		if len(base) != 1:
-			base += 's'
-		base = " ".join(base.split())
-		seq.append( agi.execute, 'Festival', base )
-		seq.append( agi.finish, )
-		return seq()
-		seq.append( agi.streamFile, self.YOU_CURRENTLY_HAVE )
-		seq.append( agi.sayNumber, len(self.questions))
-		if len(self.questions) == 1:
-			seq.append( agi.streamFile, self.QUESTION_IN_YOUR_SURVEY )
-		else:
-			seq.append( agi.streamFile, self.QUESTIONS_IN_YOUR_SURVEY )
-		seq.append( agi.streamFile, self.TO_LISTEN_TO_SURVEY_QUESTION )
-		seq.append( agi.streamFile, self.TO_RECORD_A_NEW_SURVEY_QUESTION )
-		seq.append( agi.streamFile, self.TO_FINISH_SURVEY_SETUP )
-		seq.append( agi.finish, )
-		return seq()
-	def newQuestionId( self ):
-		"""Return a new, unique, question id"""
-		import random, sys
-		bad = True
-		while bad:
-			bad = False
-			id = random.randint(0,sys.maxint)
-			for question in self.questions:
-				if id == question.__dict__.get('questionId'):
-					bad = True
-		return id
-class Question( propertied.Propertied ):
-	survey = weak.WeakProperty(
-		"survey", """Our survey object""",
-	)
-	questionId = common.IntegerProperty(
-		"questionId", """Unique identifier for our question""",
-		defaultFunction = lambda prop,client: client.survey.newQuestionId(),
-	)
-	def recordQuestion( self, agi, number=None ):
-		"""Record a question (number)"""
-		return agi.recordFile( 
-			'%s.%s'%(self.survey.surveyId,self.questionId),
-			'gsm',
-			'#*',
-			timeout=60,
-			beep = True,
-			silence=5,
-		).addCallback( 
-			self.onRecorded, agi=agi
-		).addErrback(self.onRecordAborted, agi=agi )
-	def onRecorded( self, result, agi ):
-		"""Handle recording of the question"""
-		
-
-def getManagerAPI( username, password, server='127.0.0.1', port=5038 ):
-	"""Retrieve a logged-in manager API"""
-
-class SurveySetup(rend.Page):
-	"""Page displaying the survey setup"""
-	addSlash = True
-	docFactory = loaders.htmlfile( 'index.html' )
-
-class RecordFunction( rend.Page ):
-	"""Page/application to record survey via call to user"""
-	def renderHTTP( self, ctx ):
-		"""Process rendering of the request"""
-		# process request parameters...
-		request = inevow.IRequest( ctx )
-		# XXX sanitise and check value...
-		channel = 'SIP/%s'%( request.args['ownerName'][0], )
-		
-		df = APPLICATION.amiSpecifier.login()
-		def onLogin( ami ):
-			# Note that the connect comes in *before* the originate returns,
-			# so we need to wait for the call before we even send it...
-			userConnectDF = APPLICATION.waitForCallOn( '23', timeout=15 )
-			APPLICATION.surveys['23'] = survey = Survey()
-			userConnectDF.addCallback( 
-				survey.setupSurvey, 
-			)
-			def onComplete( result ):
-				return ami.logoff()
-			ami.originate(# don't wait for this to complete...
-				# XXX handle case where the originate fails differently
-				# from the case where we just don't get a connection?
-				channel,
-				APPLICATION.agiSpecifier.context,
-				'23',
-				'1',
-				timeout=14,
-			).addCallbacks( onComplete, onComplete )
-			return userConnectDF
-		return df.addCallback( onLogin )
-
-
-
-
-def main():
-	"""Create the web-site"""
-	s = SurveySetup()
-	s.putChild( 'record', RecordFunction() )
-	site = appserver.NevowSite(s)
-	webServer = internet.TCPServer(8080, site)
-	webServer.startService()
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	log.setLevel( logging.DEBUG )
-	manager.log.setLevel( logging.DEBUG )
-	fastagi.log.setLevel( logging.DEBUG )
-	APPLICATION = Application()
-	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
-	from twisted.internet import reactor
-	reactor.callWhenRunning( main )
-	reactor.run()
diff --git a/examples/autosurvey/index.html b/examples/autosurvey/index.html
deleted file mode 100644
index 9708cc8..0000000
--- a/examples/autosurvey/index.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html>
-<head>
-  <meta content="text/html; charset=ISO-8859-1"
- http-equiv="content-type">
-  <title>Autosurvey Demo Application</title>
-</head>
-<body>
-<h1>Autosurvey Demo Application</h1>
-<p>This demonstration shows how to use StarPy to construct a simple
-automated phone-survey application for use in polling group members
-regarding decisions which need to be made.</p>
-<p>Features:</p>
-<ul>
-  <li>Enter set of phone numbers or SIP addresses to contact</li>
-  <li>The owner of the survey is called</li>
-  <ul>
-    <li>Owner can record the options</li>
-    <li>Owner can view/listen to the options from the web-form</li>
-  </ul>
-  <li>Each user is called and presented with the survey</li>
-  <li>Results of the survey can be viewed on the web-form<br>
-  </li>
-</ul>
-<h2>Setup<br>
-</h2>
-<p>Enter your phone number here:</p>
-<form enctype="multipart/form-data" method="post" action="/record"
- name="record">
-<p><input name="ownerName" value="mike"><input
- name="recordOptions" value="Record Survey" type="submit"></p>
-</form>
-<p>The survey server will call you to record the survey options...</p>
-<h3>Introduction</h3>
-<h3>Options</h3>
-<p>Enter the participant's phone numbers here:</p>
-<p><textarea cols="30" rows="4" name="participants"></textarea></p>
-<h2>Results</h2>
-</body>
-</html>
diff --git a/examples/calldurationcallback.py b/examples/calldurationcallback.py
deleted file mode 100644
index e9c8292..0000000
--- a/examples/calldurationcallback.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#! /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, utilapplication, 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()
diff --git a/examples/calldurationextensions.conf b/examples/calldurationextensions.conf
deleted file mode 100644
index 7db4894..0000000
--- a/examples/calldurationextensions.conf
+++ /dev/null
@@ -1,20 +0,0 @@
-; Extensions to allow the autosurvey example application
-; to run on the system... include into your extensions.conf
-; with a line like:
-; #include /home/mcfletch/pylive/starpy/examples/calldurationextensions.conf
-
-; You need to Goto(callduration,s,1) for those calls for which you want to have
-; callduration support for
-
-[regulardial]
-exten => s,1,Dial(SIP/3333 at testout)
-exten => s,2,Hangup()
-
-[callduration]
-exten => s,1,Answer()
-exten => s,2,AGI(agi://localhost:4576)
-exten => s,3,Goto(regulardial,s,1)
-
-exten => _X.,1,Answer()
-exten => _X.,2,AGI(agi://localhost:4576)
-exten => _X.,3,Hangup()
diff --git a/examples/connecttoivr.py b/examples/connecttoivr.py
deleted file mode 100644
index 24dcf63..0000000
--- a/examples/connecttoivr.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""Example script to generate a call to connect a remote channel to an IVR"""
-from starpy import manager
-from twisted.internet import reactor
-import sys, logging
-
-def main( channel = 'sip/20035 at aci.on.ca', connectTo=('outgoing','s','1') ):
-	f = manager.AMIFactory(sys.argv[1], sys.argv[2])
-	df = f.login()
-	def onLogin( protocol ):
-		"""On Login, attempt to originate the call"""
-		context, extension, priority = connectTo
-		df = protocol.originate(
-			channel,
-			context,extension,priority,
-		)
-		def onFinished( result ):
-			df = protocol.logoff()
-			def onLogoff( result ):
-				reactor.stop()
-			return df.addCallbacks( onLogoff, onLogoff )
-		def onFailure( reason ):
-			print reason.getTraceback()
-			return reason 
-		df.addErrback( onFailure )
-		df.addCallbacks( onFinished, onFinished )
-		return df 
-	def onFailure( reason ):
-		"""Unable to log in!"""
-		print reason.getTraceback()
-		reactor.stop()
-	df.addCallbacks( onLogin, onFailure )
-	return df
-
-if __name__ == "__main__":
-	manager.log.setLevel( logging.DEBUG )
-	logging.basicConfig()
-	reactor.callWhenRunning( main )
-	reactor.run()
diff --git a/examples/connecttoivrapp.py b/examples/connecttoivrapp.py
deleted file mode 100644
index d398b20..0000000
--- a/examples/connecttoivrapp.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""Example script to generate a call to connect a remote channel to an IVR
-
-This version of the script uses the utilapplication framework and is
-pared down for presentation on a series of slides
-"""
-from starpy import manager, utilapplication
-from twisted.internet import reactor
-import sys, logging
-APPLICATION = utilapplication.UtilApplication()
-
-def main( channel = 'sip/4167290048 at testout', connectTo=('outgoing','s','1') ):
-	df = APPLICATION.amiSpecifier.login()
-	def onLogin( protocol ):
-		"""We've logged into the manager, generate a call and log off"""
-		context, extension, priority = connectTo
-		df = protocol.originate(
-			channel,
-			context,extension,priority,
-		)
-		def onFinished( result ):
-			return protocol.logoff()
-		df.addCallbacks( onFinished, onFinished )
-		return df 
-	def onFailure( reason ):
-		print reason.getTraceback()
-	def onFinished( result ):
-		reactor.stop()
-	df.addCallbacks( 
-		onLogin, onFailure 
-	).addCallbacks( onFinished, onFinished )
-	return df
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	reactor.callWhenRunning( main )
-	reactor.run()
diff --git a/examples/fastagisetvariable.py b/examples/fastagisetvariable.py
deleted file mode 100644
index 3ad2041..0000000
--- a/examples/fastagisetvariable.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#! /usr/bin/env python
-"""Try to set a FastAGI variable"""
-from twisted.internet import reactor
-from starpy import fastagi, utilapplication
-import logging, time
-
-log = logging.getLogger( 'hellofastagi' )
-
-def testFunction( agi ):
-	"""Demonstrate simplistic use of the AGI interface with sequence of actions"""
-	log.debug( 'testFunction' )
-	def setX( ):
-		return agi.setVariable( 'this"toset', 'That"2set' )
-	def getX( result ):
-		return agi.getVariable( 'this"toset' )
-	def onX( value ):
-		print 'Retrieved value', value 
-		reactor.stop()
-	return setX().addCallback( getX ).addCallbacks( onX, onX )
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.DEBUG )
-	APPLICATION = utilapplication.UtilApplication()
-	APPLICATION.handleCallsFor( 's', testFunction )
-	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
-	reactor.run()
diff --git a/examples/getvariable.py b/examples/getvariable.py
deleted file mode 100644
index c8d5498..0000000
--- a/examples/getvariable.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#! /usr/bin/env python
-"""Demonstrate usage of getVariable on the agi interface...
-"""
-from twisted.internet import reactor
-from starpy import fastagi, utilapplication
-import logging, time, pprint
-
-log = logging.getLogger( 'hellofastagi' )
-
-def envVars( agi ):
-	"""Print out channel variables for display"""
-	vars = [
-		x.split( ' -- ' )[0].strip() 
-		for x in agi.getVariable.__doc__.splitlines()
-		if len(x.split( ' -- ' )) == 2
-	]
-	for var in vars:
-		yield var 
-
-def printVar( result, agi, vars ):
-	"""Print out the variables produced by envVars"""
-	def doPrint( result, var ):
-		print '%r -- %r'%( var, result )
-	def notAvailable( reason, var ):
-		print '%r -- UNDEFINED'%( var, )
-	try:
-		var = vars.next()
-	except StopIteration, err:
-		return None
-	else:
-		return agi.getVariable( var ).addCallback( doPrint, var ).addErrback(
-			notAvailable, var,
-		).addCallback(
-			printVar, agi, vars,
-		)
-	
-
-def testFunction( agi ):
-	"""Print out known AGI variables"""
-	log.debug( 'testFunction' )
-	print 'AGI Variables'
-	pprint.pprint( agi.variables )
-	print 'Channel Variables'
-	sequence = fastagi.InSequence()
-	sequence.append( printVar, None, agi, envVars(agi) )
-	sequence.append( agi.finish )
-	def onFailure( reason ):
-		log.error( "Failure: %s", reason.getTraceback())
-		agi.finish()
-	return sequence().addErrback( onFailure )
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	#fastagi.log.setLevel( logging.DEBUG )
-	APPLICATION = utilapplication.UtilApplication()
-	APPLICATION.handleCallsFor( 's', testFunction )
-	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
-	reactor.run()
diff --git a/examples/hellofastagi.py b/examples/hellofastagi.py
deleted file mode 100644
index 1434dad..0000000
--- a/examples/hellofastagi.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#! /usr/bin/env python
-"""Simple FastAGI server using starpy"""
-from twisted.internet import reactor
-from starpy import fastagi
-import logging, time
-
-log = logging.getLogger( 'hellofastagi' )
-
-def testFunction( agi ):
-	"""Demonstrate simplistic use of the AGI interface with sequence of actions"""
-	log.debug( 'testFunction' )
-	sequence = fastagi.InSequence()
-	sequence.append( agi.sayDateTime, time.time() )
-	sequence.append( agi.finish )
-	def onFailure( reason ):
-		log.error( "Failure: %s", reason.getTraceback())
-		agi.finish()
-	return sequence().addErrback( onFailure )
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.DEBUG )
-	f = fastagi.FastAGIFactory(testFunction)
-	reactor.listenTCP(4573, f, 50, '127.0.0.1') # only binding on local interface
-	reactor.run()
diff --git a/examples/hellofastagiapp.py b/examples/hellofastagiapp.py
deleted file mode 100644
index 9ec9fb4..0000000
--- a/examples/hellofastagiapp.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#! /usr/bin/env python
-"""FastAGI server using starpy and the utility application framework
-
-This is basically identical to hellofastagi, save that it uses the application
-framework to allow for configuration-file-based setup of the AGI service.
-"""
-from twisted.internet import reactor
-from starpy import fastagi, utilapplication
-import logging, time
-
-log = logging.getLogger( 'hellofastagi' )
-
-def testFunction( agi ):
-	"""Demonstrate simplistic use of the AGI interface with sequence of actions"""
-	log.debug( 'testFunction' )
-	sequence = fastagi.InSequence()
-	sequence.append( agi.sayDateTime, time.time() )
-	sequence.append( agi.finish )
-	def onFailure( reason ):
-		log.error( "Failure: %s", reason.getTraceback())
-		agi.finish()
-	return sequence().addErrback( onFailure )
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.DEBUG )
-	APPLICATION = utilapplication.UtilApplication()
-	APPLICATION.handleCallsFor( 's', testFunction )
-	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
-	reactor.run()
diff --git a/examples/menutest.py b/examples/menutest.py
deleted file mode 100644
index 18b5f27..0000000
--- a/examples/menutest.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#! /usr/bin/env python
-"""Sample application to test the menuing utility classes"""
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication, menu, error
-import os, logging, pprint, time
-
-log = logging.getLogger( 'menutest' )
-
-mainMenu = menu.Menu(
-	prompt = '/home/mcfletch/starpydemo/soundfiles/menutest-toplevel',
-	#prompt = 'houston',
-	textPrompt = '''Top level of the menu test example
-	
-	Pressing Star will exit this menu at any time.
-	Options zero and pound will exit with those options selected.
-	Option one will start a submenu.
-	Option two will start a digit-collecting sub-menu.
-	We'll tell you if you make an invalid selection here.''',
-	options = [
-		menu.Option( option='0' ),
-		menu.Option( option='#' ),
-		menu.ExitOn( option='*' ),
-		menu.SubMenu( 
-			option='1',
-			menu = menu.Menu(
-				prompt = '/home/mcfletch/starpydemo/soundfiles/menutest-secondlevel',
-				#prompt = 'atlantic',
-				textPrompt = '''A second-level menu in the menu test example
-				
-				Pressing Star will exit this menu at any time.
-				Options zero and pound will exit the whole menu with those options selected.
-				We won't tell you if you make an invalid selection here.
-				''',
-				tellInvalid = False, # don't report incorrect selections
-				options = [
-					menu.Option( option='0' ),
-					menu.Option( option='#' ),
-					menu.ExitOn( option='*' ),
-				],
-			),
-		),
-		menu.SubMenu(
-			option='2',
-			menu = menu.CollectDigits(
-				textPrompt = '''Digit collection example,
-				Please enter three to 5 digits.
-				''',
-				soundFile = '/home/mcfletch/starpydemo/soundfiles/menutest-digits',
-				#soundFile = 'extension',
-				maxDigits = 5,
-				minDigits = 3,
-			),
-		),
-	],
-)
-
-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""" )
-		def onComplete( result ):
-			log.info( """Final result: %r""", result )
-			agi.finish()
-		return mainMenu( agi ).addCallbacks( onComplete, onComplete )
-
-APPLICATION = Application()
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	log.setLevel( logging.DEBUG )
-	#manager.log.setLevel( logging.DEBUG )
-	fastagi.log.setLevel( logging.DEBUG )
-	menu.log.setLevel( logging.DEBUG )
-	APPLICATION.handleCallsFor( 's', APPLICATION.onS )
-	APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
-	from twisted.internet import reactor
-	reactor.run()
diff --git a/examples/menutestextensions.conf b/examples/menutestextensions.conf
deleted file mode 100644
index 7d218db..0000000
--- a/examples/menutestextensions.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-; Extensions to allow the menutest example application
-; to run on the system... include into your extensions.conf
-; with a line like:
-; #include /home/mcfletch/pylive/starpy/examples/menutestextensions.conf
-
-[menutest]
-exten => s,1,Answer()
-exten => s,2,Wait(1)
-exten => s,3,AGI(agi://localhost:4575)
-exten => s,4,Hangup()
diff --git a/examples/priexhaustion.py b/examples/priexhaustion.py
deleted file mode 100644
index 09d6d5f..0000000
--- a/examples/priexhaustion.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#! /usr/bin/env python
-"""Sample application to watch for PRI exhaustion
-
-This script watches for events on the AMI interface, tracking the identity of
-open channels in order to track how many channels are being used.  This would 
-be used to send messages to an administrator when network capacity is being 
-approached.
-
-Similarly, you could watch for spare capacity on the network and use that 
-to decide whether to allow low-priority calls, such as peering framework or
-free-world-dialup calls to go through.
-"""
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication, menu
-import os, logging, pprint, time
-from basicproperty import common, propertied, basic
-
-log = logging.getLogger( 'priexhaustion' )
-log.setLevel( logging.INFO )
-
-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 )
-	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 ) )
-
-APPLICATION = utilapplication.UtilApplication()
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	#log.setLevel( logging.DEBUG )
-	#manager.log.setLevel( logging.DEBUG )
-	#fastagi.log.setLevel( logging.DEBUG )
-	tracker = ChannelTracker()
-	reactor.callWhenRunning( tracker.main )
-	reactor.run()
diff --git a/examples/priexhaustionbare.py b/examples/priexhaustionbare.py
deleted file mode 100644
index 21875c6..0000000
--- a/examples/priexhaustionbare.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#! /usr/bin/env python
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication, menu
-import os, logging, pprint, time
-from basicproperty import common, propertied, basic
-
-log = logging.getLogger( 'priexhaustion' )
-log.setLevel( logging.INFO )
-
-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 )
-	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 = 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"""
-		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 )
-	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 ) )
-
-APPLICATION = utilapplication.UtilApplication()
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	tracker = ChannelTracker()
-	reactor.callWhenRunning( tracker.main )
-	reactor.run()
diff --git a/examples/readingdigits.py b/examples/readingdigits.py
deleted file mode 100644
index 093f1be..0000000
--- a/examples/readingdigits.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#! /usr/bin/env python
-"""Read digits from the user in various ways..."""
-from twisted.internet import reactor, defer
-from starpy import fastagi, error
-import logging, time
-
-log = logging.getLogger( 'hellofastagi' )
-
-class DialPlan( object ):
-	"""Stupid little application to report how many times it's been accessed"""
-	def __init__( self ):
-		self.count = 0
-	def __call__( self, agi ):
-		"""Store the AGI instance for later usage, kick off our operations"""
-		self.agi = agi 
-		return self.start()
-	def start( self ):
-		"""Begin the dial-plan-like operations"""
-		return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )
-	def answerFailure( self, reason ):
-		"""Deal with a failure to answer"""
-		log.warn( 
-			"""Unable to answer channel %r: %s""", 
-			self.agi.variables['agi_channel'], reason.getTraceback(),
-		)
-		self.agi.finish()
-	def onAnswered( self, resultLine ):
-		"""We've managed to answer the channel, yay!"""
-		self.count += 1
-		return self.agi.wait( 2.0 ).addCallback( self.onWaited )
-	def onWaited( self, result ):
-		"""We've finished waiting, tell the user the number"""
-		return self.agi.sayNumber( self.count, '*' ).addErrback(
-			self.onNumberFailed,
-		).addCallbacks(
-			self.onFinished, self.onFinished,
-		)
-	def onFinished( self, resultLine ):
-		"""We said the number correctly, hang up on the user"""
-		return self.agi.finish()
-	def onNumberFailed( self, reason ):
-		"""We were unable to read the number to the user"""
-		log.warn( 
-			"""Unable to read number to user on channel %r: %s""",
-			self.agi.variables['agi_channel'], reason.getTraceback(),
-		)
-		
-	def onHangupFailure( self, reason ):
-		"""Failed trying to hang up"""
-		log.warn( 
-			"""Unable to hang up channel %r: %s""", 
-			self.agi.variables['agi_channel'], reason.getTraceback(),
-		)
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.DEBUG )
-	f = fastagi.FastAGIFactory(DialPlan())
-	reactor.listenTCP(4573, f, 50, '127.0.0.1') # only binding on local interface
-	reactor.run()
diff --git a/examples/timestamp.py b/examples/timestamp.py
deleted file mode 100644
index e66e832..0000000
--- a/examples/timestamp.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#! /usr/bin/env python
-"""Provide a trivial date-and-time service"""
-from twisted.internet import reactor
-from starpy import fastagi
-import logging, time
-
-log = logging.getLogger( 'dateandtime' )
-
-def testFunction( agi ):
-	"""Give time for some time a bit in the future"""
-	log.debug( 'testFunction' )
-	df = agi.streamFile( 'at-tone-time-exactly' )
-	def onFailed( reason ):
-		log.error( "Failure: %s", reason.getTraceback())
-		return None
-	def cleanup( result ):
-		agi.finish()
-		return result
-	def onSaid( resultLine ):
-		"""Having introduced, actually read the time"""
-		t = time.time()
-		t2 = t+20.0
-		df = agi.sayDateTime( t2, format='HM' )
-		def onDateFinished( resultLine ):
-			# now need to sleep until .5 seconds before the time 
-			df = agi.wait( t2-.5-time.time() )
-			def onDoBeep( result ):
-				df = agi.streamFile( 'beep' )
-				return df
-			return df.addCallback( onDoBeep )
-		return df.addCallback( onDateFinished )
-	return df.addCallback( 
-		onSaid 
-	).addErrback( 
-		onFailed 
-	).addCallbacks(
-		cleanup, cleanup,
-	)
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.INFO )
-	f = fastagi.FastAGIFactory(testFunction)
-	reactor.listenTCP(4574, f, 50, '127.0.0.1') # only binding on local interface
-	reactor.run()
diff --git a/examples/timestampapp.py b/examples/timestampapp.py
deleted file mode 100644
index b766ead..0000000
--- a/examples/timestampapp.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#! /usr/bin/env python
-"""Provide a trivial date-and-time service"""
-from twisted.internet import reactor
-from starpy import fastagi, utilapplication
-import logging, time
-
-log = logging.getLogger( 'dateandtime' )
-
-def testFunction( agi ):
-	"""Give time for some time a bit in the future"""
-	log.debug( 'testFunction' )
-	df = agi.streamFile( 'at-tone-time-exactly' )
-	def onFailed( reason ):
-		log.error( "Failure: %s", reason.getTraceback())
-		return None
-	def cleanup( result ):
-		agi.finish()
-		return result
-	def onSaid( resultLine ):
-		"""Having introduced, actually read the time"""
-		t = time.time()
-		t2 = t+7.0
-		df = agi.sayDateTime( t2, format='HMS' )
-		def onDateFinished( resultLine ):
-			# now need to sleep until .05 seconds before the time 
-			df = agi.wait( t2-.05-time.time() )
-			def onDoBeep( result ):
-				df = agi.streamFile( 'beep' )
-				return df
-			def waitTwo( result ):
-				return agi.streamFile( 'thank-you-for-calling' )
-			return df.addCallback( onDoBeep ).addCallback( waitTwo )
-		return df.addCallback( onDateFinished )
-	return df.addCallback( 
-		onSaid 
-	).addErrback( 
-		onFailed 
-	).addCallbacks(
-		cleanup, cleanup,
-	)
-
-if __name__ == "__main__":
-	logging.basicConfig()
-	fastagi.log.setLevel( logging.INFO )
-	APPLICATION = utilapplication.UtilApplication()
-	reactor.callWhenRunning( APPLICATION.agiSpecifier.run, testFunction )
-	reactor.run()
diff --git a/fastagi.py b/fastagi.py
deleted file mode 100644
index 731bee1..0000000
--- a/fastagi.py
+++ /dev/null
@@ -1,949 +0,0 @@
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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.
-
-"""Asterisk FastAGI server for use from the dialplan
-
-You use an asterisk FastAGI like this from extensions.conf:
-
-    exten => 1000,3,AGI(agi://127.0.0.1:4573,arg1,arg2)
-
-Where 127.0.0.1 is the server and 4573 is the port on which
-the server is listening.
-
-Module defines a standard Python logging module log 'FastAGI'
-"""
-from twisted.internet import protocol, reactor, defer
-from twisted.internet import error as tw_error
-from twisted.protocols import basic
-import socket
-import logging
-import time
-from starpy import error
-
-log = logging.getLogger('FastAGI')
-
-FAILURE_CODE = -1
-
-class FastAGIProtocol(basic.LineOnlyReceiver):
-    """Protocol for the interfacing with the Asterisk FastAGI application
-
-    Attributes:
-
-        variables -- for  connected protocol, the set of variables passed
-            during initialisation, keys are all-lower-case, set of variables
-            returned for an Asterisk 1.2.1 installation on Gentoo on a locally
-            connected channel:
-
-                agi_network = 'yes'
-                agi_request = 'agi://localhost'
-                agi_channel = 'SIP/mike-ccca'
-                agi_language = 'en'
-                agi_type = 'SIP'
-                agi_uniqueid = '1139871605.0'
-                agi_callerid = 'mike'
-                agi_calleridname = 'Mike Fletcher'
-                agi_callingpres = '0'
-                agi_callingani2 = '0'
-                agi_callington = '0'
-                agi_callingtns = '0'
-                agi_dnid = '1'
-                agi_rdnis = 'unknown'
-                agi_context = 'testing'
-                agi_extension = '1'
-                agi_priority = '1'
-                agi_enhanced = '0.0'
-                agi_accountcode = ''
-
-        # Internal:
-        readingVariables -- whether the instance is still in initialising by
-            reading the setup variables from the connection
-        messageCache -- stores incoming variables
-        pendingMessages -- set of outstanding messages for which we expect
-            replies
-        lostConnectionDeferred -- deferred firing when the connection is lost
-        delimiter -- uses bald newline instead of carriage-return-newline
-
-    XXX Lots of problems with data-escaping, no docs on how to escape special
-        characters that I can see...
-    """
-    readingVariables = False
-    lostConnectionDeferred = None
-    delimiter = '\n'
-
-    def __init__(self, *args, **named):
-        """Initialise the AMIProtocol, arguments are ignored"""
-        self.messageCache = []
-        self.variables = {}
-        self.pendingMessages = []
-
-    def connectionMade(self):
-        """(Internal) Handle incoming connection (new AGI request)
-
-        Initiates read of the initial attributes passed by the server
-        """
-        log.info("New Connection")
-        self.readingVariables = True
-
-    def connectionLost(self, reason):
-        """(Internal) Handle loss of the connection (remote hangup)"""
-        log.info("""Connection terminated""")
-        try:
-            for df in self.pendingMessages:
-                df.errback(tw_error.ConnectionDone("""FastAGI connection terminated"""))
-        finally:
-            if self.lostConnectionDeferred:
-                self.lostConnectionDeferred.errback(reason)
-            del self.pendingMessages[:]
-
-    def onClose(self):
-        """Return a deferred which will fire when the connection is lost"""
-        if not self.lostConnectionDeferred:
-            self.lostConnectionDeferred = defer.Deferred()
-        return self.lostConnectionDeferred
-
-    def lineReceived(self, line):
-        """(Internal) Handle Twisted's report of an incoming line from the manager"""
-        log.debug('Line In: %r', line)
-        if self.readingVariables:
-            if not line.strip():
-                self.readingVariables = False
-                self.factory.mainFunction(self)
-            else:
-                try:
-                    key,value = line.split(':', 1)
-                    value = value[1:].rstrip('\n').rstrip('\r')
-                except ValueError, err:
-                    log.error("""Invalid variable line: %r""", line)
-                else:
-                    self.variables[ key.lower() ] = value
-                    log.debug("""%s = %r""", key, value)
-        else:
-            try:
-                df = self.pendingMessages.pop(0)
-            except IndexError, err:
-                log.warn("""Line received without pending deferred: %r""", line)
-            else:
-                if line.startswith('200'):
-                    line = line[4:]
-                    if line.lower().startswith('result='):
-                        line = line[7:]
-                    df.callback(line)
-                else:
-                    # XXX parse out the error code
-                    try:
-                        errCode, line = line.split(' ', 1)
-                        errCode = int(errCode)
-                    except ValueError,err:
-                        errCode = 500
-                    df.errback(error.AGICommandFailure(errCode, line))
-
-    def sendCommand(self, commandString):
-        """(Internal) Send the given command to the other side"""
-        log.info("Send Command: %r", commandString)
-        commandString = commandString.rstrip('\n').rstrip('\r')
-        df = defer.Deferred()
-        self.pendingMessages.append(df)
-        self.sendLine(commandString)
-        return df
-
-    def checkFailure(self, result, failure='-1'):
-        """(Internal) Check for a failure-code, raise error if == result"""
-        # result code may have trailing information...
-        try:
-            resultInt,line = result.split(' ',1)
-        except ValueError, err:
-            resultInt = result
-        if resultInt.strip() == failure:
-            raise error.AGICommandFailure(FAILURE_CODE, result)
-        return result
-
-    def resultAsInt(self, result):
-        """(Internal) Convert result to an integer value"""
-        try:
-            return int(result.strip())
-        except ValueError, err:
-            raise error.AGICommandFailure(FAILURE_CODE, result)
-
-    def secondResultItem(self, result):
-        """(Internal) Retrieve the second item on the result-line"""
-        return result.split(' ',1)[1]
-
-    def resultPlusTimeoutFlag(self, resultLine):
-        """(Internal) Result followed by optional flag declaring timeout"""
-        try:
-            digits, timeout = resultLine.split(' ',1)
-            return digits.strip(), True
-        except ValueError, err:
-            return resultLine.strip(), False
-
-    def dateAsSeconds(self, date):
-        """(Internal) Convert date to asterisk-compatible format"""
-        if hasattr(date, 'timetuple'):
-            # XXX values seem to be off here...
-            date = time.mktime(date.timetuple())
-        elif isinstance(date, time.struct_time):
-            date = time.mktime(date)
-        return date
-
-    def onRecordingComplete(self, resultLine):
-        """(Internal) Handle putative success, watch for failure-on-load problems"""
-        try:
-            digit,exitType,endposStuff = resultLine.split(' ', 2)
-        except ValueError, err:
-            pass
-        else:
-            digit = int(digit)
-            exitType = exitType.strip('()')
-            endposStuff = endposStuff.strip()
-            if endposStuff.startswith('endpos='):
-                endpos = int(endposStuff[7:].strip())
-                return digit, exitType, endpos
-        raise ValueError("""Unexpected result on streaming completion: %r"""%(resultLine))
-
-    def onStreamingComplete(self,resultLine, skipMS=0):
-        """(Internal) Handle putative success, watch for failure-on-load problems"""
-        try:
-            digit,endposStuff = resultLine.split(' ', 1)
-        except ValueError, err:
-            pass
-        else:
-            digit = int(digit)
-            endposStuff = endposStuff.strip()
-            if endposStuff.startswith('endpos='):
-                endpos = int(endposStuff[7:].strip())
-                if endpos == skipMS:
-                    # "likely" an error according to the wiki, we'll raise an error...
-                    raise error.AGICommandFailure(FAILURE_CODE, """End position %s == original position, result code %s"""%(
-                        endpos, digit
-                    ))
-                return digit, endpos
-        raise ValueError("""Unexpected result on streaming completion: %r"""%(resultLine))
-
-    def jumpOnError(self, reason, difference=100, forErrors=None):
-        """On error, jump to original priority+100
-
-        This is intended to be registered as an errBack on a deferred for
-        an end-user application.  It performs the Asterisk-standard-ish
-        jump-on-failure operation, jumping to new priority of
-        priority+difference.  It also forces return to the same context and
-        extension, in case some other piece of code has changed those.
-
-        difference -- priority jump to execute
-        forErrors -- if specified, a tuple of error classes to which this
-            particular jump is limited (i.e. only errors of this type will
-            generate a jump & disconnect)
-
-        returns deferred from the InSequence of operations required to reset
-        the address...
-        """
-        if forErrors:
-            if not isinstance(forErrors, (tuple,list)):
-                forErrors = (forErrors,)
-            reason.trap(*forErrors)
-        sequence = InSequence()
-        sequence.append(self.setContext, self.variables['agi_context'])
-        sequence.append(self.setExtension, self.variables['agi_extension'])
-        sequence.append(self.setPriority, int(self.variables['agi_priority'])+difference)
-        sequence.append(self.finish)
-        return sequence()
-
-    # End-user API
-    def finish(self):
-        """Finish the AGI "script" (drop connection)
-
-        This command simply drops the connection to the Asterisk server,
-        which the FastAGI protocol interprets as a successful termination.
-
-        Note: There *should* be a mechanism for sending a "result" code,
-        but I haven't found any documentation for it.
-        """
-        self.transport.loseConnection()
-
-    def answer(self):
-        """Answer the channel (go off-hook)
-
-        Returns deferred integer response code
-        """
-        return self.sendCommand("ANSWER").addCallback(
-            self.checkFailure
-        ).addCallback(self.resultAsInt)
-
-    def channelStatus(self, channel=None):
-        """Retrieve the current channel's status
-
-        Result integers (from the wiki):
-            0 Channel is down and available
-            1 Channel is down, but reserved
-            2 Channel is off hook
-            3 Digits (or equivalent) have been dialed
-            4 Line is ringing
-            5 Remote end is ringing
-            6 Line is up
-            7 Line is busy
-
-        Returns deferred integer result code
-
-        This could be used to decide if we can forward the channel to a given
-        user, or whether we need to shunt them off somewhere else.
-        """
-        if channel:
-            command = 'CHANNEL STATUS "%s"'%(channel)
-        else:
-            command = "CHANNEL STATUS"
-        return self.sendCommand(command).addCallback(
-            self.checkFailure,
-        ).addCallback(self.resultAsInt)
-
-    def controlStreamFile(
-            self, filename, escapeDigits,
-            skipMS=0, ffChar='*', rewChar='#', pauseChar=None,
-         ):
-        """Playback specified file with ability to be controlled by user
-
-        filename -- filename to play (on the asterisk server)
-            (don't use file-type extension!)
-        escapeDigits -- if provided,
-        skipMS -- number of milliseconds to skip on FF/REW
-        ffChar -- if provided, the set of chars that fast-forward
-        rewChar -- if provided, the set of chars that rewind
-        pauseChar -- if provided, the set of chars that pause playback
-
-        returns deferred (digit,endpos) on success, or errors on failure,
-            note that digit will be 0 if no digit was pressed AFAICS
-        """
-        command = 'CONTROL STREAM FILE "%s" %r %s %r %r'%(
-            filename, escapeDigits, skipMS, ffChar, rewChar
-        )
-        if pauseChar:
-            command += ' %r'%(pauseChar)
-
-        return self.sendCommand(command).addCallback(self.checkFailure)
-
-    def databaseDel(self, family, key):
-        """Delete the given key from the database
-
-        Returns deferred integer result code
-        """
-        command = 'DATABASE DEL "%s" "%s"'%(family, key)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='0',
-        ).addCallback(self.resultAsInt)
-
-    def databaseDeltree(self, family, keyTree=None):
-        """Delete an entire family or a tree within a family from database
-
-        Returns deferred integer result code
-        """
-        command = 'DATABASE DELTREE "%s"'%(family,)
-        if keyTree:
-            command += ' "%s"'%(keytree,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='0',
-        ).addCallback(self.resultAsInt)
-
-    def databaseGet(self, family, key):
-        """Retrieve value of the given key from database
-
-        Returns deferred string value for the key
-        """
-        command = 'DATABASE GET "%s" "%s"'%(family,key)
-        def returnValue(resultLine):
-            # get the second item without the brackets...
-            return resultLine[1:-1]
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='0',
-        ).addCallback(self.secondResultItem).addCallback(returnValue)
-
-    def databaseSet(self, family, key, value):
-        """Set value of the given key to database
-
-        a.k.a databasePut on the asterisk side
-
-        Returns deferred integer result code
-        """
-        command = 'DATABASE PUT "%s" "%s" "%s"'%(family,key, value)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='0',
-        ).addCallback(self.resultAsInt)
-    databasePut = databaseSet
-
-    def execute(self, application, *options, **kwargs):
-        """Execute a dialplan application with given options
-
-        Note: asterisk calls this "exec", which is Python keyword
-
-        comma_delimiter -- Use new style comma delimiter for diaplan
-        application arguments.  Asterisk uses pipes in 1.4 and older and
-        prefers commas in 1.6 and up.  Pass comma_delimiter=True to avoid
-        warnings from Asterisk 1.6 and up.
-
-        Returns deferred string result for the application, which
-        may have failed, result values are application dependant.
-        """
-        command = '''EXEC "%s"'''%(application)
-        if options:
-            if kwargs.pop('comma_delimiter', False) is True:
-                delimiter = ","
-            else:
-                delimiter = "|"
-
-            command += ' "%s"'%(
-                delimiter.join([
-                    str(x) for x in options
-                ])
-            )
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-2',
-        )
-
-    def getData(self, filename, timeout=2.000, maxDigits=None):
-        """Playback file, collecting up to maxDigits or waiting up to timeout
-
-        filename -- filename without extension to play
-        timeout -- timeout in seconds (Asterisk uses milliseconds)
-        maxDigits -- maximum number of digits to collect
-
-        returns deferred (str(digits), bool(timedOut))
-        """
-        timeout *= 1000
-        command = '''GET DATA "%s" %s'''%(filename, timeout)
-        if maxDigits is not None:
-            command = ' '.join([command, str(maxDigits)])
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultPlusTimeoutFlag)
-
-    def getOption(self, filename, escapeDigits, timeout=None):
-        """Playback file, collect 1 digit or timeout (return 0)
-
-        filename -- filename to play
-        escapeDigits -- digits which cancel playback/recording
-        timeout -- timeout in seconds (Asterisk uses milliseconds)
-
-        returns (chr(option) or '' on timeout, endpos)
-        """
-        command = '''GET OPTION "%s" %r'''%(filename,escapeDigits)
-        if timeout is not None:
-            timeout *= 1000
-            command += ' %s'%(timeout,)
-        def charFirst((c,position)):
-            if not c: # returns 0 on timeout
-                c = ''
-            else:
-                c = chr(c)
-            return c,position
-        return self.sendCommand(command).addCallback(
-            self.checkFailure,
-        ).addCallback(
-            self.onStreamingComplete
-        ).addCallback(charFirst)
-
-    def getVariable(self, variable):
-        """Retrieve the given channel variable
-
-        From the wiki, variables of interest:
-
-            ACCOUNTCODE -- Account code, if specified
-            ANSWEREDTIME -- Time call was answered
-            BLINDTRANSFER -- Active SIP channel that dialed the number.
-                This will return the SIP Channel that dialed the number when
-                doing blind transfers
-            CALLERID -- Current Caller ID (name and number) # deprecated?
-            CALLINGPRES -- PRI Call ID Presentation variable for incoming calls
-            CHANNEL -- Current channel name
-            CONTEXT -- Current context name
-            DATETIME -- Current datetime in format: DDMMYYYY-HH:MM:SS
-            DIALEDPEERNAME -- Name of called party (Broken)
-            DIALEDPEERNUMBER -- Number of the called party (Broken)
-            DIALEDTIME -- Time number was dialed
-            DIALSTATUS -- Status of the call
-            DNID -- Dialed Number Identifier (limited apparently)
-            EPOCH -- UNIX-style epoch-based time (seconds since 1 Jan 1970)
-            EXTEN -- Current extension
-            HANGUPCAUSE -- Last hangup return code on a Zap channel connected
-                to a PRI interface
-            INVALID_EXTEN -- Extension asked for when redirected to the i
-                (invalid) extension
-            LANGUAGE -- The current language setting. See Asterisk
-                multi-language
-            MEETMESECS -- Number of seconds user participated in a MeetMe
-                conference
-            PRIORITY -- Current priority
-            RDNIS -- The current redirecting DNIS, Caller ID that redirected
-                the call. Limitations apply.
-            SIPDOMAIN -- SIP destination domain of an inbound call
-                (if appropriate)
-            SIP_CODEC -- Used to set the SIP codec for a call (apparently
-                broken in Ver 1.0.1, ok in Ver. 1.0.3 & 1.0.4, not sure about
-                1.0.2)
-            SIPCALLID -- SIP dialog Call-ID: header
-            SIPUSERAGENT -- SIP user agent header (remote agent)
-            TIMESTAMP -- Current datetime in the format: YYYYMMDD-HHMMSS
-            TXTCIDNAME -- Result of application TXTCIDName
-            UNIQUEID -- Current call unique identifier
-            TOUCH_MONITOR -- Used for "one touch record" (see features.conf,
-                and wW dial flags). If is set on either side of the call then
-                that var contains the app_args for app_monitor otherwise the
-                default of WAV||m is used
-
-        Returns deferred string value for the key
-        """
-        def stripBrackets(value):
-            return value.strip()[1:-1]
-        command = '''GET VARIABLE "%s"'''%(variable,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='0',
-        ).addCallback(self.secondResultItem).addCallback(stripBrackets)
-
-    def hangup(self, channel=None):
-        """Cause the server to hang up on the channel
-
-        Returns deferred integer response code
-
-        Note: This command just doesn't seem to work with Asterisk 1.2.1,
-        connected channels just remain connected.
-        """
-        command = "HANGUP"
-        if channel is not None:
-            command += ' "%s"'%(channel)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def noop(self, message=None):
-        """Send a null operation to the server.  Any message sent
-        will be printed to the CLI.
-
-        Returns deferred integer response code
-        """
-        command = "NOOP"
-        if message is not None: command += ' "%s"' % message
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def playback(self, filename, doAnswer=1):
-        """Playback specified file in foreground
-
-        filename -- filename to play
-        doAnswer -- whether to:
-                -1: skip playback if the channel is not answered
-                 0: playback the sound file without answering first
-                 1: answer the channel before playback, if not yet answered
-
-        Note: this just wraps the execute method to issue
-        a PLAYBACK command.
-
-        Returns deferred integer response code
-        """
-        try:
-            option = { -1:'skip', 0:'noanswer', 1:'answer' }[ doAnswer ]
-        except KeyError:
-            raise TypeError, "doAnswer accepts values -1, 0, 1 only (%s given)" % doAnswer
-        command = 'PLAYBACK "%s"' %(filename,)
-        if option:
-            command += ' "%s"' %(option,)
-        return self.execute(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def receiveChar(self, timeout=None):
-        """Receive a single text char on text-supporting channels (rare)
-
-        timeout -- timeout in seconds (Asterisk uses milliseconds)
-
-        returns deferred (char, bool(timeout))
-        """
-        command = '''RECEIVE CHAR'''
-        if timeout is not None:
-            timeout *= 1000
-            command += ' %s'%(timeout,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultPlusTimeoutFlag)
-
-    def receiveText(self, timeout=None):
-        """Receive text until timeout
-
-        timeout -- timeout in seconds (Asterisk uses milliseconds)
-
-        Returns deferred string response value (unaltered)
-        """
-        command = '''RECEIVE TEXT'''
-        if timeout is not None:
-            timeout *= 1000
-            command += ' %s'%(timeout,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        )
-
-    def recordFile(
-            self, filename, format, escapeDigits, timeout=-1,
-            offsetSamples=None, beep=True, silence=None,
-        ):
-        """Record channel to given filename until escapeDigits or silence
-
-        filename -- filename on the server to which to save
-        format -- encoding format in which to save data
-        escapeDigits -- digits which end recording
-        timeout -- maximum time to record in seconds, -1 gives infinite
-            (Asterisk uses milliseconds)
-        offsetSamples -- move into file this number of samples before recording?
-            XXX check semantics here.
-        beep -- if true, play a Beep on channel to indicate start of recording
-        silence -- if specified, silence duration to trigger end of recording
-
-        returns deferred (str(code/digits), typeOfExit, endpos)
-
-        Where known typeOfExits include:
-            hangup, code='0'
-            dtmf, code=digits-pressed
-            timeout, code='0'
-        """
-        timeout *= 1000
-        command = '''RECORD FILE "%s" "%s" %s %s'''%(
-            filename, format, escapeDigits, timeout,
-        )
-        if offsetSamples is not None:
-            command += ' %s'%(offsetSamples,)
-        if beep:
-            command += ' BEEP'
-        if silence is not None:
-            command += ' s=%s'%(silence,)
-        def onResult(resultLine):
-            value, type, endpos = resultLine.split(' ')
-            type = type.strip()[1:-1]
-            endpos = int(endpos.split('=')[1])
-            return (value, type, endpos)
-        return self.sendCommand(command).addCallback(
-            self.onRecordingComplete
-        )
-
-    def sayXXX(self, baseCommand, value, escapeDigits=''):
-        """Underlying implementation for the common-api sayXXX functions"""
-        command = '%s %s %r'%(baseCommand, value, escapeDigits or '')
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def sayAlpha(self, string, escapeDigits=None):
-        """Spell out character string to the user until escapeDigits
-
-        returns deferred 0 or the digit pressed
-        """
-        string = "".join([x for x in string if x.isalnum()])
-        return self.sayXXX('SAY ALPHA', string, escapeDigits)
-
-    def sayDate(self, date, escapeDigits=None):
-        """Spell out the date (with somewhat unnatural form)
-
-        See sayDateTime with format 'ABdY' for a more natural reading
-
-        returns deferred 0 or digit-pressed as integer
-        """
-        return self.sayXXX('SAY DATE', self.dateAsSeconds(date), escapeDigits)
-
-    def sayDigits(self, number, escapeDigits=None):
-        """Spell out the number/string as a string of digits
-
-        returns deferred 0 or digit-pressed as integer
-        """
-        number = "".join([x for x in str(number) if x.isdigit()])
-        return self.sayXXX('SAY DIGITS', number, escapeDigits)
-
-    def sayNumber(self, number, escapeDigits=None):
-        """Say a number in natural form
-
-         returns deferred 0 or digit-pressed as integer
-        """
-        number = "".join([x for x in str(number) if x.isdigit()])
-        return self.sayXXX('SAY NUMBER', number, escapeDigits)
-
-    def sayPhonetic(self, string, escapeDigits=None):
-        """Say string using phonetics
-
-         returns deferred 0 or digit-pressed as integer
-        """
-        string = "".join([x for x in string if x.isalnum()])
-        return self.sayXXX('SAY PHONETIC', string, escapeDigits)
-
-    def sayTime(self, time, escapeDigits=None):
-        """Say string using phonetics
-
-         returns deferred 0 or digit-pressed as integer
-        """
-        return self.sayXXX('SAY TIME', self.dateAsSeconds(time), escapeDigits)
-
-    def sayDateTime(self, time, escapeDigits='', format=None, timezone=None):
-        """Say given date/time in given format until escapeDigits
-
-        time -- datetime or float-seconds-since-epoch
-        escapeDigits -- digits to cancel playback
-        format -- strftime-style format for the date to be read
-            'filename' -- filename of a soundfile (single ticks around the filename required)
-            A or a -- Day of week (Saturday, Sunday, ...)
-            B or b or h -- Month name (January, February, ...)
-            d or e -- numeric day of month (first, second, ..., thirty-first)
-            Y -- Year
-            I or l -- Hour, 12 hour clock
-            H -- Hour, 24 hour clock (single digit hours preceded by "oh")
-            k -- Hour, 24 hour clock (single digit hours NOT preceded by "oh")
-            M -- Minute
-            P or p -- AM or PM
-            Q -- "today", "yesterday" or ABdY (*note: not standard strftime value)
-            q -- "" (for today), "yesterday", weekday, or ABdY (*note: not standard strftime value)
-            R -- 24 hour time, including minute
-
-            Default format is "ABdY 'digits/at' IMp"
-        timezone -- optional timezone name from /usr/share/zoneinfo
-
-        returns deferred 0 or digit-pressed as integer
-        """
-        command = 'SAY DATETIME %s %r'%(self.dateAsSeconds(time),escapeDigits)
-        if format is not None:
-            command += ' %s'%(format,)
-            if timezone is not None:
-                command += ' %s'%(timezone,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def sendImage(self, filename):
-        """Send image on those channels which support sending images (rare)
-
-        returns deferred integer result code
-        """
-        command = 'SEND IMAGE "%s"'%(filename,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def sendText(self, text):
-        """Send text on text-supporting channels (rare)
-
-        returns deferred integer result code
-        """
-        command = "SEND TEXT %r"%(text)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.resultAsInt)
-
-    def setAutoHangup(self, time):
-        """Set channel to automatically hang up after time seconds
-
-        time -- time in seconds in the future to hang up...
-
-        returns deferred integer result code
-        """
-        command = """SET AUTOHANGUP %s"""%(time,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1', # docs don't show a failure case, actually
-        ).addCallback(self.resultAsInt)
-
-    def setCallerID(self, number):
-        """Set channel's caller ID to given number
-
-        returns deferred integer result code
-        """
-        command = "SET CALLERID %s"%(number)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def setContext(self, context):
-        """Move channel to given context (no error checking is performed)
-
-        returns deferred integer result code
-        """
-        command = """SET CONTEXT %s"""%(context,)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def setExtension(self, extension):
-        """Move channel to given extension (or 'i' if invalid) or drop if neither there
-
-        returns deferred integer result code
-        """
-        command = """SET EXTENSION %s"""%(extension,)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def setMusic(self, on=True, musicClass=None):
-        """Enable/disable and/or choose music class for channel's music-on-hold
-
-        returns deferred integer result code
-        """
-        command = """SET MUSIC %s"""%(['OFF','ON'][on],)
-        if musicClass is not None:
-            command += " %s"%(musicClass,)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def setPriority(self, priority):
-        """Move channel to given priority or drop if not there
-
-        returns deferred integer result code
-        """
-        command = """SET PRIORITY %s"""%(priority,)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def setVariable(self, variable, value):
-        """Set given channel variable to given value
-
-        variable -- the variable name passed to the server
-        value -- the variable value passed to the server, will have
-            any '"' characters removed in order to allow for " quoting
-            of the value.
-
-        returns deferred integer result code
-        """
-        value = '''"%s"'''%(str(value).replace('"', ''),)
-        command = 'SET VARIABLE "%s" "%s"'%(variable, value)
-        return self.sendCommand(command).addCallback(self.resultAsInt)
-
-    def streamFile(self, filename, escapeDigits="", offset=0):
-        """Stream given file until escapeDigits starting from offset
-
-        returns deferred (str(digit), int(endpos)) for playback
-
-        Note: streamFile is apparently unstable in AGI, may want to use
-        execute('PLAYBACK', ...) instead (according to the Wiki)
-        """
-        command = 'STREAM FILE "%s" %r'%(filename,escapeDigits)
-        if offset is not None:
-            command += ' %s'%(offset)
-        return  self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(self.onStreamingComplete, skipMS=offset)
-
-    def tddMode(self, on=True):
-        """Set TDD mode on the channel if possible (ZAP only ATM)
-
-        on -- ON (True), OFF (False) or MATE (None)
-
-        returns deferred integer result code
-        """
-        if on is True:
-            on = 'ON'
-        elif on is False:
-            on = 'OFF'
-        elif on is None:
-            on = 'MATE'
-        command = 'TDD MODE %s'%(on,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1', # failure
-        ).addCallback(
-            self.checkFailure, failure='0', # planned eventual failure case (not capable)
-        ).addCallback(
-            self.resultAsInt,
-        )
-
-    def verbose(self, message, level=None):
-        """Send a logging message to the asterisk console for debugging etc
-
-        message -- text to pass
-        level -- 1-4 denoting verbosity level
-
-        returns deferred integer result code
-        """
-        command = 'VERBOSE %r'%(message,)
-        if level is not None:
-            command += ' %s'%(level)
-        return self.sendCommand(command).addCallback(
-            self.resultAsInt,
-        )
-
-    def waitForDigit(self, timeout):
-        """Wait up to timeout seconds for single digit to be pressed
-
-        timeout -- timeout in seconds or -1 for infinite timeout
-            (Asterisk uses milliseconds)
-
-        returns deferred 0 on timeout or digit
-        """
-        timeout *= 1000
-        command = "WAIT FOR DIGIT %s"%(timeout,)
-        return self.sendCommand(command).addCallback(
-            self.checkFailure, failure='-1',
-        ).addCallback(
-            self.resultAsInt,
-        )
-
-    def wait(self, duration):
-        """Wait for X seconds (just a wrapper around callLater, doesn't talk to server)
-
-        returns deferred which fires some time after duration seconds have
-        passed
-        """
-        df = defer.Deferred()
-        reactor.callLater(duration, df.callback, 0)
-        return df
-
-
-class InSequence(object):
-    """Single-shot item creating a set of actions to run in sequence"""
-    def __init__(self):
-        self.actions = []
-        self.results = []
-        self.finalDF = None
-
-    def append(self, function, *args, **named):
-        """Append an action to the set of actions to process"""
-        self.actions.append((function, args, named))
-
-    def __call__(self):
-        """Return deferred that fires when we are finished processing all items"""
-        return self._doSequence()
-
-    def _doSequence(self):
-        """Return a deferred that does each action in sequence"""
-        finalDF = defer.Deferred()
-        self.onActionSuccess(None, finalDF=finalDF)
-        return finalDF
-
-    def recordResult(self, result):
-        """Record the result for later"""
-        self.results.append(result)
-        return result
-
-    def onActionSuccess(self, result, finalDF):
-        """Handle individual-action success"""
-        log.debug('onActionSuccess: %s', result)
-        if self.actions:
-            action = self.actions.pop(0)
-            log.debug('action %s', action)
-            df = defer.maybeDeferred(action[0], *action[1], **action[2])
-            df.addCallback(self.recordResult)
-            df.addCallback(self.onActionSuccess, finalDF=finalDF)
-            df.addErrback(self.onActionFailure, finalDF=finalDF)
-            return df
-        else:
-            finalDF.callback(self.results)
-
-    def onActionFailure(self, reason, finalDF):
-        """Handle individual-action failure"""
-        log.debug('onActionFailure')
-        reason.results = self.results
-        finalDF.errback(reason)
-
-
-class FastAGIFactory(protocol.Factory):
-    """Factory generating FastAGI server instances
-    """
-    protocol = FastAGIProtocol
-
-    def __init__(self, mainFunction):
-        """Initialise the factory
-
-        mainFunction -- function taking a connected FastAGIProtocol instance
-            this is the function that's run when the Asterisk server connects.
-        """
-        self.mainFunction = mainFunction
diff --git a/manager.py b/manager.py
deleted file mode 100644
index 6a3adb8..0000000
--- a/manager.py
+++ /dev/null
@@ -1,967 +0,0 @@
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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.
-
-"""Asterisk Manager Interface for the Twisted networking framework
-
-The Asterisk Manager Interface is a simple line-oriented protocol that allows
-for basic control of the channels active on a given Asterisk server.
-
-Module defines a standard Python logging module log 'AMI'
-"""
-
-from twisted.internet import protocol, reactor, defer
-from twisted.protocols import basic
-from twisted.internet import error as tw_error
-import socket, logging
-from starpy import error
-
-log = logging.getLogger('AMI')
-
-class AMIProtocol(basic.LineOnlyReceiver):
-    """Protocol for the interfacing with the Asterisk Manager Interface (AMI)
-
-    Provides most of the AMI Action interfaces.
-    Auto-generates ActionID fields for all calls.
-
-    Events and messages are passed around as simple dictionaries with
-    all-lowercase keys.  Values are case-sensitive.
-
-    XXX Want to allow for timeouts
-
-    Attributes:
-        count -- total count of messages sent from this protocol
-        hostName -- used along with count and ID to produce unique IDs
-        messageCache -- stores incoming message fragments from the manager
-        id -- An identifier for this instance
-    """
-    count = 0
-    amiVersion = None
-    id = None
-
-    def __init__(self, *args, **named):
-        """Initialise the AMIProtocol, arguments are ignored"""
-        self.messageCache = []
-        self.actionIDCallbacks = {}
-        self.eventTypeCallbacks = {}
-        self.hostName = socket.gethostname()
-
-    def registerEvent(self, event, function):
-        """Register callback for the given event-type
-
-        event -- string name for the event, None to match all events, or
-            a tuple of string names to match multiple events.
-
-            See http://www.voip-info.org/wiki/view/asterisk+manager+events
-            for list of events and the data they bear.  Includes:
-
-                Newchannel -- note that you can receive multiple Newchannel
-                    events for a single channel!
-                Hangup
-                Newexten
-                Newstate
-                Reload
-                Shutdown
-                ExtensionStatus
-                Rename
-                Newcallerid
-                Alarm
-                AlarmClear
-                Agentcallbacklogoff
-                Agentcallbacklogin
-                Agentlogin
-                Agentlogoff
-                MeetmeJoin
-                MeetmeLeave
-                MessageWaiting
-                Join
-                Leave
-                AgentCalled
-                ParkedCall
-                UnParkedCall
-                ParkedCalls
-                Cdr
-                ParkedCallsComplete
-                QueueParams
-                QueueMember
-
-            among other standard events.  Also includes user-defined events.
-        function -- function taking (protocol,event) as arguments or None
-            to deregister the current function.
-
-        Multiple functions may be registered for a given event
-        """
-        log.debug('Registering function %s to handle events of type %r', function, event)
-        if isinstance(event, (str,unicode,type(None))):
-            event = (event,)
-        for ev in event:
-            self.eventTypeCallbacks.setdefault(ev, []).append(function)
-
-    def deregisterEvent(self, event, function=None):
-        """Deregister callback for the given event-type
-
-        event -- event name (or names) to be deregistered, see registerEvent
-        function -- the function to be removed from the callbacks or None to
-            remove all callbacks for the event
-
-        returns success boolean
-        """
-        log.debug('Deregistering handler %s for events of type %r', function, event)
-        if isinstance(event, (str,unicode,type(None))):
-            event = (event,)
-        success = True
-        for ev in event:
-            try:
-                set = self.eventTypeCallbacks[ ev ]
-            except KeyError, err:
-                success = False
-            else:
-                try:
-                    while function in set:
-                        set.remove(function)
-                except (ValueError,KeyError), err:
-                    success = False
-                if not set or function is None:
-                    try:
-                        del self.eventTypeCallbacks[ ev ]
-                    except KeyError, err:
-                        success = False
-        return success
-
-    def lineReceived(self, line):
-        """Handle Twisted's report of an incoming line from the manager"""
-        log.debug('Line In: %r', line)
-        self.messageCache.append(line)
-        if not line.strip():
-            self.dispatchIncoming() # does dispatch and clears cache
-
-    def connectionMade(self):
-        """Handle connection to the AMI port (auto-login)
-
-        This is a Twisted customisation point, we use it to automatically
-        log into the connection we've just established.
-
-        XXX Should probably use proper Twisted-style credential negotiations
-        """
-        log.info('Connection Made')
-        df = self.login()
-
-        def onComplete(message):
-            """Check for success, errback or callback as appropriate"""
-            if not message['response'] == 'Success':
-                log.info('Login Failure: %s', message)
-                self.transport.loseConnection()
-                self.factory.loginDefer.errback(
-                    error.AMICommandFailure("""Unable to connect to manager""", message)
-                )
-            else:
-                # XXX messy here, would rather have the factory trigger its own
-                # callback...
-                log.info('Login Complete: %s', message)
-                self.factory.loginDefer.callback(
-                    self,
-                )
-
-        def onFailure(reason):
-            """Handle failure to connect (e.g. due to timeout)"""
-            log.info('Login Call Failure: %s', reason.getTraceback())
-            self.transport.loseConnection()
-            self.factory.loginDefer.errback(
-                reason
-            )
-        df.addCallbacks(onComplete, onFailure)
-
-    def connectionLost(self, reason):
-        """Connection lost, clean up callbacks"""
-        for key,callable in self.actionIDCallbacks.items():
-            try:
-                callable(tw_error.ConnectionDone("""FastAGI connection terminated"""))
-            except Exception, err:
-                log.error("""Failure during connectionLost for callable %s: %s""", callable, err)
-        self.actionIDCallbacks.clear()
-        self.eventTypeCallbacks.clear()
-    VERSION_PREFIX = 'Asterisk Call Manager'
-    END_DATA = '--END COMMAND--'
-
-    def dispatchIncoming(self):
-        """Dispatch any finished incoming events/messages"""
-        log.debug('Dispatch Incoming')
-        message = {}
-        while self.messageCache:
-            line = self.messageCache.pop(0)
-            line = line.strip()
-            if line:
-                if line.endswith(self.END_DATA):
-                    # multi-line command results...
-                    message.setdefault(' ', []).extend([
-                        l for l in line.split('\n') if (l and l!=self.END_DATA)
-                    ])
-                else:
-                    # regular line...
-                    if line.startswith(self.VERSION_PREFIX):
-                        self.amiVersion = line[len(self.VERSION_PREFIX)+1:].strip()
-                    else:
-                        try:
-                            key,value = line.split(':',1)
-                        except ValueError, err:
-                            # XXX data-safety issues, what prevents the VERSION_PREFIX from
-                            # showing up in a data-set?
-                            log.warn("""Improperly formatted line received and ignored: %r""", line)
-                        else:
-                            message[ key.lower().strip() ] = value.strip()
-        log.debug('Incoming Message: %s', message)
-        if message.has_key('actionid'):
-            key = message['actionid']
-            callback = self.actionIDCallbacks.get(key)
-            if callback:
-                try:
-                    callback(message)
-                except Exception, err:
-                    # XXX log failure here...
-                    pass
-        # otherwise is a monitor message or something we didn't send...
-        if message.has_key('event'):
-            self.dispatchEvent(message)
-
-    def dispatchEvent(self, event):
-        """Given an incoming event, dispatch to registered handlers"""
-        for key in (event['event'], None):
-            try:
-                handlers = self.eventTypeCallbacks[ key ]
-            except KeyError, err:
-                pass
-            else:
-                for handler in handlers:
-                    try:
-                        handler(self, event)
-                    except Exception, err:
-                        # would like the getException code here...
-                        log.error(
-                            'Exception in event handler %s on event %s: %s',
-                            handler, event, err
-                        )
-
-    def generateActionId(self):
-        """Generate a unique action ID
-
-        Assumes that hostName must be unique among all machines which talk
-        to a given AMI server.  With that is combined the memory location of
-        the protocol object (which should be machine-unique) and the count of
-        messages that this manager has created so far.
-
-        Generally speaking, you shouldn't need to know the action ID, as the
-        protocol handles the management of them automatically.
-        """
-        self.count += 1
-        return '%s-%s-%s'%(self.hostName, id(self), self.count)
-
-    def sendDeferred(self, message):
-        """Send with a single-callback deferred object
-
-        Returns deferred that fires when a response to this message is received
-        """
-        df = defer.Deferred()
-        actionid = self.sendMessage(message, df.callback)
-        df.addCallbacks(
-            self.cleanup, self.cleanup,
-            callbackArgs=(actionid,), errbackArgs=(actionid,)
-        )
-        return df
-
-    def cleanup(self, result, actionid):
-        """Cleanup callbacks on completion"""
-        try:
-            del self.actionIDCallbacks[actionid]
-        except KeyError, err:
-            pass
-        return result
-
-    def sendMessage(self, message, responseCallback=None):
-        """Send the message to the other side, return deferred for the result
-
-        returns the actionid for the message
-        """
-        message = dict([(k.lower(),v) for (k,v) in message.items()])
-        if not message.has_key('actionid'):
-            message['actionid'] = self.generateActionId()
-        if responseCallback:
-            self.actionIDCallbacks[message['actionid']] = responseCallback
-        log.debug("""MSG OUT: %s""", message)
-        for key,value in message.items():
-            self.sendLine('%s: %s'%(str(key.lower()), str(value)))
-        self.sendLine('')
-        return message['actionid']
-
-    def collectDeferred(self, message, stopEvent):
-        """Collect all responses to this message until stopEvent or error
-
-        returns deferred returning sequence of events/responses
-        """
-        df = defer.Deferred()
-        cache = []
-        def onEvent(event):
-            if event.get('response') == 'Error':
-                df.errback(error.AMICommandFailure(event))
-            elif event['event'] == stopEvent:
-                df.callback(cache)
-            else:
-                cache.append(event)
-        actionid = self.sendMessage(message, onEvent)
-        df.addCallbacks(
-            self.cleanup, self.cleanup,
-            callbackArgs=(actionid,), errbackArgs=(actionid,)
-        )
-        return df
-
-    def errorUnlessResponse(self, message, expected='Success'):
-        """Raise a AMICommandFailure error unless message['response'] == expected
-
-        If == expected, returns the message
-        """
-        if type(message) is dict and message['response'] != expected:
-            raise error.AMICommandFailure(message)
-        return message
-
-    ## End-user API
-    def absoluteTimeout(self, channel, timeout):
-        """Set timeout value for the given channel (in seconds)"""
-        message = {
-            'action' : 'absolutetimeout',
-            'timeout' : timeout,
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def agentLogoff(self, agent, soft):
-        """Logs off the specified agent for the queue system."""
-        if soft in (True, 'yes', 1):
-            soft='true'
-        else:
-            soft='false'
-        message = {
-            'Action' : 'AgentLogoff',
-            'Agent' : agent,
-            'Soft':soft
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def agents(self):
-        """Retrieve agents information"""
-        message = {
-            "action" : "agents"
-        }
-        return self.collectDeferred(message, "AgentsComplete")
-
-    def changeMonitor(self, channel, filename):
-        """Change the file to which the channel is to be recorded"""
-        message = {
-            'action' : 'changemonitor',
-            'channel' : channel,
-            'filename' : filename
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def command(self, command):
-        """Run asterisk console command, return deferred result for line of lines
-
-        returns deferred returning list of lines (strings) of the command
-        output.
-
-        See listCommands to see available commands
-        """
-        message = {
-            'action' : 'command',
-            'command' : command
-        }
-        df = self.sendDeferred(message)
-        df.addCallback(self.errorUnlessResponse, expected='Follows')
-
-        def onResult(message):
-            return message[' ']
-
-        return df.addCallback(onResult)
-
-    def dbDel(self, family, key):
-        """Delete key value in the AstDB database"""
-        message = {
-            'Action' : 'DBDel',
-            'Family' : family,
-            'Key' : key
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dbDelTree(self, family, key=None):
-        """Delete key value or key tree in the AstDB database"""
-        message = {
-            'Action' : 'DBDelTree',
-            'Family' : family
-        }
-        if key is not None:
-            message['Key'] = key
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dbGet(self, family, key):
-        """This action retrieves a value from the AstDB database"""
-        df = defer.Deferred()
-
-        def extractValue(ami, event):
-            value = event['val']
-            return df.callback(value)
-
-        message = {
-            'Action' : 'DBGet',
-            'family' : family,
-            'key' : key
-        }
-        self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-        self.registerEvent("DBGetResponse", extractValue)
-        return df
-
-    def dbPut(self, family, key, value):
-        """Sets a key value in the AstDB database"""
-        message = {
-            'Action' : 'DBPut',
-            'Family' : family,
-            'Key' : key,
-            'Val' : value
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def events(self, eventmask=False):
-        """Determine whether events are generated"""
-        if eventmask in ('off', False, 0):
-            eventmask = 'off'
-        elif eventmask in ('on', True, 1):
-            eventmask = 'on'
-        # otherwise is likely a type-mask
-        message = {
-            'action' : 'events',
-            'eventmask' : eventmask
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def extensionState(self, exten, context):
-        """This command reports the extension state for the given extension. If the extension has a hint, this will report the status of the device connected to the extension
-
-        The following are the possible extension states:
-
-        -2    Extension removed
-        -1    Extension hint not found
-         0    Idle
-         1    In use
-         2    Busy"""
-        message = {
-            'Action' : 'ExtensionState',
-            'Exten' : exten,
-            'Context' : context
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def getConfig(self, filename):
-        """Retrieves the data from an Asterisk configuration file"""
-        message = {
-            'Action' : 'GetConfig',
-            'filename' : filename
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def getVar(self, channel, variable):
-        """Retrieve the given variable from the channel"""
-
-        def extractVariable(message):
-            """When message comes in, extract the variable from it"""
-            if message.has_key(variable.lower()):
-                value = message[variable.lower()]
-            elif message.has_key('value'):
-                value = message['value']
-            else:
-                raise error.AMICommandFailure(message)
-            if value == '(null)':
-                value = None
-            return value
-
-        message = {
-            'action' : 'getvar',
-            'channel' : channel,
-            'variable' : variable
-        }
-        return self.sendDeferred(
-            message
-        ).addCallback(
-            self.errorUnlessResponse
-        ).addCallback(
-            extractVariable,
-        )
-
-    def hangup(self, channel):
-        """Tell channel to hang up"""
-        message = {
-            'action' : 'hangup',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def login(self):
-        """Log into the AMI interface (done automatically on connection)
-
-        Uses factory.username and factory.secret
-        """
-        self.id = self.factory.id
-        return self.sendDeferred({
-            'action' : 'login',
-            'username' : self.factory.username,
-            'secret' : self.factory.secret,
-        }).addCallback(self.errorUnlessResponse)
-
-    def listCommands(self):
-        """List the set of commands available
-
-        Returns a single message with each command-name as a key
-        """
-        message = {
-            'action' : 'listcommands'
-        }
-
-        def removeActionId(message):
-            try:
-                del message['actionid']
-            except KeyError, err:
-                pass
-            return message
-
-        return self.sendDeferred(message).addCallback(
-            self.errorUnlessResponse
-        ).addCallback(
-            removeActionId
-        )
-
-    def logoff(self):
-        """Log off from the manager instance"""
-        message = {
-            'action' : 'logoff'
-        }
-        return self.sendDeferred(message).addCallback(
-            self.errorUnlessResponse, expected = 'Goodbye',
-        )
-
-    def mailboxCount(self, mailbox):
-        """Get count of messages in the given mailbox"""
-        message = {
-            'action' : 'mailboxcount',
-            'mailbox' : mailbox
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def mailboxStatus(self, mailbox):
-        """Get status of given mailbox"""
-        message = {
-            'action' : 'mailboxstatus',
-            'mailbox' : mailbox
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def meetmeMute(self, meetme, usernum):
-        """Mute a user in a given meetme"""
-        message = {
-            'action' : 'MeetMeMute',
-            'meetme' : meetme,
-            'usernum' : usernum
-        }
-        return self.sendDeferred(message)
-
-    def meetmeUnmute(self, meetme, usernum):
-        """ Unmute a specified user in a given meetme"""
-        message = {
-            'action' : 'meetmeunmute',
-            'meetme' : meetme,
-            'usernum' : usernum
-        }
-        return self.sendDeferred(message)
-
-    def monitor(self, channel, file, format, mix):
-        """Record given channel to a file (or attempt to anyway)"""
-        message = {
-            'action' : 'monitor',
-            'channel' : channel,
-            'file' : file,
-            'format' : format,
-            'mix' : mix
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def originate(
-            self, channel, context=None, exten=None, priority=None,
-            timeout=None, callerid=None, account=None, application=None,
-            data=None, variable={}, async=False
-        ):
-        """Originate call to connect channel to given context/exten/priority
-
-        channel -- the outgoing channel to which will be dialed
-        context/exten/priority -- the dialplan coordinate to which to connect
-            the channel (i.e. where to start the called person)
-        timeout -- duration before timeout in seconds (note: not Asterisk standard!)
-        callerid -- callerid to display on the channel
-        account -- account to which the call belongs
-        application -- alternate application to Dial to use for outbound dial
-        data -- data to pass to application
-        variable -- variables associated to the call
-        async -- make the origination asynchronous
-        """
-        variable = '|'.join(["%s=%s" % (x[0], x[1]) for x in variable.items()])
-        message = dict([(k,v) for (k,v) in {
-            'action' : 'originate',
-            'channel' : channel,
-            'context' : context,
-            'exten' : exten,
-            'priority' : priority,
-            'timeout' : timeout,
-            'callerid' : callerid,
-            'account' : account,
-            'application' : application,
-            'data' : data,
-            'variable' : variable,
-            'async' : str(async)
-        }.items() if v is not None])
-        if message.has_key('timeout'):
-            message['timeout'] = message['timeout'] * 1000
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def park(self, channel, channel2, timeout):
-        """Park channel"""
-        message = {
-            'action' : 'park',
-            'channel' : channel,
-            'channel2' : channel2,
-            'timeout' : timeout
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def parkedCall(self):
-        """Check for a ParkedCall event"""
-        message = {
-            'action' : 'ParkedCall'
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def unParkedCall(self):
-        """Check for an UnParkedCall event """
-        message = {
-            'action' : 'UnParkedCall'
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def parkedCalls(self):
-        """Retrieve set of parked calls via multi-event callback"""
-        message = {
-            'action' : 'ParkedCalls'
-        }
-        return self.collectDeferred(message, 'ParkedCallsComplete')
-
-    def pauseMonitor(self,channel):
-        """Temporarily stop recording the channel"""
-        message = {
-            'action' : 'pausemonitor',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def ping(self):
-        """Check to see if the manager is alive..."""
-        message = {
-            'action' : 'ping'
-        }
-        if self.amiVersion == "1.0":
-            return self.sendDeferred(message).addCallback(
-                self.errorUnlessResponse, expected = 'Pong',
-            )
-        else:
-            return self.sendDeferred(message).addCallback(
-                self.errorUnlessResponse
-            )
-
-    def playDTMF(self, channel, digit):
-        """Play DTMF on a given channel"""
-        message = {
-            'action' : 'playdtmf',
-            'channel' : channel,
-            'digit' : digit
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def queueAdd(self, queue, interface, penalty=0, paused=True):
-        """Add given interface to named queue"""
-        if paused in (True, 'true', 1):
-            paused = 'true'
-        else:
-            paused = 'false'
-        message = {
-            'action' : 'queueadd',
-            'queue' : queue,
-            'interface' : interface,
-            'penalty' : penalty,
-            'paused' : paused
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def queuePause(self, queue, interface, paused = True):
-        if paused in (True,'true',1):
-            paused = 'true'
-        else:
-            paused = 'false'
-        message = {
-            'action' : 'queuepause',
-            'queue' : queue,
-            'interface' : interface,
-            'paused' : paused
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def queueRemove(self, queue, interface):
-        """Remove given interface from named queue"""
-        message = {
-            'action' : 'queueremove',
-            'queue' : queue,
-            'interface' : interface
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def queues(self):
-        """Retrieve information about active queues via multiple events"""
-        # XXX AMI returns improperly formatted lines so this doesn't work now.
-        message = {
-            'action' : 'queues'
-        }
-        #return self.collectDeferred(message, 'QueueStatusEnd')
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def queueStatus(self):
-        """Retrieve information about active queues via multiple events"""
-        message = {
-            'action' : 'queuestatus'
-        }
-        return self.collectDeferred(message, 'QueueStatusComplete')
-
-    def redirect(self, channel, context, exten, priority, extraChannel=None):
-        """Transfer channel(s) to given context/exten/priority"""
-        message = {
-            'action' : 'redirect',
-            'channel' : channel,
-            'context' : context,
-            'exten' : exten,
-            'priority' : priority,
-        }
-        if extraChannel is not None:
-            message['extrachannel'] = extraChannel
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def setCDRUserField(self, channel, userField, append=True):
-        """Set/add to a user field in the CDR for given channel"""
-        if append in (True, 'true', 1):
-            append = 'true'
-        else:
-            append = 'false'
-        message = {
-            'channel' : channel,
-            'userfield' : userField,
-            'append' : append,
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def setVar(self, channel, variable, value):
-        """Set channel variable to given value"""
-        message = {
-            'action' : 'setvar',
-            'channel' : channel,
-            'variable' : variable,
-            'value' : value
-        }
-        return self.sendDeferred(
-            message
-        ).addCallback(
-            self.errorUnlessResponse
-        )
-
-    def sipPeers(self):
-        """List all known sip peers"""
-        # XXX not available on my box...
-        message = {
-            'action' : 'sippeers'
-        }
-        return self.collectDeferred(message, 'PeerlistComplete')
-
-    def sipShowPeers(self, peer):
-        message = {
-            'action' : 'sipshowpeer',
-            'peer' : peer
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def status(self, channel=None):
-        """Retrieve status for the given (or all) channels via multi-event callback
-
-        channel -- channel name or None to retrieve all channels
-
-        returns deferred returning list of Status Events for each requested
-        channel
-        """
-        message = {
-            'action' : 'Status'
-        }
-        if channel:
-            message['channel'] = channel
-        return self.collectDeferred(message, 'StatusComplete')
-
-    def stopMonitor(self, channel):
-        """Stop monitoring the given channel"""
-        message = {
-            'action' : 'monitor',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def unpauseMonitor(self, channel):
-        """Resume recording a channel"""
-        message = {
-            'action' : 'unpausemonitor',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def updateConfig(self, srcfile, dstfile, reload, headers={}):
-        """Update a configuration file
-
-        headers should be a dictionary with the following keys
-        Action-XXXXXX
-        Cat-XXXXXX
-        Var-XXXXXX
-        Value-XXXXXX
-        Match-XXXXXX
-        """
-        message = {}
-        if reload in (True, 'yes', 1):
-            reload='yes'
-        else:
-            reload='no'
-        message = {
-            'action' : 'updateconfig',
-            'srcfilename' : srcfile,
-            'dstfilename' : dstfile,
-            'reload' : reload
-        }
-        for k, v in headers.items():
-            message[k] = v
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def userEvent(self, event, **headers):
-        """Sends an arbitrary event to the Asterisk Manager Interface."""
-        message = {
-            'Action' : 'UserEvent',
-            'userevent' : event
-        }
-        for i, j in headers.items():
-            message[i] = j
-        return self.sendMessage(message)
-
-    def waitEvent(self, timeout):
-        """Waits for an event to occur
-
-            After calling this action, Asterisk will send you a Success response as soon as another event is queued by the AMI"""
-        message = {
-            'action' : 'WaitEvent',
-            'timeout' : timeout
-        }
-        return self.collectDeferred(message, 'WaitEventComplete')
-
-    def dahdiDNDoff(self, channel):
-        """Toggles the do not disturb state on the specified DAHDI channel to off"""
-        messge = {
-            'action' : 'DAHDIDNDoff',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dahdiDNDon(self, channel):
-        """Toggles the do not disturb state on the specified DAHDI channel to on"""
-        messge = {
-            'action' : 'DAHDIDNDon',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dahdiDialOffhook(self, channel, number):
-        """Dials the specified number on the DAHDI channel while the phone is off-hook"""
-        message = {
-            'Action' : 'DAHDIDialOffhook',
-            'DAHDIChannel' : channel,
-            'Number' : number
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dahdiHangup(self, channel):
-        """Hangs up the specified DAHDI channel"""
-        message = {
-            'Action' : 'DAHDIHangup',
-            'DAHDIChannel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dahdiRestart(self, channel):
-        """Completly restarts the DAHDI channels, terminating any calls in progress"""
-        message = {
-            'Action' : 'DAHDIRestart',
-            'DAHDIChannel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-    def dahdiShowChannels(self):
-        """List all DAHDI channels"""
-        message = {
-            'action' : 'DAHDIShowChannels'
-        }
-        return self.collectDeferred(message, 'DAHDIShowChannelsComplete')
-
-    def dahdiTransfer(self, channel):
-        """Transfers DAHDI channel"""
-        message = {
-            'Action' : 'DAHDITransfer',
-            'channel' : channel
-        }
-        return self.sendDeferred(message).addCallback(self.errorUnlessResponse)
-
-
-class AMIFactory(protocol.ClientFactory):
-    """A factory for AMI protocols
-    """
-    protocol = AMIProtocol
-
-    def __init__(self, username, secret, id=None):
-        self.username = username
-        self.secret = secret
-        self.id = id
-
-    def login(self, ip='localhost', port=5038, timeout=5):
-        """Connect, returning our (singleton) protocol instance with login completed
-
-        XXX This is messy, we'd much rather have the factory able to create
-        large numbers of protocols simultaneously
-        """
-        self.loginDefer = defer.Deferred()
-        reactor.connectTCP(ip, port, self, timeout=timeout)
-        return self.loginDefer
-
-    def clientConnectionFailed(self, connector, reason):
-        """Connection failed, report to our callers"""
-        self.loginDefer.errback(reason)
-
diff --git a/menu.py b/menu.py
deleted file mode 100644
index 2004539..0000000
--- a/menu.py
+++ /dev/null
@@ -1,630 +0,0 @@
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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.
-
-"""IVR-based menuing system with retry, exit, and similar useful features
-
-You use the menuing system by instantiating Interaction and Option sub-classes
-as a tree of options that make up an IVR menu.  Calling the top-level menu
-produces a Deferred that fires with a list of [(Option,value),...] pairs,
-where Option is the thing chosen and value is the value entered by the user
-for choosing that option.
-
-When programming an IVR you will likely want to make Option sub-classes that
-are callable to accomplish the task indicated by the user.
-
-XXX allow for starting the menu system anywhere in the hierarchy
-XXX add the reject/accept menus to the CollectDigits (requires soundfiles
-in standard locations on the server, complicates install)
-"""
-from twisted.application import service, internet
-from twisted.internet import reactor, defer
-from starpy import manager, fastagi, utilapplication, error
-import os, logging, pprint, time
-from basicproperty import common, propertied, basic
-
-log = logging.getLogger( 'menu' )
-log.setLevel( logging.DEBUG )
-
-class Interaction( propertied.Propertied ):
-    """Base class for user-interaction operations"""
-    ALL_DIGITS = '0123456789*#'
-    timeout = common.FloatProperty(
-        "timeout", """Duration to wait for response before repeating message""",
-        defaultValue = 5,
-    )
-    maxRepetitions = common.IntegerProperty(
-        "maxRepetitions", """Maximum number of times to play before failure""",
-        defaultValue = 5,
-    )
-    onSuccess = basic.BasicProperty(
-        "onSuccess", """Optional callback for success with signature method( result, runner )""",
-    )
-    onFailure = basic.BasicProperty(
-        "onFailure", """Optional callback for failure with signature method( result, runner )""",
-    )
-    runnerClass = None
-    def __call__( self, agi, *args, **named ):
-        """Initiate AGI-based interaction with the user"""
-        return self.runnerClass( model=self,agi=agi )( *args, **named )
-
-class Runner( propertied.Propertied ):
-    """User's interaction with a given Interaction-type"""
-    agi = basic.BasicProperty(
-        "agi", """The AGI instance we use to communicate with the user""",
-    )
-    def defaultFinalDF( prop, client ):
-        """Produce the default finalDF with onSuccess/onFailure support"""
-        df = defer.Deferred()
-        model = client.model
-        if hasattr( model, 'onSuccess' ):
-            log.debug( 'register onSuccess: %s', model.onSuccess )
-            df.addCallback( model.onSuccess, runner=client )
-        if hasattr( model, 'onFailure' ):
-            log.debug( 'register onFailure: %s', model.onFailure )
-            df.addErrback( model.onFailure, runner=client )
-        return df
-    finalDF = basic.BasicProperty(
-        "finalDF", """Final deferred we will callback/errback on success/failure""",
-        defaultFunction = defaultFinalDF,
-    )
-    del defaultFinalDF
-
-    alreadyRepeated = common.IntegerProperty(
-        "alreadyRepeated", """Number of times we've repeated the message...""",
-        defaultValue = 0,
-    )
-    model = basic.BasicProperty(
-        "model", """The data-model that we are presenting to the user (e.g. Menu)""",
-    )
-    def returnResult( self, result ):
-        """Return result of deferred to our original caller"""
-        log.debug( 'returnResult: %s %s', self.model,result )
-        if not self.finalDF.called:
-            self.finalDF.debug = True
-            self.finalDF.callback( result )
-        else:
-            log.debug( 'finalDF already called, ignoring %s', result )
-        return result
-    def returnError( self, reason ):
-        """Return failure of deferred to our original caller"""
-        log.debug( 'returnError: %s', self.model )
-        if not isinstance( reason.value, error.MenuExit ):
-            log.warn( """Failure during menu: %s""", reason.getTraceback())
-        if not self.finalDF.called:
-            self.finalDF.debug = True
-            self.finalDF.errback( reason )
-        else:
-            log.debug( 'finalDF already called, ignoring %s', reason.getTraceback() )
-
-    def promptAsRunner( self, prompt ):
-        """Take set of prompt-compatible objects and produce a PromptRunner for them"""
-        realPrompt = []
-        for p in prompt:
-            if isinstance( p, (str,unicode)):
-                p = AudioPrompt( p )
-            elif isinstance( p, int ):
-                p = NumberPrompt( p )
-            elif not isinstance( p, Prompt ):
-                raise TypeError( """Unknown prompt element type on %r: %s"""%(
-                    p, p.__class__,
-                ))
-            realPrompt.append( p )
-        return PromptRunner(
-            elements = realPrompt,
-            escapeDigits = self.escapeDigits,
-            agi = self.agi,
-            timeout = self.model.timeout,
-        )
-
-class CollectDigitsRunner( Runner ):
-    """User's single interaction to enter a set of digits
-
-    Note: Asterisk is hard-coded to use # to exit the entry-mode...
-    """
-    def __call__( self, *args, **named ):
-        """Begin the AGI processing for the menu"""
-        self.readDigits()
-        return self.finalDF
-    def readDigits( self, result=None ):
-        """Begin process of reading digits from the user"""
-        soundFile = getattr( self.model, 'soundFile', None )
-        if soundFile:
-            # easiest possibility, just read out the file...
-            return self.agi.getData(
-                soundFile, timeout=self.model.timeout,
-                maxDigits = getattr( self.model, 'maxDigits', None ),
-            ).addCallback( self.onReadDigits ).addErrback( self.returnError )
-        else:
-            raise NotImplemented( """Haven't got non-soundfile menus working yet""" )
-
-        self.agi.getData( self.menu. filename, timeout=2.000, maxDigits=None )
-    def validEntry( self, digits ):
-        """Determine whether given digits are considered a "valid" entry"""
-        minDigits = getattr( self.model, 'minDigits', None )
-        if minDigits is not None:
-            if len(digits) < minDigits:
-                return False, 'Too few digits'
-        return True, None
-    def onReadDigits( self, (digits,timeout) ):
-        """Deal with succesful result from reading digits"""
-        log.info( """onReadDigits: %r, %s""", digits, timeout )
-        valid, reason = self.validEntry( digits )
-        if (not digits) and (not timeout):
-            # user pressed #
-            raise error.MenuExit(
-                self.model,
-                """User cancelled entry of digits""",
-            )
-        if not valid:
-            if self.model.tellInvalid:
-                # this should be a menu, letting the user decide to re-enter,
-                # or cancel entry
-                pass
-            self.alreadyRepeated += 1
-            if self.alreadyRepeated >= self.model.maxRepetitions:
-                log.warn( """User did not complete digit-entry for %s, timing out""", self.model )
-                raise error.MenuTimeout(
-                    self.model,
-                    """User did not finish digit-entry in %s passes of collection"""%(
-                        self.alreadyRepeated,
-                    )
-                )
-            return self.readDigits()
-        else:
-            # Yay, we got a valid response!
-            return self.returnResult( [(self, digits) ] )
-
-class CollectPasswordRunner( CollectDigitsRunner ):
-    """Password-runner, checks validity versus expected value"""
-    expected = common.StringLocaleProperty(
-        "expected", """The value expected/required from the user for this run""",
-    )
-    def __call__( self, expected, *args, **named ):
-        """Begin the AGI processing for the menu"""
-        self.expected = expected
-        return super( CollectPasswordRunner, self ).__call__( *args, **named )
-    def validEntry( self, digits ):
-        """Determine whether given digits are considered a "valid" entry"""
-        for digit in self.model.escapeDigits:
-            if digit in digits:
-                raise error.MenuExit(
-                    self.model,
-                    """User cancelled entry of password""",
-                )
-        if digits != self.expected:
-            return False, "Password doesn't match"
-        return True, None
-
-class CollectAudioRunner( Runner ):
-    """Audio-collection runner, records user audio to a file on the asterisk server"""
-    escapeDigits = common.StringLocaleProperty(
-        "escapeDigits", """Set of digits which escape from recording""",
-        defaultFunction = lambda prop,client: client.model.escapeDigits,
-        setDefaultOnGet = False,
-    )
-    def __call__( self, *args, **named ):
-        """Begin the AGI processing for the menu"""
-        self.readPrompt()
-        return self.finalDF
-    def readPrompt( self, result=None ):
-        """Begin process of reading audio from the user"""
-        if self.model.prompt:
-            # wants us to read a prompt to the user before recording...
-            runner = self.promptAsRunner( self.model.prompt )
-            runner.timeout = 0.1
-            return runner().addCallback( self.onReadPrompt ).addErrback( self.returnError )
-        else:
-            return self.collectAudio().addErrback( self.returnError )
-    def onReadPrompt( self, result ):
-        """We've finished reading the prompt to the user, check for escape"""
-        log.info( 'Finished reading prompt for collect audio: %r', result )
-        if result and result in self.escapeDigits:
-            raise error.MenuExit(
-                self.model,
-                """User cancelled entry of audio during prompt""",
-            )
-        else:
-            return self.collectAudio()
-    def collectAudio( self ):
-        """We're supposed to record audio from the user with our model's parameters"""
-        # XXX use a temporary file for recording the audio, then move to final destination
-        log.debug( 'collectAudio' )
-        if hasattr( self.model, 'temporaryFile' ):
-            filename = self.model.temporaryFile
-        else:
-            filename = self.model.filename
-        df = self.agi.recordFile(
-            filename=filename,
-            format=self.model.format,
-            escapeDigits=self.escapeDigits,
-            timeout=self.model.timeout,
-            offsetSamples=None,
-            beep=self.model.beep,
-            silence=self.model.silence,
-        ).addCallbacks(
-            self.onAudioCollected, self.onAudioCollectFail,
-        )
-        if hasattr( self.model, 'temporaryFile' ):
-            df.addCallback( self.moveToFinal )
-        return df
-    def onAudioCollected( self, result ):
-        """Process the results of collecting the audio"""
-        digits, typeOfExit, endpos = result
-        if typeOfExit in ('hangup','timeout'):
-            # expected common-case for recording...
-            return self.returnResult( (self,(digits,typeOfExit,endpos)) )
-        elif typeOfExit =='dtmf':
-            raise error.MenuExit(
-                self.model,
-                """User cancelled entry of audio""",
-            )
-        else:
-            raise ValueError( """Unrecognised recordFile results: (%s, %s %s)"""%(
-                digits, typeOfExit, endpos,
-            ))
-    def onAudioCollectFail( self, reason ):
-        """Process failure to record audio"""
-        log.error(
-            """Failure collecting audio for CollectAudio instance %s: %s""",
-            self.model, reason.getTraceback(),
-        )
-        return reason # re-raise the error...
-    def moveToFinal( self, result ):
-        """On succesful recording, move temporaryFile to final file"""
-        log.info(
-            'Moving recorded audio %r to final destination %r',
-            self.model.temporaryFile, self.model.filename
-        )
-        import os
-        try:
-            os.rename(
-                '%s.%s'%(self.model.temporaryFile,self.model.format),
-                '%s.%s'%(self.model.filename,self.model.format),
-            )
-        except (OSError, IOError), err:
-            log.error(
-                """Unable to move temporary recording file %r to target file %r: %s""",
-                self.model.temporaryFile, self.model.filename,
-                # XXX would like to use getException here...
-                err,
-            )
-            raise
-        return result
-
-
-class MenuRunner( Runner ):
-    """User's single interaction with a given menu"""
-    def defaultEscapeDigits( prop, client ):
-        """Return the default escape digits for the given client"""
-        if client.model.tellInvalid:
-            escapeDigits = client.model.ALL_DIGITS
-        else:
-            escapeDigits = "".join( [o.option for o in client.model.options] )
-        return escapeDigits
-    escapeDigits = common.StringLocaleProperty(
-        "escapeDigits", """Set of digits which escape from prompts to choose option""",
-        defaultFunction = defaultEscapeDigits,
-    )
-    del defaultEscapeDigits # clean up namespace
-
-    def __call__( self, *args, **named ):
-        """Begin the AGI processing for the menu"""
-        self.readMenu()
-        return self.finalDF
-    def readMenu( self, result=None ):
-        """Read our menu to the user"""
-        runner = self.promptAsRunner( self.model.prompt )
-        return runner().addCallback( self.onReadMenu ).addErrback( self.returnError )
-    def onReadMenu( self, pressed ):
-        """Deal with succesful result from reading menu"""
-        log.info( """onReadMenu: %r""", pressed )
-        if not pressed:
-            self.alreadyRepeated += 1
-            if self.alreadyRepeated >= self.model.maxRepetitions:
-                log.warn( """User did not complete menu selection for %s, timing out""", self.model )
-                if not self.finalDF.called:
-                    raise error.MenuTimeout(
-                        self.model,
-                        """User did not finish selection in %s passes of menu"""%(
-                            self.alreadyRepeated,
-                        )
-                    )
-                return None
-            return self.readMenu()
-        else:
-            # Yay, we got an escape-key pressed
-            for option in self.model.options:
-                if pressed in option.option:
-                    if callable( option ):
-                        # allow for chaining down into sub-menus and the like...
-                        # we return the result of calling the option via self.finalDF
-                        return defer.maybeDeferred( option, pressed, self ).addCallbacks(
-                            self.returnResult, self.returnError
-                        )
-                    elif hasattr(option, 'onSuccess' ):
-                        return defer.maybeDeferred( option.onSuccess, pressed, self ).addCallbacks(
-                            self.returnResult, self.returnError
-                        )
-                    else:
-                        return self.returnResult( [(option,pressed),] )
-            # but it wasn't anything we expected...
-            if not self.model.tellInvalid:
-                raise error.MenuUnexpectedOption(
-                    self.model, """User somehow selected %r, which isn't a recognised option?"""%(pressed,),
-                )
-            else:
-                return self.agi.getOption(
-                    self.model.INVALID_OPTION_FILE, self.escapeDigits,
-                    timeout=0,
-                ).addCallback( self.readMenu ).addErrback( self.returnError )
-
-class Menu( Interaction ):
-    """IVR-based menu, returns options selected by the user and keypresses
-
-    The Menu holds a collection of Option instances along with a prompt
-    which presents those options to the user.  The menu will attempt to
-    collect the user's selected option up to maxRepetitions times, playing
-    the prompt each time.
-
-    If tellInvalid is true, will allow any character being pressed to stop
-    the playback, and will tell the user if the pressed character is not
-    recognised.  Otherwise will simply ignore a pressed character which isn't
-    part of an Option object's 'option' property.
-
-    The menu will chain into callable Options, so that SubMenu and ExitOn can
-    be used to produce effects such as multi-level menus with options to
-    return to the parent menu level.
-
-    Returns [(option,char(pressedKey))...] for each level of menu explored
-    """
-    INVALID_OPTION_FILE = 'pm-invalid-option'
-    prompt = common.ListProperty(
-        "prompt", """(Set of) prompts to run, can be Prompt instances or filenames
-
-        Used by the PromptRunner to produce prompt selections
-        """,
-    )
-    textPrompt = common.StringProperty(
-        "textPrompt", """Textual prompt describing the option""",
-    )
-    options = common.ListProperty(
-        "options", """Set of options the user may select""",
-    )
-    tellInvalid = common.IntegerProperty(
-        "tellInvalid", """Whether to tell the user that their selection is unrecognised""",
-        defaultValue = True,
-    )
-    runnerClass = MenuRunner
-class Option( propertied.Propertied ):
-    """A single menu option that can be chosen by the user"""
-    option = common.StringLocaleProperty(
-        "option", """Keypad values which select this option (list of characters)""",
-    )
-class SubMenu( Option ):
-    """A menu-holding option, just forwards call to the held menu"""
-    menu = basic.BasicProperty(
-        "menu", """The sub-menu we are presenting to the user""",
-    )
-    def __call__( self, pressed, parent ):
-        """Get result from the sub-menu, add ourselves into the result"""
-        def onResult( result ):
-            log.debug( """Child menu %s result: %s""", self.menu, result )
-            result.insert( 0, (self,pressed) )
-            return result
-        def onFailure( reason ):
-            """Trap voluntary exit and re-start the parent menu"""
-            reason.trap( error.MenuExit )
-            log.warn( """Restarting parent menu: %s""", parent )
-            return parent.model( parent.agi )
-        return self.menu( parent.agi ).addCallbacks( onResult, onFailure )
-class ExitOn( Option ):
-    """An option which exits from the current menu level"""
-    def __call__( self, pressed, parent ):
-        """Raise a MenuExit error"""
-        raise error.MenuExit(
-            self, pressed, parent,  """User selected ExitOn option""",
-        )
-
-class CollectDigits( Interaction ):
-    """Collects some number of digits (e.g. an extension) from user"""
-    soundFile = common.StringLocaleProperty(
-        "soundFile", """File (name) for the pre-recorded blurb""",
-    )
-    textPrompt = common.StringProperty(
-        "textPrompt", """Textual prompt describing the option""",
-    )
-    readBack = common.BooleanProperty(
-        "readBack", """Whether to read the entered value back to the user""",
-        defaultValue = False,
-    )
-    minDigits = common.IntegerProperty(
-        "minDigits", """Minimum number of digits to collect (only restricted if specified)""",
-    )
-    maxDigits = common.IntegerProperty(
-        "maxDigits", """Maximum number of digits to collect (only restricted if specified)""",
-    )
-    runnerClass = CollectDigitsRunner
-    tellInvalid = common.IntegerProperty(
-        "tellInvalid", """Whether to tell the user that their selection is unrecognised""",
-        defaultValue = True,
-    )
-
-class CollectPassword( CollectDigits ):
-    """Collects some number of password digits from the user"""
-    runnerClass = CollectPasswordRunner
-    escapeDigits = common.StringLocaleProperty(
-        "escapeDigits", """Set of digits which escape from password entry""",
-        defaultValue = '',
-    )
-    soundFile = common.StringLocaleProperty(
-        "soundFile", """File (name) for the pre-recorded blurb""",
-        defaultValue = 'vm-password',
-    )
-
-class CollectAudio( Interaction ):
-    """Collects audio file from the user"""
-    prompt = common.ListProperty(
-        "prompt", """(Set of) prompts to run, can be Prompt instances or filenames
-
-        Used by the PromptRunner to produce prompt selections
-        """,
-    )
-    textPrompt = common.StringProperty(
-        "textPrompt", """Textual prompt describing the option""",
-    )
-    temporaryFile = common.StringLocaleProperty(
-        "temporaryFile", """Temporary file into which to record the audio before moving to filename""",
-    )
-    filename = common.StringLocaleProperty(
-        "filename", """Final filename into which to record the file...""",
-    )
-    deleteOnFail = common.BooleanProperty(
-        "deleteOnFail", """Whether to delete failed attempts to record a file""",
-        defaultValue = True
-    )
-    escapeDigits = common.StringLocaleProperty(
-        "escapeDigits", """Set of digits which escape from recording the file""",
-        defaultValue = '#*0123456789',
-    )
-    timeout = common.FloatProperty(
-        "timeout", """Duration to wait for recording (maximum record time)""",
-        defaultValue = 60,
-    )
-    silence = common.FloatProperty(
-        "silence", """Duration to wait for recording (maximum record time)""",
-        defaultValue = 5,
-    )
-    beep = common.BooleanProperty(
-        "beep", """Whether to play a "beep" sound at beginning of recording""",
-        defaultValue = True,
-    )
-    runnerClass = CollectAudioRunner
-
-class PromptRunner( propertied.Propertied ):
-    """Prompt formed from list of sub-prompts
-    """
-    elements = common.ListProperty(
-        "elements", """Sub-elements of the prompt to be presented""",
-    )
-    agi = basic.BasicProperty(
-        "agi", """The FastAGI instance we're controlling""",
-    )
-    escapeDigits = common.StringLocaleProperty(
-        "escapeDigits", """Set of digits which escape from playing the prompt""",
-    )
-    timeout = common.FloatProperty(
-        "timeout", """Timeout on data-entry after completed reading""",
-    )
-    def __call__( self ):
-        """Return a deferred that chains all of the sub-prompts in order
-
-        Returns from the first of the sub-prompts that recevies a selection
-
-        returns str(digit) for the key the user pressed
-        """
-        return self.onNext( None )
-    def onNext( self, result, index=0 ):
-        """Process the next operation"""
-        if result is not None:
-            return result
-        try:
-            element = self.elements[index]
-        except IndexError, err:
-            # okay, do a waitForDigit from timeout seconds...
-            return self.agi.waitForDigit( self.timeout ).addCallback(
-                self.processKey
-            ).addCallback( self.processLast )
-        else:
-            df = element.read( self.agi, self.escapeDigits )
-            df.addCallback( self.processKey )
-            df.addCallback( self.onNext, index=index+1)
-            return df
-    def processKey( self, result ):
-        """Does the pressed key belong to escapeDigits?"""
-        if isinstance( result, tuple ):
-            # getOption result...
-            if result[1] == 0:
-                # failure during load of the file...
-                log.warn( """Apparent failure during load of audio file: %s""", self.value )
-                result = 0
-            else:
-                result = result[0]
-            if isinstance( result, str ):
-                if result:
-                    result = ord( result )
-                else:
-                    result = 0
-        if result: # None or 0
-            # User pressed a key during the reading...
-            key = chr( result )
-            if key in self.escapeDigits:
-                log.info( 'Exiting early due to user press of: %r', key )
-                return key
-            else:
-                # we don't warn user in this menu if they press an unrecognised key!
-                log.info( 'Ignoring user keypress because not in escapeDigits: %r', key )
-            # completed reading without any escape digits, continue reading
-        return None
-    def processLast( self,result ):
-        if result is None:
-            result = ''
-        return result
-
-class Prompt( propertied.Propertied ):
-    """A Prompt to be read to the user"""
-    value = basic.BasicProperty(
-        "value", """Filename to be read to the user""",
-    )
-    def __init__( self, value, **named ):
-        named['value'] = value
-        super(Prompt,self).__init__( **named )
-class AudioPrompt( Prompt ):
-    """Default type of prompt, reads a file"""
-    def read( self, agi, escapeDigits ):
-        """Read the audio prompt to the user"""
-        # There's no "say file" operation...
-        return agi.getOption( self.value, escapeDigits, 0.001 )
-class TextPrompt( Prompt ):
-    """Prompt produced via festival text-to-speech reader (built-in command)"""
-    def read( self, agi, escapeDigits ):
-        return agi.execute( "Festival", self.value, escapeDigits )
-class NumberPrompt( Prompt ):
-    """Prompt that reads a number as a number"""
-    value = common.IntegerProperty(
-        "value", """Integer numeral to read""",
-    )
-    def read( self, agi, escapeDigits ):
-        """Read the audio prompt to the user"""
-        return agi.sayNumber( self.value, escapeDigits )
-class DigitsPrompt( Prompt ):
-    """Prompt that reads a number as digits"""
-    def read( self, agi, escapeDigits ):
-        """Read the audio prompt to the user"""
-        return agi.sayDigits( self.value, escapeDigits )
-class AlphaPrompt( Prompt ):
-    """Prompt that reads alphabetic string as characters"""
-    def read( self, agi, escapeDigits ):
-        """Read the audio prompt to the user"""
-        return agi.sayAlpha( self.value, escapeDigits )
-class DateTimePrompt( Prompt ):
-    """Prompt that reads a date/time as a date"""
-    format = basic.BasicProperty(
-        "format", """Format in which to read the date to the user""",
-        defaultValue = None
-    )
-    def read( self, agi, escapeDigits ):
-        """Read the audio prompt to the user"""
-        return agi.sayDateTime( self.value, escapeDigits, format=self.format )
diff --git a/setup.py b/setup.py
deleted file mode 100755
index de51866..0000000
--- a/setup.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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.
-
-"""Installs StarPy using distutils
-
-Run:
-    python setup.py install
-to install the package from the source archive.
-"""
-
-if __name__ == "__main__":
-    import sys,os, string
-    from distutils.sysconfig import *
-    from distutils.core import setup
-
-    ##############
-    ## Following is from Pete Shinners,
-    ## apparently it will work around the reported bug on
-    ## some unix machines where the data files are copied
-    ## to weird locations if the user's configuration options
-    ## were entered during the wrong phase of the moon :) .
-    from distutils.command.install_data import install_data
-    class smart_install_data(install_data):
-        def run(self):
-            #need to change self.install_dir to the library dir
-            install_cmd = self.get_finalized_command('install')
-            self.install_dir = getattr(install_cmd, 'install_lib')
-            # should create the directory if it doesn't exist!!!
-            return install_data.run(self)
-    ##############
-    def npFilesFor( dirname ):
-        """Return all non-python-file filenames in dir"""
-        result = []
-        allResults = []
-        for name in os.listdir(dirname):
-            path = os.path.join( dirname, name )
-            if os.path.isfile( path) and os.path.splitext( name )[1] not in ('.py','.pyc','.pyo') and name!='starpy.conf':
-                result.append( path )
-            elif os.path.isdir( path ) and name.lower() !='cvs':
-                allResults.extend( npFilesFor(path))
-        if result:
-            allResults.append( (dirname, result))
-        return allResults
-    dataFiles = npFilesFor( 'doc') + npFilesFor( 'examples') + [('.',('LICENSE',))]
-    dataFiles = [
-        (os.path.join('starpy',directory), files)
-        for (directory,files) in dataFiles
-    ]
-
-    from sys import hexversion
-    if hexversion >= 0x2030000:
-        # work around distutils complaints under Python 2.2.x
-        extraArguments = {
-            'classifiers': [
-                """License :: OSI Approved :: BSD License""",
-                """Programming Language :: Python""",
-                """Topic :: Software Development :: Libraries :: Python Modules""",
-                """Intended Audience :: Developers""",
-            ],
-            'keywords': 'asterisk,fastagi,twisted,protocol,manager,ami',
-            'long_description' : """Twisted Protocols for interaction with Asterisk PBX
-
-Provides Asterisk AMI and Asterisk FastAGI protocols under Twisted,
-allowing for fairly extensive customisation of Asterisk operations
-from a Twisted process.""",
-            'platforms': ['Any'],
-        }
-    else:
-        extraArguments = {
-        }
-    ### Now the actual set up call
-    setup (
-        name = "starpy",
-        version = '1.0.0b1',
-        url = "http://starpy.sourceforge.net",
-        download_url = "http://sourceforge.net/project/showfiles.php?group_id=164040",
-        description = "Twisted Protocols for interaction with the Asterisk PBX",
-        author = "Mike C. Fletcher",
-        author_email = "mcfletch at vrplumber.com",
-        license = "BSD",
-
-        package_dir = {
-            'starpy':'.',
-        },
-        packages = [
-            'starpy',
-            'starpy.examples',
-        ],
-        options = {
-            'sdist':{'force_manifest':1,'formats':['gztar','zip'],},
-        },
-        data_files = dataFiles,
-        cmdclass = {'install_data':smart_install_data},
-        **extraArguments
-    )
-
diff --git a/utilapplication.py b/utilapplication.py
deleted file mode 100644
index 0e51901..0000000
--- a/utilapplication.py
+++ /dev/null
@@ -1,198 +0,0 @@
-#
-# StarPy -- Asterisk Protocols for Twisted
-# 
-# Copyright (c) 2006, Michael C. Fletcher
-#
-# Michael C. Fletcher <mcfletch at 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)

-- 
StarPy



More information about the Pkg-voip-commits mailing list