import operator
import os
import stat
-import subprocess
+from subprocess import Popen, PIPE
import sys
import thread
import threading
'''
def run_macapp():
- basedir = os.path.expanduser('~/.tahoe')
- if not os.path.isdir(basedir):
+ nodedir = os.path.expanduser('~/.tahoe')
+ if not os.path.isdir(nodedir):
app_supp = os.path.expanduser('~/Library/Application Support/Allmydata Tahoe/')
if not os.path.isdir(app_supp):
os.makedirs(app_supp)
- os.symlink(app_supp, basedir)
+ os.symlink(app_supp, nodedir)
- app = App(basedir)
- return app.run()
+ app_cont = AppContainer(nodedir)
+ return app_cont.run()
class MacGuiClient(client.Client):
- def __init__(self, basedir, app):
- self.app = app
- client.Client.__init__(self, basedir)
+ """
+ This is a subclass of the tahoe 'client' node, which hooks into the
+ client's 'notice something went wrong' mechanism, to display the fact
+ in a manner sensible for a wx gui app
+ """
+ def __init__(self, nodedir, app_cont):
+ self.app_cont = app_cont
+ client.Client.__init__(self, nodedir)
def _service_startup_failed(self, failure):
wx.CallAfter(self.wx_abort, failure)
def wx_abort(self, failure):
wx.MessageBox(failure.getTraceback(), 'Fatal Error in Node startup')
- self.app.guiapp.ExitMainLoop()
-
-class App(object):
- def __init__(self, basedir):
- self.basedir = basedir
+ self.app_cont.guiapp.ExitMainLoop()
+
+class AppContainer(object):
+ """
+ This is the 'container' for the mac app, which takes care of initial
+ configuration concerns - e.g. running the confwiz before going any further -
+ of launching the reactor, and within it the tahoe node, on a separate thread,
+ and then lastly of launching the actual wx gui app and waiting for it to exit.
+ """
+ def __init__(self, nodedir):
+ self.nodedir = nodedir
def files_exist(self, file_list):
- extant_conf = [ os.path.exists(os.path.join(self.basedir, f)) for f in file_list ]
+ extant_conf = [ os.path.exists(os.path.join(self.nodedir, f)) for f in file_list ]
return reduce(operator.__and__, extant_conf)
def is_config_incomplete(self):
necessary_conf_files = ['introducer.furl', 'private/root_dir.cap']
need_config = not self.files_exist(necessary_conf_files)
if need_config:
- print 'some config is missing from basedir (%s): %s' % (self.basedir, necessary_conf_files)
+ print 'some config is missing from nodedir (%s): %s' % (self.nodedir, necessary_conf_files)
return need_config
def run(self):
# handle initial config
- if not os.path.exists(os.path.join(self.basedir, 'webport')):
- f = file(os.path.join(self.basedir, 'webport'), 'wb')
+ if not os.path.exists(os.path.join(self.nodedir, 'webport')):
+ f = file(os.path.join(self.nodedir, 'webport'), 'wb')
f.write('8123')
f.close()
if self.is_config_incomplete():
- app = ConfWizApp(DEFAULT_SERVER_URL, open_welcome_page=True)
- app.MainLoop()
+ confwiz = ConfWizApp(DEFAULT_SERVER_URL, open_welcome_page=True)
+ confwiz.MainLoop()
if self.is_config_incomplete():
print 'config still incomplete; confwiz cancelled, exiting'
return 1
# set up twisted logging. this will become part of the node rsn.
- logdir = os.path.join(self.basedir, 'logs')
+ logdir = os.path.join(self.nodedir, 'logs')
if not os.path.exists(logdir):
os.makedirs(logdir)
lf = logfile.LogFile('tahoesvc.log', logdir)
self.maybe_install_tahoe_script()
# actually start up the node and the ui
- os.chdir(self.basedir)
+ os.chdir(self.nodedir)
+ # start the reactor thread up, launch the tahoe node therein
self.start_reactor()
try:
- self.guiapp = MacGuiApp(app=self)
+ # launch the actual gui on the wx event loop, wait for it to quit
+ self.guiapp = MacGuiApp(app_cont=self)
self.guiapp.MainLoop()
log.msg('gui mainloop exited')
except:
log.err()
+ # shutdown the reactor, hence tahoe node, before exiting
self.stop_reactor()
return 0
def launch_reactor(self):
# run the node itself
- #c = client.Client(self.basedir)
- c = MacGuiClient(self.basedir, self)
+ #c = client.Client(self.nodedir)
+ c = MacGuiClient(self.nodedir, app_cont=self)
reactor.callLater(0, c.startService) # after reactor startup
reactor.run(installSignalHandlers=False)
self.reactor_shutdown.set()
self.reactor_shutdown.wait()
log.msg('reactor shut down')
- def webopen(self, alias=None):
- log.msg('webopen: %r' % (alias,))
- if alias is None:
- alias = 'tahoe'
- root_uri = get_aliases(self.basedir).get(alias)
- if root_uri:
- nodeurl = file(os.path.join(self.basedir, 'node.url'), 'rb').read().strip()
- if nodeurl[-1] != "/":
- nodeurl += "/"
- url = nodeurl + "uri/%s/" % urllib.quote(root_uri)
- webbrowser.open(url)
-
def maybe_install_tahoe_script(self):
path_candidates = ['/usr/local/bin', '~/bin', '~/Library/bin']
env_path = map(os.path.expanduser, os.environ['PATH'].split(':'))
class MountFrame(wx.Frame):
- def __init__(self, app):
+ def __init__(self, guiapp):
wx.Frame.__init__(self, None, -1, 'Allmydata Mount Filesystem')
self.SetSizeHints(100, 100, 600, 800)
background = wx.Panel(self, -1)
background.parent = self
- self.mount_panel = MountPanel(background, self.on_close, app)
+ self.mount_panel = MountPanel(background, self.on_close, guiapp)
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)
def on_close(self, event):
self.Show(False)
-
-def check_mount(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))
-
-def mount_filesystem(basedir, alias_name, mountpoint, display_name=None, timeout=None):
- log.msg('mount_filesystem(%r,%r,%r,%r)' % (basedir, alias_name, mountpoint, display_name))
- log.msg('sys.exec = %r' % (sys.executable,))
- if not sys.executable.endswith('Allmydata.app/Contents/MacOS/python'):
- log.msg("can't find allmydata.app: sys.executable = %r" % (sys.executable,))
- wx.MessageBox("Can't determine location of Allmydata.app")
- return False
- bin_path = sys.executable[:-6] + 'Allmydata'
- log.msg('%r exists: %r' % (bin_path, os.path.exists(bin_path),))
-
- foptions = []
- foptions.append('-olocal') # required to display in Finder on leopard
-
- if display_name is None:
- display_name = alias_name
- foptions.append('-ovolname=%s' % (display_name,))
-
- if timeout is None:
- timeout = DEFAULT_FUSE_TIMEOUT
- if timeout:
- foptions.append('-odaemon_timeout=%d' % (timeout,))
-
- icns_path = os.path.join(basedir, 'private', alias_name+'.icns')
- if not os.path.exists(icns_path):
- icns_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable),
- '../Resources/allmydata.icns'))
- log.msg('set icns_path=%s' % (icns_path,))
- log.msg('icns_path exists: %s' % os.path.exists(icns_path))
- if os.path.exists(icns_path):
- foptions.append('-ovolicon=%s' % (icns_path,))
-
- command = [bin_path, 'fuse', '--alias', alias_name] + foptions + [mountpoint]
- log.msg('spawning command %r' % (command,))
- proc = subprocess.Popen(command,
- cwd=basedir,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- log.msg('spawned process, pid %s' % (proc.pid,))
- wx.FutureCall(4096, check_mount, proc)
- return True
-
class MountPanel(wx.Panel):
- def __init__(self, parent, on_close, app):
+ def __init__(self, parent, on_close, guiapp):
wx.Panel.__init__(self, parent, -1)
self.parent = parent
- self.app = app
+ self.guiapp = guiapp
self.sizer = wx.BoxSizer(wx.VERTICAL)
- self.aliases = get_aliases(self.app.basedir)
-
self.label = wx.StaticText(self, -1, 'Allmydata Mount Filesystem')
self.mnt_label = wx.StaticText(self, -1, 'Mount')
- self.alias_choice = wx.Choice(self, -1, (120, 64), choices=self.aliases.keys())
+ self.alias_choice = wx.Choice(self, -1, (120, 64), choices=self.guiapp.aliases.keys())
root_dir = self.alias_choice.FindString('tahoe')
if root_dir != -1:
self.alias_choice.SetSelection(root_dir)
self.SetSizer(self.sizer)
self.SetAutoLayout(True)
- #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):
def do_mount(self, alias_name, mountpoint):
log.msg('do_mount(%r, %r)' % (alias_name, mountpoint))
- # XXX this needs a referential cleanup
- timeout = self.app.config['daemon-timeout']
- ok = mount_filesystem(self.app.basedir, alias_name, mountpoint, timeout=timeout)
- if ok and self.app.config['auto-open']:
- wx.FutureCall(2048, open_finder, mountpoint)
+ self.guiapp.mount_filesystem(alias_name, mountpoint)
self.parent.parent.Show(False)
- def old_do_mount(self, alias_name, mountpoint):
- log.msg('do_mount(%r, %r)' % (alias_name, mountpoint))
- log.msg('sys.exec = %r' % (sys.executable,))
- if not sys.executable.endswith('Allmydata.app/Contents/MacOS/python'):
- log.msg("can't find allmydata.app: sys.executable = %r" % (sys.executable,))
- wx.MessageBox("Can't determine location of Allmydata.app")
- self.parent.parent.Show(False)
- return
- bin_path = sys.executable[:-6] + 'Allmydata'
- log.msg('%r exists: %r' % (bin_path, os.path.exists(bin_path),))
-
- foptions = []
- foptions.append('-ovolname=%s' % (alias_name,))
- foptions.append('-olocal')
-
- timeout = DEFAULT_FUSE_TIMEOUT
- # [ ] TODO: make this configurable
- if timeout:
- foptions.append('-odaemon_timeout=%d' % (timeout,))
-
- icns_path = os.path.join(self.app.basedir, 'private', alias_name+'.icns')
- if not os.path.exists(icns_path):
- icns_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable),
- '../Resources/allmydata.icns'))
- log.msg('set icns_path=%s' % (icns_path,))
- log.msg('icns_path exists: %s' % os.path.exists(icns_path))
- if os.path.exists(icns_path):
- foptions.append('-ovolicon=%s' % (icns_path,))
-
- command = [bin_path, 'fuse', '--alias', alias_name] + foptions + [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))
-
-def open_finder(path):
- proc = subprocess.Popen(['/usr/bin/open', path],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
class MacGuiApp(wx.App):
config = {
'auto-mount': True,
'daemon-timeout': DEFAULT_FUSE_TIMEOUT,
}
- def __init__(self, app):
- self.app = app
+ def __init__(self, app_cont):
+ self.app_cont = app_cont
+ self.nodedir = app_cont.nodedir
self.load_config()
+ self.mounted_filesystems = {}
+ self.aliases = get_aliases(self.nodedir)
wx.App.__init__(self)
- # XXX should have a table of all mounted filesystems, so that can unmount on quit
+ ## load up setting from gui.conf dir
def load_config(self):
log.msg('load_config')
- confdir = os.path.join(self.app.basedir, 'gui.conf')
+ confdir = os.path.join(self.nodedir, 'gui.conf')
config = {}
config.update(self.config)
for k in self.config:
val = int(val)
config[k] = val
self.config = config
- #log.msg('loaded config: %r' % (self.config,)) # XXX
+ ## GUI wx init
def OnInit(self):
log.msg('OnInit')
try:
wx.FutureCall(4096, self.on_timer, None)
- # XXX clean up this ref
- self.app.config = self.config
-
- self.mount_frame = MountFrame(self.app)
+ self.mount_frame = MountFrame(guiapp=self)
self.setup_dock_icon()
menubar = self.setup_app_menu(self.frame)
DisplayTraceback('exception on startup')
sys.exit()
+ ## WX menu and event handling
+
def on_timer(self, event):
self.frame.Show(False)
self.perhaps_automount()
- def perhaps_automount(self):
- if self.config['auto-mount']:
- mountpoint = os.path.expanduser('~/.tahoe/mnt/__auto__')
- if not os.path.isdir(mountpoint):
- os.makedirs(mountpoint)
- timeout = self.config['daemon-timeout']
- mount_filesystem(self.app.basedir, 'tahoe', mountpoint, 'Allmydata', timeout)
- if self.config['auto-open']:
- assert os.path.isdir(mountpoint)
- wx.FutureCall(2048, open_finder, mountpoint)
-
def setup_dock_icon(self):
self.tbicon = wx.TaskBarIcon()
#self.tbicon.SetIcon(amdicon.getIcon(), "Allmydata")
file_menu = wx.Menu()
if self.config['show-webopen']:
webopen_menu = wx.Menu()
- # XXX should promote to mac app inst var
- aliases = get_aliases(self.app.basedir)
self.webopen_menu_ids = {}
- for alias in aliases:
+ for alias in self.aliases:
mid = wx.NewId()
self.webopen_menu_ids[mid] = alias
item = webopen_menu.Append(mid, alias)
frame.Bind(wx.EVT_MENU, self.on_webopen, item)
- #log.msg('menu ids: %r' % (self.webopen_menu_ids,))
file_menu.AppendMenu(WEBOPEN_ID, 'Open Web UI', webopen_menu)
- self.aliases = get_aliases(self.app.basedir)
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')
self.frame.Show(True)
def on_quit(self, event):
- #XXX unmount mounted (automounted!) filesystems
+ self.unmount_filesystems()
self.ExitMainLoop()
def on_webopen(self, event):
alias = self.webopen_menu_ids.get(event.GetId())
#log.msg('on_webopen() alias=%r' % (alias,))
- self.app.webopen(alias)
+ self.webopen(alias)
def on_account_page(self, event):
webbrowser.open(DEFAULT_SERVER_URL + ACCOUNT_PAGE)
def on_mount(self, event):
self.mount_frame.Show(True)
+ ## Gui App methods
+
+ def perhaps_automount(self):
+ if self.config['auto-mount']:
+ mountpoint = os.path.join(self.nodedir, 'mnt/__auto__')
+ if not os.path.isdir(mountpoint):
+ os.makedirs(mountpoint)
+ self.mount_filesystem(self.nodedir, 'tahoe', mountpoint, 'Allmydata')
+
+ def webopen(self, alias=None):
+ log.msg('webopen: %r' % (alias,))
+ if alias is None:
+ alias = 'tahoe'
+ root_uri = self.aliases.get(alias)
+ if root_uri:
+ nodeurl = file(os.path.join(self.nodedir, 'node.url'), 'rb').read().strip()
+ if nodeurl[-1] != "/":
+ nodeurl += "/"
+ url = nodeurl + "uri/%s/" % urllib.quote(root_uri)
+ webbrowser.open(url)
+
+ def mount_filesystem(self, alias_name, mountpoint, display_name=None):
+ log.msg('mount_filesystem(%r,%r,%r)' % (alias_name, mountpoint, display_name))
+ log.msg('sys.exec = %r' % (sys.executable,))
+
+ # first determine if we can find the 'tahoe' binary (i.e. contents of .app)
+ if not sys.executable.endswith('Allmydata.app/Contents/MacOS/python'):
+ log.msg("can't find allmydata.app: sys.executable = %r" % (sys.executable,))
+ wx.MessageBox("Can't determine location of Allmydata.app")
+ return False
+ bin_path = sys.executable[:-6] + 'Allmydata'
+ log.msg('%r exists: %r' % (bin_path, os.path.exists(bin_path),))
+
+ # check mountpoint exists
+ if not os.path.exists(mountpoint):
+ log.msg('mountpoint %r does not exist' % (mountpoint,))
+ return False
+
+ # figure out options for fuse_main
+ foptions = []
+ foptions.append('-olocal') # required to display in Finder on leopard
+ foptions.append('-ofstypename=allmydata') # shown in 'get info'
+ if display_name is None:
+ display_name = alias_name
+ foptions.append('-ovolname=%s' % (display_name,))
+ timeout = self.config['daemon-timeout']
+ foptions.append('-odaemon_timeout=%d' % (timeout,))
+ icns_path = os.path.join(self.nodedir, 'private/icons', alias_name+'.icns')
+ log.msg('icns_path %r exists: %s' % (icns_path, os.path.exists(icns_path)))
+ if not os.path.exists(icns_path):
+ icns_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable),
+ '../Resources/allmydata.icns'))
+ log.msg('set icns_path=%s' % (icns_path,))
+ if os.path.exists(icns_path):
+ foptions.append('-ovolicon=%s' % (icns_path,))
+
+
+ # actually launch tahoe fuse
+ command = [bin_path, 'fuse', '--alias', alias_name] + foptions + [mountpoint]
+ #log.msg('spawning command %r' % (command,))
+ #proc = Popen(command, cwd=self.nodedir, stdout=PIPE, stderr=PIPE)
+ #log.msg('spawned process, pid %s' % (proc.pid,))
+ self.async_run_cmd(command)
+
+ # log the outcome, record the fact that we mounted this fs
+ #wx.FutureCall(4096, self.check_proc, proc, 'fuse')
+ self.mounted_filesystems[display_name] = mountpoint
+
+ # open finder, if configured to do so
+ if self.config['auto-open']:
+ wx.FutureCall(2048, self.sync_run_cmd, ['/usr/bin/open', mountpoint])
+ return True
+
+ def unmount_filesystems(self):
+ # the user may've already unmounted some, but we should ensure that
+ # anything the gui mounted gets shut down, since they all depend on
+ # the tahoe node, which is going away
+ for name, mountpoint in self.mounted_filesystems.items():
+ log.msg('unmounting %r (%s)' % (name, mountpoint))
+ self.sync_run_cmd(['/sbin/umount', mountpoint])
+
+ def sync_run_cmd(self, argv):
+ log.msg('synchronously running command: %r' % (argv,))
+ proc = Popen(argv, cwd=self.nodedir, stdout=PIPE, stderr=PIPE)
+ proc.wait()
+ self.check_proc(proc)
+
+ def async_run_cmd(self, argv):
+ log.msg('asynchronously running command: %r' % (argv,))
+ proc = Popen(argv, cwd=self.nodedir, stdout=PIPE, stderr=PIPE)
+ log.msg('spawned process, pid: %s' % (proc.pid,))
+ wx.FutureCall(4096, self.check_proc, proc, 'async fuse process:')
+
+ def check_proc(self, proc, description=None):
+ message = []
+ if description is not None:
+ message.append(description)
+ message.append('pid: %s retcode: %s' % (proc.pid, proc.returncode,))
+ stdout = proc.stdout.read()
+ if stdout:
+ message.append('\nstdout:\n%s' % (stdout,))
+ stderr = proc.stderr.read()
+ if stderr:
+ message.append('\nstdout:\n%s' % (stderr,))
+ log.msg(' '.join(message))
+