From: Brian Warner Date: Fri, 15 Jun 2007 00:24:56 +0000 (-0700) Subject: filetable: switch to new approach with anonymous nodes X-Git-Tag: allmydata-tahoe-0.4.0~47 X-Git-Url: https://git.rkrishnan.org/architecture.txt?a=commitdiff_plain;h=106177a7f28d76808b79fe49c9eaa36d7663cc5b;p=tahoe-lafs%2Ftahoe-lafs.git filetable: switch to new approach with anonymous nodes --- diff --git a/src/allmydata/filetable.py b/src/allmydata/filetable.py index 53263bba..601cc4b7 100644 --- a/src/allmydata/filetable.py +++ b/src/allmydata/filetable.py @@ -1,29 +1,85 @@ -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: @@ -32,66 +88,47 @@ class MutableDirectoryNode(Referenceable): # 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 @@ -104,7 +141,7 @@ class GlobalVirtualDrive(service.MultiService): 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 diff --git a/src/allmydata/test/test_filetable.py b/src/allmydata/test/test_filetable.py index f4a5a662..56a59040 100644 --- a/src/allmydata/test/test_filetable.py +++ b/src/allmydata/test/test_filetable.py @@ -1,14 +1,14 @@ import os from twisted.trial import unittest -from allmydata.filetable import (MutableDirectoryNode, DeadDirectoryNodeError, +from allmydata.filetable import (MutableDirectoryNode, BadDirectoryError, BadFileError, BadNameError) class FileTable(unittest.TestCase): def test_files(self): os.mkdir("filetable") - root = MutableDirectoryNode(os.path.abspath("filetable")) + root = MutableDirectoryNode(os.path.abspath("filetable"), "root") self.failUnlessEqual(root.list(), []) root.add_file("one", "vid-one") root.add_file("two", "vid-two") @@ -54,7 +54,5 @@ class FileTable(unittest.TestCase): root.remove("subdir1") self.failUnlessEqual(root.list(), [("one", "vid-one")]) - # should our (orphaned) subdir1/subdir2 node still be able to do - # anything? - self.failUnlessRaises(DeadDirectoryNodeError, subdir1.list) +