From e24bfdc58752d49990cd9bf8dcb0e1ef76aef5ad Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Tue, 28 Apr 2009 19:18:30 -0800 Subject: [PATCH] improve error handling... restructure code so that we can support a url in a file that points to the pls file... [git-p4: depot-paths = "//depot/": change = 1304] --- shoutcast.py | 107 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/shoutcast.py b/shoutcast.py index 8fbaba0..80d3bea 100644 --- a/shoutcast.py +++ b/shoutcast.py @@ -12,7 +12,7 @@ __version__ = '$Change$' # to zero till we get one? import ConfigParser -import StringIO +import cStringIO as StringIO import os.path import random @@ -24,7 +24,7 @@ from DIDLLite import Container, MusicGenre, Item, AudioItem, Resource from FSStorage import registerklassfun from twisted.protocols import shoutcast -from twisted.python import log, threadable +from twisted.python import log, threadable, failure from twisted.internet import defer, protocol, reactor from twisted.web import error, http, resource, server from twisted.web.client import getPage, _parse @@ -180,16 +180,16 @@ class ShoutProxy(resource.Resource): self.urls = None self.fetchingurls = False - def dump_exc(self): + def dump_exc(self, failure, request): exc = StringIO.StringIO() - traceback.print_exc(file=exc) + failure.printBriefTraceback(file=exc) + failure.printTraceback() exc.seek(0) - self.request.setHeader('content-type', 'text/html') - self.request.write(error.ErrorPage(http.INTERNAL_SERVER_ERROR, + request.setHeader('content-type', 'text/html') + request.write(error.ErrorPage(http.INTERNAL_SERVER_ERROR, http.RESPONSES[http.INTERNAL_SERVER_ERROR], - '
%s
' % exc.read()).render(self.request)) - self.request.finish() - self.request = None + '
%s
' % exc.read()).render(request)) + request.finish() def startNextConnection(self, request): url = self.urls[self.urlpos] @@ -205,37 +205,32 @@ class ShoutProxy(resource.Resource): def gotPLS(self, page): self.fetchingurls = False - try: - pls = ConfigParser.SafeConfigParser() - pls.readfp(StringIO.StringIO(page)) - assert pls.getint(PLSsection, 'Version') == 2 - assert pls.has_option(PLSsection, 'numberofentries') - cnt = pls.getint(PLSsection, 'numberofentries') - self.urls = [] - for i in range(cnt): - i += 1 # stupid one based arrays - self.urls.append(pls.get(PLSsection, - 'File%d' % i)) - #log.msg('pls urls:', self.urls) - self.urlpos = random.randrange(len(self.urls)) - except: - self.dump_exc() - self.urls = None - self.triggerdefered(lambda x: x.errback(1)) - return - - self.triggerdefered(lambda x: x.callback(1)) + pls = ConfigParser.SafeConfigParser() + pls.readfp(StringIO.StringIO(page)) + # KCSM 91.1 doesn't provide a version + #assert pls.getint(PLSsection, 'Version') == 2 + assert pls.has_option(PLSsection, 'numberofentries') + cnt = pls.getint(PLSsection, 'numberofentries') + self.urls = [] + for i in range(cnt): + i += 1 # stupid one based arrays + self.urls.append(pls.get(PLSsection, + 'File%d' % i)) + #log.msg('pls urls:', self.urls) + self.urlpos = random.randrange(len(self.urls)) + + self.triggerdefered(lambda x: x.callback(True)) def errPLS(self, failure): self.fetchingurls = False - self.triggerdefered(lambda x: x.errback(1)) + # XXX - retry? + self.triggerdefered(lambda x: x.errback(failure)) def processRequest(self, ign, request): self.startNextConnection(request) def errRequest(self, failure, request): - request.write(failure.render(self.request)) - request.finish() + self.dump_exc(failure, request) def render(self, request): request.setHeader('content-type', self.mt) @@ -258,9 +253,10 @@ class ShoutProxy(resource.Resource): self.fetchingurls = True # Not really sure if ascii is the correct one, # shouldn't getPage do proper escaping for me? - getPage(self.shoutpls.encode('ascii')) \ - .addCallbacks(self.gotPLS, self.errPLS) self.afterurls = [ defer.Deferred() ] + d = getPage(self.shoutpls.encode('ascii')) + d.addCallback(self.gotPLS) + d.addErrback(self.errPLS) else: self.afterurls.append(defer.Deferred()) # Always add the callback if we don't have urls @@ -269,6 +265,7 @@ class ShoutProxy(resource.Resource): errbackArgs=(request, )) else: self.startNextConnection(request) + # and make sure the connection doesn't get closed return server.NOT_DONE_YET @@ -276,20 +273,37 @@ class ShoutProxy(resource.Resource): 'triggerdefered', ] threadable.synchronize(ShoutProxy) -class ShoutStation(AudioItem): +class ShoutURL(AudioItem): def __init__(self, *args, **kwargs): - self.station = kwargs['station'] - del kwargs['station'] + url = kwargs.pop('url') + mimetype = kwargs.pop('mimetype', 'audio/mpeg') + bitrate = kwargs.pop('bitrate', None) - kwargs['content'] = ShoutProxy(self.station['PLS_URL'], - self.station['MimeType'].encode('ascii')) + kwargs['content'] = ShoutProxy(url, mimetype) AudioItem.__init__(self, *args, **kwargs) self.url = '%s/%s' % (self.cd.urlbase, self.id) - self.res = Resource(self.url, 'http-get:*:%s:*' % \ - self.station['MimeType'].encode('ascii')) + self.res = Resource(self.url, 'http-get:*:%s:*' % mimetype) #self.res = Resource(self.url + '/pcm', 'http-get:*:%s:*' % \ # 'audio/x-wav') - self.bitrate = self.station['Bitrate'] * 128 # 1024k / 8bit + if bitrate is not None: + self.bitrate = bitrate + +class ShoutFile(ShoutURL): + def __init__(self, *args, **kwargs): + file = kwargs.pop('file') + kwargs['url'] = open(file).read().strip() + + ShoutURL.__init__(self, *args, **kwargs) + +class ShoutStation(ShoutURL): + def __init__(self, *args, **kwargs): + self.station = kwargs.pop('station') + + kwargs['url'] = self.station['PLS_URL'] + kwargs['mimetype'] = self.station['MimeType'].encode('ascii') + kwargs['bitrate'] = self.station['Bitrate'] * 128 # 1024k / 8bit + + ShoutURL.__init__(*args, **kwargs) class ShoutGenre(MusicGenre): def __init__(self, *args, **kwargs): @@ -397,10 +411,15 @@ class ShoutCast(Container): if doupdate: Container.doUpdate(self) -def detectshoutcastfile(path, fobj): - path = os.path.basename(path) +def detectshoutcastfile(origpath, fobj): + path = os.path.basename(origpath) if path == 'SHOUTcast Radio': return ShoutCast, { } + + ext = os.path.splitext(path)[1] + if ext == '.scst': + return ShoutFile, { 'file': origpath } + return None, None registerklassfun(detectshoutcastfile)