mac: added 'mount filesystem' action to the mac gui
authorrobk-tahoe <robk-tahoe@allmydata.com>
Wed, 20 Feb 2008 01:56:59 +0000 (18:56 -0700)
committerrobk-tahoe <robk-tahoe@allmydata.com>
Wed, 20 Feb 2008 01:56:59 +0000 (18:56 -0700)
this adds an action to the dock menu and to the file menu (when visible)
"Mount Filesystem".  This action opens a windows offering the user an
opportunity to select from any of the named *.cap files in their
.tahoe/private directory, and choose a corresponding mount point to mount
that at.

it launches the .app binary as a subprocess with the corresponding command
line arguments to launch the 'tahoe fuse' functionality to mount that file
system.  if a NAME.icns file is present in .tahoe/private alonside the
chosen NAME.cap, then that icon will be used when the filesystem is mounted.

this is highly unlikely to work when running from source, since it uses
introspection on sys.executable to find the relavent binary to launch in
order to get the right built .app's 'tahoe fuse' functionality.

it is also relatively likely that the code currently checked in, hence
linked into the build, will have as yet unresolved library dependencies.
it's quite unlikely to work on 10.5 with macfuse 1.3.1 at the moment.

src/allmydata/gui/macapp.py

index d8eee3e799667e5e3a1c8c25012b45d09c5160cf..e8f105773d906bac71ab844a64591009e090d803 100644 (file)
@@ -2,6 +2,7 @@
 import operator
 import os
 import stat
+import subprocess
 import sys
 import thread
 import threading
@@ -16,6 +17,7 @@ from twisted.python import log, logfile
 import allmydata
 from allmydata import client
 from allmydata.gui.confwiz import ConfWizApp, ACCOUNT_PAGE, DEFAULT_SERVER_URL
+from allmydata.uri import NewDirectoryURI
 import amdicon
 
 
@@ -170,6 +172,7 @@ def DisplayTraceback(message):
 
 WEBOPEN_ID = wx.NewId()
 ACCOUNT_PAGE_ID = wx.NewId()
+MOUNT_ID = wx.NewId()
 
 class SplashFrame(wx.Frame):
     def __init__(self):
@@ -217,10 +220,145 @@ class SplashPanel(wx.Panel):
         self.SetAutoLayout(True)
 
 
+class MountFrame(wx.Frame):
+    def __init__(self, app):
+        wx.Frame.__init__(self, None, -1, 'Allmydata Tahoe Mount Filesystem')
+
+        self.SetSizeHints(100, 100, 600, 800)
+        self.SetIcon(amdicon.getIcon())
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+
+        background = wx.Panel(self, -1)
+        background.parent = self
+        self.mount_panel = MountPanel(background, self.on_close, app)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        background_sizer = wx.BoxSizer(wx.VERTICAL)
+        background_sizer.Add(self.mount_panel, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 26)
+        background.SetSizer(background_sizer)
+        sizer.Add(background, 0, wx.EXPAND | wx.ALL, 0)
+        self.SetSizer(sizer)
+        self.SetAutoLayout(True)
+        self.Fit()
+        self.Layout()
+
+    def on_close(self, event):
+        self.Show(False)
+
+class MountPanel(wx.Panel):
+    def __init__(self, parent, on_close, app):
+        wx.Panel.__init__(self, parent, -1)
+        self.parent = parent
+        self.app = app
+
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+
+        self.caps = self.find_dir_caps()
+
+        self.label = wx.StaticText(self, -1, 'Allmydata Tahoe Mount Filesystem')
+        self.mnt_label = wx.StaticText(self, -1, 'Mount')
+        self.cap_choice = wx.Choice(self, -1, (120, 64), choices=self.caps.keys())
+        root_dir = self.cap_choice.FindString('root_dir')
+        if root_dir != -1:
+            self.cap_choice.SetSelection(root_dir)
+        self.at_label = wx.StaticText(self, -1, 'at')
+        self.mountpoint = wx.TextCtrl(self, -1, 'choose a mount dir', size=(256,22))
+        self.mnt_browse = wx.Button(self, -1, 'Browse')
+        mount_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        mount_sizer.Add(self.mnt_label, 0, wx.ALL, 4)
+        mount_sizer.Add(self.cap_choice, 0, wx.ALL, 4)
+        mount_sizer.Add(self.at_label, 0, wx.ALL, 4)
+        mount_sizer.Add(self.mountpoint, 0, wx.ALL, 4)
+        mount_sizer.Add(self.mnt_browse, 0, wx.ALL, 4)
+        self.mount = wx.Button(self, -1, 'Mount')
+        self.Bind(wx.EVT_BUTTON, self.on_mount, self.mount)
+        #self.Bind(wx.EVT_CHOICE, self.on_choice, self.cap_choice)
+        self.Bind(wx.EVT_BUTTON, self.on_mnt_browse, self.mnt_browse)
+        self.sizer.Add(self.label, 0, wx.CENTER | wx.ALL, 2)
+        self.sizer.Add(wx.Size(28,28), 1, wx.EXPAND | wx.ALL, 2)
+        self.sizer.Add(mount_sizer, 0, wx.EXPAND | wx.ALL, 0)
+        self.sizer.Add(wx.Size(28,28), 1, wx.EXPAND | wx.ALL, 2)
+        self.sizer.Add(self.mount, 0, wx.CENTER | wx.ALL, 2)
+        self.SetSizer(self.sizer)
+        self.SetAutoLayout(True)
+
+    def find_dir_caps(self):
+        priv_dir = os.path.join(self.app.basedir, 'private')
+        fs = os.listdir(priv_dir)
+        caps = {}
+        for f in fs:
+            if not f.endswith('.cap'):
+                continue
+            try:
+                log.msg('reading: %r' % (f,))
+                fh = file(os.path.join(priv_dir, f), 'rb')
+                cap = fh.read().strip()
+                fh.close()
+                uri = NewDirectoryURI.init_from_string(cap)
+                caps[f[:-4]] = cap
+            except:
+                log.msg('failed to read dir cap from "%s"' % (f,))
+                log.err()
+        return caps
+
+    #def on_choice(self, event):
+        #choice = event.GetString()
+        #log.msg('chose dir: %s' % (choice,))
+
+    def on_mount(self, event):
+        mountpoint = str(self.mountpoint.GetValue())
+        if not os.path.isdir(mountpoint):
+            wx.MessageBox(u'"%s" is not a directory' % (mountpoint,))
+        else:
+            cap_name = self.cap_choice.GetStringSelection()
+            self.do_mount(cap_name, mountpoint)
+
+    def on_mnt_browse(self, event):
+        dlg = wx.DirDialog(self, "Choose a Mountpoint Directory:",
+                           style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON)
+        if dlg.ShowModal() == wx.ID_OK:
+            mountpoint = dlg.GetPath()
+            self.mountpoint.SetValue(mountpoint)
+        dlg.Destroy()
+
+    def do_mount(self, cap_name, mountpoint):
+        log.msg('do_mount(%r, %r)' % (cap_name, mountpoint))
+        log.msg('sys.exec = %r' % (sys.executable,))
+        if not sys.executable.endswith('Tahoe.app/Contents/MacOS/python'):
+            log.msg("can't find tahoe.app: sys.executable = %r" % (sys.executable,))
+            wx.MessageBox("Can't determine location of Allmydata Tahoe.app")
+            self.parent.parent.Show(False)
+            return
+        bin_path = sys.executable[:-6] + 'Allmydata Tahoe'
+        log.msg('%r exists: %r' % (bin_path, os.path.exists(bin_path),))
+
+        icns_path = os.path.join(self.app.basedir, 'private', cap_name+'.icns')
+        if os.path.exists(icns_path):
+            icon_arg = ['-ovolicon=%s' % (icns_path,)]
+        else:
+            icon_arg = []
+
+        command = [bin_path, 'fuse', cap_name] + icon_arg + [mountpoint]
+        log.msg('spawning command %r' % (command,))
+        proc = subprocess.Popen(command,
+                                cwd=self.app.basedir,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        log.msg('spawned process, pid %s' % (proc.pid,))
+        wx.FutureCall(4096, self.check_mount, proc)
+        self.parent.parent.Show(False)
+
+    def check_mount(self, proc):
+        message = [ 'pid: %s' % (proc.pid,),
+                    'ret: %s' % (proc.returncode,),
+                    'stdout:\n%s' % (proc.stdout.read(),),
+                    'stderr:\n%s' % (proc.stderr.read(),),
+                    ]
+        log.msg('\n'.join(['spawned process:'] + message))
+
 class MacGuiApp(wx.App):
     def __init__(self, app):
-        wx.App.__init__(self)
         self.app = app
+        wx.App.__init__(self)
 
     def OnInit(self):
         try:
@@ -230,6 +368,8 @@ class MacGuiApp(wx.App):
 
             wx.FutureCall(4096, self.on_timer, None)
 
+            self.mount_frame = MountFrame(self.app)
+
             self.setup_dock_icon()
             menubar = self.setup_app_menu(self.frame)
             self.frame.SetMenuBar(menubar)
@@ -254,6 +394,8 @@ class MacGuiApp(wx.App):
         frame.Bind(wx.EVT_MENU, self.on_webopen, item)
         item = file_menu.Append(ACCOUNT_PAGE_ID, text='Open Account Page')
         frame.Bind(wx.EVT_MENU, self.on_account_page, item)
+        item = file_menu.Append(MOUNT_ID, text='Mount Filesystem')
+        frame.Bind(wx.EVT_MENU, self.on_mount, item)
         item = file_menu.Append(wx.ID_ABOUT, text='About')
         frame.Bind(wx.EVT_MENU, self.on_about, item)
         item = file_menu.Append(wx.ID_EXIT, text='Quit')
@@ -269,6 +411,8 @@ class MacGuiApp(wx.App):
         self.tbicon.Bind(wx.EVT_MENU, self.on_webopen, item)
         item = dock_menu.Append(ACCOUNT_PAGE_ID, text='Open Account Page')
         self.tbicon.Bind(wx.EVT_MENU, self.on_account_page, item)
+        item = dock_menu.Append(MOUNT_ID, text='Mount Filesystem')
+        self.tbicon.Bind(wx.EVT_MENU, self.on_mount, item)
         self.tbicon.PopupMenu(dock_menu)
 
     def on_about(self, event):
@@ -283,3 +427,6 @@ class MacGuiApp(wx.App):
     def on_account_page(self, event):
         webbrowser.open(DEFAULT_SERVER_URL + ACCOUNT_PAGE)
 
+    def on_mount(self, event):
+        self.mount_frame.Show(True)
+