From b06c74c2a10c6b3d3cc2bbf166e2c4b88bb77853 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 27 Jun 2007 17:11:06 -0700 Subject: [PATCH] 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. --- docs/codemap.txt | 25 +++++---- src/allmydata/client.py | 78 ++------------------------ src/allmydata/interfaces.py | 29 ++++++++++ src/allmydata/test/test_system.py | 6 +- src/allmydata/vdrive.py | 91 +++++++++++++++++++++++++++++++ src/allmydata/webish.py | 42 +++++++------- 6 files changed, 165 insertions(+), 106 deletions(-) create mode 100644 src/allmydata/vdrive.py diff --git a/docs/codemap.txt b/docs/codemap.txt index b61a3681..d5e55941 100644 --- a/docs/codemap.txt +++ b/docs/codemap.txt @@ -23,8 +23,10 @@ Within src/allmydata/ : node.py: the base Node, which handles connection establishment and application startup - client.py, introducer_and_vdrive.py: two specialized subclasses of Node, for users - and the central introducer/vdrive handler, respectively + client.py, introducer_and_vdrive.py: + 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, server is used by introducer_and_vdrive.py @@ -38,20 +40,21 @@ Within src/allmydata/ : download.py: download-side peer selection, share retrieval, decoding - filetable.py, vdrive.py: implements the current one-global-vdrive layer, - part runs on client nodes, part runs on the - central vdrive handler + dirnode.py: implements the directory nodes. One part runs on the + global vdrive server, the other runs inside a client + (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 - workqueue.py, filetree/*.py: building blocks for the future filetree work + uri.py: URI packing/parsing routines hashtree.py: Merkle hash tree classes debugshell.py, manhole.py: SSH-connected python shell, for debug purposes - uri.py: URI packing/parsing routines - util/*.py: misc utility classes 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 (twisted.application.service) Service instances. The Foolscap "Tub" is one of -these. Client nodes have an Uploader service and a Downloader service that turn -data into URIs and back again. They also have a VDrive service which provides -access to the single global shared filesystem. +these. Client nodes have an Uploader service and a Downloader service that +turn data into URIs and back again. They also have a VirtualDrive service +which provides access to the single global shared filesystem. 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 diff --git a/src/allmydata/client.py b/src/allmydata/client.py index c6e20ba8..a87dd0b3 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -2,8 +2,8 @@ import os, sha, stat, time from foolscap import Referenceable, SturdyRef from zope.interface import implements -from allmydata.interfaces import RIClient, IDirectoryNode -from allmydata import node, uri +from allmydata.interfaces import RIClient +from allmydata import node from twisted.internet import defer, reactor from twisted.application.internet import TimerService @@ -16,7 +16,7 @@ from allmydata.download import Downloader from allmydata.webish import WebishServer from allmydata.control import ControlServer from allmydata.introducer import IntroducerClient -from allmydata.dirnode import create_directory_node, create_directory +from allmydata.vdrive import VirtualDrive class Client(node.Node, Referenceable): implements(RIClient) @@ -25,10 +25,8 @@ class Client(node.Node, Referenceable): NODETYPE = "client" WEBPORTFILE = "webport" INTRODUCER_FURL_FILE = "introducer.furl" - GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl" MY_FURL_FILE = "myself.furl" SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline" - MY_VDRIVE_URI_FILE = "my_vdrive.uri" # we're pretty narrow-minded right now OLDEST_SUPPORTED_VERSION = allmydata.__version__ @@ -37,10 +35,10 @@ class Client(node.Node, Referenceable): node.Node.__init__(self, basedir) self.my_furl = None self.introducer_client = None - self._connected_to_vdrive = False self.add_service(StorageServer(os.path.join(basedir, self.STOREDIR))) self.add_service(Uploader()) self.add_service(Downloader()) + self.add_service(VirtualDrive()) WEBPORTFILE = os.path.join(self.basedir, self.WEBPORTFILE) if os.path.exists(WEBPORTFILE): f = open(WEBPORTFILE, "r") @@ -54,16 +52,6 @@ class Client(node.Node, Referenceable): self.introducer_furl = f.read().strip() 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, self.SUICIDE_PREVENTION_HOTLINE_FILE) if os.path.exists(hotline_file): @@ -99,10 +87,6 @@ class Client(node.Node, Referenceable): 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): c = ControlServer() c.setServiceParent(self) @@ -112,60 +96,6 @@ class Client(node.Node, Referenceable): f.close() 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): return str(allmydata.__version__), str(self.OLDEST_SUPPORTED_VERSION) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index db39c68a..20d3ca93 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -655,6 +655,35 @@ class IUploader(Interface): def upload_filehandle(filehane): """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): """You have tried to write to a read-only node.""" diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 4f14731c..651dfc1f 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -238,7 +238,8 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase): log.msg("PUBLISHING") ut = upload.Data(DATA) 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: subdir1_node.add_file("mydata567", ut)) def _stash_uri(filenode): @@ -251,7 +252,8 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase): log.msg("publish finished") 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 filenode: filenode.download_to_data()) return d1 diff --git a/src/allmydata/vdrive.py b/src/allmydata/vdrive.py new file mode 100644 index 00000000..b231a84f --- /dev/null +++ b/src/allmydata/vdrive.py @@ -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) + diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index 70db0950..e92b8db9 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -42,7 +42,7 @@ class Welcome(rend.Page): return "yes" return "no" 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 "no" def data_num_peers(self, ctx, data): @@ -64,7 +64,7 @@ class Welcome(rend.Page): return ctx.tag 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, ", T.a(href="../global_vdrive")["Click Here!"], ] @@ -72,7 +72,7 @@ class Welcome(rend.Page): "responding), no vdrive available."] 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, ", T.a(href="../my_vdrive")["Click Here!"], ] @@ -397,6 +397,26 @@ class Root(rend.Page): 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): name = "webish" @@ -404,10 +424,6 @@ class WebishServer(service.MultiService): def __init__(self, webport): service.MultiService.__init__(self) 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.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 # apparently 'ISite' does not exist #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 - -- 2.45.2