Package liveusb :: Module gui
[hide private]
[frames] | no frames]

Source Code for Module liveusb.gui

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright © 2008  Red Hat, Inc. All rights reserved. 
  4  # Copyright © 2008  Kushal Das <kushal@fedoraproject.org> 
  5  # 
  6  # This copyrighted material is made available to anyone wishing to use, modify, 
  7  # copy, or redistribute it subject to the terms and conditions of the GNU 
  8  # General Public License v.2.  This program is distributed in the hope that it 
  9  # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the 
 10  # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 11  # See the GNU General Public License for more details.  You should have 
 12  # received a copy of the GNU General Public License along with this program; if 
 13  # not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth 
 14  # Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are 
 15  # incorporated in the source code or documentation are not subject to the GNU 
 16  # General Public License and may only be used or replicated with the express 
 17  # permission of Red Hat, Inc. 
 18  # 
 19  # Author(s): Luke Macken <lmacken@redhat.com> 
 20  #            Kushal Das <kushal@fedoraproject.org> 
 21   
 22  """ 
 23  A cross-platform graphical interface for the LiveUSBCreator 
 24  """ 
 25   
 26  import os 
 27  import logging 
 28   
 29  from time import sleep 
 30  from datetime import datetime 
 31  from PyQt4 import QtCore, QtGui 
 32   
 33  from liveusb import LiveUSBCreator, LiveUSBError, LiveUSBInterface, _ 
 34  from liveusb.releases import releases 
 35  from liveusb.urlgrabber.grabber import URLGrabber, URLGrabError 
 36  from liveusb.urlgrabber.progress import BaseMeter 
 37   
 38  try: 
 39      import dbus.mainloop.qt 
 40      dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True) 
 41  except: 
 42      pass 
43 44 45 -class LiveUSBApp(QtGui.QApplication):
46 """ Main application class """
47 - def __init__(self, opts, args):
48 QtGui.QApplication.__init__(self, args) 49 self.mywindow = LiveUSBDialog(opts) 50 self.mywindow.show() 51 try: 52 self.exec_() 53 finally: 54 self.mywindow.terminate()
55
56 57 -class ReleaseDownloader(QtCore.QThread):
58
59 - def __init__(self, release, progress, proxies):
60 QtCore.QThread.__init__(self) 61 self.release = release 62 self.progress = progress 63 self.proxies = proxies 64 for rel in releases: 65 if rel['name'] == str(release): 66 self.url = rel['url'] 67 break 68 else: 69 raise LiveUSBError(_("Unknown release: %s" % release))
70
71 - def run(self):
72 self.emit(QtCore.SIGNAL("status(PyQt_PyObject)"), 73 _("Downloading %s..." % os.path.basename(self.url))) 74 grabber = URLGrabber(progress_obj=self.progress, proxies=self.proxies) 75 try: 76 iso = grabber.urlgrab(self.url, reget='simple') 77 except URLGrabError, e: 78 self.emit(QtCore.SIGNAL("dlcomplete(PyQt_PyObject)"), e.strerror) 79 else: 80 self.emit(QtCore.SIGNAL("dlcomplete(PyQt_PyObject)"), iso)
81
82 83 -class DownloadProgress(QtCore.QObject, BaseMeter):
84 """ A QObject urlgrabber BaseMeter class. 85 86 This class is called automatically by urlgrabber with our download details. 87 This class then sends signals to our main dialog window to update the 88 progress bar. 89 """
90 - def start(self, filename=None, url=None, basename=None, size=None, 91 now=None, text=None):
92 self.emit(QtCore.SIGNAL("maxprogress(int)"), size)
93
94 - def update(self, amount_read, now=None):
95 """ Update our download progressbar. 96 97 :read: the number of bytes read so far 98 """ 99 self.emit(QtCore.SIGNAL("progress(int)"), amount_read)
100
101 - def end(self, amount_read):
102 self.update(amount_read)
103
104 105 -class ProgressThread(QtCore.QThread):
106 """ A thread that monitors the progress of Live USB creation. 107 108 This thread periodically checks the amount of free space left on the 109 given drive and sends a signal to our main dialog window to update the 110 progress bar. 111 """ 112 totalsize = 0 113 orig_free = 0 114 drive = None 115 get_free_bytes = None 116
117 - def set_data(self, size, drive, freebytes):
118 self.totalsize = size / 1024 119 self.drive = drive 120 self.get_free_bytes = freebytes 121 self.orig_free = self.get_free_bytes() 122 self.emit(QtCore.SIGNAL("maxprogress(int)"), self.totalsize)
123
124 - def run(self):
125 while True: 126 free = self.get_free_bytes() 127 value = (self.orig_free - free) / 1024 128 self.emit(QtCore.SIGNAL("progress(int)"), value) 129 if value >= self.totalsize: 130 break 131 sleep(4)
132
133 - def terminate(self):
134 self.emit(QtCore.SIGNAL("progress(int)"), self.totalsize) 135 QtCore.QThread.terminate(self)
136
137 138 -class LiveUSBThread(QtCore.QThread):
139
140 - def __init__(self, live, progress, parent=None):
141 QtCore.QThread.__init__(self, parent) 142 self.progress = progress 143 self.parent = parent 144 self.live = live
145
146 - def status(self, text):
147 self.emit(QtCore.SIGNAL("status(PyQt_PyObject)"), text)
148
149 - def run(self):
150 handler = LiveUSBLogHandler(self.status) 151 self.live.log.addHandler(handler) 152 now = datetime.now() 153 try: 154 self.live.verify_filesystem() 155 if not self.live.drive['uuid'] and not self.live.label: 156 self.status(_("Error: Cannot set the label or obtain " 157 "the UUID of your device. Unable to continue.")) 158 return 159 160 self.live.check_free_space() 161 162 if not self.parent.opts.noverify: 163 # Verify the MD5 checksum inside of the ISO image 164 if not self.live.verify_iso_md5(): 165 return 166 167 # If we know about this ISO, and it's SHA1 -- verify it 168 release = self.live.get_release_from_iso() 169 if release and release['sha1']: 170 if not self.live.verify_iso_sha1(progress=self): 171 return 172 173 # Setup the progress bar 174 self.progress.set_data(size=self.live.totalsize, 175 drive=self.live.drive['device'], 176 freebytes=self.live.get_free_bytes) 177 self.progress.start() 178 179 self.live.extract_iso() 180 self.live.create_persistent_overlay() 181 self.live.update_configs() 182 self.live.install_bootloader() 183 184 duration = str(datetime.now() - now).split('.')[0] 185 self.status(_("Complete! (%s)" % duration)) 186 except LiveUSBError, e: 187 self.status(e.message) 188 self.status(_("LiveUSB creation failed!")) 189 except Exception, e: 190 self.status(e.message) 191 self.status(_("LiveUSB creation failed!")) 192 import traceback 193 self.live.log.debug(traceback.format_exc()) 194 195 self.live.log.removeHandler(handler) 196 self.progress.terminate()
197
198 - def set_max_progress(self, maximum):
199 self.emit(QtCore.SIGNAL("maxprogress(int)"), maximum)
200
201 - def update_progress(self, value):
202 self.emit(QtCore.SIGNAL("progress(int)"), value)
203
204 - def __del__(self):
205 self.wait()
206
207 208 -class LiveUSBLogHandler(logging.Handler):
209
210 - def __init__(self, cb):
211 logging.Handler.__init__(self) 212 self.cb = cb
213
214 - def emit(self, record):
215 if record.levelname in ('INFO', 'ERROR'): 216 self.cb(record.msg.encode('utf8', 'replace'))
217
218 219 -class LiveUSBDialog(QtGui.QDialog, LiveUSBInterface):
220 """ Our main dialog class """
221 - def __init__(self, opts):
222 QtGui.QDialog.__init__(self) 223 LiveUSBInterface.__init__(self) 224 self.opts = opts 225 self.setupUi(self) 226 self.live = LiveUSBCreator(opts=opts) 227 self.populate_releases() 228 self.populate_devices() 229 self.downloader = None 230 self.progress_thread = ProgressThread() 231 self.download_progress = DownloadProgress() 232 self.live_thread = LiveUSBThread(live=self.live, 233 progress=self.progress_thread, 234 parent=self) 235 self.connect_slots() 236 self.confirmed = False 237 238 # Intercept all liveusb INFO log messages, and display them in the gui 239 self.handler = LiveUSBLogHandler(lambda x: self.textEdit.append(x)) 240 self.live.log.addHandler(self.handler)
241
242 - def populate_devices(self, *args, **kw):
243 self.driveBox.clear() 244 self.textEdit.clear() 245 try: 246 self.live.detect_removable_drives() 247 for device, info in self.live.drives.items(): 248 if info['label']: 249 self.driveBox.addItem("%s (%s)" % (device, info['label'])) 250 else: 251 self.driveBox.addItem(device) 252 self.startButton.setEnabled(True) 253 except LiveUSBError, e: 254 self.textEdit.setPlainText(e.message.encode('utf8')) 255 self.startButton.setEnabled(False)
256
257 - def populate_releases(self):
258 for release in [release['name'] for release in releases]: 259 self.downloadCombo.addItem(release)
260
261 - def connect_slots(self):
262 self.connect(self.isoBttn, QtCore.SIGNAL("clicked()"), self.selectfile) 263 self.connect(self.startButton, QtCore.SIGNAL("clicked()"), self.begin) 264 self.connect(self.overlaySlider, QtCore.SIGNAL("valueChanged(int)"), 265 self.overlay_value) 266 self.connect(self.live_thread, QtCore.SIGNAL("status(PyQt_PyObject)"), 267 self.status) 268 self.connect(self.live_thread, QtCore.SIGNAL("finished()"), 269 lambda: self.enable_widgets(True)) 270 self.connect(self.live_thread, QtCore.SIGNAL("terminated()"), 271 lambda: self.enable_widgets(True)) 272 self.connect(self.live_thread, QtCore.SIGNAL("progress(int)"), 273 self.progress) 274 self.connect(self.live_thread, QtCore.SIGNAL("maxprogress(int)"), 275 self.maxprogress) 276 self.connect(self.progress_thread, QtCore.SIGNAL("progress(int)"), 277 self.progress) 278 self.connect(self.progress_thread, QtCore.SIGNAL("maxprogress(int)"), 279 self.maxprogress) 280 self.connect(self.download_progress, QtCore.SIGNAL("maxprogress(int)"), 281 self.maxprogress) 282 self.connect(self.download_progress, QtCore.SIGNAL("progress(int)"), 283 self.progress) 284 if hasattr(self, 'refreshDevicesButton'): 285 self.connect(self.refreshDevicesButton, QtCore.SIGNAL("clicked()"), 286 self.populate_devices) 287 288 # If we have access to HAL & DBus, intercept some useful signals 289 if hasattr(self.live, 'hal'): 290 self.live.hal.connect_to_signal('DeviceAdded', 291 self.populate_devices) 292 self.live.hal.connect_to_signal('DeviceRemoved', 293 self.populate_devices)
294 295 @QtCore.pyqtSignature("QString")
296 - def on_driveBox_currentIndexChanged(self, drive):
297 """ Change the maximum overlay size when each drive is selected. 298 299 This sets the maximum megabyte size of the persistent storage slider 300 to the number of free megabytes on the currently selected 301 "Target Device". If the device is not mounted, or if it has more than 302 2gigs of free space, set the maximum to 2047mb, which is apparently 303 the largest file we can/should store on a vfat partition. 304 """ 305 if not str(drive): 306 return 307 freespace = self.live.drives[str(drive).split()[0]]['free'] 308 if not freespace or freespace > 2047: 309 freespace = 2047 310 self.overlaySlider.setMaximum(freespace)
311
312 - def progress(self, value):
313 self.progressBar.setValue(value)
314
315 - def maxprogress(self, value):
316 self.progressBar.setMaximum(value)
317
318 - def status(self, text):
319 self.textEdit.append(text.encode('utf8', 'replace'))
320
321 - def enable_widgets(self, enabled=True):
322 self.startButton.setEnabled(enabled) 323 self.driveBox.setEnabled(enabled) 324 self.overlaySlider.setEnabled(enabled) 325 self.isoBttn.setEnabled(enabled) 326 self.downloadCombo.setEnabled(enabled) 327 if hasattr(self, 'refreshDevicesButton'): 328 self.refreshDevicesButton.setEnabled(enabled)
329
330 - def overlay_value(self, value):
331 self.overlayTitle.setTitle(_("Persistent Storage") + " (%d MB)" % value)
332
333 - def get_selected_drive(self):
334 return str(self.driveBox.currentText()).split()[0]
335
336 - def begin(self):
337 self.enable_widgets(False) 338 self.live.overlay = self.overlaySlider.value() 339 self.live.drive = self.get_selected_drive() 340 try: 341 self.live.mount_device() 342 except LiveUSBError, e: 343 self.status(e.message) 344 self.enable_widgets(True) 345 return 346 347 if self.live.existing_liveos(): 348 if not self.confirmed: 349 self.status(_("Your device already contains a LiveOS.\nIf you " 350 "continue, this will be overwritten.")) 351 if self.live.existing_overlay() and self.overlaySlider.value(): 352 self.status(_("Warning: Creating a new persistent overlay " 353 "will delete your existing one.")) 354 self.status(_("Press 'Create Live USB' again if you wish to " 355 "continue.")) 356 self.confirmed = True 357 self.live.unmount_device() 358 self.enable_widgets(True) 359 return 360 else: 361 # The user has confirmed that they wish to overwrite their 362 # existing Live OS. Here we delete it first, in order to 363 # accurately calculate progress. 364 try: 365 self.live.delete_liveos() 366 except LiveUSBError, e: 367 self.status(e.message) 368 self.live.unmount_device() 369 self.enable_widgets(True) 370 return 371 372 # Remove the log handler, because our live thread will register its own 373 self.live.log.removeHandler(self.handler) 374 375 # If the user has selected an ISO, use it. If not, download one. 376 if self.live.iso: 377 self.live_thread.start() 378 else: 379 self.downloader = ReleaseDownloader( 380 self.downloadCombo.currentText(), 381 progress=self.download_progress, 382 proxies=self.live.get_proxies()) 383 self.connect(self.downloader, 384 QtCore.SIGNAL("dlcomplete(PyQt_PyObject)"), 385 self.download_complete) 386 self.connect(self.downloader, 387 QtCore.SIGNAL("status(PyQt_PyObject)"), 388 self.status) 389 self.downloader.start()
390
391 - def download_complete(self, iso):
392 """ Called by our ReleaseDownloader thread upon completion. 393 394 Upon success, the thread passes in the filename of the downloaded 395 release. If the 'iso' argument is not an existing file, then 396 it is assumed that the download failed and 'iso' should contain 397 the error message. 398 """ 399 if os.path.exists(iso): 400 self.status(_("Download complete!")) 401 self.live.iso = iso 402 self.live_thread.start() 403 else: 404 self.status(_("Download failed: " + iso)) 405 self.status(_("You can try again to resume your download")) 406 self.enable_widgets(True)
407
408 - def selectfile(self):
409 isofile = QtGui.QFileDialog.getOpenFileName(self, _("Select Live ISO"), 410 ".", "ISO (*.iso)" ) 411 if isofile: 412 try: 413 self.live.iso = self._to_unicode(isofile) 414 except Exception, e: 415 self.live.log.error(e.message.encode('utf8')) 416 self.status(_("Sorry, I'm having trouble encoding the filename " 417 "of your livecd. You may have better luck if " 418 "you move your ISO to the root of your drive " 419 "(ie: C:\)")) 420 421 self.live.log.info('%s ' % os.path.basename(self.live.iso) + 422 _("selected"))
423
424 - def terminate(self):
425 """ Terminate any processes that we have spawned """ 426 self.live.terminate()
427
428 - def _to_unicode(self, obj, encoding='utf-8'):
429 if hasattr(obj, 'toUtf8'): # PyQt4.QtCore.QString 430 obj = str(obj.toUtf8()) 431 if isinstance(obj, basestring): 432 if not isinstance(obj, unicode): 433 obj = unicode(obj, encoding, 'replace') 434 return obj
435