asyncjob: Fix issues with multiple dialogs (bz 1003101)

Basically, drop usage of nested main loops. As has been documented in
other commit messages, we use nested main loops in ways they aren't
supposed to be used. They gave us async behavior that would block
callers, but had weird behavior in some edge cases.

Switch to having async dialogs be 100% async, requiring the user to
pass in a completion callback which is triggered after the async
action is complete.
This commit is contained in:
Cole Robinson 2013-09-06 20:59:01 -04:00
parent 0551d8956b
commit 6869760732
11 changed files with 272 additions and 251 deletions

View File

@ -893,37 +893,6 @@ class vmmAddHardware(vmmGObjectUI):
notebook.get_nth_page(page).show()
notebook.set_current_page(page)
def finish(self, ignore=None):
notebook = self.widget("create-pages")
try:
if self.validate(notebook.get_current_page()) is False:
return
except Exception, e:
self.err.show_err(_("Uncaught error validating hardware "
"input: %s") % str(e))
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
try:
failure, errinfo = self.add_device()
error, details = errinfo or (None, None)
except Exception, e:
failure = True
error = _("Unable to add device: %s") % str(e)
details = "".join(traceback.format_exc())
if error is not None:
self.err.show_err(error, details=details)
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
self._dev = None
if not failure:
self.close()
def show_pair_combo(self, basename, show_combo):
combo = self.widget(basename + "-combo")
label = self.widget(basename + "-label")
@ -1137,36 +1106,12 @@ class vmmAddHardware(vmmGObjectUI):
# Add device methods #
######################
def _storage_progress(self):
def do_file_allocate(asyncjob, disk):
meter = asyncjob.get_meter()
logging.debug("Starting background file allocate process")
disk.setup(meter=meter)
logging.debug("Allocation completed")
progWin = vmmAsyncJob(do_file_allocate,
[self._dev],
_("Creating Storage File"),
_("Allocation of disk storage may take "
"a few minutes to complete."),
self.topwin)
return progWin.run()
def setup_device(self):
if (self._dev.virtual_device_type == self._dev.VIRTUAL_DEV_DISK and
self._dev.creating_storage()):
return self._storage_progress()
return self._dev.setup()
def setup_device(self, asyncjob):
logging.debug("Running setup for device=%s", self._dev)
self._dev.setup(meter=asyncjob.get_meter())
logging.debug("Setup complete")
def add_device(self):
ret = self.setup_device()
if ret and ret[0]:
# Encountered an error
return (True, ret)
self._dev.get_xml_config()
logging.debug("Adding device:\n" + self._dev.get_xml_config())
@ -1197,7 +1142,7 @@ class vmmAddHardware(vmmGObjectUI):
modal=True)
if not res:
return (False, None)
return False
# Alter persistent config
try:
@ -1206,9 +1151,52 @@ class vmmAddHardware(vmmGObjectUI):
self.vm.add_device(self._dev)
except Exception, e:
self.err.show_err(_("Error adding device: %s" % str(e)))
return (True, None)
return True
return (False, None)
return False
def _finish_cb(self, error, details):
failure = True
if not error:
try:
failure = self.add_device()
except Exception, e:
failure = True
error = _("Unable to add device: %s") % str(e)
details = "".join(traceback.format_exc())
if error is not None:
self.err.show_err(error, details=details)
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
self._dev = None
if not failure:
self.close()
def finish(self, ignore=None):
notebook = self.widget("create-pages")
try:
if self.validate(notebook.get_current_page()) is False:
return
except Exception, e:
self.err.show_err(_("Uncaught error validating hardware "
"input: %s") % str(e))
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
progWin = vmmAsyncJob(self.setup_device, [],
self._finish_cb, [],
_("Creating device"),
_("Depending on the device, this may take "
"a few minutes to complete."),
self.topwin)
progWin.run()
###########################

View File

@ -86,15 +86,6 @@ class vmmMeter(urlgrabber.progress.BaseMeter):
self.started = False
# This thin wrapper only exists so we can put debugging
# code in the run() method every now & then
class asyncJobWorker(threading.Thread):
def __init__(self, callback, args):
args = [callback] + args
threading.Thread.__init__(self, target=cb_wrapper, args=args)
self.daemon = True
def cb_wrapper(callback, asyncjob, *args, **kwargs):
try:
callback(asyncjob, *args, **kwargs)
@ -108,8 +99,22 @@ def cb_wrapper(callback, asyncjob, *args, **kwargs):
asyncjob.set_error(str(e), "".join(traceback.format_exc()))
def _simple_async_done_cb(error, details, errorintro,
errorcb, parent, finish_cb):
if error:
if errorcb:
errorcb(error, details)
else:
error = errorintro + ": " + error
parent.err.show_err(error,
details=details)
if finish_cb:
finish_cb()
def _simple_async(callback, args, title, text, parent, errorintro,
show_progress, simplecb, errorcb):
show_progress, simplecb, errorcb, finish_cb):
"""
@show_progress: Whether to actually show a progress dialog
@simplecb: If true, build a callback wrapper that ignores the asyncjob
@ -122,18 +127,12 @@ def _simple_async(callback, args, title, text, parent, errorintro,
callback(*args, **kwargs)
docb = tmpcb
asyncjob = vmmAsyncJob(docb, args, title, text, parent.topwin,
asyncjob = vmmAsyncJob(docb, args,
_simple_async_done_cb,
(errorintro, errorcb, parent, finish_cb),
title, text, parent.topwin,
show_progress=show_progress)
error, details = asyncjob.run()
if error is None:
return
if errorcb:
errorcb(error, details)
else:
error = errorintro + ": " + error
parent.err.show_err(error,
details=details)
asyncjob.run()
def idle_wrapper(fn):
@ -148,18 +147,22 @@ class vmmAsyncJob(vmmGObjectUI):
"""
@staticmethod
def simple_async(callback, args, title, text, parent, errorintro,
simplecb=True, errorcb=None):
_simple_async(callback, args, title, text, parent, errorintro, True,
simplecb, errorcb)
simplecb=True, errorcb=None, finish_cb=None):
_simple_async(callback, args,
title, text, parent, errorintro, True,
simplecb, errorcb, finish_cb)
@staticmethod
def simple_async_noshow(callback, args, parent, errorintro,
simplecb=True, errorcb=None):
_simple_async(callback, args, "", "", parent, errorintro, False,
simplecb, errorcb)
simplecb=True, errorcb=None, finish_cb=None):
_simple_async(callback, args,
"", "", parent, errorintro, False,
simplecb, errorcb, finish_cb)
def __init__(self, callback, args, title, text, parent,
def __init__(self,
callback, args, finish_cb, finish_args,
title, text, parent,
show_progress=True, cancel_cb=None):
"""
@show_progress: If False, don't actually show a progress dialog
@ -175,15 +178,19 @@ class vmmAsyncJob(vmmGObjectUI):
self.cancel_cb = cancel_cb[0]
self.cancel_args = [self] + list(cancel_cb[1:])
self.job_canceled = False
self._finish_cb = finish_cb
self._finish_args = finish_args or ()
self._timer = None
self._error_info = None
self._data = None
self._is_pulsing = True
self._meter = None
args = [self] + args
self._bg_thread = asyncJobWorker(callback, args)
self._bg_thread = threading.Thread(target=cb_wrapper,
args=[callback, self] + args)
self._bg_thread.daemon = True
logging.debug("Creating async job for function cb=%s", callback)
self.builder.connect_signals({
@ -278,8 +285,19 @@ class vmmAsyncJob(vmmGObjectUI):
self.widget("warning-box").show()
self.widget("warning-text").set_markup(markup)
def _thread_finished(self):
GLib.source_remove(self._timer)
self.topwin.destroy()
self.cleanup()
error = None
details = None
if self._error_info:
error, details = self._error_info
self._finish_cb(error, details, *self._finish_args)
def run(self):
timer = GLib.timeout_add(100, self._exit_if_necessary)
self._timer = GLib.timeout_add(100, self._exit_if_necessary)
if self.show_progress:
self.topwin.present()
@ -287,15 +305,7 @@ class vmmAsyncJob(vmmGObjectUI):
if not self.cancel_cb and self.show_progress:
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
self._bg_thread.start()
Gtk.main()
GLib.source_remove(timer)
self.topwin.destroy()
self.cleanup()
return self._error_info or (None, None)
####################################################################
@ -306,7 +316,7 @@ class vmmAsyncJob(vmmGObjectUI):
def _exit_if_necessary(self):
if not self._bg_thread.is_alive():
Gtk.main_quit()
self._thread_finished()
return False
if not self._is_pulsing or not self.show_progress:

View File

@ -783,6 +783,19 @@ class vmmCloneVM(vmmGObjectUI):
self.clone_design = cd
return True
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
error = (_("Error creating virtual machine clone '%s': %s") %
(self.clone_design.clone_name, error))
self.err.show_err(error, details=details)
else:
self.close()
self.conn.schedule_priority_tick(pollvm=True)
def finish(self, src_ignore):
try:
if not self.validate():
@ -792,7 +805,8 @@ class vmmCloneVM(vmmGObjectUI):
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
title = (_("Creating virtual machine clone '%s'") %
self.clone_design.clone_name)
@ -800,19 +814,9 @@ class vmmCloneVM(vmmGObjectUI):
if self.clone_design.clone_disks:
text = title + _(" and selected storage (this may take a while)")
progWin = vmmAsyncJob(self._async_clone, [], title, text, self.topwin)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
error = (_("Error creating virtual machine clone '%s': %s") %
(self.clone_design.clone_name, error))
self.err.show_err(error, details=details)
else:
self.close()
self.conn.schedule_priority_tick(pollvm=True)
progWin = vmmAsyncJob(self._async_clone, [], self._finish_cb, [],
title, text, self.topwin)
progWin.run()
def _async_clone(self, asyncjob):
try:

View File

@ -1778,6 +1778,12 @@ class vmmCreate(vmmGObjectUI):
break
pagenum = self._get_next_pagenum(pagenum)
def _undo_finish_cursor(self):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
def finish(self, src_ignore):
# Validate the final page
page = self.widget("create-pages").get_current_page()
@ -1792,25 +1798,15 @@ class vmmCreate(vmmGObjectUI):
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
def start_install():
if not self.get_config_customize():
self.start_install(guest)
if self.get_config_customize():
try:
self.customize(guest)
except Exception, e:
self._undo_finish_cursor()
self.err.show_err(_("Error starting installation: ") + str(e))
return
self.customize(guest)
self._check_start_error(start_install)
def _undo_finish(self, ignore=None):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
def _check_start_error(self, cb, *args, **kwargs):
try:
cb(*args, **kwargs)
except Exception, e:
self._undo_finish()
self.err.show_err(_("Error starting installation: ") + str(e))
else:
self.start_install(guest)
def customize(self, guest):
virtinst_guest = vmmDomainVirtinst(self.conn, guest, self.guest.uuid)
@ -1826,11 +1822,11 @@ class vmmCreate(vmmGObjectUI):
cleanup_config_window()
if not self.is_visible():
return
self._check_start_error(self.start_install, guest)
self.start_install(guest)
def details_closed(ignore):
cleanup_config_window()
self._undo_finish()
self._undo_finish_cursor()
self.widget("summary-customize").set_active(False)
cleanup_config_window()
@ -1845,19 +1841,8 @@ class vmmCreate(vmmGObjectUI):
details_closed))
self.config_window.show()
def start_install(self, guest):
progWin = vmmAsyncJob(self.do_install, [guest],
_("Creating Virtual Machine"),
_("The virtual machine is now being "
"created. Allocation of disk storage "
"and retrieval of the installation "
"images may take a few minutes to "
"complete."),
self.topwin)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
def _install_finished_cb(self, error, details):
self._undo_finish_cursor()
if error:
error = (_("Unable to complete install: '%s'") % error)
@ -1869,7 +1854,20 @@ class vmmCreate(vmmGObjectUI):
self.close()
# Launch details dialog for new VM
self.emit("action-show-vm", self.conn.get_uri(), guest.uuid)
self.emit("action-show-vm", self.conn.get_uri(), self.guest.uuid)
def start_install(self, guest):
progWin = vmmAsyncJob(self.do_install, [guest],
self._install_finished_cb, [],
_("Creating Virtual Machine"),
_("The virtual machine is now being "
"created. Allocation of disk storage "
"and retrieval of the installation "
"images may take a few minutes to "
"complete."),
self.topwin)
progWin.run()
def do_install(self, asyncjob, guest):
meter = asyncjob.get_meter()

View File

@ -1096,27 +1096,10 @@ class vmmCreateInterface(vmmGObjectUI):
# Creation routines #
#####################
def finish(self, src):
# Validate the final page
page = self.widget("pages").get_current_page()
if self.validate(page) is not True:
return False
activate = self.widget("interface-activate").get_active()
# Start the install
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
progWin = vmmAsyncJob(self.do_install, [activate],
_("Creating virtual interface"),
_("The virtual interface is now being created."),
self.topwin)
error, details = progWin.run()
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Error creating interface: '%s'") % error
@ -1126,6 +1109,26 @@ class vmmCreateInterface(vmmGObjectUI):
self.conn.schedule_priority_tick(polliface=True)
self.close()
def finish(self, src):
# Validate the final page
page = self.widget("pages").get_current_page()
if self.validate(page) is not True:
return False
activate = self.widget("interface-activate").get_active()
# Start the install
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
progWin = vmmAsyncJob(self.do_install, [activate],
self._finish_cb, [],
_("Creating virtual interface"),
_("The virtual interface is now being created."),
self.topwin)
progWin.run()
def do_install(self, asyncjob, activate):
meter = asyncjob.get_meter()
self.interface.install(meter, create=activate)

View File

@ -432,20 +432,10 @@ class vmmCreatePool(vmmGObjectUI):
self.widget("pool-forward").show()
self.widget("pool-pages").prev_page()
def finish(self):
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
build = self.widget("pool-build").get_active()
progWin = vmmAsyncJob(self._async_pool_create, [build],
_("Creating storage pool..."),
_("Creating the storage pool may take a "
"while..."),
self.topwin)
error, details = progWin.run()
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Error creating pool: %s") % error
@ -455,6 +445,20 @@ class vmmCreatePool(vmmGObjectUI):
self.conn.schedule_priority_tick(pollpool=True)
self.close()
def finish(self):
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
build = self.widget("pool-build").get_active()
progWin = vmmAsyncJob(self._async_pool_create, [build],
self._finish_cb, [],
_("Creating storage pool..."),
_("Creating the storage pool may take a "
"while..."),
self.topwin)
progWin.run()
def _async_pool_create(self, asyncjob, build):
meter = asyncjob.get_meter()

View File

@ -217,6 +217,20 @@ class vmmCreateVolume(vmmGObjectUI):
if cap < alloc:
alloc_widget.set_value(cap)
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Error creating vol: %s") % error
self.show_err(error,
details=details)
else:
# vol-created will refresh the parent pool
self.emit("vol-created")
self.close()
def finish(self, src_ignore):
try:
if not self.validate():
@ -229,26 +243,16 @@ class vmmCreateVolume(vmmGObjectUI):
self.vol.get_xml_config())
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
progWin = vmmAsyncJob(self._async_vol_create, [],
self._finish_cb, [],
_("Creating storage volume..."),
_("Creating the storage volume may take a "
"while..."),
self.topwin)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Error creating vol: %s") % error
self.show_err(error,
details=details)
else:
# vol-created will refresh the parent pool
self.emit("vol-created")
self.close()
progWin.run()
def _async_vol_create(self, asyncjob):
conn = self.conn.get_backend()

View File

@ -131,6 +131,17 @@ class vmmDeleteDialog(vmmGObjectUI):
paths.append(row[STORAGE_ROW_PATH])
return paths
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
self.err.show_err(error, details=details)
self.conn.schedule_priority_tick(pollvm=True)
self.close()
def finish(self, src_ignore):
devs = self.get_paths_to_delete()
@ -146,7 +157,8 @@ class vmmDeleteDialog(vmmGObjectUI):
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
title = _("Deleting virtual machine '%s'") % self.vm.get_name()
text = title
@ -154,17 +166,9 @@ class vmmDeleteDialog(vmmGObjectUI):
text = title + _(" and selected storage (this may take a while)")
progWin = vmmAsyncJob(self._async_delete, [devs],
self._finish_cb, [],
title, text, self.topwin)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
self.err.show_err(error, details=details)
self.conn.schedule_priority_tick(pollvm=True)
self.close()
progWin.run()
def _async_delete(self, asyncjob, paths):
storage_errors = []

View File

@ -912,16 +912,17 @@ class vmmEngine(vmmGObject):
def cb(asyncjob):
vm.save(path, meter=asyncjob.get_meter())
def finish_cb(error, details):
if error is not None:
error = _("Error saving domain: %s") % error
src.err.show_err(error, details=details)
progWin = vmmAsyncJob(cb, [],
finish_cb, [],
_("Saving Virtual Machine"),
_("Saving virtual machine memory to disk "),
src.topwin, cancel_cb=_cancel_cb)
error, details = progWin.run()
if error is not None:
error = _("Error saving domain: %s") % error
src.err.show_err(error, details=details)
progWin.run()
def _save_cancel(self, asyncjob, vm):
logging.debug("Cancelling save job")

View File

@ -443,6 +443,20 @@ class vmmMigrateDialog(vmmGObjectUI):
return True
def _finish_cb(self, error, details, destconn):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Unable to migrate guest: %s") % error
self.err.show_err(error,
details=details)
else:
self.conn.schedule_priority_tick(pollvm=True)
destconn.schedule_priority_tick(pollvm=True)
self.close()
def finish(self, src_ignore):
try:
if not self.validate():
@ -467,33 +481,22 @@ class vmmMigrateDialog(vmmGObjectUI):
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
cancel_cb = None
if self.vm.getjobinfo_supported:
cancel_cb = (self.cancel_migration, self.vm)
progWin = vmmAsyncJob(self._async_migrate,
[self.vm, destconn, uri, rate, live, secure,
max_downtime],
_("Migrating VM '%s'" % self.vm.get_name()),
(_("Migrating VM '%s' from %s to %s. "
"This may take a while.") %
(self.vm.get_name(), srchost, dsthost)),
self.topwin, cancel_cb=cancel_cb)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Unable to migrate guest: %s") % error
self.err.show_err(error,
details=details)
else:
self.conn.schedule_priority_tick(pollvm=True)
destconn.schedule_priority_tick(pollvm=True)
self.close()
progWin = vmmAsyncJob(
self._async_migrate,
[self.vm, destconn, uri, rate, live, secure, max_downtime],
self._finish_cb, [destconn],
_("Migrating VM '%s'" % self.vm.get_name()),
(_("Migrating VM '%s' from %s to %s. This may take a while.") %
(self.vm.get_name(), srchost, dsthost)),
self.topwin, cancel_cb=cancel_cb)
progWin.run()
def _async_set_max_downtime(self, vm, max_downtime, migrate_thread):
if not migrate_thread.isAlive():

View File

@ -261,6 +261,18 @@ class vmmSnapshotPage(vmmGObjectUI):
# XXX refresh in place
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
error = _("Error creating snapshot: %s") % error
self.err.show_err(error, details=details)
return
self._refresh_snapshots()
def _on_new_ok_clicked(self, ignore):
name = self.widget("snapshot-new-name").get_text()
@ -278,21 +290,11 @@ class vmmSnapshotPage(vmmGObjectUI):
progWin = vmmAsyncJob(
lambda ignore, xml: self.vm.create_snapshot(xml),
[newsnap.get_xml_config()],
self._finish_cb, [],
_("Creating snapshot"),
_("Creating virtual machine snapshot"),
self.topwin)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error is not None:
error = _("Error creating snapshot: %s") % error
self.err.show_err(error, details=details)
return
self._refresh_snapshots()
progWin.run()
def _on_add_clicked(self, ignore):
snap = self._get_current_snapshot()
@ -327,8 +329,8 @@ class vmmSnapshotPage(vmmGObjectUI):
vmmAsyncJob.simple_async_noshow(self.vm.revert_to_snapshot,
[snap], self,
_("Error reverting to snapshot '%s'") %
snap.get_name())
self._refresh_snapshots()
snap.get_name(),
finish_cb=self._refresh_snapshots)
def _on_delete_clicked(self, ignore):
snap = self._get_current_snapshot()
@ -346,8 +348,8 @@ class vmmSnapshotPage(vmmGObjectUI):
logging.debug("Deleting snapshot '%s'", snap.get_name())
vmmAsyncJob.simple_async_noshow(snap.delete, [], self,
_("Error deleting snapshot '%s'") % snap.get_name())
self._refresh_snapshots()
_("Error deleting snapshot '%s'") % snap.get_name(),
finish_cb=self._refresh_snapshots)
def _snapshot_selected(self, selection):