#!/usr/bin/env python try: import _find_fuse_parts except ImportError: pass import fuse, re, errno, stat, os from fuse import Fuse from time import time, mktime from datetime import date from MythTV import MythDB, MythVideo, ftopen, MythTV if not hasattr(fuse, '__version__'): raise RuntimeError, \ "your fuse-py doesn't know of fuse.__version__, probably it's too old." fuse.fuse_python_api = (0, 2) TIMEOUT = 600 class Attr(fuse.Stat): def __init__(self): self.st_mode = 0 self.st_ino = 0 self.st_dev = 0 self.st_blksize = 0 self.st_nlink = 1 self.st_uid = os.getuid() self.st_gid = os.getgid() self.st_rdev = 0 self.st_size = 0 self.st_atime = 0 self.st_mtime = 0 self.st_ctime = 0 class File( object ): def __repr__(self): return str(self.path) def __init__(self, path=''): # create, assuming directory self.children = {} self.path = path+'/' self.attr = Attr() self.attr.st_mode = stat.S_IFDIR | 0555 self.attr.st_ino = 0 self.vid = None self.rec = None def set(self, rec=None, vid=None, uri=None): # adjust for file self.path = self.path[:-1] self.attr.st_mode = stat.S_IFREG | 0444 self._readvid(vid) self._readrec(rec) self._readuri(uri) def _readvid(self, vid): if vid is None: return self.vid = vid if vid.insertdate is not None: ctime = mktime(vid.insertdate.timetuple()) else: ctime = time() self.attr.st_ctime = ctime self.attr.st_mtime = ctime self.attr.st_atime = ctime self.attr.st_size = 0 def _readrec(self, rec): if rec is None: return self.rec = rec ctime = mktime(rec.lastmodified.timetuple()) self.attr.st_ctime = ctime self.attr.st_mtime = ctime self.attr.st_atime = ctime self.attr.st_size = rec.filesize def _readuri(self, uri): if uri is None: return self.uri = uri def addStr(self, path, rec=None, vid=None, uri=None): self.add(path.lstrip('/').split('/'), rec=rec, vid=vid, uri=uri) def add(self, path, rec=None, vid=None, uri=None): name = str(path[0]) if len(path) == 1: self.children[name] = File(self.path+name) self.children[name].set(rec=rec, vid=vid, uri=uri) else: if name not in self.children: self.children[name] = File(self.path+name) self.children[name].add(path[1:], rec=rec, vid=vid, uri=uri) if self.children[name].attr.st_ctime > self.attr.st_ctime: self.attr.st_ctime = self.children[name].attr.st_ctime if self.children[name].attr.st_mtime > self.attr.st_mtime: self.attr.st_mtime = self.children[name].attr.st_mtime if self.children[name].attr.st_atime > self.attr.st_atime: self.attr.st_atime = self.children[name].attr.st_atime self.attr.st_size = len(self.children) def setSizeStr(self, path, size): self.setSize(path.lstrip('/').split('/'), size) def setSize(self, path, size): if path[0] in self.children: if len(path) == 1: self.children[path[0]].attr.st_size = size else: self.children[path[0]].setSize(path[1:], size) def getPathStr(self, path): return self.getPath(path.lstrip('/').split('/')) def getPath(self, path): if path[0] == '': return self elif path[0] not in self.children: return None elif len(path) == 1: return self.children[path[0]] else: return self.children[path[0]].getPath(path[1:]) def getInode(self, inode): if self.inode == inode: return self else: for child in children: ret = child.getInode(inode) if ret is not None: return ret return None class MythFS( Fuse ): def __init__(self, *args, **kw): Fuse.__init__(self, *args, **kw) self.db = None self.mvid = None self.myth = None self.files = File() self.open_files = {} self.time = 0 def _parseFmt(self, rec): path = self.format for (tag, data, format) in (('T','title','%s'),('S','subtitle','%s'), ('R','description','%s'), ('C','category','%s'), ('U','recgroup','%s'), ('hn','hostname','%s'), ('c','chanid','%d') ): path = path.replace('%'+tag, format % rec[data]) for (data, pre) in ( ('starttime','%'), ('endtime','%e'), ('progstart','%p'),('progend','%pe') ): for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), ('j','%d'),('d','%d'),('g','%I'),('G','%H'), ('h','%I'),('H','%H'),('i','%M'),('s','%S'), ('a','%p'),('A','%p') ): path = path.replace(pre+tag, rec[data].strftime(format)) for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), ('j','%d'),('d','%d')): path = path.replace('%o'+tag, rec['originalairdate'].strftime(format)) path = path.replace('%-','-') path = path.replace('%%','%') path += '.'+rec['basename'].split('.')[-1] return path def prep(self): fmt = self.parser.largs[0].split(',',1) if fmt[0] == 'Videos': self.content = 'Videos' elif fmt[0] == 'Recordings': self.content = 'Recordings' if len(fmt) == 2: self.format = fmt[1] else: self.format = '%pY%-%pm%-%pd/(%pH%pi%-%peH%pei) %T %- %S' elif fmt[0] == 'Single': self.files = File() self.content = 'Single' path = fmt[1] fusepath = 'file.%s'%path.split('.')[-1] self.files.addStr(fusepath, uri=path) fp = ftopen(path, 'r') fp.seek(0,2) self.files.setSizeStr(fusepath, fp.tell()) fp.seek(0,0) fp.close() def _populate(self): if (time() - self.time) < TIMEOUT: return if self.content == 'Videos': raise Exception('Video mounting currently disabled') if self.mvid is None: self.mvid = MythVideo() self.files = File() for vid in self.mvid.searchVideos(): if vid.host is None: continue self.files.addStr(vid.filename, vid=vid) self.walkSG('Videos') elif self.content == 'Recordings': if self.db is None: self.db = MythDB() self.files = File() for rec in self.db.searchRecorded(): for field in ('title','subtitle'): if rec[field] == '': rec[field] = 'Untitled' if rec['originalairdate'] is None: rec['originalairdate'] = date(1900,1,1) path = self._parseFmt(rec) self.files.addStr(path, rec=rec) self.time = time() def walkSG(self, group, myth=None, base=None, path=None): if myth is None: # walk through backends c = self.db.cursor() c.execute("""SELECT DISTINCT hostname FROM storagegroup WHERE groupname=%s""", group) for host in c.fetchall(): self.walkSG(group, MythTV(host)) c.close() return if base is None: # walk through base directories for base in myth.getSGList(myth.hostname, group, ''): self.walkSG(group, myth, base, '') return dirs, files, sizes = myth.getSGList(myth.hostname, group, base+'/'+path) for d in dirs: self.walkSG(group, myth, base, path+'/'+d) for f, s in zip(files, sizes): self.files.setSizeStr(path+'/'+f, int(s)) def walkTree(self, tree, offs=''): print offs+tree.path for key,val in tree.attr.__dict__.iteritems(): print '%s %s: %s' % (offs, key, val) for child in tree.children.values(): self.walkTree(child, offs+' ') def getattr(self, path): self._populate() rec = self.files.getPathStr(path) if rec is None: return -errno.ENOENT else: return rec.attr def readdir(self, path, offset): self._populate() d = self.files.getPathStr(path) if d is None: return -errno.ENOENT return tuple([fuse.Direntry(e) for e in d.children.keys()]) def open(self, path, flags): accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR if (flags & accmode) != os.O_RDONLY: return -errno.EACCES if path not in self.open_files: self._populate() f = self.files.getPathStr(path) if f is None: return -errno.ENOENT if self.content == 'Recordings': if f.rec is None: return -errno.ENOENT self.open_files[path] = f.rec.open() elif self.content == 'Videos': if f.vid is None: return -errno.ENOENT self.open_files[path] = f.vid.open() elif self.content == 'Single': if f.uri is None: return -errno.ENOENT self.open_files[path] = ftopen(f.uri, 'r') def read(self, path, length, offset, fh=None): if path not in self.open_files: return -errno.ENOENT if self.open_files[path].tell() != offset: self.open_files[path].seek(offset) return self.open_files[path].read(length) def release(self, path, fh=None): if path in self.open_files: self.open_files[path].close() del self.open_files[path] else: return -errno.ENOENT def main(): fs = MythFS(version='MythFS 0.23.0', usage='', dash_s_do='setsingle') fs.parse(errex=1) fs.flags = 0 fs.multithreaded = False fs.prep() fs.main() if __name__ == '__main__': main()