def install_bootloader(self):
        """ Run syslinux to install the bootloader on our device """
        LiveUSBCreator.install_bootloader(self)
        self.log.info(_("Installing bootloader"))
        device = self.drive["device"]
        syslinuxdir = os.path.join(device + os.path.sep, "syslinux")
        if os.path.isdir(syslinuxdir):
            # Python for Windows is unable to delete read-only files, and some
            # may exist here if the LiveUSB stick was created in Linux
            for f in os.listdir(syslinuxdir):
                os.chmod(os.path.join(syslinuxdir, f), 0777)
            shutil.rmtree(syslinuxdir)
        shutil.move(os.path.join(device + os.path.sep, "isolinux"), syslinuxdir)
        os.unlink(os.path.join(syslinuxdir, "isolinux.cfg"))

        # Don't prompt about overwriting files from mtools (#491234)
        for ldlinux in [os.path.join(device + os.path.sep, p, "ldlinux.sys") for p in (syslinuxdir, "")]:
            if os.path.isfile(ldlinux):
                os.chmod(ldlinux, 0777)
                self.log.debug(_("Removing") + " %s" % ldlinux)
                os.unlink(ldlinux)

        self.popen(
            "syslinux%s%s -m -a -d %s %s"
            % (self.opts.force and " -f" or "", self.opts.safe and " -s" or "", "syslinux", device)
        )
    def verify_filesystem(self):
        self.log.info(_("Verifying filesystem..."))
        if self.fstype not in self.valid_fstypes:
            if not self.fstype:
                raise LiveUSBError(_("Unknown filesystem.  Your device "
                                     "may need to be reformatted."))
            else:
                raise LiveUSBError(_("Unsupported filesystem: %s" %
                                     self.fstype))
        if self.drive['label'] != 'PANTHER-USB':
            self.label = 'PANTHER-USB'
            self.log.info(_("Modify ")+self.drive['device']+_("\'s label to: ")+self.label)
            try:
                if self.fstype in ('vfat', 'msdos'):
                    try:
#                        self.popen('/sbin/dosfslabel %s %s' % (self.drive['device'], self.label))
                         self.popen('echo "mtools_skip_check=1" > ~/.mtoolsrc')
                         self.popen('mlabel -i %s ::%s' % (self.drive['device'], self.label))
                         self.popen('rm -f ~/.mtoolsrc')
 
                    except LiveUSBError:
                        # dosfslabel returns an error code even upon success
                        pass
                else:
                    self.popen('e2label %s %s' % (self.drive['device'],
                                                        self.label)) 

            except LiveUSBError, e:
                self.log.error("Unable to change volume label: %s" % str(e))
                self.label = None
 def create_persistent_overlay(self):
     if self.overlay:
         self.log.info(_("Creating") + " %sMB " % self.overlay + _("persistent overlay"))
         if self.fstype == "vfat":
             # vfat apparently can't handle sparse files
             self.popen('dd if=/dev/zero of="%s" count=%d bs=1M' % (self.get_overlay(), self.overlay))
         else:
             self.popen('dd if=/dev/zero of="%s" count=1 bs=1M seek=%d' % (self.get_overlay(), self.overlay))
 def verify_filesystem(self):
     import win32api, win32file, pywintypes
     self.log.info(_("Verifying filesystem..."))
     try:
         vol = win32api.GetVolumeInformation(self.drive['device'])
     except Exception, e:
         raise LiveUSBError(_("Make sure your USB key is plugged in and "
                              "formatted with the FAT filesystem"))
 def extract_iso(self):
     """ Extract our ISO with 7-zip directly to the USB key """
     self.log.info(_("Extracting live image to USB device..."))
     start = datetime.now()
     self.popen('7z x "%s" -x![BOOT] -y -o%s' % (self.iso, self.drive["device"]))
     delta = datetime.now() - start
     if delta.seconds:
         self.mb_per_sec = (self.isosize / delta.seconds) / 1024 ** 2
         if self.mb_per_sec:
             self.log.info(_("Wrote to device at") + " %d MB/sec" % self.mb_per_sec)
 def reset_mbr(self):
     parent = str(self.drive.get("parent", self._drive))
     if "/dev/loop" not in self.drive:
         mbr = self._get_mbr_bin()
         if mbr:
             self.log.info(_("Resetting Master Boot Record") + " of %s" % parent)
             self.popen("cat %s > %s" % (mbr, parent))
         else:
             self.log.info(_("Unable to reset MBR.  You may not have the " "`syslinux` package installed"))
     else:
         self.log.info(_("Drive is a loopback, skipping MBR reset"))
 def reset_mbr(self):
     parent = str(self.drive.get('parent', self._drive))
     if '/dev/loop' not in self.drive:
         mbr = self._get_mbr_bin()
         if mbr:
             self.log.info(_('Resetting Master Boot Record') + ' of %s' % parent)
             self.popen('cat %s > %s' % (mbr, parent))
         else:
             self.log.info(_('Unable to reset MBR.  You may not have the '
                             '`syslinux` package installed'))
     else:
         self.log.info(_('Drive is a loopback, skipping MBR reset'))
 def extract_iso(self):
     """ Extract our ISO with 7-zip directly to the USB key """
     self.log.info(_("Extracting blackPanther image to USB device..."))
     start = datetime.now()
     self.popen('7z x "%s" -x![BOOT] -x!isolinux -x!boot -y -o%s\\loopbacks\\' % (
                self.iso, self.drive['device']))
     delta = datetime.now() - start
     if delta.seconds:
         self.mb_per_sec = (self.isosize / delta.seconds) / 1024**2
         if self.mb_per_sec:
             self.log.info(_("Wrote to device at") + " %d MB/sec" % 
                           self.mb_per_sec)
 def popen(self, cmd, **kwargs):
     import win32process
     if isinstance(cmd, basestring):
         cmd = cmd.split()
     tool = os.path.join('.', 'tools', '%s.exe' % cmd[0])
     if not os.path.exists(tool):
         raise LiveUSBError(_("Cannot find") + ' %s.  ' % (cmd[0]) +
                            _("Make sure to extract the entire "
                              "liveusb-creator zip file before "
                              "running this program."))
     return LiveUSBCreator.popen(self, ' '.join([tool] + cmd[1:]),
                                 creationflags=win32process.CREATE_NO_WINDOW,
                                 **kwargs)
    def verify_iso_md5(self):
        """ Verify the ISO md5sum.

        At the moment this is Linux specific, until we port checkisomd5 
        to Windows.
        """
        self.log.info(_('Verifying ISO MD5 checksum'))
        try:
            #self.popen('checkisomd5 "%s"' % self.iso)
            self.popen('md5sum "%s"' % self.iso)
        except LiveUSBError, e:
            self.log.info(_('ISO MD5 checksum verification failed'))
            return False
    def calculate_device_checksum(self, progress=None):
        """ Calculate the SHA1 checksum of the device """
        self.log.info(_("Calculating the SHA1 of %s" % self._drive))
        if not progress:

            class DummyProgress:
                def set_max_progress(self, value):
                    pass

                def update_progress(self, value):
                    pass

            progress = DummyProgress()
        # Get size of drive
        # progress.set_max_progress(self.isosize / 1024)
        checksum = hashlib.sha1()
        device_name = str(self.drive["parent"])
        device = file(device_name, "rb")
        bytes = 1024 ** 2
        total = 0
        while bytes:
            data = device.read(bytes)
            checksum.update(data)
            bytes = len(data)
            total += bytes
            progress.update_progress(total / 1024)
        hexdigest = checksum.hexdigest()
        self.log.info("sha1(%s) = %s" % (device_name, hexdigest))
        return hexdigest
    def detect_removable_drives(self, callback=None):
        import win32file, win32api, pywintypes

        self.drives = {}
        for drive in [l + ":" for l in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]:
            try:
                if win32file.GetDriveType(drive) == win32file.DRIVE_REMOVABLE or drive == self.opts.force:
                    vol = [None]
                    try:
                        vol = win32api.GetVolumeInformation(drive)
                    except pywintypes.error, e:
                        self.log.error("Unable to get GetVolumeInformation(%s): %s" % (drive, str(e)))
                        continue
                    self.drives[drive] = {
                        "label": vol[0],
                        "mount": drive,
                        "uuid": self._get_device_uuid(drive),
                        "free": self.get_free_bytes(drive) / 1024 ** 2,
                        "fstype": "vfat",
                        "device": drive,
                        "fsversion": vol[-1],
                        "size": self._get_device_size(drive),
                    }
            except Exception, e:
                self.log.exception(e)
                self.log.error(_("Error probing device"))
    def detect_removable_drives(self):
        """ Detect all removable USB storage devices using HAL via D-Bus """
        import dbus
        self.drives = {}
        self.bus = dbus.SystemBus()
        hal_obj = self.bus.get_object("org.freedesktop.Hal",
                                      "/org/freedesktop/Hal/Manager")
        self.hal = dbus.Interface(hal_obj, "org.freedesktop.Hal.Manager")

        devices = []
        if self.opts.force:
            devices = self.hal.FindDeviceStringMatch('block.device',
                                                     self.opts.force)
        else:
            devices = self.hal.FindDeviceByCapability("storage")

        for device in devices:
            dev = self._get_device(device)
            if self.opts.force or self._storage_bus(dev) == "usb":
                if self._block_is_volume(dev):
                    self._add_device(dev)
                    continue
                else: # iterate over children looking for a volume
                    children = self.hal.FindDeviceStringMatch("info.parent",
                                                              device)
                    for child in children:
                        child = self._get_device(child)
                        if self._block_is_volume(child):
                            self._add_device(child, parent=dev)
                            #break      # don't break, allow all partitions

        if not len(self.drives):
            raise LiveUSBError(_("Unable to find any USB drives"))
    def popen(self, cmd, passive=False, **kwargs):
        """ A wrapper method for running subprocesses.

        This method handles logging of the command and it's output, and keeps
        track of the pids in case we need to kill them.  If something goes
        wrong, an error log is written out and a LiveUSBError is thrown.

        @param cmd: The commandline to execute.  Either a string or a list.
        @param passive: Enable passive process failure.
        @param kwargs: Extra arguments to pass to subprocess.Popen
        """
        self.log.debug(cmd)
        if isinstance(cmd, unicode):
            cmd = cmd.encode('utf-8', 'replace')
        self.output.write(cmd)
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                                shell=True, **kwargs)
        self.pids.append(proc.pid)
        out, err = proc.communicate()
        if isinstance(out, unicode):
            out = out.encode('utf-8', 'replace')
        if isinstance(err, unicode):
            err = err.encode('utf-8', 'replace')
        self.output.write(out + '\n' + err + '\n')
        if proc.returncode:
            self.write_log()
            if not passive:
                raise LiveUSBError(_("There was a problem executing the "
                                     "following command: `%s`\nA more detailed "
                                     "error log has been written to "
                                     "'liveusb-creator.log'" % cmd))
        return proc
    def calculate_device_checksum(self, progress=None):
        """ Calculate the SHA1 checksum of the device """
        self.log.info(_("Calculating the SHA1 of %s" % self._drive))
        time.sleep(3)
        if not progress:

            class DummyProgress:
                def set_max_progress(self, value):
                    pass

                def update_progress(self, value):
                    pass

            progress = DummyProgress()
        progress.set_max_progress(self.drive["size"])
        checksum = hashlib.sha1()
        device_name = r"\\.\%s" % self.drive["device"]
        device = file(device_name, "rb")
        bytes = 1
        total = 0
        while bytes:
            data = device.read(1024 ** 2)
            checksum.update(data)
            bytes = len(data)
            total += bytes
            progress.update_progress(total)
        hexdigest = checksum.hexdigest()
        self.log.info("sha1(%s) = %s" % (self.drive["device"], hexdigest))
        return hexdigest
 def _set_drive(self, drive):
     if not self.drives.has_key(drive):
         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']
     self.fstype = self.drives[drive]['fstype']
 def detect_removable_drives(self):
     import win32file, win32api, pywintypes
     self.drives = {}
     for drive in [l + ':' for l in 'BCDEFGHIJKLMNOPQRSTUVWXYZ']:
         try:
             if win32file.GetDriveType(drive) == win32file.DRIVE_REMOVABLE or \
                drive == self.opts.force:
                 vol = [None]
                 try:
                     vol = win32api.GetVolumeInformation(drive)
                 except pywintypes.error, e:
                     self.log.error('Unable to get GetVolumeInformation(%s): %s' % (drive, str(e)))
                     continue
                 self.drives[drive] = {
                     'label': vol[0],
                     'mount': drive,
                     'uuid': self._get_device_uuid(drive),
                     'free': self.get_free_bytes(drive) / 1024**2,
                     'fstype': 'vfat',
                     'device': drive,
                     'fsversion': vol[-1],
                     'size': self._get_device_size(drive)
                 }
         except Exception, e:
             self.log.exception(e)
             self.log.error(_("Error probing device"))
    def create_persistent_overlay(self):
        if self.overlay:
            self.log.info(_("Creating") + " %sMB " % self.overlay +
                          _("data and change storage file..please wait!"))
            if self.fstype == 'vfat':
                # vfat apparently can't handle sparse files
                self.popen('dd if=/dev/zero of="%s" count=%d bs=1M'
                           % (os.path.join(self.dest+os.path.sep,'loopbacks','data'),  self.overlay))
#                           % (self.get_overlay(), self.overlay))
                self.log.info(_("Format the blackPanther storagefile for usage..."))
                self.popen('mke2fs -L LOOP -F "%s"' 
#                           % (self.dest+'\\loopbacks\\data',  self.overlay))
                           % os.path.join(self.dest+os.path.sep,'loopbacks','data'))
        else:
            self.popen('dd if=/dev/zero of="%s" count=1 bs=1M seek=%d' 
                        % (os.path.join(self.dest+os.path.sep,'loopbacks','data'),  self.overlay))
    def verify_iso_sha1(self, progress=None):
        """ Verify the SHA1 checksum of our ISO if it is in our release list """
        if not progress:

            class DummyProgress:
                def set_max_progress(self, value):
                    pass

                def update_progress(self, value):
                    pass

            progress = DummyProgress()
        release = self.get_release_from_iso()
        if release:
            progress.set_max_progress(self.isosize / 1024)
            if "sha1" in release:
                self.log.info(_("Verifying SHA1 checksum of LiveCD image..."))
                hash = "sha1"
                checksum = hashlib.sha1()
            elif "sha256" in release:
                self.log.info(_("Verifying SHA256 checksum of LiveCD image..."))
                hash = "sha256"
                checksum = hashlib.sha256()
            isofile = file(self.iso, "rb")
            bytes = 1024 ** 2
            total = 0
            while bytes:
                data = isofile.read(bytes)
                checksum.update(data)
                bytes = len(data)
                total += bytes
                progress.update_progress(total / 1024)
            isofile.close()
            if checksum.hexdigest() == release[hash]:
                return True
            else:
                self.log.info(
                    _(
                        "Error: The SHA1 of your Live CD is "
                        "invalid.  You can run this program with "
                        "the --noverify argument to bypass this "
                        "verification check."
                    )
                )
                return False
        else:
            self.log.debug(_("Unknown ISO, skipping checksum verification"))
    def copy_bootdir(self):
        self.log.info(_("Extracting boot files to device. Please wait.."))
#        tmpdir = tempfile.mkdtemp()
        for datapath in ('/usr/share/', '/usr/lib/python2.7/site-packages',  './'):
            bootarch = os.path.join(datapath, 'liveusb', 'boot.7z')
            if os.path.isfile(bootarch):
                self.log.debug('Found boot on path: %s ' % bootarch)
                self.popen('7z x -y -o%s %s' % (self.dest, bootarch))
 def get_disk_partition(self):
     """ Return the PedDisk and partition of the selected device """
     import parted
     parent = self.drives[self._drive]['parent']
     dev = parted.Device(path = parent)
     disk = parted.Disk(device = dev)
     for part in disk.partitions:
         if self._drive == "/dev/%s" %(part.getDeviceNodeName(),):
             return disk, part
     raise LiveUSBError(_("Unable to find partition"))
 def __init__(self, *args, **kw):
     super(LinuxLiveUSBCreator, self).__init__(*args, **kw)
     extlinux = self.get_extlinux_version()
     if extlinux is None:
         self.valid_fstypes -= self.ext_fstypes
     elif extlinux < 4:
         self.log.debug(
             _("You are using an old version of syslinux-extlinux " "that does not support the ext4 filesystem")
         )
         self.valid_fstypes -= set(["ext4"])
 def verify_filesystem(self):
     self.log.info(_("Verifying filesystem..."))
     if self.fstype not in self.valid_fstypes:
         if not self.fstype:
             raise LiveUSBError(_("Unknown filesystem.  Your device " "may need to be reformatted."))
         else:
             raise LiveUSBError(_("Unsupported filesystem: %s" % self.fstype))
     if self.drive["label"] != self.label:
         self.log.info("Setting %s label to %s" % (self.drive["device"], self.label))
         try:
             if self.fstype in ("vfat", "msdos"):
                 try:
                     self.popen("/sbin/dosfslabel %s %s" % (self.drive["device"], self.label))
                 except LiveUSBError:
                     # dosfslabel returns an error code even upon success
                     pass
             else:
                 self.popen("/sbin/e2label %s %s" % (self.drive["device"], self.label))
         except LiveUSBError, e:
             self.log.error("Unable to change volume label: %s" % str(e))
 def copy_bootdir(self, progress=None):
     self.log.info(_("Extracting blackPanther bootfile to device..."))
     if not progress:
         class DummyProgress:
             def set_max_progress(self, value): pass
             def update_progress(self, value): pass
         progress = DummyProgress()
     self.popen('7z x -y -o%s boot.7z' % self.dest)
     bytes = 1
     total = 0
     progress.update_progress(total)
    def mount_device(self):
        """ Mount our device if it is not already mounted """
        import dbus

        if not self.fstype:
            raise LiveUSBError(_("Unknown filesystem.  Your device " "may need to be reformatted."))
        if self.fstype not in self.valid_fstypes:
            raise LiveUSBError(_("Unsupported filesystem: %s") % self.fstype)
        self.dest = self.drive["mount"]
        if not self.dest:
            try:
                self.log.debug("Calling %s.Mount('', %s, [], ...)" % (self.drive["udi"], self.fstype))
                dev = self._get_device(self.drive["udi"])
                dev.FilesystemMount("", [], dbus_interface="org.freedesktop.UDisks.Device")
            except dbus.exceptions.DBusException, e:
                if e.get_dbus_name() == "org.freedesktop.Hal.Device.Volume.AlreadyMounted":
                    self.log.debug("Device already mounted")
                else:
                    self.log.error("Unknown dbus exception while trying to " "mount device: %s" % str(e))
            except Exception, e:
                raise LiveUSBError(_("Unable to mount device: %s" % str(e)))
 def delete_liveos(self):
     """ Delete the existing LiveOS """
     self.log.info(_("Removing existing Live OS"))
     for path in [
         self.get_liveos(),
         os.path.join(self.dest + os.path.sep, "syslinux"),
         os.path.join(self.dest + os.path.sep, "isolinux"),
     ]:
         if os.path.exists(path):
             self.log.debug("Deleting " + path)
             # Python for Windows is unable to delete read-only files,
             if os.path.isdir(path):
                 for f in os.listdir(path):
                     try:
                         os.chmod(os.path.join(path, f), 0777)
                     except OSError, e:
                         self.log.debug("Unable to delete %s: %s" % (f, str(e)))
             try:
                 shutil.rmtree(path)
             except OSError, e:
                 raise LiveUSBError(_("Unable to remove previous LiveOS: " "%s" % str(e)))
    def extract_iso(self):
        """ Extract self.iso to self.dest """
        self.log.info(_("Extracting live image to USB device..."))
        tmpdir = tempfile.mkdtemp()
        self.popen('mount -o loop,ro "%s" %s' % (self.iso, tmpdir))
        tmpliveos = os.path.join(tmpdir, "LiveOS")
        try:
            if not os.path.isdir(tmpliveos):
                raise LiveUSBError(_("Unable to find LiveOS on ISO"))
            liveos = os.path.join(self.dest, "LiveOS")
            if not os.path.exists(liveos):
                os.mkdir(liveos)

            start = datetime.now()
            self.popen("cp %s '%s'" % (os.path.join(tmpliveos, "squashfs.img"), os.path.join(liveos, "squashfs.img")))
            delta = datetime.now() - start
            if delta.seconds:
                self.mb_per_sec = (self.isosize / delta.seconds) / 1024 ** 2
                if self.mb_per_sec:
                    self.log.info(_("Wrote to device at") + " %d MB/sec" % self.mb_per_sec)

            osmin = os.path.join(tmpliveos, "osmin.img")
            if os.path.exists(osmin):
                self.popen("cp %s '%s'" % (osmin, os.path.join(liveos, "osmin.img")))
            else:
                self.log.debug("No osmin.img found")

            isolinux = os.path.join(self.dest, "isolinux")
            if not os.path.exists(isolinux):
                os.mkdir(isolinux)
            self.popen("cp %s/* '%s'" % (os.path.join(tmpdir, "isolinux"), isolinux))

            if os.path.exists(os.path.join(tmpdir, "EFI")):
                efi = os.path.join(self.dest, "EFI")
                if not os.path.exists(efi):
                    os.mkdir(efi)
                    self.popen("cp -r %s/* '%s'" % (os.path.join(tmpdir, "EFI"), efi))
        finally:
            self.popen("umount %s" % tmpdir)
 def check_free_space(self):
     """ Make sure there is enough space for the LiveOS and overlay """
     freebytes = self.get_free_bytes()
     self.log.debug('freebytes = %d' % freebytes)
     self.log.debug('isosize = %d' % self.isosize)
     overlaysize = self.overlay * 1024**2
     self.log.debug('overlaysize = %d' % overlaysize)
     self.totalsize = overlaysize + self.isosize
     if self.totalsize > freebytes:
         raise LiveUSBError(_("Not enough free space on device." + 
                              "\n%dMB ISO + %dMB overlay > %dMB free space" %
                              (self.isosize/1024**2, self.overlay,
                               freebytes/1024**2)))
 def mount_device(self):
     """ Mount our device with HAL if it is not already mounted """
     import dbus
     if not self.fstype:
         raise LiveUSBError(_("Unknown filesystem.  Your device "
                              "may need to be reformatted."))
     if self.fstype not in self.valid_fstypes:
         raise LiveUSBError(_("Unsupported filesystem: %s") %
                              self.fstype)
     self.dest = self.drive['mount']
     if not self.dest:
         try:
             self.log.debug("Calling %s.Mount('', %s, [], ...)" % (
                            self.drive['udi'], self.fstype))
             self.drive['udi'].Mount('', self.fstype, [],
                     dbus_interface='org.freedesktop.Hal.Device.Volume')
             self.drive['unmount'] = True
         except dbus.exceptions.DBusException, e:
             if e.get_dbus_name() == \
                     'org.freedesktop.Hal.Device.Volume.AlreadyMounted':
                 self.log.debug('Device already mounted')
         except Exception, e:
             raise LiveUSBError(_("Unable to mount device: %s" % str(e)))
    def install_bootloader(self):
        """ Run syslinux to install the bootloader on our device """
        LiveUSBCreator.install_bootloader(self)
        self.log.info(_("Installing bootloader to device.."))
        device = self.drive['device']
        syslinuxdir = os.path.join(device + os.path.sep, "boot", "syslinux")

        if os.path.isdir(syslinuxdir):
            # Python for Windows is unable to delete read-only files, and some
            # may exist here if the LiveUSB stick was created in Linux
            for f in os.listdir(syslinuxdir):
                os.chmod(os.path.join(syslinuxdir, f), 0777)
  
        # Don't prompt about overwriting files from mtools (#491234)
        for ldlinux in [os.path.join(device + os.path.sep, p, 'ldlinux.sys')
                        for p in (syslinuxdir, '')]:
            if os.path.isfile(ldlinux):
                os.chmod(ldlinux, 0777)
                self.log.debug(_("Removing") + " %s" % ldlinux)
                os.unlink(ldlinux)

        self.popen('syslinux%s%s -m -a -d %s %s' %  (self.opts.force and ' -f'
                   or '', self.opts.safe and ' -s' or '', 'boot\\syslinux', device))
Exemple #31
0
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': True,
    }

    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)

        self.live.detect_removable_drives(callback=self.USBDeviceCallback)

    def USBDeviceCallback(self):
        tmpDrives = []
        previouslySelected = ''
        if len(self._usbDrives) > 0:
            previouslySelected = self._usbDrives[
                self._currentDrive].drive['device']
        for drive, info in list(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
            self.currentDriveChanged.emit()

    @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.live.drive = None
            self._currentDrive = -1
            self.currentDriveChanged.emit()
            return
        elif value > len(self._usbDrives):
            value = 0
        if self._currentDrive != value:  # or not self.live.drive 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]