From 64bcea6c62ee52f636a94cd99c53cd1f8dad379c Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Tue, 30 Jun 2009 21:57:26 -0800 Subject: [PATCH] move all the container handling into DIDLLite so that I get rid of a lot of cut and paste... add a new module that uses XML to create a custom object... One example is to support a simple mp3 streaming website: audio/mpeg http://streaming.uoregon.edu:8000/ Should be expanded to support more, but this is a good first cut... [git-p4: depot-paths = "//depot/": change = 1345] --- DIDLLite.py | 90 +++++++++++++++++++++++++++++++++++- FSStorage.py | 74 +++++++----------------------- ZipStorage.py | 125 +++++++++++++++++--------------------------------- dvd.py | 64 +++++--------------------- item.py | 69 ++++++++++++++++++++++++++++ mpegtsmod.py | 49 ++++++-------------- pymeds.py | 4 ++ pyvr.py | 79 +++++++++---------------------- shoutcast.py | 92 ++++++++++++++----------------------- 9 files changed, 303 insertions(+), 343 deletions(-) create mode 100644 item.py diff --git a/DIDLLite.py b/DIDLLite.py index 98ce044..71410df 100644 --- a/DIDLLite.py +++ b/DIDLLite.py @@ -2,7 +2,7 @@ # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter -# Copyright 2006-2008 John-Mark Gurney +# Copyright 2006-2009 John-Mark Gurney __version__ = '$Change$' # $Id$ @@ -319,13 +319,99 @@ class Container(Object, list): Object.__init__(self, cd, id, parentID, title, restricted, creator, **kwargs) list.__init__(self) + self.doingUpdate = False + self.oldchildren = {} + + def genCurrent(self): + '''This function returns a tuple/list/generator that returns + tuples of (id, name). name must match what is returned by + genChildren. If name is used for the title directly, no + override is necessary.''' + + return ((x.id, x.title) for x in self) + + def genChildren(self): + '''This function returns a list or dict of names for new + children.''' + + raise NotImplementedError + + def createObject(self, i, arg=None): + '''This function returns the (class, name, *args, **kwargs) + that will be passed to the addItem method of the + ContentDirectory. arg will be passed the value of the dict + keyed by i if genChildren is a dict.''' + + raise NotImplementedError + + def sort(self, fun=None): + if fun is None: + return list.sort(self, lambda x, y: cmp(x.title, + y.title)) + + return list.sort(self, fun) def doUpdate(self): + if self.doingUpdate: + return + self.doingUpdate = True + + didupdate = False + children = self.genChildren() + if isinstance(children, dict): + oldchildren = self.oldchildren + self.oldchildren = children + isdict = True + else: + children = set(children) + isdict = False + + names = {} + #print 'i:', `self`, `self.genCurrent`, `self.__class__` + for id, i in tuple(self.genCurrent()): + if i not in children: + didupdate = True + # delete + self.cd.delItem(id) + else: + names[i] = id + + for i in children: + if i in names: + if isdict: + if oldchildren[i] == children[i]: + continue + self.cd.delItem(names[i]) + else: + # XXX - some sort of comparision? + continue + + # new object + if isdict: + args = (children[i], ) + else: + args = () + #print 'i:', `i`, `isdict`, `args`, `self` + klass, name, args, kwargs = self.createObject(i, *args) + if klass is not None: + self.cd.addItem(self.id, klass, name, *args, + **kwargs) + didupdate = True + + # sort our children + self.sort() + + if didupdate: + self.didUpdate() + + self.doingUpdate = False + + def didUpdate(self): if self.id == '0': self.updateID = (self.updateID + 1) else: self.updateID = (self.updateID + 1) % (1l << 32) - Container.doUpdate(self.cd['0']) + Container.didUpdate(self.cd['0']) def toElement(self): diff --git a/FSStorage.py b/FSStorage.py index a04f15a..c4e3238 100644 --- a/FSStorage.py +++ b/FSStorage.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2006-2008 John-Mark Gurney +# Copyright 2006-2009 John-Mark Gurney __version__ = '$Change$' # $Id$ @@ -13,7 +13,7 @@ import os import sets import stat -from DIDLLite import Container, StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource, ResourceList +from DIDLLite import StorageFolder, Item, Resource, ResourceList from twisted.web import resource, server, static from twisted.python import log from twisted.internet import abstract, interfaces, process, protocol, reactor @@ -21,8 +21,6 @@ from zope.interface import implements __all__ = [ 'registerklassfun', 'registerfiletoignore', 'FSObject', 'FSItem', 'FSDirectory', - 'FSVideoItem', 'FSAudioItem', 'FSTextItem', 'FSImageItem', - 'mimetoklass', ] mimedict = static.loadMimeTypes() @@ -59,7 +57,7 @@ class FSObject(object): self.FSpath = path self.pstat = None - def checkUpdate(self): + def checkUpdate(self, **kwargs): # need to handle no such file or directory # push it up? but still need to handle disappearing try: @@ -68,7 +66,7 @@ class FSObject(object): return self.pstat = nstat - self.doUpdate() + self.doUpdate(**kwargs) except OSError, x: log.msg('os.stat, OSError: %s' % x) if x.errno in (errno.ENOENT, errno.ENOTDIR, errno.EPERM, ): @@ -78,10 +76,6 @@ class FSObject(object): else: raise - def doUpdate(self): - raise NotImplementedError - - def __repr__(self): return '<%s.%s: path: %s, id: %s, parent: %s, title: %s>' % \ (self.__class__.__module__, self.__class__.__name__, @@ -189,10 +183,8 @@ class DynamicTrans(resource.Resource): class FSItem(FSObject, Item): def __init__(self, *args, **kwargs): - FSObject.__init__(self, kwargs['path']) - del kwargs['path'] - mimetype = kwargs['mimetype'] - del kwargs['mimetype'] + FSObject.__init__(self, kwargs.pop('path')) + mimetype = kwargs.pop('mimetype') kwargs['content'] = DynamicTrans(self.FSpath, static.File(self.FSpath, mimetype)) Item.__init__(self, *args, **kwargs) @@ -238,7 +230,7 @@ def defFS(path, fobj): return klass, { 'path': path, 'mimetype': mt } -def dofileadd(cd, parent, path, name): +def dofileadd(path, name): klass = None fsname = os.path.join(path, name) try: @@ -262,10 +254,10 @@ def dofileadd(cd, parent, path, name): pass if klass is None or klass is IgnoreFile: - return + return None, None, None, None #print 'matched:', os.path.join(path, name), `i`, `klass` - return cd.addItem(parent, klass, name, **kwargs) + return klass, name, (), kwargs class FSDirectory(FSObject, StorageFolder): def __init__(self, *args, **kwargs): @@ -274,48 +266,14 @@ class FSDirectory(FSObject, StorageFolder): StorageFolder.__init__(self, *args, **kwargs) FSObject.__init__(self, path) - # mapping from path to objectID - self.pathObjmap = {} - self.indoUpdate = False + def genCurrent(self): + return ((x.id, os.path.basename(x.FSpath)) for x in self ) - def doUpdate(self): - # We need to rescan this dir, and see if our children has - # changed any. - if self.indoUpdate: - return - #import traceback - #traceback.print_stack() - self.indoUpdate = True - doupdate = False - children = sets.Set(os.listdir(self.FSpath)) - for i in self.pathObjmap.keys(): - if i not in children: - doupdate = True - # delete - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in children: - if i in self.pathObjmap: - continue - - # new object - nf = dofileadd(self.cd, self.id, self.FSpath, i) - - if nf is not None: - doupdate = True - self.pathObjmap[i] = nf - - # sort our children - self.sort(lambda x, y: cmp(x.title, y.title)) - - # Pass up to handle UpdateID - if doupdate: - # Calling StorageFolder.doUpdate results in calling - # ourselves. - Container.doUpdate(self) - - self.indoUpdate = False + def genChildren(self): + return os.listdir(self.FSpath) + + def createObject(self, i): + return dofileadd(self.FSpath, i) def __repr__(self): return ('<%s.%s: path: %s, id: %s, parent: %s, title: %s, ' + \ diff --git a/ZipStorage.py b/ZipStorage.py index 373bedb..8d2deb1 100644 --- a/ZipStorage.py +++ b/ZipStorage.py @@ -158,6 +158,7 @@ class ZipItem: del kwargs['name'] def checkUpdate(self): + # XXX - is this the correct order? self.doUpdate() self.zo.checkUpdate() @@ -174,9 +175,6 @@ class ZipFile(ZipItem, Item): self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) self.res.size = self.zi.file_size - def doUpdate(self): - pass - class ZipChildDir(ZipItem, StorageFolder): '''This is to represent a child dir of the zip file.''' @@ -188,48 +186,29 @@ class ZipChildDir(ZipItem, StorageFolder): del kwargs['zf'], kwargs['zo'], kwargs['name'] StorageFolder.__init__(self, *args, **kwargs) - # mapping from path to objectID - self.pathObjmap = {} - - def doUpdate(self): - doupdate = False - children = sets.Set(self.hier.keys()) - for i in self.pathObjmap.keys(): - if i not in children: - # delete - doupdate = True - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - cursep = self.sep - for i in children: - if i in self.pathObjmap: - continue - - # new object - pathname = cursep.join((self.name, i)) - if isinstance(self.hier[i], dict): - # must be a dir - self.pathObjmap[i] = self.cd.addItem(self.id, - ZipChildDir, i, zf=self.zf, zo=self, - name=pathname, hier=self.hier[i], - sep=cursep) - else: - klass, mt = FileDIDL.buildClassMT(ZipFile, i) - if klass is None: - continue - self.pathObjmap[i] = self.cd.addItem(self.id, - klass, i, zf = self.zf, zo = self, - name = pathname, mimetype = mt) - doupdate = True - - # sort our children - self.sort(lambda x, y: cmp(x.title, y.title)) - if doupdate: - StorageFolder.doUpdate(self) + def genChildren(self): + return self.hier + + def genCurrent(self): + return ((x.id, x.name.rsplit(self.sep, 1)[1]) for x in self) + + def createObject(self, i, arg): + pathname = self.sep.join((self.name, i)) + kwargs = { 'zf': self.zf, 'zo': self, 'name': pathname } + if isinstance(self.hier[i], dict): + # must be a dir + klass = ZipChildDir + kwargs['sep'] = self.sep + kwargs['hier'] = self.hier[i] + else: + klass, mt = FileDIDL.buildClassMT(ZipFile, i) + kwargs['name'] = pathname + kwargs['mimetype'] = mt + + return klass, i, (), kwargs def __repr__(self): - return '' % len(self.pathObjmap) + return '' % len(self) def tryTar(path): # Try to see if it's a tar file @@ -270,19 +249,16 @@ class ZipObject(FSObject, StorageFolder): def __init__(self, *args, **kwargs): '''If a zip argument is passed it, use that as the zip archive.''' - path = kwargs['path'] - del kwargs['path'] + path = kwargs.pop('path') StorageFolder.__init__(self, *args, **kwargs) FSObject.__init__(self, path) - # mapping from path to objectID - self.pathObjmap = {} - - def doUpdate(self): + def genChildren(self): # open the zipfile as necessary. self.zip = genZipFile(self.FSpath) nl = self.zip.namelist() + cnt = 0 cursep = None for i in self.seps: @@ -291,41 +267,26 @@ class ZipObject(FSObject, StorageFolder): cursep = i cnt = newsum self.sep = cursep + hier = buildNameHier(nl, self.zip.infolist(), cursep) - doupdate = False - children = sets.Set(hier.keys()) - for i in self.pathObjmap.keys(): - if i not in children: - doupdate = True - # delete - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in children: - if i in self.pathObjmap: - continue - - # new object - if isinstance(hier[i], dict): - # must be a dir - self.pathObjmap[i] = self.cd.addItem(self.id, - ZipChildDir, i, zf=self.zip, zo=self, - name=i, hier=hier[i], sep=cursep) - else: - klass, mt = FileDIDL.buildClassMT(ZipFile, i) - if klass is None: - continue - self.pathObjmap[i] = self.cd.addItem(self.id, - klass, i, zf = self.zip, zo = self, - name = i, mimetype = mt) - doupdate = True - - # sort our children - self.sort(lambda x, y: cmp(x.title, y.title)) - - if doupdate: - StorageFolder.doUpdate(self) + return hier + + def genCurrent(self): + return ((x.id, x.name) for x in self) + + def createObject(self, i, arg): + kwargs = { 'zf': self.zip, 'zo': self, 'name': i } + if isinstance(arg, dict): + # must be a dir + kwargs['hier'] = arg + kwargs['sep'] = self.sep + klass = ZipChildDir + else: + klass, mt = FileDIDL.buildClassMT(ZipFile, i) + kwargs['mimetype'] = mt + + return klass, i, (), kwargs def detectzipfile(path, fobj): try: diff --git a/dvd.py b/dvd.py index 33bca38..cdb420e 100644 --- a/dvd.py +++ b/dvd.py @@ -30,14 +30,12 @@ from twisted.spread import pb from twisted.web import resource, server def gennameindexes(pref, item): - ret = [] d = {} for i, title in enumerate(item): t = '%s %d (%s)' % (pref, i + 1, title.time) - ret.append(t) d[t] = i - return ret, d + return d class IterTransfer(pb.Viewable): def __init__(self, iterable, request): @@ -142,30 +140,12 @@ class DVDTitle(StorageFolder): self.doUpdate() #return self.dvddisc.checkUpdate() - def doUpdate(self): - doupdate = False - origchildren, toindex = gennameindexes('Chapter', self.dvdtitle) - children = sets.Set(origchildren) - for i in self.pathObjmap.keys(): - if i not in children: - doupdate = True - # delete - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in origchildren: - if i in self.pathObjmap: - continue - - # new object - self.pathObjmap[i] = self.cd.addItem(self.id, - DVDChapter, i, dvdtitle = self.dvdtitle, - chapter = self.dvdtitle[toindex[i]]) - doupdate = True - - if doupdate: - StorageFolder.doUpdate(self) + def genChildren(self): + return gennameindexes('Chapter', self.dvdtitle) + def createObject(self, i, arg): + return DVDChapter, i, (), { 'dvdtitle': self.dvdtitle, + 'chapter': self.dvdtitle[arg] } class DVDDisc(FSObject, StorageFolder): def __init__(self, *args, **kwargs): @@ -175,34 +155,14 @@ class DVDDisc(FSObject, StorageFolder): StorageFolder.__init__(self, *args, **kwargs) FSObject.__init__(self, path) - # mapping from path to objectID - self.pathObjmap = {} - - def doUpdate(self): - # open the DVD as necessary. + def genChildren(self): self.dvd = DVD(self.FSpath) - doupdate = False - origchildren, toindex = gennameindexes('Title', self.dvd) - children = sets.Set(origchildren) - for i in self.pathObjmap.keys(): - if i not in children: - doupdate = True - # delete - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in origchildren: - if i in self.pathObjmap: - continue - - # new object - self.pathObjmap[i] = self.cd.addItem(self.id, DVDTitle, - i, dvdtitle = self.dvd[toindex[i]], dvddisc = self) - doupdate = True - - if doupdate: - StorageFolder.doUpdate(self) + return gennameindexes('Title', self.dvd) + + def createObject(self, i, arg): + return DVDTitle, i, (), { 'dvdtitle': self.dvd[arg], + 'dvddisc': self } def detectdvd(path, fobj): if os.path.isdir(path): diff --git a/item.py b/item.py new file mode 100644 index 0000000..944b0bd --- /dev/null +++ b/item.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# Copyright 2009 John-Mark Gurney + +__version__ = '$Change$' +# $Id$ + +import FileDIDL +import xml.dom.minidom +from DIDLLite import StorageFolder, Item, Resource, ResourceList +from FSStorage import FSObject, registerklassfun + +def getElementText(elm, strip=True): + s = ''.join(x.nodeValue for x in elm.childNodes) + if strip: + s = s.strip() + + return s + +class ItemObject(FSObject, Item): + def __init__(self, *args, **kwargs): + path = kwargs.pop('path') + dom = kwargs.pop('dom') + + FSObject.__init__(self, path) + Item.__init__(self, *args, **kwargs) + + self.checkUpdate(dom=dom) + + def doUpdate(self, dom=None): + if dom is None: + dom = xml.dom.minidom.parse(open(self.FSpath)) + obj = dom.getElementsByTagName('Object')[0] + mt = getElementText(obj.getElementsByTagName('mimetype')[0]) + self.res = ResourceList() + for i in obj.getElementsByTagName('res'): + r = Resource(getElementText(i.getElementsByTagName( + 'url')[0]), 'http-get:*:%s:*' % mt) + size = i.getElementsByTagName('size') + if size: + r.size = int(getElementText(size[0])) + self.res.append(r) + + super(ItemObject, self).doUpdate() + +def canHandle(fobj): + # XXX - create schema and validate + dom = xml.dom.minidom.parse(fobj) + #print 'ch:', `dom` + obj = dom.getElementsByTagName('Object')[0] + mt = getElementText(obj.getElementsByTagName('mimetype')[0]) + + #print 'ch:', `obj`, `mt` + + return dom, mt + +def detectitem(path, fobj): + #print 'di:', `path`, `fobj` + try: + dom, mt = canHandle(fobj) + except: + #import traceback + #traceback.print_exc() + return None, None + + klass, mt = FileDIDL.buildClassMT(ItemObject, path, mimetype=mt) + + return klass, { 'path': path, 'dom': dom } + +registerklassfun(detectitem) diff --git a/mpegtsmod.py b/mpegtsmod.py index 7d07688..1ce07ff 100644 --- a/mpegtsmod.py +++ b/mpegtsmod.py @@ -48,15 +48,13 @@ class _LimitedFile(file): return file.read(self, min(size, self.remain())) def _gennameindexes(chan): - ret = [] d = {} for i in chan: t = '%s %s.%s' % (i['name'], i['major'], i['minor']) - ret.append(t) d[t] = i - return ret, d + return d class MPEGTSTransfer(pb.Viewable): def __init__(self, iterable, request): @@ -307,42 +305,25 @@ class MultiMPEGTS(FSObject, StorageFolder): StorageFolder.__init__(self, *args, **kwargs) FSObject.__init__(self, path) - # mapping from path to objectID - self.pathObjmap = {} - - def doUpdate(self): + def genChildren(self): f = mpegts.TSPStream(_LimitedFile(self.FSpath, - size= 2*1024*1024)) + size=2*1024*1024)) self.tvct = mpegts.GetTVCT(f) + #log.msg('MultiMPEGTS genChildren: tvct: %s' % self.tvct) + return _gennameindexes(self.tvct['channels']) - doupdate = False - origchildren, toindex = _gennameindexes(self.tvct['channels']) - #log.msg('MultiMPEGTS doUpdate: tvct: %s' % self.tvct) - children = sets.Set(origchildren) - for i in self.pathObjmap.keys(): - if i not in children: - doupdate = True - # delete - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in origchildren: - if i in self.pathObjmap: - continue - - # new object - if toindex[i]['prog_num'] == 0: - log.msg('bogus:', toindex[i]) - continue + def createObject(self, i, arg): + if arg['prog_num'] == 0: + log.msg('bogus:', arg) + return None, None, (), {} - #log.msg('real tvct:', toindex[i], toindex.keys(), - # self.tvct) - self.pathObjmap[i] = self.cd.addItem(self.id, MPEGTS, - i, path = self.FSpath, tvct = toindex[i]) - doupdate = True + #log.msg('real tvct:', arg, toindex.keys(), self.tvct) + return MPEGTS, i, (), { 'path': self.FSpath, 'tvct': arg } - if doupdate: - StorageFolder.doUpdate(self) + def sort(self): + return super(MultiMPEGTS, self).sort(lambda x, y: + cmp((x.tvct['major'], x.tvct['minor']), + (y.tvct['major'], y.tvct['minor']))) def findtsstream(fp, pktsz=188): d = fp.read(200*pktsz) diff --git a/pymeds.py b/pymeds.py index a0223a1..9f7775d 100755 --- a/pymeds.py +++ b/pymeds.py @@ -30,6 +30,7 @@ def tryloadmodule(mod): modules = [ 'shoutcast', 'pyvr', + 'item', 'dvd', 'ZipStorage', 'mpegtsmod', @@ -196,6 +197,9 @@ def makeService(config): s.doStop() service.MultiService.stopService(self) + import pickle + pickle.dump(cds, open('test.pickle', 'wb'), -1) + serv = PyMedS() internet.TCPServer(listenPort, site).setServiceParent(serv) diff --git a/pyvr.py b/pyvr.py index 2fe594e..3e05ca5 100644 --- a/pyvr.py +++ b/pyvr.py @@ -109,7 +109,6 @@ class PYVRShows(Container): Container.__init__(self, *args, **kwargs) - self.pathObjmap = {} self.shows = {} self.lastmodified = None @@ -140,34 +139,18 @@ class PYVRShows(Container): return ret + def genChildren(self): + return self.eplisttodict(self.pyvr.shows[self.show]) + + def createObject(self, i, arg): + return PYVRShow, i, (), { 'url': self.pyvr.url, 'info': arg } + + def sort(self): + Container.sort(self, lambda x, y: cmp(x.info['pos'], + y.info['pos'])) + def doUpdate(self): - nl = self.eplisttodict(self.pyvr.shows[self.show]) - - doupdate = False - for i in self.pathObjmap.keys(): - if i not in nl: - # delete - doupdate = True - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in nl: - if i in self.pathObjmap and self.shows[i] == nl[i]: - continue - doupdate = True - if i in self.pathObjmap: - # changed - self.cd.delItem(self.pathObjmap[i]) - self.pathObjmap[i] = self.cd.addItem(self.id, - PYVRShow, i, url=self.pyvr.url, info=nl[i]) - - self.shows = nl - - # sort our children - #self.sort(lambda x, y: cmp(x.title, y.title)) - self.sort(lambda x, y: cmp(x.info['pos'], y.info['pos'])) - if doupdate: - Container.doUpdate(self) + Container.doUpdate(self) self.lastmodified = self.pyvr.lastmodified @@ -178,7 +161,6 @@ class PYVR(Container): Container.__init__(self, *args, **kwargs) - self.pathObjmap = {} #self.pend = None self.lastmodified = None self.newobjs = None @@ -241,42 +223,23 @@ class PYVR(Container): self.newobjs = recxmltoobj(page.read()) self.doUpdate() + def genChildren(self): + return self.newobjs.iterkeys() + + def createObject(self, i): + return PYVRShows, i, (), { 'show': i, 'pyvr': self } + def doUpdate(self): if self.newobjs is None: import traceback traceback.print_stack(file=log.logfile) - return + raise ValueError('did not get shows') - nl = self.newobjs - - doupdate = False - for i in self.pathObjmap.keys(): - if i not in nl: - # delete - doupdate = True - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - # This data is referenced when adding new shows - self.shows = nl - for i in nl: - if i in self.pathObjmap: - continue - doupdate = True - try: - self.pathObjmap[i] = self.cd.addItem(self.id, - PYVRShows, i, show=i, pyvr=self) - except: - import traceback - traceback.print_exc(file=log.logfile) - raise + self.shows = self.newobjs - self.newobjs = None + Container.doUpdate(self) - # sort our children - self.sort(lambda x, y: cmp(x.title, y.title)) - if doupdate: - Container.doUpdate(self) + self.newobjs = None def detectpyvrfile(path, fobj): bn = os.path.basename(path) diff --git a/shoutcast.py b/shoutcast.py index 82df566..b952b34 100644 --- a/shoutcast.py +++ b/shoutcast.py @@ -15,7 +15,7 @@ import ConfigParser import cStringIO as StringIO import os.path import random - +import time import traceback from py_shoutcast import * @@ -312,7 +312,6 @@ class ShoutGenre(MusicGenre): #self.feeds = ShoutcastFeedAsync(self.genre) self.feeds = feeds.ShoutcastFeed(self.genre) self.sl = None - self.pathObjmap = {} MusicGenre.__init__(self, *args, **kwargs) @@ -335,43 +334,36 @@ class ShoutGenre(MusicGenre): return ret def checkUpdate(self): - self.doUpdate() + # Sometimes the shoutcast server returns a 503 (busy) which + # isn't valid XML, try again. + while True: + try: + stations = self.feeds.parse_stations() + break + except: + traceback.print_exc() + + time.sleep(1) - def doUpdate(self): - #traceback.print_stack(file=log.logfile) - stations = self.feeds.parse_stations() if stations == self.sl: return - nl = self.genStations(stations) - - doupdate = False - for i in self.pathObjmap.keys(): - if i not in nl: - # delete - doupdate = True - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for name, i in nl.iteritems(): - if name in self.pathObjmap: - if cmpStation(i, self.cd[self.pathObjmap[name]].station): - continue - # Didn't match, readd - self.cd.delItem(self.pathObjmap[name]) - del self.pathObjmap[name] - - doupdate = True - self.pathObjmap[name] = self.cd.addItem(self.id, - ShoutStation, '%sk-%s' % (i['Bitrate'], name), - station = i) - self.sl = stations - # sort our children - self.sort(lambda *a: stationwbitratecmp(*a)) - if doupdate: - Container.doUpdate(self) + self.doUpdate() + + def genChildren(self): + return self.genStations(self.sl) + + def genCurrent(self): + return ((x.id, x.station) for x in self) + + def createObject(self, name, arg): + return ShoutStation, '%sk-%s' % (arg['Bitrate'], name), (), \ + { 'station': arg } + + def sort(self): + super(ShoutGenre, self).sort(lambda *a: stationwbitratecmp(*a)) class ShoutCast(Container): def __init__(self, *args, **kwargs): @@ -380,38 +372,24 @@ class ShoutCast(Container): #self.genres = GenreFeedAsync() self.genres = feeds.GenreFeed() self.genre_list = None - self.pathObjmap = {} def checkUpdate(self): - self.doUpdate() - - def doUpdate(self): - #traceback.print_stack(file=log.logfile) nl = self.genres.parse_genres() if nl == self.genre_list: return - doupdate = False - for i in self.pathObjmap.keys(): - if i not in nl: - # delete - doupdate = True - self.cd.delItem(self.pathObjmap[i]) - del self.pathObjmap[i] - - for i in nl: - if i in self.pathObjmap: - continue - doupdate = True - self.pathObjmap[i] = self.cd.addItem(self.id, - ShoutGenre, i, genre=i) - self.genre_list = nl - # sort our children - self.sort(lambda x, y: cmp(x.title, y.title)) - if doupdate: - Container.doUpdate(self) + super(ShoutCast, self).doUpdate() + + def genChildren(self): + return self.genre_list + + def genCurrent(self): + return ((x.id, x.genre) for x in self) + + def createObject(self, i): + return ShoutGenre, i, (), { 'genre': i } def detectshoutcastfile(origpath, fobj): path = os.path.basename(origpath)