Beispiel #1
0
class Flickrfs(Fuse):

  def __init__(self, *args, **kw):
  
    Fuse.__init__(self, *args, **kw)
    log.info("mountpoint: %s", repr(self.mountpoint))
    log.info("mount options: %s", ', '.join(self.optlist))
    log.info("named mount options: %s",
        ', '.join([ "%s: %s" % (k, v) for k, v in self.optdict.items() ]))
    
    self.inodeCache = inodes.InodeCache(dbPath) # Inodes need to be stored.
    self.imgCache = inodes.ImageCache()
    self.NSID = ""
    self.transfl = TransFlickr(browserName)

    # Set some variables to be utilized by statfs function.
    self.statfsCounter = -1
    self.max = 0L
    self.used = 0L

    self.NSID = self.transfl.getUserId()
    if self.NSID is None:
      log.error("can't retrieve user information")
      sys.exit(-1)

    log.info('getting list of licenses available')
    self.licenses = self.transfl.getLicenses()
    if self.licenses is None:
      log.error("can't retrieve license information")
      sys.exit(-1)

    # do stuff to set up your filesystem here, if you want
    self._mkdir("/")
    self._mkdir("/tags")
    self._mkdir("/tags/personal")
    self._mkdir("/tags/public")
    background(timerThread, self.sets_thread, 
               self.sync_sets_thread, sets_sync_int) #sync every 2 minutes


  def imageResize(self, bufData):
    # If no resizing information is present, then return the buffer directly.
    if GetResizeStr() == "":
      return bufData

    # Else go ahead and do the conversion.
    im = '/tmp/flickrfs-' + str(int(random.random()*1000000000))
    f = open(im, 'w')
    f.write(bufData)
    f.close()
    cmd = 'identify -format "%%w" %s'%(im,)
    status,ret = commands.getstatusoutput(cmd)
    msg = ("%s command not found; you must install Imagemagick to get "
      "auto photo resizing")
    if status!=0:
      print msg % 'identify'
      log.error(msg, identify)
      return bufData
    try:
      if int(ret)<int(GetResizeStr().split('x')[0]):
        log.info('image size is smaller than size specified in config.txt;'
                 ' retaining original size')
        return bufData
    except:
      log.error('invalid format of image.size in config.txt')
      return bufData
    log.debug("resizing image %s to size %s" % (im, GetResizeStr()))
    cmd = 'convert %s -resize %s %s-conv'%(im, GetResizeStr(), im)
    ret = os.system(cmd)
    if ret!=0:
      print msg % 'convert'
      log.error(msg, 'convert')
      return bufData
    else:
      f = open(im + '-conv')
      return f.read()


  def writeMetaInfo(self, id, INFO):
    #The metadata may be unicode strings, so we need to encode them on write
    filePath = os.path.join(flickrfsHome, '.'+id)
    f = codecs.open(filePath, 'w', 'utf8')
    f.write('# Metadata file : flickrfs - Virtual filesystem for flickr\n')
    f.write('# Photo owner: %s NSID: %s\n' % (INFO[7], INFO[8]))
    f.write('# Handy link to photo: %s\n'%(INFO[9]))
    f.write('# Licences available: \n')
    for (k, v) in self.licenses:
      f.write('# %s : %s\n' % (k, v))
    f.write('[metainfo]\n')
    f.write("%s:%s\n"%('title', INFO[4]))
    f.write("%s:%s\n"%('description', INFO[3]))
    tags = ','.join(INFO[5])
    f.write("%s:%s\n"%('tags', tags))
    f.write("%s:%s\n"%('license',INFO[6]))
    f.close()
    f = open(filePath)
    f.read()
    fileSize = f.tell()
    f.close()
    return fileSize

  def __populate_set(self, set_id, curdir):
    # Exception handling will be done by background function.
    photosInSet = self.transfl.getPhotosFromPhotoset(set_id)
    for b,p in photosInSet.iteritems():
      info = self.transfl.parseInfoFromPhoto(b,p)
      self._mkfileWithMeta(curdir, info)
    log.info("set %s populated, photo count %s", curdir, len(photosInSet))

  def sets_thread(self):
    """
      The beauty of the FUSE python implementation is that with the 
      python interpreter running in foreground, you can have threads
    """
    print "Sets are being populated in the background."
    log.info("started")
    self._mkdir("/sets")
    for a in self.transfl.getPhotosetList():
      title = a.title[0].elementText.replace('/', '_')
      log.info("populating set %s", title)
      curdir = "/sets/" + title
      if title.strip()=='':
        curdir = "/sets/" + a['id']
      set_id = a['id']
      self._mkdir(curdir, id=set_id)
      self.__populate_set(set_id, curdir)

  def _sync_code(self, psetOnline, curdir):
    psetLocal = set(map(lambda x: x[0], self.getdir(curdir, False)))
    for b in psetOnline:
      info = self.transfl.parseInfoFromPhoto(b)
      imageTitle = info.get('title','')
      if hasattr(b, 'originalformat'):
        imageTitle = self.__getImageTitle(imageTitle, 
                                        b['id'], b['originalformat'])
      else:
        imageTitle = self.__getImageTitle(imageTitle, b['id'])
      path = "%s/%s"%(curdir, imageTitle)
      inode = self.inodeCache.get(path)
      # This exception throwing is just for debugging.
      if inode == None and self.inodeCache.has_key(path):
        e = OSError("Path %s present in inodeCache" % path)
        e.errno = ENOENT
        raise e
      if inode == None: # Image inode not present in the set.
        log.debug("new image found: %s", path)
        self._mkfileWithMeta(curdir, info)
      else:
        if inode.mtime != int(info.get('dupdate')):
          log.debug("image %s changed", path)
          self.inodeCache.pop(path)
          if self.inodeCache.has_key(path + ".meta"):
            self.inodeCache.pop(path + ".meta")
          self._mkfileWithMeta(curdir, info)
        psetLocal.discard(imageTitle)
    if len(psetLocal)>0:
      log.info('%s photos have been deleted online' % len(psetLocal))
    for c in psetLocal:
      log.info('deleting %s', c)
      self.unlink("%s/%s" % (curdir, c), False)

  def __sync_set_in_background(self, set_id, curdir):
    # Exception handling will be done by background function.
    log.info("syncing set %s", curdir)
    psetOnline = self.transfl.getPhotosFromPhotoset(set_id)
    self._sync_code(psetOnline, curdir)
    log.info("set %s sync successfully finished", curdir)
    
  def sync_sets_thread(self):
    log.info("started")
    setListOnline = self.transfl.getPhotosetList()
    setListLocal = self.getdir('/sets', False)
    
    for a in setListOnline:
      title = a.title[0].elementText.replace('/', '_')
      if title.strip()=="":
        title = a['id']
      if (title,0) not in setListLocal: #New set added online
        log.info("new set %s found online", title)
        self._mkdir('/sets/'+title, a['id'])
      else: #Present Online
        setListLocal.remove((title,0))
    for a in setListLocal: #List of sets present locally, but not online
      log.info('set %s no longer online, recursively deleting it', a)
      self.rmdir('/sets/'+a[0], online=False, recr=True)
        
    for a in setListOnline:
      title = a.title[0].elementText.replace('/', '_')
      curdir = "/sets/" + title
      if title.strip()=='':
        curdir = "/sets/" + a['id']
      set_id = a['id']
      self.__sync_set_in_background(set_id, curdir)
    log.info('finished')

  def sync_stream_thread(self):
    log.info('started')
    psetOnline = self.transfl.getPhotoStream(self.NSID)
    self._sync_code(psetOnline, '/stream')
    log.info('finished')
      
  def stream_thread(self):
    log.info("started")
    print "Populating photostream"
    for b in self.transfl.getPhotoStream(self.NSID):
      info = self.transfl.parseInfoFromPhoto(b)
      self._mkfileWithMeta('/stream', info)
    log.info("finished")
    print "Photostream population finished."
      
  def tags_thread(self, path):
    ind = string.rindex(path, '/')
    tagName = path[ind+1:]
    if tagName.strip()=='':
      log.error("the tagName '%s' doesn't contain any tags", tagName)
      return 
    log.info("started for %s", tagName)
    sendtagList = ','.join(tagName.split(':'))
    if(path.startswith('/tags/personal')):
      user_id = self.NSID
    else:
      user_id = None
    for b in self.transfl.getTaggedPhotos(sendtagList, user_id):
      info = self.transfl.parseInfoFromPhoto(b)
      self._mkfileWithMeta(path, info)

  def getUnixPerms(self, info):
    mode = info.get('mode')
    if mode is not None:
      return mode
    perms = info.get('perms')
    if perms is None:
      return 0644
    if perms is "1": # public
      return 0755
    elif perms is "2": # friends only. Add 1 to 4 in middle letter.
      return 0754
    elif perms is "3": # family only. Add 2 to 4 in middle letter.
      return 0764
    elif perms is "4": # friends and family. Add 1+2 to 4 in middle letter.
      return 0774
    else:
      return 0744 # private

  def __getImageTitle(self, title, id, format = "jpg"):
    temp = title.replace('/', '')
#    return "%s_%s.%s" % (temp[:32], id, format)
    # Store the photos original name. Thus, when pictures are uploaded
    # their names would remain as it is, allowing easy resumption of
    # uploading of images, in case some of the photos fail uploading.
    return "%s.%s" % (temp, format)

  def _mkfileWithMeta(self, path, info):
    # Don't write the meta information here, because it requires access to
    # the full INFO. Only do with the smaller version of information that
    # is provided.
    if info is None:
      return
    title = info.get("title", "")
    id =    info.get("id", "")
    ext =   info.get("format", "jpg")
    title = self.__getImageTitle(title, id, ext)

    # Refactor this section of code, so that it can be called
    # from read.
    # Figure out a way to retrieve information, which can be 
    # used in _mkfile.
    mtime = info.get("dupdate")
    ctime = info.get("dupload")
    perms = self.getUnixPerms(info)
    self._mkfile(path+"/"+title, id=id, mode=perms, mtime=mtime, ctime=ctime)
    self._mkfile(path+'/.'+title+'.meta', id)

  def _parsepathid(self, path, id=""):
    #Path and Id may be unicode strings, so encode them to utf8 now before
    #we use them, otherwise python will throw errors when we combine them
    #with regular strings.
    path = path.encode('utf8')
    if id!=0: id = id.encode('utf8')
    parentDir, name = os.path.split(path)
    if parentDir=='':
      parentDir = '/'
    log.debug("parentDir %s", parentDir)
    return path, id, parentDir, name

  def _mkdir(self, path, id="", mtime=None, ctime=None):
    path, id, parentDir, name = self._parsepathid(path, id)
    log.debug("creating directory %s", path)
    self.inodeCache[path] = inodes.DirInode(path, id, mtime=mtime, ctime=ctime)
    if path!='/':
      pinode = self.getInode(parentDir)
      pinode.nlink += 1
      self.updateInode(parentDir, pinode)
      log.debug("nlink of %s is now %s", parentDir, pinode.nlink)

  def _mkfile(self, path, id="", mode=None, 
              comm_meta="", mtime=None, ctime=None):
    path, id, parentDir, name = self._parsepathid(path, id)
    log.debug("creating file %s with id %s", path, id)
    image_name, extension = os.path.splitext(name)
    if not extension:
      log.error("can't create file without extension")
      return
    fInode = inodes.FileInode(path, id, mode=mode, comm_meta=comm_meta,
                              mtime=mtime, ctime=ctime)
    self.inodeCache[path] = fInode
    # Now create the meta info inode if the meta info file exists
    # refactoring: create the meta info inode, regardless of the
    # existence of datapath.
#    path = os.path.join(parentDir, '.' + image_name + '.meta')
#    datapath = os.path.join(flickrfsHome, '.'+id)
#    if os.path.exists(datapath):
#    size = os.path.getsize(datapath)
#    self.inodeCache[path] = FileInode(path, id)

  def getattr(self, path):
    # getattr is being called 4-6 times every second for '/'
    # Don't log those calls, as they clutter up the log file.
    if path != "/":
      logattr.debug("getattr: %s", path)
    templist = path.split('/')
    if path.startswith('/sets/'):
      templist[2] = templist[2].split(':')[0]
    elif path.startswith('/stream'):
      templist[1] = templist[1].split(':')[0]
    path = '/'.join(templist)

    inode=self.getInode(path)
    if inode:
      #log.debug("inode %s", inode)
      statTuple = (inode.mode,inode.ino,inode.dev,inode.nlink,
          inode.uid,inode.gid,inode.size,inode.atime,inode.mtime,inode.ctime)
      #log.debug("statsTuple %s", statTuple)
      return statTuple
    else:
      e = OSError("No such file"+path)
      e.errno = ENOENT
      raise e

  def readlink(self, path):
    log.debug("readlink")
    return os.readlink(path)
  
  def getdir(self, path, hidden=True):
    logattr.debug("getdir: %s", path)
    templist = []
    if hidden:
      templist = ['.', '..']
    for a in self.inodeCache.keys():
      ind = a.rindex('/')
      if path=='/':
        path=""
      if path==a[:ind]:
        name = a.split('/')[-1]
        if name=="":
          continue
        if hidden and name.startswith('.'):
          templist.append(name)
        elif not name.startswith('.'):
          templist.append(name)
    return map(lambda x: (x,0), templist)

  def unlink(self, path, online=True):
    log.debug("unlink %s", path)
    if self.inodeCache.has_key(path):
      inode = self.inodeCache.pop(path)
      # Remove the meta data file as well if it exists
      if self.inodeCache.has_key(path + ".meta"):
        self.inodeCache.pop(path + ".meta")

      typesinfo = mimetypes.guess_type(path)
      if typesinfo[0] is None or typesinfo[0].count('image')<=0:
        log.debug("unlinked non-image file %s", path)
        return

      if path.startswith('/sets/'):
        ind = path.rindex('/')
        pPath = path[:ind]
        pinode = self.getInode(pPath)
        if online:
          self.transfl.removePhotofromSet(photoId=inode.photoId, 
                                          photosetId=pinode.setId)
          log.info("photo %s removed from set", path)
      del inode
    else:
      log.error("%s is not a known file", path)
      #Dont' raise an exception. Not useful when
      #using editors like Vim. They make loads of 
      #crap buffer files
  
  def rmdir(self, path, online=True, recr=False):
    log.debug("removing %s", path)
    if self.inodeCache.has_key(path):
      for a in self.inodeCache.keys():
        if a.startswith(path+'/'):
          if recr:
            self.unlink(a, online)
          else:
            e = OSError("Directory not empty")
            e.errno = ENOTEMPTY
            raise e
    else:
      log.error("%s is not a known directory", path)
      e = OSError("No such folder"+path)
      e.errno = ENOENT
      raise e
      
    if path=='/sets' or path=='/tags' or path=='/tags/personal' \
        or path=='/tags/public' or path=='/stream':
      log.debug("attempt to remove framework file %s rejected", path)
      e = OSError("removal of folder %s not allowed" % (path))
      e.errno = EPERM
      raise e

    ind = path.rindex('/')
    pPath = path[:ind]
    inode = self.inodeCache.pop(path)
    if online and path.startswith('/sets/'):
      self.transfl.deleteSet(inode.setId)
    del inode
    pInode = self.getInode(pPath)
    pInode.nlink -= 1
    self.updateInode(pPath, pInode)
  
  def symlink(self, path, path1):
    log.debug("symlink")
    return os.symlink(path, path1)

  def rename(self, path, path1):
    log.debug("%s %s", path, path1)
    #Donot allow Vim to create a file~
    #Check for .meta in both paths
    if path.count('~')>0 or path1.count('~')>0:
      log.debug("vim enablement path entered")
      try:
        #Get inode, but _dont_ remove from cache
        inode = self.getInode(path)
        if inode is not None:
          self.inodeCache[path1] = inode
      except:
        log.debug("couldn't find inode for %s", path)
      return

    #Read from path
    inode = self.getInode(path)
    if inode is None or not hasattr(inode, 'photoId'):
      return
    fname = os.path.join(flickrfsHome, '.'+inode.photoId)
    f = open(fname, 'r')
    buf = f.read()
    f.close()

    #Now write to path1
    inode = self.getInode(path1)
    if inode is None or not hasattr(inode, 'photoId'):
      return
    fname = os.path.join(flickrfsHome, '.'+inode.photoId)
    f = open(fname, 'w')
    f.write(buf)
    f.close()
    inode.size = os.path.getsize(fname)
    self.updateInode(path1, inode)
    retinfo = self.parse(fname, inode.photoId)
    if retinfo.count('Error')>0:
      log.error(retinfo)
    
  def link(self, srcpath, destpath):
    log.debug("%s %s", srcpath, destpath)
    #Add image from stream to set, w/o retrieving
    slist = srcpath.split('/')
    sname_file = slist.pop(-1)
    dlist = destpath.split('/')
    dname_file = dlist.pop(-1)
    error = 0
    if sname_file=="" or sname_file.startswith('.'):
      error = 1
    if dname_file=="" or dname_file.startswith('.'):
      error = 1
    if not destpath.startswith('/sets/'):
      error = 1
    if error is 1:
      log.error("linking is allowed only between 2 image files")
      return
    sinode = self.getInode(srcpath)
    self._mkfile(destpath, id=sinode.id, mode=sinode.mode, 
                 comm_meta=sinode.comm_meta, mtime=sinode.mtime, 
                 ctime=sinode.ctime)
    parentPath = '/'.join(dlist)
    pinode = self.getInode(parentPath)
    if pinode.setId==0:
      try:
        pinode.setId = self.transfl.createSet(parentPath, sinode.photoId)
        self.updateInode(parentPath, pinode)
      except:
        e = OSError("Can't create a new set")
        e.errno = EIO
        raise e
    else:
      self.transfl.put2Set(pinode.setId, sinode.photoId)

  
  def chmod(self, path, mode):
    log.debug("%s %s" % path, mode)
    inode = self.getInode(path)
    typesinfo = mimetypes.guess_type(path)

    if inode.comm_meta is None:
      log.debug("chmod on directory ignored")
      return
        
    elif typesinfo[0] is None or typesinfo[0].count('image')<=0:
      
      os.chmod(path, mode)
      return

    elif self.transfl.setPerm(inode.photoId, mode, inode.comm_meta)==True:
      inode.mode = mode
      self.updateInode(path, inode)
      return
    
  def chown(self, path, user, group):
    log.debug("%s:%s %s (ignored)", user, group, path)
    
  def truncate(self, path, size):
    log.debug("%s %s", path, size)
    ind = path.rindex('/')
    name_file = path[ind+1:]

    typeinfo = mimetypes.guess_type(path)
    if typeinfo[0] is None or typeinfo[0].count('image')<=0:
      inode = self.getInode(path)
      filePath = os.path.join(flickrfsHome, '.'+inode.photoId)
      f = open(filePath, 'w+')
      return f.truncate(size)
    
  def mknod(self, path, mode, dev):
    log.debug("%s %s %s ", path, mode, dev)
    templist = path.split('/')
    name_file = templist[-1]

    if name_file.startswith('.') and name_file.count('.meta') > 0:
      # We need to handle the special case, where some meta files are being
      # created through mknod. Creation of meta files is done when adding
      # images automatically; and they should not go through mknod system call.
      # Editors like Vim, try to generate random swap files when reading
      # meta; and this should be *disallowed*.
      log.debug("mknod for meta file %s ignored", path)
      return

    if path.startswith('/sets/'):
      templist[2] = templist[2].split(':')[0]
    elif path.startswith('/stream'):
      templist[1] = templist[1].split(':')[0]
    path = '/'.join(templist)

    log.debug("modified file path %s", path)
    #Lets guess what kind of a file is this. 
    #Is it an image file? or, some other temporary file
    #created by the tools you're using. 
    typeinfo = mimetypes.guess_type(path)
    if typeinfo[0] is None or typeinfo[0].count('image') <= 0:
      f = open(os.path.join(flickrfsHome,'.'+name_file), 'w')
      f.close()
      # TODO(manishrjain): This should not be FileInode, it should rather be
      # Inode.
      self.inodeCache[path] = inodes.FileInode(path, name_file, mode=mode)
    else:
      self._mkfile(path, id="NEW", mode=mode)

  def mkdir(self, path, mode):
    log.debug("%s with mode %s", path, mode)
    if path.startswith("/tags"):
      if path.count('/')==3:   #/tags/personal (or private)/dirname ONLY
        self._mkdir(path)
        background(self.tags_thread, path)
      else:
        e = OSError("Not allowed to create directory %s" % path)
        e.errno = EACCES
        raise e
    elif path.startswith("/sets"):
      if path.count('/')==2:  #Only allow creation of new set /sets/newset
        self._mkdir(path, id=0)
          #id=0 means that not yet created online
      else:
        e = OSError("Not allowed to create directory %s" % path)
        e.errno = EACCES
        raise e
    elif path=='/stream':
      self._mkdir(path)
      background(timerThread, self.stream_thread, 
                 self.sync_stream_thread, stream_sync_int)
      
    else:
      e = OSError("Not allowed to create directory %s" % path)
      e.errno = EACCES
      raise e
      
  def utime(self, path, times):
    inode = self.getInode(path)
    inode.atime = times[0]
    inode.mtime = times[1]
    self.updateInode(path, inode)
    return 0

  def open(self, path, flags):
    log.info("%s with flags %s", path, flags)
    ind = path.rindex('/')
    name_file = path[ind+1:]
    if name_file.startswith('.') and name_file.endswith('.meta'):
      self.handleAccessToNonImage(path)
      return 0
    typesinfo = mimetypes.guess_type(path)
    if typesinfo[0] is None or typesinfo[0].count('image')<=0:
      log.debug('non-image file found %s', path)
      self.handleAccessToNonImage(path)
      return 0
    
    templist = path.split('/')
    if path.startswith('/sets/'):
      templist[2] = templist[2].split(':')[0]
    elif path.startswith('/stream'):
      templist[1] = templist[1].split(':')[0]
    path = '/'.join(templist)
    log.debug("path after modification is %s", path)
    
    inode = self.getInode(path)
    if inode.photoId=="NEW": #Just skip if new (i.e. uploading)
      return 0
    if self.imgCache.getBuffer(inode.photoId)=="":  
      log.debug("retrieving image %s from flickr", inode.photoId)
      self.imgCache.setBuffer(inode.photoId,
          str(self.transfl.getPhoto(inode.photoId)))
      inode.size = self.imgCache.getBufLen(inode.photoId)
      log.debug("size of image is %s", inode.size)
      self.updateInode(path, inode)
    return 0
    
  def read(self, path, length, offset):
    log.debug("%s offset %s length %s", path, offset, length)
    ind = path.rindex('/')
    name_file = path[ind+1:]
    if name_file.startswith('.') and name_file.endswith('.meta'):
      # Check if file is not present. If not, retrieve and 
      # create the file locally.
      buf = self.handleReadNonImage(path, length, offset)
      return buf
    typesinfo = mimetypes.guess_type(path)
    if typesinfo[0] is None or typesinfo[0].count('image')<=0:
      return self.handleReadNonImage(path, length, offset)
    return self.handleReadImage(path, length, offset)

  def parse(self, fname, photoId):
    cp = ConfigParser.ConfigParser()
    log.debug("parsing file %s for photoid %s", fname, photoId)
    cp.read(fname)
    log.debug("file %s has been read by ConfigParser", fname)
    options = cp.options('metainfo')
    title=''
    desc=''
    tags=''
    license=''
    if 'description' in options:
      desc = cp.get('metainfo', 'description')
    if 'tags' in options:
      tags = cp.get('metainfo', 'tags')
    if 'title' in options:
      title = cp.get('metainfo', 'title')
    if 'license' in options:
      license = cp.get('metainfo', 'license')
      
    log.debug("setting metadata for file %s", fname)
    if self.transfl.setMeta(photoId, title, desc)==False:
      return "Error:Can't set Meta information"
      
    log.debug("setting tags for %s", fname)
    if self.transfl.setTags(photoId, tags)==False:
      log.debug("setting tags for %s failed", fname)
      return "Error:Can't set tags"

    log.debug("setting license for %s", fname)
    if self.transfl.setLicense(photoId, license)==False:
      return "Error:Can't set license"
            
 #   except:
 #     log.error("Can't parse file:%s:"%(fname,))
 #     return "Error:Can't parse"
    return 'Success:Updated photo:%s:%s:'%(fname,photoId)

  ##################################################
  # 'handle' Functions for handling read and writes.
  ##################################################
  def handleAccessToNonImage(self, path):
    inode = self.getInode(path)
    if inode is None:
      log.error("inode %s doesn't exist", path)
      e = OSError("No inode found")
      e.errno = EIO
      raise e
    fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext
    # Handle the case when file already exists.
    if not os.path.exists(fname) or os.path.getsize(fname) == 0L:
      log.info("retrieving meta information for file %s and photo id %s", 
               fname, inode.photoId)
      INFO = self.transfl.getPhotoInfo(inode.photoId)
      size = self.writeMetaInfo(inode.photoId, INFO)
      log.info("information has been written for photo id %s", inode.photoId)
      inode.size = size
      self.updateInode(path, inode)
      time.sleep(1) # Enough time for OS to call for getattr again.
    return inode

  def handleReadNonImage(self, path, length, offset):
    inode = self.handleAccessToNonImage(path)
    f = open(os.path.join(flickrfsHome, '.'+inode.photoId), 'r')
    f.seek(offset)
    return f.read(length)
  
  def handleReadImage(self, path, length, offset):
    inode = self.getInode(path)
    if inode is None:
      log.error("inode %s doesn't exist", path)
      e = OSError("No inode found")
      e.errno = EIO
      raise e
    if self.imgCache.getBufLen(inode.photoId) is 0:  
      log.debug("retrieving image %s from flickr", inode.photoId)
      buf = retryFlickrOp(False, self.transfl.getPhoto,
                          inode.photoId)
      if len(buf) == 0:
        log.error("can't retrieve image %s", inode.photoId)
        e = OSError("Unable to retrieve image.")
        e.errno = EIO
        raise e
      self.imgCache.setBuffer(inode.photoId, buf)
      inode.size = self.imgCache.getBufLen(inode.photoId)
    temp =  self.imgCache.getBuffer(inode.photoId, offset, offset+length)
    if len(temp) < length:
      self.imgCache.popBuffer(inode.photoId)
    self.updateInode(path, inode)
    return temp

  def handleWriteToNonImage(self, path, buf, off):
    inode = self.handleAccessToNonImage(path)
    fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext
    log.debug("writing to %s", fname)
    f = open(fname, 'r+')
    f.seek(off)
    f.write(buf)
    f.close()
    if len(buf)<4096:
      inode.size = os.path.getsize(fname)
      retinfo = self.parse(fname, inode.photoId)
      if retinfo.count('Error')>0:
        e = OSError(retinfo.split(':')[1])
        e.errno = EIO
        raise e
      self.updateInode(path, inode)
    return len(buf)

  def handleUploadingImage(self, path, inode, taglist):
    tags = [ '"%s"'%(a,) for a in taglist]
    tags.append('flickrfs')
    taglist = ' '.join(tags)
    log.info('uploading %s with len %s', 
             path, self.imgCache.getBufLen(inode.photoId))
    id = None
    bufData = self.imgCache.getBuffer(inode.photoId)
    bufData = self.imageResize(bufData)
    id = retryFlickrOp(False, self.transfl.uploadfile,
                       path, taglist, bufData, inode.mode)
    if id is None:
      log.error("unable to upload file %s", inode.photoId)
      e = OSError("Unable to upload file.")
      e.errno = EIO
      raise e
    self.imgCache.popBuffer(inode.photoId)
    inode.photoId = id
    self.updateInode(path, inode)
    return inode

  def handleWriteToBuffer(self, path, buf):
    inode = self.getInode(path)
    if inode is None:
      log.error("inode %s doesn't exist", path)
      e = OSError("No inode found")
      e.errno = EIO
      raise e
    self.imgCache.addBuffer(inode.photoId, buf)
    return inode

  def handleWriteAddToSet(self, parentPath, pinode, inode):
    #Create set if it doesn't exist online (i.e. if id=0)
    if pinode.setId is 0:
      # Retry creation of set if unsuccessful.
      pinode.setId = retryFlickrOp(False, self.transfl.createSet,
                                   parentPath, inode.photoId)
      # If the set is created, then return.
      if pinode.setId is not None:
        self.updateInode(parentPath, pinode)
        return
      else:
        log.error("unable to create set %s", parentPath)
        e = OSError("Unable to create set.")
        e.errno = EIO
        raise e
    else:
      # If the operation put2Set doesn't throw exception, that means
      # that the picture has been successfully added to set.
      # Return in that case, retry otherwise.
      retryFlickrOp(True, self.transfl.put2Set,
                    pinode.setId, inode.photoId)
      return

  #############################
  # End of 'handle' Functions.
  #############################

  def write(self, path, buf, off):
    log.debug("write to %s at offset %s", path, off)
    ind = path.rindex('/')
    name_file = path[ind+1:]
    if name_file.startswith('.') and name_file.count('.meta')>0:
      return self.handleWriteToNonImage(path, buf, off)
    typesinfo = mimetypes.guess_type(path)
    if typesinfo[0] is None or typesinfo[0].count('image')<=0:
      return self.handleWriteToNonImage(path, buf, off)
    templist = path.split('/')
    inode = None
    if path.startswith('/tags'):
      e = OSError("Copying to tags not allowed")
      e.errno = EIO
      raise e
    if path.startswith('/stream'):
      tags = templist[1].split(':')
      templist[1] = tags.pop(0)
      path = '/'.join(templist)
      inode = self.handleWriteToBuffer(path, buf)
      if len(buf) < 4096:
        self.handleUploadingImage(path, inode, tags)
    elif path.startswith('/sets/'):
      setnTags = templist[2].split(':')
      setName = setnTags.pop(0)
      templist[2] = setName
      path = '/'.join(templist)
      inode = self.handleWriteToBuffer(path, buf)
      if len(buf) < 4096:
        templist.pop(-1)
        parentPath = '/'.join(templist)
        pinode = self.getInode(parentPath)
        inode = self.handleUploadingImage(path, inode, setnTags)
        self.handleWriteAddToSet(parentPath, pinode, inode)
    log.debug("done write to %s at offset %s", path, off)
    if len(buf)<4096:
      templist = path.split('/')
      templist.pop(-1)
      parentPath = '/'.join(templist)
      try:
        self.inodeCache.pop(path)
      except:
        pass
      INFO = self.transfl.getPhotoInfo(inode.photoId)
      info = self.transfl.parseInfoFromFullInfo(inode.photoId, INFO)
      self._mkfileWithMeta(parentPath, info)
      self.writeMetaInfo(inode.photoId, INFO)
    return len(buf)

  def getInode(self, path):
    if self.inodeCache.has_key(path):
      #log.debug("got cached inode for %s", path)
      return self.inodeCache[path]
    else:
      #log.debug("%s is not in inode cache", path)
      return None

  def updateInode(self, path, inode):
    self.inodeCache[path] = inode

  def release(self, path, flags):
    log.debug("%s with flags %s ignored", path, flags)
    return 0
  
  def statfs(self):
    """
  Should return a tuple with the following elements in respective order:
  
  F_BSIZE - Preferred file system block size. (int)
  F_FRSIZE - Fundamental file system block size. (int)
  F_BLOCKS - Total number of blocks in the filesystem. (long)
  F_BFREE - Total number of free blocks. (long)
  F_BAVAIL - Free blocks available to non-super user. (long)
  F_FILES - Total number of file nodes. (long)
  F_FFREE - Total number of free file nodes. (long)
  F_FAVAIL - Free nodes available to non-super user. (long)
  F_FLAG - Flags. System dependent: see statvfs() man page. (int)
  F_NAMEMAX - Maximum file name length. (int)
  Feel free to set any of the above values to 0, which tells
  the kernel that the info is not available.
    """
    block_size = 1024
    blocks = 0L
    blocks_free = 0L
    files = 0L
    files_free = 0L
    namelen = 255
    # statfs is being called repeatedly at least once a second.
    # The bandwidth information doesn't change that often, so
    # save upon communication with flickr servers to retrieve this
    # information. Only retrieve it once in a while.
    if self.statfsCounter >= 500 or self.statfsCounter is -1:
      (self.max, self.used) = self.transfl.getBandwidthInfo()
      self.statfsCounter = 0
      log.info('retrieved bandwidth info: max %s used %s', 
               self.max, self.used)
    self.statfsCounter = self.statfsCounter + 1

    if self.max is not None:
      blocks = long(self.max)/block_size
      blocks_used = long(self.used)/block_size
      blocks_free = blocks - blocks_used
      blocks_available = blocks_free
      return (block_size, blocks, blocks_free, blocks_available, 
              files, files_free, namelen)

  def fsync(self, path, isfsyncfile):
    log.debug("%s,  isfsyncfile=%s", path, isfsyncfile)
    return 0
Beispiel #2
-1
  def __init__(self, *args, **kw):
  
    Fuse.__init__(self, *args, **kw)
    log.info("mountpoint: %s", repr(self.mountpoint))
    log.info("mount options: %s", ', '.join(self.optlist))
    log.info("named mount options: %s",
        ', '.join([ "%s: %s" % (k, v) for k, v in self.optdict.items() ]))
    
    self.inodeCache = inodes.InodeCache(dbPath) # Inodes need to be stored.
    self.imgCache = inodes.ImageCache()
    self.NSID = ""
    self.transfl = TransFlickr(browserName)

    # Set some variables to be utilized by statfs function.
    self.statfsCounter = -1
    self.max = 0L
    self.used = 0L

    self.NSID = self.transfl.getUserId()
    if self.NSID is None:
      log.error("can't retrieve user information")
      sys.exit(-1)

    log.info('getting list of licenses available')
    self.licenses = self.transfl.getLicenses()
    if self.licenses is None:
      log.error("can't retrieve license information")
      sys.exit(-1)

    # do stuff to set up your filesystem here, if you want
    self._mkdir("/")
    self._mkdir("/tags")
    self._mkdir("/tags/personal")
    self._mkdir("/tags/public")
    background(timerThread, self.sets_thread, 
               self.sync_sets_thread, sets_sync_int) #sync every 2 minutes