mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-08 11:24:25 +00:00
filetree: make delete() work
This commit is contained in:
parent
37ede96167
commit
bbf188d2c6
@ -207,6 +207,25 @@ class _DirectorySubTree(object):
|
||||
# now we can finally add the new node
|
||||
node.add(child_name, new_node)
|
||||
|
||||
def delete_node_at_path(self, path):
|
||||
assert len(path) > 0
|
||||
child_name = path[-1]
|
||||
|
||||
# first step: get the parent directory
|
||||
node = self.root
|
||||
for subdir_name in path[:-1]:
|
||||
subdir_node = node.get(subdir_name) # may raise NoSuchChildError
|
||||
node = subdir_node
|
||||
|
||||
# 'node' is now pointing at the parent directory. Let's make sure the
|
||||
# path they want to delete actually exists. We don't really care what
|
||||
# the child *is*, just that it exists.
|
||||
node.get(child_name) # may raise NoSuchChildError
|
||||
|
||||
# now delete it
|
||||
# TODO: How do we free the subtree that was just orphaned?
|
||||
node.delete(child_name)
|
||||
|
||||
|
||||
class LocalFileSubTreeNode(BaseDataNode):
|
||||
prefix = "LocalFileDirectory"
|
||||
|
@ -166,6 +166,12 @@ class ISubTree(Interface):
|
||||
run synchronously, and returns None.
|
||||
"""
|
||||
|
||||
def delete_node_at_path(path):
|
||||
"""Delete the node at the the given path.
|
||||
|
||||
This must run synchronously, and returns None.
|
||||
"""
|
||||
|
||||
def serialize_subtree_to_file(f):
|
||||
"""Create a string which describes my structure and write it to the
|
||||
given filehandle (using only .write()). This string should be
|
||||
@ -279,16 +285,18 @@ class IVirtualDrive(Interface):
|
||||
is complete.
|
||||
"""
|
||||
|
||||
def upload_now(path, uploadable):
|
||||
"""Upload a file to the given path. The path must not already exist.
|
||||
def upload_data(path, data):
|
||||
"""Upload a string 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. Do not use the workqueue.
|
||||
path[:-1] must refer to a writable DIRECTORY node.
|
||||
|
||||
This uses the workqueue, and returns None.
|
||||
"""
|
||||
|
||||
def upload_later(path, filename):
|
||||
"""Upload a file from disk to the given path. Use the workqueue.
|
||||
def upload(path, filename):
|
||||
"""Upload a file from disk to the given path.
|
||||
|
||||
This uses the workqueue, and returns None.
|
||||
"""
|
||||
|
||||
def delete(path):
|
||||
|
@ -74,6 +74,7 @@ class SubTreeMaker(object):
|
||||
|
||||
class VirtualDrive(object):
|
||||
implements(IVirtualDrive)
|
||||
debug = False
|
||||
|
||||
def __init__(self, workqueue, downloader, uploader, root_node):
|
||||
assert IWorkQueue(workqueue)
|
||||
@ -242,6 +243,8 @@ class VirtualDrive(object):
|
||||
# as necessary
|
||||
def _got_subtrees(subtrees, new_node_boxname):
|
||||
for (subtree, subpath) in reversed(subtrees):
|
||||
if self.debug:
|
||||
print "SUBTREE", subtree, subpath
|
||||
assert subtree.is_mutable()
|
||||
must_update = subtree.mutation_modifies_parent()
|
||||
subtree_node = subtree.create_node_now()
|
||||
@ -263,6 +266,11 @@ class VirtualDrive(object):
|
||||
d.addCallback(_got_subtrees, new_node_boxname)
|
||||
return d
|
||||
|
||||
def deletepath(self, path):
|
||||
if self.debug:
|
||||
print "DELETEPATH(%s)" % (path,)
|
||||
return self.addpath(path, None)
|
||||
|
||||
def modify_subtree(self, subtree_node, localpath, new_node,
|
||||
new_subtree_boxname=None):
|
||||
# TODO: I'm lying here, we don't know who the parent is, so we can't
|
||||
@ -278,7 +286,10 @@ class VirtualDrive(object):
|
||||
parent_is_mutable)
|
||||
def _got_subtree(subtree):
|
||||
assert subtree.is_mutable()
|
||||
subtree.put_node_at_path(localpath, new_node)
|
||||
if new_node:
|
||||
subtree.put_node_at_path(localpath, new_node)
|
||||
else:
|
||||
subtree.delete_node_at_path(localpath)
|
||||
return subtree.update_now(self._uploader)
|
||||
d.addCallback(_got_subtree)
|
||||
if new_subtree_boxname:
|
||||
@ -312,26 +323,18 @@ class VirtualDrive(object):
|
||||
target = download.Data()
|
||||
return self.download(path, target)
|
||||
|
||||
def upload_now(self, path, uploadable):
|
||||
def upload_data(self, path, data):
|
||||
assert isinstance(path, list)
|
||||
# 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))
|
||||
def _uploaded(uri):
|
||||
assert isinstance(uri, str)
|
||||
new_node = file.CHKFileNode().new(uri)
|
||||
boxname = self.workqueue.create_boxname(new_node)
|
||||
self.workqueue.add_addpath(boxname, path)
|
||||
self.workqueue.add_delete_box(boxname)
|
||||
d.addCallback(_uploaded)
|
||||
return d
|
||||
f, tempfilename = self.workqueue.create_tempfile()
|
||||
f.write(data)
|
||||
f.close()
|
||||
boxname = self.workqueue.create_boxname()
|
||||
self.workqueue.add_upload_chk(tempfilename, boxname)
|
||||
self.workqueue.add_addpath(boxname, path)
|
||||
self.workqueue.add_delete_box(boxname)
|
||||
self.workqueue.add_delete_tempfile(tempfilename)
|
||||
|
||||
def upload_later(self, path, filename):
|
||||
def upload(self, path, filename):
|
||||
assert isinstance(path, list)
|
||||
filename = os.path.abspath(filename)
|
||||
boxname = self.workqueue.create_boxname()
|
||||
@ -341,21 +344,7 @@ class VirtualDrive(object):
|
||||
|
||||
def delete(self, path):
|
||||
assert isinstance(path, list)
|
||||
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()
|
||||
boxname = subtree.update(self.workqueue)
|
||||
if boxname:
|
||||
self.workqueue.add_addpath(boxname, prepath)
|
||||
self.workqueue.add_delete_box(boxname)
|
||||
return self
|
||||
d.addCallback(_got_parent)
|
||||
return d
|
||||
self.workqueue.add_deletepath(path)
|
||||
|
||||
def add_node(self, path, node):
|
||||
assert isinstance(path, list)
|
||||
|
@ -224,6 +224,15 @@ class IUploader(Interface):
|
||||
def upload_ssk(write_capability, new_version, uploadable):
|
||||
pass # TODO
|
||||
|
||||
def upload_data(data):
|
||||
"""Like upload(), but accepts a string."""
|
||||
|
||||
def upload_filename(filename):
|
||||
"""Like upload(), but accepts an absolute pathname."""
|
||||
|
||||
def upload_filehandle(filehane):
|
||||
"""Like upload(), but accepts an open filehandle."""
|
||||
|
||||
|
||||
class IWorkQueue(Interface):
|
||||
"""Each filetable root is associated a work queue, which is persisted on
|
||||
@ -315,6 +324,13 @@ class IWorkQueue(Interface):
|
||||
steps to be added to the workqueue.
|
||||
"""
|
||||
|
||||
def add_deletepath(path):
|
||||
"""When executed, finds the subtree that contains the node at 'path'
|
||||
and modifies it (and any necessary parent subtrees) to delete that
|
||||
path. This will probably cause one or more 'add_modify_subtree' or
|
||||
'add_modify_redirection' steps to be added to the workqueue.
|
||||
"""
|
||||
|
||||
def add_modify_subtree(subtree_node, localpath, new_node_boxname,
|
||||
new_subtree_boxname=None):
|
||||
"""When executed, this step retrieves the subtree specified by
|
||||
@ -322,7 +338,8 @@ class IWorkQueue(Interface):
|
||||
then modifies it such that a subtree-relative 'localpath' points to
|
||||
the new node. It then serializes the subtree in its new form, and
|
||||
optionally puts a node that describes the new subtree in
|
||||
'new_node_boxname'.
|
||||
'new_node_boxname'. If 'new_node_boxname' is None, this deletes the
|
||||
given path.
|
||||
|
||||
The idea is that 'subtree_node' will refer a CHKDirectorySubTree, and
|
||||
'new_node_boxname' will contain the CHKFileNode that points to a
|
||||
|
@ -323,10 +323,11 @@ from allmydata.filetree.interfaces import (ISubTree, INode, IDirectoryNode,
|
||||
IFileNode, NoSuchDirectoryError,
|
||||
NoSuchChildError)
|
||||
from allmydata.filetree.file import CHKFileNode
|
||||
from allmydata import upload
|
||||
from allmydata.interfaces import IDownloader
|
||||
from allmydata.util import bencode
|
||||
|
||||
class InPairs(unittest.TestCase):
|
||||
class Utils(unittest.TestCase):
|
||||
def test_in_pairs(self):
|
||||
l = range(8)
|
||||
pairs = list(directory.in_pairs(l))
|
||||
@ -338,19 +339,28 @@ class FakeMesh(object):
|
||||
|
||||
def __init__(self):
|
||||
self.files = {}
|
||||
|
||||
def upload(self, uploadable):
|
||||
uri = "stub-uri-%d" % len(self.files)
|
||||
if self.debug:
|
||||
print "FakeMesh.upload -> %s" % uri
|
||||
assert upload.IUploadable.providedBy(uploadable)
|
||||
f = uploadable.get_filehandle()
|
||||
data = f.read()
|
||||
uploadable.close_filehandle(f)
|
||||
self.files[uri] = data
|
||||
return defer.succeed(uri)
|
||||
|
||||
def upload_filename(self, filename):
|
||||
uri = "stub-uri-%d" % len(self.files)
|
||||
if self.debug:
|
||||
print "FakeMesh.upload_filename(%s) -> %s" % (filename, uri)
|
||||
data = open(filename,"r").read()
|
||||
self.files[uri] = data
|
||||
return defer.succeed(uri)
|
||||
print "FakeMesh.upload_filename(%s)" % filename
|
||||
return self.upload(upload.FileName(filename))
|
||||
|
||||
def upload_data(self, data):
|
||||
uri = "stub-uri-%d" % len(self.files)
|
||||
if self.debug:
|
||||
print "FakeMesh.upload_data(%s) -> %s" % (data, uri)
|
||||
self.files[uri] = data
|
||||
return defer.succeed(uri)
|
||||
print "FakeMesh.upload_data(%s)" % data
|
||||
return self.upload(upload.Data(data))
|
||||
|
||||
def download(self, uri, target):
|
||||
if self.debug:
|
||||
print "FakeMesh.download(%s)" % uri
|
||||
@ -363,7 +373,9 @@ class FakeMesh(object):
|
||||
class VDrive(unittest.TestCase):
|
||||
|
||||
def makeVirtualDrive(self, basedir, root_node=None, mesh=None):
|
||||
wq = workqueue.WorkQueue(os.path.join(basedir, "1.workqueue"))
|
||||
wq = workqueue.WorkQueue(os.path.join("test_filetree",
|
||||
"VDrive",
|
||||
basedir, "1.workqueue"))
|
||||
if mesh:
|
||||
assert IUploader.providedBy(mesh)
|
||||
assert IDownloader.providedBy(mesh)
|
||||
@ -557,7 +569,7 @@ class VDrive(unittest.TestCase):
|
||||
f.write(DATA)
|
||||
f.close()
|
||||
|
||||
rc = v.upload_later(["a","b","upload1"], filename)
|
||||
rc = v.upload(["a","b","upload1"], filename)
|
||||
self.failUnlessIdentical(rc, None)
|
||||
|
||||
d = v.workqueue.flush()
|
||||
@ -579,16 +591,16 @@ class VDrive(unittest.TestCase):
|
||||
|
||||
def testCHKDirUpload(self):
|
||||
DATA = "here is some data\n"
|
||||
d = defer.maybeDeferred(self.makeCHKTree, "upload")
|
||||
filename = "upload1"
|
||||
f = open(filename, "w")
|
||||
f.write(DATA)
|
||||
f.close()
|
||||
|
||||
d = defer.maybeDeferred(self.makeCHKTree, "chk-upload")
|
||||
def _made(v):
|
||||
self.v = v
|
||||
|
||||
filename = "upload1"
|
||||
f = open(filename, "w")
|
||||
f.write(DATA)
|
||||
f.close()
|
||||
|
||||
rc = v.upload_later(["a","b","upload1"], filename)
|
||||
rc = v.upload(["a","b","upload1"], filename)
|
||||
self.failUnlessIdentical(rc, None)
|
||||
|
||||
return v.workqueue.flush()
|
||||
@ -609,3 +621,47 @@ class VDrive(unittest.TestCase):
|
||||
|
||||
return d
|
||||
|
||||
def testCHKDirDelete(self):
|
||||
DATA = "here is some data\n"
|
||||
filename = "upload1"
|
||||
f = open(filename, "w")
|
||||
f.write(DATA)
|
||||
f.close()
|
||||
|
||||
d = defer.maybeDeferred(self.makeCHKTree, "chk-delete")
|
||||
def _made(v):
|
||||
self.v = v
|
||||
d.addCallback(_made)
|
||||
|
||||
d.addCallback(lambda r:
|
||||
self.v.upload(["a","b","upload1"], filename))
|
||||
d.addCallback(lambda r:
|
||||
self.v.upload_data(["a","b","upload2"], DATA))
|
||||
d.addCallback(lambda r:
|
||||
self.v.upload(["a","c","upload3"], filename))
|
||||
d.addCallback(lambda r:
|
||||
self.v.workqueue.flush())
|
||||
|
||||
d.addCallback(lambda r: self.v.list([]))
|
||||
d.addCallback(lambda contents:
|
||||
self.failUnlessListsAreEqual(contents.keys(), ["a"]))
|
||||
d.addCallback(lambda r: self.v.list(["a"]))
|
||||
d.addCallback(lambda contents:
|
||||
self.failUnlessListsAreEqual(contents.keys(), ["b","c"]))
|
||||
d.addCallback(lambda r: self.v.list(["a","b"]))
|
||||
d.addCallback(lambda contents:
|
||||
self.failUnlessListsAreEqual(contents.keys(),
|
||||
["upload1", "upload2"]))
|
||||
#d.addCallback(lambda r: self.v.download_as_data(["a","b","upload1"]))
|
||||
#d.addCallback(self.failUnlessEqual, DATA)
|
||||
|
||||
# now delete it
|
||||
d.addCallback(lambda r: self.v.delete(["a","b","upload2"]))
|
||||
d.addCallback(lambda r: self.v.workqueue.flush())
|
||||
d.addCallback(lambda r: self.v.list(["a","b"]))
|
||||
d.addCallback(lambda contents:
|
||||
self.failUnlessListsAreEqual(contents.keys(),
|
||||
["upload1"]))
|
||||
|
||||
|
||||
return d
|
||||
|
@ -177,6 +177,12 @@ class WorkQueue(object):
|
||||
lines.extend(path)
|
||||
self._create_step_first(lines)
|
||||
|
||||
def add_deletepath(self, path):
|
||||
assert isinstance(path, (list, tuple))
|
||||
lines = ["deletepath"]
|
||||
lines.extend(path)
|
||||
self._create_step_first(lines)
|
||||
|
||||
def add_modify_subtree(self, subtree_node, localpath, new_node_boxname,
|
||||
new_subtree_boxname=None):
|
||||
assert isinstance(localpath, (list, tuple))
|
||||
@ -184,6 +190,8 @@ class WorkQueue(object):
|
||||
self.add_delete_box(box1)
|
||||
# TODO: it would probably be easier if steps were represented in
|
||||
# directories, with a separate file for each argument
|
||||
if new_node_boxname is None:
|
||||
new_node_boxname = ""
|
||||
if new_subtree_boxname is None:
|
||||
new_subtree_boxname = ""
|
||||
lines = ["modify_subtree",
|
||||
@ -323,6 +331,13 @@ class WorkQueue(object):
|
||||
print "STEP_ADDPATH(%s -> %s)" % (boxname, "/".join(path))
|
||||
path = list(path)
|
||||
return self.vdrive.addpath(path, boxname)
|
||||
|
||||
def step_deletepath(self, *path):
|
||||
if self.debug:
|
||||
print "STEP_DELETEPATH(%s)" % "/".join(path)
|
||||
path = list(path)
|
||||
return self.vdrive.deletepath(path)
|
||||
|
||||
def step_modify_subtree(self, subtree_node_boxname, new_node_boxname,
|
||||
new_subtree_boxname, *localpath):
|
||||
# the weird order of arguments is a consequence of the fact that
|
||||
@ -330,7 +345,9 @@ class WorkQueue(object):
|
||||
if not new_subtree_boxname:
|
||||
new_subtree_boxname = None
|
||||
subtree_node = self.read_from_box(subtree_node_boxname)
|
||||
new_node = self.read_from_box(new_node_boxname)
|
||||
new_node = None
|
||||
if new_node_boxname:
|
||||
new_node = self.read_from_box(new_node_boxname)
|
||||
localpath = list(localpath)
|
||||
return self.vdrive.modify_subtree(subtree_node, localpath,
|
||||
new_node, new_subtree_boxname)
|
||||
|
Loading…
x
Reference in New Issue
Block a user