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')
85 # allow buttons to have icons
87 gtk_settings = gtk.settings_get_default()
88 gtk_settings.props.gtk_button_images = True
92 # discover the architecture and language
93 def discover_arch_lang(self):
94 # figure out the architecture
95 (sysname, nodename, release, version, machine) = os.uname()
96 self.architecture = machine
98 # figure out the language
99 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
100 default_locale = locale.getdefaultlocale()[0]
101 if default_locale == None:
102 self.language = 'en-US'
104 self.language = default_locale.replace('_', '-')
105 if self.language not in available_languages:
106 self.language = self.language.split('-')[0]
107 if self.language not in available_languages:
108 for l in available_languages:
109 if l[0:2] == self.language:
111 # if language isn't available, default to english
112 if self.language not in available_languages:
113 self.language = 'en-US'
115 # build all relevant paths
116 def build_paths(self, tbb_version = None):
117 homedir = os.getenv('HOME')
119 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
120 if os.path.exists(homedir) == False:
122 os.mkdir(homedir, 0700)
124 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
125 if not os.access(homedir, os.W_OK):
126 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
128 tbb_data = '%s/.torbrowser' % homedir
131 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
132 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
133 self.paths['tarball_sig_file'] = tbb_data+'/download/'+tarball_filename+'.asc'
134 self.paths['tarball_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
135 self.paths['tarball_sig_url'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
136 self.paths['tarball_filename'] = tarball_filename
137 self.paths['tarball_sig_filename'] = tarball_filename+'.asc'
141 'tbl_bin': '/usr/bin/torbrowser-launcher',
142 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
143 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
144 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
145 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
146 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
147 'data_dir': tbb_data,
148 'download_dir': tbb_data+'/download',
149 'gnupg_homedir': tbb_data+'/gnupg_homedir',
150 'settings_file': tbb_data+'/settings',
151 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
152 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
155 'dir': tbb_data+'/tbb/stable/'+self.architecture,
156 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
157 'vidalia_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
158 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
159 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
162 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
163 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
164 'vidalia_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
165 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
166 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
172 def mkdir(self, path):
174 if os.path.exists(path) == False:
175 os.makedirs(path, 0700)
178 print _("Cannot create directory {0}").format(path)
180 if not os.access(path, os.W_OK):
181 print _("{0} is not writable").format(path)
185 # if gnupg_homedir isn't set up, set it up
186 def init_gnupg(self):
187 if not os.path.exists(self.paths['gnupg_homedir']):
188 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
189 if self.mkdir(self.paths['gnupg_homedir']):
191 print _('Importing keys')
192 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
193 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['sebastian_key']]).wait()
194 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['alexandre_key']]).wait()
197 def load_settings(self):
199 'tbl_version': self.tbl_version,
200 'preferred': 'stable',
201 'installed_version': {
209 'update_over_tor': True,
210 'check_for_updates': False,
211 'last_update_check_timestamp': 0
214 if os.path.isfile(self.paths['settings_file']):
215 settings = pickle.load(open(self.paths['settings_file']))
217 # what version settings is this?
218 if not 'tbl_version' in settings:
219 settings['tbl_version'] = '0.0.1'
221 # sanity checks for current version
222 if settings['tbl_version'] == self.tbl_version:
224 if not 'preferred' in settings:
225 good_settings = False
226 if not 'installed_version' in settings:
227 good_settings = False
228 if not 'stable' in settings['installed_version']:
229 good_settings = False
230 if not 'alpha' in settings['installed_version']:
231 good_settings = False
232 if not 'latest_version' in settings:
233 good_settings = False
234 if not 'stable' in settings['latest_version']:
235 good_settings = False
236 if not 'alpha' in settings['latest_version']:
237 good_settings = False
238 if not 'update_over_tor' in settings:
239 good_settings = False
240 if not 'check_for_updates' in settings:
241 good_settings = False
242 if not 'last_update_check_timestamp' in settings:
243 good_settings = False
246 self.settings = settings
248 self.settings = default_settings
250 # settings migrations for previous versions
251 elif settings['tbl_version'] == '0.0.1':
252 self.settings = default_settings
253 self.settings['installed_version']['alpha'] = settings['installed_version']
257 self.mkdir(self.paths['tbb']['alpha']['dir'])
258 # todo: move already-installed TBB alpha to new location
259 if os.path.exists(self.paths['data_dir']+'/tbb/x86_64'):
261 if os.path.exists(self.paths['data_dir']+'/tbb/i686'):
265 self.settings = default_settings
269 def save_settings(self):
270 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
273 # get the process id of a program
274 def get_pid(self, bin_path, python = False):
277 for p in psutil.process_iter():
279 if p.pid != os.getpid():
282 if len(p.cmdline) > 1:
283 if 'python' in p.cmdline[0]:
286 if len(p.cmdline) > 0:
297 # bring program's x window to front
298 def bring_window_to_front(self, pid):
299 # figure out the window id
301 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
302 for line in p.stdout.readlines():
303 line_split = line.split()
304 cur_win_id = line_split[0]
305 cur_win_pid = int(line_split[2])
306 if cur_win_pid == pid:
311 subprocess.call(['wmctrl', '-i', '-a', win_id])
314 def __init__(self, common):
315 print _('Starting settings dialog')
319 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
320 self.window.set_title(_("Tor Browser Launcher Settings"))
321 self.window.set_icon_from_file(self.common.paths['icon_file'])
322 self.window.set_position(gtk.WIN_POS_CENTER)
323 self.window.set_border_width(10)
324 self.window.connect("delete_event", self.delete_event)
325 self.window.connect("destroy", self.destroy)
327 # build the rest of the UI
328 self.box = gtk.VBox(False, 10)
329 self.window.add(self.box)
332 self.hbox = gtk.HBox(False, 10)
333 self.box.pack_start(self.hbox, True, True, 0)
336 self.settings_box = gtk.VBox(False, 10)
337 self.hbox.pack_start(self.settings_box, True, True, 0)
338 self.settings_box.show()
340 self.labels_box = gtk.VBox(False, 10)
341 self.hbox.pack_start(self.labels_box, True, True, 0)
342 self.labels_box.show()
345 self.preferred_box = gtk.HBox(False, 10)
346 self.settings_box.pack_start(self.preferred_box, True, True, 0)
347 self.preferred_box.show()
349 self.preferred_label = gtk.Label(_('I prefer'))
350 self.preferred_label.set_line_wrap(True)
351 self.preferred_box.pack_start(self.preferred_label, True, True, 0)
352 self.preferred_label.show()
354 self.preferred_options = []
355 for i in self.common.available_versions:
356 self.preferred_options.append(self.common.available_versions[i])
357 self.preferred_options.sort()
359 self.preferred = gtk.combo_box_new_text()
360 for option in self.preferred_options:
361 self.preferred.append_text(option)
362 if self.common.settings['preferred'] in self.common.available_versions:
363 self.preferred.set_active( self.preferred_options.index(self.common.available_versions[self.common.settings['preferred']]) )
365 self.preferred.set_active(0)
366 self.preferred_box.pack_start(self.preferred, True, True, 0)
367 self.preferred.show()
370 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
371 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
372 if self.common.settings['update_over_tor']:
373 self.tor_update_checkbox.set_active(True)
375 self.tor_update_checkbox.set_active(False)
376 self.tor_update_checkbox.show()
379 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
380 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
381 if self.common.settings['check_for_updates']:
382 self.update_checkbox.set_active(True)
384 self.update_checkbox.set_active(False)
385 self.update_checkbox.show()
388 if(self.common.settings['installed_version'][self.common.settings['preferred']]):
389 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version'][self.common.settings['preferred']]))
391 self.label1 = gtk.Label(_('Not installed'))
392 self.label1.set_line_wrap(True)
393 self.labels_box.pack_start(self.label1, True, True, 0)
396 if(self.common.settings['last_update_check_timestamp'] > 0):
397 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']))))
399 self.label1 = gtk.Label(_('Never checked for updates'))
400 self.label1.set_line_wrap(True)
401 self.labels_box.pack_start(self.label1, True, True, 0)
405 self.button_box = gtk.HButtonBox()
406 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
407 self.box.pack_start(self.button_box, True, True, 0)
408 self.button_box.show()
410 # save and launch button
411 save_launch_image = gtk.Image()
412 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
413 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
414 self.save_launch_button.set_image(save_launch_image)
415 self.save_launch_button.connect("clicked", self.save_launch, None)
416 self.button_box.add(self.save_launch_button)
417 self.save_launch_button.show()
419 # save and exit button
420 save_exit_image = gtk.Image()
421 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
422 self.save_exit_button = gtk.Button(_("Save & Exit"))
423 self.save_exit_button.set_image(save_exit_image)
424 self.save_exit_button.connect("clicked", self.save_exit, None)
425 self.button_box.add(self.save_exit_button)
426 self.save_exit_button.show()
429 cancel_image = gtk.Image()
430 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
431 self.cancel_button = gtk.Button(_("Cancel"))
432 self.cancel_button.set_image(cancel_image)
433 self.cancel_button.connect("clicked", self.destroy, None)
434 self.button_box.add(self.cancel_button)
435 self.cancel_button.show()
444 def save_launch(self, widget, data=None):
446 p = subprocess.Popen([self.common.paths['tbl_bin']])
450 def save_exit(self, widget, data=None):
456 # figure out the selected preferred option
458 selected = self.preferred_options[self.preferred.get_active()]
459 for i in self.common.available_versions:
460 if self.common.available_versions[i] == selected:
463 self.common.settings['preferred'] = preferred
466 self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
467 self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
470 self.common.save_settings()
473 def delete_event(self, widget, event, data=None):
475 def destroy(self, widget, data=None):
480 def __init__(self, common):
481 print _('Starting launcher dialog')
485 self.set_gui(None, '', [])
486 self.launch_gui = True
487 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
489 # is vidalia already running and we just need to open a new firefox?
490 if self.common.settings['installed_version']:
491 vidalia_pid = self.common.get_pid('./App/vidalia')
492 firefox_pid = self.common.get_pid(self.common.paths['tbb'][self.common.settings['preferred']]['firefox_bin'])
494 if vidalia_pid and not firefox_pid:
495 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
496 self.common.bring_window_to_front(vidalia_pid)
497 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']])
499 elif vidalia_pid and firefox_pid:
500 print _('Vidalia and Firefox are already open, bringing them to focus')
502 # bring firefox to front, then vidalia
503 self.common.bring_window_to_front(firefox_pid)
504 self.common.bring_window_to_front(vidalia_pid)
508 check_for_updates = False
509 if self.common.settings['check_for_updates']:
510 check_for_updates = True
512 if not check_for_updates:
513 # how long was it since the last update check?
514 # 86400 seconds = 24 hours
515 current_timestamp = int(time.time())
516 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
517 check_for_updates = True
519 if check_for_updates:
521 print 'Checking for update'
522 self.set_gui('task', _("Checking for Tor Browser update."),
523 ['download_update_check',
526 # no need to check for update
527 print _('Checked for update within 24 hours, skipping')
528 self.start_launcher()
532 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
533 self.window.set_title(_("Tor Browser"))
534 self.window.set_icon_from_file(self.common.paths['icon_file'])
535 self.window.set_position(gtk.WIN_POS_CENTER)
536 self.window.set_border_width(10)
537 self.window.connect("delete_event", self.delete_event)
538 self.window.connect("destroy", self.destroy)
540 # build the rest of the UI
543 # download or run TBB
544 def start_launcher(self):
545 # is TBB already installed?
546 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
547 if os.path.isfile(start) and os.access(start, os.X_OK):
548 if self.common.settings['installed_version'][self.common.settings['preferred']] == self.common.settings['latest_version'][self.common.settings['preferred']]:
549 # current version of tbb is installed, launch it
551 self.launch_gui = False
552 elif self.common.settings['installed_version'][self.common.settings['preferred']] < self.common.settings['latest_version'][self.common.settings['preferred']]:
553 # there is a tbb upgrade available
554 self.set_gui('task', _("Your Tor Browser is out of date."),
555 ['download_tarball_sig',
561 # for some reason the installed tbb is newer than the current version?
562 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
566 # are the tarball and sig already downloaded?
567 if os.path.isfile(self.common.paths['tarball_file']) and os.path.isfile(self.common.paths['tarball_sig_file']):
568 # start the gui with verify
569 self.set_gui('task', _("Installing Tor Browser."),
576 self.set_gui('task', _("Downloading and installing Tor Browser."),
577 ['download_tarball_sig',
583 # there are different GUIs that might appear, this sets which one we want
584 def set_gui(self, gui, message, tasks, autostart=True):
586 self.gui_message = message
587 self.gui_tasks = tasks
589 self.gui_autostart = autostart
591 # set all gtk variables to False
593 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
598 self.progressbar = False
599 self.button_box = False
600 self.start_button = False
601 self.exit_button = False
603 # build the application's UI
607 self.box = gtk.VBox(False, 20)
608 self.window.add(self.box)
610 if self.gui == 'error':
612 self.label = gtk.Label( self.gui_message )
613 self.label.set_line_wrap(True)
614 self.box.pack_start(self.label, True, True, 0)
618 exit_image = gtk.Image()
619 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
620 self.exit_button = gtk.Button("Exit")
621 self.exit_button.set_image(exit_image)
622 self.exit_button.connect("clicked", self.destroy, None)
623 self.box.add(self.exit_button)
624 self.exit_button.show()
626 elif self.gui == 'task':
628 self.label = gtk.Label( self.gui_message )
629 self.label.set_line_wrap(True)
630 self.box.pack_start(self.label, True, True, 0)
634 self.progressbar = gtk.ProgressBar(adjustment=None)
635 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
636 self.progressbar.set_pulse_step(0.01)
637 self.box.pack_start(self.progressbar, True, True, 0)
640 self.button_box = gtk.HButtonBox()
641 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
642 self.box.pack_start(self.button_box, True, True, 0)
643 self.button_box.show()
646 start_image = gtk.Image()
647 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
648 self.start_button = gtk.Button(_("Start"))
649 self.start_button.set_image(start_image)
650 self.start_button.connect("clicked", self.start, None)
651 self.button_box.add(self.start_button)
652 if not self.gui_autostart:
653 self.start_button.show()
656 exit_image = gtk.Image()
657 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
658 self.exit_button = gtk.Button(_("Exit"))
659 self.exit_button.set_image(exit_image)
660 self.exit_button.connect("clicked", self.destroy, None)
661 self.button_box.add(self.exit_button)
662 self.exit_button.show()
667 if self.gui_autostart:
670 # start button clicked, begin tasks
671 def start(self, widget, data=None):
672 # disable the start button
673 if self.start_button:
674 self.start_button.set_sensitive(False)
676 # start running tasks
679 # run the next task in the task list
683 if self.gui_task_i >= len(self.gui_tasks):
687 task = self.gui_tasks[self.gui_task_i]
689 # get ready for the next task
692 if task == 'download_update_check':
693 print _('Downloading'), self.common.paths['update_check_url']
694 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
696 if task == 'attempt_update':
697 print _('Checking to see if update it needed')
698 self.attempt_update()
700 elif task == 'download_tarball':
701 print _('Downloading'), self.common.paths['tarball_url']
702 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
704 elif task == 'download_tarball_sig':
705 print _('Downloading'), self.common.paths['tarball_sig_url']
706 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
708 elif task == 'verify':
709 print _('Verifying signature')
712 elif task == 'extract':
713 print _('Extracting'), self.common.paths['tarball_filename']
717 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
720 elif task == 'start_over':
721 print _('Starting download over again')
724 def response_received(self, response):
725 class FileDownloader(Protocol):
726 def __init__(self, file, total, progress, done_cb):
730 self.progress = progress
731 self.all_done = done_cb
733 def dataReceived(self, bytes):
734 self.file.write(bytes)
735 self.so_far += len(bytes)
736 percent = float(self.so_far) / float(self.total)
737 self.progress.set_fraction(percent)
738 amount = float(self.so_far)
740 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
743 amount = amount / float(size)
746 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
748 def connectionLost(self, reason):
749 print _('Finished receiving body:'), reason.getErrorMessage()
750 self.all_done(reason)
752 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
753 response.deliverBody(dl)
755 def response_finished(self, msg):
756 if msg.check(ResponseDone):
757 self.file_download.close()
762 print "FINISHED", msg
763 ## FIXME handle errors
765 def download_error(self, f):
766 print _("Download error"), f
767 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
771 def download(self, name, url, path):
772 # initialize the progress bar
773 self.progressbar.set_fraction(0)
774 self.progressbar.set_text(_('Downloading {0}').format(name))
775 self.progressbar.show()
778 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
779 d = agent.request('GET', url,
780 Headers({'User-Agent': ['torbrowser-launcher']}),
783 self.file_download = open(path, 'w')
784 d.addCallback(self.response_received).addErrback(self.download_error)
786 if not reactor.running:
789 def attempt_update(self):
790 # load the update check file
792 versions = json.load(open(self.common.paths['update_check_file']))
796 # filter linux versions
798 for version in versions:
799 if str(version).find('-Linux') != -1:
800 valid_versions.append(str(version))
802 for version in valid_versions:
803 if version.find('alpha') != -1:
804 latest_alpha = version
805 valid_versions.remove(latest_alpha)
806 # find stable (whatever is left after alpha)
807 if len(valid_versions):
808 latest_stable = valid_versions[0]
810 if latest_stable or latest_alpha:
812 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
814 self.common.settings['latest_version']['alpha'] = latest_alpha[:-len('-Linux')]
815 self.common.settings['last_update_check_timestamp'] = int(time.time())
816 self.common.settings['check_for_updates'] = False
817 self.common.save_settings()
818 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
819 self.start_launcher()
822 # failed to find the latest version
823 self.set_gui('error', _("Error checking for updates."), [], False)
826 # not a valid JSON object
827 self.set_gui('error', _("Error checking for updates."), [], False)
834 # initialize the progress bar
835 self.progressbar.set_fraction(0)
836 self.progressbar.set_text(_('Verifying Signature'))
837 self.progressbar.show()
839 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['tarball_sig_file']])
840 self.pulse_until_process_exits(p)
842 if p.returncode == 0:
845 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)
849 if not reactor.running:
853 # initialize the progress bar
854 self.progressbar.set_fraction(0)
855 self.progressbar.set_text(_('Installing'))
856 self.progressbar.show()
859 # make sure this file is a tarfile
860 if tarfile.is_tarfile(self.common.paths['tarball_file']):
861 tf = tarfile.open(self.common.paths['tarball_file'])
862 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
864 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
868 # installation is finished, so save installed_version
869 self.common.settings['installed_version'] = self.common.settings['latest_version']
870 self.common.save_settings()
874 def run(self, run_next_task = True):
875 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
879 # make the progress bar pulse until process p (a Popen object) finishes
880 def pulse_until_process_exits(self, p):
881 while p.poll() == None:
883 self.progressbar.pulse()
886 # start over and download TBB again
887 def start_over(self):
888 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
889 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
894 def refresh_gtk(self):
895 while gtk.events_pending():
896 gtk.main_iteration(False)
899 def delete_event(self, widget, event, data=None):
901 def destroy(self, widget, data=None):
902 if hasattr(self, 'file_download'):
903 self.file_download.close()
907 if __name__ == "__main__":
908 tor_browser_launcher_version = '0.0.2'
910 print _('Tor Browser Launcher')
911 print _('By Micah Lee, licensed under GPLv3')
912 print _('version {0}').format(tor_browser_launcher_version)
913 print 'https://github.com/micahflee/torbrowser-launcher'
915 common = TBLCommon(tor_browser_launcher_version)
917 # is torbrowser-launcher already running?
918 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
920 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
921 common.bring_window_to_front(tbl_pid)
924 if '-settings' in sys.argv:
926 app = TBLSettings(common)
930 app = TBLLauncher(common)