4 https://github.com/micahflee/torbrowser-launcher/
6 Copyright (c) 2013 Micah Lee <micahflee@riseup.net>
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
31 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
33 from twisted.internet import gtk2reactor
35 from twisted.internet import reactor
41 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
43 from twisted.web.client import Agent, ResponseDone
44 from twisted.web.http_headers import Headers
45 from twisted.internet.protocol import Protocol
46 from twisted.internet.ssl import ClientContextFactory
48 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
49 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
51 class VerifyTorProjectCert(ClientContextFactory):
53 def __init__(self, torproject_pem):
54 self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
56 def getContext(self, host, port):
57 ctx = ClientContextFactory.getContext(self)
58 ctx.set_verify_depth(0)
59 ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
62 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
63 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
67 def __init__(self, tbl_version):
68 print _('Initializing Tor Browser Launcher')
69 self.tbl_version = tbl_version
72 self.discover_arch_lang()
74 self.mkdir(self.paths['data_dir'])
76 self.mkdir(self.paths['download_dir'])
77 self.mkdir(self.paths['tbb'][self.settings['preferred']]['dir'])
80 self.available_versions = {
81 'stable': _('Tor Browser Bundle - stable'),
82 'alpha': _('Tor Browser Bundle - alpha')
83 # commenting out obs bundle until I have a reliable way to find latest version. related bugs:
84 # https://trac.torproject.org/projects/tor/ticket/8644
85 # https://trac.torproject.org/projects/tor/ticket/8645
86 #'obs': _('Obsfproxy Tor Browser Bundle')
89 # allow buttons to have icons
91 gtk_settings = gtk.settings_get_default()
92 gtk_settings.props.gtk_button_images = True
96 # discover the architecture and language
97 def discover_arch_lang(self):
98 # figure out the architecture
99 (sysname, nodename, release, version, machine) = os.uname()
100 self.architecture = machine
102 # figure out the language
103 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
104 default_locale = locale.getdefaultlocale()[0]
105 if default_locale == None:
106 self.language = 'en-US'
108 self.language = default_locale.replace('_', '-')
109 if self.language not in available_languages:
110 self.language = self.language.split('-')[0]
111 if self.language not in available_languages:
112 for l in available_languages:
113 if l[0:2] == self.language:
115 # if language isn't available, default to english
116 if self.language not in available_languages:
117 self.language = 'en-US'
119 # build all relevant paths
120 def build_paths(self, tbb_version = None):
121 homedir = os.getenv('HOME')
123 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
124 if os.path.exists(homedir) == False:
126 os.mkdir(homedir, 0700)
128 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
129 if not os.access(homedir, os.W_OK):
130 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
132 tbb_data = '%s/.torbrowser' % homedir
135 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
136 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
137 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
138 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
139 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
140 self.paths['tarball_filename'] = tarball_filename
141 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
145 'tbl_bin': '/usr/bin/torbrowser-launcher',
146 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
147 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
148 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
149 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
150 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
151 'data_dir': tbb_data,
152 'download_dir': tbb_data+'/download',
153 'gnupg_homedir': tbb_data+'/gnupg_homedir',
154 'settings_file': tbb_data+'/settings',
155 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
156 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
159 'dir': tbb_data+'/tbb/stable/'+self.architecture,
160 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
161 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
162 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
163 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
166 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
167 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
168 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
169 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
170 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
173 'dir': tbb_data+'/tbb/obs/'+self.architecture,
174 'start': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
175 'vidalia_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
176 'firefox_bin': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
177 'firefox_profile': tbb_data+'/tbb/obs/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
183 def mkdir(self, path):
185 if os.path.exists(path) == False:
186 os.makedirs(path, 0700)
189 print _("Cannot create directory {0}").format(path)
191 if not os.access(path, os.W_OK):
192 print _("{0} is not writable").format(path)
196 # if gnupg_homedir isn't set up, set it up
197 def init_gnupg(self):
198 if not os.path.exists(self.paths['gnupg_homedir']):
199 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
200 if self.mkdir(self.paths['gnupg_homedir']):
202 print _('Importing keys')
203 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
204 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['sebastian_key']]).wait()
205 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['alexandre_key']]).wait()
208 def load_settings(self):
210 'tbl_version': self.tbl_version,
211 'preferred': 'stable',
212 'installed_version': {
222 'update_over_tor': True,
223 'check_for_updates': False,
224 'last_update_check_timestamp': 0
227 if os.path.isfile(self.paths['settings_file']):
228 settings = pickle.load(open(self.paths['settings_file']))
230 # what version settings is this?
231 if not 'tbl_version' in settings:
232 settings['tbl_version'] = '0.0.1'
234 # sanity checks for current version
235 if settings['tbl_version'] == self.tbl_version:
237 if not 'preferred' in settings:
238 good_settings = False
239 if not 'installed_version' in settings:
240 good_settings = False
241 if not 'stable' in settings['installed_version']:
242 good_settings = False
243 if not 'alpha' in settings['installed_version']:
244 good_settings = False
245 if not 'obs' in settings['installed_version']:
246 good_settings = False
247 if not 'latest_version' in settings:
248 good_settings = False
249 if not 'stable' in settings['latest_version']:
250 good_settings = False
251 if not 'alpha' in settings['latest_version']:
252 good_settings = False
253 if not 'obs' in settings['latest_version']:
254 good_settings = False
255 if not 'update_over_tor' in settings:
256 good_settings = False
257 if not 'check_for_updates' in settings:
258 good_settings = False
259 if not 'last_update_check_timestamp' in settings:
260 good_settings = False
263 self.settings = settings
265 self.settings = default_settings
267 # settings migrations for previous versions
268 elif settings['tbl_version'] == '0.0.1':
269 self.settings = default_settings
270 self.settings['installed_version']['alpha'] = settings['installed_version']
274 self.mkdir(self.paths['tbb']['alpha']['dir'])
275 # todo: move already-installed TBB alpha to new location
276 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
278 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
282 self.settings = default_settings
286 def save_settings(self):
287 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
290 # get the process id of a program
291 def get_pid(self, bin_path, python = False):
294 for p in psutil.process_iter():
296 if p.pid != os.getpid():
299 if len(p.cmdline) > 1:
300 if 'python' in p.cmdline[0]:
303 if len(p.cmdline) > 0:
314 # bring program's x window to front
315 def bring_window_to_front(self, pid):
316 # figure out the window id
318 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
319 for line in p.stdout.readlines():
320 line_split = line.split()
321 cur_win_id = line_split[0]
322 cur_win_pid = int(line_split[2])
323 if cur_win_pid == pid:
328 subprocess.call(['wmctrl', '-i', '-a', win_id])
331 def __init__(self, common):
332 print _('Starting settings dialog')
336 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
337 self.window.set_title(_("Tor Browser Launcher Settings"))
338 self.window.set_icon_from_file(self.common.paths['icon_file'])
339 self.window.set_position(gtk.WIN_POS_CENTER)
340 self.window.set_border_width(10)
341 self.window.connect("delete_event", self.delete_event)
342 self.window.connect("destroy", self.destroy)
344 # build the rest of the UI
345 self.box = gtk.VBox(False, 10)
346 self.window.add(self.box)
349 self.hbox = gtk.HBox(False, 10)
350 self.box.pack_start(self.hbox, True, True, 0)
353 self.settings_box = gtk.VBox(False, 10)
354 self.hbox.pack_start(self.settings_box, True, True, 0)
355 self.settings_box.show()
357 self.labels_box = gtk.VBox(False, 10)
358 self.hbox.pack_start(self.labels_box, True, True, 0)
359 self.labels_box.show()
362 self.pref_ver_box = gtk.HBox(False, 10)
363 self.settings_box.pack_start(self.pref_ver_box, True, True, 0)
364 self.pref_ver_box.show()
366 self.pref_ver_label = gtk.Label(_('I prefer'))
367 self.pref_ver_label.set_line_wrap(True)
368 self.pref_ver_box.pack_start(self.pref_ver_label, True, True, 0)
369 self.pref_ver_label.show()
372 for i in self.common.available_versions:
373 options.append(self.common.available_versions[i])
376 self.pref_ver = gtk.combo_box_new_text()
377 for option in options:
378 self.pref_ver.append_text(option)
379 self.pref_ver.set_active(0)
380 self.pref_ver_box.pack_start(self.pref_ver, True, True, 0)
384 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
385 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
386 self.tor_update_checkbox.show()
389 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
390 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
391 self.update_checkbox.show()
394 if(self.common.settings['installed_version']):
395 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
397 self.label1 = gtk.Label(_('Not installed'))
398 self.label1.set_line_wrap(True)
399 self.labels_box.pack_start(self.label1, True, True, 0)
402 if(self.common.settings['last_update_check_timestamp'] > 0):
403 self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
405 self.label1 = gtk.Label(_('Never checked for updates'))
406 self.label1.set_line_wrap(True)
407 self.labels_box.pack_start(self.label1, True, True, 0)
411 self.button_box = gtk.HButtonBox()
412 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
413 self.box.pack_start(self.button_box, True, True, 0)
414 self.button_box.show()
416 # save and launch button
417 save_launch_image = gtk.Image()
418 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
419 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
420 self.save_launch_button.set_image(save_launch_image)
421 self.save_launch_button.connect("clicked", self.save_launch, None)
422 self.button_box.add(self.save_launch_button)
423 self.save_launch_button.show()
425 # save and exit button
426 save_exit_image = gtk.Image()
427 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
428 self.save_exit_button = gtk.Button(_("Save & Exit"))
429 self.save_exit_button.set_image(save_exit_image)
430 self.save_exit_button.connect("clicked", self.save_exit, None)
431 self.button_box.add(self.save_exit_button)
432 self.save_exit_button.show()
435 cancel_image = gtk.Image()
436 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
437 self.cancel_button = gtk.Button(_("Cancel"))
438 self.cancel_button.set_image(cancel_image)
439 self.cancel_button.connect("clicked", self.destroy, None)
440 self.button_box.add(self.cancel_button)
441 self.cancel_button.show()
450 def save_launch(self, widget, data=None):
452 p = subprocess.Popen([self.common.paths['tbl_bin']])
456 def save_exit(self, widget, data=None):
465 def delete_event(self, widget, event, data=None):
467 def destroy(self, widget, data=None):
472 def __init__(self, common):
473 print _('Starting launcher dialog')
477 self.set_gui(None, '', [])
478 self.launch_gui = True
479 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
481 # is vidalia already running and we just need to open a new firefox?
482 if self.common.settings['installed_version']:
483 vidalia_pid = self.common.get_pid('./App/vidalia')
484 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
486 if vidalia_pid and not firefox_pid:
487 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
488 self.common.bring_window_to_front(vidalia_pid)
489 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'], '-no-remote', '-profile', self.common.paths['tbb'][self.common.settings['preferred']]['firefox_profile']])
491 elif vidalia_pid and firefox_pid:
492 print _('Vidalia and Firefox are already open, bringing them to focus')
494 # bring firefox to front, then vidalia
495 self.common.bring_window_to_front(firefox_pid)
496 self.common.bring_window_to_front(vidalia_pid)
500 check_for_updates = False
501 if self.common.settings['check_for_updates']:
502 check_for_updates = True
504 if not check_for_updates:
505 # how long was it since the last update check?
506 # 86400 seconds = 24 hours
507 current_timestamp = int(time.time())
508 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
509 check_for_updates = True
511 if check_for_updates:
513 print 'Checking for update'
514 self.set_gui('task', _("Checking for Tor Browser update."),
515 ['download_update_check',
518 # no need to check for update
519 print _('Checked for update within 24 hours, skipping')
520 self.start_launcher()
524 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
525 self.window.set_title(_("Tor Browser"))
526 self.window.set_icon_from_file(self.common.paths['icon_file'])
527 self.window.set_position(gtk.WIN_POS_CENTER)
528 self.window.set_border_width(10)
529 self.window.connect("delete_event", self.delete_event)
530 self.window.connect("destroy", self.destroy)
532 # build the rest of the UI
535 # download or run TBB
536 def start_launcher(self):
537 # is TBB already installed?
538 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
539 if os.path.isfile(start) and os.access(start, os.X_OK):
540 if self.common.settings['installed_version'][self.common.settings['preferred']] == self.common.settings['latest_version'][self.common.settings['preferred']]:
541 # current version of tbb is installed, launch it
543 self.launch_gui = False
544 elif self.common.settings['installed_version'][self.common.settings['preferred']] < self.common.settings['latest_version'][self.common.settings['preferred']]:
545 # there is a tbb upgrade available
546 self.set_gui('task', _("Your Tor Browser is out of date."),
548 'download_tarball_sig',
553 # for some reason the installed tbb is newer than the current version?
554 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
558 # are the tarball and sig already downloaded?
559 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
560 # start the gui with verify
561 self.set_gui('task', _("Installing Tor Browser."),
568 self.set_gui('task', _("Downloading and installing Tor Browser."),
570 'download_tarball_sig',
575 # there are different GUIs that might appear, this sets which one we want
576 def set_gui(self, gui, message, tasks, autostart=True):
578 self.gui_message = message
579 self.gui_tasks = tasks
581 self.gui_autostart = autostart
583 # set all gtk variables to False
585 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
590 self.progressbar = False
591 self.button_box = False
592 self.start_button = False
593 self.exit_button = False
595 # build the application's UI
599 self.box = gtk.VBox(False, 20)
600 self.window.add(self.box)
602 if self.gui == 'error':
604 self.label = gtk.Label( self.gui_message )
605 self.label.set_line_wrap(True)
606 self.box.pack_start(self.label, True, True, 0)
610 exit_image = gtk.Image()
611 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
612 self.exit_button = gtk.Button("Exit")
613 self.exit_button.set_image(exit_image)
614 self.exit_button.connect("clicked", self.destroy, None)
615 self.box.add(self.exit_button)
616 self.exit_button.show()
618 elif self.gui == 'task':
620 self.label = gtk.Label( self.gui_message )
621 self.label.set_line_wrap(True)
622 self.box.pack_start(self.label, True, True, 0)
626 self.progressbar = gtk.ProgressBar(adjustment=None)
627 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
628 self.progressbar.set_pulse_step(0.01)
629 self.box.pack_start(self.progressbar, True, True, 0)
632 self.button_box = gtk.HButtonBox()
633 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
634 self.box.pack_start(self.button_box, True, True, 0)
635 self.button_box.show()
638 start_image = gtk.Image()
639 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
640 self.start_button = gtk.Button(_("Start"))
641 self.start_button.set_image(start_image)
642 self.start_button.connect("clicked", self.start, None)
643 self.button_box.add(self.start_button)
644 if not self.gui_autostart:
645 self.start_button.show()
648 exit_image = gtk.Image()
649 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
650 self.exit_button = gtk.Button(_("Exit"))
651 self.exit_button.set_image(exit_image)
652 self.exit_button.connect("clicked", self.destroy, None)
653 self.button_box.add(self.exit_button)
654 self.exit_button.show()
659 if self.gui_autostart:
662 # start button clicked, begin tasks
663 def start(self, widget, data=None):
664 # disable the start button
665 if self.start_button:
666 self.start_button.set_sensitive(False)
668 # start running tasks
671 # run the next task in the task list
675 if self.gui_task_i >= len(self.gui_tasks):
679 task = self.gui_tasks[self.gui_task_i]
681 # get ready for the next task
684 if task == 'download_update_check':
685 print _('Downloading'), self.common.paths['update_check_url']
686 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
688 if task == 'attempt_update':
689 print _('Checking to see if update it needed')
690 self.attempt_update()
692 elif task == 'download_tarball':
693 print _('Downloading'), self.common.paths['tarball_url']
694 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
696 elif task == 'download_tarball_sig':
697 print _('Downloading'), self.common.paths['tarball_sig_url']
698 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
700 elif task == 'verify':
701 print _('Verifying signature')
704 elif task == 'extract':
705 print _('Extracting'), self.common.paths['tarball_filename']
709 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
712 elif task == 'start_over':
713 print _('Starting download over again')
716 def response_received(self, response):
717 class FileDownloader(Protocol):
718 def __init__(self, file, total, progress, done_cb):
722 self.progress = progress
723 self.all_done = done_cb
725 def dataReceived(self, bytes):
726 self.file.write(bytes)
727 self.so_far += len(bytes)
728 percent = float(self.so_far) / float(self.total)
729 self.progress.set_fraction(percent)
730 amount = float(self.so_far)
732 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
735 amount = amount / float(size)
738 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
740 def connectionLost(self, reason):
741 print _('Finished receiving body:'), reason.getErrorMessage()
742 self.all_done(reason)
744 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
745 response.deliverBody(dl)
747 def response_finished(self, msg):
748 if msg.check(ResponseDone):
749 self.file_download.close()
754 print "FINISHED", msg
755 ## FIXME handle errors
757 def download_error(self, f):
758 print _("Download error"), f
759 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
763 def download(self, name, url, path):
764 # initialize the progress bar
765 self.progressbar.set_fraction(0)
766 self.progressbar.set_text(_('Downloading {0}').format(name))
767 self.progressbar.show()
770 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
771 d = agent.request('GET', url,
772 Headers({'User-Agent': ['torbrowser-launcher']}),
775 self.file_download = open(path, 'w')
776 d.addCallback(self.response_received).addErrback(self.download_error)
778 if not reactor.running:
781 def attempt_update(self):
782 # load the update check file
784 versions = json.load(open(self.common.paths['update_check_file']))
789 # filter linux versions
791 for version in versions:
792 if str(version).find('-Linux') != -1:
793 valid_versions.append(str(version))
795 for version in valid_versions:
796 if version.find('alpha') != -1:
797 latest_alpha = version
798 valid_versions.remove(latest_alpha)
799 # find stable (whatever is left after alpha)
800 if len(valid_versions):
801 latest_stable = valid_versions[0]
803 if latest_stable or latest_alpha or latest_obs:
805 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
807 self.common.settings['latest_version']['alpha'] = latest_stable[:-len('-Linux')]
809 self.common.settings['latest_version']['obs'] = latest_stable[:-len('-Linux')]
810 self.common.settings['last_update_check_timestamp'] = int(time.time())
811 self.common.settings['check_for_updates'] = False
812 self.common.save_settings()
813 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
814 self.start_launcher()
817 # failed to find the latest version
818 self.set_gui('error', _("Error checking for updates."), [], False)
821 # not a valid JSON object
822 self.set_gui('error', _("Error checking for updates."), [], False)
829 # initialize the progress bar
830 self.progressbar.set_fraction(0)
831 self.progressbar.set_text(_('Verifying Signature'))
832 self.progressbar.show()
834 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
835 self.pulse_until_process_exits(p)
837 if p.returncode == 0:
840 self.set_gui('task', _("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again."), ['start_over'], False)
844 if not reactor.running:
848 # initialize the progress bar
849 self.progressbar.set_fraction(0)
850 self.progressbar.set_text(_('Installing'))
851 self.progressbar.show()
854 # make sure this file is a tarfile
855 if tarfile.is_tarfile(self.common.paths['tarball_file']):
856 tf = tarfile.open(self.common.paths['tarball_file'])
857 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
859 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
863 # installation is finished, so save installed_version
864 self.common.settings['installed_version'] = self.common.settings['latest_version']
865 self.common.save_settings()
869 def run(self, run_next_task = True):
870 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
874 # make the progress bar pulse until process p (a Popen object) finishes
875 def pulse_until_process_exits(self, p):
876 while p.poll() == None:
878 self.progressbar.pulse()
881 # start over and download TBB again
882 def start_over(self):
883 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
884 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
889 def refresh_gtk(self):
890 while gtk.events_pending():
891 gtk.main_iteration(False)
894 def delete_event(self, widget, event, data=None):
896 def destroy(self, widget, data=None):
897 if hasattr(self, 'file_download'):
898 self.file_download.close()
902 if __name__ == "__main__":
903 tor_browser_launcher_version = '0.0.2'
905 print _('Tor Browser Launcher')
906 print _('By Micah Lee, licensed under GPLv3')
907 print _('version {0}').format(tor_browser_launcher_version)
908 print 'https://github.com/micahflee/torbrowser-launcher'
910 common = TBLCommon(tor_browser_launcher_version)
912 # is torbrowser-launcher already running?
913 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
915 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
916 common.bring_window_to_front(tbl_pid)
919 if '-settings' in sys.argv:
921 app = TBLSettings(common)
925 app = TBLLauncher(common)