-import os, shutil
+import os
from zope.interface import implements
from foolscap import Referenceable
from allmydata.interfaces import RIMutableDirectoryNode
+from allmydata.util import bencode, idlib
+from allmydata.util.assertutil import _assert
from twisted.application import service
from twisted.python import log
-class DeadDirectoryNodeError(Exception):
- """The directory referenced by this node has been deleted."""
-
-class BadDirectoryError(Exception):
- """There was a problem with the directory being referenced."""
-class BadFileError(Exception):
- """The file being referenced does not exist."""
class BadNameError(Exception):
"""Bad filename component"""
+class BadFileError(Exception):
+ pass
+
+class BadDirectoryError(Exception):
+ pass
+
class MutableDirectoryNode(Referenceable):
+ """I represent a single directory.
+
+ I am associated with a file on disk, using a randomly-generated (and
+ hopefully unique) name. This file contains a serialized dictionary which
+ maps child names to 'child specifications'. These specifications are
+ tuples, either of ('file', URI), or ('subdir', nodename).
+ """
+
implements(RIMutableDirectoryNode)
- def __init__(self, basedir):
+ def __init__(self, basedir, name=None):
self._basedir = basedir
+ if name:
+ self._name = name
+ # for well-known nodes, make sure they exist
+ try:
+ ignored = self._read_from_file()
+ except EnvironmentError:
+ self._write_to_file({})
+ else:
+ self._name = self.create_filename()
+ self._write_to_file({}) # start out empty
+
+ def make_subnode(self, name=None):
+ return self.__class__(self._basedir, name)
+
+ def _read_from_file(self):
+ f = open(os.path.join(self._basedir, self._name), "rb")
+ data = f.read()
+ f.close()
+ children_specifications = bencode.bdecode(data)
+ children = {}
+ for k,v in children_specifications.items():
+ nodetype = v[0]
+ if nodetype == "file":
+ (uri, ) = v[1:]
+ child = uri
+ elif nodetype == "subdir":
+ (nodename, ) = v[1:]
+ child = self.make_subnode(nodename)
+ else:
+ _assert("Unknown nodetype in node specification %s" % (v,))
+ children[k] = child
+ return children
+
+ def _write_to_file(self, children):
+ children_specifications = {}
+ for k,v in children.items():
+ if isinstance(v, MutableDirectoryNode):
+ child = ("subdir", v._name)
+ else:
+ assert isinstance(v, str)
+ child = ("file", v) # URI
+ children_specifications[k] = child
+ data = bencode.bencode(children_specifications)
+ f = open(os.path.join(self._basedir, self._name), "wb")
+ f.write(data)
+ f.close()
- def make_subnode(self, basedir):
- return self.__class__(basedir)
+
+ def create_filename(self):
+ return idlib.b2a(os.urandom(8))
def validate_name(self, name):
if name == "." or name == ".." or "/" in name:
# these are the public methods, available to anyone who holds a reference
def list(self):
- log.msg("Dir(%s).list" % self._basedir)
- results = []
- if not os.path.isdir(self._basedir):
- raise DeadDirectoryNodeError("This directory has been deleted")
- for name in os.listdir(self._basedir):
- absname = os.path.join(self._basedir, name)
- if os.path.isdir(absname):
- results.append( (name, self.make_subnode(absname)) )
- elif os.path.isfile(absname):
- f = open(absname, "rb")
- data = f.read()
- f.close()
- results.append( (name, data) )
- # anything else is ignored
+ log.msg("Dir(%s).list()" % self._name)
+ children = self._read_from_file()
+ results = list(children.items())
return sorted(results)
remote_list = list
def get(self, name):
+ log.msg("Dir(%s).get(%s)" % (self._name, name))
self.validate_name(name)
- absname = os.path.join(self._basedir, name)
- if os.path.isdir(absname):
- return self.make_subnode(absname)
- elif os.path.isfile(absname):
- f = open(absname, "rb")
- data = f.read()
- f.close()
- return data
- else:
- raise BadFileError("there is nothing named '%s' in this directory"
- % name)
+ children = self._read_from_file()
+ if name not in children:
+ raise BadFileError("no such child")
+ return children[name]
remote_get = get
def add_directory(self, name):
self.validate_name(name)
- absname = os.path.join(self._basedir, name)
- if os.path.isdir(absname):
- raise BadDirectoryError("the directory '%s' already exists" % name)
- if os.path.exists(absname):
- raise BadDirectoryError("the directory '%s' already exists "
- "(but isn't a directory)" % name)
- os.mkdir(absname)
- return self.make_subnode(absname)
+ children = self._read_from_file()
+ if name in children:
+ raise BadDirectoryError("the directory already existed")
+ children[name] = child = self.make_subnode()
+ self._write_to_file(children)
+ return child
remote_add_directory = add_directory
def add_file(self, name, uri):
self.validate_name(name)
- f = open(os.path.join(self._basedir, name), "wb")
- f.write(uri)
- f.close()
+ children = self._read_from_file()
+ children[name] = uri
+ self._write_to_file(children)
remote_add_file = add_file
def remove(self, name):
self.validate_name(name)
- absname = os.path.join(self._basedir, name)
- if os.path.isdir(absname):
- shutil.rmtree(absname)
- elif os.path.isfile(absname):
- os.unlink(absname)
- else:
- raise BadFileError("Cannot delete non-existent file '%s'" % name)
+ children = self._read_from_file()
+ if name not in children:
+ raise BadFileError("cannot remove non-existent child")
+ dead_child = children[name]
+ del children[name]
+ self._write_to_file(children)
+ #return dead_child
remote_remove = remove
vdrive_dir = os.path.join(basedir, self.VDRIVEDIR)
if not os.path.exists(vdrive_dir):
os.mkdir(vdrive_dir)
- self._root = MutableDirectoryNode(vdrive_dir)
+ self._root = MutableDirectoryNode(vdrive_dir, "root")
def get_root(self):
return self._root