#!/usr/bin/env python
#
# This requires the python FUSE bindings, and the FUSE module loaded in your kernel.
#
# Linux: modprobe fuse
# FreeBSD: kldload fuse
# 
# This is not a 100% implementation of all fuse functions. Only writing and
# creating new files is supported.
#
# Author: jls@semicomplete.com

import os, stat, errno
import fuse
import urllib
import libxml2
import time
import base64

class tastystat(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

class delicious(object):
  user = "YOUR_DELICIOUS_USER"
  password = "YOUR PASSWORD"

  _instance = None

  def __init__(self):
    self.myfiles = None
    self.lastupdate = 0
    self.cachethreshold = 10

  def singleton(self):
    if not self._instance:
      self._instance = delicious()
    return self._instance
  singleton = classmethod(singleton)

  def getfiles(self):
    if time.time() - self.lastupdate < self.cachethreshold and self.myfiles:
      return self.myfiles
    fd = urllib.urlopen("https://%s:%s@api.del.icio.us/v1/posts/all?tag=tastydrive" % (self.user, self.password))
    data = fd.read()
    doc = libxml2.parseMemory(data, len(data))
    files = {}
    for post in doc.xpathEval("/posts/post"):
      url = [x for x in post.xpathEval("@href")][0].content
      filename = [x for x in post.xpathEval("@description")][0].content
      files.update({ "%s" % filename: url})
    self.myfiles = files.copy()
    self.lastupdate = time.time()
    return files 

  def savefile(self, file, data):
    #url = urllib.quote("data:application/octet-stream;base64,%s" % b64)
    #url = "data:application/octet-stream;base64,%s" % b64
    #url = "data:text/plain;base64,%s" % b64

    types = {
      "png": "image/png", 
      "jpg": "image/jpeg",
      "doc": "application/msword",
      "ppt": "application/vnd.ms-powerpoint",
      "html": "text/html",
      "mp3": "audio/mpeg",
    }

    if '.' in file:
      ext = file.split(".")[-1]
    else:
      ext = ""

    redir = "http://wzus.ask.com/r?t=p&url=&u="
    #url = urllib.quote("data:%s;base64,%s" % (types.get(ext, "text/plain"), b64))

    print "Saving data: %s (%d bytes)" % (file, len(data))
    shard = 0
    b64data = urllib.quote(base64.b64encode(data))
    print "b64 len: %d" % len(b64data)
    while len(b64data) > 0:
      print "b64 len: %d" % len(b64data)
      #length = len(data) > 60000 and 60000 or len(data)
      length = 60000
      piece = b64data[:length]
      #b64 = urllib.quote(base64.b64encode(piece))
      #url = "http://%s?%s" % (types.get(ext, "text/plain"), b64)
      url = "http://chunk/%s" % (piece)
      args = {
        "description": "%d:%s" % (shard, file),
        "tags": "tastydrive:%s" % (file),
        "url": url,
      }
      print "  %s: Saving shard %d" % (file, shard)
      fd = urllib.urlopen("https://%s:%s@api.del.icio.us/v1/posts/add" % (self.user, self.password),
                          urllib.urlencode(args))
      fd.read()
      fd.close()
      b64data = b64data[len(piece):]
      shard += 1

    # Write bootstrap shard
    ctype = types.get(ext, "text/plain")
    bootstrap = """ 
<html>
  <head>
    <script src="http://del.icio.us/feeds/json/YOUR_USERNAME/tastydrive:%(file)s?count=100"></script>
    <script>
      function sorter(a,b) {
        return parseInt(a.d.split(".")[0]) - parseInt(b.d.split(".")[0])
      }
      ctype = "%(ctype)s";
      data = ""
      posts = Delicious.posts.sort(sorter);
      for (var i = 0, post; post = posts[i]; i++) {
        data += post.u.substring(13); 
        document.writeln("["+data.length+" total] Chunk " + i + " size: " + post.u.length + "<br>")
      }
      url = "data:"+ctype+";base64,"+data;
      document.location = url;
    </script>
  </head>
  <body>
  </body>
</html>
""" % ({ "file": file, "ctype": ctype })
    b64strap = urllib.quote(base64.b64encode(bootstrap))
    
    #ctype = types.get(ext, "text/plain")
    url = "data:text/html;base64,%s" % (b64strap)
    redir = "http://wzus.ask.com/r?t=p&url=&u=%s" % url
    print "Redir url size: %d" % len(redir)
    args = {
      "description": "%s" % (file),
      "tags": "tastydrive",
      "url": redir,
    }
    fd = urllib.urlopen("https://%s:%s@api.del.icio.us/v1/posts/add" % (self.user, self.password),
                        urllib.urlencode(args))
    fd.read()
    fd.close()


  def addfile(self, file):
    self.myfiles[file] = ""
    self.savefile(file, "")

class TastyDriveFS(fuse.Fuse):
  openfiles = {}
  lastcheck = 0

  def getattr(self, path):
    st = tastystat()
    d = delicious.singleton()
    files = d.getfiles()
    print "path: %s" % path
    if path == '/':
      st.st_mode = stat.S_IFDIR | 0755
      st.st_nlink = 2
    elif path[1:] in files:
      # ... check path here for delicious name description
      st.st_mode = stat.S_IFREG | 0644
      st.st_nlink = 1
      st.st_size = len(files[path[1:]])
    else:
      return -errno.ENOENT
    return st

  def readdir(self, path, offset):
    contents = ['.', '..']
    contents.extend([x for x in delicious.singleton().getfiles()])
    for r in  contents:
      yield fuse.Direntry(r)

  def open(self, path, flags):
    path = path[1:]
    d = delicious.singleton()
    files = d.getfiles()
    if path not in files:
      return -errno.ENOENT

    if flags & (os.O_WRONLY | os.O_RDWR | os.O_CREAT):
      # I am *far* too lazy to implement any kind of multiaccessness for writes.
      if path in self.openfiles:
        return -errno.EBUSY

      self.openfiles[path] = files.get(path, "")

  def read(self, path, size, offset):
    path = path[1:]
    d = delicious.singleton()
    files = d.getfiles()
    print "Path (read): %s" % path
    if path not in files:
      return -errno.ENOENT
    slen = len(files[path])
    if offset < slen:
      if offset + size > slen:
        size = slen - offset
      buf = files[path][offset:offset+size]
    else:
      buf = ''
    return buf

  def truncate(self, path, size):
    print "Truncated %s" % path
    self.openfiles[path[1:]] = ""

  def release(self, path, flags):
    # Save to delicious
    path = path[1:]

    if path in self.openfiles:
      delicious.singleton().savefile(path, self.openfiles[path])
      del self.openfiles[path]
    
  def write(self, path, buf, offset):
    path = path[1:]
    data = self.openfiles[path]
    if offset < len(data):
      # do something?
      pass
    else:
      data = "%s%s%s" % (data, "X" * (offset - len(data)), buf)
    self.openfiles[path] = data
    return len(buf)
    #return -errno.ENOSYS

  def mknod ( self, path, mode, dev ):
    print "mknod"
    delicious.singleton().addfile(path[1:])


def main():
  usage="""
Userspace hello example

""" + fuse.Fuse.fusage
  server = TastyDriveFS(version="%prog " + fuse.__version__,
           usage=usage,
           dash_s_do='setsingle')

  server.parse(errex=1)
  server.main()

if __name__ == '__main__':
  main()
