Add the 'vdrive' service, for clients to access the public/private root dirs.

These allow client-side code to conveniently retrieve the IDirectoryNode
instances for both the global shared public root directory, and the per-user
private root directory.
This commit is contained in:
Brian Warner
2007-06-27 17:11:06 -07:00
parent 1d86cd161b
commit b06c74c2a1
6 changed files with 165 additions and 106 deletions

View File

@ -23,8 +23,10 @@ Within src/allmydata/ :
node.py: the base Node, which handles connection establishment and node.py: the base Node, which handles connection establishment and
application startup application startup
client.py, introducer_and_vdrive.py: two specialized subclasses of Node, for users client.py, introducer_and_vdrive.py:
and the central introducer/vdrive handler, respectively these are two specialized subclasses of Node, for users and the central
introducer/vdrive handler, respectively. Each works by assembling a
collection of services underneath a top-level Node instance.
introducer.py: node introduction handlers, client is used by client.py, introducer.py: node introduction handlers, client is used by client.py,
server is used by introducer_and_vdrive.py server is used by introducer_and_vdrive.py
@ -38,20 +40,21 @@ Within src/allmydata/ :
download.py: download-side peer selection, share retrieval, decoding download.py: download-side peer selection, share retrieval, decoding
filetable.py, vdrive.py: implements the current one-global-vdrive layer, dirnode.py: implements the directory nodes. One part runs on the
part runs on client nodes, part runs on the global vdrive server, the other runs inside a client
central vdrive handler (starting with vdrive.py)
vdrive.py: provides a client-side service that accesses the global
shared virtual drive and the per-user private drive.
webish.py, web/*.xhtml: provides the web frontend, using a Nevow server webish.py, web/*.xhtml: provides the web frontend, using a Nevow server
workqueue.py, filetree/*.py: building blocks for the future filetree work uri.py: URI packing/parsing routines
hashtree.py: Merkle hash tree classes hashtree.py: Merkle hash tree classes
debugshell.py, manhole.py: SSH-connected python shell, for debug purposes debugshell.py, manhole.py: SSH-connected python shell, for debug purposes
uri.py: URI packing/parsing routines
util/*.py: misc utility classes util/*.py: misc utility classes
test/*.py: unit tests test/*.py: unit tests
@ -59,9 +62,9 @@ Within src/allmydata/ :
Both the client and the central introducer-and-vdrive node runs as a tree of Both the client and the central introducer-and-vdrive node runs as a tree of
(twisted.application.service) Service instances. The Foolscap "Tub" is one of (twisted.application.service) Service instances. The Foolscap "Tub" is one of
these. Client nodes have an Uploader service and a Downloader service that turn these. Client nodes have an Uploader service and a Downloader service that
data into URIs and back again. They also have a VDrive service which provides turn data into URIs and back again. They also have a VirtualDrive service
access to the single global shared filesystem. which provides access to the single global shared filesystem.
The Uploader is given an "upload source" (which could be an open filehandle, The Uploader is given an "upload source" (which could be an open filehandle,
a filename on local disk, or even a string), and returns a Deferred that a filename on local disk, or even a string), and returns a Deferred that

View File

@ -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, IDirectoryNode from allmydata.interfaces import RIClient
from allmydata import node, uri from allmydata import node
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.application.internet import TimerService from twisted.application.internet import TimerService
@ -16,7 +16,7 @@ from allmydata.download import Downloader
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
from allmydata.dirnode import create_directory_node, create_directory from allmydata.vdrive import VirtualDrive
class Client(node.Node, Referenceable): class Client(node.Node, Referenceable):
implements(RIClient) implements(RIClient)
@ -25,10 +25,8 @@ class Client(node.Node, Referenceable):
NODETYPE = "client" NODETYPE = "client"
WEBPORTFILE = "webport" WEBPORTFILE = "webport"
INTRODUCER_FURL_FILE = "introducer.furl" INTRODUCER_FURL_FILE = "introducer.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_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__
@ -37,10 +35,10 @@ class Client(node.Node, Referenceable):
node.Node.__init__(self, basedir) node.Node.__init__(self, basedir)
self.my_furl = None self.my_furl = None
self.introducer_client = None self.introducer_client = None
self._connected_to_vdrive = False
self.add_service(StorageServer(os.path.join(basedir, self.STOREDIR))) self.add_service(StorageServer(os.path.join(basedir, self.STOREDIR)))
self.add_service(Uploader()) self.add_service(Uploader())
self.add_service(Downloader()) self.add_service(Downloader())
self.add_service(VirtualDrive())
WEBPORTFILE = os.path.join(self.basedir, self.WEBPORTFILE) WEBPORTFILE = os.path.join(self.basedir, self.WEBPORTFILE)
if os.path.exists(WEBPORTFILE): if os.path.exists(WEBPORTFILE):
f = open(WEBPORTFILE, "r") f = open(WEBPORTFILE, "r")
@ -54,16 +52,6 @@ class Client(node.Node, Referenceable):
self.introducer_furl = f.read().strip() self.introducer_furl = f.read().strip()
f.close() f.close()
self.global_vdrive_furl = None
GLOBAL_VDRIVE_FURL_FILE = os.path.join(self.basedir,
self.GLOBAL_VDRIVE_FURL_FILE)
if os.path.exists(GLOBAL_VDRIVE_FURL_FILE):
f = open(GLOBAL_VDRIVE_FURL_FILE, "r")
self.global_vdrive_furl = f.read().strip()
f.close()
#self.add_service(VDrive())
self._my_vdrive = None
hotline_file = os.path.join(self.basedir, hotline_file = os.path.join(self.basedir,
self.SUICIDE_PREVENTION_HOTLINE_FILE) self.SUICIDE_PREVENTION_HOTLINE_FILE)
if os.path.exists(hotline_file): if os.path.exists(hotline_file):
@ -99,10 +87,6 @@ class Client(node.Node, Referenceable):
self.register_control() self.register_control()
if self.global_vdrive_furl:
self.vdrive_connector = self.tub.connectTo(self.global_vdrive_furl,
self._got_vdrive)
def register_control(self): def register_control(self):
c = ControlServer() c = ControlServer()
c.setServiceParent(self) c.setServiceParent(self)
@ -112,60 +96,6 @@ class Client(node.Node, Referenceable):
f.close() f.close()
os.chmod("control.furl", 0600) os.chmod("control.furl", 0600)
def _got_vdrive(self, vdrive_server):
# vdrive_server implements RIVirtualDriveServer
self.log("connected to vdrive server")
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_uri(self, root_uri):
furl, wk = uri.unpack_dirnode_uri(root_uri)
self._vdrive_furl = furl
return create_directory_node(self, root_uri)
def _got_vdrive_rootnode(self, rootnode):
self.log("got vdrive root")
self._vdrive_root = rootnode
self._connected_to_vdrive = True
#vdrive = self.getServiceNamed("vdrive")
#vdrive.set_server(vdrive_server)
#vdrive.set_root(vdrive_root)
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
webish.set_vdrive_rootnode(rootnode)
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_URI_FILE, "r")
my_vdrive_uri = f.read().strip()
f.close()
return create_directory_node(self, my_vdrive_uri)
except EnvironmentError:
assert self._vdrive_furl
d = create_directory(self, self._vdrive_furl)
def _got_directory(dirnode):
f = open(MY_VDRIVE_URI_FILE, "w")
f.write(dirnode.get_uri() + "\n")
f.close()
return dirnode
d.addCallback(_got_directory)
return d
def _got_my_vdrive(self, my_vdrive):
IDirectoryNode(my_vdrive)
self._my_vdrive = my_vdrive
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
webish.set_my_vdrive_rootnode(my_vdrive)
def remote_get_versions(self): def remote_get_versions(self):
return str(allmydata.__version__), str(self.OLDEST_SUPPORTED_VERSION) return str(allmydata.__version__), str(self.OLDEST_SUPPORTED_VERSION)

View File

@ -655,6 +655,35 @@ class IUploader(Interface):
def upload_filehandle(filehane): def upload_filehandle(filehane):
"""Like upload(), but accepts an open filehandle.""" """Like upload(), but accepts an open filehandle."""
class IVirtualDrive(Interface):
"""I am a service that may be available to a client.
Within any client program, this service can be retrieved by using
client.getService('vdrive').
"""
def have_public_root():
"""Return a Boolean, True if get_public_root() will work."""
def get_public_root():
"""Get the public read-write directory root.
This returns a Deferred that fires with an IDirectoryNode instance
corresponding to the global shared root directory."""
def have_private_root():
"""Return a Boolean, True if get_public_root() will work."""
def get_private_root():
"""Get the private directory root.
This returns a Deferred that fires with an IDirectoryNode instance
corresponding to this client's private root directory."""
def get_node(self, uri):
"""Transform a URI into an IDirectoryNode or IFileNode.
This returns a Deferred that will fire with an instance that provides
either IDirectoryNode or IFileNode, as appropriate."""
class NotCapableError(Exception): class NotCapableError(Exception):
"""You have tried to write to a read-only node.""" """You have tried to write to a read-only node."""

View File

@ -238,7 +238,8 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
log.msg("PUBLISHING") log.msg("PUBLISHING")
ut = upload.Data(DATA) ut = upload.Data(DATA)
c0 = self.clients[0] c0 = self.clients[0]
d1 = c0._vdrive_root.create_empty_directory("subdir1") d1 = c0.getServiceNamed("vdrive").get_public_root()
d1.addCallback(lambda root: root.create_empty_directory("subdir1"))
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):
@ -251,7 +252,8 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
log.msg("publish finished") log.msg("publish finished")
c1 = self.clients[1] c1 = self.clients[1]
d1 = c1._vdrive_root.get("subdir1") d1 = c1.getServiceNamed("vdrive").get_public_root()
d1.addCallback(lambda root: root.get("subdir1"))
d1.addCallback(lambda subdir1: subdir1.get("mydata567")) d1.addCallback(lambda subdir1: subdir1.get("mydata567"))
d1.addCallback(lambda filenode: filenode.download_to_data()) d1.addCallback(lambda filenode: filenode.download_to_data())
return d1 return d1

91
src/allmydata/vdrive.py Normal file
View File

@ -0,0 +1,91 @@
import os
from twisted.application import service
from zope.interface import implements
from allmydata.interfaces import IVirtualDrive
from allmydata import dirnode, uri
from twisted.internet import defer
class NoGlobalVirtualDriveError(Exception):
pass
class NoPrivateVirtualDriveError(Exception):
pass
class VirtualDrive(service.MultiService):
implements(IVirtualDrive)
name = "vdrive"
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
GLOBAL_VDRIVE_URI_FILE = "global_root.uri"
MY_VDRIVE_URI_FILE = "my_vdrive.uri"
def __init__(self):
service.MultiService.__init__(self)
self._global_uri = None
self._private_uri = None
def startService(self):
service.MultiService.startService(self)
basedir = self.parent.basedir
client = self.parent
tub = self.parent.tub
global_vdrive_furl = None
furl_file = os.path.join(basedir, self.GLOBAL_VDRIVE_FURL_FILE)
if os.path.exists(furl_file):
f = open(furl_file, "r")
global_vdrive_furl = f.read().strip()
f.close()
global_uri_file = os.path.join(basedir,
self.GLOBAL_VDRIVE_URI_FILE)
if os.path.exists(global_uri_file):
f = open(global_uri_file)
self._global_uri = f.read().strip()
f.close()
elif global_vdrive_furl:
d = tub.getReference(global_vdrive_furl)
d.addCallback(lambda vdrive_server:
vdrive_server.callRemote("get_public_root_uri"))
def _got_global_uri(global_uri):
self._global_uri = global_uri
d.addCallback(_got_global_uri)
private_uri_file = os.path.join(basedir,
self.MY_VDRIVE_URI_FILE)
if os.path.exists(private_uri_file):
f = open(private_uri_file)
private_vdrive_uri = f.read().strip()
f.close()
elif global_vdrive_furl:
d = dirnode.create_directory(client, global_vdrive_furl)
def _got_directory(dirnode):
private_uri = dirnode.get_uri()
self._private_uri = private_uri
f = open(private_uri_file, "w")
f.write(private_uri + "\n")
f.close()
d.addCallback(_got_directory)
def get_node(self, node_uri):
if uri.is_dirnode_uri(node_uri):
return dirnode.create_directory_node(self.parent, node_uri)
else:
return defer.succeed(dirnode.FileNode(node_uri, self.parent))
def have_public_root(self):
return bool(self._global_uri)
def get_public_root(self):
if not self._global_uri:
return defer.fail(NoGlobalVirtualDriveError())
return self.get_node(self._global_uri)
def have_private_root(self):
return bool(self._private_uri)
def get_private_root(self):
if not self._private_uri:
return defer.fail(NoPrivateVirtualDriveError())
return self.get_node(self._private_uri)

View File

@ -42,7 +42,7 @@ class Welcome(rend.Page):
return "yes" return "yes"
return "no" return "no"
def data_connected_to_vdrive(self, ctx, data): def data_connected_to_vdrive(self, ctx, data):
if IClient(ctx).connected_to_vdrive(): if IClient(ctx).getServiceNamed("vdrive").have_public_root():
return "yes" return "yes"
return "no" return "no"
def data_num_peers(self, ctx, data): def data_num_peers(self, ctx, data):
@ -64,7 +64,7 @@ class Welcome(rend.Page):
return ctx.tag return ctx.tag
def render_global_vdrive(self, ctx, data): def render_global_vdrive(self, ctx, data):
if self.has_global_vdrive: if IClient(ctx).getServiceNamed("vdrive").have_public_root():
return T.p["To view the global shared filestore, ", return T.p["To view the global shared filestore, ",
T.a(href="../global_vdrive")["Click Here!"], T.a(href="../global_vdrive")["Click Here!"],
] ]
@ -72,7 +72,7 @@ class Welcome(rend.Page):
"responding), no vdrive available."] "responding), no vdrive available."]
def render_my_vdrive(self, ctx, data): def render_my_vdrive(self, ctx, data):
if self.has_my_vdrive: if IClient(ctx).getServiceNamed("vdrive").have_private_root():
return T.p["To view your personal private non-shared filestore, ", return T.p["To view your personal private non-shared filestore, ",
T.a(href="../my_vdrive")["Click Here!"], T.a(href="../my_vdrive")["Click Here!"],
] ]
@ -397,6 +397,26 @@ class Root(rend.Page):
child_welcome = Welcome() child_welcome = Welcome()
def child_global_vdrive(self, ctx):
client = IClient(ctx)
vdrive = client.getServiceNamed("vdrive")
if vdrive.have_public_root():
d = vdrive.get_public_root()
d.addCallback(lambda dirnode: Directory(dirnode, "/"))
return d
else:
return static.Data("sorry, still initializing", "text/plain")
def child_private_vdrive(self, ctx):
client = IClient(ctx)
vdrive = client.getServiceNamed("vdrive")
if vdrive.have_private_root():
d = vdrive.get_private_root()
d.addCallback(lambda dirnode: Directory(dirnode, "~"))
return d
else:
return static.Data("sorry, still initializing", "text/plain")
class WebishServer(service.MultiService): class WebishServer(service.MultiService):
name = "webish" name = "webish"
@ -404,10 +424,6 @@ class WebishServer(service.MultiService):
def __init__(self, webport): def __init__(self, webport):
service.MultiService.__init__(self) service.MultiService.__init__(self)
self.root = Root() self.root = Root()
self.root.child_welcome.has_global_vdrive = False
self.root.child_welcome.has_my_vdrive = False
placeholder = static.Data("sorry, still initializing", "text/plain")
self.root.putChild("vdrive", placeholder)
self.root.putChild("", url.here.child("welcome"))#Welcome()) self.root.putChild("", url.here.child("welcome"))#Welcome())
self.site = site = appserver.NevowSite(self.root) self.site = site = appserver.NevowSite(self.root)
@ -425,15 +441,3 @@ class WebishServer(service.MultiService):
# I thought you could do the same with an existing interface, but # I thought you could do the same with an existing interface, but
# apparently 'ISite' does not exist # apparently 'ISite' does not exist
#self.site._client = self.parent #self.site._client = self.parent
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_rootnode(self, my_vdrive):
self.root.putChild("my_vdrive", Directory(my_vdrive, "~"))
self.root.child_welcome.has_my_vdrive = True