filetable: switch to new approach with anonymous nodes

This commit is contained in:
Brian Warner 2007-06-14 17:24:56 -07:00
parent 36ae9b5dd9
commit 106177a7f2
2 changed files with 95 additions and 60 deletions

View File

@ -1,29 +1,85 @@
import os, shutil import os
from zope.interface import implements from zope.interface import implements
from foolscap import Referenceable from foolscap import Referenceable
from allmydata.interfaces import RIMutableDirectoryNode from allmydata.interfaces import RIMutableDirectoryNode
from allmydata.util import bencode, idlib
from allmydata.util.assertutil import _assert
from twisted.application import service from twisted.application import service
from twisted.python import log 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): class BadNameError(Exception):
"""Bad filename component""" """Bad filename component"""
class BadFileError(Exception):
pass
class BadDirectoryError(Exception):
pass
class MutableDirectoryNode(Referenceable): 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) implements(RIMutableDirectoryNode)
def __init__(self, basedir): def __init__(self, basedir, name=None):
self._basedir = basedir 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, basedir): def make_subnode(self, name=None):
return self.__class__(basedir) 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 create_filename(self):
return idlib.b2a(os.urandom(8))
def validate_name(self, name): def validate_name(self, name):
if name == "." or name == ".." or "/" in 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 # these are the public methods, available to anyone who holds a reference
def list(self): def list(self):
log.msg("Dir(%s).list" % self._basedir) log.msg("Dir(%s).list()" % self._name)
results = [] children = self._read_from_file()
if not os.path.isdir(self._basedir): results = list(children.items())
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
return sorted(results) return sorted(results)
remote_list = list remote_list = list
def get(self, name): def get(self, name):
log.msg("Dir(%s).get(%s)" % (self._name, name))
self.validate_name(name) self.validate_name(name)
absname = os.path.join(self._basedir, name) children = self._read_from_file()
if os.path.isdir(absname): if name not in children:
return self.make_subnode(absname) raise BadFileError("no such child")
elif os.path.isfile(absname): return children[name]
f = open(absname, "rb")
data = f.read()
f.close()
return data
else:
raise BadFileError("there is nothing named '%s' in this directory"
% name)
remote_get = get remote_get = get
def add_directory(self, name): def add_directory(self, name):
self.validate_name(name) self.validate_name(name)
absname = os.path.join(self._basedir, name) children = self._read_from_file()
if os.path.isdir(absname): if name in children:
raise BadDirectoryError("the directory '%s' already exists" % name) raise BadDirectoryError("the directory already existed")
if os.path.exists(absname): children[name] = child = self.make_subnode()
raise BadDirectoryError("the directory '%s' already exists " self._write_to_file(children)
"(but isn't a directory)" % name) return child
os.mkdir(absname)
return self.make_subnode(absname)
remote_add_directory = add_directory remote_add_directory = add_directory
def add_file(self, name, uri): def add_file(self, name, uri):
self.validate_name(name) self.validate_name(name)
f = open(os.path.join(self._basedir, name), "wb") children = self._read_from_file()
f.write(uri) children[name] = uri
f.close() self._write_to_file(children)
remote_add_file = add_file remote_add_file = add_file
def remove(self, name): def remove(self, name):
self.validate_name(name) self.validate_name(name)
absname = os.path.join(self._basedir, name) children = self._read_from_file()
if os.path.isdir(absname): if name not in children:
shutil.rmtree(absname) raise BadFileError("cannot remove non-existent child")
elif os.path.isfile(absname): dead_child = children[name]
os.unlink(absname) del children[name]
else: self._write_to_file(children)
raise BadFileError("Cannot delete non-existent file '%s'" % name) #return dead_child
remote_remove = remove remote_remove = remove
@ -104,7 +141,7 @@ class GlobalVirtualDrive(service.MultiService):
vdrive_dir = os.path.join(basedir, self.VDRIVEDIR) vdrive_dir = os.path.join(basedir, self.VDRIVEDIR)
if not os.path.exists(vdrive_dir): if not os.path.exists(vdrive_dir):
os.mkdir(vdrive_dir) os.mkdir(vdrive_dir)
self._root = MutableDirectoryNode(vdrive_dir) self._root = MutableDirectoryNode(vdrive_dir, "root")
def get_root(self): def get_root(self):
return self._root return self._root

View File

@ -1,14 +1,14 @@
import os import os
from twisted.trial import unittest from twisted.trial import unittest
from allmydata.filetable import (MutableDirectoryNode, DeadDirectoryNodeError, from allmydata.filetable import (MutableDirectoryNode,
BadDirectoryError, BadFileError, BadNameError) BadDirectoryError, BadFileError, BadNameError)
class FileTable(unittest.TestCase): class FileTable(unittest.TestCase):
def test_files(self): def test_files(self):
os.mkdir("filetable") os.mkdir("filetable")
root = MutableDirectoryNode(os.path.abspath("filetable")) root = MutableDirectoryNode(os.path.abspath("filetable"), "root")
self.failUnlessEqual(root.list(), []) self.failUnlessEqual(root.list(), [])
root.add_file("one", "vid-one") root.add_file("one", "vid-one")
root.add_file("two", "vid-two") root.add_file("two", "vid-two")
@ -54,7 +54,5 @@ class FileTable(unittest.TestCase):
root.remove("subdir1") root.remove("subdir1")
self.failUnlessEqual(root.list(), [("one", "vid-one")]) self.failUnlessEqual(root.list(), [("one", "vid-one")])
# should our (orphaned) subdir1/subdir2 node still be able to do
# anything?
self.failUnlessRaises(DeadDirectoryNodeError, subdir1.list)