From 6869760732464f0a714db32d96b8ae9779bcda9c Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Fri, 6 Sep 2013 20:59:01 -0400 Subject: [PATCH] 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. --- virtManager/addhardware.py | 112 +++++++++++++++------------------ virtManager/asyncjob.py | 90 ++++++++++++++------------ virtManager/clone.py | 32 +++++----- virtManager/create.py | 66 ++++++++++--------- virtManager/createinterface.py | 43 +++++++------ virtManager/createpool.py | 30 +++++---- virtManager/createvol.py | 32 +++++----- virtManager/delete.py | 26 ++++---- virtManager/engine.py | 11 ++-- virtManager/migrate.py | 47 +++++++------- virtManager/snapshots.py | 34 +++++----- 11 files changed, 272 insertions(+), 251 deletions(-) diff --git a/virtManager/addhardware.py b/virtManager/addhardware.py index 6a2ff926c..542eddb8f 100644 --- a/virtManager/addhardware.py +++ b/virtManager/addhardware.py @@ -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() ########################### diff --git a/virtManager/asyncjob.py b/virtManager/asyncjob.py index f152f2fa5..f0367d8f9 100644 --- a/virtManager/asyncjob.py +++ b/virtManager/asyncjob.py @@ -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: diff --git a/virtManager/clone.py b/virtManager/clone.py index 9e20c51a4..96d57a020 100644 --- a/virtManager/clone.py +++ b/virtManager/clone.py @@ -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: diff --git a/virtManager/create.py b/virtManager/create.py index c3fe51eca..c80243ceb 100644 --- a/virtManager/create.py +++ b/virtManager/create.py @@ -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() diff --git a/virtManager/createinterface.py b/virtManager/createinterface.py index 093480bff..e7ca6411a 100644 --- a/virtManager/createinterface.py +++ b/virtManager/createinterface.py @@ -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) diff --git a/virtManager/createpool.py b/virtManager/createpool.py index fd9f805a2..dac436c2c 100644 --- a/virtManager/createpool.py +++ b/virtManager/createpool.py @@ -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() diff --git a/virtManager/createvol.py b/virtManager/createvol.py index 857b15365..0ba9f0893 100644 --- a/virtManager/createvol.py +++ b/virtManager/createvol.py @@ -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() diff --git a/virtManager/delete.py b/virtManager/delete.py index e11acb1ef..64ccdaf1e 100644 --- a/virtManager/delete.py +++ b/virtManager/delete.py @@ -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 = [] diff --git a/virtManager/engine.py b/virtManager/engine.py index 3ae5b3de5..161860b4e 100644 --- a/virtManager/engine.py +++ b/virtManager/engine.py @@ -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") diff --git a/virtManager/migrate.py b/virtManager/migrate.py index e68efea51..ef42516d1 100644 --- a/virtManager/migrate.py +++ b/virtManager/migrate.py @@ -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(): diff --git a/virtManager/snapshots.py b/virtManager/snapshots.py index ec2c50284..61884c515 100644 --- a/virtManager/snapshots.py +++ b/virtManager/snapshots.py @@ -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):