diff --git a/ContentDirectory.py b/ContentDirectory.py index 511f883..9c60b90 100644 --- a/ContentDirectory.py +++ b/ContentDirectory.py @@ -23,7 +23,7 @@ from twisted.python import log from twisted.web import resource, static from elementtree.ElementTree import Element, SubElement, tostring -from upnp import UPnPPublisher +from upnp import UPnPPublisher, errorCode from DIDLLite import DIDLElement, Container, Movie, Resource, MusicTrack import traceback @@ -56,6 +56,8 @@ class ContentDirectoryControl(UPnPPublisher, dict): self.delItem(i) assert len(self.children[id]) == 0 del self.children[id] + # Remove from parent + self.children[self[id].parentID].remove(self[id]) del self[id] def getchildren(self, item): @@ -121,6 +123,14 @@ class ContentDirectoryControl(UPnPPublisher, dict): didl = DIDLElement() result = {} + # check to see if object needs to be updated + if ObjectID in self and hasattr(self[ObjectID], 'checkUpdate'): + self[ObjectID].checkUpdate() + + # return error code if we don't exist + if ObjectID not in self: + raise errorCode(701) + try: if BrowseFlag == 'BrowseDirectChildren': ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount] diff --git a/FSStorage.py b/FSStorage.py new file mode 100644 index 0000000..24de529 --- /dev/null +++ b/FSStorage.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python + +import errno +import os +import sets +import stat +from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource +from twisted.web import static +from twisted.python import log + +mimedict = static.loadMimeTypes() + +def statcmp(a, b, cmpattrs = [ 'st_ino', 'st_dev', 'st_size', 'st_mtime', ]): + if a is None or b is None: + return False + + for i in cmpattrs: + if getattr(a, i) != getattr(b, i): + return False + return True + +class FSObject(object): + def __init__(self, path): + self.FSpath = path + self.pstat = None + + def checkUpdate(self): + # need to handle no such file or directory + # push it up? but still need to handle disappearing + try: + nstat = os.stat(self.FSpath) + if statcmp(self.pstat, nstat): + return + + self.pstat = nstat + self.doUpdate() + except OSError, x: + log.msg('OSError: %s' % x) + if x.errno in (errno.ENOENT, errno.ENOTDIR, errno.EPERM, ): + # We can't access it anymore, delete it + self.cd.delItem(self.id) + else: + raise x + except: + import traceback + print traceback.print_exc() + + def doUpdate(self): + raise NotImplementedError + +class FSItem(FSObject, Item): + def __init__(self, *args, **kwargs): + FSObject.__init__(self, kwargs['path']) + del kwargs['path'] + urlbase = kwargs['urlbase'] + del kwargs['urlbase'] + mimetype = kwargs['mimetype'] + del kwargs['mimetype'] + Item.__init__(self, *args, **kwargs) + self.url = '%s%s' % (urlbase, self.FSpath) + self.mimetype = mimetype + + def doUpdate(self): + self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) + self.res.size = os.path.getsize(fpath) + +class FSVideoItem(FSItem, VideoItem): + pass + +class FSAudioItem(FSItem, AudioItem): + pass + +class FSTextItem(FSItem, TextItem): + pass + +class FSImageItem(FSItem, ImageItem): + pass + +mimetoklass = { + 'application/ogg': FSAudioItem, + 'video': FSVideoItem, + 'audio': FSAudioItem, + 'text': FSTextItem, + 'image': FSImageItem, +} + +def dofileadd(cd, parent, urlbase, path, name): + fn, ext = os.path.splitext(name) + ext = ext.lower() + try: + mt = mimedict[ext] + except KeyError: + log.msg('no mime-type for: %s' % name) + return + + ty = mt.split('/')[0] + if mimetoklass.has_key(mt): + klass = mimetoklass[mt] + elif mimetoklass.has_key(ty): + klass = mimetoklass[ty] + else: + raise KeyError, 'no item for mt: %s' % mt + + return cd.addItem(parent, klass, name, urlbase = urlbase, + path = os.path.join(path, name), mimetype = mt) + +class FSDirectory(StorageFolder, FSObject): + def __init__(self, *args, **kwargs): + path = kwargs['path'] + del kwargs['path'] + urlbase = kwargs['urlbase'] + del kwargs['urlbase'] + StorageFolder.__init__(self, *args, **kwargs) + FSObject.__init__(self, path) + + # mapping from path to objectID + self.pathObjmap = {} + self.urlbase = urlbase + + def doUpdate(self): + # We need to rescan this dir, and see if our children has + # changed any. + log.msg('doUpdate: %s, path: %s' % (self.title, self.FSpath)) + children = sets.Set(os.listdir(self.FSpath)) + for i in self.pathObjmap: + if i not in children: + # delete + self.cd.delItem(self.pathObjmap[i]) + + log.msg('doUpdate: %s, children: %s' % (self.title, children)) + for i in children: + fname = os.path.join(self.FSpath, i) + if i in self.pathObjmap: + continue + + # new object + if os.path.isdir(fname): + # new dir + nf = self.cd.addContainer(self.id, i, klass = FSDirectory, path = fname, urlbase = self.urlbase) + elif os.path.isfile(fname): + # new file + nf = dofileadd(self.cd, self.id, self.urlbase, self.FSpath, i) + else: + nf = None + log.msg('skipping: %s' % fname) + + if nf is not None: + self.pathObjmap[i] = nf + log.msg('i: %s, nf: %s' % (i, nf)) + self.cd[nf].checkUpdate() + + # sort our children + log.msg('doUpdate: %s, sorting: %s' % (self.title, list.__str__(self))) + self.sort(lambda x, y: cmp(x.title, y.title)) + log.msg('sorted') diff --git a/pymediaserv b/pymediaserv index 0accf1c..d01419d 100755 --- a/pymediaserv +++ b/pymediaserv @@ -6,6 +6,7 @@ # Copyright 2006 John-Mark Gurney from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder +from FSStorage import FSDirectory import os import os.path import random @@ -84,8 +85,6 @@ medianode.contentTypes.update( { '.mp4': 'video/mpeg', '.ogm': 'application/ogg', '.vob': 'video/mpeg', - '.mp3': 'audio/mpeg', - '.ogg': 'audio/x-ogg', }) root.putChild('media', medianode) @@ -124,6 +123,7 @@ def addFSPath(cds, parent, dpath): except KeyError: pass +cds.addContainer('0', 'media', klass = FSDirectory, path = 'media', urlbase = urlbase) addFSPath(cds, '0', 'media') site = server.Site(root)