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.get_nth_page(page).show()
notebook.set_current_page(page) 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): def show_pair_combo(self, basename, show_combo):
combo = self.widget(basename + "-combo") combo = self.widget(basename + "-combo")
label = self.widget(basename + "-label") label = self.widget(basename + "-label")
@@ -1137,36 +1106,12 @@ class vmmAddHardware(vmmGObjectUI):
# Add device methods # # Add device methods #
###################### ######################
def _storage_progress(self): def setup_device(self, asyncjob):
def do_file_allocate(asyncjob, disk): logging.debug("Running setup for device=%s", self._dev)
meter = asyncjob.get_meter() self._dev.setup(meter=asyncjob.get_meter())
logging.debug("Setup complete")
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 add_device(self): def add_device(self):
ret = self.setup_device()
if ret and ret[0]:
# Encountered an error
return (True, ret)
self._dev.get_xml_config() self._dev.get_xml_config()
logging.debug("Adding device:\n" + self._dev.get_xml_config()) logging.debug("Adding device:\n" + self._dev.get_xml_config())
@@ -1197,7 +1142,7 @@ class vmmAddHardware(vmmGObjectUI):
modal=True) modal=True)
if not res: if not res:
return (False, None) return False
# Alter persistent config # Alter persistent config
try: try:
@@ -1206,9 +1151,52 @@ class vmmAddHardware(vmmGObjectUI):
self.vm.add_device(self._dev) self.vm.add_device(self._dev)
except Exception, e: except Exception, e:
self.err.show_err(_("Error adding device: %s" % str(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 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): def cb_wrapper(callback, asyncjob, *args, **kwargs):
try: try:
callback(asyncjob, *args, **kwargs) callback(asyncjob, *args, **kwargs)
@@ -108,8 +99,22 @@ def cb_wrapper(callback, asyncjob, *args, **kwargs):
asyncjob.set_error(str(e), "".join(traceback.format_exc())) 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, 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 @show_progress: Whether to actually show a progress dialog
@simplecb: If true, build a callback wrapper that ignores the asyncjob @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) callback(*args, **kwargs)
docb = tmpcb 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) show_progress=show_progress)
error, details = asyncjob.run() asyncjob.run()
if error is None:
return
if errorcb:
errorcb(error, details)
else:
error = errorintro + ": " + error
parent.err.show_err(error,
details=details)
def idle_wrapper(fn): def idle_wrapper(fn):
@@ -148,18 +147,22 @@ class vmmAsyncJob(vmmGObjectUI):
""" """
@staticmethod @staticmethod
def simple_async(callback, args, title, text, parent, errorintro, def simple_async(callback, args, title, text, parent, errorintro,
simplecb=True, errorcb=None): simplecb=True, errorcb=None, finish_cb=None):
_simple_async(callback, args, title, text, parent, errorintro, True, _simple_async(callback, args,
simplecb, errorcb) title, text, parent, errorintro, True,
simplecb, errorcb, finish_cb)
@staticmethod @staticmethod
def simple_async_noshow(callback, args, parent, errorintro, def simple_async_noshow(callback, args, parent, errorintro,
simplecb=True, errorcb=None): simplecb=True, errorcb=None, finish_cb=None):
_simple_async(callback, args, "", "", parent, errorintro, False, _simple_async(callback, args,
simplecb, errorcb) "", "", 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=True, cancel_cb=None):
""" """
@show_progress: If False, don't actually show a progress dialog @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_cb = cancel_cb[0]
self.cancel_args = [self] + list(cancel_cb[1:]) self.cancel_args = [self] + list(cancel_cb[1:])
self.job_canceled = False self.job_canceled = False
self._finish_cb = finish_cb
self._finish_args = finish_args or ()
self._timer = None
self._error_info = None self._error_info = None
self._data = None self._data = None
self._is_pulsing = True self._is_pulsing = True
self._meter = None self._meter = None
args = [self] + args self._bg_thread = threading.Thread(target=cb_wrapper,
self._bg_thread = asyncJobWorker(callback, args) args=[callback, self] + args)
self._bg_thread.daemon = True
logging.debug("Creating async job for function cb=%s", callback) logging.debug("Creating async job for function cb=%s", callback)
self.builder.connect_signals({ self.builder.connect_signals({
@@ -278,8 +285,19 @@ class vmmAsyncJob(vmmGObjectUI):
self.widget("warning-box").show() self.widget("warning-box").show()
self.widget("warning-text").set_markup(markup) 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): 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: if self.show_progress:
self.topwin.present() self.topwin.present()
@@ -287,15 +305,7 @@ class vmmAsyncJob(vmmGObjectUI):
if not self.cancel_cb and self.show_progress: if not self.cancel_cb and self.show_progress:
self.topwin.get_window().set_cursor( self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH)) Gdk.Cursor.new(Gdk.CursorType.WATCH))
self._bg_thread.start() 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): def _exit_if_necessary(self):
if not self._bg_thread.is_alive(): if not self._bg_thread.is_alive():
Gtk.main_quit() self._thread_finished()
return False return False
if not self._is_pulsing or not self.show_progress: if not self._is_pulsing or not self.show_progress:

View File

@@ -783,6 +783,19 @@ class vmmCloneVM(vmmGObjectUI):
self.clone_design = cd self.clone_design = cd
return True 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): def finish(self, src_ignore):
try: try:
if not self.validate(): if not self.validate():
@@ -792,7 +805,8 @@ class vmmCloneVM(vmmGObjectUI):
return return
self.topwin.set_sensitive(False) 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'") % title = (_("Creating virtual machine clone '%s'") %
self.clone_design.clone_name) self.clone_design.clone_name)
@@ -800,19 +814,9 @@ class vmmCloneVM(vmmGObjectUI):
if self.clone_design.clone_disks: if self.clone_design.clone_disks:
text = title + _(" and selected storage (this may take a while)") text = title + _(" and selected storage (this may take a while)")
progWin = vmmAsyncJob(self._async_clone, [], title, text, self.topwin) progWin = vmmAsyncJob(self._async_clone, [], self._finish_cb, [],
error, details = progWin.run() title, text, self.topwin)
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)
def _async_clone(self, asyncjob): def _async_clone(self, asyncjob):
try: try:

View File

@@ -1778,6 +1778,12 @@ class vmmCreate(vmmGObjectUI):
break break
pagenum = self._get_next_pagenum(pagenum) 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): def finish(self, src_ignore):
# Validate the final page # Validate the final page
page = self.widget("create-pages").get_current_page() page = self.widget("create-pages").get_current_page()
@@ -1792,25 +1798,15 @@ class vmmCreate(vmmGObjectUI):
self.topwin.set_sensitive(False) 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))
def start_install(): if self.get_config_customize():
if not self.get_config_customize(): try:
self.start_install(guest) self.customize(guest)
except Exception, e:
self._undo_finish_cursor()
self.err.show_err(_("Error starting installation: ") + str(e))
return return
else:
self.customize(guest) self.start_install(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))
def customize(self, guest): def customize(self, guest):
virtinst_guest = vmmDomainVirtinst(self.conn, guest, self.guest.uuid) virtinst_guest = vmmDomainVirtinst(self.conn, guest, self.guest.uuid)
@@ -1826,11 +1822,11 @@ class vmmCreate(vmmGObjectUI):
cleanup_config_window() cleanup_config_window()
if not self.is_visible(): if not self.is_visible():
return return
self._check_start_error(self.start_install, guest) self.start_install(guest)
def details_closed(ignore): def details_closed(ignore):
cleanup_config_window() cleanup_config_window()
self._undo_finish() self._undo_finish_cursor()
self.widget("summary-customize").set_active(False) self.widget("summary-customize").set_active(False)
cleanup_config_window() cleanup_config_window()
@@ -1845,19 +1841,8 @@ class vmmCreate(vmmGObjectUI):
details_closed)) details_closed))
self.config_window.show() self.config_window.show()
def start_install(self, guest): def _install_finished_cb(self, error, details):
progWin = vmmAsyncJob(self.do_install, [guest], self._undo_finish_cursor()
_("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))
if error: if error:
error = (_("Unable to complete install: '%s'") % error) error = (_("Unable to complete install: '%s'") % error)
@@ -1869,7 +1854,20 @@ class vmmCreate(vmmGObjectUI):
self.close() self.close()
# Launch details dialog for new VM # 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): def do_install(self, asyncjob, guest):
meter = asyncjob.get_meter() meter = asyncjob.get_meter()

View File

@@ -1096,27 +1096,10 @@ class vmmCreateInterface(vmmGObjectUI):
# Creation routines # # Creation routines #
##################### #####################
def finish(self, src): def _finish_cb(self, error, details):
# 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()
self.topwin.set_sensitive(True) 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: if error:
error = _("Error creating interface: '%s'") % error error = _("Error creating interface: '%s'") % error
@@ -1126,6 +1109,26 @@ class vmmCreateInterface(vmmGObjectUI):
self.conn.schedule_priority_tick(polliface=True) self.conn.schedule_priority_tick(polliface=True)
self.close() 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): def do_install(self, asyncjob, activate):
meter = asyncjob.get_meter() meter = asyncjob.get_meter()
self.interface.install(meter, create=activate) self.interface.install(meter, create=activate)

View File

@@ -432,20 +432,10 @@ class vmmCreatePool(vmmGObjectUI):
self.widget("pool-forward").show() self.widget("pool-forward").show()
self.widget("pool-pages").prev_page() self.widget("pool-pages").prev_page()
def finish(self): def _finish_cb(self, error, details):
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()
self.topwin.set_sensitive(True) 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: if error:
error = _("Error creating pool: %s") % error error = _("Error creating pool: %s") % error
@@ -455,6 +445,20 @@ class vmmCreatePool(vmmGObjectUI):
self.conn.schedule_priority_tick(pollpool=True) self.conn.schedule_priority_tick(pollpool=True)
self.close() 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): def _async_pool_create(self, asyncjob, build):
meter = asyncjob.get_meter() meter = asyncjob.get_meter()

View File

@@ -217,6 +217,20 @@ class vmmCreateVolume(vmmGObjectUI):
if cap < alloc: if cap < alloc:
alloc_widget.set_value(cap) 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): def finish(self, src_ignore):
try: try:
if not self.validate(): if not self.validate():
@@ -229,26 +243,16 @@ class vmmCreateVolume(vmmGObjectUI):
self.vol.get_xml_config()) self.vol.get_xml_config())
self.topwin.set_sensitive(False) 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, [], progWin = vmmAsyncJob(self._async_vol_create, [],
self._finish_cb, [],
_("Creating storage volume..."), _("Creating storage volume..."),
_("Creating the storage volume may take a " _("Creating the storage volume may take a "
"while..."), "while..."),
self.topwin) self.topwin)
error, details = progWin.run() 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()
def _async_vol_create(self, asyncjob): def _async_vol_create(self, asyncjob):
conn = self.conn.get_backend() conn = self.conn.get_backend()

View File

@@ -131,6 +131,17 @@ class vmmDeleteDialog(vmmGObjectUI):
paths.append(row[STORAGE_ROW_PATH]) paths.append(row[STORAGE_ROW_PATH])
return paths 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): def finish(self, src_ignore):
devs = self.get_paths_to_delete() devs = self.get_paths_to_delete()
@@ -146,7 +157,8 @@ class vmmDeleteDialog(vmmGObjectUI):
return return
self.topwin.set_sensitive(False) 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() title = _("Deleting virtual machine '%s'") % self.vm.get_name()
text = title text = title
@@ -154,17 +166,9 @@ class vmmDeleteDialog(vmmGObjectUI):
text = title + _(" and selected storage (this may take a while)") text = title + _(" and selected storage (this may take a while)")
progWin = vmmAsyncJob(self._async_delete, [devs], progWin = vmmAsyncJob(self._async_delete, [devs],
self._finish_cb, [],
title, text, self.topwin) title, text, self.topwin)
error, details = progWin.run() 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()
def _async_delete(self, asyncjob, paths): def _async_delete(self, asyncjob, paths):
storage_errors = [] storage_errors = []

View File

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

View File

@@ -443,6 +443,20 @@ class vmmMigrateDialog(vmmGObjectUI):
return True 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): def finish(self, src_ignore):
try: try:
if not self.validate(): if not self.validate():
@@ -467,33 +481,22 @@ class vmmMigrateDialog(vmmGObjectUI):
return return
self.topwin.set_sensitive(False) 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 cancel_cb = None
if self.vm.getjobinfo_supported: if self.vm.getjobinfo_supported:
cancel_cb = (self.cancel_migration, self.vm) cancel_cb = (self.cancel_migration, self.vm)
progWin = vmmAsyncJob(self._async_migrate, progWin = vmmAsyncJob(
[self.vm, destconn, uri, rate, live, secure, self._async_migrate,
max_downtime], [self.vm, destconn, uri, rate, live, secure, max_downtime],
_("Migrating VM '%s'" % self.vm.get_name()), self._finish_cb, [destconn],
(_("Migrating VM '%s' from %s to %s. " _("Migrating VM '%s'" % self.vm.get_name()),
"This may take a while.") % (_("Migrating VM '%s' from %s to %s. This may take a while.") %
(self.vm.get_name(), srchost, dsthost)), (self.vm.get_name(), srchost, dsthost)),
self.topwin, cancel_cb=cancel_cb) self.topwin, cancel_cb=cancel_cb)
error, details = progWin.run() 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()
def _async_set_max_downtime(self, vm, max_downtime, migrate_thread): def _async_set_max_downtime(self, vm, max_downtime, migrate_thread):
if not migrate_thread.isAlive(): if not migrate_thread.isAlive():

View File

@@ -261,6 +261,18 @@ class vmmSnapshotPage(vmmGObjectUI):
# XXX refresh in place # 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): def _on_new_ok_clicked(self, ignore):
name = self.widget("snapshot-new-name").get_text() name = self.widget("snapshot-new-name").get_text()
@@ -278,21 +290,11 @@ class vmmSnapshotPage(vmmGObjectUI):
progWin = vmmAsyncJob( progWin = vmmAsyncJob(
lambda ignore, xml: self.vm.create_snapshot(xml), lambda ignore, xml: self.vm.create_snapshot(xml),
[newsnap.get_xml_config()], [newsnap.get_xml_config()],
self._finish_cb, [],
_("Creating snapshot"), _("Creating snapshot"),
_("Creating virtual machine snapshot"), _("Creating virtual machine snapshot"),
self.topwin) self.topwin)
progWin.run()
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()
def _on_add_clicked(self, ignore): def _on_add_clicked(self, ignore):
snap = self._get_current_snapshot() snap = self._get_current_snapshot()
@@ -327,8 +329,8 @@ class vmmSnapshotPage(vmmGObjectUI):
vmmAsyncJob.simple_async_noshow(self.vm.revert_to_snapshot, vmmAsyncJob.simple_async_noshow(self.vm.revert_to_snapshot,
[snap], self, [snap], self,
_("Error reverting to snapshot '%s'") % _("Error reverting to snapshot '%s'") %
snap.get_name()) snap.get_name(),
self._refresh_snapshots() finish_cb=self._refresh_snapshots)
def _on_delete_clicked(self, ignore): def _on_delete_clicked(self, ignore):
snap = self._get_current_snapshot() snap = self._get_current_snapshot()
@@ -346,8 +348,8 @@ class vmmSnapshotPage(vmmGObjectUI):
logging.debug("Deleting snapshot '%s'", snap.get_name()) logging.debug("Deleting snapshot '%s'", snap.get_name())
vmmAsyncJob.simple_async_noshow(snap.delete, [], self, vmmAsyncJob.simple_async_noshow(snap.delete, [], self,
_("Error deleting snapshot '%s'") % snap.get_name()) _("Error deleting snapshot '%s'") % snap.get_name(),
self._refresh_snapshots() finish_cb=self._refresh_snapshots)
def _snapshot_selected(self, selection): def _snapshot_selected(self, selection):