#!/usr/bin/env python #@+leo-ver=4 #@+node:@file gmailfs.py #@@first # # Copyright (C) 2004 Richard Jones # # GmailFS - Gmail Filesystem Version 0.8.0 # This program can be distributed under the terms of the GNU GPL. # See the file COPYING. # """ GmailFS provides a filesystem using a Google Gmail account as its storage medium """ #@+others #@+node:imports import fuse from fuse import Fuse import os from errno import * from stat import * from os.path import abspath, expanduser fuse.fuse_python_api = (0, 2) import thread import quopri import libgmail from lgconstants import * import sys,traceback,re,string,time,tempfile,array,logging,logging.handlers try: import OpenSSLProxy except: pass #@-node:imports # Globals DefaultUsername = 'defaultUser' DefaultPassword = 'defaultPassword' DefaultFsname = 'gmailfs' References={} GMAILFS_VERSION = '3' PathStartDelim = '__a__' PathEndDelim = '__b__' FileStartDelim = '__c__' FileEndDelim = '__d__' LinkStartDelim = '__e__' LinkEndDelim = '__f__' MagicStartDelim = '__g__' MagicEndDelim = '__h__' InodeTag ='i' DevTag = 'd' NumberLinksTag = 'k' FsNameTag = 'q' ModeTag = 'e' UidTag = 'u' GidTag = 'g' SizeTag = 's' AtimeTag = 'a' MtimeTag = 'm' CtimeTag = 'c' BlockSizeTag = 'z' VersionTag = 'v' RefInodeTag = 'r' FileNameTag = 'n' PathNameTag = 'p' LinkToTag = 'l' NumberQueryRetries = 3 regexObjectTrailingMB = re.compile(r'\s?MB$') def _addLoggingHandlerHelper(handler): """ Sets our default formatter on the log handler before adding it to the log object. """ handler.setFormatter(defaultLogFormatter) log.addHandler(handler) def GmailConfig(fname): import ConfigParser cp = ConfigParser.ConfigParser() global References global DefaultUsername, DefaultPassword, DefaultFsname global NumberQueryRetries if cp.read(fname) == []: log.warning("Unable to read configuration file: " + str(fname)) return sections = cp.sections() if "account" in sections: options = cp.options("account") if "username" in options: DefaultUsername = cp.get("account", "username") if "password" in options: DefaultPassword = cp.get("account", "password") else: log.error("Unable to find GMail account configuration") if "filesystem" in sections: options = cp.options("filesystem") if "fsname" in options: DefaultFsname = cp.get("filesystem", "fsname") else: log.warning("Using default file system (Dangerous!)") if "logs" in sections: options = cp.options("logs") if "level" in options: level = cp.get("logs", "level") log.setLevel(logging._levelNames[level]) if "logfile" in options: logfile = abspath(expanduser(cp.get("logs", "logfile"))) log.removeHandler(defaultLoggingHandler) _addLoggingHandlerHelper(logging.handlers.RotatingFileHandler(logfile, "a", 5242880, 3)) if "connection" in sections: options = cp.options("connection") try: if "proxy" in options: OpenSSLProxy.OpenSSLInstallHandler(cp.get("connection", "proxy")) else: OpenSSLProxy.OpenSSLInstallHandler() except: log.error("OpenSSLProxy is missing. Can't use HTTPS proxy!"); if "retries" in options: NumberQueryRetries = cp.getint("connection", "retries") if "references" in sections: options = cp.options("references") for option in options: record = cp.get("references",option) fields = record.split(':') if len(fields)<1 or len(fields)>3: log.warning("Invalid reference '%s' in configuration." % (record)) continue reference = reference_class(*fields) References[option] = reference class reference_class: def __init__(self,fsname,username=None,password=None): self.fsname = fsname if username is None or username == '': self.username = DefaultUsername else: self.username = username if password is None or password == '': self.password = DefaultPassword else: self.password = password # This ensures backwards compatability where # old filesystems were stored with 7bit encodings # but new ones are all quoted printable def fixQuotedPrintable(body): # first remove headers newline = body.find("\r\n\r\n") if newline >= 0: body = body[newline:] fixed = body if re.search("Content-Transfer-Encoding: quoted",body): fixed = quopri.decodestring(body) # Map unicode return fixed.replace('\u003d','=') def _getMessagesByQuery(ga,queryString): tries = 0 while triesself.currentOffset+self.blocksize: # self.commitToGmail() # self.currentOffset = (off/self.blocksize)*self.blocksize+(off/self.blocksize) # self.buffer = self.readFromGmail(self.currentOffset/self.blocksize,1) if self.currentOffset == -1 or offself.currentOffset: self.commitToGmail() self.currentOffset = off; self.buffer = self.readFromGmail(self.currentOffset/self.blocksize,1) currentBlock = self.currentOffset/self.blocksize written = 0 while towrite>0: thiswrote = min(towrite,min(self.blocksize-(self.currentOffset%self.blocksize),self.blocksize)) log.debug("wrote "+str(thiswrote)+" bytes off:"+str(off)+" self.currentOffset:"+str(self.currentOffset)) self.buffer[self.currentOffset%self.blocksize:] = buf[written:written+thiswrote] towrite -= thiswrote written += thiswrote self.currentOffset += thiswrote self.lastBlock=currentBlock if self.currentOffset/self.blocksize>currentBlock: self.needsWriting = 1 self.commitToGmail() currentBlock+=1 if towrite>0: self.buffer = self.readFromGmail(currentBlock,1) else: self.needsWriting = 1 if off+buflen>self.inode.size: self.inode.size=off+buflen return buflen def commitToGmail(self): """ Send any unsaved data to users gmail account as an attachment """ if not self.needsWriting: return 1 ga = self.inode.ga subject = ('b='+str(self.inode.ino)+ ' x='+str(self.lastBlock)+ ' '+FsNameTag+'='+MagicStartDelim+ fsNameVar +MagicEndDelim ) tmpf = tempfile.NamedTemporaryFile() arr = array.array('c') arr.fromlist(self.buffer) if log.isEnabledFor(logging.DEBUG): log.debug("wrote to tmp file:"+arr.tostring()) tmpf.write(arr.tostring()) tmpf.flush() gmsg = libgmail.GmailComposedMessage(username, subject, fsNameVar,filenames = [tmpf.name]) if _sendMessage(ga,gmsg): log.debug("Sent write commit ok") self.needsWriting=0 self.inode.update() tmpf.close() return 1 else: log.error("Sent write commit failed") tmpf.close() return 0 def read(self,readlen,offset): """ Read readlen bytes from an open file from position offset bytes into the files data """ readlen = min(self.inode.size-offset,readlen) outbuf = list(" "*readlen) toread = readlen; upto=0; while toread>0: readoffset = (offset+upto)%self.blocksize thisread = min(toread,min(self.blocksize-(readoffset%self.blocksize),self.blocksize)) outbuf[upto:] = self.readFromGmail((offset+upto)/self.blocksize,0)[readoffset:readoffset+thisread] upto+=thisread toread-=thisread log.debug("still to read:"+str(toread)) return outbuf return retbuf def readFromGmail(self,readblock,deleteAfter): """ Read data block with block number 'readblock' for this file from users gmail account, if 'deleteAfter' is true then the block will be removed from Gmail after reading """ log.debug("about to try and find inode:"+str(self.inode.ino)+" blocknumber:"+str(readblock)) if self.lastBlockRead == readblock: contentList = list(" "*self.blocksize) contentList[0:] = self.lastBlockBuffer return contentList ga = self.inode.ga subject = 'b='+str(self.inode.ino)+' x='+str(readblock)+'' folder = _getMessagesByQuery(ga,'subject:'+subject) if len(folder)!=1: return list(" "*self.blocksize) if len(folder._threads[0])!=1: return list(" "*self.blocksize) thread = folder._threads[0] if not thread._messages: thread._messages = thread._getMessages(thread) msg = thread._messages[len(thread._messages)-1] log.debug("got msg with subject:"+msg.subject) log.debug("got "+str(len(msg.attachments))+" attachments") a = msg.attachments[0] if log.isEnabledFor(logging.DEBUG): log.debug("read from gmail:"+a.content) if deleteAfter: ga.trashMessage(msg) self.lastBlockRead = readblock self.lastBlockBuffer = a.content contentList = list(" "*self.blocksize) contentList[0:] = a.content return contentList #@-node:class OpenFile #@+node:class Gmailfs class Gmailfs(Fuse): #@ @+others #@+node:__init__ def __init__(self, extraOpts, mountpoint, *args, **kw): Fuse.__init__(self, *args, **kw) self.fuse_args.mountpoint = mountpoint self.optdict = extraOpts log.info("Mountpoint: %s" % mountpoint) # obfuscate sensitive fields before logging loggableOptdict = self.optdict.copy() loggableOptdict['password'] = '*' * 8 log.info("Named mount options: %s" % (loggableOptdict,)) # do stuff to set up your filesystem here, if you want self.openfiles = {} self.inodeCache = {} global DefaultBlockSize global fsNameVar global password global username DefaultBlockSize = 5*1024*1024 fsNameVar = DefaultFsname password = DefaultPassword username = DefaultUsername options_required = 1 if self.optdict.has_key("reference"): try: reference = References[self.optdict['reference']] username = reference.username password = reference.password fsNameVar = reference.fsname except: log.error("Invalid reference supplied. Using defaults.") else: options_required = 0 if not self.optdict.has_key("username"): if options_required: log.warning('mount: warning, should mount with username=gmailuser option, using default') else: username = self.optdict['username'] if not self.optdict.has_key("password"): if options_required: log.warning('mount: warning, should mount with password=gmailpass option, using default') else: password = self.optdict['password'] if not self.optdict.has_key("fsname"): if options_required: log.warning('mount: warning, should mount with fsname=name option, using default') else: fsNameVar = self.optdict['fsname'] if self.optdict.has_key("blocksize"): DefaultBlockSize = int(self.optdict['blocksize']) self.ga = libgmail.GmailAccount(username, password) self.ga.login() if username.find("@")<0: username = username+"@gmail.com" log.info("Connected to gmail") #thread.start_new_thread(self.mythread, ()) pass #@-node:__init__ #@+node:mythread def mythread(self): """ The beauty of the FUSE python implementation is that with the python interp running in foreground, you can have threads """ log.debug("mythread: started") #while 1: # time.sleep(120) # print "mythread: ticking" #@-node:mythread #@+node:attribs flags = 1 #@-node:attribs class GmailStat(fuse.Stat): def __init__(self): self.st_mode = 0 self.st_ino = 0 self.st_dev = 0 self.st_nlink = 0 self.st_uid = 0 self.st_gid = 0 self.st_size = 0 self.st_atime = 0 self.st_mtime = 0 self.st_ctime = 0 #@+node:getattr def getattr(self, path): st = Gmailfs.GmailStat(); log.debug("get stat:"+path) #st_mode (protection bits) #st_ino (inode number) #st_dev (device) #st_nlink (number of hard links) #st_uid (user ID of owner) #st_gid (group ID of owner) #st_size (size of file, in bytes) #st_atime (time of most recent access) #st_mtime (time of most recent content modification) #st_ctime (time of most recent content modification or metadata change). if path == '/': inode=self.getinode(path) if not inode: self._mkfileOrDir("/",None,S_IFDIR|S_IRUSR|S_IXUSR|S_IWUSR|S_IRGRP|S_IXGRP|S_IXOTH|S_IROTH,-1,1,2) inode = self.getinode(path) else: inode = self.getinode(path) if inode: if log.isEnabledFor(logging.DEBUG): log.debug("inode "+str(inode)) st.st_mode = inode.mode st.st_ino = inode.ino st.st_dev = inode.dev st.st_nlink = inode.nlink st.st_uid = inode.uid st.st_gid = inode.gid st.st_size = inode.size st.st_atime = inode.atime st.st_mtime = inode.mtime st.st_ctme = inode.ctime # statTuple = (inode.mode,inode.ino,inode.dev,inode.nlink,inode.uid, # inode.gid,inode.size,inode.atime,inode.mtime, # inode.ctime) if log.isEnabledFor(logging.DEBUG): log.debug("statsTuple "+str(st)) return st else: e = OSError("No such file"+path) e.errno = ENOENT raise e #@-node:getattr #@+node:readlink def readlink(self, path): log.debug("readlink: path='%s'" % path) inode = self.getinode(path) if not (inode.mode & S_IFLNK): e = OSError("Not a link"+path) e.errno = EINVAL raise e log.debug("about to follow link in body:"+inode.msg.source) body = fixQuotedPrintable(inode.msg.source) m = re.search(LinkToTag+'='+LinkStartDelim+'(.*)'+ LinkEndDelim,body) return m.group(1) #@-node:readlink #@+node:readdir def readdir(self, path, offset): try: log.debug("at top of readdir"); log.debug("getting dir "+path) fspath = _pathSeparatorEncode(path) log.debug("querying for:"+''+PathNameTag+'='+PathStartDelim+ fspath+PathEndDelim) # FIX need to check if directory exists and return error if it doesnt, actually # this may be done for us by fuse folder = _getMessagesByQuery(self.ga,''+PathNameTag+'='+ PathStartDelim+fspath+PathEndDelim) log.debug("got folder ") lst = [] for dirlink in ".", "..": lst.append(dirlink) for thread in folder: assert len(thread) == 1 for msg in thread: log.debug("thread.summary is " + thread.snippet) m = re.search(FileNameTag+'='+FileStartDelim+'(.*)'+ FileEndDelim, thread.snippet) if (m): # Match succeeded, we got the whole filename. log.debug("Used summary for filename") filename = m.group(1) else: # Filename was too long, have to fetch message. log.debug("Long filename, had to fetch message") body = fixQuotedPrintable(msg.source) m = re.search(FileNameTag+'='+FileStartDelim+'(.*)'+ FileEndDelim, body) filename = m.group(1) log.debug("Found file:"+filename) # this test for length is a special case hack for the root directory to prevent ls /gmail_root # returning "". This is hack is requried due to adding modifiable root directory as an afterthought, rather # than designed in at the start. if len(filename)>0: lst.append(filename) except: _logException("got exception when getmessages") lst = None #return map(lambda x: (x,0), lst) for r in lst: yield fuse.Direntry(r) #@-node:getdir #@+node:unlink def unlink(self, path): log.debug("unlink called on:"+path) try: inode = self.getinode(path) inode.unlink(path) #del self.inodeCache[path] # this cache flushing in unfortunate but currently necessary # to avoid problems with hard links losing track of # number of the number of links self.inodeCache = {} return 0 except: _logException("Error unlinking file"+path) e = OSError("Error unlinking file"+path) e.errno = EINVAL raise e #@-node:unlink #@+node:rmdir def rmdir(self, path): log.debug("rmdir called on:"+path) #this is already checked before rmdir is even called #dirlist = self.getdir(path) #if len(dirlist)>0: # e = OSError("directory not empty"+path) # e.errno = ENOTEMPTY # raise e inode = self.getinode(path) inode.unlink(path) #del self.inodeCache[path] # this cache flushing in unfortunate but currently necessary # to avoid problems with hard links losing track of # number of the number of links self.inodeCache = {} # update number of links in parent directory ind = string.rindex(path,'/') parentdir = path[:ind] log.debug("about to rmdir with parentdir:"+parentdir) if len(parentdir)==0: parentdir = "/" parentdirinode = self.getinode(parentdir) parentdirinode.nlink-=1 parentdirinode.update() del self.inodeCache[parentdir] return 0 #@-node:rmdir #@+node:symlink def symlink(self, path, path1): log.debug("symlink: path='%s', path1='%s'" % (path, path1)) self._mkfileOrDir(path1,path,S_IFLNK|S_IRWXU|S_IRWXG|S_IRWXO,-1,0,1) #@-node:symlink #@+node:rename def rename(self, path, path1): log.debug("rename from:"+path+" to:"+path1) msg = self.getinodemsg(path) ind = string.rindex(path1,'/') log.debug("ind:"+str(ind)) dirpath = path1[:ind] if len(dirpath)==0: dirpath="/" name = path1[ind+1:] log.debug("dirpath:"+dirpath+" name:"+name) fspath = _pathSeparatorEncode(dirpath) m = re.match(VersionTag+'=(.*) '+RefInodeTag+'=(.*) '+FsNameTag+'='+MagicStartDelim+'(.*)'+ MagicEndDelim, msg.subject) subject = ( VersionTag+"="+GMAILFS_VERSION+ " "+RefInodeTag+"="+m.group(2)+ " "+FsNameTag+"="+MagicStartDelim+ fsNameVar +MagicEndDelim ) bodytmp = fixQuotedPrintable(msg.source) m = re.search(FileNameTag+'='+FileStartDelim+'(.*)'+FileEndDelim+ ' '+PathNameTag+'='+PathStartDelim+'(.*)'+PathEndDelim+ ' '+LinkToTag+'='+LinkStartDelim+'(.*)'+LinkEndDelim, bodytmp) body = (FileNameTag+"="+FileStartDelim+ name +FileEndDelim+ " "+PathNameTag+"="+PathStartDelim+ fspath +PathEndDelim+ " "+LinkToTag+"="+LinkStartDelim+ m.group(3) +LinkEndDelim ) gmsg = libgmail.GmailComposedMessage(username, subject, body) if _sendMessage(self.ga,gmsg): log.debug("Sent "+subject+" ok") self.ga.trashMessage(msg) if self.inodeCache.has_key(path): #del self.inodeCache[path] # this cache flushing in unfortunate but currently necessary # to avoid problems with hard links losing track of # number of the number of links self.inodeCache = {} return 0 else: e = OSError("Couldnt send mesg"+path) e.errno = ENOSPC raise e #@-node:rename #@+node:link def link(self, path, path1): log.debug("hard link: path='%s', path1='%s'" % (path, path1)) inode = self.getinode(path) if not (inode.mode & S_IFREG): e = OSError("hard links only supported for regular files not directories:"+path) e.errno = EPERM raise e inode.nlink+=1 inode.update() self._mkfileOrDir(path1,None,inode.mode,inode.ino,0,1) return 0 #@-node:link #@+node:chmod def chmod(self, path, mode): log.debug("chmod called with path:"+path+" mode:"+str(mode)) inode = self.getinode(path) inode.mode = (inode.mode & ~(S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)) | mode inode.update() return 0 #@-node:chmod #@+node:chown def chown(self, path, user, group): log.debug("chown called with user:"+str(user)+" and group:"+str(group)) inode = self.getinode(path) inode.uid = user inode.gid = group inode.update() return 0 #@-node:chown #@+node:truncate def truncate(self, path, size): log.debug("truncate "+path+" to size:"+str(size)) inode = self.getinode(path) # this is VERY lazy, we leave the truncated data around # it WILL be harvested when we grow the file again or # when we delete the file but should probably FIX inode.size = size; inode.update() return 0 #@-node:truncate #@+node:mknod def mknod(self, path, mode, dev): """ Python has no os.mknod, so we can only do some things """ log.debug("mknod:"+path) if S_ISREG(mode) | S_ISFIFO(mode) | S_ISSOCK(mode): self._mkfileOrDir(path,None,mode,-1,0,1) #open(path, "w") else: return -EINVAL #@-node:mknod def _mkfileOrDir(self,path,path2,mode,inodenumber,size,nlinks): ind = string.rindex(path,'/') log.debug("ind:"+str(ind)) dirpath = path[:ind] if len(dirpath)==0: dirpath="/" name = path[ind+1:] log.debug("dirpath:"+dirpath+" name:"+name) fspath = _pathSeparatorEncode(dirpath) if path2 == None: path2 = "" if inodenumber==-1: inodeno = int(time.time()) else: inodeno = inodenumber subject = (VersionTag+"="+GMAILFS_VERSION+ " "+RefInodeTag+"="+str(inodeno)+ " "+FsNameTag+"="+MagicStartDelim+ fsNameVar +MagicEndDelim ) body = (""+FileNameTag+"="+FileStartDelim+ name +FileEndDelim+ " "+PathNameTag+"="+PathStartDelim+ fspath +PathEndDelim+ " "+LinkToTag+"="+LinkStartDelim+ path2 +LinkEndDelim ) gmsg = libgmail.GmailComposedMessage(username, subject, body) if _sendMessage(self.ga,gmsg): log.debug("Sent "+subject+" ok") else: e = OSError("Couldnt send mesg in _mkfileOrDir "+path) e.errno = ENOSPC raise e # only create inode if number not provided if inodenumber==-1: timeString = str(int(time.time())) subject = (VersionTag+"="+GMAILFS_VERSION+ " "+InodeTag+"="+str(inodeno)+ " "+DevTag+"=11 "+NumberLinksTag+"="+str(nlinks)+ " "+FsNameTag+"="+MagicStartDelim+ fsNameVar +MagicEndDelim ) body = (""+ModeTag+"="+str(mode)+ " "+UidTag+"="+str(os.getuid())+" "+GidTag+"="+str(os.getgid())+" "+SizeTag+"="+str(size)+ " "+AtimeTag+"="+timeString+ " "+MtimeTag+"="+timeString+ " "+CtimeTag+"="+timeString+ " "+BlockSizeTag+"="+str(DefaultBlockSize) ) gmsg = libgmail.GmailComposedMessage(username, subject, body) if _sendMessage(self.ga,gmsg): log.debug("Sent "+subject+" ok") else: e = OSError("Couldnt send mesg"+path) e.errno = ENOSPC raise e #@+node:mkdir def mkdir(self, path, mode): log.debug("mkdir path:"+path+" mode:"+str(mode)) self._mkfileOrDir(path,None,mode|S_IFDIR, -1,1,2) inode = self.getinode(path) ind = string.rindex(path,'/') log.debug("ind:"+str(ind)) parentdir = path[:ind] if len(parentdir)==0: parentdir="/" parentdirinode = self.getinode(parentdir) parentdirinode.nlink+=1 parentdirinode.update() del self.inodeCache[parentdir] #@-node:mkdir #@+node:utime def utime(self, path, times): log.debug("utime for path:"+path+" times:"+str(times)) inode = self.getinode(path) inode.atime = times[0] inode.mtime = times[1] return 0 #@-node:utime #@+node:open def open(self, path, flags): log.debug("gmailfs.py:Gmailfs:open: %s" % path) try: inode = self.getinode(path) f = OpenFile(inode) self.openfiles[path] = f return 0 except: _logException("Error opening file"+path) e = OSError("Error opening file"+path) e.errno = EINVAL raise e #@-node:open #@+node:read def read(self, path, readlen, offset): try: log.debug("gmailfs.py:Gmailfs:read: %s" % path) log.debug("reading len:"+str(readlen)+" offset:"+str(offset)) f = self.openfiles[path] buf = f.read(readlen,offset) arr = array.array('c') arr.fromlist(buf) rets = arr.tostring() return rets except: _logException("Error reading file"+path) e = OSError("Error reading file"+path) e.errno = EINVAL raise e #@-node:read #@+node:write def write(self, path, buf, off): try: log.debug("gmailfs.py:Gmailfs:write: %s" % path) if log.isEnabledFor(logging.DEBUG): log.debug("writing:"+str(buf)) f = self.openfiles[path] written = f.write(buf,off) return written except: _logException("Error opening file"+path) e = OSError("Error opening file"+path) e.errno = EINVAL raise e #@-node:write #@+node:release def release(self, path, flags): log.debug("gmailfs.py:Gmailfs:release: %s %s" % (path, flags)) f = self.openfiles[path] f.close() del self.openfiles[path] return 0 #@-node:release #@+node:statfs def statfs(self): """ Should return a tuple with the following 6 elements: - blocksize - size of file blocks, in bytes - totalblocks - total number of blocks in the filesystem - freeblocks - number of free blocks - availblocks - number of blocks available to non-superuser - totalfiles - total number of file inodes - freefiles - nunber of free file inodes Feel free to set any of the above values to 0, which tells the kernel that the info is not available. """ st = fuse.StatVfs() block_size = 1024 quotaInfo = self.ga.getQuotaInfo() quotaMbUsed = quotaInfo[QU_SPACEUSED] quotaMbTotal = quotaInfo[QU_QUOTA] blocks = int(regexObjectTrailingMB.sub('', quotaMbTotal)) * 1024 * 1024 / block_size quotaPercent = quotaInfo[QU_PERCENT] blocks_free = blocks - int(regexObjectTrailingMB.sub('', quotaMbUsed)) * 1024 * 1024 / block_size blocks_avail = blocks_free # I guess... log.debug("%s of %s used. (%s)\n" % (quotaMbUsed, quotaMbTotal, quotaPercent)) log.debug("Blocks: %s free, %s total\n" % (blocks_free, blocks)) files = 0 files_free = 0 namelen = 80 st.f_bsize = block_size st.f_frsize = block_size st.f_blocks = blocks st.f_bfree = blocks_free st.f_bavail = blocks_avail st.f_files = files st.f_ffree = files_free return st #@-node:statfs #@+node:fsync def fsync(self, path, isfsyncfile): log.debug("gmailfs.py:Gmailfs:fsync: path=%s, isfsyncfile=%s" % (path, isfsyncfile)) inode = self.getinode(path) f = self.openfiles[path] f.commitToGmail() return 0 #@-node:fsync def getinodemsg(self, path): try: log.debug("check getnodemsg:"+path) ind = string.rindex(path,'/') log.debug("ind:"+str(ind)) dirpath = path[:ind] if len(dirpath)==0: dirpath="/" name = path[ind+1:] log.debug("dirpath:"+dirpath+" name:"+name) fspath = _pathSeparatorEncode(dirpath) folder = _getMessagesByQuery(self.ga, FileNameTag+'='+ FileStartDelim+name+FileEndDelim+ ' '+PathNameTag+'='+PathStartDelim+ fspath +PathEndDelim) if len(folder)!=1: return None; if len(folder._threads[0])!=1: return None; thread = folder._threads[0] if not thread._messages: thread._messages = thread._getMessages(thread) return thread._messages[len(thread._messages)-1] except: _logException("no slash in path:"+path) return None def getinode(self, path): if self.inodeCache.has_key(path): return self.inodeCache[path] msg = self.getinodemsg(path) if msg == None: return None inode = GmailInode(msg,self.ga) if inode: self.inodeCache[path] = inode return inode #@-others #@-node:class Gmailfs #@+node:mainline # Setup logging log = logging.getLogger('gmailfs') defaultLogLevel = logging.WARNING log.setLevel(defaultLogLevel) defaultLogFormatter = logging.Formatter("%(asctime)s %(levelname)-10s %(message)s", "%x %X") # log to stdout while parsing the config while defaultLoggingHandler = logging.StreamHandler(sys.stdout) _addLoggingHandlerHelper(defaultLoggingHandler) GmailConfig(['/etc/gmailfs.conf',os.path.expanduser('~/.gmailfs')]) try: libgmail.ConfigLogs(log) except: pass def main(mountpoint, namedOptions): server = Gmailfs(namedOptions,mountpoint,version="gmailfs 0.8.0",usage='',dash_s_do='setsingle') # server.parser.mountpoint = mountpoint server.parse(errex=1) server.flags = 0 server.multithreaded = False; server.main() if __name__ == '__main__': main() #@-node:mainline #@-others #@-node:@file gmailfs.py #@-leo