~vcs-imports/shtoom/main

« back to all changes in this revision

Viewing changes to shtoom/upnp.py

  • Committer: anthony
  • Date: 2005-03-31 14:37:57 UTC
  • Revision ID: Arch-1:shtoom@bazaar.ubuntu.com%shtoom--trunk--0--patch-981
* ALSA handles stereo-only devices now
* Microphone samples are asynchronously pushed up by the lower layers
rather than being polled by the upper layers. On Mac, the high-precision
realtime thread is used to push microphone samples. This fixes a bug
with short reads and a bug with inaccurate polling. On ALSA, there is a
LoopingCall in the ALSA driver that polls at an appropriate interval.
* The encoder base class has a buffer to store up the appropriate
bytes of microphone data to make up a media frame. This buffer gets
flushed when the audio device closes or reopens.
* The audio device gets closed and reopened during important state
transitions, namely call start and call end. This fixes the "That jerk!"
bug, in which you could say "I have to call that jerk!" immediately
before a call connected and then the jerk in question would hear you say
it when he answered.
* Remove some extremely detailed diags that measured the number of 
packets sent per second and the number of packets received per second.
Those diags have served well and are now retired.
* The Mac audio loopback test is rewritten, and a bug involving
closing the loopback test versus closing a phone call is fixed.
* The discovery/selection of the appropriate audio device is done
before the construction of the Phone object. This makes the other
platforms' initialization process parallel to the Mac initialization
process, and also I prefer this approach. (The other approach is that
you construct the Phone object and then it discovers/selects the audio
device itself.)
* Plays ringing sounds for inbound and outbound ringing sounds
* New audio_device option for selecting a different ALSA or OSS device
* New playout algorithm

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
import sys, socket, random, urlparse
29
29
from nonsuckhttp import urlopen
30
30
from shtoom.soapsucks import BeautifulSoap, SOAPRequestFactory, soapenurl
31
 
from shtoom.defcache import DeferredCache 
 
31
from shtoom.defcache import DeferredCache
32
32
 
33
33
from shtoom.interfaces import NATMapper as INATMapper
34
34
from shtoom.nat import BaseMapper
69
69
                                        system='UPnP')
70
70
        if status == "200":
71
71
            self.gotSearchResponse = True
72
 
            self.handleSearchResponse(message)
73
72
            if self.upnpTimeout:
74
73
                self.upnpTimeout.cancel()
75
74
                self.upnpTimeout = None
 
75
            self.handleSearchResponse(message)
76
76
 
77
77
    def handleSearchResponse(self, message):
78
78
        import urlparse
79
79
        headers, body = self.parseSearchResponse(message)
80
80
        loc = headers.get('location')
81
81
        if not loc:
82
 
            log.msg("No location header in response to M-SEARCH!", 
 
82
            log.msg("No location header in response to M-SEARCH!",
83
83
                                                            system='UPnP')
84
84
            return
85
85
        loc = loc[0]
174
174
 
175
175
    def stupidrandomdelaytoworkaroundbug(self, body, loc):
176
176
        """
177
 
        On Mac (but not on Linux), Twisted seems to drop the ball on 
178
 
        connecTCP().  The TCP connection gets set up (as confirmed by packet 
179
 
        trace showing three-part handshake), but the factory object never gets 
180
 
        buildProtocol().  Eventually (depending on the timeout value), the 
 
177
        On Mac (but not on Linux), Twisted seems to drop the ball on
 
178
        connecTCP().  The TCP connection gets set up (as confirmed by packet
 
179
        trace showing three-part handshake), but the factory object never gets
 
180
        buildProtocol().  Eventually (depending on the timeout value), the
181
181
        factory object gets its connectionFailed() method called instead.
182
 
    
183
 
        Mysteriously, this *always* happens on the urlopen in this method, and 
184
 
        never on any of the other TCP connections that are made during Shtoom 
 
182
 
 
183
        Mysteriously, this *always* happens on the urlopen in this method, and
 
184
        never on any of the other TCP connections that are made during Shtoom
185
185
        setup.  Also mysteriously, inserting this stupidrandomdelay of 4 seconds
186
186
        fixes it.
187
187
 
188
 
        Surely I should write a minimal test case and then either give the test 
189
 
        case to the Twisted folks or fix it myself, but some other things are 
 
188
        Surely I should write a minimal test case and then either give the test
 
189
        case to the Twisted folks or fix it myself, but some other things are
190
190
        way too urgent today.
191
191
 
192
192
        The sequence of events that I observed was: connect 1; connect 2;
199
199
        """
200
200
        log.msg("after stupidrandomrelaytoworkaroundbug, got an IGDevice from %s"%(loc,), system='UPnP')
201
201
        if self.controlURL is not None:
202
 
            log.msg("already found UPnP, discarding duplicate response", 
 
202
            log.msg("already found UPnP, discarding duplicate response",
203
203
                                                                system="UPnP")
204
204
            # We already got a working one - ignore this one
205
205
            return
207
207
        bs = BeautifulSoap(data)
208
208
        manufacturer = bs.first('manufacturer')
209
209
        if manufacturer and manufacturer.contents:
210
 
            log.msg("you're behind a %s"%(manufacturer.contents[0]), 
 
210
            log.msg("you're behind a %s"%(manufacturer.contents[0]),
211
211
                                                                system='UPnP')
212
212
            self.upnpInfo['manufacturer'] = manufacturer.contents[0]
213
213
        friendly = bs.first('friendlyName')
221
221
            log.msg("upnp response has no urlbase, falling back to %s"%(loc,), system='UPnP')
222
222
            self.urlbase = loc
223
223
 
224
 
        wanservices = bs.fetch('service', 
 
224
        wanservices = bs.fetch('service',
225
225
            dict(serviceType='urn:schemas-upnp-org:service:WANIPConnection:%'))
226
226
        for service in wanservices:
227
227
            scpdurl = service.get('SCPDURL')
240
240
    def handleWanServiceDesc(self, body):
241
241
        log.msg("got WANServiceDesc from %s"%(self.urlbase,), system='UPnP')
242
242
        data = body.read()
243
 
        self.soap = SOAPRequestFactory(self.controlURL, 
 
243
        self.soap = SOAPRequestFactory(self.controlURL,
244
244
                            "urn:schemas-upnp-org:service:WANIPConnection:1")
245
245
        self.soap.setSCPD(data)
246
246
        self.completedDiscovery()
266
266
        return cd
267
267
 
268
268
    def getGenericPortMappingEntry(self, nextPMI=0, cd=None, saved=None):
269
 
        if saved is None: 
 
269
        if saved is None:
270
270
            saved = {}
271
271
        request = self.soap.GetGenericPortMappingEntry(
272
272
                                                NewPortMappingIndex=nextPMI)
273
273
        d = soapenurl(request)
274
 
        d.addCallbacks(lambda x: self.cb_gotGenericPortMappingEntry(x, 
275
 
                                                                 nextPMI+1, 
 
274
        d.addCallbacks(lambda x: self.cb_gotGenericPortMappingEntry(x,
 
275
                                                                 nextPMI+1,
276
276
                                                                 cd, saved),
277
 
                       lambda x: self.cb_failedGenericPortMappingEntry(x, 
 
277
                       lambda x: self.cb_failedGenericPortMappingEntry(x,
278
278
                                                                    cd, saved))
279
279
 
280
280
    def cb_gotGenericPortMappingEntry(self, response, nextPMI, cd, saved):
296
296
        cd = defer.Deferred()
297
297
        d = getLocalIPAddress()
298
298
        d.addCallback(lambda locIP: self._cbAddPortMapping(intport, extport,
299
 
                                                           desc, proto, lease, 
 
299
                                                           desc, proto, lease,
300
300
                                                           locIP, cd))
301
301
        return cd
302
302
 
303
303
    def _cbAddPortMapping(self, iport, eport, desc, proto, lease, locip, cd):
304
 
        request = self.soap.AddPortMapping(NewRemoteHost=None, 
 
304
        request = self.soap.AddPortMapping(NewRemoteHost=None,
305
305
                                           NewExternalPort=eport,
306
306
                                           NewProtocol=proto,
307
307
                                           NewInternalPort=iport,
310
310
                                           NewPortMappingDescription=desc,
311
311
                                           NewLeaseDuration=lease)
312
312
        d = soapenurl(request)
313
 
        d.addCallbacks(lambda x,cd=cd:self.cb_gotAddPortMapping(x,cd), 
 
313
        d.addCallbacks(lambda x,cd=cd:self.cb_gotAddPortMapping(x,cd),
314
314
                       lambda x,cd=cd:self.cb_failedAddPortMapping(x,cd))
315
 
        
 
315
 
316
316
    def cb_gotAddPortMapping(self, response, compdef):
317
317
        log.msg('AddPortMapping ok', system='UPnP')
318
318
        compdef.callback(None)
325
325
    def deletePortMapping(self, extport, proto='UDP'):
326
326
        "remove a port mapping"
327
327
        cd = defer.Deferred()
328
 
        request = self.soap.DeletePortMapping(NewRemoteHost=None, 
 
328
        request = self.soap.DeletePortMapping(NewRemoteHost=None,
329
329
                                              NewExternalPort=extport,
330
330
                                              NewProtocol=proto)
331
331
        d = soapenurl(request)
332
 
        d.addCallbacks(lambda x,cd=cd:self.cb_gotDeletePortMapping(x,cd), 
 
332
        d.addCallbacks(lambda x,cd=cd:self.cb_gotDeletePortMapping(x,cd),
333
333
                       lambda x,cd=cd:self.cb_failedDeletePortMapping(x,cd))
334
334
        return cd
335
 
        
336
 
        
 
335
 
 
336
 
337
337
    def cb_gotDeletePortMapping(self, response, compdef):
338
338
        log.msg('DeletePortMapping ok', system='UPnP')
339
339
        compdef.callback(None)
401
401
            # XXX when the SOAP code fixes up types, remove the 'str()' version
402
402
            if existing is None:
403
403
                existing = mappings.get((ptype,str(extport)))
404
 
            if existing is None: 
 
404
            if existing is None:
405
405
                break
406
 
            exhost = existing['NewInternalClient'] 
407
 
            exint = existing['NewInternalPort'] 
 
406
            exhost = existing['NewInternalClient']
 
407
            exint = existing['NewInternalPort']
408
408
            exproto = existing['NewProtocol']
409
409
            # More string nasties when I fix SOAP typing
410
410
            if exproto == ptype and exhost == locIP and exint in (intport, str(intport)):
416
416
            extport += random.randint(1,20)
417
417
        # XXX hardcoded description makes me sad - should be an optional
418
418
        # argument!?
419
 
        d = self.upnp.addPortMapping(intport=intport, extport=extport, 
 
419
        d = self.upnp.addPortMapping(intport=intport, extport=extport,
420
420
                                        desc='Shtoom', proto=ptype, lease=0)
421
421
        d.addCallback(lambda x: self.upnp.getExternalIPAddress())
422
422
        d.addCallback(lambda x: self.cb_map_addedPortMapping(x, extport, port))
423
423
 
424
424
    def cb_map_addedPortMapping(self, extaddr, extport, port):
425
 
        cd = self._mapped[port] 
 
425
        cd = self._mapped[port]
426
426
        self._mapped[port] = (extaddr, extport)
427
427
        cd.callback((extaddr, extport))
428
428
 
465
465
        log.msg("no UPnP found!", system="UPnP")
466
466
        return None
467
467
    # A little bit of tricksiness here. If we got the same upnp server,
468
 
    # keep the UPnPMapper alive, so that unmap of existing entries work 
 
468
    # keep the UPnPMapper alive, so that unmap of existing entries work
469
469
    # correctly. Otherwise, kill it.
470
470
    global _cached_mapper
471
471
    if _cached_mapper and _cached_mapper.upnp and _cached_mapper.upnp.controlURL != upnp.controlURL: