mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-20 05:28:04 +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 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, basedir):
|
||||
return self.__class__(basedir)
|
||||
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 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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user