#!/usr/bin/env python # Copyright 2006 John-Mark Gurney # # $Id$ # import FileDIDL import errno 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 __all__ = [ 'registerklassfun', 'FSObject', 'FSItem', 'FSVideoItem', 'FSAudioItem', 'FSTextItem', 'FSImageItem', 'mimetoklass', 'FSDirectory', ] mimedict = static.loadMimeTypes() klassfuns = [] def registerklassfun(fun): klassfuns.append(fun) 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 self.pstat = nstat self.doUpdate() except OSError, x: log.msg('os.stat, 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) return None else: raise x return self def doUpdate(self): raise NotImplementedError def __repr__(self): return '<%s.%s: path: %s>' % (self.__class__.__module__, self.__class__.__name__, self.FSpath) class FSItem(FSObject, Item): def __init__(self, *args, **kwargs): FSObject.__init__(self, kwargs['path']) del kwargs['path'] mimetype = kwargs['mimetype'] del kwargs['mimetype'] kwargs['content'] = static.File(self.FSpath) Item.__init__(self, *args, **kwargs) self.url = '%s/%s' % (self.cd.urlbase, self.id) self.mimetype = mimetype def doUpdate(self): self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) self.res.size = os.path.getsize(self.FSpath) Item.doUpdate(self) def defFS(path, fobj): if os.path.isdir(path): # new dir return FSDirectory, { 'path': path } elif os.path.isfile(path): # new file - fall through to below pass else: log.msg('skipping (not dir or reg): %s' % path) return None, None klass, mt = FileDIDL.buildClassMT(FSItem, path) return klass, { 'path': path, 'mimetype': mt } def dofileadd(cd, parent, path, name): klass = None fsname = os.path.join(path, name) try: fobj = open(fsname) except: fobj = None for i in itertools.chain(klassfuns, ( defFS, )): try: try: fobj.seek(0) # incase the call expects a clean file except: pass #log.msg('testing:', `i`, `fsname`, `fobj`) klass, kwargs = i(fsname, fobj) if klass is not None: break except: #import traceback #traceback.print_exc(file=log.logfile) pass if klass is None: return #log.msg('matched:', os.path.join(path, name), `i`, `klass`) return cd.addItem(parent, klass, name, **kwargs) class FSDirectory(FSObject, StorageFolder): def __init__(self, *args, **kwargs): 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): # We need to rescan this dir, and see if our children has # changed any. 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: StorageFolder.doUpdate(self)