~wxl/usb-creator/usb-creator

« back to all changes in this revision

Viewing changes to usbcreator/frontends/gtk/frontend.py

  • Committer: Mathieu Trudel-Lapierre
  • Date: 2015-12-11 17:37:20 UTC
  • mfrom: (472.1.46 usb-creator)
  • Revision ID: mathieu.trudel-lapierre@canonical.com-20151211173720-6wobakg37b2habnx
* Rework the whole imaging process for writing to devices:
  - Use an equivalent of dd to make an exact copy of the image to the device
  - This also breaks persistence.
* Update UI and frontend code to drop the persistence widgets.
* Drop Erase Disk widgets too.
* debian/usb-creator-common.postinst, debian/usb-creator-common.prerm:
  stop usb-creator-helper on package upgrades and removals.
  (LP: #1497569)

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
 
31
31
if 'USBCREATOR_LOCAL' in os.environ:
32
32
    ui_path = os.path.join(os.getcwd(), 'gui/usbcreator-gtk.ui')
33
 
    eula_path = os.path.join(os.getcwd(), 'gui/ubuntu-nexus7-USAGE-NOTICE-en.txt')
34
33
else:
35
34
    ui_path = '/usr/share/usb-creator/usbcreator-gtk.ui'
36
 
    eula_path = '/usr/share/usb-creator/ubuntu-nexus7-USAGE-NOTICE-en.txt'
37
35
 
38
36
Gdk.threads_init()
39
37
 
60
58
        from dbus.mainloop.glib import DBusGMainLoop
61
59
        DBusGMainLoop(set_as_default=True)
62
60
 
63
 
    def __init__(self, backend, img=None, persistent=True,
64
 
                 allow_system_internal=False):
 
61
    def __init__(self, backend, img=None, allow_system_internal=False):
65
62
 
66
63
        self.allow_system_internal = allow_system_internal
67
 
        self.fastboot_mode = backend.__class__.__name__ == 'FastbootBackend'
68
64
 
69
65
        self.all_widgets = set()
70
66
 
101
97
        self.failed_exit.connect('clicked', lambda x: self.failed_exit.hide())
102
98
        self.progress_cancel_button.connect('clicked', lambda x: self.warning_dialog.show())
103
99
 
 
100
        # Set warning dialog transient window
 
101
        self.warning_dialog.set_transient_for(self.install_window)
 
102
 
104
103
        def format_value(scale, value):
105
104
            return misc.format_mb_size(value)
106
 
        self.persist_value.set_adjustment(
107
 
            Gtk.Adjustment.new(0, 0, 100, 1, 10, 0))
108
 
        self.persist_value.connect('format-value', format_value)
 
105
 
 
106
        # TODO: remove format button
 
107
        self.format_dest.hide()
109
108
 
110
109
        # Connect to backend signals.
111
110
        self.backend = backend
121
120
        self.backend.install_progress_pulse_stop_cb = self.progress_pulse_stop
122
121
        self.backend.retry_cb = self.retry
123
122
        self.backend.target_changed_cb = self.update_target
124
 
        self.backend.format_ended_cb = self.format_ended
125
 
        self.backend.format_failed_cb = self.format_failed
126
123
 
127
124
        # Pulse state.
128
125
        self.pulsing = False
129
126
 
130
 
        # Hide erase in fastboot mode
131
 
        if self.fastboot_mode:
132
 
            self.format_dest.hide()
133
 
            self.intro_label.set_text(_('To run Ubuntu on a portable device, it needs to be set up first.'))
134
 
            self.label2.set_text(_('Source disc image (.img):'))
135
 
            self.label3.set_text(_('Target device:'))
136
 
            self.window.set_title(_('Ubuntu Core Installer'))
137
 
            self.button_install.set_label(_('Install Ubuntu Core'))
138
 
            self.button_help.set_label(_('Legal'))
139
 
            self.button_help.connect('clicked', self.show_eula)
140
 
            self.eula_dialog.add_buttons(
141
 
                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
142
 
                Gtk.STOCK_OK, Gtk.ResponseType.OK
143
 
            )
144
 
            with open(eula_path, 'r') as f:
145
 
                text = ''.join(f.readlines())
146
 
            self.eula_text.get_buffer().set_text(text)
147
 
        else:
148
 
            # we currently do not have help
149
 
            self.button_help.hide()
150
 
            #self.button_help.connect('clicked', lambda x: Gtk.show_uri(self.button_help.get_screen(),
151
 
            #                                                           'ghelp:usb-creator',
152
 
            #                                                           Gtk.get_current_event_time()))
 
127
        # we currently do not have help
 
128
        self.button_help.hide()
 
129
        #self.button_help.connect('clicked', lambda x: Gtk.show_uri(self.button_help.get_screen(),
 
130
        #                                                           'ghelp:usb-creator',
 
131
        #                                                           Gtk.get_current_event_time()))
153
132
            
154
133
        self.setup_sources_treeview()
155
134
        self.setup_targets_treeview()
156
 
        self.persist_vbox.set_sensitive(False)
157
135
 
158
136
        # Pre-populate the source view.
159
137
        if img is not None:
171
149
        # Sets first pre-populated image as current in the backend
172
150
        self.selection_changed_source(self.source_treeview.get_selection())
173
151
 
174
 
        if not persistent:
175
 
            self.persist_disabled.set_active(True)
176
 
            self.persist_vbox.hide()
177
 
 
178
152
        self.window.show()
179
153
        selection = self.source_treeview.get_selection()
180
154
        selection.connect('changed', self.selection_changed_source)
301
275
            logging.debug('No target selected.')
302
276
            return ''
303
277
 
304
 
    def get_persistence(self):
305
 
        if self.persist_enabled.get_active() and \
306
 
            self.persist_enabled.get_state() != Gtk.StateType.INSENSITIVE:
307
 
            val = self.persist_value.get_value()
308
 
            return int(val)
309
 
        else:
310
 
            return 0
311
 
 
312
278
    def get_gnome_drive(self, dev):
313
279
        try:
314
280
            monitor = Gio.VolumeMonitor.get()
367
333
        cell_name.set_property('ellipsize', Pango.EllipsizeMode.END)
368
334
        cell_pixbuf = Gtk.CellRendererPixbuf()
369
335
        column_name = Gtk.TreeViewColumn(_('CD-Drive/Image'))
370
 
        if self.fastboot_mode:
371
 
            column_name = Gtk.TreeViewColumn(_('Image'))
372
336
        column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
373
337
        column_name.set_resizable(True)
374
338
        column_name.set_expand(True)
379
343
        column_name.set_cell_data_func(cell_name, column_data_func, 0)
380
344
        column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func, None)
381
345
 
382
 
        if not self.fastboot_mode:
383
 
            cell_version = Gtk.CellRendererText()
384
 
            cell_version.set_property('ellipsize', Pango.EllipsizeMode.END)
385
 
            column_name = Gtk.TreeViewColumn(_('OS Version'), cell_version)
386
 
            column_name.set_cell_data_func(cell_version, column_data_func, 1)
387
 
            column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
388
 
            column_name.set_resizable(True)
389
 
            column_name.set_expand(True)
390
 
            column_name.set_min_width(75)
391
 
            self.source_treeview.append_column(column_name)
 
346
        cell_version = Gtk.CellRendererText()
 
347
        cell_version.set_property('ellipsize', Pango.EllipsizeMode.END)
 
348
        column_name = Gtk.TreeViewColumn(_('OS Version'), cell_version)
 
349
        column_name.set_cell_data_func(cell_version, column_data_func, 1)
 
350
        column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
 
351
        column_name.set_resizable(True)
 
352
        column_name.set_expand(True)
 
353
        column_name.set_min_width(75)
 
354
        self.source_treeview.append_column(column_name)
392
355
 
393
356
        cell_size = Gtk.CellRendererText()
394
357
        cell_size.set_property('ellipsize', Pango.EllipsizeMode.END)
444
407
 
445
408
        target = self.backend.targets[udi]
446
409
 
447
 
        # Update persistence maximum value.
448
 
        if not self.fastboot_mode:
449
 
            self.persist_vbox.set_sensitive(False)
450
 
            self.persist_enabled_vbox.set_sensitive(False)
451
 
            persist_mb = target['persist'] / 1024 / 1024
452
 
            if persist_mb > misc.MIN_PERSISTENCE:
453
 
                    self.persist_vbox.set_sensitive(True)
454
 
                    self.persist_enabled_vbox.set_sensitive(True)
455
 
                    self.persist_value.set_range(misc.MIN_PERSISTENCE, persist_mb)
456
 
                    self.persist_value.set_value(misc.MIN_PERSISTENCE)
457
 
 
458
410
        # Update install button state.
459
411
        status = target['status']
460
412
        source = self.backend.get_current_source()
469
421
        self.open_dest.hide()
470
422
        if status == misc.CANNOT_USE:
471
423
            msg = _('The device is not large enough to hold this image.')
472
 
        elif status == misc.NEED_SPACE:
473
 
            msg = _('There is not enough free space for this image.')
474
 
            self.open_dest.show()
475
424
        else:
476
425
            msg = ''
477
426
        self.dest_status.set_text(msg)
478
427
 
479
 
    def format_ended(self):
480
 
        selection = self.dest_treeview.get_selection()
481
 
        self.selection_changed_target(selection)
482
 
 
483
428
    def selection_changed_source(self, selection):
484
429
        model, iterator = selection.get_selected()
485
430
        if not iterator:
489
434
        self.selection_changed_target(self.dest_treeview.get_selection())
490
435
 
491
436
    def selection_changed_target(self, selection):
492
 
        '''The selected partition has changed and the bounds on the persistence
493
 
        slider need to be changed, or the slider needs to be disabled, to
494
 
        reflect the amount of free space on the partition.'''
495
 
 
496
437
        model, iterator = selection.get_selected()
497
438
        if not iterator:
498
439
            return
500
441
        if udi:
501
442
            self.update_target(udi)
502
443
 
503
 
        if self.fastboot_mode:
504
 
            return
505
 
 
506
444
        dev = self.backend.targets[udi]
507
445
        p = dev['parent']
508
446
        if p and p in self.backend.targets:
509
447
            dev = self.backend.targets[p]
510
 
        if dev['formatting']:
511
 
            self.format_dest.set_sensitive(False)
512
 
            self.format_dest_label.set_text(_('Erasing...'))
513
 
            self.format_dest_spinner.show()
514
 
            self.format_dest_spinner.start()
515
 
        else:
516
 
            self.format_dest.set_sensitive(True)
517
 
            self.format_dest_label.set_text(_('Erase Disk'))
518
 
            self.format_dest_spinner.hide()
519
 
            self.format_dest_spinner.stop()
520
448
 
521
449
    def setup_targets_treeview(self):
522
450
        def column_data_func(layout, cell, model, iterator, column):
537
465
                    cell.set_property('text', dev['label'])
538
466
            elif column == 2:
539
467
                cell.set_property('text', misc.format_size(dev['capacity']))
540
 
            elif column == 3:
541
 
                free = dev['free']
542
 
                if free >= 0:
543
 
                    cell.set_property('text', misc.format_size(free))
544
 
                else:
545
 
                    cell.set_property('text', '')
546
468
 
547
469
        def pixbuf_data_func(column, cell, model, iterator, data):
548
470
            if not self.backend:
582
504
        column_name.set_cell_data_func(cell_name, column_data_func, 0)
583
505
        column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func, None)
584
506
 
585
 
        if not self.fastboot_mode:
586
 
            cell_name = Gtk.CellRendererText()
587
 
            cell_name.set_property('ellipsize', Pango.EllipsizeMode.END)
588
 
            column_name = Gtk.TreeViewColumn(_('Label'), cell_name)
589
 
            column_name.set_cell_data_func(cell_name, column_data_func, 1)
590
 
            column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
591
 
            column_name.set_resizable(True)
592
 
            column_name.set_expand(True)
593
 
            column_name.set_min_width(75)
594
 
            self.dest_treeview.append_column(column_name)
595
 
 
596
 
            cell_capacity = Gtk.CellRendererText()
597
 
            cell_capacity.set_property('ellipsize', Pango.EllipsizeMode.END)
598
 
            column_name = Gtk.TreeViewColumn(_('Capacity'), cell_capacity)
599
 
            column_name.set_cell_data_func(cell_capacity, column_data_func, 2)
600
 
            column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
601
 
            column_name.set_resizable(True)
602
 
            column_name.set_expand(False)
603
 
            column_name.set_min_width(75)
604
 
            self.dest_treeview.append_column(column_name)
605
 
 
606
 
            cell_free = Gtk.CellRendererText()
607
 
            cell_free.set_property('ellipsize', Pango.EllipsizeMode.END)
608
 
            column_name = Gtk.TreeViewColumn(_('Free Space'), cell_free)
609
 
            column_name.set_cell_data_func(cell_free, column_data_func, 3)
610
 
            column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
611
 
            column_name.set_resizable(True)
612
 
            column_name.set_expand(False)
613
 
            column_name.set_min_width(75)
614
 
            self.dest_treeview.append_column(column_name)
 
507
        cell_name = Gtk.CellRendererText()
 
508
        cell_name.set_property('ellipsize', Pango.EllipsizeMode.END)
 
509
        column_name = Gtk.TreeViewColumn(_('Label'), cell_name)
 
510
        column_name.set_cell_data_func(cell_name, column_data_func, 1)
 
511
        column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
 
512
        column_name.set_resizable(True)
 
513
        column_name.set_expand(True)
 
514
        column_name.set_min_width(75)
 
515
        self.dest_treeview.append_column(column_name)
 
516
 
 
517
        cell_capacity = Gtk.CellRendererText()
 
518
        cell_capacity.set_property('ellipsize', Pango.EllipsizeMode.END)
 
519
        column_name = Gtk.TreeViewColumn(_('Capacity'), cell_capacity)
 
520
        column_name.set_cell_data_func(cell_capacity, column_data_func, 2)
 
521
        column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
 
522
        column_name.set_resizable(True)
 
523
        column_name.set_expand(False)
 
524
        column_name.set_min_width(75)
 
525
        self.dest_treeview.append_column(column_name)
615
526
 
616
527
    def add_file_source_dialog(self, *args):
617
528
        filename = ''
618
 
        chooser = Gtk.FileChooserDialog(title=None,action=Gtk.FileChooserAction.OPEN,
619
 
            buttons=(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK))
 
529
        chooser = Gtk.FileChooserDialog(title=None, parent=self.window,
 
530
                                        action=Gtk.FileChooserAction.OPEN,
 
531
                                        buttons=(Gtk.STOCK_CANCEL,
 
532
                                                 Gtk.ResponseType.CANCEL,
 
533
                                                 Gtk.STOCK_OPEN,
 
534
                                                 Gtk.ResponseType.OK))
620
535
        def _add_filter(p, n):
621
536
            filter = Gtk.FileFilter()
622
537
            filter.add_pattern(p)
623
538
            filter.set_name(n)
624
539
            chooser.add_filter(filter)
625
540
 
626
 
        if not self.fastboot_mode:
627
 
            _add_filter('*.iso', _('CD Images'))
 
541
        _add_filter('*.iso', _('CD Images'))
628
542
        _add_filter('*.img', _('Disk Images'))            
629
543
 
630
544
        folder = os.path.expanduser('~')
644
558
                    return
645
559
                iterator = model.iter_next(iterator)
646
560
            self.backend.add_image(filename)
647
 
        elif response == Gtk.ResponseType.CANCEL:
 
561
        else:
648
562
            chooser.destroy()
649
563
 
650
564
    def install(self, widget):
651
565
        source = self.get_source()
652
566
        target = self.get_target()
653
 
        persist = self.get_persistence()
654
 
        if self.fastboot_mode and not misc.check_eula():    
655
 
            if self.show_eula() == Gtk.ResponseType.CANCEL:
656
 
                return
657
 
        # TODO evand 2009-07-31: Make these the default values in the
658
 
        # GtkBuilder file.
659
 
        starting_up = _('Starting up...')
660
 
        self.progress_title.set_markup('<big><b>' + starting_up + '</b></big>')
661
 
        self.progress_info.set_text('')
662
567
        if source and target:
 
568
            d = Gtk.MessageDialog(transient_for=self.window, modal=True,
 
569
                    message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO)
 
570
            d.set_markup(_('Are you sure you want to write the disc image to the device?'))
 
571
            d.format_secondary_markup(_('All existing data will be lost.'))
 
572
            response = d.run()
 
573
            d.destroy()
 
574
            if response != Gtk.ResponseType.YES:
 
575
                return
 
576
 
 
577
            starting_up = _('Starting up...')
 
578
            self.progress_title.set_markup('<big><b>' + starting_up + '</b></big>')
 
579
            self.progress_info.set_text('')
 
580
 
663
581
            self.install_window.show()
664
582
            self.window.hide()
665
583
            self.unity.show_progress()
666
584
            self.delete_timeout(self.update_loop)
667
585
            try:
668
 
                self.backend.install(source, target, persist,
 
586
                self.backend.install(source, target,
669
587
                                     allow_system_internal=self.allow_system_internal)
670
588
            except:
671
589
                self._fail()
672
590
 
673
591
    @thread_wrap
674
 
    def progress(self, complete, remaining, speed):
 
592
    def progress(self, complete):
675
593
        if self.pulsing:
676
594
            self.progress_info.set_text('')
677
595
            return
680
598
            complete = 100
681
599
        self.progress_bar.set_fraction(complete / 100.0)
682
600
        self.unity.set_progress(complete / 100.0)
683
 
        if remaining and speed:
684
 
            # TODO evand 2009-07-24: Could use a time formatting function
685
 
            # like our human size function.
686
 
            mins = int(remaining / 60)
687
 
            secs = int(remaining % 60)
688
 
            text = _('%d%% complete (%dm%ss remaining)') % \
689
 
                    (complete, mins, secs)
690
 
            self.progress_info.set_text(text)
691
 
        else:
 
601
        if complete < 100:
692
602
            self.progress_info.set_text(_('%d%% complete') % complete)
 
603
        else:
 
604
            self.progress_info.set_text(_('Finishing...'))
693
605
 
694
606
    @thread_wrap
695
607
    def progress_message(self, message):
717
629
        retry_dialog.destroy()
718
630
        return response == Gtk.ResponseType.YES
719
631
 
720
 
    def show_eula(self, unused=None):
721
 
        response = self.eula_dialog.run()
722
 
        if response == Gtk.ResponseType.OK:
723
 
            misc.check_eula(True)
724
 
        self.eula_dialog.hide()
725
 
        return response
726
 
 
727
632
    def quit(self, *args):
728
633
        self.backend.cancel_install()
729
634
        if Gtk.main_level() > 0:
741
646
        self.unity.show_progress(False)
742
647
        if not message:
743
648
            message = _('Installation failed.')
 
649
        self.failed_dialog.set_transient_for(self.install_window)
744
650
        self.failed_dialog_label.set_text(message)
745
651
        self.failed_dialog.run()
746
652
        Gtk.main_quit()
747
653
 
748
654
    @thread_wrap
749
655
    def success(self):
750
 
        self.backend.unmount()
751
656
        self.warning_dialog.hide()
752
657
        self.install_window.hide()
753
658
        self.unity.show_progress(False)
766
671
        except dbus.DBusException:
767
672
            logging.exception('Error checking for kvm:')
768
673
 
769
 
        if self.fastboot_mode:
770
 
            self.finished_dialog_label.set_text(
771
 
                _('Installation is complete. Your device is rebooting into Ubuntu Core.'))
772
 
            self.kvm_test.hide()
 
674
        self.finished_dialog.set_transient_for(self.install_window)
773
675
        self.finished_dialog.run()
774
676
        self.backend.shutdown()
775
677
        Gtk.main_quit()
781
683
        dialog.run()
782
684
        dialog.destroy()
783
685
 
784
 
    @thread_wrap
785
 
    def format_failed(self, message):
786
 
        # TODO sort through error types (message.get_dbus_name()) in backend,
787
 
        # individual functions in frontend for each error type.
788
 
        d = Gtk.MessageDialog(transient_for=self.window, modal=True,
789
 
                              message_type=Gtk.MessageType.ERROR,
790
 
                              buttons=Gtk.ButtonsType.CLOSE)
791
 
        d.set_markup(str(message))
792
 
        d.run()
793
 
        d.destroy()
794
 
 
795
686
    def format_dest_clicked(self, *args):
796
 
        model, iterator = self.dest_treeview.get_selection().get_selected()
797
 
        if not iterator:
798
 
            return
799
 
        udi = model[iterator][0]
800
 
        d = Gtk.MessageDialog(transient_for=self.window, modal=True,
801
 
                message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO)
802
 
        # TODO information about the device we're about to format.
803
 
        d.set_markup(_('Are you sure you want to erase the entire disk?'))
804
 
        response = d.run()
805
 
        d.destroy()
806
 
        if response == Gtk.ResponseType.YES:
807
 
            self.format_dest.set_sensitive(False)
808
 
            self.format_dest_label.set_text(_('Erasing...'))
809
 
            self.format_dest_spinner.show()
810
 
            self.format_dest_spinner.start()
811
 
            self.backend.format(udi)
 
687
        return
812
688
 
813
689
    def open_dest_folder(self, *args):
814
690
        model, iterator = self.dest_treeview.get_selection().get_selected()