diff options
author | Papoteur <papoteur@mageia.org> | 2021-03-28 16:58:01 +0200 |
---|---|---|
committer | Papoteur <papoteur@mageia.org> | 2021-03-28 16:58:01 +0200 |
commit | 38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1 (patch) | |
tree | 7a612ac188100b9a0437ed0dd97ab7cb2518dbd8 | |
parent | 0ed27d2f918852df495b565bd9be7c442bb4d3fd (diff) | |
parent | 0d9522e267f8da916e57bcee51739e5573e209cb (diff) | |
download | isodumper-38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1.tar isodumper-38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1.tar.gz isodumper-38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1.tar.bz2 isodumper-38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1.tar.xz isodumper-38a4a0a35c92c457e2c2a8cc7bf2ea4a6c09e3d1.zip |
Merge branch 'topic/newgui'
-rwxr-xr-x | backend/magiback | 7 | ||||
-rwxr-xr-x | backend/raw_write.py | 90 | ||||
-rwxr-xr-x | lib/isodumper.py | 748 |
3 files changed, 503 insertions, 342 deletions
diff --git a/backend/magiback b/backend/magiback index 49162b3..c65b3a3 100755 --- a/backend/magiback +++ b/backend/magiback @@ -38,9 +38,6 @@ class Isodumper(raw_write.Dumper): <arg type='s' name='label' direction='in'/> <arg type='s' name='key' direction='in'/> </method> - <method name='get_sum'> - <arg type='s' name='source' direction='in'/> - </method> <method name='end'> <arg type='b' name='success' direction='out'/> <arg type='s' name='message' direction='out'/> @@ -121,10 +118,6 @@ class Isodumper(raw_write.Dumper): def progress(self): return self._progress - def get_sum(self, source): - self.key_thread = threading.Thread(target=self._get_sum, args=(source,)) - self.key_thread.start() - def check_write(self, target, source): if hasattr(self, 'key_thread'): self.key_thread.join() diff --git a/backend/raw_write.py b/backend/raw_write.py index ac454f1..7891b43 100755 --- a/backend/raw_write.py +++ b/backend/raw_write.py @@ -27,7 +27,6 @@ import gettext import hashlib import io import logging -import datetime ########### # imports # ########### @@ -37,8 +36,6 @@ import sys import time from subprocess import Popen, PIPE -import gnupg - class Dumper(object): @@ -126,62 +123,6 @@ class Dumper(object): self.finished.set() return - def _get_sum(self, source): - self.return_state = False - self.signature_checked = False - mageia_keyid = "835E41F4EDCA7A90" - logging.debug("Starting getting sum") - # Check if the sum file has a valid signature - gpg = gnupg.GPG() - gpg.encoding = 'utf-8' - # Use Mageia public key - self.sum_type = 'sha3' - sig_file = "{}.{}.gpg".format(source, self.sum_type) - self.source_file = "{}.{}".format(source, self.sum_type) - keys_list = gpg.list_keys() - key_present = False - for entry in keys_list: - if (mageia_keyid == entry['keyid']): - if entry['expires'] and (datetime.datetime.now().timestamp() > float(entry['expires'])): - logging.info("Mageia key expired, reloading") - else: - logging.info("Mageia key already present") - key_present = True - break - try: - if not key_present: - gpg.recv_keys('pool.sks-keyservers.net', mageia_keyid) - self.sum_check_searched = True - with open(sig_file, 'rb') as g: - self.signature_found = True - verified = gpg.verify_file(g, close_file=False) - if verified: - self.signature_checked = True - logging.debug("signature checked") - g.close() - else: - g.seek(0) - verified = gpg.verify_file(g, self.source_file) - if verified: - self.signature_checked = True - logging.debug("Detached signature is OK") - else: - self.signature_checked = False - logging.warning("Signature is false") - except Exception as e: - self.signature_found = False - logging.error(str(e)) - logging.info(_("Signature file {} not found\n" + _("or key expired")).format(sig_file)) - try: - # Look for sum files in the same directory as source - with open(self.source_file,'r') as fs: - # Read the sum in the file - self.sum_check=(fs.readline()).split()[0] - self.sum_file = True - except: - logging.info(_("Sum file {} not found\n").format(self.source_file)) - self.sum_file = False - def _check_write(self, target, source): logging.debug("Start checking") self.return_message = "" @@ -207,25 +148,30 @@ class Dumper(object): sha512func.update(block) sha512sumcalc=sha512func.hexdigest().upper() #f.close() + self.sum_check = "" + sum_type = "sha3" + sum_file = f"{source}.{sum_type}" + try: + # Look for sum files in the same directory as source + with open(sum_file,'r') as fs: + # Read the sum in the file + self.sum_check=(fs.readline()).split()[0] + self.sum_file = True + except: + logging.info(_("Sum file {} not found\n").format(sum_file)) + self.sum_file = False self.return_state = True - if self.signature_found and not self.signature_checked: - #, keep the pourcent, this is the place for source file name - self.return_message = _('Invalid signature for %s')%self.source_file - self.return_state = False + #if self.signature_found and not self.signature_checked: + ##, keep the pourcent, this is the place for source file name + #self.return_message = _('Invalid signature for %s')%self.source_file + #self.return_state = False if (self.sum_check == "") : # Can't get stored sum self.return_message += _('SHA3 sum: {}').format(sha512sumcalc) # compare the sums elif (sha512sumcalc == self.sum_check) : - if self.signature_checked and self.signature_found: + #if self.signature_checked and self.signature_found: #, keep the bracket, this is the place for sum type - self.return_message +="\n" + _("The {} sum check is OK and the sum is signed").format(self.sum_type) - else : - if self.signature_found: - self.return_message +="\n" + _("The validation of the GPG signature failed !") + "\n" + _("The integrity of the ISO image could not be verified.") - self.return_state = False - else: - #, keep the bracket, this is the place for sum type - self.return_message +="\n" + _("The {} sum check is OK but the signature can't be found").format(self.sum_type) + self.return_message +="\n" + _("The {} sum check is OK").format(sum_type) else: self.return_message +="\n" + _("/!\\The computed and stored sums don't match") self.return_state = False diff --git a/lib/isodumper.py b/lib/isodumper.py index 464943f..86433f6 100755 --- a/lib/isodumper.py +++ b/lib/isodumper.py @@ -45,6 +45,8 @@ import re import gettext from subprocess import Popen, PIPE from pydbus import SystemBus +import gnupg +import datetime import logging import threading @@ -52,6 +54,8 @@ from gi.repository import GLib from queue import SimpleQueue import psutil import manatools.args as args +import manatools.ui.common as common +import manatools.ui.basedialog as basedialog class NoUDisks2(Exception): @@ -124,7 +128,13 @@ class UDisks2(object): iface = self.bus.get(self.SERVICE, path_to_encrypted) fs = iface[self.SERVICE + '.Filesystem'] if fs.MountPoints: # partition is mounted - fs.Unmount({}) + try: + fs.Unmount({}) + except GLib.GError as e: + print(str(e)) + return False, _("A partition is busy. Try to free it before starting again.") + except: + raise Exception(str(e)) iface = self.bus.get(self.SERVICE, block['path']) fs = iface[self.SERVICE + '.Encrypted'] fs.Lock({}) @@ -135,11 +145,37 @@ class UDisks2(object): fs = iface[self.SERVICE + '.Filesystem'] if fs.MountPoints: # partition is mounted logging.debug(f"Unmounting {block['path']}") - fs.Unmount({}) + try: + fs.Unmount({}) + except GLib.GError as e: + print(str(e)) + return False, _("A partition is busy. Try to free it before starting again.") + except: + raise Exception(str(e)) else: logging.debug(f"Not mounted {block['path']}") return True, "" + + def get_partitions(self, target): + # keep only the end, like sdb + target = os.path.basename(target) + partitions = [] + objects = self._udisks2_obj_manager.GetManagedObjects() + re_block = re.compile('(?P<path>.*?/block_devices/(?P<id>.*))') + blocks = [m.groupdict() for m in + [re_block.match(path) for path in (objects.keys())] + if m] + for block in blocks: + if (self.SERVICE + '.Partition' in objects[block['path']]): # we skip non partition blocks + # Table should be something like '/org/freedesktop/UDisks2/block_devices/sdb' + if objects[block['path']][self.SERVICE + '.Partition']['Table'].split('/')[-1] == target: + partition = objects[block['path']][self.SERVICE + '.Block'] + partitions.append( {'device': bytes(partition['Device']).decode("UTF-8", "replace").strip("\0"), + 'label': partition['IdLabel'], + 'type': partition['IdType']}) + return partitions + def eject(self, device): ''' device is expected like /dev/sda''' block = os.path.basename(device) @@ -148,7 +184,6 @@ class UDisks2(object): i_drive = self.bus.get(self.SERVICE, drive) i_drive.Eject({}) - class Info(object): def __init__(self, title, richtext, text): self.title = title @@ -161,7 +196,7 @@ class ParseCLI(args.AppArgs): super().__init__(command) -class IsoDumper(object): +class IsoDumper(basedialog.BaseDialog): def get_devices(self, selected=None): self.list = self.u.find_devices() @@ -203,7 +238,7 @@ class IsoDumper(object): self.ima.setLabel(self.ChooseImage) self.backup_select.setLabel(self.ChooseImage) self.dialog.recalcLayout() - self.restore() + self.initial_state() def update_list_on_event(self): selitem = self.devicelist.selectedItem().label() @@ -211,6 +246,10 @@ class IsoDumper(object): self.get_devices(selected = selitem) if self.devicelist.selectedItem().label() != selitem: self.restore() + + def refresh_signal(self, device, params): + self.devicelist.deleteAllItems() + self.get_devices() self.dialog.recalcLayout() def device_selected(self): @@ -221,10 +260,23 @@ class IsoDumper(object): if self.dev.startswith(name + ' (' + path.lstrip()): self.deviceSize = size self.device_name = name.rstrip().replace(' ', '') - self.logger(_('Target Device: ') + self.dev) - self.formatbt.setEnabled() - self.ima.setEnabled() - self.backup_select.setEnabled() + message = _('Target Device: {}').format(self.dev) + self.logger(message) + logging.info(message) + partitions = self.u.get_partitions(self.dev.split('(')[1].split(')')[0]) + #, verb in singular 3rd person + self.logger(_("Contents partition(s)")) + for partition in partitions: + self.logger(_("{device}: Type {type} Label {label}").format(device=partition['device'], + type=partition['type'], + label=partition['label'],)) + self.partition_cbox.setEnabled() + self.partition_cbox.setChecked(False) + self.backup_cbox.setEnabled() + self.backup_cbox.setChecked(False) + self.write_cbox.setEnabled() + self.write_cbox.setChecked(False) + self.cryptcb.setChecked(False) break @staticmethod @@ -238,57 +290,123 @@ class IsoDumper(object): def backup_choosed(self): # Add .img if not specified - if not self.backup_img_name.lower().endswith('.img'): - self.backup_img_name = self.backup_img_name + ".img" - head, tail = os.path.split(self.backup_img_name) - self.backup_dest = self.backup_img_name - self.backup_select.setLabel(tail) - self.dialog.recalcLayout() - self.backupbt.setEnabled() - - def do_format(self, format_type, name): - target = self.dev.split('(')[1].split(')')[0] - info = Info(_("Formatting confirmation"), True, self.warning) - if self.ask_YesOrNo(info): - rc = self.raw_format(target, format_type, name) - self.operation = False - if rc == 0: - message = _('The device was formatted successfully.') - self.logger(message) - self.success() - elif rc == 5: - message = _("An error occurred while creating a partition.") - self.logger(message) - self.emergency(message) - elif rc == 127: - message = _('Authentication error.') - self.logger(message) - self.emergency(message) - else: - message = _('An error occurred.') - self.emergency(message) + self.backup_img_name = yui.YUI.app().askForSaveFileName("", "*.img", _("Backup to:")) + if self.backup_img_name != '': + if not self.backup_img_name.lower().endswith('.img'): + self.backup_img_name = self.backup_img_name + ".img" + head, tail = os.path.split(self.backup_img_name) + self.backup_dest = self.backup_img_name + self.backup_select.setLabel(tail) + self.dialog.recalcLayout() + self.start_bt.setEnabled() + self.backup_is_selected = True + else: + self.backup_is_selected = False + + def get_sum(self, source): + self.return_state = False + self.signature_checked = False + logging.debug("Starting getting sum") + # Check if the sum file has a valid signature + gpg = gnupg.GPG() + gpg.encoding = 'utf-8' + # Use Mageia public key + mageia_keyid = "835E41F4EDCA7A90" + self.sum_type = 'sha3' + sig_file = "{}.{}.gpg".format(source, self.sum_type) + self.source_file = "{}.{}".format(source, self.sum_type) + keys_list = gpg.list_keys() + key_present = False + for entry in keys_list: + if (mageia_keyid == entry['keyid']): + if entry['expires'] and (datetime.datetime.now().timestamp() > float(entry['expires'])): + logging.info("Mageia key expired, reloading") + else: + logging.info("Mageia key already present") + key_present = True + break + try: + if not key_present: + gpg.recv_keys('pool.sks-keyservers.net', mageia_keyid) + self.sum_check_searched = True + with open(sig_file, 'rb') as g: + self.signature_found = True + verified = gpg.verify_file(g, close_file=False) + if verified: + self.signature_checked = True + logging.debug("signature checked") + g.close() + else: + g.seek(0) + verified = gpg.verify_file(g, self.source_file) + if verified: + self.signature_checked = True + logging.debug("Detached signature is OK") + else: + self.signature_checked = False + logging.warning("Signature is false") + except Exception as e: + self.signature_found = False + logging.error(str(e)) + logging.info(_("Signature file {} not found\n" + _("or key expired")).format(sig_file)) + + def do_format(self): + #code, format_type, name = self.ask_format() + if self.partition_cb.value() != "": + self.operation = True + format_type = list(self.format_type.keys())[list(self.format_type.values()).index(self.partition_cb.value())] + if format_type == 'persist': + self.emergency(_("Persistence partition is to use when writing a Live ISO image.")) + return + target = self.dev.split('(')[1].split(')')[0] + info = Info(_("Formatting confirmation in {}".format(self.partition_cb.value())), True, self.warning) + if self.ask_YesOrNo(info): + name = self.partition_label.value() + if format_type == 'fat32': + name = name.upper()[:11] + elif format_type == 'exfat' or format_type == 'ntfs': + name = name[:32] + rc = self.raw_format(target, format_type, name) + self.operation = False + if rc == 0: + message = _('The device was formatted successfully.') + self.logger(message) + self.success() + elif rc == 5: + message = _("An error occurred while creating a partition.") + self.logger(message) + self.emergency(message) + elif rc == 127: + message = _('Authentication error.') + self.logger(message) + self.emergency(message) + else: + message = _('An error {} occurred.'.format(rc)) + self.emergency(message) + self.initial_state() - def restore(self): + def initial_state(self): + self.image_is_selected = False + self.backup_is_selected = False self.backup_select.setDisabled() - self.backupbt.setDisabled() - self.formatbt.setDisabled() + self.backup_cbox.setDisabled() + self.partition_cbox.setDisabled() self.ima.setDisabled() - self.writebt.setDisabled() + self.img_name = "" + self.ima.setLabel(self.ChooseImage) + self.write_cbox.setDisabled() self.devicelist.setEnabled() self.progress.setLabel(_("Progress")) self.progress.setValue(0) self.progress.setDisabled() self.refreshbt.setEnabled() - self.persistencecb.setChecked(False) - self.persistencecb.setDisabled() - self.cryptcb.setChecked(False) + #self.persistencecb.setChecked(False) + #self.persistencecb.setDisabled() + self.partition_cb.setDisabled() + self.partition_label.setDisabled() self.cryptcb.setDisabled() self.cryptkey.setDisabled() - - def onProgress(self, frac): - self.logger(_('Wrote: {}% '.format(frac))) - self.progress.setValue(frac) - self.dialog.pollEvent() + self.start_bt.setDisabled() def raw_format(self, usb_path, fstype, label): self.u.do_unmount(usb_path) @@ -318,6 +436,11 @@ class IsoDumper(object): return rc def backup_go(self): + self.wip_unsensitive() + self.devicelist.setDisabled() + self.backup_select.setDisabled() + self.progress.setEnabled() + self.progress.setLabel("Backup") dest = self.backup_img_name if os.path.exists(dest): info = Info(_("Backup confirmation"), True, _("Do you want to overwrite the file?")) @@ -330,45 +453,53 @@ class IsoDumper(object): sizeM = str(self.deviceSize / (1024 * 1024)) message = _("The destination directory is too small to receive the backup (%s Mb needed)") % (sizeM) self.logger(message) + logging.warning(message) self.emergency(message) else: + self.operation = True self.returncode = 0 source = self.dev.split('(')[1].split(')')[0] - self.logger(_('Backup to:') + ' ' + dest) - bus = SystemBus() - iface = bus.get("org.mageia.Magiback", "Isodumper") + message = _('Backup to: {}').format(dest) + self.logger(message) + logging.info(message) # Writing step - iface.do_write(source, dest, self.deviceSize) - while not iface.done: - progress = iface.progress + self.iface.do_write(source, dest, self.deviceSize) + while not self.iface.done: + progress = self.iface.progress self.progress.setValue(progress) self.dialog.pollEvent() time.sleep(.2) - success, message = iface.end() + success, message = self.iface.end() if success: - self.logger( - _('{source} successfully written to {target}').format(source=source.split('/')[-1], target=dest)) + #: don't translate source or target + message = _('{source} successfully written to {target}').format(source=source.split('/')[-1], target=dest) + self.logger(message) + logging.info(message) self.progress.setEnabled() self.progress.setValue(100) self.dialog.pollEvent() self.logger(message) + logging.info(message) self.success() + self.initial_state() else: self.emergency(message) + self.initial_state() def do_write(self): - self.writebt.setDisabled() + self.wip_unsensitive() + #self.writebt.setDisabled() self.devicelist.setDisabled() - self.formatbt.setDisabled() - self.backupbt.setDisabled() self.backup_select.setDisabled() self.progress.setEnabled() + self.progress.setLabel("Writing") source = self.img_name target = self.dev.split('(')[1].split(')')[0] b = os.path.getsize(source) if b > (self.deviceSize): message = _('The device is too small to contain the ISO file.') self.logger(message) + logging.error(message) self.emergency(message) else: info = Info(_("Writing confirmation"), True, self.warning) @@ -381,79 +512,87 @@ class IsoDumper(object): else: return self.ima.setDisabled() - self.writebt.setDisabled() + #: don't translate source or target self.progress.setLabel(_('Writing {source} to {target}').format(source=source.split('/')[-1], target=target.split('/')[-1])) - self.logger(_('Executing copy from ') + source + _(' to ') + target) - bus = SystemBus() - iface = bus.get("org.mageia.Magiback", "Isodumper") + message = _('Executing copy from {source} to {target}').format(source=source, target=target) + self.operation = True + self.logger(message) + logging.info(message) success, message = self.u.do_unmount(target) self.udev_wait(_("unmounting")) if success: # Writing step - iface.do_write(source, target, b) - iface.get_sum(source) - progress = iface.progress - while not iface.done: - progress = iface.progress + self.iface.do_write(source, target, b) + progress = self.iface.progress + while not self.iface.done: + progress = self.iface.progress self.progress.setValue(progress) self.dialog.pollEvent() time.sleep(.5) - success, message = iface.end() + success, message = self.iface.end() if success: - self.logger( - _('Image {source} successfully written to {target}').format(source=source.split('/')[-1], - target=target)) - self.logger(_('Bytes written: ') + str(b)) + #: don't translate source or target + message = _('Image {source} successfully written to {target}').format(source=source.split('/')[-1], + target=target) + message += "\n" + _('Bytes written: {}').format(str(b)) + self.logger(message) + logging.info(message) self.progress.setLabel(_('Checking ') + target.split('/')[-1]) self.progress.setValue(0) self.dialog.pollEvent() # Checking - iface.check_write(target, source) + self.iface.check_write(target, source) progress = 0 while progress < 100: self.progress.setValue(progress) self.dialog.pollEvent() time.sleep(.5) - progress = iface.progress - nowarning, message = iface.end() + progress = self.iface.progress + nowarning, message = self.iface.end() self.progress.setEnabled() self.logger(message) + logging.info(message) # Add persistent partition if asked - if self.persistencecb.isChecked(): + if self.partition_cbox.isChecked() and self.partition_cb.value() == _("Persistent partition"): self.progress.setLabel(_("Adding persistent partition")) self.progress.setValue(0) if self.cryptcb.isChecked(): if self.cryptkey.value() == "": - self.logger( - _("No key for encrypted partition provided. Adding the partition aborted.")) + message = _("No key for encrypted partition provided. Adding the partition aborted.") + self.logger(message) + logging.warning(message) else: - iface.do_persistence(target, "mgalive-persist", self.cryptkey.value()) - while not iface.done: - progress = iface.progress + self.iface.do_persistence(target, self.partition_label.value(), self.cryptkey.value()) + while not self.iface.done: + progress = self.iface.progress self.progress.setValue(progress) - self.progress.setLabel(iface.message) + self.progress.setLabel(self.iface.message) self.dialog.pollEvent() time.sleep(.5) - if iface.state: - self.logger(_("Added encrypted persistent partition")) + if self.iface.state: + message = _("Added encrypted persistent partition") + self.logger(message) + logging.warning(message) else: self.logger(_("Adding encrypted persistent partition failed")) - self.logger(iface.message) + self.logger(self.iface.message) nowarning = False else: - iface.do_persistence(target, "mgalive-persist", "") - while not iface.done: - progress = iface.progress + self.iface.do_persistence(target, self.partition_label.value(), "") + while not self.iface.done: + progress = self.iface.progress self.progress.setValue(progress) self.dialog.pollEvent() time.sleep(.5) - if iface.state: - self.logger(_("Added persistent partition")) + if self.iface.state: + message = _("Added persistent partition") else: - self.logger(_("Adding persistent partition failed")) - self.logger(iface.message) + message = _("Adding persistent partition failed") + self.logger(self.iface.message) nowarning = False + self.logger(message) + logging.warning(message) # Unmount if partitions are automatically mounted and then eject self.progress.setValue(100) self.dialog.pollEvent() @@ -467,22 +606,23 @@ class IsoDumper(object): self.warn() else: self.emergency(message) - self.restore() + self.initial_state() else: self.emergency(message) + self.initial_state() else: self.emergency(message) - self.restore() + self.initial_state() else: - self.restore() + self.initial_state() + self.operation = False def success(self): self.operation = False self.final_unsensitive() - info = Info(_("Success"), True, _("The operation completed successfully.\n\ - You are free to unplug it now, a logfile \n\ -(/home/-user- or /root)/.isodumper/isodumper.log will be saved when\n\ - you close the application.")) + info = Info(_("Success"), True, _("The operation completed successfully.") + "\n"\ + + _("You are free to unplug it now, a logfile \n\ +(/home/-user-/.isodumper/isodumper.log has been saved.") + _("You may also consult /var/log/magiback.log")) if self.ask_OK(info): return @@ -490,12 +630,13 @@ class IsoDumper(object): self.operation = False self.final_unsensitive() info = Info(_("Warning"), True, _("The operation completed, but with anomalies.\n\ - Check carefully the messages in log view.\n\ - You are free to unplug it now, a logfile \n\ -(/home/-user- or /root)/.isodumper/isodumper.log will be saved when\n\ - you close the application.")) + Check carefully the messages in log view") + "\n"\ ++ _("You are free to unplug it now, a logfile \n\ +(/home/-user-/.isodumper/isodumper.log has been saved.") + "\n"\ + + _("You may also consult /var/log/magiback.log")) if self.ask_OK(info): return + def confirm_close(self, *args): if not self.operation: # no writing , backup nor format running self.close() @@ -518,44 +659,38 @@ class IsoDumper(object): def final_unsensitive(self): self.ima.setDisabled() self.devicelist.setDisabled() - self.writebt.setDisabled() + self.write_cbox.setDisabled() + self.backup_cbox.setDisabled() self.progress.setDisabled() self.backup_select.setDisabled() - self.persistencecb.setDisabled() + self.partition_cbox.setDisabled() def wip_unsensitive(self): self.ima.setDisabled() self.devicelist.setDisabled() - self.writebt.setDisabled() + self.backup_cbox.setDisabled() self.backup_select.setDisabled() - self.backupbt.setDisabled() + self.write_cbox.setDisabled() self.refreshbt.setDisabled() - self.persistencecb.setDisabled() + self.partition_cbox.setDisabled() + self.partition_cb.setDisabled() + self.partition_label.setDisabled() + self.cryptcb.setDisabled() + self.cryptkey.setDisabled() + self.start_bt.setDisabled() def close(self): - self.write_logfile() - self.dialog.destroy() - self.dialog = None - if yui.YUI.app().isTextMode(): - self.glib_loop.quit() - self.glib_thread.join() - - def write_logfile(self): - logpath = os.path.join(os.path.expanduser('~'), '.isodumper') - if not (os.path.isdir(logpath)): - os.mkdir(logpath) - logfile = open(logpath + '/isodumper.log', "w", encoding="utf-8") - logfile.write(self.logview.logText()) - logfile.close() - - print((self.logview.logText())) + # to exit from _handleEvents loop + self._running = False def logger(self, text): self.logview.appendLines(text + "\n") def activate_devicelist(self): self.devicelist.setEnabled() - self.logger(_('Image ') + ": " + self.img_name) + message = _('Image ') + ": " + self.img_name + self.logger(message) + logging.warning(message) # self.chooser.set_tooltip_text(self.img_name) @@ -594,6 +729,7 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog return def __init__(self, debug=False): + #: placeholder for release number APP = "isodumper" DIR = "/usr/share/locale" # Call translation catalog @@ -614,7 +750,7 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog logpath = os.path.join(os.path.expanduser('~'), '.isodumper') if not (os.path.isdir(logpath)): os.mkdir(logpath) - logging.basicConfig(filename=os.path.join(logpath,"isodumper2.log"), + logging.basicConfig(filename=os.path.join(logpath,"isodumper.log"), format='%(asctime)s %(levelname)-8s %(message)s', level=level) @@ -623,6 +759,10 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog # Operation running self.operation = False + + # Prepare interface on DBus + self.bus = SystemBus() + self.iface = self.bus.get("org.mageia.Magiback", "Isodumper") self.ChooseImage = _("Choose an image") self.warning = _("Warning\nThis will destroy all data on the target device,\n\ @@ -632,15 +772,15 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog """ Init/Constructor for the 'widgets' """ - yui.YUI.app().setApplicationTitle(_("IsoDumper") + " " + version.RELEASE) + self.dialog = basedialog.BaseDialog.__init__(self, _("Isodumper {}").format(version.RELEASE), "", basedialog.DialogType.POPUP, 100, 20) yui.YUI.app().setApplicationIcon("/usr/share/icons/isodumper.png") - self.atelier = yui.YUI.widgetFactory() - self.dialog = self.atelier.createPopupDialog() + + def UIlayout(self, layout): # create the main gui - # +---+-----------------+ + # +---------------------+ # + banner + - # +---+-----------------+ - # | devicelist | + # +---------------------+ + # | devicebox | # +---+-----+------+----+ # + L | ima | writebt | # +---+-----+------+----+ @@ -654,72 +794,104 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog # +---------------------+ # | refreshbt | aboutbt | helpbt | quitbt | # +---------------------+ - self.ancrage = self.atelier.createReplacePoint(self.dialog) - self.box = self.atelier.createVBox(self.ancrage) - self.bannerbox = self.atelier.createHBox(self.box) - self.banner = self.atelier.createImage(self.bannerbox, "/usr/share/isodumper/header.svg") - self.devicebox = self.atelier.createHBox(self.box) - self.devicelist = self.atelier.createComboBox(self.devicebox, _("Device to work on:"), False) + #self.ancrage = self.factory.createReplacePoint(self.dialog) + self.box = self.factory.createVBox(layout) + self.bannerbox = self.factory.createHBox(self.box) + self.banner = self.factory.createImage(self.bannerbox, "/usr/share/isodumper/header.svg") + self.devicebox = self.factory.createHBox(self.box) + self.devicelist = self.factory.createComboBox(self.devicebox, _("Select the device to work on:"), False) self.devicelist.setNotify() self.devicelist.setStretchable(0, True) - self.writebox = self.atelier.createHBox(self.box) - self.atelier.createLabel(self.writebox, _("Write Image:")) + self.refreshbt = self.factory.createPushButton(self.devicebox, _("Update list")) + self.refreshbt.setStretchable(0, True) + self.operation_frame = self.factory.createFrame(self.box, _("Select operations")) + self.operationbox = self.factory.createVBox(self.operation_frame) + self.labelbox = self.factory.createHBox(self.operationbox) + self.labelbox.setStretchable(0, True) + self.factory.createLabel(self.labelbox, "The selected operations will be executed in order from top to bottom.\nIf both write image and create partition are selected, the partition\nwill be created in the free space after the image.").setStretchable(0, True) + self.backupbox = self.factory.createHBox(self.operationbox) + self.backup_cbox = self.factory.createCheckBox(self.backupbox, _("Backup the device to:")) + self.backup_cbox.setNotify() + # add file picker for backup file name + self.backup_select = self.factory.createPushButton(self.backupbox, self.ChooseImage) + self.backup_select.setStretchable(0, True) + self.factory.createHStretch(self.backupbox) + self.writebox = self.factory.createHBox(self.operationbox) + self.write_cbox = self.factory.createCheckBox(self.writebox, _("Write Image from:")) + self.write_cbox.setNotify() # add file picker for image file - self.ima = self.atelier.createPushButton(self.writebox, self.ChooseImage) + self.ima = self.factory.createPushButton(self.writebox, self.ChooseImage) self.ima.setStretchable(0, True) - self.ima.setDisabled() - self.atelier.createHStretch(self.writebox) - self.writebt = self.atelier.createPushButton(self.writebox, _("&Write to device")) - self.writebt.setDisabled() - self.persistencebox = self.atelier.createHBox(self.box) - self.persistencecol1 = self.atelier.createVBox(self.persistencebox) - self.persistencecol2 = self.atelier.createVBox(self.persistencebox) - self.persistencecb = self.atelier.createCheckBox(self.persistencecol1, - _("Add a persistent partition in the remaining space")) - self.persistencebox2 = self.atelier.createHBox(self.box) - self.cryptcb = self.atelier.createCheckBox(self.persistencecol1, _("Encrypt partition")) - self.cryptkey = self.atelier.createInputField(self.persistencecol2, _("Key:"), passwordMode=True) - self.persistencecb.setDisabled() - self.persistencecb.setNotify() - self.cryptcb.setDisabled() + self.factory.createHStretch(self.writebox) + #self.writebt = self.factory.createPushButton(self.writebox, _("&Write to device")) + self.partitionbox = self.factory.createHBox(self.operationbox) + self.partition_cbox = self.factory.createCheckBox(self.partitionbox, _("Create partition of type:")) + self.partition_cbox.setNotify() + self.partition_cb = self.factory.createComboBox(self.partitionbox, _("Type:")) + self.partition_cb.addItem("", True) + self.format_type = {'fat32': _("FAT 32"), + 'ext4': _("ext4"), + 'ntfs': _("NTFS"), + 'exfat': _("exfat"), + 'persist': _("Persistent partition"), + } + for key in self.format_type: + self.partition_cb.addItem(self.format_type[key], False) + self.partition_cb.setNotify() + self.partition_label = self.factory.createInputField(self.partitionbox, _("Label:")) + self.partition_label.setStretchable(0, True) + self.persistencebox = self.factory.createHBox(self.operationbox) + #self.persistencecb = self.factory.createCheckBox(self.persistencecol1, + # _("Add a persistent partition in the remaining space")) + self.cryptcb = self.factory.createCheckBox(self.persistencebox, _("Encrypt partition using LUKS, with key:")) self.cryptcb.setNotify() - self.cryptkey.setDisabled() - self.backupbox = self.atelier.createHBox(self.box) - self.atelier.createLabel(self.backupbox, _("Backup to:")) - # add file picker for backup file name - self.backup_select = self.atelier.createPushButton(self.backupbox, self.ChooseImage) - self.backup_select.setStretchable(0, True) - self.backup_select.setDisabled() - self.atelier.createHStretch(self.backupbox) - self.backupbt = self.atelier.createPushButton(self.backupbox, _("Backup the device")) - self.backupbt.setDisabled() - self.formatbox = self.atelier.createHBox(self.box) - self.atelier.createLabel(self.formatbox, _("Format the device in FAT, exFAT, NTFS or ext:")) - self.atelier.createHStretch(self.formatbox) - self.formatbt = self.atelier.createPushButton(self.formatbox, _("Format the device")) - self.formatbt.setDisabled() - self.progressbox = self.atelier.createHBox(self.box) - self.progress = self.atelier.createProgressBar(self.progressbox, _("Progress"), 100) + self.cryptkey = self.factory.createInputField(self.persistencebox, _("Key:"), passwordMode=True) + self.cryptkey.setStretchable(0, True) + #self.formatbt = self.factory.createPushButton(self.formatbox, _("Format the device")) + self.execute_frame = self.factory.createFrame(self.box, _("Execution")) + self.execute_framevb = self.factory.createVBox(self.execute_frame) + self.execute_label = self.factory.createLabel(self.execute_framevb, _("When you are sure all options are correct, start:")) + self.start_bt = self.factory.createPushButton(self.execute_framevb, _("Execute")) + self.start_bt.setNotify() + self.progressbox = self.factory.createHBox(self.execute_framevb) + self.progress = self.factory.createProgressBar(self.progressbox, _("Progress"), 100) self.progress.setStretchable(0, True) - self.reportbox = self.atelier.createHBox(self.box) + self.reportbox = self.factory.createHBox(self.box) self.reportbox.setWeight(1, 30) - self.logview = self.atelier.createLogView(self.reportbox, _("Report"), 10) + self.logview = self.factory.createLogView(self.reportbox, _("Report"), 10) self.logview.setStretchable(0, True) - self.buttonsbox = self.atelier.createHBox(self.box) - self.refreshbt = self.atelier.createPushButton(self.devicebox, _("Refresh")) - self.refreshbt.setStretchable(0, True) - self.aboutbt = self.atelier.createPushButton(self.buttonsbox, _("About")) + self.buttonsbox = self.factory.createHBox(self.box) + self.aboutbt = self.factory.createPushButton(self.buttonsbox, _("About")) self.aboutbt.setStretchable(0, True) - self.helpbt = self.atelier.createPushButton(self.buttonsbox, _("Help")) + self.helpbt = self.factory.createPushButton(self.buttonsbox, _("Help")) self.helpbt.setStretchable(0, True) - self.quitbt = self.atelier.createPushButton(self.buttonsbox, _("Quit")) + self.quitbt = self.factory.createPushButton(self.buttonsbox, _("Quit")) self.quitbt.setStretchable(0, True) + # Set the initial state, active, disabled + self.initial_state() + # Connect events + self.eventManager.addWidgetEvent(self.quitbt, self.confirm_close) + self.eventManager.addWidgetEvent(self.ima, self.ask_image) + #self.eventManager.addWidgetEvent(self.writebt, self.do_write) + self.eventManager.addWidgetEvent(self.cryptcb, self.check_encryt) + #self.eventManager.addWidgetEvent(self.backupbt, self.backup_go) + self.eventManager.addWidgetEvent(self.backup_select, self.backup_choosed) + #self.eventManager.addWidgetEvent(self.formatbt, self.do_format) + self.eventManager.addWidgetEvent(self.backup_cbox, self.select_backup) + self.eventManager.addWidgetEvent(self.write_cbox, self.select_write) + self.eventManager.addWidgetEvent(self.partition_cbox, self.select_partition) + self.eventManager.addWidgetEvent(self.partition_cb, self.select_partition_type) + self.eventManager.addWidgetEvent(self.devicelist, self.device_selected) + self.eventManager.addWidgetEvent(self.start_bt, self.start) + self.eventManager.addWidgetEvent(self.refreshbt, self.update_list) + self.eventManager.addWidgetEvent(self.helpbt, self.help_dialog) + self.eventManager.addWidgetEvent(self.aboutbt, self.aboutDialog) self.u = None try: self.u = UDisks2() except: message = _('UDisks2 is not available on your system') - self.logger(message) + logging.error(message) self.emergency(message) if not self.get_devices(): self.dialog.destroy() @@ -727,7 +899,7 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog exit() self.device_selected() self.dialog.recalcLayout() - self.ancrage.showChild() + self.uEventQueue = SimpleQueue() if yui.YUI.app().isTextMode(): self.glib_loop = GLib.MainLoop() @@ -745,8 +917,107 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog def device_changed(self, a, b): - self.uEventQueue.put({'event': "device-changed", 'value': True}) + logging.debug(f"Signal {a}") + if not self.operation: + if 'drives' in a.split('/'): + logging.debug('device-changed') + self.uEventQueue.put({'event': "device-changed", 'value': True}) + + def ask_image(self): + self.img_name = yui.YUI.app().askForExistingFile("", "*.iso *.img", self.ChooseImage) + if self.img_name != "": + # Check if the image has a signed checksum + self.get_sum(self.img_name) + if self.signature_found: + if not self.signature_checked: + info = Info(_("Warning"), True, + _("The validation of the GPG signature failed !") + "\n" + _("Do you want to continue?")) + if self.ask_YesOrNo(info): + pass + else: + self.img_name = "" + return + else: + info = Info(_("Warning"), True, + _('No GPG signature has been found or the key is expired. Are you sure you want to use this image?')) + if self.ask_YesOrNo(info): + pass + else: + self.img_name = "" + return + self.ima.setLabel(os.path.basename(self.img_name)) + self.dialog.recalcLayout() + self.start_bt.setEnabled() + self.activate_devicelist() + self.image_is_selected = True + #self.persistencecb.setEnabled() + else: + self.image_is_selected = False + + def select_backup(self): + if self.backup_cbox.isChecked(): + self.backup_select.setEnabled() + else: + self.backup_select.setDisabled() + + def select_write(self): + if self.write_cbox.isChecked(): + self.ima.setEnabled() + else: + self.ima.setDisabled() + + def select_partition(self): + if self.partition_cbox.isChecked(): + self.partition_cb.setEnabled() + self.partition_label.setEnabled() + else: + self.partition_cb.setDisabled() + self.partition_label.setDisabled() + self.cryptcb.setDisabled() + self.cryptkey.setDisabled() + + def select_partition_type(self): + if self.partition_cb.value() in ("ext4" ,"Persistent partition"): + self.cryptcb.setEnabled() + if self.partition_cb.value() == "Persistent partition": + self.partition_label.setValue("mgalive-persist") + else: + self.cryptcb.setDisabled() + self.cryptcb.setDisabled() + self.cryptkey.setDisabled() + if self.partition_cb.value() != "": + self.start_bt.setEnabled() + else: + self.start_bt.setDisabled() + + def check_encryt(self): + if self.cryptcb.isChecked(): + self.cryptkey.setEnabled() + else: + self.cryptkey.setDisabled() + + def start(self): + if self.backup_cbox.isChecked(): + # check that a name is set + if self.backup_is_selected: + self.backup_go() + self.backup_is_selected = False + else: + info = Info(_("Error"), True, _("No image for backup is selected.")) + self.ask_OK(info) + if self.write_cbox.isChecked(): + if self.image_is_selected: + self.do_write() + self.image_is_selected = False + else: + info = Info(_("Error"), True, _("No image to write is selected.")) + self.ask_OK(info) + return + if self.partition_cbox.isChecked(): + # Create a partition without writing image, will use all the device space. + self.do_format() + def ask_format(self): atelier = yui.YUI.widgetFactory() dialog = atelier.createPopupDialog() @@ -839,87 +1110,38 @@ exFAT, NTFS or ext. You can specify a volume name and the format in a new dialog dlg.setMinSize(60, 8) return dlg.show() == yui.YMGAMessageBox.B_ONE - def handleevent(self): + def doSomethingIntoLoop(self): self.traitement = None - while True: - event = self.dialog.waitForEvent(100) - if event.eventType() == yui.YEvent.CancelEvent: - self.close() - break - if event.widget() == self.quitbt: - self.close() - break - if event.widget() == self.ima: - self.img_name = yui.YUI.app().askForExistingFile("", "*.iso *.img", self.ChooseImage) - if self.img_name != "": - self.ima.setLabel(os.path.basename(self.img_name)) - self.dialog.recalcLayout() - self.writebt.setEnabled() - self.activate_devicelist() - self.persistencecb.setEnabled() - if event.widget() == self.persistencecb: - print(self.persistencecb.value) - if self.persistencecb.isChecked(): - self.cryptcb.setEnabled() - else: - self.cryptcb.setDisabled() - self.cryptkey.setDisabled() - self.cryptcb.setChecked(False) - if event.widget() == self.cryptcb: - if self.cryptcb.isChecked(): - self.cryptkey.setEnabled() - else: - self.cryptkey.setDisabled() - if event.widget() == self.writebt: - self.wip_unsensitive() - self.do_write() - self.restore() - if event.widget() == self.backupbt: - self.wip_unsensitive() - self.backup_go() - self.restore() - if event.widget() == self.backup_select: - self.backup_img_name = yui.YUI.app().askForSaveFileName("", "*.img", _("Backup to:")) - if self.backup_img_name != '': - self.backup_choosed() - if event.widget() == self.formatbt: - code, format_type, name = self.ask_format() - if code: - self.do_format(format_type, name) - self.restore() - if event.widget() == self.devicelist: - self.device_selected() - try: - if event.widget() == self.refreshbt: - self.update_list() - except: - pass - try: - if event.widget() == self.helpbt: - self.help_dialog() - except: - pass - try: - if event.widget() == self.quitbt: - self.confirm_close() - except: - pass - if event.widget() == self.aboutbt: - self.aboutDialog() - try: - item = self.uEventQueue.get_nowait() - if item['event'] == "device-changed": - self.update_list_on_event() - except Exception as e: - pass - - def run(self): + #if event.eventType() == yui.YEvent.CancelEvent: + #self.confirm_close() + #break try: - self.handleevent() + item = self.uEventQueue.get_nowait() + logging.debug(f"Dealing with {item['event']}") + if item['event'] == "device-changed": + self.update_list_on_event() + except Exception as e: + pass + + def run(self, debug=False): + + self._setupUI() + # setting to False will break the event loop + self._running = True + self.timeout = 100 + + try: + self._handleEvents() except Exception: import traceback traceback.print_exc() yui.YDialog.deleteAllDialogs() + # Closing + self.dialog.destroy() + self.dialog = None + if yui.YUI.app().isTextMode(): + self.glib_loop.quit() + self.glib_thread.join() if __name__ == "__main__": |