#!/usr/bin/env python # Copyright 2006 John-Mark Gurney # # $Id$ # import itertools import os.path import sets import time import iterzipfile zipfile = iterzipfile import FileDIDL from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource from FSStorage import FSObject, registerklassfun 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''' 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() def doUpdate(self): self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) self.res.size = self.zi.file_size Item.doUpdate(self) class ZipChildDir(ZipItem, StorageFolder): '''This is to represent a child dir of the zip file.''' 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'] del kwargs['path'] StorageFolder.__init__(self, *args, **kwargs) FSObject.__init__(self, path) # mapping from path to objectID self.pathObjmap = {} def doUpdate(self): # open the zipfile as necessary. self.zip = zipfile.ZipFile(self.FSpath) hier = buildNameHier(self.zip.namelist(), self.zip.infolist()) children = sets.Set(hier.keys()) for i in self.pathObjmap.keys(): if i not in children: # 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, self, i) else: 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) # sort our children self.sort(lambda x, y: cmp(x.title, y.title)) def detectzipfile(path, fobj): try: z = zipfile.ZipFile(fobj) except: return None, None return ZipObject, { 'path': path } registerklassfun(detectzipfile)