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