class CouchFSDocument(fuse.Fuse): def __init__(self, mountpoint, uri=None, *args, **kwargs): fuse.Fuse.__init__(self, *args, **kwargs) db_uri, doc_id = uri.rsplit('/', 1) self.doc_id = unquote(doc_id) self.db = Database(db_uri) def get_dirs(self): dirs = {} attachments = self.db[self.doc_id].get('_attachments', {}).keys() for att in attachments: parents = [u''] for name in att.split('/'): filenames = dirs.setdefault(u'/'.join(parents[1:]), set()) if name != COUCHFS_DIRECTORY_PLACEHOLDER: filenames.add(name) parents.append(name) return dirs def readdir(self, path, offset): path = _normalize_path(path) for r in '.', '..': yield fuse.Direntry(r) for name in self.get_dirs().get(path, []): yield fuse.Direntry(name.encode('utf-8')) def getattr(self, path): path = _normalize_path(path) try: st = CouchStat() if path == '' or path in self.get_dirs().keys(): st.st_mode = stat.S_IFDIR | 0775 st.st_nlink = 2 else: att = self.db[self.doc_id].get('_attachments', {}) data = att[path] st.st_mode = stat.S_IFREG | 0664 st.st_nlink = 1 st.st_size = data['length'] return st except (KeyError, ResourceNotFound): return -errno.ENOENT def open(self, path, flags): path = _normalize_path(path) try: #data = self.db.get_attachment(self.db[self.doc_id], path.split('/')[-1]) #att = self.db[self.doc_id].get('_attachments', {}) #data = att[path.split('/')[-1]] parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts if filename in self.get_dirs()[dirname]: return 0 return -errno.ENOENT except (KeyError, ResourceNotFound): return -errno.ENOENT #accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR #if (flags & accmode) != os.O_RDONLY: # return -errno.EACCES def read(self, path, size, offset): path = _normalize_path(path) try: data = self.db.get_attachment(self.db[self.doc_id], path) if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" slen = len(data) if offset < slen: if offset + size > slen: size = slen - offset buf = data[offset:offset+size] else: buf = '' return buf except (KeyError, ResourceNotFound): pass return -errno.ENOENT def write(self, path, buf, offset): path = _normalize_path(path) try: data = self.db.get_attachment(self.db[self.doc_id], path) if data is None: data = "" elif isinstance(data, cStringIO.InputType): data = data.getvalue() data = data[0:offset] + buf + data[offset+len(buf):] self.db.put_attachment(self.db[self.doc_id], data, filename=path) return len(buf) except (KeyError, ResourceNotFound): pass return -errno.ENOENT def mknod(self, path, mode, dev): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=path) def unlink(self, path): path = _normalize_path(path) parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts self.db.delete_attachment(self.db[self.doc_id], path) if filename != COUCHFS_DIRECTORY_PLACEHOLDER and len(self.get_dirs().get(dirname, [])) == 0: print "putting to:", u'%s/%s' % (dirname, COUCHFS_DIRECTORY_PLACEHOLDER) self.db.put_attachment(self.db[self.doc_id], u'', filename=u'%s/%s' % (dirname, COUCHFS_DIRECTORY_PLACEHOLDER)) def truncate(self, path, size): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=path) return 0 def utime(self, path, times): return 0 def mkdir(self, path, mode): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=u'%s/%s' % (path, COUCHFS_DIRECTORY_PLACEHOLDER)) return 0 def rmdir(self, path): path = _normalize_path(path) self.db.delete_attachment(self.db[self.doc_id], u'%s/%s' % (path, COUCHFS_DIRECTORY_PLACEHOLDER)) return 0 def rename(self, pathfrom, pathto): pathfrom, pathto = _normalize_path(pathfrom), _normalize_path(pathto) data = self.db.get_attachment(self.db[self.doc_id], pathfrom) if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" self.db.put_attachment(self.db[self.doc_id], data, filename=pathto) self.db.delete_attachment(self.db[self.doc_id], pathfrom) return 0 def fsync(self, path, isfsyncfile): return 0 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 blocks = 1024 * 1024 blocks_free = blocks blocks_avail = blocks_free files = 0 files_free = 0 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
class CouchFSDocument(fuse.Fuse): def __init__(self, mountpoint, uri=None, *args, **kwargs): fuse.Fuse.__init__(self, *args, **kwargs) db_uri, doc_id = uri.rsplit('/', 1) self.doc_id = unquote(doc_id) self.db = Database(db_uri) def get_dirs(self): dirs = {} attachments = self.db[self.doc_id].get('_attachments', {}).keys() for att in attachments: parents = [u''] for name in att.split('/'): filenames = dirs.setdefault(u'/'.join(parents[1:]), set()) if name != COUCHFS_DIRECTORY_PLACEHOLDER: filenames.add(name) parents.append(name) return dirs def readdir(self, path, offset): path = _normalize_path(path) for r in '.', '..': yield fuse.Direntry(r) for name in self.get_dirs().get(path, []): yield fuse.Direntry(name.encode('utf-8')) def getattr(self, path): path = _normalize_path(path) try: st = CouchStat() if path == '' or path in self.get_dirs().keys(): st.st_mode = stat.S_IFDIR | 0775 st.st_nlink = 2 else: att = self.db[self.doc_id].get('_attachments', {}) data = att[path] st.st_mode = stat.S_IFREG | 0664 st.st_nlink = 1 st.st_size = data['length'] return st except (KeyError, ResourceNotFound): return -errno.ENOENT def open(self, path, flags): path = _normalize_path(path) try: #data = self.db.get_attachment(self.db[self.doc_id], path.split('/')[-1]) #att = self.db[self.doc_id].get('_attachments', {}) #data = att[path.split('/')[-1]] parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts if filename in self.get_dirs()[dirname]: return 0 return -errno.ENOENT except (KeyError, ResourceNotFound): return -errno.ENOENT #accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR #if (flags & accmode) != os.O_RDONLY: # return -errno.EACCES def read(self, path, size, offset): path = _normalize_path(path) try: data = self.db.get_attachment(self.db[self.doc_id], path) if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" slen = len(data) if offset < slen: if offset + size > slen: size = slen - offset buf = data[offset:offset + size] else: buf = '' return buf except (KeyError, ResourceNotFound): pass return -errno.ENOENT def write(self, path, buf, offset): path = _normalize_path(path) try: data = self.db.get_attachment(self.db[self.doc_id], path) if data is None: data = "" elif isinstance(data, cStringIO.InputType): data = data.getvalue() data = data[0:offset] + buf + data[offset + len(buf):] self.db.put_attachment(self.db[self.doc_id], data, filename=path) return len(buf) except (KeyError, ResourceNotFound): pass return -errno.ENOENT def mknod(self, path, mode, dev): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=path) def unlink(self, path): path = _normalize_path(path) parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts self.db.delete_attachment(self.db[self.doc_id], path) if filename != COUCHFS_DIRECTORY_PLACEHOLDER and len( self.get_dirs().get(dirname, [])) == 0: print "putting to:", u'%s/%s' % (dirname, COUCHFS_DIRECTORY_PLACEHOLDER) self.db.put_attachment(self.db[self.doc_id], u'', filename=u'%s/%s' % (dirname, COUCHFS_DIRECTORY_PLACEHOLDER)) def truncate(self, path, size): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=path) return 0 def utime(self, path, times): return 0 def mkdir(self, path, mode): path = _normalize_path(path) self.db.put_attachment(self.db[self.doc_id], u'', filename=u'%s/%s' % (path, COUCHFS_DIRECTORY_PLACEHOLDER)) return 0 def rmdir(self, path): path = _normalize_path(path) self.db.delete_attachment( self.db[self.doc_id], u'%s/%s' % (path, COUCHFS_DIRECTORY_PLACEHOLDER)) return 0 def rename(self, pathfrom, pathto): pathfrom, pathto = _normalize_path(pathfrom), _normalize_path(pathto) data = self.db.get_attachment(self.db[self.doc_id], pathfrom) if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" self.db.put_attachment(self.db[self.doc_id], data, filename=pathto) self.db.delete_attachment(self.db[self.doc_id], pathfrom) return 0 def fsync(self, path, isfsyncfile): return 0 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 blocks = 1024 * 1024 blocks_free = blocks blocks_avail = blocks_free files = 0 files_free = 0 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
class CouchFSDatabase(fuse.Fuse): def __init__(self, mountpoint, db_uri=None, *args, **kwargs): print "db_uri: " + repr(db_uri) fuse.Fuse.__init__(self, *args, **kwargs) self.db = Database(db_uri) def readdir(self, path, offset): print self.db path = _path_to_docid(path) print "readdir: %r" % path for r in '.', '..': yield fuse.Direntry(r) if path == "": startkey = "+,\0" endkey = "+" + chr(ord(",")+1) else: startkey = "+" + path + "/" endkey = "+" + path + chr(ord("/")+1) for row in self.db.view('_all_docs', startkey=startkey, endkey=endkey).rows: cpath = row.key #TODO unescape, if necessary print repr(cpath) if "," in cpath: prefix, cpath = cpath.split(",", 1) if "/" in cpath: dirname, name = cpath.rsplit("/", 1) else: name = cpath yield fuse.Direntry(name.encode('utf-8')) def children_count(self, docid): print self.db print "children_count: %r" % docid if docid == "": startkey = "+,\0" endkey = "+" + chr(ord(",")+1) else: startkey = "+" + docid + "/" endkey = "+" + docid + chr(ord("/")+1) return 2 + self.db.view('_all_docs', startkey=startkey, endkey=endkey, limit=0).total_rows def getattr(self, path): path = _path_to_docid(path) print "getattr: %r, %r" % (path, path in self.db) try: st = CouchStat() if path == '': #TODO we should create a "." document for the root directory st.st_mode = stat.S_IFDIR | 0775 st.st_nlink = self.children_count(path) return st doc = self.db[path] except (KeyError, ResourceNotFound): return -errno.ENOENT if doc["type"] == "dir": st.st_mode = stat.S_IFDIR | doc["mode"] st.st_nlink = self.children_count(path) st.st_mtime = doc["mtime"] else: data = self.db[path].get('_attachments', {}).get("content", {"length":0}) print repr(data) print repr(doc) st.st_mode = stat.S_IFREG | doc["mode"] st.st_nlink = 1 st.st_size = data['length'] st.st_mtime = doc["mtime"] return st def open(self, path, flags): path = _path_to_docid(path) try: doc = self.db[path] #TODO check mode #TODO should we allow open for directories? if doc: return 0 else: return -errno.ENOENT except (KeyError, ResourceNotFound): return -errno.ENOENT #accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR #if (flags & accmode) != os.O_RDONLY: # return -errno.EACCES def read(self, path, size, offset): path = _path_to_docid(path) try: data = self.db.get_attachment(self.db[path], "content") if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" slen = len(data) if offset < slen: if offset + size > slen: size = slen - offset buf = data[offset:offset+size] else: buf = '' return buf except (KeyError, ResourceNotFound): pass return -errno.ENOENT def write(self, path, buf, offset): path = _path_to_docid(path) try: #TODO can we push some Javascript that does the change on the server? # -> http://wiki.apache.org/couchdb/Document_Update_Handlers self._update_mtime(path) data = self.db.get_attachment(self.db[path], "content") if data is None: data = "" elif isinstance(data, cStringIO.InputType): data = data.getvalue() data = data[0:offset] + buf + data[offset+len(buf):] self.db.put_attachment(self.db[path], data, filename="content") return len(buf) except (KeyError, ResourceNotFound): pass return -errno.ENOENT def _current_time(self): return int(time()) def _update_mtime(self, docid): doc = self.db[docid] doc["mtime"] = self._current_time() self.db.save(doc) def _update_mtime_for_parent(self, path): if "/" in path: parent, name = path.rsplit("/", 1) self._update_mtime(_path_to_docid(parent)) else: #TODO update root directory unless normalized path is "" pass def mknod(self, path, mode, dev): if (mode & stat.S_IFREG) != 0: type = "file" else: type = "special" path = _path_to_docid(path) self.db.save({ "_id": path, "type": type, "mode": mode, "dev": dev, "mtime": self._current_time() }) if type == "file": self.db.put_attachment(self.db[path], u'', filename="content") return 0 def unlink(self, path): path = _path_to_docid(path) del self.db[path] # recreate directory placeholder, if necessary parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts def truncate(self, path, size): path = _path_to_docid(path) self.db.put_attachment(self.db[path], u'', filename="content") return 0 def utime(self, path, times): return 0 def mkdir(self, path, mode): path = _path_to_docid(path) if path in self.db: return -errno.EACCES self.db.save({ "_id": path, "type": "dir", "mode": mode, "mtime": self._current_time() }) return 0 def rmdir(self, path): path = _path_to_docid(path) #TODO don't delete non-empty directories if path not in self.db: return -errno.ENOENT elif self.db[path]["type"] != "dir": return -errno.EACCES del self.db[path] return 0 def rename(self, pathfrom, pathto): #TODO use self.db.copy(...) pathfrom, pathto = _path_to_docid(pathfrom), _path_to_docid(pathto) doc = self.db[pathfrom].clone() doc["_id"] = pathto self.db.save(doc) data = self.db.get_attachment(self.db[pathfrom], "content") if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" self.db.put_attachment(self.db[pathto], data, filename="content") del self.db[pathfrom] return 0 def fsync(self, path, isfsyncfile): self.db.commit() return 0 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 blocks = 1024 * 1024 blocks_free = blocks blocks_avail = blocks_free files = 0 files_free = 0 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
class CouchFSDatabase(fuse.Fuse): def __init__(self, mountpoint, db_uri=None, *args, **kwargs): print "db_uri: " + repr(db_uri) fuse.Fuse.__init__(self, *args, **kwargs) self.db = Database(db_uri) def readdir(self, path, offset): print self.db path = _path_to_docid(path) print "readdir: %r" % path for r in '.', '..': yield fuse.Direntry(r) if path == "": startkey = "+,\0" endkey = "+" + chr(ord(",") + 1) else: startkey = "+" + path + "/" endkey = "+" + path + chr(ord("/") + 1) for row in self.db.view('_all_docs', startkey=startkey, endkey=endkey).rows: cpath = row.key #TODO unescape, if necessary print repr(cpath) if "," in cpath: prefix, cpath = cpath.split(",", 1) if "/" in cpath: dirname, name = cpath.rsplit("/", 1) else: name = cpath yield fuse.Direntry(name.encode('utf-8')) def children_count(self, docid): print self.db print "children_count: %r" % docid if docid == "": startkey = "+,\0" endkey = "+" + chr(ord(",") + 1) else: startkey = "+" + docid + "/" endkey = "+" + docid + chr(ord("/") + 1) return 2 + self.db.view( '_all_docs', startkey=startkey, endkey=endkey, limit=0).total_rows def getattr(self, path): path = _path_to_docid(path) print "getattr: %r, %r" % (path, path in self.db) try: st = CouchStat() if path == '': #TODO we should create a "." document for the root directory st.st_mode = stat.S_IFDIR | 0775 st.st_nlink = self.children_count(path) return st doc = self.db[path] except (KeyError, ResourceNotFound): return -errno.ENOENT if doc["type"] == "dir": st.st_mode = stat.S_IFDIR | doc["mode"] st.st_nlink = self.children_count(path) st.st_mtime = doc["mtime"] else: data = self.db[path].get('_attachments', {}).get("content", {"length": 0}) print repr(data) print repr(doc) st.st_mode = stat.S_IFREG | doc["mode"] st.st_nlink = 1 st.st_size = data['length'] st.st_mtime = doc["mtime"] return st def open(self, path, flags): path = _path_to_docid(path) try: doc = self.db[path] #TODO check mode #TODO should we allow open for directories? if doc: return 0 else: return -errno.ENOENT except (KeyError, ResourceNotFound): return -errno.ENOENT #accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR #if (flags & accmode) != os.O_RDONLY: # return -errno.EACCES def read(self, path, size, offset): path = _path_to_docid(path) try: data = self.db.get_attachment(self.db[path], "content") if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" slen = len(data) if offset < slen: if offset + size > slen: size = slen - offset buf = data[offset:offset + size] else: buf = '' return buf except (KeyError, ResourceNotFound): pass return -errno.ENOENT def write(self, path, buf, offset): path = _path_to_docid(path) try: #TODO can we push some Javascript that does the change on the server? # -> http://wiki.apache.org/couchdb/Document_Update_Handlers self._update_mtime(path) data = self.db.get_attachment(self.db[path], "content") if data is None: data = "" elif isinstance(data, cStringIO.InputType): data = data.getvalue() data = data[0:offset] + buf + data[offset + len(buf):] self.db.put_attachment(self.db[path], data, filename="content") return len(buf) except (KeyError, ResourceNotFound): pass return -errno.ENOENT def _current_time(self): return int(time()) def _update_mtime(self, docid): doc = self.db[docid] doc["mtime"] = self._current_time() self.db.save(doc) def _update_mtime_for_parent(self, path): if "/" in path: parent, name = path.rsplit("/", 1) self._update_mtime(_path_to_docid(parent)) else: #TODO update root directory unless normalized path is "" pass def mknod(self, path, mode, dev): if (mode & stat.S_IFREG) != 0: type = "file" else: type = "special" path = _path_to_docid(path) self.db.save({ "_id": path, "type": type, "mode": mode, "dev": dev, "mtime": self._current_time() }) if type == "file": self.db.put_attachment(self.db[path], u'', filename="content") return 0 def unlink(self, path): path = _path_to_docid(path) del self.db[path] # recreate directory placeholder, if necessary parts = path.rsplit(u'/', 1) if len(parts) == 1: dirname, filename = u'', parts[0] else: dirname, filename = parts def truncate(self, path, size): path = _path_to_docid(path) self.db.put_attachment(self.db[path], u'', filename="content") return 0 def utime(self, path, times): return 0 def mkdir(self, path, mode): path = _path_to_docid(path) if path in self.db: return -errno.EACCES self.db.save({ "_id": path, "type": "dir", "mode": mode, "mtime": self._current_time() }) return 0 def rmdir(self, path): path = _path_to_docid(path) #TODO don't delete non-empty directories if path not in self.db: return -errno.ENOENT elif self.db[path]["type"] != "dir": return -errno.EACCES del self.db[path] return 0 def rename(self, pathfrom, pathto): #TODO use self.db.copy(...) pathfrom, pathto = _path_to_docid(pathfrom), _path_to_docid(pathto) doc = self.db[pathfrom].clone() doc["_id"] = pathto self.db.save(doc) data = self.db.get_attachment(self.db[pathfrom], "content") if isinstance(data, cStringIO.InputType): data = data.getvalue() elif data is None: data = "" self.db.put_attachment(self.db[pathto], data, filename="content") del self.db[pathfrom] return 0 def fsync(self, path, isfsyncfile): self.db.commit() return 0 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 blocks = 1024 * 1024 blocks_free = blocks blocks_avail = blocks_free files = 0 files_free = 0 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