From 1d01658bb73eff193855de34db81445346769cb7 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Tue, 25 Apr 2006 00:03:52 -0800 Subject: [PATCH] add support for dynamicly changing file systems.. we will now notice when directories and files change size, and update the proper information.. hmm.. just realized we may still need to checkUpdate on all the children we return, and that's better than always checkUpdate'ing on add... [git-p4: depot-paths = "//depot/": change = 766] --- ContentDirectory.py | 12 +++- FSStorage.py | 155 ++++++++++++++++++++++++++++++++++++++++++++ pymediaserv | 4 +- 3 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 FSStorage.py 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)