@@ -614,6 +614,69 @@ def __init__(self, category):
614614
615615PkgInfoSearchCache = namedtuple ('PkgInfoSearchCache' , ['name' , 'display_name' , 'keywords' , 'summary' , 'description' ])
616616
617+
618+ class CooperativeIterator :
619+ """
620+ Iterates over items cooperatively within the GLib main loop, yielding periodically to keep the UI responsive.
621+ """
622+ def __init__ (self , iterable , on_per_item , * , on_progress = None , on_finish = None , on_error = None , max_duration_ms = 16 , ** kwargs ):
623+ self ._size = len (iterable ) if hasattr (iterable , '__len__' ) else None
624+ self ._iterator = iter (iterable )
625+ self ._on_per_item = on_per_item
626+ self ._on_progress = on_progress
627+ self ._on_finish = on_finish
628+ self ._on_error = on_error
629+ self ._kwargs = kwargs
630+ self ._max_duration = max_duration_ms / 1000.0
631+ self ._cancelled = False
632+
633+ def run (self ):
634+ self ._start_time = time .monotonic ()
635+ self ._current_index = 0
636+ GLib .idle_add (self ._process )
637+
638+ def _process (self ):
639+ if self ._cancelled :
640+ return False
641+
642+ start_time = time .monotonic ()
643+
644+ try :
645+ while not self ._cancelled :
646+ item = next (self ._iterator )
647+
648+ self ._on_per_item (item , ** self ._kwargs )
649+
650+ self ._current_index += 1
651+ if self ._on_progress and self ._size is not None :
652+ self ._on_progress (self ._current_index / self ._size )
653+
654+ if (time .monotonic () - start_time ) >= self ._max_duration :
655+ return True
656+
657+ except StopIteration :
658+ if os .getenv ("DEBUG" , False ):
659+ elapsed_time = time .monotonic () - self ._start_time
660+ print (f"CooperativeIterator: Finished processing { self ._on_per_item .__name__ } in { elapsed_time :.3f} seconds." )
661+
662+ if self ._on_finish :
663+ try :
664+ self ._on_finish (** self ._kwargs )
665+ except Exception as e :
666+ print (f"CooperativeIterator: Error in on_finish: { e } " )
667+ return False
668+
669+ except RuntimeError :
670+ if self ._on_error :
671+ try :
672+ self ._on_error ()
673+ except Exception as e :
674+ print (f"CooperativeIterator: Error in on_error: { e } " )
675+ return False
676+
677+ def cancel (self ):
678+ self ._cancelled = True
679+
617680class Application (Gtk .Application ):
618681 (ACTION_TAB , PROGRESS_TAB , SPINNER_TAB ) = list (range (3 ))
619682
@@ -665,7 +728,7 @@ def __init__(self):
665728 self .one_package_idle_timer = 0
666729 self .installer_pulse_timer = 0
667730 self .search_changed_timer = 0
668- self .search_idle_timer = 0
731+ self .search_iterator = None
669732 self .generate_search_cache_idle_timer = 0
670733
671734 self .action_button_signal_id = 0
@@ -2210,9 +2273,9 @@ def on_back_button_clicked(self, button):
22102273 self .go_back_action ()
22112274
22122275 def cancel_running_search (self ):
2213- if self .search_idle_timer > 0 :
2214- GLib . source_remove ( self .search_idle_timer )
2215- self .search_idle_timer = 0
2276+ if self .search_iterator :
2277+ self .search_iterator . cancel ( )
2278+ self .search_iterator = None
22162279
22172280 def go_back_action (self ):
22182281 XApp .set_window_progress (self .main_window , 0 )
@@ -2412,35 +2475,34 @@ def show_search_results(self, terms):
24122475 search_in_description = self .settings .get_boolean (prefs .SEARCH_IN_DESCRIPTION )
24132476
24142477 package_type_preference = self .settings .get_string (prefs .PACKAGE_TYPE_PREFERENCE )
2415- hidden_packages = set ()
24162478 allow_unverified_flatpaks = self .settings .get_boolean (prefs .ALLOW_UNVERIFIED_FLATPAKS )
24172479
2418- list_size = len (listing )
2419- self .search_progress = 0
2420-
2421- def idle_search_one_package (pkginfos , list_size ):
2422- try :
2423- pkginfo = next (pkginfos )
2424- except StopIteration :
2425- self .search_idle_timer = 0
2426- self .search_progress = 0
2480+ def on_finish (list_size , searched_packages , hidden_packages , package_type_preference , ** kwargs ):
2481+ self .search_iterator = None
24272482
2428- if package_type_preference == prefs .PACKAGE_TYPE_PREFERENCE_APT :
2429- results = [p for p in searched_packages if not (p .pkg_hash .startswith ("f" ) and p .name in hidden_packages )]
2430- elif package_type_preference == prefs .PACKAGE_TYPE_PREFERENCE_FLATPAK :
2431- results = [p for p in searched_packages if not (p .pkg_hash .startswith ("a" ) and p .name in hidden_packages )]
2432- else :
2433- results = searched_packages
2483+ if package_type_preference == prefs .PACKAGE_TYPE_PREFERENCE_APT :
2484+ results = [p for p in searched_packages if not (p .pkg_hash .startswith ("f" ) and p .name in hidden_packages )]
2485+ elif package_type_preference == prefs .PACKAGE_TYPE_PREFERENCE_FLATPAK :
2486+ results = [p for p in searched_packages if not (p .pkg_hash .startswith ("a" ) and p .name in hidden_packages )]
2487+ else :
2488+ results = searched_packages
2489+ self .on_search_results_complete (results )
24342490
2435- GLib .idle_add (self .on_search_results_complete , results )
2436- return False
2437- except RuntimeError : # dictionary changed size during iteration
2438- self .search_idle_timer = 0
2439- self .search_progress = 0
2491+ def on_error ():
2492+ self .search_iterator = None
24402493
2441- self .go_back_action ()
2442- return False
2494+ self .go_back_action ()
24432495
2496+ def search_one_package (
2497+ pkginfo ,
2498+ list_size ,
2499+ searched_packages ,
2500+ hidden_packages ,
2501+ allow_unverified_flatpaks ,
2502+ package_type_preference ,
2503+ search_in_summary ,
2504+ search_in_description
2505+ ):
24442506 flatpak = pkginfo .pkg_hash .startswith ("f" )
24452507 is_match = False
24462508
@@ -2487,21 +2549,29 @@ def idle_search_one_package(pkginfos, list_size):
24872549 elif package_type_preference == prefs .PACKAGE_TYPE_PREFERENCE_FLATPAK and flatpak :
24882550 hidden_packages .add (DEB_EQUIVS .get (pkginfo .name ))
24892551
2490- self .search_progress = self .search_progress + 1
2491- self .update_progress (self .search_progress / list_size )
2492-
2493- return True
2552+ def on_progress (progress ):
2553+ self .update_progress (progress )
2554+
2555+ self .search_iterator = CooperativeIterator (
2556+ listing ,
2557+ search_one_package ,
2558+ on_finish = on_finish ,
2559+ on_error = on_error ,
2560+ on_progress = on_progress ,
2561+ list_size = len (listing ),
2562+ searched_packages = [],
2563+ hidden_packages = set (),
2564+ allow_unverified_flatpaks = allow_unverified_flatpaks ,
2565+ package_type_preference = package_type_preference ,
2566+ search_in_summary = search_in_summary ,
2567+ search_in_description = search_in_description
2568+ )
2569+ self .search_iterator .run ()
24942570
2495- self .search_idle_timer = GLib .idle_add (idle_search_one_package , iter (listing ), list_size )
24962571
24972572 def update_progress (self , progress ):
24982573 progress = max (0.0 , min (1.0 , progress ))
2499-
2500- def update_progress_ui ():
2501- self .progress_bar .set_fraction (progress )
2502- return False
2503-
2504- GLib .idle_add (update_progress_ui )
2574+ self .progress_bar .set_fraction (progress )
25052575
25062576 def on_search_results_complete (self , results ):
25072577 self .page_stack .set_visible_child_name (self .PAGE_LIST )
0 commit comments