2121# pylint: disable=import-error
2222"""Global Qubes Config tool."""
2323import sys
24+ import threading
25+ import time
2426from typing import Dict , Optional , List , Union , Any
2527from html import escape
2628import importlib .resources
2729import logging
2830
2931import qubesadmin
30- import qubesadmin .events
3132import qubesadmin .exc
3233import qubesadmin .vm
3334from ..widgets .gtk_utils import (
3637 load_theme ,
3738 is_theme_light ,
3839 resize_window_to_reasonable ,
40+ show_dialog ,
3941)
4042from ..widgets .gtk_widgets import ProgressBarDialog , ViewportHandler
4143from ..widgets .utils import open_url_in_disposable
@@ -267,6 +269,8 @@ def __init__(self, qapp: qubesadmin.Qubes, policy_manager: PolicyManager):
267269 self .progress_bar_dialog = ProgressBarDialog (
268270 self , _ ("Loading system settings..." )
269271 )
272+ self .save_thread : threading .Thread | None = None
273+ self .save_errors : List [str ] = []
270274 self .handlers : Dict [str , PageHandler ] = {}
271275
272276 def do_command_line (self , command_line ):
@@ -555,13 +559,7 @@ def get_current_page(self) -> Optional[PageHandler]:
555559 self .main_notebook .get_nth_page (page_num ).get_name (), None
556560 )
557561
558- def save_page (self , page : PageHandler ) -> bool :
559- """Save provided page and emit any necessary signals;
560- return True if successful, False otherwise"""
561- # pylint: disable=protected-access
562- # need to invalidate cache before and after saving to avoid
563- # stale cache
564-
562+ def perform_save (self , page ):
565563 self .qapp ._invalidate_cache_all ()
566564 try :
567565 page .save ()
@@ -572,11 +570,56 @@ def save_page(self, page: PageHandler) -> bool:
572570 self .qapp ._invalidate_cache_all ()
573571 page .reset ()
574572 except Exception as ex :
573+ self .save_errors .append (str (ex ))
574+
575+ def save_page (self , page : PageHandler ) -> bool :
576+ """Save provided page and emit any necessary signals;
577+ return True if successful, False otherwise"""
578+ # pylint: disable=protected-access
579+ # need to invalidate cache before and after saving to avoid
580+ # stale cache
581+
582+ self .save_thread = threading .Thread (target = self .perform_save , args = [page ])
583+ self .save_thread .start ()
584+
585+ spinner = None
586+ dialog = None
587+ time .sleep (0.01 )
588+
589+ if self .save_thread .is_alive ():
590+ # show waiting dialog
591+ spinner = Gtk .Spinner ()
592+ spinner .start ()
593+ dialog = show_dialog (
594+ self .main_window ,
595+ _ ("Saving changes" ),
596+ _ ("Saving global configuration changes..." ),
597+ {},
598+ spinner ,
599+ )
600+ dialog .set_deletable (False )
601+ dialog .show ()
602+
603+ # wait for thread and spin spinner
604+ while self .save_thread .is_alive ():
605+ while Gtk .events_pending ():
606+ Gtk .main_iteration ()
607+ time .sleep (0.1 )
608+
609+ # cleanup
610+ if spinner :
611+ spinner .stop ()
612+ if dialog :
613+ dialog .destroy ()
614+
615+ if self .save_errors :
575616 show_error (
576617 self .main_window ,
577618 _ ("Could not save changes" ),
578- _ ("The following error occurred: " ) + escape (str (ex )),
619+ _ ("The following error occurred: " )
620+ + escape ("\n " .join (self .save_errors )),
579621 )
622+ self .save_errors = []
580623 return False
581624 return True
582625
0 commit comments