diff --git a/DIDLLite.py b/DIDLLite.py index d88ee0f..41f5338 100644 --- a/DIDLLite.py +++ b/DIDLLite.py @@ -32,7 +32,7 @@ class Resource: return root -class Object: +class Object(object): """The root class of the entire content directory class heirachy.""" klass = 'object' diff --git a/FSStorage.py b/FSStorage.py index 1d33613..0cece5e 100644 --- a/FSStorage.py +++ b/FSStorage.py @@ -10,6 +10,7 @@ import itertools 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 @@ -97,8 +98,9 @@ def defFS(path, fobj): def dofileadd(cd, parent, path, name): klass = None + fsname = os.path.join(path, name) try: - fobj = open(path) + fobj = open(fsname) except: fobj = None for i in itertools.chain(klassfuns, ( defFS, )): @@ -108,12 +110,13 @@ def dofileadd(cd, parent, path, name): except: pass - klass, kwargs = i(os.path.join(path, name), fobj) + klass, kwargs = i(fsname, fobj) if klass is not None: break except: - import traceback - traceback.print_exc() + #import traceback + #traceback.print_exc(file=log.logfile) + pass if klass is None: return diff --git a/ZipStorage.py b/ZipStorage.py index cb26fb4..70c5f2a 100644 --- a/ZipStorage.py +++ b/ZipStorage.py @@ -4,33 +4,165 @@ # $Id$ # +import itertools import os.path +import sets +import time import zipfile +import FileDIDL from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource from FSStorage import FSObject, registerklassfun -def isatpath(f, p): - if f[:len(p)] != p: - # First part of path doesn't match, it's not - return False - - slash = f[len(p):].find('/') - if slash != -1 and slash != len(f) - len(p) + 1: - # Another path component, skip it, as long as it's not the char - return False +from twisted.python import threadable, log +from twisted.spread import pb +from twisted.web import http +from twisted.web import server +from twisted.web import resource + +def inserthierdict(d, name, obj): + if not name: + return + i = name.find('/') + if i == -1: + d[name] = obj + return + + dname = name[:i] + rname = name[i + 1:] + # remaining path components + try: + inserthierdict(d[dname], rname, obj) + except KeyError: + d[dname] = {} + inserthierdict(d[dname], rname, obj) + +def buildNameHier(names, objs): + ret = {} + for n, o in itertools.izip(names, objs): + inserthierdict(ret, n, o) + + return ret + +class ZipFileTransfer(pb.Viewable): + def __init__(self, zf, name, request): + self.zf = zf + self.size = zf.getinfo(name).file_size + self.iter = zf.readiter(name) + self.request = request + self.written = 0 + request.registerProducer(self, 0) + + def resumeProducing(self): + if not self.request: + return + # get data and write to request. + try: + data = self.iter.next() + if data: + self.written += len(data) + # this .write will spin the reactor, calling + # .doWrite and then .resumeProducing again, so + # be prepared for a re-entrant call + self.request.write(data) + except StopIteration: + if self.request: + self.request.unregisterProducer() + self.request.finish() + self.request = None + + def pauseProducing(self): + pass + + def stopProducing(self): + # close zipfile + self.request = None + + # Remotely relay producer interface. + + def view_resumeProducing(self, issuer): + self.resumeProducing() + + def view_pauseProducing(self, issuer): + self.pauseProducing() + + def view_stopProducing(self, issuer): + self.stopProducing() + + synchronized = ['resumeProducing', 'stopProducing'] + +threadable.synchronize(ZipFileTransfer) + +class ZipResource(resource.Resource): + # processors = {} + + isLeaf = True + + def __init__(self, zf, name, mt): + resource.Resource.__init__(self) + self.zf = zf + self.zi = zf.getinfo(name) + self.name = name + self.mt = mt + + def getFileSize(self): + return self.zi.file_size + + def render(self, request): + request.setHeader('content-type', self.mt) + + # We could possibly send the deflate data directly! + if None and self.encoding: + request.setHeader('content-encoding', self.encoding) + + if request.setLastModified(time.mktime(list(self.zi.date_time) + + [ 0, 0, -1])) is http.CACHED: + return '' + + request.setHeader('content-length', str(self.getFileSize())) + if request.method == 'HEAD': + return '' + + # return data + ZipFileTransfer(self.zf, self.name, request) + # and make sure the connection doesn't get closed + return server.NOT_DONE_YET + +class ZipItem(Item): + '''An item in the zip file''' - # otherwise, it is at this level - endtrim = len(f) - if slash: - endtrip -= 1 + def __init__(self, *args, **kwargs): + self.zo = kwargs['zo'] + del kwargs['zo'] + self.zf = kwargs['zf'] + del kwargs['zf'] + self.name = kwargs['name'] + del kwargs['name'] + self.zi = self.zf.getinfo(self.name) + self.mimetype = kwargs['mimetype'] + del kwargs['mimetype'] + kwargs['content'] = ZipResource(self.zf, self.name, + self.mimetype) + Item.__init__(self, *args, **kwargs) + self.url = '%s/%s' % (self.cd.urlbase, self.id) + + def checkUpdate(self): + self.doUpdate() + return self.zo.checkUpdate() - return f[len(p):endtrim] + def doUpdate(self): + log.msg('doUpdate:', `self`, self.name) + self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) + self.res.size = self.zi.file_size + Item.doUpdate(self) -class ZipChildDir(StorageFolder): +class ZipChildDir(ZipItem, StorageFolder): '''This is to represent a child dir of the zip file.''' -class ZipFile(FSObject, StorageFolder): + def __init__(self): + raise NotImplementedError + +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'] @@ -42,20 +174,12 @@ class ZipFile(FSObject, StorageFolder): # mapping from path to objectID self.pathObjmap = {} - def getdirents(path): - '''Returns the list of entires for the path, '' is the root path.''' - lst = self.zip.namelist() - if path: - path += '/' - - return filter(lambda x, p = path: isatpath(x, p), lst) - def doUpdate(self): # open the zipfile as necessary. self.zip = zipfile.ZipFile(self.FSpath) + hier = buildNameHier(self.zip.namelist(), self.zip.infolist()) - allzipfiles = sets.Set(self.zip.namelist()) - children = sets.Set(getdirents('')) + children = sets.Set(hier.keys()) for i in self.pathObjmap.keys(): if i not in children: # delete @@ -67,17 +191,23 @@ class ZipFile(FSObject, StorageFolder): continue # new object - if i not in allzipfiles: + if isinstance(hier[i], dict): # must be a dir + self.pathObjmap[i] = self.cd.addItem(self.id, + ZipChildDir, i, self, i) else: - ZipChild(self, i) + klass, mt = FileDIDL.buildClassMT(ZipItem, i) + self.pathObjmap[i] = self.cd.addItem(self.id, + klass, i, zf = self.zip, zo = self, + name = i, mimetype = mt) def detectzipfile(path, fobj): try: z = zipfile.ZipFile(fobj) + log.msg(`z`) except: return None, None - return ZipFile, {} + return ZipObject, { 'path': path } registerklassfun(detectzipfile) diff --git a/pymediaserv b/pymediaserv index 9619356..e20ab9a 100755 --- a/pymediaserv +++ b/pymediaserv @@ -8,6 +8,9 @@ # $Id$ # +# Modules to import, maybe config file or something? +import ZipStorage + from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder from FSStorage import FSDirectory import os