[Pkg-privacy-commits] [tails-installer] 115/210: Merge branch 'feature/udisks2' into feature/new-ui

Intrigeri intrigeri at moszumanska.debian.org
Wed May 24 15:26:35 UTC 2017


This is an automated email from the git hooks/post-receive script.

intrigeri pushed a commit to tag 3.90.0
in repository tails-installer.

commit 631ae9ab919cf2c4ca2c0745b850724bfd0ffcca
Merge: 6684521 a3f79a2
Author: Martin Briza <mbriza at redhat.com>
Date:   Thu Jul 2 15:21:43 2015 +0200

    Merge branch 'feature/udisks2' into feature/new-ui
    
    Conflicts:
    	liveusb/creator.py
    	liveusb/gui.py

 README.rst         |   2 +
 liveusb/creator.py | 175 +++++++++++++++++++++++++++++++++++++++++------------
 liveusb/gui.py     |  20 ++----
 3 files changed, 143 insertions(+), 54 deletions(-)

diff --cc liveusb/creator.py
index b204799,b57c41c..85505be
--- a/liveusb/creator.py
+++ b/liveusb/creator.py
@@@ -394,11 -389,8 +394,18 @@@ class LiveUSBCreator(object)
                  return release
  
      def _set_drive(self, drive):
 +        if drive == None:
 +            self._drive = None
 +            return
          if not self.drives.has_key(drive):
--            raise LiveUSBError(_("Cannot find device %s" % drive))
++            found = False
++            for key in self.drives.keys():
++                if self.drives[key]['device'] == drive:
++                    drive = key
++                    found = True
++                    break
++            if not found:
++                raise LiveUSBError(_("Cannot find device %s" % drive))
          self.log.debug("%s selected: %s" % (drive, self.drives[drive]))
          self._drive = drive
          self.uuid = self.drives[drive]['uuid']
@@@ -510,99 -501,124 +516,194 @@@ class LinuxLiveUSBCreator(LiveUSBCreato
                      'that does not support the ext4 filesystem'))
              self.valid_fstypes -= set(['ext4'])
  
-     def detect_removable_drives(self, callback=None):
-         """ Detect all removable USB storage devices using UDisks via D-Bus """
+     def detect_removable_drives(self, callbackAdded=None, callbackRemoved=None):
+         """ Detect all removable USB storage devices using UDisks2 via D-Bus """
          import dbus
+         self.callbackAdded = callbackAdded
+         self.callbackRemoved = callbackRemoved
          self.drives = {}
          self.bus = dbus.SystemBus()
++        """
 +        udisks_obj = self.bus.get_object("org.freedesktop.UDisks",
 +                                         "/org/freedesktop/UDisks")
 +        self.udisks = dbus.Interface(udisks_obj, "org.freedesktop.UDisks")
 +
 +        def handle_reply(devices):
 +            for device in devices:
 +                dev_obj = self.bus.get_object("org.freedesktop.UDisks", device)
 +                dev = dbus.Interface(dev_obj, "org.freedesktop.DBus.Properties")
 +
 +                data = {
 +                    'udi': str(device),
 +                    'is_optical': bool(dev.Get(device, 'DeviceIsOpticalDisc')),
 +                    'label': unicode(dev.Get(device, 'IdLabel')).replace(' ', '_'),
 +                    'fstype': str(dev.Get(device, 'IdType')),
 +                    'fsversion': str(dev.Get(device, 'IdVersion')),
 +                    'uuid': str(dev.Get(device, 'IdUuid')),
 +                    'device': str(dev.Get(device, 'DeviceFile')),
 +                    'mount': map(unicode, list(dev.Get(device, 'DeviceMountPaths'))),
 +                    'bootable': 'boot' in map(str,
 +                        list(dev.Get(device, 'PartitionFlags'))),
 +                    'parent': None,
 +                    'size': int(dev.Get(device, 'DeviceSize')),
 +                    'fullSize': int(dev.Get(device, 'DeviceSize')),
 +                    'model': str(dev.Get(device, 'DriveModel')),
 +                    'vendor': str(dev.Get(device, 'DriveVendor'))
 +                }
 +
 +                # Only pay attention to USB devices, unless --force'd
 +                iface = str(dev.Get(device, 'DriveConnectionInterface'))
 +                if iface != 'usb' and self.opts.force != data['device']:
 +                    self.log.warning('Skipping non-usb drive: %s' % device)
 +                    continue
 +
 +                # Skip optical drives
 +                if data['is_optical'] and self.opts.force != data['device']:
 +                    self.log.debug('Skipping optical device: %s' % data['device'])
 +                    continue
 +
 +                # Skip things without a size
 +                if not data['size'] and not self.opts.force:
 +                    self.log.debug('Skipping device without size: %s' % device)
 +                    continue
 +
 +                # Skip devices with unknown filesystems
 +                if data['fstype'] not in self.valid_fstypes and \
 +                        self.opts.force != data['device']:
 +                    self.log.debug('Skipping %s with unknown filesystem: %s' % (
 +                        data['device'], data['fstype']))
 +                    continue
 +
 +                parent = dev.Get(device, 'PartitionSlave')
 +                if parent and parent != '/':
 +                    data['parent'] = str(dbus.Interface(self._get_device(parent),
 +                            'org.freedesktop.DBus.Properties').Get(parent,
 +                                'DeviceFile'))
 +                    data['fullSize'] = int(dbus.Interface(self._get_device(parent),
 +                            'org.freedesktop.DBus.Properties').Get(parent,
 +                                'DeviceSize'))
 +
 +                mount = data['mount']
 +                if mount:
 +                    if len(mount) > 1:
 +                        self.log.warning('Multiple mount points for %s' %
 +                                data['device'])
 +                    mount = data['mount'] = data['mount'][0]
 +                else:
 +                    mount = data['mount'] = None
++        """
+         udisks_obj = self.bus.get_object("org.freedesktop.UDisks2",
+                                          "/org/freedesktop/UDisks2")
+         self.udisks = dbus.Interface(udisks_obj, 'org.freedesktop.DBus.ObjectManager')
+ 
+         def strify(s):
+             return bytearray(s).replace(b'\x00', b'').decode('utf-8')
+ 
+         def handleAdded(name, device):
+             if ('org.freedesktop.UDisks2.Block' in device and
+                 'org.freedesktop.UDisks2.Filesystem' in device and
+                 'org.freedesktop.UDisks2.Partition' in device):
+                 self.log.debug('Found block device with filesystem on %s' % name)
+             else:
+                 return
+ 
+             partition = device['org.freedesktop.UDisks2.Partition']
+             fs = device['org.freedesktop.UDisks2.Filesystem']
+             blk = device['org.freedesktop.UDisks2.Block']
+ 
+             if blk['Drive'] == '/':
+                 self.log.debug('Skipping root drive: %s' % name)
+                 return
+ 
+             drive_obj = self.bus.get_object("org.freedesktop.UDisks2", blk['Drive'])
+             drive = dbus.Interface(drive_obj, "org.freedesktop.DBus.Properties").GetAll("org.freedesktop.UDisks2.Drive")
+ 
+             # this is probably the only check we need, including Drive != "/"
+             if (not drive[u'Removable'] or
+                 drive[u'Optical'] or
+                     (drive[u'ConnectionBus'] != 'usb' and
+                      drive[u'ConnectionBus'] != 'sdio')):
+                 self.log.debug('Skipping a device that is not removable, connected via USB or is optical: %s' % name)
+                 return
+ 
+             data = {
+                 'udi': str(blk['Drive']),
+                 'label': str(blk['IdLabel']),
+                 'fstype': str(blk['IdType']),
+                 'fsversion': str(blk['IdVersion']),
+                 'uuid': str(blk['IdUUID']),
+                 'device': strify(blk['Device']),
+                 'mount': map(strify, fs['MountPoints']),
+                 'size': int(blk['Size']),
+             }
+             self.log.debug('data = %r' % data)
+ 
+             if '/boot' in data['mount']:
+                 self.log.debug('Skipping boot device: %s' % name)
+                 return
+ 
+             # Skip things without a size
+             if not data['size'] and not self.opts.force:
+                 self.log.debug('Skipping device without size: %s' % device)
+                 return
+ 
+             # Skip devices with unknown filesystems
+             if data['fstype'] not in self.valid_fstypes and \
+                     self.opts.force != data['device']:
+                 self.log.debug('Skipping %s with unknown filesystem: %s' % (
+                     data['device'], data['fstype']))
+                 return
+ 
+             mount = data['mount']
+             if mount:
+                 if len(mount) > 1:
+                     self.log.warning('Multiple mount points for %s' %
+                             data['device'])
+                 mount = data['mount'] = data['mount'][0]
+             else:
+                 mount = data['mount'] = None
  
-                 data['free'] = mount and \
-                         self.get_free_bytes(mount) / 1024**2 or None
+             data['free'] = mount and \
+                     self.get_free_bytes(mount) / 1024**2 or None
  
-                 self.log.debug(pformat(data))
 +
-                 self.drives[data['device']] = data
+             parent_obj = self.bus.get_object("org.freedesktop.UDisks2", partition[u'Table'])
+             parent = dbus.Interface(parent_obj, "org.freedesktop.DBus.Properties").Get("org.freedesktop.UDisks2.Block", "Device")
+             data['parent'] = strify(parent)
  
-             # Remove parent drives if a valid partition exists
-             for parent in [d['parent'] for d in self.drives.values()]:
-                 if parent in self.drives:
-                     del(self.drives[parent])
+             self.log.debug(pformat(data))
  
-             if callback:
-                 callback()
+             self.drives[name] = data
  
-         def handle_error(error):
-             self.log.error(str(error))
+             if self.callbackAdded:
+                 self.callbackAdded()
  
-         self.udisks.EnumerateDevices(reply_handler=handle_reply,
-                                      error_handler=handle_error)
+         def handleRemoved(path, interfaces):
+ 
+             print ("KEYS!", path, self.drives.keys())
+             if self.drives.has_key(path):
+                 print("PRE REMOVED", path, interfaces)
+                 del self.drives[path]
+                 print("POST REMOVED", path, interfaces)
+ 
+             if self.callbackRemoved:
+                 self.callbackRemoved()
+             #blk = device['org.freedesktop.UDisks2.Block']
+             #if path.startswith("/org/freedesktop/UDisks2"):
+ 
+             #if 'org.freedesktop.UDisks2.Block' in interfaces:
+                 #object = self.bus.get_object("org.freedesktop.UDisks2", path)
+                 #device = dbus.Interface(object, "org.freedesktop.UDisks2.Block").GetAll("Drive")
+                 #print device
+             #print("FOUND:", device['org.freedesktop.UDisks2.Block'])
+             #print("GOT:", self.drives.keys())
+             #callbackRemoved()
+ 
+         self.bus.add_signal_receiver(handleAdded, "InterfacesAdded", "org.freedesktop.DBus.ObjectManager", "org.freedesktop.UDisks2", "/org/freedesktop/UDisks2")
+         self.bus.add_signal_receiver(handleRemoved, "InterfacesRemoved", "org.freedesktop.DBus.ObjectManager", "org.freedesktop.UDisks2", "/org/freedesktop/UDisks2")
+ 
+         for name, device in self.udisks.GetManagedObjects().iteritems():
+             handleAdded(name, device)
  
      def _storage_bus(self, dev):
          storage_bus = None
diff --cc liveusb/gui.py
index 0deb08e,5360917..5da2857
--- a/liveusb/gui.py
+++ b/liveusb/gui.py
@@@ -216,776 -152,510 +216,768 @@@ class ReleaseWriterProgressThread(QThre
          self.alive = False
  
      def terminate(self):
 -        self.emit(QtCore.SIGNAL("progress(int)"), self.totalsize)
 -        QtCore.QThread.terminate(self)
 +        self.parent().progress = self.totalSize
 +        self.terminate()
  
  
 -class LiveUSBThread(QtCore.QThread):
 +class ReleaseWriterThread(QThread):
 +    """ The actual write to the portable drive """
  
 -    def __init__(self, live, progress, parent=None):
 -        QtCore.QThread.__init__(self, parent)
 -        self.progress = progress
 +    def __init__(self, parent, progressThread):
 +        QThread.__init__(self, parent)
 +
 +        self.live = parent.live
          self.parent = parent
 -        self.live = live
 +        self.progressThread = progressThread
  
 -    def status(self, text):
 -        self.emit(QtCore.SIGNAL("status(PyQt_PyObject)"), text)
  
      def run(self):
 -        handler = LiveUSBLogHandler(self.status)
 -        self.live.log.addHandler(handler)
 +        # TODO move this to the backend
 +        #handler = LiveUSBLogHandler(self.parent.status)
 +        #self.live.log.addHandler(handler)
          now = datetime.now()
          try:
 -            #if self.parent.opts.format:
 -            #    self.live.unmount_device()
 -            #    self.live.format_device()
 -
 -            # Initialize zip-drive-compatible geometry
 -            #if self.parent.opts.zip:
 -            #    self.live.dest = self.live.drive['mount']
 -            #    self.live.drive['unmount'] = True
 -            #    self.live.unmount_device()
 -            #    self.live.initialize_zip_geometry()
 -            #    self.live.drive = self.parent.get_selected_drive()
 -            #    self.live.dest = self.live.drive['mount']
 -            #    self.live.drive['unmount'] = True
 -            #    self.live.unmount_device()
 -            #    self.live.format_device()
 -
 -            # If we're going to dd the image
 -            if self.parent.destructiveButton.isChecked():
 -                self.parent.progressBar.setRange(0, 0)
 -                self.live.dd_image()
 -                self.live.log.removeHandler(handler)
 -                duration = str(datetime.now() - now).split('.')[0]
 -                self.status(_("Complete! (%s)") % duration)
 -                self.parent.progressBar.setRange(0, 1)
 -                return
 +            if self.parent.release.liveUSBData.option('dd'):
 +                self.ddImage(now)
 +            else:
 +                self.copyImage(now)
 +        except Exception, e:
 +            self.parent.release.addError(e.args[0])
 +            self.live.log.exception(e)
  
 -            self.live.verify_filesystem()
 -            if not self.live.drive['uuid'] and not self.live.label:
 -                self.status(_("Error: Cannot set the label or obtain "
 -                              "the UUID of your device.  Unable to continue."))
 -                self.live.log.removeHandler(handler)
 +        self.parent.running = False
 +        #self.live.log.removeHandler(handler)
 +
 +    def ddImage(self, now):
 +        # TODO move this to the backend
 +        self.live.dd_image()
 +        #self.live.log.removeHandler(handler)
 +        #duration = str(datetime.now() - now).split('.')[0]
 +        self.parent.status = 'Finished!'
 +        self.parent.finished = True
 +        self.progressThread.stop()
 +        return
 +
 +    def copyImage(self, now):
 +        # TODO move this to the backend
 +
 +        self.parent.status = _('Checking the source image')
 +        self.live.check_free_space()
 +
 +        if not self.live.opts.noverify:
 +            # Verify the MD5 checksum inside of the ISO image
 +            if not self.live.verify_iso_md5():
 +                #self.live.log.removeHandler(handler)
                  return
  
 -            self.live.check_free_space()
 -
 -            if not self.parent.opts.noverify:
 -                # Verify the MD5 checksum inside of the ISO image
 -                if not self.live.verify_iso_md5():
 -                    self.live.log.removeHandler(handler)
 +            # If we know about this ISO, and it's SHA1 -- verify it
 +            release = self.live.get_release_from_iso()
 +            if release and ('sha1' in release or 'sha256' in release):
 +                if not self.live.verify_iso_sha1(self):
 +                    #self.live.log.removeHandler(handler)
                      return
  
 -                # If we know about this ISO, and it's SHA1 -- verify it
 -                release = self.live.get_release_from_iso()
 -                if release and ('sha1' in release or 'sha256' in release):
 -                    if not self.live.verify_iso_sha1(progress=self):
 -                        self.live.log.removeHandler(handler)
 -                        return
 +        self.parent.status = _('Unpacking the image')
 +        # Setup the progress bar
 +        self.progressThread.set_data(size=self.live.totalsize,
 +                                     drive=self.live.drive['device'],
 +                                     freebytes=self.live.get_free_bytes)
 +        self.progressThread.start()
  
 -            # Setup the progress bar
 -            self.progress.set_data(size=self.live.totalsize,
 -                                   drive=self.live.drive['device'],
 -                                   freebytes=self.live.get_free_bytes)
 -            self.progress.start()
 +        self.live.extract_iso()
  
 -            self.live.extract_iso()
 -            self.live.create_persistent_overlay()
 -            self.live.update_configs()
 -            self.live.install_bootloader()
 -            self.live.bootable_partition()
 +        if self.live.blank_mbr() or self.parent.release.liveUSBData.option('resetMBR'):
 +            self.live.reset_mbr()
  
 -            if self.parent.opts.device_checksum:
 -                self.live.calculate_device_checksum(progress=self)
 -            if self.parent.opts.liveos_checksum:
 -                self.live.calculate_liveos_checksum()
 +        self.parent.status = _('Writing the data')
 +        self.live.create_persistent_overlay()
 +        self.live.update_configs()
 +        self.live.install_bootloader()
 +        self.live.bootable_partition()
  
 -            self.progress.stop()
 +        self.parent.status = _('Checking the written data')
 +        if self.live.opts.device_checksum:
 +            self.live.calculate_device_checksum(progressThread=self)
 +        if self.live.opts.liveos_checksum:
 +            self.live.calculate_liveos_checksum()
  
 -            # Flush all filesystem buffers and unmount
 -            self.live.flush_buffers()
 -            self.live.unmount_device()
 +        self.progressThread.stop()
  
 -            duration = str(datetime.now() - now).split('.')[0]
 -            self.status(_("Complete! (%s)" % duration))
 +        # Flush all filesystem buffers and unmount
 +        self.live.flush_buffers()
 +        self.live.unmount_device()
 +        self.parent.status = _('Finished!')
 +        self.parent.finished = True
  
 -        except Exception, e:
 -            self.status(e.args[0])
 -            self.status(_("LiveUSB creation failed!"))
 -            self.live.log.exception(e)
 +        duration = str(datetime.now() - now).split('.')[0]
 +        #self.parent.status = 'Complete! (%s)' % duration
  
 -        self.live.log.removeHandler(handler)
 -        self.progress.terminate()
 +        self.progressThread.stop()
  
      def set_max_progress(self, maximum):
 -        self.emit(QtCore.SIGNAL("maxprogress(int)"), maximum)
 +        self.parent.maxProgress = maximum
  
      def update_progress(self, value):
 -        self.emit(QtCore.SIGNAL("progress(int)"), value)
 +        self.parent.progress = value
 +
 +class ReleaseWriter(QObject):
 +    """ Here we can track the progress of the writing and control it """
 +    runningChanged = pyqtSignal()
 +    currentChanged = pyqtSignal()
 +    maximumChanged = pyqtSignal()
 +    statusChanged = pyqtSignal()
 +    finishedChanged = pyqtSignal()
 +
 +    _running = False
 +    _current = -1.0
 +    _maximum = -1.0
 +    _status = ''
 +    _finished = False
 +
 +    def __init__(self, parent):
 +        QObject.__init__(self, parent)
 +        self.live = parent.live
 +        self.release = parent
 +        self.progressWatcher = ReleaseWriterProgressThread(self)
 +        self.worker = ReleaseWriterThread(self, self.progressWatcher)
 +
 +    def reset(self):
 +        self._running = False
 +        self._current = -1.0
 +        self._maximum = -1.0
 +        self.runningChanged.emit()
 +        self.currentChanged.emit()
 +        self.maximumChanged.emit()
 +
 +    @pyqtSlot()
 +    def run(self):
 +        self._running = True
 +        self._current = 0.0
 +        self._maximum = 100.0
 +        self.runningChanged.emit()
 +        self.currentChanged.emit()
 +        self.maximumChanged.emit()
 +        self.status = 'Writing'
 +        self.worker.start()
 +
 +    @pyqtSlot()
 +    def cancel(self):
 +        self.progressWatcher.stop()
 +        self.worker.terminate()
 +        self.reset()
 +
 +    @pyqtProperty(bool, notify=runningChanged)
 +    def running(self):
 +        return self._running
 +
 +    @running.setter
 +    def running(self, value):
 +        if self._running != value:
 +            self._running = value
 +            self.runningChanged.emit()
 +
 +    @pyqtProperty(float, notify=maximumChanged)
 +    def maxProgress(self):
 +        return self._maximum
 +
 +    @maxProgress.setter
 +    def maxProgress(self, value):
 +        if (value != self._maximum):
 +            self._maximum = value
 +            self.maximumChanged.emit()
 +
 +    @pyqtProperty(float, notify=currentChanged)
 +    def progress(self):
 +        return self._current
 +
 +    @progress.setter
 +    def progress(self, value):
 +        if (value != self._current):
 +            self._current = value
 +            self.currentChanged.emit()
 +
 +    @pyqtProperty(str, notify=statusChanged)
 +    def status(self):
 +        return self._status
 +
 +    @status.setter
 +    def status(self, s):
 +        if self._status != s:
 +            self._status = s
 +            self.statusChanged.emit()
 +
 +    @pyqtProperty(bool, notify=finishedChanged)
 +    def finished(self):
 +        return self._finished
 +
 +    @finished.setter
 +    def finished(self, value):
 +        if self._finished != value:
 +            self._finished = value
 +            self.finishedChanged.emit()
 +
 +
 +class Release(QObject):
 +    ''' Contains the information about the particular release of Fedora
 +        I think there should be a cleanup of all the properties - there seem to be more of them than needed
 +    '''
 +    screenshotsChanged = pyqtSignal()
 +    errorChanged = pyqtSignal()
 +    warningChanged = pyqtSignal()
 +    infoChanged = pyqtSignal()
 +    statusChanged = pyqtSignal()
 +    pathChanged = pyqtSignal()
 +    sizeChanged = pyqtSignal()
 +
 +    def __init__(self, parent, index, live, data):
 +        QObject.__init__(self, parent)
 +
 +        self._index = index
 +        self.live = live
 +        self.liveUSBData = parent
  
 -    def __del__(self):
 -        self.wait()
 +        self._data = data
 +        """
 +        self._name = name.replace('_', ' ')
 +        self._logo = logo
 +        self._size = size
 +        self._arch = arch
 +        self._fullName = fullName
 +        self._releaseDate = releaseDate
 +        self._summary = summary
 +        self._fullDescription = fullDescription
 +        self._isLocal = isLocal
 +        self._screenshots = screenshots
 +        self._url = url
 +        """
  
 +        self._path = ''
  
 -class LiveUSBLogHandler(logging.Handler):
 +        self._info = []
 +        self._warning = []
 +        self._error = []
  
 -    def __init__(self, cb):
 -        logging.Handler.__init__(self)
 -        self.cb = cb
 +        """
 +        if self._logo == '':
 +            if self._name == 'Fedora Workstation':
 +                self._logo = 'qrc:/logo-color-workstation.png'
 +            elif self._name == 'Fedora Server':
 +                self._logo = 'qrc:/logo-color-server.png'
 +            elif self._name == 'Fedora Cloud':
 +                self._logo = 'qrc:/logo-color-cloud.png'
 +            elif self._name == 'Fedora KDE':
 +                self._logo = 'qrc:/logo-plasma5.png'
 +            elif self._name == 'Fedora Xfce':
 +                self._logo = 'qrc:/logo-xfce.svg'
 +            elif self._name == 'Fedora LXDE':
 +                self._logo = 'qrc:/logo-lxde.png'
 +            else:
 +                self._logo = 'qrc:/logo-fedora.svg'
 +
 +        if self._name == 'Fedora Workstation':
 +            self._fullDescription = _('Fedora Workstation is a reliable, user-friendly, and powerful operating system for your laptop or desktop computer. It supports a wide range of developers, from hobbyists and students to professionals in corporate environments.')
 +        if self._name == 'Fedora Server':
 +            self._fullDescription = _('Fedora Server is a powerful, flexible operating system that includes the best and latest datacenter technologies. It puts you in control of all your infrastructure and services.')
 +        if self._name == 'Fedora Cloud':
 +            self._fullDescription = _('Fedora Cloud provides a minimal image of Fedora for use in public and private cloud environments. It includes just the bare essentials, so you get enough to run your cloud application -- and nothing more.')
  
 -    def emit(self, record):
 -        if record.levelname in ('INFO', 'ERROR', 'WARN'):
 -            self.cb(record.msg)
 +        """
  
 +        self._download = ReleaseDownload(self)
 +        self._download.pathChanged.connect(self.pathChanged)
  
 -class LiveUSBWindow(QtGui.QMainWindow, LiveUSBInterface):
 -    """ Our main dialog class """
 +        self._writer = ReleaseWriter(self)
  
 -    def __init__(self, opts, args):
 -        self.in_process = False
 -        QtGui.QMainWindow.__init__(self)
 -        LiveUSBInterface.__init__(self)
 -        self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
 -        self.opts = opts
 -        self.args = args
 -        self.setupUi(self)
 -        self.live = LiveUSBCreator(opts=opts)
 -        self.populate_releases()
 -        self.populate_devices()
 -        self.downloader = None
 -        self.progress_thread = ProgressThread()
 -        self.download_progress = DownloadProgress()
 -        self.live_thread = LiveUSBThread(live=self.live,
 -                                         progress=self.progress_thread,
 -                                         parent=self)
 -        self.connect_slots()
 -        self.confirmed = False
 -        self.mbr_reset_confirmed = False
 -
 -        if self.opts.destructive:
 -            self.destructiveButton.setChecked(True)
 -
 -        # Intercept all liveusb INFO log messages, and display them in the gui
 -        self.handler = LiveUSBLogHandler(lambda x: self.textEdit.append(x))
 -        self.live.log.addHandler(self.handler)
 -        if not self.opts.verbose:
 -            self.live.log.removeHandler(self.live.handler)
 -
 -        # If an ISO was specified on the command line, use it.
 -        if args:
 -            for arg in self.args:
 -                if arg.lower().endswith('.iso') and os.path.exists(arg):
 -                    self.selectfile(arg)
 -
 -        # Determine if we have admin rights
 -        if not self.live.is_admin():
 -            self.live.log.error(_('Warning: This tool needs to be run as an '
 -                'Administrator. To do this, right click on the icon and open '
 -                'the Properties. Under the Compatibility tab, check the "Run '
 -                'this program as an administrator" box.'))
 -
 -    def populate_devices(self, *args, **kw):
 -        if self.in_process:
 -            return
 -        self.driveBox.clear()
 -        #self.textEdit.clear()
 -
 -        def update_devices():
 -            self.driveBox.clear()
 -            if len(self.live.drives) <= 0:
 -                self.textEdit.setPlainText(_("Unable to find any USB drives"))
 -                self.startButton.setEnabled(False)
 -                return
 -            for device, info in self.live.drives.items():
 -                if info['label']:
 -                    self.driveBox.addItem("%s (%s)" % (info['device'], info['label']))
 -                else:
 -                    self.driveBox.addItem(device)
 -            self.startButton.setEnabled(True)
 +        self._download.runningChanged.connect(self.inspectDestination)
  
 -        try:
 -            self.live.detect_removable_drives(callbackAdded=update_devices,callbackRemoved=update_devices)
 -        except LiveUSBError, e:
 -            self.textEdit.setPlainText(e.args[0])
 -            self.startButton.setEnabled(False)
 +        self.pathChanged.connect(self.statusChanged)
 +        self._download.runningChanged.connect(self.statusChanged)
 +        self._writer.runningChanged.connect(self.statusChanged)
 +        self._writer.statusChanged.connect(self.statusChanged)
  
 -        update_devices()
  
 -    def populate_releases(self):
 -        for release in [release['name'] for release in releases]:
 -            self.downloadCombo.addItem(release)
 +    @pyqtSlot()
 +    def get(self):
 +        if len(self._path) <= 0:
 +            self._download.run()
  
 -    def refresh_releases(self):
 -        self.live.log.info(_('Refreshing releases...'))
 -        global releases
 -        try:
 -            releases = get_fedora_releases()
 -            self.downloadCombo.clear()
 -            for release in [release['name'] for release in releases]:
 -                self.downloadCombo.addItem(release)
 -            self.live.log.info(_('Releases updated!'))
 -        except Exception, e:
 -            self.live.log.error(_('Unable to fetch releases: %r') % e)
 -
 -    def connect_slots(self):
 -        self.connect(self, QtCore.SIGNAL('triggered()'), self.terminate)
 -        self.connect(self.isoBttn, QtCore.SIGNAL("clicked()"), self.selectfile)
 -        self.connect(self.startButton, QtCore.SIGNAL("clicked()"), self.begin)
 -        self.connect(self.overlaySlider, QtCore.SIGNAL("valueChanged(int)"),
 -                     self.overlay_value)
 -        self.connect(self.live_thread, QtCore.SIGNAL("status(PyQt_PyObject)"),
 -                     self.status)
 -        self.connect(self.live_thread, QtCore.SIGNAL("finished()"),
 -                     lambda: self.enable_widgets(True))
 -        self.connect(self.live_thread, QtCore.SIGNAL("terminated()"),
 -                     lambda: self.enable_widgets(True))
 -        self.connect(self.live_thread, QtCore.SIGNAL("progress(int)"),
 -                     self.progress)
 -        self.connect(self.live_thread, QtCore.SIGNAL("maxprogress(int)"),
 -                     self.maxprogress)
 -        self.connect(self.progress_thread, QtCore.SIGNAL("progress(int)"),
 -                     self.progress)
 -        self.connect(self.progress_thread, QtCore.SIGNAL("maxprogress(int)"),
 -                     self.maxprogress)
 -        self.connect(self.download_progress, QtCore.SIGNAL("maxprogress(int)"),
 -                     self.maxprogress)
 -        self.connect(self.download_progress, QtCore.SIGNAL("progress(int)"),
 -                     self.progress)
 -        self.connect(self.destructiveButton, QtCore.SIGNAL("toggled(bool)"),
 -                     self.method_destructive_toggled)
 -        self.connect(self.nonDestructiveButton, QtCore.SIGNAL("toggled(bool)"),
 -                     self.method_nondestructive_toggled)
 -        if hasattr(self, 'refreshDevicesButton'):
 -            self.connect(self.refreshDevicesButton, QtCore.SIGNAL("clicked()"),
 -                         self.populate_devices)
 -        if hasattr(self, 'refreshReleasesButton'):
 -            self.connect(self.refreshReleasesButton, QtCore.SIGNAL("clicked()"),
 -                         self.refresh_releases)
 -
 -        # If we have access to HAL & DBus, intercept some useful signals
 -        if hasattr(self.live, 'udisks'):
 -            self.live.udisks.connect_to_signal('DeviceAdded',
 -                                            self.populate_devices)
 -            self.live.udisks.connect_to_signal('DeviceRemoved',
 -                                            self.populate_devices)
 -
 -    @QtCore.pyqtSignature("QString")
 -    def on_driveBox_currentIndexChanged(self, drive):
 -        """ Change the maximum overlay size when each drive is selected.
 -
 -        This sets the maximum megabyte size of the persistent storage slider
 -        to the number of free megabytes on the currently selected
 -        "Target Device".  If the device is not mounted, or if it has more than
 -        2gigs of free space, set the maximum to 2047mb, which is apparently
 -        the largest file we can/should store on a vfat partition.
 -        """
 -        drive = unicode(drive)
 -        if not drive:
 -            return
 -        print self.live.drives
 -        for key in self.live.drives:
 -            if self.live.drives[key]['device'] == drive.split()[0]:
 -                self._refresh_overlay_slider(key)
 +    @pyqtSlot()
 +    def write(self):
 +        self._warning = []
 +        self._error = []
 +        self.errorChanged.emit()
 +        self.warningChanged.emit()
 +        self._writer.run()
  
 -    def _refresh_overlay_slider(self, drive=None):
 -        """
 -        Reset the persistent storage slider based on the amount of free space
 -        on the device and the ISO size.
 -        """
 -        if not drive:
 -            drive = self.get_selected_drive()
 -            if not drive:
 -                return
 +    @pyqtSlot()
 +    def inspectDestination(self):
 +        self._warning = []
 +        self.warningChanged.emit()
 +        self._info = []
 +        self.infoChanged.emit()
 +        self._error = []
 +        self.errorChanged.emit()
 +
 +        if not self.live.drive:
 +            return
  
 -        device = self.live.drives[drive]
 -        freespace = device['free']
 -        device_size = device['size'] / 1024**2
 -        current_overlay = self.overlaySlider.value()
 -
 -        if device['fsversion'] == 'FAT32':
 -            self.live.log.debug(_('Partition is FAT32; Restricting overlay '
 -                                  'size to 4G'))
 -            max_space = MAX_FAT32
 -        elif device['fsversion'] == 'FAT16':
 -            self.live.log.debug(_('Partition is FAT16; Restricting overlay '
 -                                  'size to 2G'))
 -            max_space = MAX_FAT16
 +        if self.parent().option('dd'):
 +            self.addWarning(_('You are about to perform a destructive install. This will erase all data and partitions on your USB drive'))
          else:
 -            max_space = MAX_EXT
 -
 -        if freespace:
 -            if freespace > device_size:
 -                freespace = device_size
 -            if freespace > max_space:
 -                freespace = max_space
 -
 -        if not device['mount']:
 -            self.live.log.warning(_('Device is not yet mounted, so we cannot '
 -                                    'determine the amount of free space.'))
 -            if not freespace:
 -                freespace = device_size
 +            if self.live.blank_mbr():
 +                self.addInfo(_('The Master Boot Record on your device is blank. Writing the image will reset the MBR on this device'))
 +            elif not self.live.mbr_matches_syslinux_bin() and not self.parent().option('resetMBR'):
 +                self.addInfo(_('The Master Boot Record on your device does not match your system\'s syslinux MBR.\n'
 +                              'If you have trouble booting it, try setting the \"Reset the MBR\" advanced option.'))
 +
 +        try:
 +            self.live.mount_device()
 +        except LiveUSBError, e:
 +            self.info = e.args[0]
 +            self._running = False
 +            self.runningChanged.emit()
 +        except OSError, e:
 +            self.addInfo(_('Unable to mount device'))
 +            self._running = False
 +            self.runningChanged.emit()
 +
 +        if self.live.existing_liveos() and not self.parent().option('dd'):
 +            self.addWarning(_('Your device already contains a live OS. If you continue, it will be overwritten.'))
 +
 +        self.live.verify_filesystem()
 +        if not self.live.drive['uuid'] and not self.live.label:
 +            self.parent.status = _('Error: Cannot set the label or obtain '
 +                          'the UUID of your device.  Unable to continue.')
 +            #self.live.log.removeHandler(handler)
 +            return
 +
 +    @pyqtProperty(int, constant=True)
 +    def index(self):
 +        return self._index
 +
 +    @pyqtProperty(str, constant=True)
 +    def name(self):
 +        return self._data['name']
 +
 +    @pyqtProperty(str, constant=True)
 +    def logo(self):
 +        return self._data['logo']
 +
 +    @pyqtProperty(float, notify=sizeChanged)
 +    def size(self):
 +        return self._data['size']
 +
 +    @size.setter
 +    def size(self, value):
 +        if value != self._size:
 +            self._data['size'] = value
 +            self.sizeChanged.emit()
 +
 +    @pyqtProperty(str, constant=True)
 +    def arch(self):
 +        return self._data['arch']
 +
 +    @pyqtProperty(QDateTime, constant=True)
 +    def releaseDate(self):
 +        return QDateTime.fromString(self._data['releaseDate'])
 +
 +    @pyqtProperty(str, constant=True)
 +    def summary(self):
 +        return self._data['summary']
 +
 +    @pyqtProperty(str, constant=True)
 +    def description(self):
 +        return self._data['description']
 +
 +    @pyqtProperty(bool, constant=True)
 +    def isLocal(self):
 +        return self._data['source'] == 'Local'
 +
 +    @pyqtProperty('QStringList', notify=screenshotsChanged)
 +    def screenshots(self):
 +        return self._data['screenshots']
 +
 +    @pyqtProperty(str, constant=True)
 +    def url(self):
 +        return self._data['url']
 +
 +    @pyqtProperty(str, notify=pathChanged)
 +    def path(self):
 +        return self._download.path
 +
 +    @path.setter
 +    def path(self, value):
 +        if value.startswith('file://'):
 +            value = value.replace('file://', '', 1)
 +        if self._path != value:
 +            self._download.path = value
 +            self.pathChanged.emit();
 +            self.size = self.live.isosize
 +
 +    @pyqtProperty(bool, notify=pathChanged)
 +    def readyToWrite(self):
 +        return len(self.path) != 0
 +
 +    @pyqtProperty(ReleaseDownload, constant=True)
 +    def download(self):
 +        return self._download
 +
 +    @pyqtProperty(ReleaseWriter, constant=True)
 +    def writer(self):
 +        return self._writer
 +
 +    @pyqtProperty(str, notify=statusChanged)
 +    def status(self):
 +        if not self._download.running and not self.readyToWrite and not self._writer.running:
 +            return _('Starting')
 +        elif self._download.running:
 +            return _('Downloading')
 +        elif len(self._error) > 0:
 +            return _('Error')
 +        elif self.readyToWrite and not self._writer.running and not self._writer.finished:
 +            return _('Ready to write')
 +        elif self._writer.status:
 +            return self._writer.status
          else:
 -            if not freespace:
 -                self.live.log.warning(_('No free space on %s') % drive)
 -                freespace = 0
 +            return _('Finished')
 +
 +    @pyqtProperty('QStringList', notify=infoChanged)
 +    def info(self):
 +        return self._info
 +
 +    @info.setter
 +    def info(self, value):
 +        if self._info != value:
 +            self._info = value
 +            self.infoChanged.emit()
 +
 +    def addInfo(self, value):
 +        if value not in self._info:
 +            self._info.append(value)
 +            self.infoChanged.emit()
 +
 +    @pyqtProperty('QStringList', notify=warningChanged)
 +    def warning(self):
 +        return self._warning
 +
 +    def addWarning(self, value):
 +        if value not in self._warning:
 +            self._warning.append(value)
 +            self.warningChanged.emit()
 +
 +    @pyqtProperty('QStringList', notify=errorChanged)
 +    def error(self):
 +        return self._error
 +
 +    def addError(self, value):
 +        if value not in self._error:
 +            self._error.append(value)
 +            self.errorChanged.emit()
 +
 +class ReleaseListModel(QAbstractListModel):
 +    """ An abstraction over the list of releases to have them nicely exposed to QML and ready to be filtered
 +    """
 +    def __init__(self, parent):
 +        QAbstractListModel.__init__(self, parent)
  
 -        # Subtract the size of the ISO from our maximum overlay size
 -        if self.live.isosize:
 -            iso_size = self.live.isosize / 1024**2
 -            if freespace + iso_size > device['free']:
 -                freespace -= iso_size
 +    def rowCount(self, parent=QModelIndex()):
 +        return len(self.parent().releaseData)
  
 -        freespace -= 1 # Don't fill the device 100%
 +    def roleNames(self):
 +        return {Qt.UserRole + 1 : 'release'}
  
 -        if freespace < 0:
 -            freespace = 0
 -        if freespace < current_overlay:
 -            self.overlaySlider.setValue(freespace)
 -            self.live.overlay = self.overlaySlider.value()
 +    def data(self, index, role=Qt.DisplayRole):
 +        if index.isValid():
 +            return self.parent().releaseData[index.row()]
 +        return None
  
 -        self.overlaySlider.setMaximum(freespace)
 +class ReleaseListProxy(QSortFilterProxyModel):
 +    """ Filtering proxy for the release list
 +    """
 +    archChanged = pyqtSignal()
 +    nameFilterChanged = pyqtSignal()
 +    isFrontChanged = pyqtSignal()
 +
 +    _archFilter = ['x86_64']
 +    _nameFilter = ''
 +    _frontPage = True
 +
 +    _archMap = {'64bit': ['x86_64'], '32bit': ['i686','i386']}
 +
 +    def __init__(self, parent, sourceModel):
 +        QSortFilterProxyModel.__init__(self, parent)
 +        self.setSourceModel(sourceModel)
 +
 +    def rowCount(self, parent=QModelIndex()):
 +        if self._frontPage and self.sourceModel().rowCount(parent) > 4:
 +            return 4
 +        return self.sourceModel().rowCount(parent)
 +
 +    def filterAcceptsRow(self, sourceRow, sourceParent):
 +        row = self.sourceModel().index(sourceRow, 0, sourceParent).data()
 +        if len(self._archFilter) == 0 or row.arch.lower() in [x.lower() for x in self._archFilter] or row.isLocal:
 +            if len(self._nameFilter) == 0 or self._nameFilter.lower() in row.name.lower() or self._nameFilter.lower() in row.summary.lower():
 +                return True
 +        return False
 +
 +    @pyqtProperty(str, notify=nameFilterChanged)
 +    def nameFilter(self):
 +        return self._nameFilter
 +
 +    @nameFilter.setter
 +    def nameFilter(self, value):
 +        if value != self._nameFilter:
 +            self._nameFilter = value
 +            self.nameFilterChanged.emit()
 +            self.invalidateFilter()
 +
 +    @pyqtProperty('QStringList', constant=True)
 +    def possibleArchs(self):
 +        return self._archMap.keys()
 +
 +    @pyqtProperty(str, notify=archChanged)
 +    def archFilter(self):
 +        for name, abbrs in self._archMap.items():
 +            if abbrs == self._archFilter:
 +                return name
 +        self.archFilter = '64bit'
 +        return '64bit'
 +
 +    @archFilter.setter
 +    def archFilter(self, value):
 +        if self._archMap.has_key(value) and self.archFilter != self._archMap[value]:
 +            self._archFilter = self._archMap[value]
 +            self.archChanged.emit()
 +            self.invalidateFilter()
 +
 +    @pyqtProperty(bool, notify=isFrontChanged)
 +    def isFront(self):
 +        return self._frontPage
 +
 +    @isFront.setter
 +    def isFront(self, value):
 +        if value != self._frontPage:
 +            self._frontPage = value
 +            self.isFrontChanged.emit()
 +            self.invalidate()
  
 -    def progress(self, value):
 -        self.progressBar.setValue(value)
 -
 -    def maxprogress(self, value):
 -        self.progressBar.setMaximum(value)
 -
 -    def status(self, text):
 -        if not isinstance(text, basestring):
 -            text = str(text)
 -        self.textEdit.append(text)
 -
 -    def enable_widgets(self, enabled=True):
 -        self.startButton.setEnabled(enabled)
 -        self.driveBox.setEnabled(enabled)
 -        self.overlaySlider.setEnabled(enabled)
 -        self.isoBttn.setEnabled(enabled)
 -        self.downloadCombo.setEnabled(enabled)
 -        self.destructiveButton.setEnabled(enabled)
 -        self.nonDestructiveButton.setEnabled(enabled)
 -        if hasattr(self, 'refreshDevicesButton'):
 -            self.refreshDevicesButton.setEnabled(enabled)
 -        if hasattr(self, 'refreshReleasesButton'):
 -            self.refreshReleasesButton.setEnabled(enabled)
 -        self.in_process = not enabled
 -
 -    def overlay_value(self, value):
 -        self.overlayTitle.setTitle(_("Persistent Storage") + " (%d MB)" % value)
 -
 -    def get_selected_drive(self):
 -        text = self.live._to_unicode(self.driveBox.currentText()).split()
 -        if text:
 -            for key, drive in self.live.drives:
 -                if drive['device'] == text:
 -                    return key
 -
 -    def begin(self):
 -        """ Begin the liveusb creation process.
--
 -        This method is called when the "Create LiveUSB" button is clicked.
 -        """
 -        self.enable_widgets(False)
 -        self.live.overlay = self.overlaySlider.value()
 -        self.live.drive = self.get_selected_drive()
 +class LiveUSBLogHandler(logging.Handler):
  
 -        # Unmount the device and check the MBR
 -        if self.nonDestructiveButton.isChecked():
 -            if self.live.blank_mbr():
 -                if not self.mbr_reset_confirmed:
 -                    self.status(_("The Master Boot Record on your device is blank. "
 -                                  "Pressing 'Create LiveUSB' again will reset the "
 -                                  "MBR on this device."))
 -                    self.mbr_reset_confirmed = True
 -                    self.enable_widgets(True)
 -                    return
 -                if self.live.drive['mount']:
 -                    self.live.dest = self.live.drive['mount']
 -                    self.live.unmount_device()
 -                self.live.reset_mbr()
 -            elif not self.live.mbr_matches_syslinux_bin():
 -                if self.opts.reset_mbr:
 -                    self.live.reset_mbr()
 -                else:
 -                    self.live.log.warn(_("Warning: The Master Boot Record on your device "
 -                                  "does not match your system's syslinux MBR.  If you "
 -                                  "have trouble booting this stick, try running the "
 -                                  "liveusb-creator with the --reset-mbr option."))
 -
 -            try:
 -                self.live.mount_device()
 -                self._refresh_overlay_slider() # To reflect the drives free space
 -            except LiveUSBError, e:
 -                self.status(e.args[0])
 -                self.enable_widgets(True)
 -                return
 -            except OSError, e:
 -                self.status(_('Unable to mount device'))
 -                self.enable_widgets(True)
 -                return
 +    def __init__(self, cb):
 +        logging.Handler.__init__(self)
 +        self.cb = cb
  
 -            if self.live.existing_liveos():
 -                if not self.confirmed:
 -                    self.status(_("Your device already contains a LiveOS.\nIf you "
 -                                  "continue, this will be overwritten."))
 -                    if self.live.existing_overlay() and self.overlaySlider.value():
 -                        self.status(_("Warning: Creating a new persistent overlay "
 -                                      "will delete your existing one."))
 -                    self.status(_("Press 'Create Live USB' again if you wish to "
 -                                  "continue."))
 -                    self.confirmed = True
 -                    #self.live.unmount_device()
 -                    self.enable_widgets(True)
 -                    return
 -                else:
 -                    # The user has confirmed that they wish to overwrite their
 -                    # existing Live OS.  Here we delete it first, in order to
 -                    # accurately calculate progress.
 -                    self.confirmed = False
 -                    try:
 -                        self.live.delete_liveos()
 -                    except LiveUSBError, e:
 -                        self.status(e.args[0])
 -                        #self.live.unmount_device()
 -                        self.enable_widgets(True)
 -                        return
 -        else:
 -            # Require confirmation for destructive installs
 -            if not self.confirmed:
 -                self.status(_("WARNING: You are about to perform a destructive install. This will destroy all data and partitions on your USB drive. Press 'Create Live USB' again to continue."))
 -                self.confirmed = True
 -                self.enable_widgets(True)
 -                return
 +    def emit(self, record):
 +        if record.levelname in ('INFO', 'ERROR', 'WARN'):
 +            self.cb(record.msg)
  
 -        # Remove the log handler, because our live thread will register its own
 -        self.live.log.removeHandler(self.handler)
 +class USBDrive(QObject):
  
 -        # If the user has selected an ISO, use it.  If not, download one.
 -        if self.live.iso:
 -            self.live_thread.start()
 -        else:
 -            self.downloader = ReleaseDownloader(
 -                    self.downloadCombo.currentText(),
 -                    progress=self.download_progress,
 -                    proxies=self.live.get_proxies())
 -            self.connect(self.downloader,
 -                         QtCore.SIGNAL("dlcomplete(PyQt_PyObject)"),
 -                         self.download_complete)
 -            self.connect(self.downloader,
 -                         QtCore.SIGNAL("status(PyQt_PyObject)"),
 -                         self.status)
 -            self.downloader.start()
 -
 -    def download_complete(self, iso):
 -        """ Called by our ReleaseDownloader thread upon completion.
 -
 -        Upon success, the thread passes in the filename of the downloaded
 -        release.  If the 'iso' argument is not an existing file, then
 -        it is assumed that the download failed and 'iso' should contain
 -        the error message.
 -        """
 -        if os.path.exists(iso):
 -            self.status(_("Download complete!"))
 -            self.live.iso = iso
 -            self.live_thread.start()
 -        else:
 -            self.status(_("Download failed: " + iso))
 -            self.status(_("You can try again to resume your download"))
 -            self.enable_widgets(True)
 -
 -    def selectfile(self, isofile=None):
 -        if not isofile:
 -            isofile = QtGui.QFileDialog.getOpenFileName(self,
 -                         _("Select Live ISO"), ".", "ISO (*.iso)" )
 -        if isofile:
 -            try:
 -                self.live.set_iso(isofile)
 -            except Exception, e:
 -                self.live.log.error(e.args[0])
 -                self.status(_("Unable to encode the filename of your livecd.  "
 -                              "You may have better luck if you move your ISO "
 -                              "to the root of your drive (ie: C:\)"))
 -
 -            self.live.log.info('%s ' % os.path.basename(self.live.iso) +
 -                               _("selected"))
 -            self._refresh_overlay_slider()
 -            self.downloadGroup.setEnabled(False)
 +    def __init__(self, parent, name, drive):
 +        QObject.__init__(self, parent)
 +        self._name = name
 +        self._drive = drive
  
 -    def terminate(self):
 -        """ Terminate any processes that we have spawned """
 -        self.live.terminate()
 +    @pyqtProperty(str, constant=True)
 +    def text(self):
 +        return self._name
 +
 +    @pyqtProperty(str, constant=True)
 +    def drive(self):
 +        return self._drive
  
 -    def method_destructive_toggled(self, enabled):
 -        if enabled:
 -            self.overlayTitle.setEnabled(False)
 +class LiveUSBData(QObject):
 +    """ An entry point to all the exposed properties.
 +        There is a list of images and USB drives
 +    """
 +    releasesChanged = pyqtSignal()
 +    currentImageChanged = pyqtSignal()
 +    usbDrivesChanged = pyqtSignal()
 +    currentDriveChanged = pyqtSignal()
 +    optionsChanged = pyqtSignal()
 +
 +    _currentIndex = 0
 +    _currentDrive = 0
 +
 +    # man, this is just awkward... but it seems like the only way to do it in a predictable manner without creating a new class
 +    _optionKeys = ['dd', 'resetMBR']
 +    _optionNames = {'dd': _('Use <b>dd</b> to write the image - this will erase everything on your portable drive'),
 +                    'resetMBR': _('Reset the MBR (Master Boot Record)'),
 +                   }
 +    _optionValues = {'dd': False,
 +                     'resetMBR': False,
 +                    }
 +
 +    def __init__(self, opts):
 +        QObject.__init__(self)
 +        self.live = LiveUSBCreator(opts=opts)
 +        self._releaseModel = ReleaseListModel(self)
 +        self._releaseProxy = ReleaseListProxy(self, self._releaseModel)
 +
 +        self.releaseData = []
 +
 +        for release in releases:
 +            self.releaseData.append(Release(self,
 +                                            len(self.releaseData),
 +                                            self.live,
 +                                            release
 +                                    ))
 +        self._usbDrives = []
 +        self.currentDriveChanged.connect(self.currentImage.inspectDestination)
 +
-         usbTimer = QTimer(self)
-         usbTimer.setInterval(5000) # check for USB drives every 5 seconds
-         usbTimer.timeout.connect(self.USBDeviceEnumerationStart)
-         usbTimer.start()
++        self.live.detect_removable_drives(callbackAdded=self.USBDeviceCallback, callbackRemoved=self.USBDeviceCallback)
 +
-     @pyqtSlot()
-     def USBDeviceEnumerationStart(self):
-         try:
-             self.live.detect_removable_drives(callback=self.USBDeviceCallback)
-         except LiveUSBError, e:
-             pass # TODO
 +
 +    def USBDeviceCallback(self):
 +        tmpDrives = []
 +        previouslySelected = ''
 +        if len(self._usbDrives) > 0:
 +            previouslySelected = self._usbDrives[self._currentDrive].drive['device']
 +        for drive, info in self.live.drives.items():
 +            name = ''
 +            if 'vendor' in info and 'model' in info:
 +                name = info['vendor'] + ' ' + info['model']
 +            elif 'label' in info:
 +                name = info['device'] + ' - ' + info['label']
 +            else:
 +                name = info['device']
 +
 +            gb = 1000.0 # if it's decided to use base 2 values, change this
 +
 +            if 'fullSize' in info:
 +                usedSize = info['fullSize']
 +            else:
 +                usedSize = info['size']
 +
 +            if usedSize < gb ** 1:
 +                name += ' (%.1f B)'  % (usedSize / (gb ** 0))
 +            elif usedSize < gb ** 2:
 +                name += ' (%.1f KB)' % (usedSize / (gb ** 1))
 +            elif usedSize < gb ** 3:
 +                name += ' (%.1f MB)' % (usedSize / (gb ** 2))
 +            elif usedSize < gb ** 4:
 +                name += ' (%.1f GB)' % (usedSize / (gb ** 3))
 +            else:
 +                name += ' (%.1f TB)' % (usedSize / (gb ** 4))
 +
 +            tmpDrives.append(USBDrive(self, name, info))
 +
 +        if tmpDrives != self._usbDrives:
 +            self._usbDrives = tmpDrives
 +            self.usbDrivesChanged.emit()
 +
 +            self.currentDrive = -1
 +            for i, drive in enumerate(self._usbDrives):
 +                if drive.drive['device'] == previouslySelected:
 +                    self.currentDrive = i
 +
 +    @pyqtProperty(ReleaseListModel, notify=releasesChanged)
 +    def releaseModel(self):
 +        return self._releaseModel
 +
 +    @pyqtProperty(ReleaseListProxy, notify=releasesChanged)
 +    def releaseProxyModel(self):
 +        return self._releaseProxy
 +
 +    @pyqtProperty(int, notify=currentImageChanged)
 +    def currentIndex(self):
 +        return self._currentIndex
 +
 +    @currentIndex.setter
 +    def currentIndex(self, value):
 +        if value != self._currentIndex:
 +            self.currentDriveChanged.disconnect(self.currentImage.inspectDestination)
 +            self._currentIndex = value
 +            self.currentImageChanged.emit()
 +            self.currentDriveChanged.connect(self.currentImage.inspectDestination)
 +
 +    @pyqtProperty(Release, notify=currentImageChanged)
 +    def currentImage(self):
 +        return self.releaseData[self._currentIndex]
 +
 +    @pyqtProperty(QQmlListProperty, notify=usbDrivesChanged)
 +    def usbDrives(self):
 +        return QQmlListProperty(USBDrive, self, self._usbDrives)
 +
 +    @pyqtProperty('QStringList', notify=usbDrivesChanged)
 +    def usbDriveNames(self):
 +        return list(i.text for i in self._usbDrives)
 +
 +    @pyqtProperty(int, notify=currentDriveChanged)
 +    def currentDrive(self):
 +        return self._currentDrive
 +
 +    @currentDrive.setter
 +    def currentDrive(self, value):
 +        if len(self._usbDrives) == 0:
-             self._currentDrive
++            self._currentDrive = -1
++            self.currentDriveChanged.emit()
++            self.live.drive = None
 +            return
 +        if value > len(self._usbDrives):
 +            value = 0
-             self.currentDriveChanged.emit()
-         if len(self._usbDrives) != 0 and (self._currentDrive != value or self.live.drive != self._usbDrives[value].drive['device']):
++        print ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", value, len(self._usbDrives))
++        if self._currentDrive != value or self.live.drives[self.live.drive]['device'] != self._usbDrives[value].drive['device']:
 +            self._currentDrive = value
 +            if len(self._usbDrives) > 0:
 +                self.live.drive = self._usbDrives[self._currentDrive].drive['device']
 +            self.currentDriveChanged.emit()
 +            for r in self.releaseData:
 +                r.download.finished = False
 +
 +    @pyqtProperty('QStringList', constant=True)
 +    def optionNames(self):
 +        ret = []
 +        for i in self._optionKeys:
 +            ret.append(self._optionNames[i])
 +        return ret
 +
 +    @pyqtProperty('QVariant', notify=optionsChanged)
 +    def optionValues(self):
 +        ret = []
 +        for i in self._optionKeys:
 +            ret.append(self._optionValues[i])
 +        return ret
 +
 +    @pyqtSlot(int, bool)
 +    def setOption(self, index, value):
 +        key = self._optionKeys[index]
 +        if self._optionValues[key] != value:
 +            # dd and resetMBR options are mutually exclusive
 +            if key == 'dd' and value:
 +                self._optionValues['resetMBR'] = False
 +            if key == 'resetMBR' and value:
 +                self._optionValues['dd'] = False
 +            self._optionValues[key] = value
 +            self.optionsChanged.emit()
 +            self.currentImage.inspectDestination()
 +
 +    @pyqtSlot()
 +    def option(self, index):
 +        return self._optionValues[index]
 +
 +
 +class LiveUSBApp(QGuiApplication):
 +    """ Main application class """
 +    def __init__(self, opts, args):
 +        QGuiApplication.__init__(self, args)
 +        translator = QTranslator()
 +        translator.load(QLocale.system().name(), "po")
 +        self.installTranslator(translator)
 +        qmlRegisterUncreatableType(ReleaseDownload, 'LiveUSB', 1, 0, 'Download', 'Not creatable directly, use the liveUSBData instance instead')
 +        qmlRegisterUncreatableType(ReleaseWriter, 'LiveUSB', 1, 0, 'Writer', 'Not creatable directly, use the liveUSBData instance instead')
 +        qmlRegisterUncreatableType(ReleaseListModel, 'LiveUSB', 1, 0, 'ReleaseModel', 'Not creatable directly, use the liveUSBData instance instead')
 +        qmlRegisterUncreatableType(Release, 'LiveUSB', 1, 0, 'Release', 'Not creatable directly, use the liveUSBData instance instead')
 +        qmlRegisterUncreatableType(USBDrive, 'LiveUSB', 1, 0, 'Drive', 'Not creatable directly, use the liveUSBData instance instead')
 +        qmlRegisterUncreatableType(LiveUSBData, 'LiveUSB', 1, 0, 'Data', 'Use the liveUSBData root instance')
 +
 +        engine = QQmlApplicationEngine()
 +        self.data = LiveUSBData(opts)
 +        engine.rootContext().setContextProperty('liveUSBData', self.data)
 +        if (opts.directqml):
 +            engine.load(QUrl('liveusb/liveusb.qml'))
 +        else:
 +            engine.load(QUrl('qrc:/liveusb.qml'))
 +        engine.rootObjects()[0].show()
  
 -    def method_nondestructive_toggled(self, enabled):
 -        if enabled:
 -            self.overlayTitle.setEnabled(True)
 +        self.exec_()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/tails-installer.git



More information about the Pkg-privacy-commits mailing list