diff options
-rw-r--r-- | CHANGELOG | 11 | ||||
-rwxr-xr-x | lib/isodumper.py | 196 | ||||
-rwxr-xr-x | lib/raw_format.py | 39 | ||||
-rw-r--r-- | share/isodumper/isodumper.glade | 141 |
4 files changed, 306 insertions, 81 deletions
@@ -1,6 +1,17 @@ The initial state comes from https://launchpad.net/usb-imagewriter The modifications are: +IsoDumper 0.33 +------------ + - added a help button and dialog box. + - added a Refresh button to find new devices. + - added a UEFI feature, allowing creating a FAT32 stick, and copy the ISO file the new partition. + - adding a catch for protected devices when formatting. + - take the device size from the selected device, not the last one. + - detect also SD-cards. + - check the size for destination when backup. + + IsoDumper 0.32 ------------ - fix wrong progress steps with small images. diff --git a/lib/isodumper.py b/lib/isodumper.py index fc15ba2..f15bf6e 100755 --- a/lib/isodumper.py +++ b/lib/isodumper.py @@ -32,9 +32,9 @@ import gettext from gettext import gettext as _ from subprocess import call, Popen, PIPE import time +import dbus def find_devices(): - import dbus bus = dbus.SystemBus() proxy = bus.get_object("org.freedesktop.UDisks", "/org/freedesktop/UDisks") iface = dbus.Interface(proxy, "org.freedesktop.UDisks") @@ -42,10 +42,11 @@ def find_devices(): list=[] for dev in devs: - dev_obj = bus.get_object("org.freedesktop.UDisks", dev) - dev = dbus.Interface(dev_obj, "org.freedesktop.DBus.Properties") - item=[] - if str(dev.Get('', 'DriveConnectionInterface')) == 'usb' and not str(dev.Get('', 'PartitionType')) and str(dev.Get('', 'DeviceIsMediaAvailable')) == '1': + dev_obj = bus.get_object("org.freedesktop.UDisks", dev) + dev = dbus.Interface(dev_obj, "org.freedesktop.DBus.Properties") + item=[] + dci=str(dev.Get('', 'DriveConnectionInterface')) + if (dci == 'usb' or dci == 'sdio') and not str(dev.Get('', 'PartitionType')) and str(dev.Get('', 'DeviceIsMediaAvailable')) == '1': vend = str(dev.Get('', 'DriveVendor')) path = str(dev.Get('', 'DeviceFile')) name = str(dev.Get('', 'DriveModel')) @@ -56,12 +57,25 @@ def find_devices(): list.append(item) return list +def mount(device, fs): + res = '' + _bus = dbus.SystemBus() + _proxy = _bus.get_object('org.freedesktop.UDisks','/org/freedesktop/UDisks') + _iface = dbus.Interface(_proxy, 'org.freedesktop.UDisks') + for _dev in _iface.EnumerateDevices(): + _dev_obj = _bus.get_object('org.freedesktop.UDisks', _dev) + _dev_prop = dbus.Interface(_dev_obj, 'org.freedesktop.DBus.Properties') + if _dev_prop.Get('','DeviceFile')==device: + _idev = dbus.Interface(_dev_obj, 'org.freedesktop.DBus.UDisks.Device') + res = _idev.get_dbus_method('FilesystemMount', + dbus_interface='org.freedesktop.UDisks.Device')(fs,[]) + return res class IsoDumper: def __init__(self,user): APP="isodumper" DIR="/usr/share/locale" - RELEASE="v0.32" + RELEASE="v0.33" gettext.bindtextdomain(APP, DIR) gettext.textdomain(APP) @@ -114,7 +128,6 @@ class IsoDumper: "on_emergency_button_clicked" : self.restore, "on_confirm_cancel_button_clicked": self.restore, "on_filechooserbutton_file_set" : self.activate_devicelist, -# "on_detail_expander_activate" : self.expander_control, "on_device_combobox_changed" : self.device_selected, "on_nodev_close_clicked" : self.close, "on_backup_button_clicked" : self.backup_go, @@ -126,6 +139,9 @@ class IsoDumper: "on_format_cancel_clicked" : self.format_cancel, "on_format_go_clicked" : self.do_format, "on_write_button_clicked" : self.do_write, + "on_help_close_clicked": self.help_close, + "on_help_clicked": self.help_dialog, + "on_update_button_clicked":self.update_list, } self.wTree.signal_autoconnect(dict) @@ -133,31 +149,38 @@ class IsoDumper: # make sure we have a target device self.get_devices() + def update_list(self, widget): + self.devicelist.remove_text(0) + self.get_devices() + self.restore(widget) def get_devices(self): dialog = self.wTree.get_widget("nodev_dialog") - list=find_devices() - while len(list)==0: + self.list=find_devices() + while len(self.list)==0: exit_dialog=dialog.run() - list = find_devices() + self.list = find_devices() if (exit_dialog==2) : dialog.destroy() exit(0) -# self.combo = self.wTree.get_widget("device_combobox") - for name, path, size in list: - self.deviceSize=size + for name, path, size in self.list: # convert in Mbytes sizeM=str(int(size)/(1024*1024)) self.devicelist.append_text(name+' ('+path.lstrip()+') '+sizeM+_('Mb')) - self.device_name=name.rstrip().replace(' ', '') - dialog.destroy() + dialog.hide() def device_selected(self, widget): self.dev = self.devicelist.get_active_text() - self.backup_select.set_sensitive(True) - self.wTree.get_widget("format_button").set_sensitive(True) - self.wTree.get_widget("filechooserbutton").set_sensitive(True) - self.logger(_('Target Device: ')+ self.dev) + if self.dev != None: + for name, path, size in self.list: + if self.dev.startswith(name): + self.deviceSize=eval(size) + self.device_name=name.rstrip().replace(' ', '') + break + self.backup_select.set_sensitive(True) + self.wTree.get_widget("format_button").set_sensitive(True) + self.wTree.get_widget("filechooserbutton").set_sensitive(True) + self.logger(_('Target Device: ')+ self.dev) def backup_sel(self,widget): if self.backup_bname.get_current_folder_uri() == None : @@ -212,11 +235,31 @@ class IsoDumper: if resp: dialog.hide() if self.wTree.get_widget("format_fat").get_active(): - self.raw_format(target, 'fat32', dev_name.upper()[:11]) + rc=self.raw_format(target, 'fat32', dev_name.upper()[:11]) if self.wTree.get_widget("format_ntfs").get_active(): - self.raw_format(target, 'ntfs', dev_name[:32]) + rc=self.raw_format(target, 'ntfs', dev_name[:32]) if self.wTree.get_widget("format_ext4").get_active(): - self.raw_format(target, 'ext4', dev_name) + rc=self.raw_format(target, 'ext4', dev_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() + elif rc == 127: + message = _('Authentication error.') + self.logger(message) + self.emergency() + else: + message = _('An error occurred.') + self.emergency() + self.wTree.get_widget("format").hide() + self.backup_select.set_sensitive(True) + self.wTree.get_widget("format_button").set_sensitive(True) + self.wTree.get_widget("filechooserbutton").set_sensitive(True) else: dialog.hide() @@ -225,8 +268,11 @@ class IsoDumper: self.wTree.get_widget("format_button").set_sensitive(True) self.wTree.get_widget("filechooserbutton").set_sensitive(True) self.devicelist.set_sensitive(True) -# self.write_logfile() self.wTree.get_widget("emergency_dialog").hide() + progress = self.wTree.get_widget("progressbar") + progress.set_text("") + progress.set_fraction(0) + progress.set_sensitive(False) def raw_format(self, usb_path, fstype, label): self.operation=True @@ -243,27 +289,9 @@ class IsoDumper: if rc is None: working=True else: - 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() - elif rc == 127: - message = _('Authentication error.') - self.logger(message) - self.emergency() - else: - message = _('An error occurred.') - self.emergency() - self.wTree.get_widget("format").hide() self.process = None working= False - self.backup_select.set_sensitive(True) - self.wTree.get_widget("format_button").set_sensitive(True) - self.wTree.get_widget("filechooserbutton").set_sensitive(True) + return rc def format_cancel(self, widget): dialog=self.wTree.get_widget("format") @@ -280,18 +308,26 @@ class IsoDumper: if os.path.exists(dest): dialog=self.wTree.get_widget("confirm_overwrite") resp=dialog.run() - if resp !=-5: + if resp !=-5: # GTK_RESPONSE_OK dialog.hide() return True else: dialog.hide() - source = self.dev.split('(')[1].split(')')[0] - self.logger(_('Backup in:')+' '+dest) - task = self.raw_write(source, dest, eval(self.deviceSize)) - gobject.idle_add(task.next) - while gtk.events_pending(): - gtk.main_iteration(True) - self.success() + # check free space + st = os.statvfs(os.path.dirname(dest)) + free = st.f_bavail * st.f_frsize + if free<self.deviceSize : + sizeM=str(self.deviceSize/(1024*1024)) + self.logger(_("The destination directory is too small to receive the backup (%s Mb needed)")%(sizeM)) + self.emergency() + else: + source = self.dev.split('(')[1].split(')')[0] + self.logger(_('Backup in:')+' '+dest) + task = self.raw_write(source, dest, self.deviceSize) + gobject.idle_add(task.next) + while gtk.events_pending(): + gtk.main_iteration(True) + self.success() def do_write(self, widget): write_button = self.wTree.get_widget("write_button") @@ -310,13 +346,13 @@ class IsoDumper: self.logger(_('Image: ')+source) self.logger(_('Target Device: ')+self.dev) b = os.path.getsize(source) - if b > (eval(self.deviceSize)) : + if b > (self.deviceSize): self.logger(_('The device is too small to contain the ISO file.')) self.emergency() else: resp = dialog.run() if resp: - if eval(self.deviceSize)> 1024*1024*1024*32 : + if self.deviceSize> 1024*1024*1024*32 : message=self.wTree.get_widget("label1") message.set_text(_('The device is bigger than 32 Gbytes. Are you sure you want use it?')) resp = dialog.run() @@ -332,16 +368,54 @@ class IsoDumper: self.do_umount(target) dialog.hide() # Writing step - task = self.raw_write(source, target, os.path.getsize(source)) - gobject.idle_add(task.next) - while gtk.events_pending(): - gtk.main_iteration(True) - self.success() + # Iso dump or Uefi preparation + uefi_checkbox=self.wTree.get_widget("uefi_check") + if uefi_checkbox.get_active(): + #uefi mode : formats FAT32, names MGALIVE, copies the ISO + target = self.dev.split('(')[1].split(')')[0] + dev_name="MGALIVE" + rc=self.raw_format(target, 'fat32', dev_name) + if rc == 0: + message = _('The device was formatted successfully.') + self.logger(message) + elif rc == 5: + message = _("An error occurred while creating a partition.") + self.logger(message) + self.emergency() + elif rc == 127: + message = _('Authentication error.') + self.logger(message) + self.emergency() + else: + message = _('An error occurred.') + self.emergency() + if rc == 0: + dest=mount(target+'1','vfat') + if dest!="": + self.logger(_("Mounted in: ")+dest) + dest+='/'+os.path.basename(source) + self.logger(_('Executing copy from ')+source+_(' to ')+dest) + task = self.raw_write(source, dest, os.path.getsize(source)) + gobject.idle_add(task.next) + while gtk.events_pending(): + gtk.main_iteration(True) + self.success() + else: + self.logger(_("Error mounting the partition")) + else: + #Dump mode + task = self.raw_write(source, target, os.path.getsize(source)) + gobject.idle_add(task.next) + while gtk.events_pending(): + gtk.main_iteration(True) + self.success() else: # self.close('dummy') dialog.hide() combo.set_sensitive(True) write_button.set_sensitive(True) + format_button.set_sensitive(True) + backup_select.set_sensitive(True) def do_umount(self, target): mounts = self.get_mounted(target) if mounts: @@ -533,6 +607,14 @@ class IsoDumper: # if widget.get_expanded(): # gobject.timeout_add(130, lambda: self.window.reshow_with_initial_size()) + def help_dialog(self, widget): + dialog = self.wTree.get_widget("help_dialog") + dialog.run() + + def help_close(self, widget): + dialog = self.wTree.get_widget("help_dialog") + dialog.hide() + def about(self, widget): about_button = self.wTree.get_widget("about_button") about_button.set_sensitive(True) diff --git a/lib/raw_format.py b/lib/raw_format.py index 8ef036b..84ca8c7 100755 --- a/lib/raw_format.py +++ b/lib/raw_format.py @@ -5,11 +5,11 @@ # Copyright (C) 2013 THE isodumper'S COPYRIGHT HOLDER # This file is distributed under the same license as the isodumper package. # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -59,18 +59,23 @@ def get_mounted(target): sys.exit(6) def raw_format(device_path, fstype, volume_label, uid, gid): - + do_umount(device_path) - + # First erase MBR and partition table , if any os.system ("dd if=/dev/zero of=%s bs=512 count=1 >/dev/null 2>&1" % device_path) - + device = parted.getDevice(device_path) - - # Create a default partition set up + + # Create a default partition set up disk = parted.freshDisk(device, 'msdos') - disk.commit() - regions = disk.getFreeSpaceRegions() + try: + disk.commit() + except: + # Unable to write the new partition + print "Can't create partition. The device could be read-only." + sys.exit(5) + regions = disk.getFreeSpaceRegions() if len(regions) > 0: #print "Build partition" @@ -79,7 +84,7 @@ def raw_format(device_path, fstype, volume_label, uid, gid): #print "start %s" % start end = device.getLength() - start - 1024 #print end - + # Alignment #cylinder = device.endSectorToCylinder(end) #end = device.endCylinderToSector(cylinder) @@ -89,16 +94,16 @@ def raw_format(device_path, fstype, volume_label, uid, gid): except: print "Geometry error - Can't create partition" sys.exit(5) - + # fstype fs = parted.FileSystem(type=fstype, geometry=geometry) - + # Create partition partition = parted.Partition(disk=disk, type=parted.PARTITION_NORMAL, geometry=geometry, fs=fs) constraint = parted.Constraint(exactGeom=geometry) disk.addPartition(partition=partition, constraint=constraint) disk.commit() - + # Format partition according to the fstype specified if fstype == "fat32": os.system("mkdosfs -F 32 -n \"%s\" %s >/dev/null 2>&1" % (volume_label, partition.path)) @@ -144,14 +149,14 @@ def main(): uid = a elif o in ("-g"): gid = a - + argc = len(sys.argv) if argc < 11: print "Too few arguments" print "for help use --help" exit(2) - + raw_format(device, fstype, label, uid, gid) - + if __name__ == "__main__": main() diff --git a/share/isodumper/isodumper.glade b/share/isodumper/isodumper.glade index 64769c4..15e4246 100644 --- a/share/isodumper/isodumper.glade +++ b/share/isodumper/isodumper.glade @@ -418,9 +418,6 @@ Public License instead of this License. <property name="position">0</property> </packing> </child> - <child> - <placeholder/> - </child> </widget> </child> </widget> @@ -885,6 +882,91 @@ Public License instead of this License. </widget> </child> </widget> + <widget class="GtkDialog" id="help_dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Isodumper - Help</property> + <property name="window_position">center-on-parent</property> + <property name="default_width">500</property> + <property name="default_height">300</property> + <property name="type_hint">dialog</property> + <property name="transient_for">main_dialog</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <widget class="GtkButton" id="help_close"> + <property name="label">gtk-close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_help_close_clicked"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <widget class="GtkTextView" id="textview1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="pixels_above_lines">2</property> + <property name="pixels_below_lines">2</property> + <property name="editable">False</property> + <property name="wrap_mode">word</property> + <property name="left_margin">5</property> + <property name="right_margin">5</property> + <property name="text" translatable="yes">Mageia IsoDumper +---------------- +This GUI program is primarily for safely writing a bootable ISO image to a USB flash drive, an operation devious & potentially hazardous when done by hand. As a bonus, it can also back up the entire previous contents of the flash drive onto the hard disc, and restore the flash drive to its previous state subsequently. It gives also a feature for formatting the USB device. + +IsoDumper can be launched either from the menus, or a user or root console with the command 'isodumper'. For normal users, the root password is solicited; this is necessary for the program's operation. The flash drive can be inserted beforehand or once the program is started. In the latter case, a dialogue will say that there is no flash drive inserted, and allow a 'retry' to find it once it is. (You may have to close any automatically opened File Manager window). + +The fields of the main window are as follows: +- Device to work on: the device of the USB flash drive, a drop-down list to choose from. +- Write Image: to choose the source ISO image *.iso (or flash drive backup file *.img) to write out. +- Write to device: This button launches the operation - with a prior warning dialogue. If a flash drive backup was requested, this is done first. Then (or only) the image file write. Each operation is shown in the progress bar beneath. +- Backup in: define the name and placement of the backup image file. The current flash drive will be backed up to a disc file. Note that the entire flash drive is preserved, regardless of its actual contents; ensure that you have the necessary free disc space (the same size as the USB device). This backup file can be used later to restore the flash drive by selecting it as the source *.img file to write out. +- Backup the device: launch the backup operation. +- Format the device: create an unique partition on the entire volume in the specified format in FAT, NTFS or ext. You can specify a volume name and the format in a new dialog box. +- Details: this button shows detailed log information. + +</property> + </widget> + </child> + </widget> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> <widget class="GtkWindow" id="main_dialog"> <property name="can_focus">False</property> <property name="title" translatable="yes">IsoDumper</property> @@ -1011,6 +1093,21 @@ Public License instead of this License. </packing> </child> <child> + <widget class="GtkCheckButton" id="uefi_check"> + <property name="label" translatable="yes">For UEFI boot</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip" translatable="yes">Use FAT32 format with MGALIVE as volume name.</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> <widget class="GtkButton" id="write_button"> <property name="visible">True</property> <property name="sensitive">False</property> @@ -1053,7 +1150,7 @@ Public License instead of this License. <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> </widget> @@ -1362,9 +1459,24 @@ Public License instead of this License. <widget class="GtkHButtonBox" id="hbuttonbox"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="spacing">5</property> + <property name="spacing">4</property> <property name="layout_style">end</property> <child> + <widget class="GtkButton" id="update_button"> + <property name="label">gtk-refresh</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_update_button_clicked"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> <widget class="GtkButton" id="about_button"> <property name="label">gtk-about</property> <property name="visible">True</property> @@ -1376,7 +1488,22 @@ Public License instead of this License. <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">0</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="help"> + <property name="label">gtk-help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_help_clicked"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> </packing> </child> <child> @@ -1391,7 +1518,7 @@ Public License instead of this License. <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">1</property> + <property name="position">3</property> </packing> </child> </widget> |