mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-19 21:17:54 +00:00
vdrive: switch to URI:DIR and URI:DIR-RO, providing transitive readonlyness
This commit is contained in:
parent
2cf7cfbe4a
commit
fb02488a8e
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user