]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/gui/confwiz.py
Change relative imports to absolute
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / gui / confwiz.py
1
2 DEFAULT_SERVER_URL = 'https://www.allmydata.com/'
3
4 BACKEND = 'native_client.php'
5 ACCOUNT_PAGE = 'account'
6 WELCOME_PAGE = 'welcome_install'
7 TAHOESVC_NAME = 'Tahoe'
8 WINFUSESVC_NAME = 'Allmydata SMB'
9
10 CONVERGENCE_DOMAIN_TAG = "allmydata_root_cap_to_convergence_domain_tag_v1"
11
12 import os
13 import re
14 import socket
15 import sys
16 #import time
17 import traceback
18 import urllib2
19 from urllib import urlencode
20 import webbrowser
21 import wx
22
23 from allmydata.util.assertutil import precondition
24 from allmydata.util import hashutil, base32
25 from allmydata import uri
26 import allmydata
27 from allmydata.gui import amdlogo
28
29 from foolscap.api import Tub
30 from twisted.python import usage
31
32 class AuthError(Exception):
33     pass
34
35 def unicode_to_utf8(uobj):
36     assert precondition(isinstance(uobj, unicode))
37     return uobj.encode('utf-8')
38
39
40 def post(url, args):
41     argstr = urlencode(args)
42     conn = urllib2.urlopen(url, argstr)
43     return conn.read()
44
45 def get_root_cap(url, user, passwd):
46     args = {
47         'action': 'authenticate',
48         'email': unicode_to_utf8(user),
49         'passwd': unicode_to_utf8(passwd),
50         }
51     root_cap = post(url, args)
52     if root_cap == '0':
53         raise AuthError()
54     elif not uri.is_uri(root_cap):
55         raise ValueError('%r is not a URI' % (root_cap,))
56     else:
57         return root_cap
58
59 def create_account(url, user, passwd, subscribe):
60     args = {
61         'action': 'create_account',
62         'email': unicode_to_utf8(user),
63         'passwd': unicode_to_utf8(passwd),
64         'subscribe': subscribe and 'true' or 'false',
65         }
66     result_code = post(url, args)
67     return result_code
68
69 def record_install(url, user, passwd, nodeid, nickname):
70     args = {
71         'action': 'record_install',
72         'email': unicode_to_utf8(user),
73         'passwd': unicode_to_utf8(passwd),
74         'nodeid': nodeid,
75         'moniker': nickname,
76         }
77     result_code = post(url, args)
78     return result_code
79
80 def record_uninstall(url, nodeid):
81     args = {
82         'action': 'record_uninstall',
83         'nodeid': nodeid,
84         }
85     result_code = post(url, args)
86     return result_code
87
88 def get_introducer_furl(url):
89     return post(url, { 'action': 'getintroducerfurl' })
90
91 def get_config(url, user, passwd):
92     args = {
93         'action': 'get_config',
94         'email': unicode_to_utf8(user),
95         'passwd': unicode_to_utf8(passwd),
96         }
97     config = post(url, args)
98     return config
99
100 def get_basedir():
101     if sys.platform == 'win32':
102         from allmydata.windows import registry
103         return registry.get_base_dir_path()
104     else:
105         return os.path.expanduser('~/.tahoe')
106
107 def write_config_file(filename, contents):
108     basedir = get_basedir()
109     path = os.path.join(basedir, filename)
110     dirname = os.path.dirname(path)
111     if not os.path.exists(dirname):
112         os.makedirs(dirname)
113     iff = file(path, 'wb')
114     iff.write(contents)
115     iff.close()
116
117 def write_root_cap(root_cap):
118     write_config_file('private/root_dir.cap', root_cap+'\n')
119     convergence = base32.b2a(hashutil.tagged_hash(CONVERGENCE_DOMAIN_TAG, root_cap))
120     write_config_file('private/convergence', convergence+'\n')
121
122 def get_nodeid():
123     CERTFILE = "node.pem"
124     certfile = os.path.join(get_basedir(), "private", CERTFILE)
125     tub = Tub(certFile=certfile)
126     return tub.getTubID()
127
128 def get_nickname():
129     nick = None
130     nnfile = os.path.join(get_basedir(), 'nickname')
131     if os.path.exists(nnfile):
132         try:
133             fh = file(nnfile, 'rb')
134             nick = fh.read().strip()
135             fh.close()
136         except:
137             DisplayTraceback('Failed to read existing nickname file %s' % (nnfile,))
138     if not nick:
139         nick = socket.gethostname()
140     return nick
141
142 def maybe_write_file(filename, contents):
143     fname = os.path.join(get_basedir(), filename)
144     try:
145         if not os.path.exists(fname):
146             fh = file(fname, 'wb')
147             fh.write(contents)
148             fh.write('\n')
149             fh.close()
150     except:
151         DisplayTraceback('Failed to write file %s' % (fname,))
152
153 def configure(backend, user, passwd):
154     _config_re = re.compile('^([^:]*): (.*)$')
155     config = get_config(backend, user, passwd)
156     config_dict = {}
157     for line in config.split('\n'):
158         if line:
159             m = _config_re.match(line)
160             if m:
161                 fname, contents = m.groups()
162                 config_dict[fname] = contents
163     for fname, contents in config_dict.items():
164         write_config_file(fname, contents+'\n')
165
166 def start_windows_service(svc_name):
167     try:
168         import win32service
169         import win32serviceutil as wsu
170         if wsu.QueryServiceStatus(svc_name)[1] != win32service.SERVICE_RUNNING:
171             wsu.StartService(svc_name)
172     except:
173         DisplayTraceback('Failed to start windows service "%s"' % (svc_name,))
174
175 def maybe_start_services():
176     if sys.platform == 'win32':
177         start_windows_service(TAHOESVC_NAME)
178         start_windows_service(WINFUSESVC_NAME)
179
180 def DisplayTraceback(message):
181     xc = traceback.format_exception(*sys.exc_info())
182     wx.MessageBox(u"%s\n (%s)"%(message,''.join(xc)), 'Error')
183
184 class ConfWizApp(wx.App):
185     def __init__(self, server, open_welcome_page=False):
186         self.server = server
187         self.show_welcome = open_welcome_page
188         wx.App.__init__(self, 0)
189
190     def get_backend(self):
191         return self.server + BACKEND
192
193     def open_welcome_page(self):
194         if self.show_welcome:
195             args = {'v':    str(allmydata.__version__),
196                     'plat': sys.platform,
197                     }
198             webbrowser.open(self.server + WELCOME_PAGE + '?' + urlencode(args))
199
200     def OnInit(self):
201         try:
202             wx.InitAllImageHandlers()
203
204             self.login_frame = WizardFrame(self, LoginPanel)
205             self.login_frame.CenterOnScreen()
206             self.SetTopWindow(self.login_frame)
207             #self.SetExitOnFrameDelete(True)
208             self.login_frame.Show(True)
209
210             return True
211         except:
212             DisplayTraceback('config wizard init threw an exception')
213
214     def swap_to_register_frame(self):
215         try:
216             self.login_frame.Show(False)
217             self.regiser_frame = WizardFrame(self, RegisterPanel)
218             self.regiser_frame.CenterOnScreen()
219             self.SetTopWindow(self.regiser_frame)
220             self.SetExitOnFrameDelete(True)
221             self.regiser_frame.Show(True)
222         except:
223             DisplayTraceback('config wizard threw an exception')
224
225 class WizardFrame(wx.Frame):
226     def __init__(self, app, panel_class):
227         #title = 'Allmydata Config Wizard'
228         title = 'Setup - Allmydata'
229         wx.Frame.__init__(self, None, -1, title)
230         self.app = app
231         self.SetIcon(amdlogo.getIcon())
232         self.Bind(wx.EVT_CLOSE, self.close)
233
234         self.SetSizeHints(500, 360, 600, 800)
235
236         banner = wx.Panel(self, -1)
237         banner.SetSize((496,58))
238         banner.SetBackgroundColour(wx.WHITE)
239
240         banner_title = wx.StaticText(banner, -1, panel_class.title)
241         banner_desc = wx.StaticText(banner, -1, "        " + panel_class.description)
242         font = banner_title.GetFont()
243         font.SetWeight(wx.FONTWEIGHT_BOLD)
244         banner_title.SetFont(font)
245         banner_icon = wx.StaticBitmap(banner, -1, amdlogo.getBitmap())
246         banner_label_sizer = wx.BoxSizer(wx.VERTICAL)
247         banner_label_sizer.Add(banner_title, 0, wx.EXPAND | wx.ALL, 2)
248         banner_label_sizer.Add(banner_desc, 0, wx.EXPAND | wx.ALL, 2)
249
250         banner_sizer = wx.BoxSizer(wx.HORIZONTAL)
251         banner_sizer.Add(banner_label_sizer, 1, wx.EXPAND | wx.ALL, 12)
252         banner_sizer.Add(banner_icon, 0, wx.ALL, 12)
253         banner.SetSizer(banner_sizer)
254         banner.SetAutoLayout(True)
255
256         background = wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
257         background.parent = self
258
259         button_panel = wx.Panel(self, -1)
260         button_panel.SetSize((496, 64))
261
262         self.panel = panel_class(background, button_panel, app)
263         sizer = wx.BoxSizer(wx.VERTICAL)
264         background_sizer = wx.BoxSizer(wx.VERTICAL)
265         background_sizer.Add(wx.Size(2,2), 0, wx.EXPAND | wx.ALL, self.panel.padding)
266         background_sizer.Add(self.panel, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 26)
267         background_sizer.Add(wx.Size(2,2), 0, wx.EXPAND | wx.ALL, self.panel.padding)
268         background.SetSizer(background_sizer)
269
270
271         sizer.Add(banner, 0, wx.EXPAND | wx.HORIZONTAL, 0)
272         sizer.Add(background, 0, wx.EXPAND | wx.ALL, 0)
273         sizer.Add(button_panel, 0, wx.EXPAND | wx.HORIZONTAL, 0)
274         self.SetSizer(sizer)
275         self.SetAutoLayout(True)
276         self.Fit()
277         self.Layout()
278
279     def close(self, event):
280         self.Show(False)
281         self.app.ExitMainLoop()
282
283
284 class LoginPanel(wx.Panel):
285     padding = 26
286     title = 'Sign in'
287     description = 'Sign in to your existing account'
288
289     def __init__(self, parent, button_panel, app):
290         wx.Panel.__init__(self, parent, -1)
291         self.parent = parent
292         self.app = app
293
294         self.sizer = wx.BoxSizer(wx.VERTICAL)
295
296         self.user_label = wx.StaticText(self, -1, 'Email')
297         self.pass_label = wx.StaticText(self, -1, 'Password')
298         self.user_field = wx.TextCtrl(self, -1, u'', size=(260,-1))
299         self.pass_field = wx.TextCtrl(self, -1, u'', size=(260,-1), style=wx.TE_PASSWORD)
300         self.warning_label = wx.StaticText(self, -1, '')
301         self.warning_label.SetOwnForegroundColour(wx.RED)
302         wx.EVT_CHAR(self.user_field, self.on_user_entry)
303         wx.EVT_CHAR(self.pass_field, self.on_pass_entry)
304         login_sizer = wx.FlexGridSizer(2, 2, 5, 4)
305         login_sizer.Add(self.user_label, 0, wx.ALIGN_RIGHT | wx.ALL, 2)
306         login_sizer.Add(self.user_field, 0, wx.EXPAND | wx.ALL, 2)
307         login_sizer.Add(self.pass_label, 0, wx.ALIGN_RIGHT | wx.ALL, 2)
308         login_sizer.Add(self.pass_field, 0, wx.EXPAND | wx.ALL, 2)
309         self.sizer.Add(login_sizer, 1, wx.EXPAND | wx.ALL, 2)
310         self.sizer.Add(self.warning_label, 0, wx.CENTER | wx.ALL, 2)
311         self.SetSizer(self.sizer)
312         self.SetAutoLayout(True)
313
314         self.reg_label = wx.StaticText(button_panel, -1, "Don't have an account?")
315         self.reg_button = wx.Button(button_panel, -1, 'Create Account')
316         self.login_button = wx.Button(button_panel, -1, 'Sign In')
317         button_panel.Bind(wx.EVT_BUTTON, self.on_reg_button, self.reg_button)
318         button_panel.Bind(wx.EVT_BUTTON, self.on_login, self.login_button)
319         btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
320         btn_sizer.Add(wx.Size(2,2), 1, wx.EXPAND | wx.ALL, 12)
321         btn_sizer.Add(self.reg_label, 0, wx.ALIGN_RIGHT | wx.ALL, 12)
322         btn_sizer.Add(self.reg_button, 0, wx.ALIGN_RIGHT | wx.ALL, 12)
323         btn_sizer.Add(self.login_button, 0, wx.ALIGN_RIGHT | wx.ALL, 12)
324         button_panel.SetSizer(btn_sizer)
325         self.button_panel = button_panel
326
327     def on_reg_button(self, event):
328         self.app.swap_to_register_frame()
329
330     def on_user_entry(self, event):
331         if event.GetKeyCode() == wx.WXK_RETURN:
332             self.pass_field.SetFocus()
333         else:
334             event.Skip()
335
336     def on_pass_entry(self, event):
337         if event.GetKeyCode() == wx.WXK_RETURN:
338             self.on_login(event)
339         else:
340             event.Skip()
341
342     def on_login(self, event):
343         user = self.user_field.GetValue()
344         passwd = self.pass_field.GetValue()
345         self.warning_label.SetLabel('Connecting...')
346         self.Layout()
347         wx.Yield()
348
349         backend = self.app.get_backend()
350
351         if passwd == '':
352             self.warning_label.SetLabel('You must enter a password')
353             self.pass_field.SetFocus()
354             self.Layout()
355             return
356
357         try:
358             root_cap = get_root_cap(backend, user, passwd)
359             write_root_cap(root_cap)
360         except AuthError:
361             self.warning_label.SetLabel('Your email and/or password is incorrect')
362             self.user_field.SetFocus()
363             self.Layout()
364             return
365
366         nodeid = get_nodeid()
367         nickname = get_nickname()
368         ret = record_install(backend, user, passwd, nodeid, nickname)
369         if ret != 'ok':
370             wx.MessageBox('Error "%s" recording this system (%s)' % (ret, nodeid), 'Error')
371
372         configure(backend, user, passwd)
373         maybe_start_services()
374         maybe_write_file('nickname', nickname)
375         maybe_write_file('accountname', user)
376
377         self.app.open_welcome_page()
378
379         # exit
380         self.parent.parent.Close()
381
382 class RegisterPanel(wx.Panel):
383     padding = 7
384     title = 'Create account'
385     description = 'Create a new account on Allmydata'
386
387     def __init__(self, parent, button_panel, app):
388         wx.Panel.__init__(self, parent, -1)
389         self.parent = parent
390         self.app = app
391
392         self.sizer = wx.BoxSizer(wx.VERTICAL)
393
394         self.user_label = wx.StaticText(self, -1, 'Email')
395         self.pass_label = wx.StaticText(self, -1, 'Password')
396         self.conf_label = wx.StaticText(self, -1, 'Confirm Password')
397         self.user_field = wx.TextCtrl(self, -1, u'', size=(260,-1))
398         self.pass_field = wx.TextCtrl(self, -1, u'', size=(260,-1), style=wx.TE_PASSWORD)
399         self.conf_field = wx.TextCtrl(self, -1, u'', size=(260,-1), style=wx.TE_PASSWORD)
400         self.subscribe_box = wx.CheckBox(self, -1, 'Sign up for our Newsletter')
401         self.subscribe_box.SetValue(True)
402         self.warning_label = wx.StaticText(self, -1, '')
403         self.warning_label.SetOwnForegroundColour(wx.RED)
404         wx.EVT_CHAR(self.user_field, self.on_user_entry)
405         wx.EVT_CHAR(self.pass_field, self.on_pass_entry)
406         wx.EVT_CHAR(self.conf_field, self.on_conf_entry)
407         login_sizer = wx.FlexGridSizer(3, 2, 5, 4)
408         login_sizer.Add(self.user_label, 0, wx.ALIGN_RIGHT | wx.ALL, 2)
409         login_sizer.Add(self.user_field, 0, wx.EXPAND | wx.ALL, 2)
410         login_sizer.Add(self.pass_label, 0, wx.ALIGN_RIGHT | wx.ALL, 2)
411         login_sizer.Add(self.pass_field, 0, wx.EXPAND | wx.ALL, 2)
412         login_sizer.Add(self.conf_label, 0, wx.ALIGN_RIGHT | wx.ALL, 2)
413         login_sizer.Add(self.conf_field, 0, wx.EXPAND | wx.ALL, 2)
414         login_sizer.Add(wx.Size(2,2), 0, wx.ALIGN_RIGHT | wx.ALL, 2)
415         self.sizer.Add(login_sizer, 0, wx.EXPAND | wx.ALL, 2)
416         self.sizer.Add(self.warning_label, 0, wx.CENTER | wx.ALL, 2)
417         self.sizer.Add(wx.Size(2,2), 0, wx.EXPAND | wx.ALL, 4)
418         self.sizer.Add(self.subscribe_box, 0, wx.CENTER | wx.ALL, 2)
419         self.SetSizer(self.sizer)
420         self.SetAutoLayout(True)
421
422         self.reg_button = wx.Button(button_panel, -1, 'Create Account')
423         button_panel.Bind(wx.EVT_BUTTON, self.on_create_account, self.reg_button)
424         btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
425         btn_sizer.Add(wx.Size(2,2), 1, wx.EXPAND | wx.ALL, 12)
426         btn_sizer.Add(self.reg_button, 0, wx.ALIGN_RIGHT | wx.ALL, 12)
427         button_panel.SetSizer(btn_sizer)
428         self.button_panel = button_panel
429
430         self.Fit()
431
432     def on_user_entry(self, event):
433         if event.GetKeyCode() == wx.WXK_RETURN:
434             self.pass_field.SetFocus()
435         else:
436             event.Skip()
437
438     def on_pass_entry(self, event):
439         if event.GetKeyCode() == wx.WXK_RETURN:
440             self.conf_field.SetFocus()
441         else:
442             event.Skip()
443
444     def on_conf_entry(self, event):
445         if event.GetKeyCode() == wx.WXK_RETURN:
446             self.on_create_account(event)
447         else:
448             event.Skip()
449
450     def on_create_account(self, event):
451         user = self.user_field.GetValue()
452         passwd = self.pass_field.GetValue()
453         pconf = self.conf_field.GetValue()
454         subscribe = self.subscribe_box.IsChecked()
455         self.warning_label.SetLabel('Connecting...')
456         self.Layout()
457         wx.Yield()
458
459         if passwd == '':
460             self.warning_label.SetLabel('You must enter a password')
461             self.pass_field.SetFocus()
462             self.Layout()
463             return
464
465         if passwd != pconf:
466             self.warning_label.SetLabel("Passwords don't match")
467             self.pass_field.SetValue('')
468             self.conf_field.SetValue('')
469             self.pass_field.SetFocus()
470             self.Layout()
471             return
472
473         backend = self.app.get_backend()
474
475         #print 'calling create_account', time.asctime()
476         result_code = create_account(backend, user, passwd, subscribe)
477
478         if result_code == 'account_exists':
479             # try and log into it; if valid, use it anyway
480             try:
481                 #print 'calling get_root_cap (ae)', time.asctime()
482                 root_cap = get_root_cap(backend, user, passwd)
483                 write_root_cap(root_cap)
484             except AuthError:
485                 self.warning_label.SetLabel('That email address is already registered')
486                 self.user_field.SetFocus()
487                 self.Layout()
488                 return
489         elif result_code == 'error':
490             self.warning_label.SetLabel('an error occurred')
491             self.user_field.SetFocus()
492             self.Layout()
493             return
494         elif result_code == 'ok':
495             #print 'calling get_root_cap (ok)', time.asctime()
496             root_cap = get_root_cap(backend, user, passwd)
497             write_root_cap(root_cap)
498         else:
499             self.warning_label.SetLabel('an unexpected error occurred ("%s")' % (result_code,))
500             self.user_field.SetFocus()
501             self.Layout()
502             return
503
504         nodeid = get_nodeid()
505         nickname = get_nickname()
506         ret = record_install(backend, user, passwd, nodeid, nickname)
507         if ret != 'ok':
508             wx.MessageBox('Error "%s" recording this system (%s)' % (ret, nodeid), 'Error')
509
510         configure(backend, user, passwd)
511         maybe_start_services()
512         maybe_write_file('nickname', nickname)
513         maybe_write_file('accountname', user)
514
515         self.app.open_welcome_page()
516
517         # exit
518         self.parent.parent.Close()
519
520 def do_uninstall(server_url):
521     nodeid = get_nodeid()
522     ret = record_uninstall(server_url + BACKEND, nodeid)
523     print ret
524     if ret != 'ok':
525         print 'Error "%s" recording uninstall of this system (%s)' % (ret, nodeid)
526
527 class Options(usage.Options):
528     synopsis = "Usage:  confwiz [options]"
529
530     optFlags = [
531         ['uninstall', 'u', 'record uninstall'],
532         ]
533     optParameters = [
534         ['server', 's', DEFAULT_SERVER_URL, 'url of server to contact'],
535         ]
536
537 def main(argv):
538     config = Options()
539     try:
540         config.parseOptions(argv[1:])
541     except usage.error, e:
542         print config
543         print "%s:  %s" % (sys.argv[0], e)
544         sys.exit(-1)
545
546     server = config['server']
547     if not server.endswith('/'):
548         server += '/'
549
550     if config['uninstall']:
551         do_uninstall(server)
552     else:
553         app = ConfWizApp(server)
554         app.MainLoop()
555
556
557 if __name__ == '__main__':
558     main(sys.argv)