mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-06-18 23:38:18 +00:00
vdrive: switch to URI:DIR and URI:DIR-RO, providing transitive readonlyness
This commit is contained in:
@ -2,8 +2,8 @@
|
|||||||
import os, sha, stat, time
|
import os, sha, stat, time
|
||||||
from foolscap import Referenceable, SturdyRef
|
from foolscap import Referenceable, SturdyRef
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from allmydata.interfaces import RIClient
|
from allmydata.interfaces import RIClient, IDirectoryNode
|
||||||
from allmydata import node
|
from allmydata import node, vdrive, uri
|
||||||
|
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.application.internet import TimerService
|
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.storageserver import StorageServer
|
||||||
from allmydata.upload import Uploader
|
from allmydata.upload import Uploader
|
||||||
from allmydata.download import Downloader
|
from allmydata.download import Downloader
|
||||||
from allmydata.vdrive import DirectoryNode
|
|
||||||
from allmydata.webish import WebishServer
|
from allmydata.webish import WebishServer
|
||||||
from allmydata.control import ControlServer
|
from allmydata.control import ControlServer
|
||||||
from allmydata.introducer import IntroducerClient
|
from allmydata.introducer import IntroducerClient
|
||||||
@ -28,7 +27,7 @@ class Client(node.Node, Referenceable):
|
|||||||
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
|
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
|
||||||
MY_FURL_FILE = "myself.furl"
|
MY_FURL_FILE = "myself.furl"
|
||||||
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
|
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
|
# we're pretty narrow-minded right now
|
||||||
OLDEST_SUPPORTED_VERSION = allmydata.__version__
|
OLDEST_SUPPORTED_VERSION = allmydata.__version__
|
||||||
@ -115,16 +114,20 @@ class Client(node.Node, Referenceable):
|
|||||||
def _got_vdrive(self, vdrive_server):
|
def _got_vdrive(self, vdrive_server):
|
||||||
# vdrive_server implements RIVirtualDriveServer
|
# vdrive_server implements RIVirtualDriveServer
|
||||||
self.log("connected to vdrive server")
|
self.log("connected to vdrive server")
|
||||||
d = vdrive_server.callRemote("get_public_root_furl")
|
d = vdrive_server.callRemote("get_public_root_uri")
|
||||||
d.addCallback(self._got_vdrive_root_furl, vdrive_server)
|
d.addCallback(self._got_vdrive_uri)
|
||||||
d.addCallback(self._create_my_vdrive)
|
d.addCallback(self._got_vdrive_rootnode)
|
||||||
|
d.addCallback(self._create_my_vdrive, vdrive_server)
|
||||||
d.addCallback(self._got_my_vdrive)
|
d.addCallback(self._got_my_vdrive)
|
||||||
|
|
||||||
def _got_vdrive_root_furl(self, vdrive_root_furl, vdrive_server):
|
def _got_vdrive_uri(self, root_uri):
|
||||||
root = DirectoryNode(vdrive_root_furl, self)
|
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.log("got vdrive root")
|
||||||
self._vdrive_server = vdrive_server
|
self._vdrive_root = rootnode
|
||||||
self._vdrive_root = root
|
|
||||||
self._connected_to_vdrive = True
|
self._connected_to_vdrive = True
|
||||||
|
|
||||||
#vdrive = self.getServiceNamed("vdrive")
|
#vdrive = self.getServiceNamed("vdrive")
|
||||||
@ -133,34 +136,34 @@ class Client(node.Node, Referenceable):
|
|||||||
|
|
||||||
if "webish" in self.namedServices:
|
if "webish" in self.namedServices:
|
||||||
webish = self.getServiceNamed("webish")
|
webish = self.getServiceNamed("webish")
|
||||||
webish.set_vdrive_root(root)
|
webish.set_vdrive_rootnode(rootnode)
|
||||||
|
|
||||||
def _create_my_vdrive(self, ignored=None):
|
def _create_my_vdrive(self, ignored, vdrive_server):
|
||||||
MY_VDRIVE_FURL_FILE = os.path.join(self.basedir,
|
MY_VDRIVE_URI_FILE = os.path.join(self.basedir,
|
||||||
self.MY_VDRIVE_FURL_FILE)
|
self.MY_VDRIVE_URI_FILE)
|
||||||
try:
|
try:
|
||||||
f = open(MY_VDRIVE_FURL_FILE, "r")
|
f = open(MY_VDRIVE_URI_FILE, "r")
|
||||||
my_vdrive_furl = f.read().strip()
|
my_vdrive_uri = f.read().strip()
|
||||||
f.close()
|
f.close()
|
||||||
return defer.succeed(DirectoryNode(my_vdrive_furl, self))
|
return vdrive.create_directory_node(self, my_vdrive_uri)
|
||||||
except EnvironmentError:
|
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):
|
def _got_directory(dirnode):
|
||||||
f = open(MY_VDRIVE_FURL_FILE, "w")
|
f = open(MY_VDRIVE_URI_FILE, "w")
|
||||||
f.write(dirnode.furl + "\n")
|
f.write(dirnode.get_uri() + "\n")
|
||||||
f.close()
|
f.close()
|
||||||
dirnode._set_client(self)
|
|
||||||
return dirnode
|
return dirnode
|
||||||
d.addCallback(_got_directory)
|
d.addCallback(_got_directory)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _got_my_vdrive(self, my_vdrive):
|
def _got_my_vdrive(self, my_vdrive):
|
||||||
assert isinstance(my_vdrive, DirectoryNode), my_vdrive
|
IDirectoryNode(my_vdrive)
|
||||||
self._my_vdrive = my_vdrive
|
self._my_vdrive = my_vdrive
|
||||||
|
|
||||||
if "webish" in self.namedServices:
|
if "webish" in self.namedServices:
|
||||||
webish = self.getServiceNamed("webish")
|
webish = self.getServiceNamed("webish")
|
||||||
webish.set_my_vdrive_root(my_vdrive)
|
webish.set_my_vdrive_rootnode(my_vdrive)
|
||||||
|
|
||||||
|
|
||||||
def remote_get_versions(self):
|
def remote_get_versions(self):
|
||||||
|
@ -1,165 +1,110 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from zope.interface import implements
|
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.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):
|
class BadWriteEnablerError(Exception):
|
||||||
"""Bad filename component"""
|
|
||||||
|
|
||||||
class BadFileError(Exception):
|
|
||||||
pass
|
pass
|
||||||
|
class ChildAlreadyPresentError(Exception):
|
||||||
class BadDirectoryError(Exception):
|
|
||||||
pass
|
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):
|
class NoPublicRootError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class VirtualDriveServer(service.MultiService, Referenceable):
|
class VirtualDriveServer(service.MultiService, Referenceable):
|
||||||
implements(RIVirtualDriveServer)
|
implements(RIVirtualDriveServer)
|
||||||
name = "filetable"
|
name = "filetable"
|
||||||
VDRIVEDIR = "vdrive"
|
|
||||||
|
|
||||||
def __init__(self, basedir=".", offer_public_root=True):
|
def __init__(self, basedir, offer_public_root=True):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
vdrive_dir = os.path.join(basedir, self.VDRIVEDIR)
|
self._basedir = os.path.abspath(basedir)
|
||||||
if not os.path.exists(vdrive_dir):
|
fileutil.make_dirs(self._basedir)
|
||||||
os.mkdir(vdrive_dir)
|
|
||||||
self._vdrive_dir = vdrive_dir
|
|
||||||
self._root = None
|
self._root = None
|
||||||
if offer_public_root:
|
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):
|
def set_furl(self, myfurl):
|
||||||
service.MultiService.startService(self)
|
self._myfurl = myfurl
|
||||||
# _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 _register_all_dirnodes(self, tub):
|
def get_public_root_uri(self):
|
||||||
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):
|
|
||||||
if self._root:
|
if self._root:
|
||||||
return self._root_furl
|
return uri.pack_dirnode_uri(self._myfurl, self._root)
|
||||||
raise NoPublicRootError
|
raise NoPublicRootError
|
||||||
remote_get_public_root_furl = get_public_root_furl
|
remote_get_public_root_uri = get_public_root_uri
|
||||||
|
|
||||||
def create_directory(self):
|
def create_directory(self, index, write_enabler):
|
||||||
node = MutableDirectoryNode(self._vdrive_dir)
|
data = [write_enabler, []]
|
||||||
furl = self.parent.tub.registerReference(node, node._name)
|
self._write_to_file(index, data)
|
||||||
return DirectoryNode(furl)
|
return index
|
||||||
remote_create_directory = create_directory
|
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
|
||||||
|
@ -126,33 +126,79 @@ RIMutableDirectoryNode_ = Any() # TODO: how can we avoid this?
|
|||||||
FileNode_ = Any() # TODO: foolscap needs constraints on copyables
|
FileNode_ = Any() # TODO: foolscap needs constraints on copyables
|
||||||
DirectoryNode_ = Any() # TODO: same
|
DirectoryNode_ = Any() # TODO: same
|
||||||
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
|
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
|
||||||
|
EncryptedThing = str
|
||||||
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_
|
|
||||||
|
|
||||||
|
|
||||||
class RIVirtualDriveServer(RemoteInterface):
|
class RIVirtualDriveServer(RemoteInterface):
|
||||||
def get_public_root_furl():
|
def get_public_root_uri():
|
||||||
"""If this vdrive server does not offer a public root, this will
|
"""Obtain the URI for this server's global publically-writable root
|
||||||
raise an exception."""
|
directory. This returns a read-write directory URI.
|
||||||
return FURL
|
|
||||||
|
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):
|
class IFileNode(Interface):
|
||||||
def download(target):
|
def download(target):
|
||||||
@ -161,23 +207,66 @@ class IFileNode(Interface):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class IDirectoryNode(Interface):
|
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():
|
def list():
|
||||||
"""I return a Deferred that fires with a"""
|
"""I return a Deferred that fires with a dictionary mapping child
|
||||||
pass
|
name to an IFileNode or IDirectoryNode."""
|
||||||
|
|
||||||
def get(name):
|
def get(name):
|
||||||
"""I return a Deferred that fires with a specific named child."""
|
"""I return a Deferred that fires with a specific named child node,
|
||||||
pass
|
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
|
"""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):
|
def add_file(name, uploadable):
|
||||||
"""I upload a file (using the given IUploadable), then attach the
|
"""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
|
"""I remove the child at the specific name. I return a Deferred that
|
||||||
fires when the operation finishes."""
|
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
|
"""I create and attach an empty directory at the given name. I return
|
||||||
a Deferred that fires when the operation finishes."""
|
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):
|
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
|
"""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
|
is referenced by name. On the new parent, the child will live under
|
||||||
|
@ -8,6 +8,7 @@ from allmydata.introducer import Introducer
|
|||||||
class IntroducerAndVdrive(node.Node):
|
class IntroducerAndVdrive(node.Node):
|
||||||
PORTNUMFILE = "introducer.port"
|
PORTNUMFILE = "introducer.port"
|
||||||
NODETYPE = "introducer"
|
NODETYPE = "introducer"
|
||||||
|
VDRIVEDIR = "vdrive"
|
||||||
|
|
||||||
def __init__(self, basedir="."):
|
def __init__(self, basedir="."):
|
||||||
node.Node.__init__(self, basedir)
|
node.Node.__init__(self, basedir)
|
||||||
@ -21,8 +22,11 @@ class IntroducerAndVdrive(node.Node):
|
|||||||
f.write(self.urls["introducer"] + "\n")
|
f.write(self.urls["introducer"] + "\n")
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
vds = self.add_service(VirtualDriveServer(self.basedir))
|
vdrive_dir = os.path.join(self.basedir, self.VDRIVEDIR)
|
||||||
self.urls["vdrive"] = self.tub.registerReference(vds, "vdrive")
|
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"])
|
self.log(" vdrive is at %s" % self.urls["vdrive"])
|
||||||
f = open(os.path.join(self.basedir, "vdrive.furl"), "w")
|
f = open(os.path.join(self.basedir, "vdrive.furl"), "w")
|
||||||
f.write(self.urls["vdrive"] + "\n")
|
f.write(self.urls["vdrive"] + "\n")
|
||||||
|
@ -129,6 +129,30 @@ class DumpOptions(usage.Options):
|
|||||||
if not self['filename']:
|
if not self['filename']:
|
||||||
raise usage.UsageError("<filename> parameter is required")
|
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 = """
|
client_tac = """
|
||||||
# -*- python -*-
|
# -*- python -*-
|
||||||
|
|
||||||
@ -164,7 +188,9 @@ class Options(usage.Options):
|
|||||||
["restart", None, RestartOptions, "Restart a node."],
|
["restart", None, RestartOptions, "Restart a node."],
|
||||||
["dump-uri-extension", None, DumpOptions,
|
["dump-uri-extension", None, DumpOptions,
|
||||||
"Unpack and display the contents of a uri_extension file."],
|
"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."],
|
"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
|
rc = start(basedir, so) or rc
|
||||||
elif command == "dump-uri-extension":
|
elif command == "dump-uri-extension":
|
||||||
rc = dump_uri_extension(so)
|
rc = dump_uri_extension(so)
|
||||||
elif command == "dump-directory-node":
|
elif command == "dump-root-dirnode":
|
||||||
rc = dump_directory_node(so)
|
rc = dump_root_dirnode(so.basedirs[0], so)
|
||||||
|
elif command == "dump-dirnode":
|
||||||
|
rc = dump_directory_node(so.basedirs[0], so)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
@ -329,30 +357,71 @@ def dump_uri_extension(config):
|
|||||||
print
|
print
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def dump_directory_node(config):
|
def dump_root_dirnode(basedir, config):
|
||||||
from allmydata import filetable, vdrive
|
from allmydata import uri
|
||||||
filename = config['filename']
|
|
||||||
|
|
||||||
basedir, name = os.path.split(filename)
|
root_dirnode_file = os.path.join(basedir, "vdrive", "root")
|
||||||
dirnode = filetable.MutableDirectoryNode(basedir, name)
|
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
|
||||||
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
|
print
|
||||||
|
|
||||||
children = dirnode._read_from_file()
|
vds = filetable.VirtualDriveServer(os.path.join(basedir, "vdrive"), False)
|
||||||
names = sorted(children.keys())
|
data = vds._read_from_file(index)
|
||||||
for name in names:
|
if we:
|
||||||
v = children[name]
|
if we != data[0]:
|
||||||
if isinstance(v, vdrive.FileNode):
|
print "ERROR: write_enabler does not match"
|
||||||
value = "File (uri=%s...)" % v.uri[:40]
|
|
||||||
elif isinstance(v, vdrive.DirectoryNode):
|
for (H_key, E_key, E_write, E_read) in data[1]:
|
||||||
lastslash = v.furl.rindex("/")
|
if verbose:
|
||||||
furlname = v.furl[lastslash+1:lastslash+1+15]
|
print " H_key %s" % idlib.b2a(H_key)
|
||||||
value = "Directory (furl=%s.../%s...)" % (v.furl[:15], furlname)
|
print " E_key %s" % idlib.b2a(E_key)
|
||||||
else:
|
print " E_write %s" % idlib.b2a(E_write)
|
||||||
value = "weird: %s" % (v,)
|
print " E_read %s" % idlib.b2a(E_read)
|
||||||
print "%20s: %s" % (name, value)
|
key = vdrive.decrypt(rk, E_key)
|
||||||
print
|
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
|
return 0
|
||||||
|
|
||||||
|
@ -1,54 +1,86 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from allmydata.filetable import (MutableDirectoryNode,
|
from allmydata import filetable, uri
|
||||||
BadFileError, BadNameError)
|
from allmydata.util import hashutil
|
||||||
from allmydata.vdrive import FileNode, DirectoryNode
|
|
||||||
|
|
||||||
|
|
||||||
class FileTable(unittest.TestCase):
|
class FileTable(unittest.TestCase):
|
||||||
def test_files(self):
|
def test_vdrive_server(self):
|
||||||
os.mkdir("filetable")
|
basedir = "filetable/FileTable/test_vdrive_server"
|
||||||
basedir = os.path.abspath("filetable")
|
vds = filetable.VirtualDriveServer(basedir)
|
||||||
root = MutableDirectoryNode(basedir, "root")
|
vds.set_furl("myFURL")
|
||||||
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")
|
|
||||||
|
|
||||||
self.failUnlessEqual(root.get("one"), FileNode("vid-one"))
|
root_uri = vds.get_public_root_uri()
|
||||||
self.failUnlessRaises(BadFileError, root.get, "missing")
|
self.failUnless(uri.is_dirnode_uri(root_uri))
|
||||||
self.failUnlessRaises(BadNameError, root.get, "/etc/passwd") # evil
|
self.failUnless(uri.is_mutable_dirnode_uri(root_uri))
|
||||||
self.failUnlessRaises(BadNameError, root.get, "..") # sneaky
|
furl, key = uri.unpack_dirnode_uri(root_uri)
|
||||||
self.failUnlessRaises(BadNameError, root.get, ".") # dumb
|
self.failUnlessEqual(furl, "myFURL")
|
||||||
|
self.failUnlessEqual(len(key), hashutil.KEYLEN)
|
||||||
|
|
||||||
# now play with directories
|
wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
|
||||||
subdir1 = root.add("subdir1", DirectoryNode("subdir1.furl"))
|
empty_list = vds.list(index)
|
||||||
self.failUnless(isinstance(subdir1, DirectoryNode))
|
self.failUnlessEqual(empty_list, [])
|
||||||
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))
|
|
||||||
|
|
||||||
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
|
self.failUnlessRaises(filetable.ChildAlreadyPresentError,
|
||||||
root.add,
|
vds.set,
|
||||||
"subdir1", DirectoryNode("subdir1.furl"))
|
index, we, "key2", "name2", "write2", "read2")
|
||||||
|
|
||||||
root.remove("subdir1")
|
self.failUnlessRaises(filetable.BadWriteEnablerError,
|
||||||
self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))])
|
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)
|
||||||
|
@ -240,7 +240,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
|
|||||||
d1.addCallback(lambda subdir1_node:
|
d1.addCallback(lambda subdir1_node:
|
||||||
subdir1_node.add_file("mydata567", ut))
|
subdir1_node.add_file("mydata567", ut))
|
||||||
def _stash_uri(filenode):
|
def _stash_uri(filenode):
|
||||||
self.uri = filenode.uri
|
self.uri = filenode.get_uri()
|
||||||
return filenode
|
return filenode
|
||||||
d1.addCallback(_stash_uri)
|
d1.addCallback(_stash_uri)
|
||||||
return d1
|
return d1
|
||||||
|
@ -1,17 +1,262 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet import defer
|
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 callRemote(self, methname, *args, **kwargs):
|
||||||
def _call():
|
def _call(ignored):
|
||||||
meth = getattr(self, methname)
|
meth = getattr(self.target, methname)
|
||||||
return meth(*args, **kwargs)
|
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):
|
class Traverse(unittest.TestCase):
|
||||||
def make_tree(self, basedir):
|
def make_tree(self, basedir):
|
||||||
os.makedirs(basedir)
|
os.makedirs(basedir)
|
||||||
@ -67,3 +312,4 @@ class Traverse(unittest.TestCase):
|
|||||||
["2.a", "2.b", "d2.1"]))
|
["2.a", "2.b", "d2.1"]))
|
||||||
return d
|
return d
|
||||||
del Traverse
|
del Traverse
|
||||||
|
"""
|
||||||
|
@ -86,3 +86,31 @@ def unpack_extension_readable(data):
|
|||||||
if "hash" in k:
|
if "hash" in k:
|
||||||
unpacked[k] = idlib.b2a(unpacked[k])
|
unpacked[k] = idlib.b2a(unpacked[k])
|
||||||
return unpacked
|
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))
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from allmydata.Crypto.Hash import SHA256
|
from allmydata.Crypto.Hash import SHA256
|
||||||
|
import os
|
||||||
|
|
||||||
def netstring(s):
|
def netstring(s):
|
||||||
return "%d:%s," % (len(s), s,)
|
return "%d:%s," % (len(s), s,)
|
||||||
@ -56,3 +57,25 @@ def key_hash(data):
|
|||||||
def key_hasher():
|
def key_hasher():
|
||||||
return tagged_hasher("allmydata_encryption_key_v1")
|
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
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
|
|
||||||
"""This is the client-side facility to manipulate virtual drives."""
|
"""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.application import service
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.python import log
|
from twisted.python import log
|
||||||
from allmydata import upload, download
|
from allmydata import upload, download, uri
|
||||||
from foolscap import Copyable, RemoteCopy
|
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):
|
class VDrive(service.MultiService):
|
||||||
name = "vdrive"
|
name = "vdrive"
|
||||||
@ -181,99 +188,243 @@ class VDrive(service.MultiService):
|
|||||||
return self.get_file(from_where, download.FileHandle(filehandle))
|
return self.get_file(from_where, download.FileHandle(filehandle))
|
||||||
|
|
||||||
|
|
||||||
class DirectoryNode(Copyable, RemoteCopy):
|
def create_directory_node(client, diruri):
|
||||||
"""I have either a .furl attribute or a .get(tub) method."""
|
assert uri.is_dirnode_uri(diruri)
|
||||||
typeToCopy = "allmydata.com/tahoe/interfaces/DirectoryNode/v1"
|
if uri.is_mutable_dirnode_uri(diruri):
|
||||||
copytype = typeToCopy
|
dirnode_class = MutableDirectoryNode
|
||||||
def __init__(self, furl=None, client=None):
|
else:
|
||||||
# RemoteCopy subclasses are always called without arguments
|
dirnode_class = ImmutableDirectoryNode
|
||||||
self.furl = furl
|
(furl, key) = uri.unpack_dirnode_uri(diruri)
|
||||||
self._set_client(client)
|
d = client.tub.getReference(furl)
|
||||||
def _set_client(self, client):
|
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
|
self._client = client
|
||||||
return self
|
self._tub = client.tub
|
||||||
def getStateToCopy(self):
|
self._rref = rref
|
||||||
return {"furl": self.furl }
|
self._readkey = readkey
|
||||||
def setCopyableState(self, state):
|
self._writekey = None
|
||||||
self.furl = state['furl']
|
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):
|
def __hash__(self):
|
||||||
return hash((self.__class__, self.furl))
|
return hash((self.__class__, self._uri))
|
||||||
def __cmp__(self, them):
|
def __cmp__(self, them):
|
||||||
if cmp(type(self), type(them)):
|
if cmp(type(self), type(them)):
|
||||||
return cmp(type(self), type(them))
|
return cmp(type(self), type(them))
|
||||||
if cmp(self.__class__, them.__class__):
|
if cmp(self.__class__, them.__class__):
|
||||||
return 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):
|
def list(self):
|
||||||
d = self._client.tub.getReference(self.furl)
|
d = self._rref.callRemote("list", self._index)
|
||||||
d.addCallback(lambda node: node.callRemote("list"))
|
entries = {}
|
||||||
d.addCallback(lambda children:
|
def _got(res):
|
||||||
[(name,child._set_client(self._client))
|
dl = []
|
||||||
for name,child in children])
|
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
|
return d
|
||||||
|
|
||||||
|
def _hash_name(self, name):
|
||||||
|
return hashutil.dir_name_hash(self._readkey, name)
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
d = self._client.tub.getReference(self.furl)
|
H_name = self._hash_name(name)
|
||||||
d.addCallback(lambda node: node.callRemote("get", name))
|
d = self._rref.callRemote("get", self._index, H_name)
|
||||||
d.addCallback(lambda child: child._set_client(self._client))
|
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
|
return d
|
||||||
|
|
||||||
def add(self, name, child):
|
def _set(self, name, write_child, read_child):
|
||||||
d = self._client.tub.getReference(self.furl)
|
if not self._mutable:
|
||||||
d.addCallback(lambda node: node.callRemote("add", name, child))
|
return defer.fail(NotMutableError())
|
||||||
d.addCallback(lambda newnode: newnode._set_client(self._client))
|
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
|
return d
|
||||||
|
|
||||||
def add_file(self, name, uploadable):
|
def add_file(self, name, uploadable):
|
||||||
|
if not self._mutable:
|
||||||
|
return defer.fail(NotMutableError())
|
||||||
uploader = self._client.getServiceNamed("uploader")
|
uploader = self._client.getServiceNamed("uploader")
|
||||||
d = uploader.upload(uploadable)
|
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
|
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,
|
def move_child_to(self, current_child_name,
|
||||||
new_parent, new_child_name=None):
|
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:
|
if new_child_name is None:
|
||||||
new_child_name = current_child_name
|
new_child_name = current_child_name
|
||||||
d = self.get(current_child_name)
|
d = self.get(current_child_name)
|
||||||
d.addCallback(lambda child: new_parent.add(new_child_name, child))
|
d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
|
||||||
d.addCallback(lambda child: self.remove(current_child_name))
|
d.addCallback(lambda child: self.delete(current_child_name))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
class FileNode(Copyable, RemoteCopy):
|
class MutableDirectoryNode(ImmutableDirectoryNode):
|
||||||
"""I have a .uri attribute."""
|
implements(IDirectoryNode)
|
||||||
typeToCopy = "allmydata.com/tahoe/interfaces/FileNode/v1"
|
|
||||||
copytype = typeToCopy
|
def __init__(self, myuri, client, rref, writekey):
|
||||||
def __init__(self, uri=None, client=None):
|
readkey = hashutil.dir_read_key_hash(writekey)
|
||||||
# RemoteCopy subclasses are always called without arguments
|
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.uri = uri
|
||||||
self._set_client(client)
|
|
||||||
def _set_client(self, client):
|
|
||||||
self._client = client
|
self._client = client
|
||||||
return self
|
|
||||||
def getStateToCopy(self):
|
def get_uri(self):
|
||||||
return {"uri": self.uri }
|
return self.uri
|
||||||
def setCopyableState(self, state):
|
|
||||||
self.uri = state['uri']
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.__class__, self.uri))
|
return hash((self.__class__, self.uri))
|
||||||
def __cmp__(self, them):
|
def __cmp__(self, them):
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
|
|
||||||
<div><a href=".">Refresh this view</a></div>
|
<div><a href=".">Refresh this view</a></div>
|
||||||
<div><a href="..">Parent Directory</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:
|
"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">
|
<table n:render="sequence" n:data="children" border="1">
|
||||||
<tr n:pattern="header">
|
<tr n:pattern="header">
|
||||||
|
@ -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 nevow.static import File as nevow_File # TODO: merge with static.File?
|
||||||
from allmydata.util import idlib
|
from allmydata.util import idlib
|
||||||
from allmydata.uri import unpack_uri
|
from allmydata.uri import unpack_uri
|
||||||
from allmydata.interfaces import IDownloadTarget
|
from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
|
||||||
from allmydata.vdrive import FileNode, DirectoryNode
|
from allmydata.vdrive import FileNode
|
||||||
from allmydata import upload
|
from allmydata import upload
|
||||||
from zope.interface import implements, Interface
|
from zope.interface import implements, Interface
|
||||||
import urllib
|
import urllib
|
||||||
@ -120,10 +120,10 @@ class Directory(rend.Page):
|
|||||||
dirname = self._dirname + "/" + name
|
dirname = self._dirname + "/" + name
|
||||||
d = self._dirnode.get(name)
|
d = self._dirnode.get(name)
|
||||||
def _got_child(res):
|
def _got_child(res):
|
||||||
if isinstance(res, FileNode):
|
if IFileNode.providedBy(res):
|
||||||
dl = get_downloader_service(ctx)
|
dl = get_downloader_service(ctx)
|
||||||
return Downloader(dl, name, res)
|
return Downloader(dl, name, res)
|
||||||
elif isinstance(res, DirectoryNode):
|
elif IDirectoryNode.providedBy(res):
|
||||||
return Directory(res, dirname)
|
return Directory(res, dirname)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("what is this %s" % res)
|
raise RuntimeError("what is this %s" % res)
|
||||||
@ -134,18 +134,39 @@ class Directory(rend.Page):
|
|||||||
return ctx.tag["Directory of '%s':" % self._dirname]
|
return ctx.tag["Directory of '%s':" % self._dirname]
|
||||||
|
|
||||||
def render_header(self, ctx, data):
|
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):
|
def data_share_uri(self, ctx, data):
|
||||||
return self._dirnode.furl
|
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):
|
def data_children(self, ctx, data):
|
||||||
d = self._dirnode.list()
|
d = self._dirnode.list()
|
||||||
|
d.addCallback(lambda dict: sorted(dict.items()))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def render_row(self, ctx, data):
|
def render_row(self, ctx, data):
|
||||||
name, target = 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
|
# file
|
||||||
dlurl = urllib.quote(name)
|
dlurl = urllib.quote(name)
|
||||||
ctx.fillSlots("filename",
|
ctx.fillSlots("filename",
|
||||||
@ -160,17 +181,7 @@ class Directory(rend.Page):
|
|||||||
#extract and display file size
|
#extract and display file size
|
||||||
ctx.fillSlots("size", unpack_uri(uri)['size'])
|
ctx.fillSlots("size", unpack_uri(uri)['size'])
|
||||||
|
|
||||||
# this creates a button which will cause our child__delete method
|
elif IDirectoryNode.providedBy(target):
|
||||||
# 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):
|
|
||||||
# directory
|
# directory
|
||||||
subdir_url = urllib.quote(name)
|
subdir_url = urllib.quote(name)
|
||||||
ctx.fillSlots("filename",
|
ctx.fillSlots("filename",
|
||||||
@ -178,19 +189,14 @@ class Directory(rend.Page):
|
|||||||
ctx.fillSlots("type", "DIR")
|
ctx.fillSlots("type", "DIR")
|
||||||
ctx.fillSlots("size", "-")
|
ctx.fillSlots("size", "-")
|
||||||
ctx.fillSlots("uri", "-")
|
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:
|
else:
|
||||||
raise RuntimeError("unknown thing %s" % (target,))
|
raise RuntimeError("unknown thing %s" % (target,))
|
||||||
return ctx.tag
|
return ctx.tag
|
||||||
|
|
||||||
def render_forms(self, ctx, data):
|
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):
|
def render_results(self, ctx, data):
|
||||||
req = inevow.IRequest(ctx)
|
req = inevow.IRequest(ctx)
|
||||||
@ -235,14 +241,13 @@ class Directory(rend.Page):
|
|||||||
log.msg("starting webish upload")
|
log.msg("starting webish upload")
|
||||||
|
|
||||||
uploader = get_uploader_service(ctx)
|
uploader = get_uploader_service(ctx)
|
||||||
d = uploader.upload(upload.FileHandle(contents.file))
|
uploadable = upload.FileHandle(contents.file)
|
||||||
name = contents.filename
|
name = contents.filename
|
||||||
def _uploaded(uri):
|
if privateupload:
|
||||||
if privateupload:
|
d = uploader.upload(uploadable)
|
||||||
return self.uploadprivate(name, uri)
|
d.addCallback(lambda uri: self.uploadprivate(name, uri))
|
||||||
else:
|
else:
|
||||||
return self._dirnode.add(name, FileNode(uri))
|
d = self._dirnode.add_file(name, uploadable)
|
||||||
d.addCallback(_uploaded)
|
|
||||||
def _done(res):
|
def _done(res):
|
||||||
log.msg("webish upload complete")
|
log.msg("webish upload complete")
|
||||||
return res
|
return res
|
||||||
@ -271,15 +276,15 @@ class Directory(rend.Page):
|
|||||||
def bind_mount(self, ctx):
|
def bind_mount(self, ctx):
|
||||||
namearg = annotate.Argument("name",
|
namearg = annotate.Argument("name",
|
||||||
annotate.String("Name to place incoming directory: "))
|
annotate.String("Name to place incoming directory: "))
|
||||||
furlarg = annotate.Argument("furl",
|
uriarg = annotate.Argument("uri",
|
||||||
annotate.String("FURL of Shared Directory"))
|
annotate.String("URI of Shared Directory"))
|
||||||
meth = annotate.Method(arguments=[namearg, furlarg],
|
meth = annotate.Method(arguments=[namearg, uriarg],
|
||||||
label="Add Shared Directory")
|
label="Add Shared Directory")
|
||||||
return annotate.MethodBinding("mount", meth,
|
return annotate.MethodBinding("mount", meth,
|
||||||
action="Mount Shared Directory")
|
action="Mount Shared Directory")
|
||||||
|
|
||||||
def mount(self, name, furl):
|
def mount(self, name, uri):
|
||||||
d = self._dirnode.attach_shared_directory(name, furl)
|
d = self._dirnode.set_uri(name, uri)
|
||||||
#d.addCallback(lambda done: url.here.child(name))
|
#d.addCallback(lambda done: url.here.child(name))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -287,7 +292,7 @@ class Directory(rend.Page):
|
|||||||
# perform the delete, then redirect back to the directory page
|
# perform the delete, then redirect back to the directory page
|
||||||
args = inevow.IRequest(ctx).args
|
args = inevow.IRequest(ctx).args
|
||||||
name = args["name"][0]
|
name = args["name"][0]
|
||||||
d = self._dirnode.remove(name)
|
d = self._dirnode.delete(name)
|
||||||
d.addCallback(lambda done: url.here.up())
|
d.addCallback(lambda done: url.here.up())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -324,7 +329,7 @@ class Downloader(resource.Resource):
|
|||||||
def __init__(self, downloader, name, filenode):
|
def __init__(self, downloader, name, filenode):
|
||||||
self._downloader = downloader
|
self._downloader = downloader
|
||||||
self._name = name
|
self._name = name
|
||||||
assert isinstance(filenode, FileNode)
|
IFileNode(filenode)
|
||||||
self._filenode = filenode
|
self._filenode = filenode
|
||||||
|
|
||||||
def render(self, ctx):
|
def render(self, ctx):
|
||||||
@ -397,14 +402,14 @@ class WebishServer(service.MultiService):
|
|||||||
# apparently 'ISite' does not exist
|
# apparently 'ISite' does not exist
|
||||||
#self.site._client = self.parent
|
#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.putChild("global_vdrive", Directory(root, "/"))
|
||||||
self.root.child_welcome.has_global_vdrive = True
|
self.root.child_welcome.has_global_vdrive = True
|
||||||
# I tried doing it this way and for some reason it didn't seem to work
|
# I tried doing it this way and for some reason it didn't seem to work
|
||||||
#print "REMEMBERING", self.site, dl, IDownloader
|
#print "REMEMBERING", self.site, dl, IDownloader
|
||||||
#self.site.remember(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.putChild("my_vdrive", Directory(my_vdrive, "~"))
|
||||||
self.root.child_welcome.has_my_vdrive = True
|
self.root.child_welcome.has_my_vdrive = True
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user