]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser-launcher
had to make the launch_gui bool a class variable
[torbrowser-launcher.git] / torbrowser-launcher
1 #!/usr/bin/env python
2 .view-twitterator-follow-eff .views-row .views-field-field-tweet {
3
4 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json
5
6 import pygtk
7 pygtk.require('2.0')
8 import gtk
9
10 class TorBrowserLauncher:
11   def __init__(self):
12     # initialize the app
13     self.discover_arch_lang()
14     self.build_paths()
15     self.mkdirs()
16
17     # allow buttons to have icons
18     try:
19       settings = gtk.settings_get_default()
20       settings.props.gtk_button_images = True
21     except:
22       pass
23
24     self.launch_gui = True
25
26     # load settings
27     if self.load_settings():
28       self.build_paths(self.settings['latest_version'])
29
30       # how long was it since the last update check?
31       # 86400 seconds = 24 hours
32       current_timestamp = int(time.time())
33       if current_timestamp - self.settings['last_update_check_timestamp'] >= 86400:
34         # check for update
35         print 'Checking for update'
36         self.set_gui('task', "Checking for Tor Browser update.", 
37           ['download_update_check', 
38            'attempt_update'])
39
40       else:
41         # no need to check for update
42         print 'Checked for update within 24 hours, skipping'
43         self.start_launcher()
44
45     else:
46       self.set_gui('error', "Error loading settings. Delete ~/.torbrowser and try again.", [])
47
48     if self.launch_gui:
49       # set up the window
50       self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
51       self.window.set_title("Tor Browser")
52       self.window.set_position(gtk.WIN_POS_CENTER)
53       self.window.set_border_width(10)
54       self.window.connect("delete_event", self.delete_event)
55       self.window.connect("destroy", self.destroy)
56
57       # build the rest of the UI
58       self.build_ui()
59       gtk.main()
60
61   # download or run TBB
62   def start_launcher(self):
63     # is TBB already installed?
64     if os.path.isfile(self.paths['file']['start']) and os.access(self.paths['file']['start'], os.X_OK):
65       if self.settings['installed_version'] == self.settings['latest_version']:
66         # current version of tbb is installed, launch it
67         self.run(False)
68         self.launch_gui = False
69       elif self.settings['installed_version'] < self.settings['latest_version']:
70         # there is a tbb upgrade available
71         self.set_gui('task', "Your Tor Browser is out of date.", 
72           ['download_tarball', 
73            'download_tarball_sig', 
74            'verify', 
75            'extract', 
76            'run'])
77       else:
78         # for some reason the installed tbb is newer than the current version?
79         self.set_gui('error', "Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?", [])
80
81     # not installed
82     else:
83       # are the tarball and sig already downloaded?
84       if os.path.isfile(self.paths['file']['tarball']) and os.path.isfile(self.paths['file']['tarball_sig']):
85         # start the gui with verify
86         self.set_gui('task', "Installing Tor Browser.", 
87           ['verify', 
88            'extract', 
89            'run'])
90
91       # first run
92       else:
93         self.set_gui('task', "Downloading and installing Tor Browser.", 
94           ['download_tarball', 
95            'download_tarball_sig', 
96            'verify', 
97            'extract', 
98            'run'])
99
100   # discover the architecture and language
101   def discover_arch_lang(self):
102     # figure out the architecture
103     (sysname, nodename, release, version, machine) = os.uname()
104     self.architecture = machine
105
106     # figure out the language
107     available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
108     default_locale = locale.getdefaultlocale()[0]
109     if default_locale == None:
110       self.language = 'en-US'
111     else:
112       self.language = default_locale.replace('_', '-')
113       if self.language not in available_languages:
114         self.language = self.language.split('-')[0]
115         if self.language not in available_languages:
116           for l in available_languages:
117             if l[0:2] == self.language:
118               self.language = l
119       # if language isn't available, default to english
120       if self.language not in available_languages:
121         self.language = 'en-US'
122
123   # build all relevant paths
124   def build_paths(self, tbb_version = None):
125     tbb_data = os.getenv('HOME')+'/.torbrowser'
126
127     if tbb_version:
128       tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
129       self.paths['file']['tarball'] = tbb_data+'/download/'+tarball_filename
130       self.paths['file']['tarball_sig'] = tbb_data+'/download/'+tarball_filename+'.asc'
131       self.paths['url']['tarball'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
132       self.paths['url']['tarball_sig'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
133       self.paths['filename']['tarball'] = tarball_filename
134       self.paths['filename']['tarball_sig'] = tarball_filename+'.asc'
135
136     else:
137       self.paths = {
138         'dir': {
139           'data': tbb_data,
140           'download': tbb_data+'/download',
141           'tbb': tbb_data+'/tbb/'+self.architecture,
142           'gpg': tbb_data+'/gpgtmp'
143         },
144         'file': {
145           'settings': tbb_data+'/settings',
146           'version': tbb_data+'/version',
147           'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
148           'update_check': tbb_data+'/download/RecommendedTBBVersions',
149           'verify': '/usr/share/torbrowser-launcher/verify.sh'
150         },
151         'url': {
152           'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
153         },
154         'filename': {}
155       }
156
157   # create directories that don't exist
158   def mkdirs(self):
159     if os.path.exists(self.paths['dir']['download']) == False:
160       os.makedirs(self.paths['dir']['download'])
161     if os.path.exists(self.paths['dir']['tbb']) == False:
162       os.makedirs(self.paths['dir']['tbb'])
163
164   # there are different GUIs that might appear, this sets which one we want
165   def set_gui(self, gui, message, tasks, autostart=True):
166     self.gui = gui
167     self.gui_message = message
168     self.gui_tasks = tasks
169     self.gui_task_i = 0
170     self.gui_autostart = autostart
171
172   # set all gtk variables to False
173   def clear_ui(self):
174     if self.timer:
175       gobject.source_remove(self.timer)
176     self.timer = False
177
178     if self.box:
179       self.box.destroy()
180     self.box = False
181
182     self.label1 = False
183     self.label2 = False
184     self.label = False
185     self.progressbar = False
186     self.button_box = False
187     self.start_button = False
188     self.exit_button = False
189
190   # build the application's UI
191   def build_ui(self):
192     self.box = gtk.VBox(False, 20)
193     self.window.add(self.box)
194
195     if self.gui == 'error':
196       # labels
197       self.label1 = gtk.Label( self.gui_message ) 
198       self.label1.set_line_wrap(True)
199       self.box.pack_start(self.label1, True, True, 0)
200       self.label1.show()
201
202       self.label2 = gtk.Label("You can fix the problem by deleting:\n"+self.paths['dir']['data']+"\n\nHowever, you will lose all your bookmarks and other Tor Browser preferences.") 
203       self.label2.set_line_wrap(True)
204       self.box.pack_start(self.label2, True, True, 0)
205       self.label2.show()
206
207       # exit button
208       exit_image = gtk.Image()
209       exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
210       self.exit_button = gtk.Button("Exit")
211       self.exit_button.set_image(exit_image)
212       self.exit_button.connect("clicked", self.destroy, None)
213       self.box.add(self.exit_button)
214       self.exit_button.show()
215
216     elif self.gui == 'task':
217       # label
218       self.label = gtk.Label( self.gui_message ) 
219       self.label.set_line_wrap(True)
220       self.box.pack_start(self.label, True, True, 0)
221       self.label.show()
222       
223       # progress bar
224       self.progressbar = gtk.ProgressBar(adjustment=None)
225       self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
226       self.progressbar.set_pulse_step(0.01)
227       self.box.pack_start(self.progressbar, True, True, 0)
228
229       # button box
230       self.button_box = gtk.HButtonBox()
231       self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
232       self.box.pack_start(self.button_box, True, True, 0)
233       self.button_box.show()
234
235       # start button
236       start_image = gtk.Image()
237       start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
238       self.start_button = gtk.Button("Start")
239       self.start_button.set_image(start_image)
240       self.start_button.connect("clicked", self.start, None)
241       self.button_box.add(self.start_button)
242       if not self.gui_autostart:
243         self.start_button.show()
244
245       # exit button
246       exit_image = gtk.Image()
247       exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
248       self.exit_button = gtk.Button("Exit")
249       self.exit_button.set_image(exit_image)
250       self.exit_button.connect("clicked", self.destroy, None)
251       self.button_box.add(self.exit_button)
252       self.exit_button.show()
253
254     self.box.show()
255     self.window.show()
256
257     if self.gui_autostart:
258       self.start(None)
259
260   # start button clicked, begin tasks
261   def start(self, widget, data=None):
262     # disable the start button
263     if self.start_button:
264       self.start_button.set_sensitive(False)
265
266     # start running tasks
267     self.run_task()
268     
269   # run the next task in the task list
270   def run_task(self):
271     self.refresh_gtk()
272
273     if self.gui_task_i >= len(self.gui_tasks):
274       self.destroy(False)
275       return
276
277     task = self.gui_tasks[self.gui_task_i]
278     
279     # get ready for the next task
280     self.gui_task_i += 1
281
282     if task == 'download_update_check':
283       print 'Downloading '+self.paths['url']['update_check']
284       self.download('update check', self.paths['url']['update_check'], self.paths['file']['update_check'])
285     
286     if task == 'attempt_update':
287       print 'Checking to see if update it needed'
288       self.attempt_update()
289
290     elif task == 'download_tarball':
291       print 'Downloading '+self.paths['url']['tarball']
292       self.download('tarball', self.paths['url']['tarball'], self.paths['file']['tarball'])
293
294     elif task == 'download_tarball_sig':
295       print 'Downloading '+self.paths['url']['tarball_sig']
296       self.download('signature', self.paths['url']['tarball_sig'], self.paths['file']['tarball_sig'])
297
298     elif task == 'verify':
299       print 'Verifying signature'
300       self.verify()
301
302     elif task == 'extract':
303       print 'Extracting '+self.paths['filename']['tarball']
304       self.extract()
305
306     elif task == 'run':
307       print 'Running '+self.paths['file']['start']
308       self.run()
309     
310     elif task == 'start_over':
311       print 'Starting download over again'
312       self.start_over()
313
314
315   def download(self, name, url, path):
316     # initialize the progress bar
317     self.progressbar.set_fraction(0) 
318     self.progressbar.set_text('Downloading '+name)
319     self.progressbar.show()
320     self.refresh_gtk()
321
322     # start the download
323     self.dl_response = urllib2.urlopen(url)
324     self.dl_total_size = self.dl_response.info().getheader('Content-Length').strip()
325     self.dl_total_size = int(self.dl_total_size)
326     self.dl_bytes_so_far = 0
327
328     # set a timer to download more chunks
329     self.timer = gobject.timeout_add(1, self.download_chunk, name)
330
331     # open a file to write to
332     self.file_download = open(path, 'w')
333
334   def download_chunk(self, name):
335     # download 10kb a time
336     chunk = self.dl_response.read(10240)
337     self.dl_bytes_so_far += len(chunk)
338     self.file_download.write(chunk)
339
340     if not chunk:
341       self.file_download.close()
342       # next task!
343       self.run_task()
344       return False
345
346     percent = float(self.dl_bytes_so_far) / self.dl_total_size
347     self.progressbar.set_fraction(percent)
348     percent = round(percent*100, 2)
349     self.progressbar.set_text("Downloaded %d%% of %s" % (percent, name))
350     self.refresh_gtk()
351     
352     sys.stdout.write("Downloaded %d of %d bytes (%0.2f%%)\r" % (self.dl_bytes_so_far, self.dl_total_size, percent))
353
354     if self.dl_bytes_so_far >= self.dl_total_size:
355       sys.stdout.write('\n')
356
357     return True
358
359   def attempt_update(self):
360     # load the update check file
361     try:
362       versions = json.load(open(self.paths['file']['update_check']))
363       latest_version = None
364
365       end = '-Linux'
366       for version in versions:
367         if str(version).find(end) != -1:
368           latest_version = str(version)
369
370       if latest_version:
371         self.settings['latest_version'] = latest_version[:-len(end)]
372         self.settings['last_update_check_timestamp'] = int(time.time())
373         self.save_settings()
374         self.build_paths(self.settings['latest_version'])
375         self.start_launcher()
376
377       else:
378         # failed to find the latest version
379         self.set_gui('error', "Error checking for updates.", [], False)
380     
381     except:
382       # not a valid JSON object
383       self.set_gui('error', "Error checking for updates.", [], False)
384
385     # now start over
386     self.clear_ui()
387     self.build_ui()
388
389   def verify(self):
390     # initialize the progress bar
391     self.progressbar.set_fraction(0) 
392     self.progressbar.set_text('Verifying Signature')
393     self.progressbar.show()
394
395     p = subprocess.Popen([self.paths['file']['verify'], self.paths['dir']['gpg'], self.paths['file']['tarball_sig']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
396     self.pulse_until_process_exits(p)
397
398     output = p.stdout.read()
399     
400     if 'Good signature' in output:
401       self.run_task()
402     else:
403       self.progressbar.hide()
404       self.label.set_text("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again.")
405       self.gui_tasks = ['start_over']
406       self.gui_task_i = 0
407       self.start_button.show()
408       self.start_button.set_sensitive(True)
409
410   def extract(self):
411     # initialize the progress bar
412     self.progressbar.set_fraction(0) 
413     self.progressbar.set_text('Installing')
414     self.progressbar.show()
415
416     p = subprocess.Popen(['tar', '-xf', self.paths['file']['tarball'], '-C', self.paths['dir']['tbb']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
417     self.pulse_until_process_exits(p)
418
419     # installation is finished, so save installed_version
420     self.settings['installed_version'] = self.settings['latest_version']
421     self.save_settings()
422
423     self.run_task()
424
425   def run(self, run_next_task = True):
426     subprocess.Popen([self.paths['file']['start']])
427     if run_next_task:
428       self.run_task()
429
430   # make the progress bar pulse until process p (a Popen object) finishes
431   def pulse_until_process_exits(self, p):
432     while p.poll() == None:
433       time.sleep(0.01)
434       self.progressbar.pulse()
435       self.refresh_gtk()
436
437   # start over and download TBB again
438   def start_over(self):
439     self.label.set_text("Downloading Tor Browser Bundle over again.")
440     self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
441     self.gui_task_i = 0
442     self.start(None)
443
444   # load settings
445   def load_settings(self):
446     if os.path.isfile(self.paths['file']['settings']):
447       self.settings = pickle.load(open(self.paths['file']['settings']))
448       # sanity checks
449       if not 'installed_version' in self.settings:
450         return False
451       if not 'latest_version' in self.settings:
452         return False
453       if not 'last_update_check_timestamp' in self.settings:
454         return False
455     else:
456       self.settings = {
457         'installed_version': False,
458         'latest_version': '0',
459         'last_update_check_timestamp': 0
460       }
461       self.save_settings()
462     return True
463
464   # save settings
465   def save_settings(self):
466     pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
467     return True
468   
469   # refresh gtk
470   def refresh_gtk(self):
471     while gtk.events_pending():
472        gtk.main_iteration(False)
473
474   # exit
475   def delete_event(self, widget, event, data=None):
476     return False
477   def destroy(self, widget, data=None):
478     gtk.main_quit()
479
480 if __name__ == "__main__":
481   tor_browser_launcher_version = '0.1'
482
483   print 'Tor Browser Launcher'
484   print 'version %s' % (tor_browser_launcher_version)
485   print 'https://github.com/micahflee/torbrowser-launcher'
486
487   app = TorBrowserLauncher()
488