mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-19 21:17:54 +00:00
snapshot filetree work: it's getting close
This commit is contained in:
parent
26b01a9808
commit
ff6b09d973
@ -21,27 +21,67 @@ class SubTreeNode:
|
||||
|
||||
def __init__(self, tree):
|
||||
self.enclosing_tree = tree
|
||||
# node_children maps child name to another SubTreeNode instance. This
|
||||
# is only for internal directory nodes. All Files and external links
|
||||
# are listed in child_specifications instead.
|
||||
self.node_children = {}
|
||||
# child_specifications maps child name to a string which describes
|
||||
# how to obtain the actual child. For example, if "foo.jpg" in this
|
||||
# node represents a FILE with a uri of "fooURI", then
|
||||
# self.child_specifications["foo.jpg"] = "(FILE,fooURI")
|
||||
# subdirectory_node_children maps child name to another SubTreeNode
|
||||
# instance. This is only for internal directory nodes. All other
|
||||
# nodes are listed in child_specifications instead.
|
||||
self.subdirectory_node_children = {}
|
||||
# child_specifications maps child name to a specification tuple which
|
||||
# describes how to obtain the actual child. For example, if "foo.jpg"
|
||||
# in this node represents a CHK-encoded FILE with a uri of "fooURI",
|
||||
# then self.child_specifications["foo.jpg"] = ("CHKFILE","fooURI")
|
||||
self.child_specifications = {}
|
||||
|
||||
def is_directory(self):
|
||||
return True
|
||||
|
||||
def list(self):
|
||||
return sorted(self.node_children.keys() +
|
||||
return sorted(self.subdirectory_node_children.keys() +
|
||||
self.child_specifications.keys())
|
||||
|
||||
def serialize(self):
|
||||
def get(self, childname):
|
||||
if childname in self.subdirectory_node_children:
|
||||
return self.subdirectory_node_children[childname]
|
||||
elif childname in self.child_specifications:
|
||||
return to_node(self.child_specifications[childname])
|
||||
else:
|
||||
raise NoSuchChildError("no child named '%s'" % (childname,))
|
||||
|
||||
def get_subtree(self):
|
||||
return self.enclosing_tree
|
||||
|
||||
def delete(self, childname):
|
||||
assert self.enclosing_tree.is_mutable()
|
||||
if childname in self.subdirectory_node_children:
|
||||
del self.subdirectory_node_children[childname]
|
||||
elif childname in self.child_specifications:
|
||||
del to_node(self.child_specifications[childname])
|
||||
else:
|
||||
raise NoSuchChildError("no child named '%s'" % (childname,))
|
||||
|
||||
def add_subdir(self, childname):
|
||||
assert childname not in self.subdirectory_node_children
|
||||
assert childname not in self.child_specifications
|
||||
newnode = SubTreeNode(self.enclosing_tree)
|
||||
self.subdirectory_node_children[childname] = newnode
|
||||
return newnode
|
||||
|
||||
def add(self, childname, node):
|
||||
assert childname not in self.subdirectory_node_children
|
||||
assert childname not in self.child_specifications
|
||||
spec = to_spec(node)
|
||||
self.child_specifications[childname] = spec
|
||||
return self
|
||||
|
||||
def serialize_to_sexprs(self):
|
||||
# note: this is a one-pass recursive serialization that will result
|
||||
# in the whole file table being held in memory. This is only
|
||||
# appropriate for directories with fewer than, say, 10k nodes. If we
|
||||
# support larger directories, we should turn this into some kind of
|
||||
# generator instead, and write the serialized data directly to a
|
||||
# tempfile.
|
||||
#
|
||||
# ["DIRECTORY", name1, child1, name2, child2..]
|
||||
|
||||
data = ["DIRECTORY"]
|
||||
for name in sorted(self.node_children.keys()):
|
||||
data.append(name)
|
||||
@ -51,7 +91,7 @@ class SubTreeNode:
|
||||
data.append(self.child_specifications[name].serialize())
|
||||
return data
|
||||
|
||||
def unserialize(self, data):
|
||||
def populate_from_sexprs(self, data):
|
||||
assert data[0] == "DIRECTORY"
|
||||
assert len(data) % 2 == 1
|
||||
for i in range(1, len(data), 2):
|
||||
@ -61,97 +101,14 @@ class SubTreeNode:
|
||||
child_type = child_data[0]
|
||||
if child_type == "DIRECTORY":
|
||||
child = SubTreeNode(self.enclosing_tree)
|
||||
child.unserialize(child_data)
|
||||
child.populate_from_sexprs(child_data)
|
||||
self.node_children[name] = child
|
||||
else:
|
||||
self.child_specifications[name] = child_data
|
||||
|
||||
class _SubTreeMixin(object):
|
||||
|
||||
def get(self, path, opener):
|
||||
"""Return a Deferred that fires with the node at the given path, or
|
||||
None if there is no such node. This will traverse and even create
|
||||
subtrees as necessary."""
|
||||
d = self.get_node_for_path(path)
|
||||
def _done(res):
|
||||
if res == None:
|
||||
# traversal done, unable to find the node
|
||||
return None
|
||||
if res[0] == True:
|
||||
# found the node
|
||||
node = res[1]
|
||||
assert INode.providedBy(node)
|
||||
return node
|
||||
# otherwise, we must open and recurse into a new subtree
|
||||
next_subtree_spec = res[1]
|
||||
subpath = res[2]
|
||||
d1 = opener.open(next_subtree_spec, self.is_mutable())
|
||||
def _opened(next_subtree):
|
||||
assert ISubTree.providedBy(next_subtree)
|
||||
return next_subtree.get(subpath, opener)
|
||||
d1.addCallback(_opened)
|
||||
return d1
|
||||
d.addCallback(_done)
|
||||
return d
|
||||
|
||||
def find_lowest_containing_subtree_for_path(self, path, opener):
|
||||
"""Find the subtree which contains the target path, opening new
|
||||
subtrees if necessary. Return a Deferred that fires with (subtree,
|
||||
prepath, postpath), where prepath is the list of path components that
|
||||
got to the subtree, and postpath is the list of remaining path
|
||||
components (indicating a subpath within the resulting subtree). This
|
||||
will traverse and even create subtrees as necessary."""
|
||||
d = self.get_or_create_node_for_path(path)
|
||||
def _done(res):
|
||||
if res[0] == True:
|
||||
node = res[1]
|
||||
# found the node in our own tree. The whole path we were
|
||||
# given was used internally, and is therefore the postpath
|
||||
return (self, [], path)
|
||||
# otherwise, we must open and recurse into a new subtree
|
||||
ignored, next_subtree_spec, prepath, postpath = res
|
||||
d1 = opener.open(next_subtree_spec, self.is_mutable())
|
||||
def _opened(next_subtree):
|
||||
assert ISubTree.providedBy(next_subtree)
|
||||
f = next_subtree.find_lowest_containing_subtree_for_path
|
||||
return f(postpath, opener)
|
||||
d1.addCallback(_opened)
|
||||
def _found(res2):
|
||||
subtree, prepath2, postpath2 = res2
|
||||
return (subtree, prepath + prepath2, postpath2)
|
||||
d1.addCallback(_found)
|
||||
return d1
|
||||
d.addCallback(_done)
|
||||
return d
|
||||
|
||||
|
||||
class _MutableSubTreeMixin(object):
|
||||
|
||||
def add(self, path, child, opener, work_queue):
|
||||
assert len(path) > 0
|
||||
d = self.find_lowest_containing_subtree_for_path(path[:-1], opener)
|
||||
def _found(res):
|
||||
subtree, prepath, postpath = res
|
||||
assert IMutableSubTree.providedBy(subtree)
|
||||
# postpath is from the top of the subtree to the directory where
|
||||
# this child should be added. add_subpath wants the path from the
|
||||
# top of the subtree to the child itself, so we need to append
|
||||
# the child's name here.
|
||||
addpath = postpath + [path[-1]]
|
||||
# this add_path will cause some steps to be added, as well as the
|
||||
# internal node to be modified
|
||||
d1 = subtree.add_subpath(addpath, child, work_queue)
|
||||
if subtree.mutation_affects_parent():
|
||||
def _added(boxname):
|
||||
work_queue.add_addpath(boxname, prepath)
|
||||
d1.addCallback(_added)
|
||||
return d1
|
||||
d.addCallback(_found)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
class _DirectorySubTree(_SubTreeMixin):
|
||||
class _DirectorySubTree(object):
|
||||
"""I represent a set of connected directories that all share the same
|
||||
access control: any given person can read or write anything in this tree
|
||||
as a group, and it is not possible to give access to some pieces of this
|
||||
@ -167,99 +124,60 @@ class _DirectorySubTree(_SubTreeMixin):
|
||||
"""
|
||||
implements(ISubTree)
|
||||
|
||||
|
||||
def new(self):
|
||||
self.root = SubTreeNode(self)
|
||||
self.mutable = True # sure, why not
|
||||
|
||||
def unserialize(self, serialized_data):
|
||||
"""Populate all nodes from serialized_data, previously created by
|
||||
calling my serialize() method. 'serialized_data' is a series of
|
||||
nested lists (s-expressions), probably recorded in bencoded form."""
|
||||
self.root = SubTreeNode(self)
|
||||
self.root.unserialize(serialized_data)
|
||||
def populate_from_specification(self, spec, parent_is_mutable, downloader):
|
||||
return self.populate_from_node(to_node(spec),
|
||||
parent_is_mutable, downloader)
|
||||
|
||||
def populate_from_data(self, data):
|
||||
self.root = SubTreeNode()
|
||||
self.root.populate_from_sexprs(bencode.bdecode(data))
|
||||
return self
|
||||
|
||||
def serialize(self):
|
||||
"""Return a series of nested lists which describe my structure
|
||||
in a form that can be bencoded."""
|
||||
return self.root.serialize()
|
||||
|
||||
def is_mutable(self):
|
||||
return IMutableSubTree.providedBy(self)
|
||||
|
||||
def get_node_for_path(self, path):
|
||||
# this is restricted to traversing our own subtree.
|
||||
subpath = path
|
||||
node = self.root
|
||||
while subpath:
|
||||
name = subpath.pop(0)
|
||||
if name in node.node_children:
|
||||
node = node.node_children[name]
|
||||
assert isinstance(node, SubTreeNode)
|
||||
continue
|
||||
if name in node.child_specifications:
|
||||
# the path takes us out of this SubTree and into another
|
||||
next_subtree_spec = node.child_specifications[name]
|
||||
result = (False, next_subtree_spec, subpath)
|
||||
return defer.succeed(result)
|
||||
return defer.succeed(None)
|
||||
# we've run out of path components, so we must be at the terminus
|
||||
result = (True, node)
|
||||
return defer.succeed(result)
|
||||
|
||||
def get_or_create_node_for_path(self, path):
|
||||
# this is restricted to traversing our own subtree, but will create
|
||||
# internal directory nodes as necessary
|
||||
prepath = []
|
||||
postpath = path[:]
|
||||
node = self.root
|
||||
while postpath:
|
||||
name = postpath.pop(0)
|
||||
prepath.append(name)
|
||||
if name in node.node_children:
|
||||
node = node.node_children[name]
|
||||
assert isinstance(node, SubTreeNode)
|
||||
continue
|
||||
if name in node.child_specifications:
|
||||
# the path takes us out of this SubTree and into another
|
||||
next_subtree_spec = node.child_specifications[name]
|
||||
result = (False, next_subtree_spec, prepath, postpath)
|
||||
return defer.succeed(result)
|
||||
# need to create a new node
|
||||
new_node = SubTreeNode(self)
|
||||
node.node_children[name] = new_node
|
||||
node = new_node
|
||||
continue
|
||||
# we've run out of path components, so we must be at the terminus
|
||||
result = (True, node)
|
||||
return defer.succeed(result)
|
||||
|
||||
class ImmutableDirectorySubTree(_DirectorySubTree):
|
||||
pass
|
||||
|
||||
class _MutableDirectorySubTree(_DirectorySubTree, _MutableSubTreeMixin):
|
||||
implements(IMutableSubTree)
|
||||
|
||||
def add_subpath(self, subpath, child, work_queue):
|
||||
prepath = subpath[:-1]
|
||||
name = subpath[-1]
|
||||
d = self.get_node_for_path(prepath)
|
||||
def _found(results):
|
||||
assert results is not None
|
||||
assert results[0] == True
|
||||
node = results[1]
|
||||
# modify the in-RAM copy
|
||||
node.child_specifications[name] = child
|
||||
# now serialize and upload ourselves
|
||||
boxname = self.upload_my_serialized_form(work_queue)
|
||||
# our caller will perform the addpath, if necessary
|
||||
return boxname
|
||||
d.addCallback(_found)
|
||||
return d
|
||||
return self.root.serialize_to_sexprs()
|
||||
|
||||
def serialize_to_file(self, f):
|
||||
f.write(bencode.bencode(self.serialize()))
|
||||
|
||||
class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
|
||||
def is_mutable(self):
|
||||
return self.mutable
|
||||
|
||||
def get_node_for_path(self, path):
|
||||
# this is restricted to traversing our own subtree. Returns
|
||||
# (found_path, node, remaining_path)
|
||||
found_path = []
|
||||
remaining_path = path[:]
|
||||
node = self.root
|
||||
while remaining_path:
|
||||
name = remaining_path[0]
|
||||
if name in node.node_children:
|
||||
node = node.node_children[name]
|
||||
assert isinstance(node, SubTreeNode)
|
||||
found_path.append(name)
|
||||
remaining_path.pop(0)
|
||||
continue
|
||||
if name in node.child_specifications:
|
||||
# the path takes us out of this subtree and into another
|
||||
next_subtree_spec = node.child_specifications[name]
|
||||
node = to_node(next_subtree_spec)
|
||||
found_path.append(name)
|
||||
remaining_path.pop(0)
|
||||
break
|
||||
# The node *would* be in this subtree if it existed, but it
|
||||
# doesn't. Leave found_path and remaining_path alone, and node
|
||||
# points at the last parent node that was on the path.
|
||||
break
|
||||
return (found_path, node, remaining_path)
|
||||
|
||||
class CHKDirectorySubTree(_DirectorySubTree):
|
||||
# maybe mutable, maybe not
|
||||
|
||||
def mutation_affects_parent(self):
|
||||
return True
|
||||
@ -267,7 +185,14 @@ class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
|
||||
def set_uri(self, uri):
|
||||
self.old_uri = uri
|
||||
|
||||
def upload_my_serialized_form(self, work_queue):
|
||||
def populate_from_node(self, node, parent_is_mutable, downloader):
|
||||
node = ICHKDirectoryNode(node)
|
||||
self.mutable = parent_is_mutable
|
||||
d = downloader.download(node.get_uri(), download.Data())
|
||||
d.addCallback(self.populate_from_data)
|
||||
return d
|
||||
|
||||
def update(self, prepath, work_queue):
|
||||
# this is the CHK form
|
||||
f, filename = work_queue.create_tempfile(".chkdir")
|
||||
self.serialize_to_file(f)
|
||||
@ -277,21 +202,32 @@ class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
|
||||
work_queue.add_delete_tempfile(filename)
|
||||
work_queue.add_retain_uri_from_box(boxname)
|
||||
work_queue.add_delete_box(boxname)
|
||||
work_queue.add_addpath(boxname, prepath)
|
||||
work_queue.add_unlink_uri(self.old_uri)
|
||||
# TODO: think about how self.old_uri will get updated. I *think* that
|
||||
# this whole instance will get replaced, so it ought to be ok. But
|
||||
# this needs investigation.
|
||||
return boxname
|
||||
|
||||
class MutableSSKDirectorySubTree(_MutableDirectorySubTree):
|
||||
class SSKDirectorySubTree(_DirectorySubTree):
|
||||
|
||||
def new(self):
|
||||
_MutableDirectorySubTree.new(self)
|
||||
_DirectorySubTree.new(self)
|
||||
self.version = 0
|
||||
# TODO: populate
|
||||
|
||||
def mutation_affects_parent(self):
|
||||
return False
|
||||
|
||||
def populate_from_node(self, node, parent_is_mutable, downloader):
|
||||
node = ISSKDirectoryNode(node)
|
||||
self.read_capability = node.get_read_capability()
|
||||
self.write_capability = node.get_write_capability()
|
||||
self.mutable = bool(self.write_capability)
|
||||
d = downloader.download_ssk(self.read_capability, download.Data())
|
||||
d.addCallback(self.populate_from_data)
|
||||
return d
|
||||
|
||||
def set_version(self, version):
|
||||
self.version = version
|
||||
|
||||
@ -300,9 +236,9 @@ class MutableSSKDirectorySubTree(_MutableDirectorySubTree):
|
||||
f, filename = work_queue.create_tempfile(".sskdir")
|
||||
self.serialize_to_file(f)
|
||||
f.close()
|
||||
work_queue.add_upload_ssk(filename, self.get_write_capability(),
|
||||
work_queue.add_upload_ssk(filename, self.write_capability,
|
||||
self.version)
|
||||
self.version = self.version + 1
|
||||
work_queue.add_delete_tempfile(filename)
|
||||
work_queue.add_retain_ssk(self.get_read_capability())
|
||||
work_queue.add_retain_ssk(self.read_capability)
|
||||
|
||||
|
@ -2,16 +2,39 @@
|
||||
from zope.interface import Interface
|
||||
|
||||
class INode(Interface):
|
||||
"""This is some sort of retrievable node."""
|
||||
"""This is some sort of retrievable node. All objects which implement
|
||||
other I*Node interfaces also implement this one."""
|
||||
def is_directory():
|
||||
"""Return True if this node is an internal directory node."""
|
||||
|
||||
class IFileNode(Interface):
|
||||
"""This is a file which can be retrieved."""
|
||||
def get_uri():
|
||||
"""Return the URI of the target file. This URI can be passed
|
||||
to an IDownloader to retrieve the data."""
|
||||
|
||||
class IDirectoryNode(Interface):
|
||||
"""This is a directory which can be listed."""
|
||||
# these calls do not modify the subtree
|
||||
def list():
|
||||
"""Return a list of names which are children of this node."""
|
||||
"""Return a dictionary mapping each childname to a node. These nodes
|
||||
implement various I*Node interfaces depending upon what they can do."""
|
||||
def get(childname):
|
||||
"""Return a child node. Raises NoSuchChildError if there is no
|
||||
child of that name."""
|
||||
def get_subtree():
|
||||
"""Return the ISubTree which contains this node."""
|
||||
|
||||
# the following calls modify the subtree. After calling them, you must
|
||||
# tell the enclosing subtree to serialize and upload itself. They can
|
||||
# only be called if this directory node is associated with a mutable
|
||||
# subtree.
|
||||
def delete(childname):
|
||||
"""Delete any child referenced by this name."""
|
||||
def add_subdir(childname):
|
||||
"""Create a new directory node, and return it."""
|
||||
def add(childname, node):
|
||||
"""Add a new node to this path. Returns self."""
|
||||
|
||||
class ISubTree(Interface):
|
||||
"""A subtree is a collection of Nodes: files, directories, other trees.
|
||||
@ -27,25 +50,27 @@ class ISubTree(Interface):
|
||||
a DirectoryNode, or it might be a FileNode.
|
||||
"""
|
||||
|
||||
def get(path, opener):
|
||||
"""Return a Deferred that fires with the node at the given path, or
|
||||
None if there is no such node. This will traverse and create subtrees
|
||||
as necessary."""
|
||||
def populate_from_specification(spec, parent_is_mutable, downloader):
|
||||
"""Given a specification tuple, arrange to populate this subtree by
|
||||
pulling data from some source (possibly the mesh, or the queen, or an
|
||||
HTTP server, or the local filesystem). Return a Deferred that will
|
||||
fire (with self) when this subtree is ready for use (specifically
|
||||
when it is ready for get() and add() calls).
|
||||
"""
|
||||
|
||||
def add(path, child, opener, work_queue):
|
||||
"""Add 'child' (which must implement INode) to the tree at 'path'
|
||||
(which must be a list of pathname components). This will schedule all
|
||||
the work necessary to cause the child to be added reliably."""
|
||||
def populate_from_node(node, parent_is_mutable, downloader):
|
||||
"""Like populate_from_specification."""
|
||||
|
||||
def find_lowest_containing_subtree_for_path(path, opener):
|
||||
# not for external use. This is used internally by add().
|
||||
"""Find the subtree which contains the target path, opening new
|
||||
subtrees if necessary. Return a Deferred that fires with (subtree,
|
||||
prepath, postpath), where prepath is the list of path components that
|
||||
got to the subtree, and postpath is the list of remaining path
|
||||
components (indicating a subpath within the resulting subtree). This
|
||||
will traverse and even create subtrees as necessary."""
|
||||
def populate_from_data(data):
|
||||
"""Used internally by populate_from_specification. This is called
|
||||
with a sequence of bytes that describes the contents of the subtree,
|
||||
probably a bencoded tuple or s-expression. Returns self.
|
||||
"""
|
||||
|
||||
def unserialize(serialized_data):
|
||||
"""Populate all nodes from serialized_data, previously created by
|
||||
calling my serialize() method. 'serialized_data' is a series of
|
||||
nested lists (s-expressions), probably recorded in bencoded form."""
|
||||
|
||||
def is_mutable():
|
||||
"""This returns True if we have the ability to modify this subtree.
|
||||
@ -54,63 +79,85 @@ class ISubTree(Interface):
|
||||
"""
|
||||
|
||||
def get_node_for_path(path):
|
||||
"""Ask this subtree to follow the path through its internal nodes. If
|
||||
the path terminates within this subtree, return (True, node), where
|
||||
'node' implements INode (and also IMutableNode if this subtree
|
||||
is_mutable). If the path takes us beyond this subtree, return (False,
|
||||
next_subtree_spec, subpath), where 'next_subtree_spec' is a string
|
||||
that can be passed to an Opener to create a new subtree, and
|
||||
'subpath' is the subset of 'path' that can be passed to this new
|
||||
subtree. If the path cannot be found within the subtree (and it is
|
||||
not in the domain of some child subtree), return None.
|
||||
"""Ask this subtree to follow the path through its internal nodes.
|
||||
|
||||
Returns a tuple of (found_path, node, remaining_path). This method
|
||||
operations synchronously, and does not return a Deferred.
|
||||
|
||||
(found_path=path, found_node, [])
|
||||
If the path terminates within this subtree, found_path=path and
|
||||
remaining_path=[], and the node will be an internal IDirectoryNode.
|
||||
|
||||
(found_path, last_node, remaining_path)
|
||||
If the path does not terminate within this subtree but neither does
|
||||
it exit this subtree, the last internal IDirectoryNode that *was* on
|
||||
the path will be returned in 'node'. The path components that led to
|
||||
this node will be in found_path, and the remaining components will be
|
||||
in remaining_path. If you want to create the target node, loop over
|
||||
remaining_path as follows::
|
||||
|
||||
while remaining_path:
|
||||
node = node.add_subdir(remaining_path.pop(0))
|
||||
|
||||
(found_path, exit_node, remaining_path)
|
||||
If the path leaves this subtree, 'node' will be a different kind of
|
||||
INode (probably one that points at a child directory of some sort),
|
||||
found_path will be the components that led to this point, and
|
||||
remaining_path will be the remaining components. If you still wish to
|
||||
locate the target, use 'node' to open a new subtree, then provide
|
||||
'remaining_path' to the new subtree's get_node_for_path() method.
|
||||
|
||||
"""
|
||||
|
||||
def get_or_create_node_for_path(path):
|
||||
"""Like get_node_for_path, but instead of returning None, the subtree
|
||||
will create internal nodes as necessary. Therefore it always returns
|
||||
either (True, node), or (False, next_subtree_spec, prepath, postpath).
|
||||
def update(prepath, workqueue):
|
||||
"""Perform and schedule whatever work is necessary to record this
|
||||
subtree to persistent storage and update the parent at 'prepath'
|
||||
with a new child specification.
|
||||
|
||||
For directory subtrees, this will cause the subtree to serialize
|
||||
itself to a file, then add instructions to the workqueue to first
|
||||
upload this file to the mesh, then add the file's URI to the parent's
|
||||
subtree. The second instruction will possibly cause recursion, until
|
||||
some subtree is updated which does not require notifying the parent.
|
||||
"""
|
||||
|
||||
def serialize():
|
||||
"""Return a series of nested lists which describe my structure
|
||||
in a form that can be bencoded."""
|
||||
|
||||
def unserialize(serialized_data):
|
||||
"""Populate all nodes from serialized_data, previously created by
|
||||
calling my serialize() method. 'serialized_data' is a series of
|
||||
nested lists (s-expressions), probably recorded in bencoded form."""
|
||||
|
||||
|
||||
class IMutableSubTree(Interface):
|
||||
def mutation_affects_parent():
|
||||
"""This returns True for CHK nodes where you must inform the parent
|
||||
of the new URI each time you change the child subtree. It returns
|
||||
False for SSK nodes (or other nodes which have a pointer stored in
|
||||
some mutable form).
|
||||
"""
|
||||
|
||||
def add_subpath(subpath, child_spec, work_queue):
|
||||
"""Ask this subtree to add the given child to an internal node at the
|
||||
given subpath. The subpath must not exit the subtree through another
|
||||
subtree (specifically get_subtree_for_path(subpath) must either
|
||||
return None or (True,node), and in the latter case, this subtree will
|
||||
create new internal nodes as necessary).
|
||||
|
||||
The subtree will probably serialize itself to a file and add steps to
|
||||
the work queue to accomplish its goals.
|
||||
|
||||
This returns a Deferred (the value of which is ignored) when
|
||||
everything has been added to the work queue.
|
||||
"""
|
||||
|
||||
def serialize_to_file(f):
|
||||
"""Write a bencoded data structure to the given filehandle that can
|
||||
be used to reproduce the contents of this subtree."""
|
||||
|
||||
class ISubTreeSpecification(Interface):
|
||||
def serialize():
|
||||
"""Return a tuple that describes this subtree. This tuple can be
|
||||
passed to IOpener.open() to reconstitute the subtree."""
|
||||
#class IMutableSubTree(Interface):
|
||||
# def mutation_affects_parent():
|
||||
# """This returns True for CHK nodes where you must inform the parent
|
||||
# of the new URI each time you change the child subtree. It returns
|
||||
# False for SSK nodes (or other nodes which have a pointer stored in
|
||||
# some mutable form).
|
||||
# """
|
||||
#
|
||||
# def add_subpath(subpath, child_spec, work_queue):
|
||||
# """Ask this subtree to add the given child to an internal node at the
|
||||
# given subpath. The subpath must not exit the subtree through another
|
||||
# subtree (specifically get_subtree_for_path(subpath) must either
|
||||
# return None or (True,node), and in the latter case, this subtree will
|
||||
# create new internal nodes as necessary).
|
||||
#
|
||||
# The subtree will probably serialize itself to a file and add steps to
|
||||
# the work queue to accomplish its goals.
|
||||
#
|
||||
# This returns a Deferred (the value of which is ignored) when
|
||||
# everything has been added to the work queue.
|
||||
# """
|
||||
#
|
||||
# def serialize_to_file(f):
|
||||
# """Write a bencoded data structure to the given filehandle that can
|
||||
# be used to reproduce the contents of this subtree."""
|
||||
#
|
||||
#class ISubTreeSpecification(Interface):
|
||||
# def serialize():
|
||||
# """Return a tuple that describes this subtree. This tuple can be
|
||||
# passed to IOpener.open() to reconstitute the subtree. It can also be
|
||||
# bencoded and stuffed in a series of persistent bytes somewhere on the
|
||||
# mesh or in a file."""
|
||||
|
||||
class IOpener(Interface):
|
||||
def open(subtree_specification, parent_is_mutable):
|
||||
@ -121,3 +168,51 @@ class IOpener(Interface):
|
||||
local disk, or asking some central-service node for the current
|
||||
value."""
|
||||
|
||||
|
||||
class IVirtualDrive(Interface):
|
||||
|
||||
# commands to manipulate files
|
||||
|
||||
def list(path):
|
||||
"""List the contents of the directory at the given path.
|
||||
|
||||
'path' is a list of strings (empty to refer to the root directory)
|
||||
and must refer to a DIRECTORY node. This method returns a Deferred
|
||||
that fires with a dictionary that maps strings to filetypes. The
|
||||
strings are useful as path name components. The filetypes are
|
||||
Interfaces: either IDirectoryNode if path+[childname] can be used in
|
||||
a 'list' method, or IFileNode if path+[childname] can be used in a
|
||||
'download' method.
|
||||
"""
|
||||
|
||||
def download(path, target):
|
||||
"""Download the file at the given path to 'target'.
|
||||
|
||||
'path' must refer to a FILE. 'target' must implement IDownloadTarget.
|
||||
This returns a Deferred that fires (with 'target') when the download
|
||||
is complete.
|
||||
"""
|
||||
|
||||
def upload_now(path, uploadable):
|
||||
"""Upload a file to the given path. The path must not already exist.
|
||||
|
||||
path[:-1] must refer to a writable DIRECTORY node. 'uploadable' must
|
||||
implement IUploadable. This returns a Deferred that fires (with
|
||||
'uploadable') when the upload is complete.
|
||||
"""
|
||||
|
||||
def upload_later(path, filename):
|
||||
"""Upload a file from disk to the given path.
|
||||
"""
|
||||
|
||||
def delete(path):
|
||||
"""Delete the file or directory at the given path.
|
||||
|
||||
Returns a Deferred that fires (with self) when the delete is
|
||||
complete.
|
||||
"""
|
||||
|
||||
# commands to manipulate subtrees
|
||||
|
||||
# ... detach subtree, merge subtree, etc
|
||||
|
||||
|
@ -1,35 +1,38 @@
|
||||
|
||||
from zope.interface import implements
|
||||
from twisted.internet import defer
|
||||
from allmydata.util import bencode
|
||||
from allmydata.filetree import interfaces, directory
|
||||
from allmydata.filetree import specification as fspec
|
||||
from allmydata.filetree.file import CHKFile, MutableSSKFile, ImmutableSSKFile
|
||||
|
||||
def unserialize_subtree_specification(serialized_spec):
|
||||
assert isinstance(serialized_spec, tuple)
|
||||
for stype in [fspec.CHKDirectorySpecification,
|
||||
fspec.ImmutableSSKDirectorySpecification,
|
||||
fspec.MutableSSKDirectorySpecification,
|
||||
fspec.LocalFileRedirection,
|
||||
fspec.QueenRedirection,
|
||||
fspec.HTTPRedirection,
|
||||
fspec.QueenOrLocalFileRedirection,
|
||||
]:
|
||||
if tuple[0] == stype:
|
||||
spec = stype()
|
||||
spec.unserialize(serialized_spec)
|
||||
return spec
|
||||
raise RuntimeError("unable to unserialize subtree specification '%s'" %
|
||||
(serialized_spec,))
|
||||
from allmydata.filetree import interfaces, directory, redirect
|
||||
#from allmydata.filetree.file import CHKFile, MutableSSKFile, ImmutableSSKFile
|
||||
#from allmydata.filetree.specification import unserialize_subtree_specification
|
||||
|
||||
all_openable_subtree_types = [
|
||||
directory.CHKDirectorySubTree,
|
||||
directory.SSKDirectorySubTree,
|
||||
redirect.LocalFileRedirection,
|
||||
redirect.QueenRedirection,
|
||||
redirect.HTTPRedirection,
|
||||
redirect.QueenOrLocalFileRedirection,
|
||||
]
|
||||
|
||||
class Opener(object):
|
||||
implements(interfaces.IOpener)
|
||||
def __init__(self, queen):
|
||||
def __init__(self, queen, downloader):
|
||||
self._queen = queen
|
||||
self._downloader = downloader
|
||||
self._cache = {}
|
||||
|
||||
def _create(self, spec, parent_is_mutable):
|
||||
assert isinstance(spec, tuple)
|
||||
for subtree_class in all_openable_subtree_types:
|
||||
if spec[0] == subtree_class.stype:
|
||||
subtree = subtree_class()
|
||||
d = subtree.populate_from_specification(spec,
|
||||
parent_is_mutable,
|
||||
self._downloader)
|
||||
return d
|
||||
raise RuntimeError("unable to handle subtree specification '%s'"
|
||||
% (spec,))
|
||||
|
||||
def open(self, subtree_specification, parent_is_mutable):
|
||||
spec = interfaces.ISubTreeSpecification(subtree_specification)
|
||||
|
||||
@ -37,38 +40,16 @@ class Opener(object):
|
||||
if spec in self._cache:
|
||||
return defer.succeed(self._cache[spec])
|
||||
|
||||
# is it a file?
|
||||
if isinstance(spec, fspec.CHKFileSpecification):
|
||||
return self._get_chk_file(spec)
|
||||
if isinstance(spec, (fspec.MutableSSKFileSpecification,
|
||||
fspec.ImmutableSSKFileSpecification)):
|
||||
return self._get_ssk_file(spec)
|
||||
|
||||
# is it a directory?
|
||||
if isinstance(spec, fspec.CHKDirectorySpecification):
|
||||
return self._get_chk_dir(spec, parent_is_mutable)
|
||||
if isinstance(spec, (fspec.ImmutableSSKDirectorySpecification,
|
||||
fspec.MutableSSKDirectorySpecification)):
|
||||
return self._get_ssk_dir(spec)
|
||||
|
||||
# is it a redirection to a file or directory?
|
||||
if isinstance(spec, fspec.LocalFileRedirection):
|
||||
return self._get_local_redir(spec)
|
||||
if isinstance(spec, fspec.QueenRedirection):
|
||||
return self._get_queen_redir(spec)
|
||||
if isinstance(spec, fspec.HTTPRedirection):
|
||||
return self._get_http_redir(spec)
|
||||
if isinstance(spec, fspec.QueenOrLocalFileRedirection):
|
||||
return self._get_queen_or_local_redir(spec)
|
||||
|
||||
# none of the above
|
||||
raise RuntimeError("I do not know how to open '%s'" % (spec,))
|
||||
d = defer.maybeDeferred(self._create, spec, parent_is_mutable)
|
||||
d.addCallback(self._add_to_cache, spec)
|
||||
return d
|
||||
|
||||
def _add_to_cache(self, subtree, spec):
|
||||
self._cache[spec] = subtree
|
||||
# TODO: remove things from the cache eventually
|
||||
return subtree
|
||||
|
||||
"""
|
||||
def _get_chk_file(self, spec):
|
||||
subtree = CHKFile(spec.get_uri())
|
||||
return defer.succeed(subtree)
|
||||
@ -82,93 +63,4 @@ class Opener(object):
|
||||
subtree = ImmutableSSKFile(spec.get_read_cap())
|
||||
return defer.succeed(subtree)
|
||||
|
||||
def _get_chk_dir(self, spec, parent_is_mutable):
|
||||
uri = spec.get_uri()
|
||||
if parent_is_mutable:
|
||||
subtree = directory.MutableCHKDirectorySubTree()
|
||||
subtree.set_uri(uri)
|
||||
else:
|
||||
subtree = directory.ImmutableDirectorySubTree()
|
||||
d = self.downloader.get_chk(uri)
|
||||
d.addCallback(subtree.unserialize)
|
||||
d.addCallback(self._add_to_cache, spec)
|
||||
return d
|
||||
|
||||
def _get_ssk_dir(self, spec):
|
||||
mutable = isinstance(spec, fspec.ImmutableSSKDirectorySpecification)
|
||||
if mutable:
|
||||
subtree = directory.ImmutableDirectorySubTree()
|
||||
else:
|
||||
assert isinstance(spec, fspec.MutableSSKDirectorySpecification)
|
||||
subtree = directory.MutableSSKDirectorySubTree()
|
||||
subtree.set_write_capability(spec.get_write_capability())
|
||||
read_cap = spec.get_read_capability()
|
||||
subtree.set_read_capability(read_cap)
|
||||
d = self.downloader.get_ssk_latest(read_cap)
|
||||
def _set_version(res):
|
||||
version, data = res
|
||||
if mutable:
|
||||
subtree.set_version(version)
|
||||
return data
|
||||
d.addCallback(_set_version)
|
||||
d.addCallback(subtree.unserialize)
|
||||
d.addCallback(self._add_to_cache, spec)
|
||||
return d
|
||||
|
||||
def _get_local_redir(self, spec):
|
||||
# there is a local file which contains a bencoded serialized
|
||||
# subtree specification.
|
||||
filename = spec.get_filename()
|
||||
# TODO: will this enable outsiders to cause us to read from
|
||||
# arbitrary files? Think about this.
|
||||
f = open(filename, "rb")
|
||||
data = bencode.bdecode(f.read())
|
||||
f.close()
|
||||
# note: we don't cache the contents of the file. TODO: consider
|
||||
# doing this based upon mtime. It is important that we be able to
|
||||
# notice if the file has been changed.
|
||||
new_spec = unserialize_subtree_specification(data)
|
||||
return self.open(new_spec, True)
|
||||
|
||||
def _get_queen_redir(self, spec):
|
||||
# this specifies a handle for which the Queen maintains a
|
||||
# serialized subtree specification.
|
||||
handle = spec.get_handle()
|
||||
d = self._queen.callRemote("lookup_handle", handle)
|
||||
d.addCallback(unserialize_subtree_specification)
|
||||
d.addCallback(self.open, True)
|
||||
return d
|
||||
|
||||
def _get_http_redir(self, spec):
|
||||
# this specifies a URL at which there is a bencoded serialized
|
||||
# subtree specification.
|
||||
url = spec.get_url()
|
||||
from twisted.web import client
|
||||
d = client.getPage(url)
|
||||
d.addCallback(bencode.bdecode)
|
||||
d.addCallback(unserialize_subtree_specification)
|
||||
d.addCallback(self.open, False)
|
||||
return d
|
||||
|
||||
def _get_queen_or_local_redir(self, spec):
|
||||
# there is a local file which contains a bencoded serialized
|
||||
# subtree specification. The queen also has a copy. Whomever has
|
||||
# the higher version number wins.
|
||||
filename = spec.get_filename()
|
||||
f = open(filename, "rb")
|
||||
local_version, local_data = bencode.bdecode(f.read())
|
||||
f.close()
|
||||
handle = spec.get_handle()
|
||||
# TODO: pubsub so we can cache the queen's results
|
||||
d = self._queen.callRemote("lookup_handle", handle)
|
||||
def _got_queen(response):
|
||||
queen_version, queen_data = response
|
||||
if queen_version > local_version:
|
||||
return queen_data
|
||||
return local_data
|
||||
d.addCallback(_got_queen)
|
||||
d.addCallback(unserialize_subtree_specification)
|
||||
d.addCallback(self.open, True)
|
||||
return d
|
||||
|
||||
|
||||
"""
|
||||
|
98
src/allmydata/filetree/redirect.py
Normal file
98
src/allmydata/filetree/redirect.py
Normal file
@ -0,0 +1,98 @@
|
||||
|
||||
from allmydata.util import bencode
|
||||
|
||||
class LocalFileRedirection(object):
|
||||
stype = "LocalFileRedirection"
|
||||
|
||||
def populate_from_specification(self, spec, parent_is_mutable, downloader):
|
||||
# return a Deferred that fires (with self) when this node is ready
|
||||
# for use
|
||||
|
||||
(stype, filename) = spec
|
||||
assert stype == self.stype
|
||||
#filename = spec.get_filename()
|
||||
# there is a local file which contains a bencoded serialized
|
||||
# subtree specification.
|
||||
|
||||
# TODO: will this enable outsiders to cause us to read from
|
||||
# arbitrary files? Think about this.
|
||||
f = open(filename, "rb")
|
||||
data = f.read()
|
||||
f.close()
|
||||
# note: we don't cache the contents of the file. TODO: consider
|
||||
# doing this based upon mtime. It is important that we be able to
|
||||
# notice if the file has been changed.
|
||||
|
||||
return self.populate_from_data(data)
|
||||
|
||||
def populate_from_data(self, data):
|
||||
# data is a subtree specification for our one child
|
||||
self.child_spec = bencode.bdecode(data)
|
||||
return self
|
||||
|
||||
class QueenRedirection(object):
|
||||
stype = "QueenRedirection"
|
||||
|
||||
def populate_from_specification(self, spec, parent_is_mutable, downloader):
|
||||
# this specifies a handle for which the Queen maintains a
|
||||
# serialized subtree specification.
|
||||
(stype, handle) = spec
|
||||
|
||||
# TODO: queen?
|
||||
d = self._queen.callRemote("lookup_handle", handle)
|
||||
d.addCallback(self.populate_from_data)
|
||||
return d
|
||||
|
||||
def populate_from_data(self, data):
|
||||
self.child_spec = bencode.bdecode(data)
|
||||
return self
|
||||
|
||||
class QueenOrLocalFileRedirection(object):
|
||||
stype = "QueenOrLocalFileRedirection"
|
||||
|
||||
def populate_from_specification(self, spec, parent_is_mutable, downloader):
|
||||
# there is a local file which contains a bencoded serialized
|
||||
# subtree specification. The queen also has a copy. Whomever has
|
||||
# the higher version number wins.
|
||||
(stype, filename, handle) = spec
|
||||
|
||||
f = open(filename, "rb")
|
||||
#local_version, local_data = bencode.bdecode(f.read())
|
||||
local_version_and_data = f.read()
|
||||
f.close()
|
||||
|
||||
# TODO: queen?
|
||||
# TODO: pubsub so we can cache the queen's results
|
||||
d = self._queen.callRemote("lookup_handle", handle)
|
||||
d.addCallback(self._choose_winner, local_version_and_data)
|
||||
return d
|
||||
|
||||
def _choose_winner(self, queen_version_and_data, local_version_and_data):
|
||||
queen_version, queen_data = bencode.bdecode(queen_version_and_data)
|
||||
local_version, local_data = bencode.bdecode(local_version_and_data)
|
||||
if queen_version > local_version:
|
||||
data = queen_data
|
||||
else:
|
||||
data = local_data
|
||||
return self.populate_from_data(data)
|
||||
|
||||
def populate_from_data(self, data):
|
||||
# NOTE: two layers of bencoding here, TODO
|
||||
self.child_spec = bencode.bdecode(data)
|
||||
return self
|
||||
|
||||
class HTTPRedirection(object):
|
||||
stype = "HTTPRedirection"
|
||||
|
||||
def populate_from_specification(self, spec, parent_is_mutable, downloader):
|
||||
# this specifies a URL at which there is a bencoded serialized
|
||||
# subtree specification.
|
||||
(stype, url) = spec
|
||||
from twisted.web import client
|
||||
d = client.getPage(url)
|
||||
d.addCallback(self.populate_from_data)
|
||||
return d
|
||||
|
||||
def populate_from_data(self, data):
|
||||
self.child_spec = bencode.bdecode(data)
|
||||
return self
|
@ -1,4 +1,5 @@
|
||||
|
||||
"""
|
||||
from zope.interface import implements
|
||||
from allmydata.filetree.interfaces import ISubTreeSpecification
|
||||
|
||||
@ -40,87 +41,24 @@ class MutableSSKFileSpecification(ImmutableSSKFileSpecification):
|
||||
self.read_cap = data[1]
|
||||
self.write_cap = data[2]
|
||||
|
||||
class CHKDirectorySpecification(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "CHK-Directory"
|
||||
def set_uri(self, uri):
|
||||
self.uri = uri
|
||||
def serialize(self):
|
||||
return (self.stype, self.uri)
|
||||
def unserialize(self, data):
|
||||
assert data[0] == self.stype
|
||||
self.uri = data[1]
|
||||
|
||||
class ImmutableSSKDirectorySpecification(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "SSK-Readonly-Directory"
|
||||
def set_read_capability(self, read_cap):
|
||||
self.read_cap = read_cap
|
||||
def get_read_capability(self):
|
||||
return self.read_cap
|
||||
def serialize(self):
|
||||
return (self.stype, self.read_cap)
|
||||
def unserialize(self, data):
|
||||
assert data[0] == self.stype
|
||||
self.read_cap = data[1]
|
||||
|
||||
class MutableSSKDirectorySpecification(ImmutableSSKDirectorySpecification):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "SSK-ReadWrite-Directory"
|
||||
def set_write_capability(self, write_cap):
|
||||
self.write_cap = write_cap
|
||||
def get_write_capability(self):
|
||||
return self.write_cap
|
||||
def serialize(self):
|
||||
return (self.stype, self.read_cap, self.write_cap)
|
||||
def unserialize(self, data):
|
||||
assert data[0] == self.stype
|
||||
self.read_cap = data[1]
|
||||
self.write_cap = data[2]
|
||||
|
||||
|
||||
|
||||
class LocalFileRedirection(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "LocalFile"
|
||||
def set_filename(self, filename):
|
||||
self.filename = filename
|
||||
def get_filename(self):
|
||||
return self.filename
|
||||
def serialize(self):
|
||||
return (self.stype, self.filename)
|
||||
|
||||
class QueenRedirection(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "QueenRedirection"
|
||||
def set_handle(self, handle):
|
||||
self.handle = handle
|
||||
def get_handle(self):
|
||||
return self.handle
|
||||
def serialize(self):
|
||||
return (self.stype, self.handle)
|
||||
|
||||
class HTTPRedirection(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "HTTPRedirection"
|
||||
def set_url(self, url):
|
||||
self.url = url
|
||||
def get_url(self):
|
||||
return self.url
|
||||
def serialize(self):
|
||||
return (self.stype, self.url)
|
||||
|
||||
class QueenOrLocalFileRedirection(object):
|
||||
implements(ISubTreeSpecification)
|
||||
stype = "QueenOrLocalFile"
|
||||
def set_filename(self, filename):
|
||||
self.filename = filename
|
||||
def get_filename(self):
|
||||
return self.filename
|
||||
def set_handle(self, handle):
|
||||
self.handle = handle
|
||||
def get_handle(self):
|
||||
return self.handle
|
||||
def serialize(self):
|
||||
return (self.stype, self.handle, self.filename)
|
||||
def unserialize_subtree_specification(serialized_spec):
|
||||
assert isinstance(serialized_spec, tuple)
|
||||
for stype in [CHKDirectorySpecification,
|
||||
ImmutableSSKDirectorySpecification,
|
||||
MutableSSKDirectorySpecification,
|
||||
|
||||
LocalFileRedirection,
|
||||
QueenRedirection,
|
||||
HTTPRedirection,
|
||||
QueenOrLocalFileRedirection,
|
||||
]:
|
||||
if tuple[0] == stype:
|
||||
spec = stype()
|
||||
spec.unserialize(serialized_spec)
|
||||
return spec
|
||||
raise RuntimeError("unable to unserialize subtree specification '%s'" %
|
||||
(serialized_spec,))
|
||||
"""
|
||||
|
176
src/allmydata/filetree/vdrive.py
Normal file
176
src/allmydata/filetree/vdrive.py
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
from allmydata.filetree import interfaces, opener
|
||||
|
||||
class VirtualDrive(object):
|
||||
implements(interfaces.IVirtualDrive)
|
||||
|
||||
def __init__(self, workqueue, downloader, root_specification):
|
||||
self.workqueue = workqueue
|
||||
workqueue.set_vdrive(self)
|
||||
# TODO: queen?
|
||||
self.opener = Opener(queen, downloader)
|
||||
self.root_specification = root_specification
|
||||
|
||||
# these methods are used to walk through our subtrees
|
||||
|
||||
def _get_root(self):
|
||||
return self.opener.open(self.root_specification, False)
|
||||
|
||||
def _get_node(self, path):
|
||||
d = self._get_closest_node(path)
|
||||
def _got_node((node, remaining_path)):
|
||||
if remaining_path:
|
||||
return None
|
||||
return node
|
||||
d.addCallback(_got_node)
|
||||
return d
|
||||
|
||||
def _get_closest_node(self, path):
|
||||
"""Find the closest directory node parent for the desired path.
|
||||
Return a Deferred that fires with (node, remaining_path).
|
||||
"""
|
||||
d = self._get_root()
|
||||
d.addCallback(self._get_closest_node_1, path)
|
||||
return d
|
||||
|
||||
def _get_closest_node_1(self, subtree, path):
|
||||
d = subtree.get_node_for_path(path)
|
||||
d.addCallback(self._get_closest_node_2, subtree.is_mutable())
|
||||
return d
|
||||
|
||||
def _get_closest_node_2(self, res, parent_is_mutable):
|
||||
(found_path, node, remaining_path) = res
|
||||
if node.is_directory():
|
||||
# traversal done
|
||||
return (node, remaining_path)
|
||||
# otherwise, we must open and recurse into a new subtree
|
||||
d = self.opener.open(node, parent_is_mutable)
|
||||
def _opened(next_subtree):
|
||||
next_subtree = ISubTree(next_subtree)
|
||||
return self._get_closest_node_1(next_subtree, remaining_path)
|
||||
d.addCallback(_opened)
|
||||
return d
|
||||
|
||||
def _get_directory(self, path):
|
||||
"""Return a Deferred that fires with the IDirectoryNode at the given
|
||||
path, or raise NoSuchDirectoryError if there is no such node. This
|
||||
will traverse subtrees as necessary."""
|
||||
d = self._get_node(path)
|
||||
def _got_directory(node):
|
||||
if not node:
|
||||
raise NoSuchDirectoryError
|
||||
assert interfaces.IDirectoryNode(node)
|
||||
return node
|
||||
d.addCallback(_got_directory)
|
||||
return d
|
||||
|
||||
def _get_file(self, path):
|
||||
"""Return a Deferred that files with an IFileNode at the given path,
|
||||
or raises a NoSuchDirectoryError or NoSuchChildError, or some other
|
||||
error if the path refers to something other than a file."""
|
||||
d = self._get_node(path)
|
||||
def _got_node(node):
|
||||
if not node:
|
||||
raise NoSuchChildError
|
||||
return IFileNode(node)
|
||||
d.addCallback(_got_node)
|
||||
return d
|
||||
|
||||
def _get_file_uri(self, path):
|
||||
d = self._get_file(path)
|
||||
d.addCallback(lambda filenode: filenode.get_uri())
|
||||
return d
|
||||
|
||||
def _child_should_not_exist(self, path):
|
||||
d = self._get_node(path)
|
||||
def _got_node(node):
|
||||
if node is not None:
|
||||
raise PathAlreadyExistsError
|
||||
d.addCallback(_got_node)
|
||||
return d
|
||||
|
||||
def _child_should_exist(self, path):
|
||||
d = self._get_node(path)
|
||||
def _got_node(node):
|
||||
if node is None:
|
||||
raise PathDoesNotExistError
|
||||
d.addCallback(_got_node)
|
||||
return d
|
||||
|
||||
def _get_closest_node_and_prepath(self, path):
|
||||
d = self._get_closest_node(path)
|
||||
def _got_closest((node, remaining_path)):
|
||||
prepath_len = len(path) - len(remaining_path)
|
||||
prepath = path[:prepath_len]
|
||||
assert path[prepath_len:] == remaining_path
|
||||
return (prepath, node, remaining_path)
|
||||
d.addCallback(_got_closest)
|
||||
return d
|
||||
|
||||
# these are called by the workqueue
|
||||
|
||||
def add(self, path, new_node):
|
||||
parent_path = path[:-1]
|
||||
new_node_path = path[-1]
|
||||
d = self._get_closest_node_and_prepath(parent_path)
|
||||
def _got_closest((prepath, node, remaining_path)):
|
||||
# now tell it to create any necessary parent directories
|
||||
while remaining_path:
|
||||
node = node.add_subdir(remaining_path.pop(0))
|
||||
# 'node' is now the directory where the child wants to go
|
||||
return node, prepath
|
||||
d.addCallback(_got_closest)
|
||||
def _add_new_node((node, prepath)):
|
||||
node.add(new_node_path, new_node)
|
||||
subtree = node.get_subtree()
|
||||
# now, tell the subtree to serialize and upload itself, using the
|
||||
# workqueue. The subtree will also queue a step to notify its
|
||||
# parent (using 'prepath'), if necessary.
|
||||
return subtree.update(prepath, self.workqueue)
|
||||
d.addCallback(_add_new_node)
|
||||
return d
|
||||
|
||||
# these are user-visible
|
||||
|
||||
def list(self, path):
|
||||
d = self._get_directory(path)
|
||||
d.addCallback(lambda node: node.list())
|
||||
return d
|
||||
|
||||
def download(self, path, target):
|
||||
d = self._get_file_uri(path)
|
||||
d.addCallback(lambda uri: self.downloader.download(uri, target))
|
||||
return d
|
||||
|
||||
def upload_now(self, path, uploadable):
|
||||
# note: the first few steps of this do not use the workqueue, but I
|
||||
# think things should remain consistent anyways. If the node is shut
|
||||
# down before the file has finished uploading, then we forget all
|
||||
# abou the file.
|
||||
uploadable = IUploadable(uploadable)
|
||||
d = self._child_should_not_exist(path)
|
||||
# then we upload the file
|
||||
d.addCallback(lambda ignored: self.uploader.upload(uploadable))
|
||||
d.addCallback(lambda uri: self.workqueue.create_boxname(uri))
|
||||
d.addCallback(lambda boxname:
|
||||
self.workqueue.add_addpath(boxname, path))
|
||||
return d
|
||||
|
||||
def upload_later(self, path, filename):
|
||||
boxname = self.workqueue.create_boxname()
|
||||
self.workqueue.add_upload_chk(filename, boxname)
|
||||
self.workqueue.add_addpath(boxname, path)
|
||||
|
||||
def delete(self, path):
|
||||
parent_path = path[:-1]
|
||||
orphan_path = path[-1]
|
||||
d = self._get_closest_node_and_prepath(parent_path)
|
||||
def _got_parent((prepath, node, remaining_path)):
|
||||
assert not remaining_path
|
||||
node.delete(orphan_path)
|
||||
# now serialize and upload
|
||||
subtree = node.get_subtree()
|
||||
return subtree.update(prepath, self.workqueue)
|
||||
d.addCallback(_got_parent)
|
||||
return d
|
||||
|
@ -5,7 +5,7 @@ from twisted.internet import defer
|
||||
from allmydata.filetree.interfaces import IOpener, IDirectoryNode
|
||||
from allmydata.filetree.directory import (ImmutableDirectorySubTree,
|
||||
SubTreeNode,
|
||||
MutableCHKDirectorySubTree)
|
||||
CHKDirectorySubTree)
|
||||
from allmydata.filetree.specification import (CHKFileSpecification,
|
||||
CHKDirectorySpecification)
|
||||
from allmydata import workqueue
|
||||
@ -303,3 +303,9 @@ class MultipleSubTrees(unittest.TestCase):
|
||||
|
||||
|
||||
return d
|
||||
|
||||
del OneSubTree
|
||||
del MultipleSubTrees
|
||||
|
||||
class Redirect(unittest.TestCase):
|
||||
pass
|
||||
|
@ -279,6 +279,11 @@ class FileHandle:
|
||||
# the originator of the filehandle reserves the right to close it
|
||||
pass
|
||||
|
||||
class IUploader(Interface):
|
||||
def upload(uploadable):
|
||||
"""Upload the file. 'uploadable' must impement IUploadable. This
|
||||
returns a Deferred which fires with the URI of the file."""
|
||||
|
||||
class Uploader(service.MultiService):
|
||||
"""I am a service that allows file uploading.
|
||||
"""
|
||||
@ -296,7 +301,7 @@ class Uploader(service.MultiService):
|
||||
return hasher.digest()
|
||||
|
||||
def upload(self, f):
|
||||
# this returns (verifierid, encoding_params)
|
||||
# this returns the URI
|
||||
assert self.parent
|
||||
assert self.running
|
||||
f = IUploadable(f)
|
||||
|
@ -26,7 +26,7 @@ class IWorkQueue(Interface):
|
||||
|
||||
def create_tempfile(suffix=""):
|
||||
"""Return (f, filename)."""
|
||||
def create_boxname():
|
||||
def create_boxname(contents=None):
|
||||
"""Return a unique box name (as a string)."""
|
||||
|
||||
def add_upload_chk(source_filename, stash_uri_in_boxname):
|
||||
@ -156,6 +156,9 @@ class WorkQueue(object):
|
||||
# line specifies what kind of step it is
|
||||
assert self.seqnum < 1000 # TODO: don't let this grow unboundedly
|
||||
|
||||
def set_vdrive(self, vdrive):
|
||||
self.vdrive = vdrive
|
||||
|
||||
def create_tempfile(self, suffix=""):
|
||||
randomname = b2a(os.urandom(10))
|
||||
filename = randomname + suffix
|
||||
@ -342,8 +345,8 @@ class WorkQueue(object):
|
||||
|
||||
def step_addpath(self, boxname, *path):
|
||||
data = self.read_from_box(boxname)
|
||||
child_spec = unserialize(data)
|
||||
return self.root.add_subpath(path, child_spec, self)
|
||||
child_node = unserialize(data) # TODO: unserialize ?
|
||||
return self.vdrive.add(path, node)
|
||||
|
||||
def step_retain_ssk(self, index_a, read_key_a):
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user