vdrive: switch to URI:DIR and URI:DIR-RO, providing transitive readonlyness

This commit is contained in:
Brian Warner 2007-06-25 13:23:51 -07:00
parent 2cf7cfbe4a
commit fb02488a8e
13 changed files with 978 additions and 393 deletions

View File

@ -2,8 +2,8 @@
import os, sha, stat, time
from foolscap import Referenceable, SturdyRef
from zope.interface import implements
from allmydata.interfaces import RIClient
from allmydata import node
from allmydata.interfaces import RIClient, IDirectoryNode
from allmydata import node, vdrive, uri
from twisted.internet import defer, reactor
from twisted.application.internet import TimerService
@ -13,7 +13,6 @@ from allmydata.Crypto.Util.number import bytes_to_long
from allmydata.storageserver import StorageServer
from allmydata.upload import Uploader
from allmydata.download import Downloader
from allmydata.vdrive import DirectoryNode
from allmydata.webish import WebishServer
from allmydata.control import ControlServer
from allmydata.introducer import IntroducerClient
@ -28,7 +27,7 @@ class Client(node.Node, Referenceable):
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
MY_FURL_FILE = "myself.furl"
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
MY_VDRIVE_FURL_FILE = "my_vdrive.furl"
MY_VDRIVE_URI_FILE = "my_vdrive.uri"
# we're pretty narrow-minded right now
OLDEST_SUPPORTED_VERSION = allmydata.__version__
@ -115,16 +114,20 @@ class Client(node.Node, Referenceable):
def _got_vdrive(self, vdrive_server):
# vdrive_server implements RIVirtualDriveServer
self.log("connected to vdrive server")
d = vdrive_server.callRemote("get_public_root_furl")
d.addCallback(self._got_vdrive_root_furl, vdrive_server)
d.addCallback(self._create_my_vdrive)
d = vdrive_server.callRemote("get_public_root_uri")
d.addCallback(self._got_vdrive_uri)
d.addCallback(self._got_vdrive_rootnode)
d.addCallback(self._create_my_vdrive, vdrive_server)
d.addCallback(self._got_my_vdrive)
def _got_vdrive_root_furl(self, vdrive_root_furl, vdrive_server):
root = DirectoryNode(vdrive_root_furl, self)
def _got_vdrive_uri(self, root_uri):
furl, wk = uri.unpack_dirnode_uri(root_uri)
self._vdrive_furl = furl
return vdrive.create_directory_node(self, root_uri)
def _got_vdrive_rootnode(self, rootnode):
self.log("got vdrive root")
self._vdrive_server = vdrive_server
self._vdrive_root = root
self._vdrive_root = rootnode
self._connected_to_vdrive = True
#vdrive = self.getServiceNamed("vdrive")
@ -133,34 +136,34 @@ class Client(node.Node, Referenceable):
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
webish.set_vdrive_root(root)
webish.set_vdrive_rootnode(rootnode)
def _create_my_vdrive(self, ignored=None):
MY_VDRIVE_FURL_FILE = os.path.join(self.basedir,
self.MY_VDRIVE_FURL_FILE)
def _create_my_vdrive(self, ignored, vdrive_server):
MY_VDRIVE_URI_FILE = os.path.join(self.basedir,
self.MY_VDRIVE_URI_FILE)
try:
f = open(MY_VDRIVE_FURL_FILE, "r")
my_vdrive_furl = f.read().strip()
f = open(MY_VDRIVE_URI_FILE, "r")
my_vdrive_uri = f.read().strip()
f.close()
return defer.succeed(DirectoryNode(my_vdrive_furl, self))
return vdrive.create_directory_node(self, my_vdrive_uri)
except EnvironmentError:
d = self._vdrive_server.callRemote("create_directory")
assert self._vdrive_furl
d = vdrive.create_directory(self, self._vdrive_furl)
def _got_directory(dirnode):
f = open(MY_VDRIVE_FURL_FILE, "w")
f.write(dirnode.furl + "\n")
f = open(MY_VDRIVE_URI_FILE, "w")
f.write(dirnode.get_uri() + "\n")
f.close()
dirnode._set_client(self)
return dirnode
d.addCallback(_got_directory)
return d
def _got_my_vdrive(self, my_vdrive):
assert isinstance(my_vdrive, DirectoryNode), my_vdrive
IDirectoryNode(my_vdrive)
self._my_vdrive = my_vdrive
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
webish.set_my_vdrive_root(my_vdrive)
webish.set_my_vdrive_rootnode(my_vdrive)
def remote_get_versions(self):

View File

@ -1,165 +1,110 @@
import os
from zope.interface import implements
from foolscap import Referenceable
from allmydata.interfaces import RIVirtualDriveServer, RIMutableDirectoryNode
from allmydata.vdrive import FileNode, DirectoryNode
from allmydata.util import bencode, idlib
from twisted.application import service
from twisted.python import log
from foolscap import Referenceable
from allmydata.interfaces import RIVirtualDriveServer
from allmydata.util import bencode, idlib, hashutil, fileutil
from allmydata import uri
class BadNameError(Exception):
"""Bad filename component"""
class BadFileError(Exception):
class BadWriteEnablerError(Exception):
pass
class BadDirectoryError(Exception):
class ChildAlreadyPresentError(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', FURL).
"""
implements(RIMutableDirectoryNode)
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 _read_from_file(self):
data = open(os.path.join(self._basedir, self._name), "rb").read()
children = bencode.bdecode(data)
child_nodes = {}
for k,v in children.iteritems():
if v[0] == "file":
child_nodes[k] = FileNode(v[1])
elif v[0] == "subdir":
child_nodes[k] = DirectoryNode(v[1])
else:
raise RuntimeError("unknown child spec '%s'" % (v[0],))
return child_nodes
def _write_to_file(self, children):
child_nodes = {}
for k,v in children.iteritems():
if isinstance(v, FileNode):
child_nodes[k] = ("file", v.uri)
elif isinstance(v, DirectoryNode):
child_nodes[k] = ("subdir", v.furl)
else:
raise RuntimeError("unknown child[%s] node '%s'" % (k,v))
data = bencode.bencode(child_nodes)
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:
raise BadNameError("'%s' is not cool" % name)
# these are the public methods, available to anyone who holds a reference
def list(self):
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)
children = self._read_from_file()
if name not in children:
raise BadFileError("no such child")
return children[name]
remote_get = get
def add(self, name, child):
self.validate_name(name)
children = self._read_from_file()
if name in children:
raise BadNameError("the child already existed")
children[name] = child
self._write_to_file(children)
return child
remote_add = add
def remove(self, name):
self.validate_name(name)
children = self._read_from_file()
if name not in children:
raise BadFileError("cannot remove non-existent child")
child = children[name]
del children[name]
self._write_to_file(children)
return child
remote_remove = remove
class NoPublicRootError(Exception):
pass
class VirtualDriveServer(service.MultiService, Referenceable):
implements(RIVirtualDriveServer)
name = "filetable"
VDRIVEDIR = "vdrive"
def __init__(self, basedir=".", offer_public_root=True):
def __init__(self, basedir, offer_public_root=True):
service.MultiService.__init__(self)
vdrive_dir = os.path.join(basedir, self.VDRIVEDIR)
if not os.path.exists(vdrive_dir):
os.mkdir(vdrive_dir)
self._vdrive_dir = vdrive_dir
self._basedir = os.path.abspath(basedir)
fileutil.make_dirs(self._basedir)
self._root = None
if offer_public_root:
self._root = MutableDirectoryNode(vdrive_dir, "root")
rootfile = os.path.join(self._basedir, "root")
if not os.path.exists(rootfile):
write_key = hashutil.random_key()
(wk, we, rk, index) = \
hashutil.generate_dirnode_keys_from_writekey(write_key)
self.create_directory(index, we)
f = open(rootfile, "wb")
f.write(wk)
f.close()
self._root = wk
else:
f = open(rootfile, "rb")
self._root = f.read()
def startService(self):
service.MultiService.startService(self)
# _register_all_dirnodes exists to avoid hacking our Tub to
# automatically translate inbound your-reference names
# (Tub.getReferenceForName) into MutableDirectoryNode instances by
# looking in our basedir for them. Without that hack, we have to
# register all nodes at startup to make sure they'll be available to
# all callers. In addition, we must register any new ones that we
# create later on.
tub = self.parent.tub
self._root_furl = tub.registerReference(self._root, "root")
self._register_all_dirnodes(tub)
def set_furl(self, myfurl):
self._myfurl = myfurl
def _register_all_dirnodes(self, tub):
for name in os.listdir(self._vdrive_dir):
node = MutableDirectoryNode(self._vdrive_dir, name)
ignored_furl = tub.registerReference(node, name)
def get_public_root_furl(self):
def get_public_root_uri(self):
if self._root:
return self._root_furl
return uri.pack_dirnode_uri(self._myfurl, self._root)
raise NoPublicRootError
remote_get_public_root_furl = get_public_root_furl
remote_get_public_root_uri = get_public_root_uri
def create_directory(self):
node = MutableDirectoryNode(self._vdrive_dir)
furl = self.parent.tub.registerReference(node, node._name)
return DirectoryNode(furl)
def create_directory(self, index, write_enabler):
data = [write_enabler, []]
self._write_to_file(index, data)
return index
remote_create_directory = create_directory
# the file on disk consists of the write_enabler token and a list of
# (H(name), E(name), E(write), E(read)) tuples.
def _read_from_file(self, index):
name = idlib.b2a(index)
data = open(os.path.join(self._basedir, name), "rb").read()
return bencode.bdecode(data)
def _write_to_file(self, index, data):
name = idlib.b2a(index)
f = open(os.path.join(self._basedir, name), "wb")
f.write(bencode.bencode(data))
f.close()
def get(self, index, key):
data = self._read_from_file(index)
for (H_key, E_key, E_write, E_read) in data[1]:
if H_key == key:
return (E_write, E_read)
raise IndexError("unable to find key %s" % idlib.b2a(key))
remote_get = get
def list(self, index):
data = self._read_from_file(index)
response = [ (E_key, E_write, E_read)
for (H_key, E_key, E_write, E_read) in data[1] ]
return response
remote_list = list
def delete(self, index, write_enabler, key):
data = self._read_from_file(index)
if data[0] != write_enabler:
raise BadWriteEnablerError
for i,(H_key, E_key, E_write, E_read) in enumerate(data[1]):
if H_key == key:
del data[1][i]
self._write_to_file(index, data)
return
raise IndexError("unable to find key %s" % idlib.b2a(key))
remote_delete = delete
def set(self, index, write_enabler, key, name, write, read):
data = self._read_from_file(index)
if data[0] != write_enabler:
raise BadWriteEnablerError
# first, see if the key is already present
for i,(H_key, E_key, E_write, E_read) in enumerate(data[1]):
if H_key == key:
raise ChildAlreadyPresentError
# now just append the data
data[1].append( (key, name, write, read) )
self._write_to_file(index, data)
remote_set = set

View File

@ -126,33 +126,79 @@ RIMutableDirectoryNode_ = Any() # TODO: how can we avoid this?
FileNode_ = Any() # TODO: foolscap needs constraints on copyables
DirectoryNode_ = Any() # TODO: same
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
class RIMutableDirectoryNode(RemoteInterface):
def list():
return ListOf( TupleOf(str, # name, relative to directory
AnyNode_,
),
maxLength=100,
)
def get(name=str):
return AnyNode_
def add(name=str, what=AnyNode_):
return AnyNode_
def remove(name=str):
return AnyNode_
EncryptedThing = str
class RIVirtualDriveServer(RemoteInterface):
def get_public_root_furl():
"""If this vdrive server does not offer a public root, this will
raise an exception."""
return FURL
def get_public_root_uri():
"""Obtain the URI for this server's global publically-writable root
directory. This returns a read-write directory URI.
If this vdrive server does not offer a public root, this will
raise an exception."""
return URI
def create_directory(index=Hash, write_enabler=Hash):
"""Create a new (empty) directory, unattached to anything else.
This returns the same index that was passed in.
"""
return Hash
def get(index=Hash, key=Hash):
"""Retrieve a named child of the given directory. 'index' specifies
which directory is being accessed, and is generally the hash of the
read key. 'key' is the hash of the read key and the child name.
This operation returns a pair of encrypted strings. The first string
is meant to be decrypted by the Write Key and provides read-write
access to the child. If this directory holds read-only access to the
child, this first string will be an empty string. The second string
is meant to be decrypted by the Read Key and provides read-only
access to the child.
When the child is a read-write directory, the encrypted URI:DIR-RO
will be in the read slot, and the encrypted URI:DIR will be in the
write slot. When the child is a read-only directory, the encrypted
URI:DIR-RO will be in the read slot and the write slot will be empty.
When the child is a CHK file, the encrypted URI:CHK will be in the
read slot, and the write slot will be empty.
This might raise IndexError if there is no child by the desired name.
"""
return (EncryptedThing, EncryptedThing)
def list(index=Hash):
"""List the contents of a directory.
This returns a list of (NAME, WRITE, READ) tuples. Each value is an
encrypted string (although the WRITE value may sometimes be an empty
string).
NAME: the child name, encrypted with the Read Key
WRITE: the child write URI, encrypted with the Write Key, or an
empty string if this child is read-only
READ: the child read URI, encrypted with the Read Key
"""
return ListOf((EncryptedThing, EncryptedThing, EncryptedThing),
maxLength=100,
)
def set(index=Hash, write_enabler=Hash, key=Hash,
name=EncryptedThing, write=EncryptedThing, read=EncryptedThing):
"""Set a child object.
This will raise IndexError if a child with the given name already
exists.
"""
pass
def delete(index=Hash, write_enabler=Hash, key=Hash):
"""Delete a specific child.
This uses the hashed key to locate a specific child, and deletes it.
"""
pass
def create_directory():
return DirectoryNode_
class IFileNode(Interface):
def download(target):
@ -161,23 +207,66 @@ class IFileNode(Interface):
pass
class IDirectoryNode(Interface):
def is_mutable():
"""Return True if this directory is mutable, False if it is read-only.
"""
def get_uri():
"""Return the directory URI that can be used by others to get access
to this directory node. If this node is read-only, the URI will only
offer read-only access. If this node is read-write, the URI will
offer read-write acess.
If you have read-write access to a directory and wish to share merely
read-only access with others, use get_immutable_uri().
The dirnode ('1') URI returned by this method can be used in set() on
a different directory ('2') to 'mount' a reference to this directory
('1') under the other ('2'). This URI is just a string, so it can be
passed around through email or other out-of-band protocol.
"""
def get_immutable_uri():
"""Return the directory URI that can be used by others to get
read-only access to this directory node. The result is a read-only
URI, regardless of whether this dirnode is read-only or read-write.
If you have merely read-only access to this dirnode,
get_immutable_uri() will return the same thing as get_uri().
"""
def list():
"""I return a Deferred that fires with a"""
pass
"""I return a Deferred that fires with a dictionary mapping child
name to an IFileNode or IDirectoryNode."""
def get(name):
"""I return a Deferred that fires with a specific named child."""
pass
"""I return a Deferred that fires with a specific named child node,
either an IFileNode or an IDirectoryNode."""
def add(name, child):
def set_uri(name, child_uri):
"""I add a child (by URI) at the specific name. I return a Deferred
that fires when the operation finishes.
The child_uri could be for a file, or for a directory (either
read-write or read-only, using a URI that came from get_uri() ).
If this directory node is read-only, the Deferred will errback with a
NotMutableError."""
def set_node(name, child):
"""I add a child at the specific name. I return a Deferred that fires
when the operation finishes."""
when the operation finishes. This Deferred will fire with the child
node that was just added.
If this directory node is read-only, the Deferred will errback with a
NotMutableError."""
def add_file(name, uploadable):
"""I upload a file (using the given IUploadable), then attach the
resulting FileNode to the directory at the given name."""
resulting FileNode to the directory at the given name. I return a
Deferred that fires (with the IFileNode of the uploaded file) when
the operation completes."""
def remove(name):
def delete(name):
"""I remove the child at the specific name. I return a Deferred that
fires when the operation finishes."""
@ -185,18 +274,6 @@ class IDirectoryNode(Interface):
"""I create and attach an empty directory at the given name. I return
a Deferred that fires when the operation finishes."""
def attach_shared_directory(name, furl):
"""I attach a directory that was shared (possibly by someone else)
with IDirectoryNode.get_furl to this parent at the given name. I
return a Deferred that fires when the operation finishes."""
def get_shared_directory_furl():
"""I return a FURL that can be used to attach this directory to
somewhere else. The FURL is just a string, so it can be passed
through email or other out-of-band protocol. Use it by passing it in
to attach_shared_directory(). I return a Deferred that fires when the
operation finishes."""
def move_child_to(current_child_name, new_parent, new_child_name=None):
"""I take one of my children and move them to a new parent. The child
is referenced by name. On the new parent, the child will live under

View File

@ -8,6 +8,7 @@ from allmydata.introducer import Introducer
class IntroducerAndVdrive(node.Node):
PORTNUMFILE = "introducer.port"
NODETYPE = "introducer"
VDRIVEDIR = "vdrive"
def __init__(self, basedir="."):
node.Node.__init__(self, basedir)
@ -21,8 +22,11 @@ class IntroducerAndVdrive(node.Node):
f.write(self.urls["introducer"] + "\n")
f.close()
vds = self.add_service(VirtualDriveServer(self.basedir))
self.urls["vdrive"] = self.tub.registerReference(vds, "vdrive")
vdrive_dir = os.path.join(self.basedir, self.VDRIVEDIR)
vds = self.add_service(VirtualDriveServer(vdrive_dir))
vds_furl = self.tub.registerReference(vds, "vdrive")
vds.set_furl(vds_furl)
self.urls["vdrive"] = vds_furl
self.log(" vdrive is at %s" % self.urls["vdrive"])
f = open(os.path.join(self.basedir, "vdrive.furl"), "w")
f.write(self.urls["vdrive"] + "\n")

View File

@ -129,6 +129,30 @@ class DumpOptions(usage.Options):
if not self['filename']:
raise usage.UsageError("<filename> parameter is required")
class DumpRootDirnodeOptions(BasedirMixin, usage.Options):
optParameters = [
["basedir", "C", None, "the vdrive-server's base directory"],
]
class DumpDirnodeOptions(BasedirMixin, usage.Options):
optParameters = [
["uri", "u", None, "the URI of the dirnode to dump."],
["basedir", "C", None, "which directory to create the introducer in"],
]
optFlags = [
["verbose", "v", "be extra noisy (show encrypted data)"],
]
def parseArgs(self, *args):
if len(args) == 1:
self['uri'] = args[-1]
args = args[:-1]
BasedirMixin.parseArgs(self, *args)
def postOptions(self):
BasedirMixin.postOptions(self)
if not self['uri']:
raise usage.UsageError("<uri> parameter is required")
client_tac = """
# -*- python -*-
@ -164,7 +188,9 @@ class Options(usage.Options):
["restart", None, RestartOptions, "Restart a node."],
["dump-uri-extension", None, DumpOptions,
"Unpack and display the contents of a uri_extension file."],
["dump-directory-node", None, DumpOptions,
["dump-root-dirnode", None, DumpRootDirnodeOptions,
"Compute most of the URI for the vdrive server's root dirnode."],
["dump-dirnode", None, DumpDirnodeOptions,
"Unpack and display the contents of a vdrive DirectoryNode."],
]
@ -211,8 +237,10 @@ def runner(argv, run_by_human=True):
rc = start(basedir, so) or rc
elif command == "dump-uri-extension":
rc = dump_uri_extension(so)
elif command == "dump-directory-node":
rc = dump_directory_node(so)
elif command == "dump-root-dirnode":
rc = dump_root_dirnode(so.basedirs[0], so)
elif command == "dump-dirnode":
rc = dump_directory_node(so.basedirs[0], so)
return rc
def run():
@ -329,30 +357,71 @@ def dump_uri_extension(config):
print
return 0
def dump_directory_node(config):
from allmydata import filetable, vdrive
filename = config['filename']
def dump_root_dirnode(basedir, config):
from allmydata import uri
basedir, name = os.path.split(filename)
dirnode = filetable.MutableDirectoryNode(basedir, name)
root_dirnode_file = os.path.join(basedir, "vdrive", "root")
try:
f = open(root_dirnode_file, "rb")
key = f.read()
rooturi = uri.pack_dirnode_uri("fakeFURL", key)
print rooturi
return 0
except EnvironmentError:
print "unable to read root dirnode file from %s" % root_dirnode_file
return 1
def dump_directory_node(basedir, config):
from allmydata import filetable, vdrive, uri
from allmydata.util import hashutil, idlib
dir_uri = config['uri']
verbose = config['verbose']
furl, key = uri.unpack_dirnode_uri(dir_uri)
if uri.is_mutable_dirnode_uri(dir_uri):
wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
else:
wk, we, rk, index = hashutil.generate_dirnode_keys_from_readkey(key)
filename = os.path.join(basedir, "vdrive", idlib.b2a(index))
print
print "DirectoryNode at %s" % name
print "dirnode uri: %s" % dir_uri
print "filename : %s" % filename
print "index : %s" % idlib.b2a(index)
if wk:
print "writekey : %s" % idlib.b2a(wk)
print "write_enabler: %s" % idlib.b2a(we)
else:
print "writekey : None"
print "write_enabler: None"
print "readkey : %s" % idlib.b2a(rk)
print
children = dirnode._read_from_file()
names = sorted(children.keys())
for name in names:
v = children[name]
if isinstance(v, vdrive.FileNode):
value = "File (uri=%s...)" % v.uri[:40]
elif isinstance(v, vdrive.DirectoryNode):
lastslash = v.furl.rindex("/")
furlname = v.furl[lastslash+1:lastslash+1+15]
value = "Directory (furl=%s.../%s...)" % (v.furl[:15], furlname)
else:
value = "weird: %s" % (v,)
print "%20s: %s" % (name, value)
print
vds = filetable.VirtualDriveServer(os.path.join(basedir, "vdrive"), False)
data = vds._read_from_file(index)
if we:
if we != data[0]:
print "ERROR: write_enabler does not match"
for (H_key, E_key, E_write, E_read) in data[1]:
if verbose:
print " H_key %s" % idlib.b2a(H_key)
print " E_key %s" % idlib.b2a(E_key)
print " E_write %s" % idlib.b2a(E_write)
print " E_read %s" % idlib.b2a(E_read)
key = vdrive.decrypt(rk, E_key)
print " key %s" % key
if hashutil.dir_name_hash(rk, key) != H_key:
print " ERROR: H_key does not match"
if wk and E_write:
if len(E_write) < 14:
print " ERROR: write data is short:", idlib.b2a(E_write)
write = vdrive.decrypt(wk, E_write)
print " write: %s" % write
read = vdrive.decrypt(rk, E_read)
print " read: %s" % read
print
return 0

View File

@ -1,54 +1,86 @@
import os
from twisted.trial import unittest
from allmydata.filetable import (MutableDirectoryNode,
BadFileError, BadNameError)
from allmydata.vdrive import FileNode, DirectoryNode
from allmydata import filetable, uri
from allmydata.util import hashutil
class FileTable(unittest.TestCase):
def test_files(self):
os.mkdir("filetable")
basedir = os.path.abspath("filetable")
root = MutableDirectoryNode(basedir, "root")
self.failUnlessEqual(root.list(), [])
root.add("one", FileNode("vid-one"))
root.add("two", FileNode("vid-two"))
self.failUnlessEqual(root.list(), [("one", FileNode("vid-one")),
("two", FileNode("vid-two"))])
root.remove("two")
self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))])
self.failUnlessRaises(BadFileError, root.remove, "two")
self.failUnlessRaises(BadFileError, root.remove, "three")
def test_vdrive_server(self):
basedir = "filetable/FileTable/test_vdrive_server"
vds = filetable.VirtualDriveServer(basedir)
vds.set_furl("myFURL")
self.failUnlessEqual(root.get("one"), FileNode("vid-one"))
self.failUnlessRaises(BadFileError, root.get, "missing")
self.failUnlessRaises(BadNameError, root.get, "/etc/passwd") # evil
self.failUnlessRaises(BadNameError, root.get, "..") # sneaky
self.failUnlessRaises(BadNameError, root.get, ".") # dumb
root_uri = vds.get_public_root_uri()
self.failUnless(uri.is_dirnode_uri(root_uri))
self.failUnless(uri.is_mutable_dirnode_uri(root_uri))
furl, key = uri.unpack_dirnode_uri(root_uri)
self.failUnlessEqual(furl, "myFURL")
self.failUnlessEqual(len(key), hashutil.KEYLEN)
# now play with directories
subdir1 = root.add("subdir1", DirectoryNode("subdir1.furl"))
self.failUnless(isinstance(subdir1, DirectoryNode))
subdir1a = root.get("subdir1")
self.failUnless(isinstance(subdir1a, DirectoryNode))
self.failUnlessEqual(subdir1a, subdir1)
entries = root.list()
self.failUnlessEqual(len(entries), 2)
one_index = entries.index( ("one", FileNode("vid-one")) )
subdir_index = 1 - one_index
self.failUnlessEqual(entries[subdir_index][0], "subdir1")
subdir2 = entries[subdir_index][1]
self.failUnless(isinstance(subdir2, DirectoryNode))
wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
empty_list = vds.list(index)
self.failUnlessEqual(empty_list, [])
self.failUnlessEqual(len(root.list()), 2)
vds.set(index, we, "key1", "name1", "write1", "read1")
vds.set(index, we, "key2", "name2", "", "read2")
self.failUnlessRaises(BadNameError, # replacing an existing child
root.add,
"subdir1", DirectoryNode("subdir1.furl"))
self.failUnlessRaises(filetable.ChildAlreadyPresentError,
vds.set,
index, we, "key2", "name2", "write2", "read2")
root.remove("subdir1")
self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))])
self.failUnlessRaises(filetable.BadWriteEnablerError,
vds.set,
index, "not the write enabler",
"key2", "name2", "write2", "read2")
self.failUnlessEqual(vds.get(index, "key1"),
("write1", "read1"))
self.failUnlessEqual(vds.get(index, "key2"),
("", "read2"))
self.failUnlessRaises(IndexError,
vds.get, index, "key3")
self.failUnlessEqual(sorted(vds.list(index)),
[ ("name1", "write1", "read1"),
("name2", "", "read2"),
])
self.failUnlessRaises(filetable.BadWriteEnablerError,
vds.delete,
index, "not the write enabler", "name1")
self.failUnlessEqual(sorted(vds.list(index)),
[ ("name1", "write1", "read1"),
("name2", "", "read2"),
])
self.failUnlessRaises(IndexError,
vds.delete,
index, we, "key3")
vds.delete(index, we, "key1")
self.failUnlessEqual(sorted(vds.list(index)),
[ ("name2", "", "read2"),
])
self.failUnlessRaises(IndexError,
vds.get, index, "key1")
self.failUnlessEqual(vds.get(index, "key2"),
("", "read2"))
vds2 = filetable.VirtualDriveServer(basedir)
vds2.set_furl("myFURL")
root_uri2 = vds.get_public_root_uri()
self.failUnless(uri.is_mutable_dirnode_uri(root_uri2))
furl2, key2 = uri.unpack_dirnode_uri(root_uri2)
(wk2, we2, rk2, index2) = \
hashutil.generate_dirnode_keys_from_writekey(key2)
self.failUnlessEqual(sorted(vds2.list(index2)),
[ ("name2", "", "read2"),
])
def test_no_root(self):
basedir = "FileTable/test_no_root"
vds = filetable.VirtualDriveServer(basedir, offer_public_root=False)
vds.set_furl("myFURL")
self.failUnlessRaises(filetable.NoPublicRootError,
vds.get_public_root_uri)

View File

@ -240,7 +240,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
d1.addCallback(lambda subdir1_node:
subdir1_node.add_file("mydata567", ut))
def _stash_uri(filenode):
self.uri = filenode.uri
self.uri = filenode.get_uri()
return filenode
d1.addCallback(_stash_uri)
return d1

View File

@ -1,17 +1,262 @@
import os
from twisted.trial import unittest
from twisted.internet import defer
from allmydata import vdrive, filetable
from twisted.python import failure
from allmydata import vdrive, filetable, uri
from allmydata.interfaces import IDirectoryNode
from foolscap import eventual
class LocalDirNode(filetable.MutableDirectoryNode):
class LocalReference:
def __init__(self, target):
self.target = target
def callRemote(self, methname, *args, **kwargs):
def _call():
meth = getattr(self, methname)
def _call(ignored):
meth = getattr(self.target, methname)
return meth(*args, **kwargs)
return defer.maybeDeferred(_call)
d = eventual.fireEventually(None)
d.addCallback(_call)
return d
class MyTub:
def __init__(self, vds, myfurl):
self.vds = vds
self.myfurl = myfurl
def getReference(self, furl):
assert furl == self.myfurl
return eventual.fireEventually(LocalReference(self.vds))
class MyClient:
def __init__(self, vds, myfurl):
self.tub = MyTub(vds, myfurl)
class Test(unittest.TestCase):
def test_create_directory(self):
basedir = "vdrive/test_create_directory"
vds = filetable.VirtualDriveServer(basedir)
vds.set_furl("myFURL")
self.client = client = MyClient(vds, "myFURL")
d = vdrive.create_directory(client, "myFURL")
def _created(node):
self.failUnless(IDirectoryNode.providedBy(node))
self.failUnless(node.is_mutable())
d.addCallback(_created)
return d
def test_one(self):
basedir = "vdrive/test_one"
vds = filetable.VirtualDriveServer(basedir)
vds.set_furl("myFURL")
root_uri = vds.get_public_root_uri()
self.client = client = MyClient(vds, "myFURL")
d1 = vdrive.create_directory_node(client, root_uri)
d2 = vdrive.create_directory_node(client, root_uri)
d = defer.gatherResults( [d1,d2] )
d.addCallback(self._test_one_1)
return d
def _test_one_1(self, (rootnode1, rootnode2) ):
self.failUnlessEqual(rootnode1, rootnode2)
self.failIfEqual(rootnode1, "not")
self.rootnode = rootnode = rootnode1
self.failUnless(rootnode.is_mutable())
self.readonly_uri = rootnode.get_immutable_uri()
d = vdrive.create_directory_node(self.client, self.readonly_uri)
d.addCallback(self._test_one_2)
return d
def _test_one_2(self, ro_rootnode):
self.ro_rootnode = ro_rootnode
self.failIf(ro_rootnode.is_mutable())
self.failUnlessEqual(ro_rootnode.get_immutable_uri(),
self.readonly_uri)
rootnode = self.rootnode
ignored = rootnode.dump()
# root/
d = rootnode.list()
def _listed(res):
self.failUnlessEqual(res, {})
d.addCallback(_listed)
file1 = uri.pack_uri("i"*32, "k"*16, "e"*32, 25, 100, 12345)
file2 = uri.pack_uri("i"*31 + "2", "k"*16, "e"*32, 25, 100, 12345)
file2_node = vdrive.FileNode(file2, None)
d.addCallback(lambda res: rootnode.set_uri("foo", file1))
# root/
# root/foo =file1
d.addCallback(lambda res: rootnode.list())
def _listed2(res):
self.failUnlessEqual(res.keys(), ["foo"])
file1_node = res["foo"]
self.failUnless(isinstance(file1_node, vdrive.FileNode))
self.failUnlessEqual(file1_node.uri, file1)
d.addCallback(_listed2)
d.addCallback(lambda res: rootnode.get("foo"))
def _got_foo(res):
self.failUnless(isinstance(res, vdrive.FileNode))
self.failUnlessEqual(res.uri, file1)
d.addCallback(_got_foo)
d.addCallback(lambda res: rootnode.get("missing"))
# this should raise an exception
d.addBoth(self.shouldFail, IndexError, "get('missing')",
"unable to find child named 'missing'")
d.addCallback(lambda res: rootnode.create_empty_directory("bar"))
# root/
# root/foo =file1
# root/bar/
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["foo", "bar"])
def _listed3(res):
self.failIfEqual(res["foo"], res["bar"])
self.failIfEqual(res["bar"], res["foo"])
self.failIfEqual(res["foo"], "not")
self.failIfEqual(res["bar"], self.rootnode)
self.failUnlessEqual(res["foo"], res["foo"])
# make sure the objects can be used as dict keys
testdict = {res["foo"]: 1, res["bar"]: 2}
bar_node = res["bar"]
self.failUnless(isinstance(bar_node, vdrive.MutableDirectoryNode))
self.bar_node = bar_node
bar_ro_uri = bar_node.get_immutable_uri()
return rootnode.set_uri("bar-ro", bar_ro_uri)
d.addCallback(_listed3)
# root/
# root/foo =file1
# root/bar/
# root/bar-ro/ (read-only)
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["foo", "bar", "bar-ro"])
def _listed4(res):
self.failIf(res["bar-ro"].is_mutable())
self.bar_node_readonly = res["bar-ro"]
# add another file to bar/
bar = res["bar"]
return bar.set_node("file2", file2_node)
d.addCallback(_listed4)
d.addCallback(self.failUnlessIdentical, file2_node)
# and a directory
d.addCallback(lambda res: self.bar_node.create_empty_directory("baz"))
# root/
# root/foo =file1
# root/bar/
# root/bar/file2 =file2
# root/bar/baz/
# root/bar-ro/ (read-only)
# root/bar-ro/file2 =file2
# root/bar-ro/baz/
d.addCallback(lambda res: self.bar_node.list())
d.addCallback(self.failUnlessKeysMatch, ["file2", "baz"])
d.addCallback(lambda res:
self.failUnless(res["baz"].is_mutable()))
d.addCallback(lambda res: self.bar_node_readonly.list())
d.addCallback(self.failUnlessKeysMatch, ["file2", "baz"])
d.addCallback(lambda res:
self.failIf(res["baz"].is_mutable()))
# try to add a file to bar-ro, should get exception
d.addCallback(lambda res:
self.bar_node_readonly.set_uri("file3", file2))
d.addBoth(self.shouldFail, vdrive.NotMutableError,
"bar-ro.set('file3')")
# try to delete a file from bar-ro, should get exception
d.addCallback(lambda res: self.bar_node_readonly.delete("file2"))
d.addBoth(self.shouldFail, vdrive.NotMutableError,
"bar-ro.delete('file2')")
# try to mkdir in bar-ro, should get exception
d.addCallback(lambda res:
self.bar_node_readonly.create_empty_directory("boffo"))
d.addBoth(self.shouldFail, vdrive.NotMutableError,
"bar-ro.mkdir('boffo')")
d.addCallback(lambda res: rootnode.delete("foo"))
# root/
# root/bar/
# root/bar/file2 =file2
# root/bar/baz/
# root/bar-ro/ (read-only)
# root/bar-ro/file2 =file2
# root/bar-ro/baz/
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro"])
d.addCallback(lambda res:
self.bar_node.move_child_to("file2",
self.rootnode, "file4"))
# root/
# root/file4 = file4
# root/bar/
# root/bar/baz/
# root/bar-ro/ (read-only)
# root/bar-ro/baz/
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro", "file4"])
d.addCallback(lambda res:self.bar_node.list())
d.addCallback(self.failUnlessKeysMatch, ["baz"])
d.addCallback(lambda res:self.bar_node_readonly.list())
d.addCallback(self.failUnlessKeysMatch, ["baz"])
d.addCallback(lambda res:
rootnode.move_child_to("file4",
self.bar_node_readonly, "boffo"))
d.addBoth(self.shouldFail, vdrive.NotMutableError,
"mv root/file4 root/bar-ro/boffo")
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro", "file4"])
d.addCallback(lambda res:self.bar_node.list())
d.addCallback(self.failUnlessKeysMatch, ["baz"])
d.addCallback(lambda res:self.bar_node_readonly.list())
d.addCallback(self.failUnlessKeysMatch, ["baz"])
d.addCallback(lambda res:
rootnode.move_child_to("file4", self.bar_node))
d.addCallback(lambda res: rootnode.list())
d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro"])
d.addCallback(lambda res:self.bar_node.list())
d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"])
d.addCallback(lambda res:self.bar_node_readonly.list())
d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"])
return d
def shouldFail(self, res, expected_failure, which, substring=None):
if isinstance(res, failure.Failure):
res.trap(expected_failure)
if substring:
self.failUnless(substring in str(res),
"substring '%s' not in '%s'"
% (substring, str(res)))
else:
self.fail("%s was supposed to raise %s, not get '%s'" %
(which, expected_failure, res))
def failUnlessKeysMatch(self, res, expected_keys):
self.failUnlessEqual(sorted(res.keys()),
sorted(expected_keys))
return res
"""
class Traverse(unittest.TestCase):
def make_tree(self, basedir):
os.makedirs(basedir)
@ -67,3 +312,4 @@ class Traverse(unittest.TestCase):
["2.a", "2.b", "d2.1"]))
return d
del Traverse
"""

View File

@ -86,3 +86,31 @@ def unpack_extension_readable(data):
if "hash" in k:
unpacked[k] = idlib.b2a(unpacked[k])
return unpacked
def is_dirnode_uri(uri):
return uri.startswith("URI:DIR:") or uri.startswith("URI:DIR-RO:")
def is_mutable_dirnode_uri(uri):
return uri.startswith("URI:DIR:")
def unpack_dirnode_uri(uri):
assert is_dirnode_uri(uri)
# URI:DIR:furl:key
# but note that the furl contains colons
for prefix in ("URI:DIR:", "URI:DIR-RO:"):
if uri.startswith(prefix):
uri = uri[len(prefix):]
break
else:
assert 0
colon = uri.rindex(":")
furl = uri[:colon]
key = uri[colon+1:]
return furl, idlib.a2b(key)
def make_immutable_dirnode_uri(mutable_uri):
assert is_mutable_dirnode_uri(mutable_uri)
furl, writekey = unpack_dirnode_uri(mutable_uri)
readkey = hashutil.dir_read_key_hash(writekey)
return "URI:DIR-RO:%s:%s" % (furl, idlib.b2a(readkey))
def pack_dirnode_uri(furl, writekey):
return "URI:DIR:%s:%s" % (furl, idlib.b2a(writekey))

View File

@ -1,4 +1,5 @@
from allmydata.Crypto.Hash import SHA256
import os
def netstring(s):
return "%d:%s," % (len(s), s,)
@ -56,3 +57,25 @@ def key_hash(data):
def key_hasher():
return tagged_hasher("allmydata_encryption_key_v1")
KEYLEN = 16
def random_key():
return os.urandom(KEYLEN)
def dir_write_enabler_hash(write_key):
return tagged_hash("allmydata_dir_write_enabler_v1", write_key)
def dir_read_key_hash(write_key):
return tagged_hash("allmydata_dir_read_key_v1", write_key)[:KEYLEN]
def dir_index_hash(read_key):
return tagged_hash("allmydata_dir_index_v1", read_key)
def dir_name_hash(readkey, name):
return tagged_pair_hash("allmydata_dir_name_v1", readkey, name)
def generate_dirnode_keys_from_writekey(write_key):
readkey = dir_read_key_hash(write_key)
write_enabler = dir_write_enabler_hash(write_key)
index = dir_index_hash(readkey)
return write_key, write_enabler, readkey, index
def generate_dirnode_keys_from_readkey(read_key):
index = dir_index_hash(read_key)
return None, None, read_key, index

View File

@ -1,11 +1,18 @@
"""This is the client-side facility to manipulate virtual drives."""
import os.path
from zope.interface import implements
from twisted.application import service
from twisted.internet import defer
from twisted.python import log
from allmydata import upload, download
from foolscap import Copyable, RemoteCopy
from allmydata import upload, download, uri
from allmydata.Crypto.Cipher import AES
from allmydata.util import hashutil, idlib
from allmydata.interfaces import IDirectoryNode, IFileNode
class NotMutableError(Exception):
pass
class VDrive(service.MultiService):
name = "vdrive"
@ -181,99 +188,243 @@ class VDrive(service.MultiService):
return self.get_file(from_where, download.FileHandle(filehandle))
class DirectoryNode(Copyable, RemoteCopy):
"""I have either a .furl attribute or a .get(tub) method."""
typeToCopy = "allmydata.com/tahoe/interfaces/DirectoryNode/v1"
copytype = typeToCopy
def __init__(self, furl=None, client=None):
# RemoteCopy subclasses are always called without arguments
self.furl = furl
self._set_client(client)
def _set_client(self, client):
def create_directory_node(client, diruri):
assert uri.is_dirnode_uri(diruri)
if uri.is_mutable_dirnode_uri(diruri):
dirnode_class = MutableDirectoryNode
else:
dirnode_class = ImmutableDirectoryNode
(furl, key) = uri.unpack_dirnode_uri(diruri)
d = client.tub.getReference(furl)
def _got(rref):
dirnode = dirnode_class(diruri, client, rref, key)
return dirnode
d.addCallback(_got)
return d
def encrypt(key, data):
# TODO: add the hmac
IV = os.urandom(14)
counterstart = IV + "\x00"*2
assert len(counterstart) == 16, len(counterstart)
cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
crypttext = cryptor.encrypt(data)
return IV + crypttext
def decrypt(key, data):
# TODO: validate the hmac
assert len(data) >= 14, len(data)
IV = data[:14]
counterstart = IV + "\x00"*2
assert len(counterstart) == 16, len(counterstart)
cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
plaintext = cryptor.decrypt(data[14:])
return plaintext
class ImmutableDirectoryNode:
implements(IDirectoryNode)
def __init__(self, myuri, client, rref, readkey):
self._uri = myuri
self._client = client
return self
def getStateToCopy(self):
return {"furl": self.furl }
def setCopyableState(self, state):
self.furl = state['furl']
self._tub = client.tub
self._rref = rref
self._readkey = readkey
self._writekey = None
self._write_enabler = None
self._index = hashutil.dir_index_hash(self._readkey)
self._mutable = False
def dump(self):
return ["URI: %s" % self._uri,
"rk: %s" % idlib.b2a(self._readkey),
"index: %s" % idlib.b2a(self._index),
]
def is_mutable(self):
return self._mutable
def get_uri(self):
return self._uri
def get_immutable_uri(self):
# return the dirnode URI for a read-only form of this directory
if self._mutable:
return uri.make_immutable_dirnode_uri(self._uri)
else:
return self._uri
def __hash__(self):
return hash((self.__class__, self.furl))
return hash((self.__class__, self._uri))
def __cmp__(self, them):
if cmp(type(self), type(them)):
return cmp(type(self), type(them))
if cmp(self.__class__, them.__class__):
return cmp(self.__class__, them.__class__)
return cmp(self.furl, them.furl)
return cmp(self._uri, them._uri)
def _encrypt(self, key, data):
return encrypt(key, data)
def _decrypt(self, key, data):
return decrypt(key, data)
def _decrypt_child(self, E_write, E_read):
if E_write and self._writekey:
# we prefer read-write children when we can get them
return self._decrypt(self._writekey, E_write)
else:
return self._decrypt(self._readkey, E_read)
def list(self):
d = self._client.tub.getReference(self.furl)
d.addCallback(lambda node: node.callRemote("list"))
d.addCallback(lambda children:
[(name,child._set_client(self._client))
for name,child in children])
d = self._rref.callRemote("list", self._index)
entries = {}
def _got(res):
dl = []
for (E_name, E_write, E_read) in res:
name = self._decrypt(self._readkey, E_name)
child_uri = self._decrypt_child(E_write, E_read)
d2 = self._create_node(child_uri)
def _created(node, name):
entries[name] = node
d2.addCallback(_created, name)
dl.append(d2)
return defer.DeferredList(dl)
d.addCallback(_got)
d.addCallback(lambda res: entries)
return d
def _hash_name(self, name):
return hashutil.dir_name_hash(self._readkey, name)
def get(self, name):
d = self._client.tub.getReference(self.furl)
d.addCallback(lambda node: node.callRemote("get", name))
d.addCallback(lambda child: child._set_client(self._client))
H_name = self._hash_name(name)
d = self._rref.callRemote("get", self._index, H_name)
def _check_index_error(f):
f.trap(IndexError)
raise IndexError("get(index=%s): unable to find child named '%s'"
% (idlib.b2a(self._index), name))
d.addErrback(_check_index_error)
d.addCallback(lambda (E_write, E_read):
self._decrypt_child(E_write, E_read))
d.addCallback(self._create_node)
return d
def add(self, name, child):
d = self._client.tub.getReference(self.furl)
d.addCallback(lambda node: node.callRemote("add", name, child))
d.addCallback(lambda newnode: newnode._set_client(self._client))
def _set(self, name, write_child, read_child):
if not self._mutable:
return defer.fail(NotMutableError())
H_name = self._hash_name(name)
E_name = self._encrypt(self._readkey, name)
E_write = ""
if self._writekey and write_child:
E_write = self._encrypt(self._writekey, write_child)
E_read = self._encrypt(self._readkey, read_child)
d = self._rref.callRemote("set", self._index, self._write_enabler,
H_name, E_name, E_write, E_read)
return d
def set_uri(self, name, child_uri):
write, read = self._split_uri(child_uri)
return self._set(name, write, read)
def set_node(self, name, child):
d = self.set_uri(name, child.get_uri())
d.addCallback(lambda res: child)
return d
def delete(self, name):
if not self._mutable:
return defer.fail(NotMutableError())
H_name = self._hash_name(name)
d = self._rref.callRemote("delete", self._index, self._write_enabler,
H_name)
return d
def _create_node(self, child_uri):
if uri.is_dirnode_uri(child_uri):
return create_directory_node(self._client, child_uri)
else:
return defer.succeed(FileNode(child_uri, self._client))
def _split_uri(self, child_uri):
if uri.is_dirnode_uri(child_uri):
if uri.is_mutable_dirnode_uri(child_uri):
write = child_uri
read = uri.make_immutable_dirnode_uri(child_uri)
else:
write = None
read = child_uri
return (write, read)
return (None, child_uri) # file
def create_empty_directory(self, name):
if not self._mutable:
return defer.fail(NotMutableError())
child_writekey = hashutil.random_key()
my_furl, parent_writekey = uri.unpack_dirnode_uri(self._uri)
child_uri = uri.pack_dirnode_uri(my_furl, child_writekey)
child = MutableDirectoryNode(child_uri, self._client, self._rref,
child_writekey)
d = self._rref.callRemote("create_directory",
child._index, child._write_enabler)
d.addCallback(lambda index: self.set_node(name, child))
return d
def add_file(self, name, uploadable):
if not self._mutable:
return defer.fail(NotMutableError())
uploader = self._client.getServiceNamed("uploader")
d = uploader.upload(uploadable)
d.addCallback(lambda uri: self.add(name, FileNode(uri, self._client)))
d.addCallback(lambda uri: self.set_node(name,
FileNode(uri, self._client)))
return d
def remove(self, name):
d = self._client.tub.getReference(self.furl)
d.addCallback(lambda node: node.callRemote("remove", name))
d.addCallback(lambda newnode: newnode._set_client(self._client))
return d
def create_empty_directory(self, name):
vdrive_server = self._client._vdrive_server
d = vdrive_server.callRemote("create_directory")
d.addCallback(lambda node: self.add(name, node))
return d
def attach_shared_directory(self, name, furl):
d = self.add(name, DirectoryNode(furl))
return d
def get_shared_directory_furl(self):
return defer.succeed(self.furl)
def move_child_to(self, current_child_name,
new_parent, new_child_name=None):
if not (self._mutable and new_parent.is_mutable()):
return defer.fail(NotMutableError())
if new_child_name is None:
new_child_name = current_child_name
d = self.get(current_child_name)
d.addCallback(lambda child: new_parent.add(new_child_name, child))
d.addCallback(lambda child: self.remove(current_child_name))
d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
d.addCallback(lambda child: self.delete(current_child_name))
return d
class FileNode(Copyable, RemoteCopy):
"""I have a .uri attribute."""
typeToCopy = "allmydata.com/tahoe/interfaces/FileNode/v1"
copytype = typeToCopy
def __init__(self, uri=None, client=None):
# RemoteCopy subclasses are always called without arguments
class MutableDirectoryNode(ImmutableDirectoryNode):
implements(IDirectoryNode)
def __init__(self, myuri, client, rref, writekey):
readkey = hashutil.dir_read_key_hash(writekey)
ImmutableDirectoryNode.__init__(self, myuri, client, rref, readkey)
self._writekey = writekey
self._write_enabler = hashutil.dir_write_enabler_hash(writekey)
self._mutable = True
def create_directory(client, furl):
write_key = hashutil.random_key()
(wk, we, rk, index) = \
hashutil.generate_dirnode_keys_from_writekey(write_key)
myuri = uri.pack_dirnode_uri(furl, wk)
d = client.tub.getReference(furl)
def _got_vdrive_server(vdrive_server):
node = MutableDirectoryNode(myuri, client, vdrive_server, wk)
d2 = vdrive_server.callRemote("create_directory", index, we)
d2.addCallback(lambda res: node)
return d2
d.addCallback(_got_vdrive_server)
return d
class FileNode:
implements(IFileNode)
def __init__(self, uri, client):
self.uri = uri
self._set_client(client)
def _set_client(self, client):
self._client = client
return self
def getStateToCopy(self):
return {"uri": self.uri }
def setCopyableState(self, state):
self.uri = state['uri']
def get_uri(self):
return self.uri
def __hash__(self):
return hash((self.__class__, self.uri))
def __cmp__(self, them):

View File

@ -13,9 +13,11 @@
<div><a href=".">Refresh this view</a></div>
<div><a href="..">Parent Directory</a></div>
<div>To share this directory, paste the following FURL string into an
<div>To share this directory, paste the following URI string into an
"Add Shared Directory" box:
<pre class="overflow" n:render="string" n:data="share_url" /></div>
<pre class="overflow" n:render="string" n:data="share_uri" /></div>
<div>To share a transitively read-only copy, use the following URI instead:
<pre class="overflow" n:render="string" n:data="share_readonly_uri" /></div>
<table n:render="sequence" n:data="children" border="1">
<tr n:pattern="header">

View File

@ -6,8 +6,8 @@ from nevow import inevow, rend, loaders, appserver, url, tags as T
from nevow.static import File as nevow_File # TODO: merge with static.File?
from allmydata.util import idlib
from allmydata.uri import unpack_uri
from allmydata.interfaces import IDownloadTarget
from allmydata.vdrive import FileNode, DirectoryNode
from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
from allmydata.vdrive import FileNode
from allmydata import upload
from zope.interface import implements, Interface
import urllib
@ -120,10 +120,10 @@ class Directory(rend.Page):
dirname = self._dirname + "/" + name
d = self._dirnode.get(name)
def _got_child(res):
if isinstance(res, FileNode):
if IFileNode.providedBy(res):
dl = get_downloader_service(ctx)
return Downloader(dl, name, res)
elif isinstance(res, DirectoryNode):
elif IDirectoryNode.providedBy(res):
return Directory(res, dirname)
else:
raise RuntimeError("what is this %s" % res)
@ -134,18 +134,39 @@ class Directory(rend.Page):
return ctx.tag["Directory of '%s':" % self._dirname]
def render_header(self, ctx, data):
return "Directory of '%s':" % self._dirname
header = "Directory of '%s':" % self._dirname
if not self._dirnode.is_mutable():
header += " (readonly)"
return header
def data_share_url(self, ctx, data):
return self._dirnode.furl
def data_share_uri(self, ctx, data):
return self._dirnode.get_uri()
def data_share_readonly_uri(self, ctx, data):
return self._dirnode.get_immutable_uri()
def data_children(self, ctx, data):
d = self._dirnode.list()
d.addCallback(lambda dict: sorted(dict.items()))
return d
def render_row(self, ctx, data):
name, target = data
if isinstance(target, FileNode):
if self._dirnode.is_mutable():
# this creates a button which will cause our child__delete method
# to be invoked, which deletes the file and then redirects the
# browser back to this directory
del_url = url.here.child("_delete")
#del_url = del_url.add("uri", target.uri)
del_url = del_url.add("name", name)
delete = T.form(action=del_url, method="post")[
T.input(type='submit', value='del', name="del"),
]
else:
delete = "-"
ctx.fillSlots("delete", delete)
if IFileNode.providedBy(target):
# file
dlurl = urllib.quote(name)
ctx.fillSlots("filename",
@ -160,17 +181,7 @@ class Directory(rend.Page):
#extract and display file size
ctx.fillSlots("size", unpack_uri(uri)['size'])
# this creates a button which will cause our child__delete method
# to be invoked, which deletes the file and then redirects the
# browser back to this directory
del_url = url.here.child("_delete")
#del_url = del_url.add("uri", target.uri)
del_url = del_url.add("name", name)
delete = T.form(action=del_url, method="post")[
T.input(type='submit', value='del', name="del"),
]
ctx.fillSlots("delete", delete)
elif isinstance(target, DirectoryNode):
elif IDirectoryNode.providedBy(target):
# directory
subdir_url = urllib.quote(name)
ctx.fillSlots("filename",
@ -178,19 +189,14 @@ class Directory(rend.Page):
ctx.fillSlots("type", "DIR")
ctx.fillSlots("size", "-")
ctx.fillSlots("uri", "-")
del_url = url.here.child("_delete")
del_url = del_url.add("name", name)
delete = T.form(action=del_url, method="post")[
T.input(type='submit', value='del', name="del"),
]
ctx.fillSlots("delete", delete)
else:
raise RuntimeError("unknown thing %s" % (target,))
return ctx.tag
def render_forms(self, ctx, data):
return webform.renderForms()
if self._dirnode.is_mutable():
return webform.renderForms()
return T.div["No upload forms: directory is immutable"]
def render_results(self, ctx, data):
req = inevow.IRequest(ctx)
@ -235,14 +241,13 @@ class Directory(rend.Page):
log.msg("starting webish upload")
uploader = get_uploader_service(ctx)
d = uploader.upload(upload.FileHandle(contents.file))
uploadable = upload.FileHandle(contents.file)
name = contents.filename
def _uploaded(uri):
if privateupload:
return self.uploadprivate(name, uri)
else:
return self._dirnode.add(name, FileNode(uri))
d.addCallback(_uploaded)
if privateupload:
d = uploader.upload(uploadable)
d.addCallback(lambda uri: self.uploadprivate(name, uri))
else:
d = self._dirnode.add_file(name, uploadable)
def _done(res):
log.msg("webish upload complete")
return res
@ -271,15 +276,15 @@ class Directory(rend.Page):
def bind_mount(self, ctx):
namearg = annotate.Argument("name",
annotate.String("Name to place incoming directory: "))
furlarg = annotate.Argument("furl",
annotate.String("FURL of Shared Directory"))
meth = annotate.Method(arguments=[namearg, furlarg],
uriarg = annotate.Argument("uri",
annotate.String("URI of Shared Directory"))
meth = annotate.Method(arguments=[namearg, uriarg],
label="Add Shared Directory")
return annotate.MethodBinding("mount", meth,
action="Mount Shared Directory")
def mount(self, name, furl):
d = self._dirnode.attach_shared_directory(name, furl)
def mount(self, name, uri):
d = self._dirnode.set_uri(name, uri)
#d.addCallback(lambda done: url.here.child(name))
return d
@ -287,7 +292,7 @@ class Directory(rend.Page):
# perform the delete, then redirect back to the directory page
args = inevow.IRequest(ctx).args
name = args["name"][0]
d = self._dirnode.remove(name)
d = self._dirnode.delete(name)
d.addCallback(lambda done: url.here.up())
return d
@ -324,7 +329,7 @@ class Downloader(resource.Resource):
def __init__(self, downloader, name, filenode):
self._downloader = downloader
self._name = name
assert isinstance(filenode, FileNode)
IFileNode(filenode)
self._filenode = filenode
def render(self, ctx):
@ -397,14 +402,14 @@ class WebishServer(service.MultiService):
# apparently 'ISite' does not exist
#self.site._client = self.parent
def set_vdrive_root(self, root):
def set_vdrive_rootnode(self, root):
self.root.putChild("global_vdrive", Directory(root, "/"))
self.root.child_welcome.has_global_vdrive = True
# I tried doing it this way and for some reason it didn't seem to work
#print "REMEMBERING", self.site, dl, IDownloader
#self.site.remember(dl, IDownloader)
def set_my_vdrive_root(self, my_vdrive):
def set_my_vdrive_rootnode(self, my_vdrive):
self.root.putChild("my_vdrive", Directory(my_vdrive, "~"))
self.root.child_welcome.has_my_vdrive = True