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))
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]