mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-29 17:28:53 +00:00
filetable: switch to new approach with anonymous nodes
This commit is contained in:
parent
36ae9b5dd9
commit
106177a7f2
@ -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
|
||||||
|
@ -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)
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user