From 0d8819b6760fed8785c2f547e16bbece4ac8e38f Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 1 Jan 2026 21:59:12 +0100 Subject: [PATCH 1/5] Squashed commit of the following: commit 7f0b3f4995954792a9ac4c6b8517213535d97cae Author: h8d13 Date: Thu Jan 1 21:57:04 2026 +0100 change name again commit 86bd457d3fc6010d9b03cca8a95615bb64b01943 Author: h8d13 Date: Thu Jan 1 21:55:18 2026 +0100 Simplify further commit 1209a7f55aac1f3e152455f90b1838ede5b22f58 Author: h8d13 Date: Thu Jan 1 21:53:37 2026 +0100 Add comments commit f752e1faede83ecde618fe6c7b77c45f0cd402f9 Author: h8d13 Date: Thu Jan 1 21:51:05 2026 +0100 Change def name commit 01e49d421740d7afd50e878f15ea12a70ca65aca Author: h8d13 Date: Thu Jan 1 21:49:50 2026 +0100 Simplify further commit f1a6888c08af3cc033c9e7ae3110e20c99d85f0a Author: h8d13 Date: Thu Jan 1 21:36:55 2026 +0100 Clean up commit c57eb71b191d8c070926fb919352836ff19346db Author: h8d13 Date: Thu Jan 1 21:32:59 2026 +0100 Simplify commit 9eaecefd82e6e39b8642ca5694871df45b00723b Author: h8d13 Date: Thu Jan 1 21:16:53 2026 +0100 Use a set instead of list for faster lookups commit 9ade12842927e2142a0afd55650016689eefd6ba Author: h8d13 Date: Thu Jan 1 21:11:09 2026 +0100 Remove useless stub model re-use actual model file commit fc1ab34ad73d5d35740f44fe89ac0767b58c3a8d Author: h8d13 Date: Thu Jan 1 20:29:09 2026 +0100 Makes loading pakcages list about .2s with first 100 results then Threads loading of other pkgs in bg Loading packages... in .2s - Use -Sl instead of full info - Then update individual pkgs real-time on demand --- archinstall/lib/interactions/general_conf.py | 8 +++- archinstall/lib/packages/packages.py | 49 +++++++++++++++----- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index e0a250ffee..324f57e50b 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -5,7 +5,7 @@ from typing import assert_never from archinstall.lib.models.packages import Repository -from archinstall.lib.packages.packages import list_available_packages +from archinstall.lib.packages.packages import enrich_package_info, list_available_packages from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import EditMenu, SelectMenu, Tui from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -173,11 +173,15 @@ def ask_additional_packages_to_install( elif p in package_groups: preset_packages.append(package_groups[p]) + def preview_package(item): + enrich_package_info(item.value) + return item.value.info() + items = [ MenuItem( name, value=pkg, - preview_action=lambda x: x.value.info(), + preview_action=preview_package, ) for name, pkg in packages.items() ] diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index c8d8e53276..8af4cebb0a 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -128,6 +128,34 @@ def check_package_upgrade(package: str) -> str | None: return None +def _create_package_stub(repo: str, name: str, version: str) -> AvailablePackage: + defaults = {field: '' for field in AvailablePackage.model_fields} + defaults.update({'repository': repo, 'name': name, 'version': version}) + return AvailablePackage(**defaults) + + +def _update_package(pkg: AvailablePackage, detailed: AvailablePackage) -> None: + for field in AvailablePackage.model_fields: + setattr(pkg, field, getattr(detailed, field)) + + +def enrich_package_info(pkg: AvailablePackage) -> None: + # Skip if already enriched + if pkg.description: + return + # Try to populate info for singular pkg + try: + package_info = [] + for line in Pacman.run(f'-Si {pkg.name}'): + package_info.append(line.decode().strip()) + + if package_info: + detailed = _parse_package_output(package_info, AvailablePackage) + _update_package(pkg, detailed) + except Exception: + pass + + @lru_cache def list_available_packages( repositories: tuple[Repository, ...], @@ -136,24 +164,21 @@ def list_available_packages( Returns a list of all available packages in the database """ packages: dict[str, AvailablePackage] = {} - current_package: list[str] = [] - filtered_repos = [repo.value for repo in repositories] try: Pacman.run('-Sy') except Exception as e: debug(f'Failed to sync Arch Linux package database: {e}') - for line in Pacman.run('-S --info'): - dec_line = line.decode().strip() - current_package.append(dec_line) - - if dec_line.startswith('Validated'): - if current_package: - avail_pkg = _parse_package_output(current_package, AvailablePackage) - if avail_pkg.repository in filtered_repos: - packages[avail_pkg.name] = avail_pkg - current_package = [] + # Load package stubs from repositories + for repo in repositories: + try: + for line in Pacman.run(f'-Sl {repo.value}'): + parts = line.decode().strip().split() + if len(parts) >= 3: + packages[parts[1]] = _create_package_stub(parts[0], parts[1], parts[2]) + except Exception as e: + debug(f'Failed to list packages from {repo.value}: {e}') return packages From 11239a9ed4c900a896df4826bb10ff5ed73efd13 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 1 Jan 2026 22:09:43 +0100 Subject: [PATCH 2/5] mypy --- archinstall/lib/interactions/general_conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index 324f57e50b..f300cf1400 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -173,7 +173,7 @@ def ask_additional_packages_to_install( elif p in package_groups: preset_packages.append(package_groups[p]) - def preview_package(item): + def preview_package(item: MenuItem) -> str: enrich_package_info(item.value) return item.value.info() From 499c8aa000c5faf02b3e72f492cb94fe7eda3484 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 1 Jan 2026 22:16:56 +0100 Subject: [PATCH 3/5] Mypy + Pylint --- archinstall/lib/interactions/general_conf.py | 7 +++++-- archinstall/lib/packages/packages.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index f300cf1400..f94fb5b68b 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -174,8 +174,11 @@ def ask_additional_packages_to_install( preset_packages.append(package_groups[p]) def preview_package(item: MenuItem) -> str: - enrich_package_info(item.value) - return item.value.info() + pkg = item.value + if isinstance(pkg, AvailablePackage): + enrich_package_info(pkg) + return pkg.info() + return '' items = [ MenuItem( diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index 8af4cebb0a..4260716416 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -129,14 +129,14 @@ def check_package_upgrade(package: str) -> str | None: def _create_package_stub(repo: str, name: str, version: str) -> AvailablePackage: - defaults = {field: '' for field in AvailablePackage.model_fields} + defaults = {field_name: '' for field_name in AvailablePackage.model_fields.keys()} defaults.update({'repository': repo, 'name': name, 'version': version}) return AvailablePackage(**defaults) def _update_package(pkg: AvailablePackage, detailed: AvailablePackage) -> None: - for field in AvailablePackage.model_fields: - setattr(pkg, field, getattr(detailed, field)) + for field_name in AvailablePackage.model_fields.keys(): + setattr(pkg, field_name, getattr(detailed, field_name)) def enrich_package_info(pkg: AvailablePackage) -> None: From 110bfb58bd55301ee17ae1119fb607c8bf52cc89 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 10:08:46 +0100 Subject: [PATCH 4/5] Feedback Use single Thread to pre-fetch next 50 while keeping much faster -Sl call. This makes it much smotther when not using search "/" --- archinstall/lib/interactions/general_conf.py | 44 ++++++++++++++++---- archinstall/lib/packages/packages.py | 40 +++++++++++++----- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index f94fb5b68b..e6fc046b3e 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -1,5 +1,6 @@ from __future__ import annotations +import threading from enum import Enum from pathlib import Path from typing import assert_never @@ -173,18 +174,11 @@ def ask_additional_packages_to_install( elif p in package_groups: preset_packages.append(package_groups[p]) - def preview_package(item: MenuItem) -> str: - pkg = item.value - if isinstance(pkg, AvailablePackage): - enrich_package_info(pkg) - return pkg.info() - return '' - items = [ MenuItem( name, value=pkg, - preview_action=preview_package, + preview_action=None, # Will be set after menu_group is created ) for name, pkg in packages.items() ] @@ -201,6 +195,40 @@ def preview_package(item: MenuItem) -> str: menu_group = MenuItemGroup(items, sort_items=True) menu_group.set_selected_by_value(preset_packages) + # Helper to prefetch next packages in background + def _prefetch_packages(group: MenuItemGroup, current_item: MenuItem) -> None: + try: + filtered_items = group.items + current_idx = filtered_items.index(current_item) + + # Collect next 50 packages + prefetch = [] + for i in range(current_idx + 1, min(current_idx + 51, len(filtered_items))): + next_pkg = filtered_items[i].value + if isinstance(next_pkg, AvailablePackage): + prefetch.append(next_pkg) + + if prefetch: + enrich_package_info(prefetch[0], prefetch=prefetch[1:]) + except (ValueError, IndexError): + pass + + # Preview function for packages - enriches current and prefetches next 50 + def preview_package(item: MenuItem) -> str: + pkg = item.value + if isinstance(pkg, AvailablePackage): + # Enrich current package synchronously + enrich_package_info(pkg) + # Prefetch next 50 in background thread + threading.Thread(target=_prefetch_packages, args=(menu_group, item), daemon=True).start() + return pkg.info() + return '' + + # Set preview action for package items only + for item in items: + if isinstance(item.value, AvailablePackage): + item.preview_action = preview_package + result = SelectMenu[AvailablePackage | PackageGroup]( menu_group, header=header, diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index 4260716416..169d35fd37 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -139,19 +139,37 @@ def _update_package(pkg: AvailablePackage, detailed: AvailablePackage) -> None: setattr(pkg, field_name, getattr(detailed, field_name)) -def enrich_package_info(pkg: AvailablePackage) -> None: - # Skip if already enriched - if pkg.description: +def enrich_package_info(pkg: AvailablePackage, prefetch: list[AvailablePackage] = []) -> None: + # Collect packages that need enrichment + to_enrich = [] + if not pkg.description: + to_enrich.append(pkg) + + for p in prefetch: + if not p.description: + to_enrich.append(p) + + if not to_enrich: return - # Try to populate info for singular pkg - try: - package_info = [] - for line in Pacman.run(f'-Si {pkg.name}'): - package_info.append(line.decode().strip()) - if package_info: - detailed = _parse_package_output(package_info, AvailablePackage) - _update_package(pkg, detailed) + # Batch fetch with single pacman call + try: + pkg_names = ' '.join(p.name for p in to_enrich) + current_package = [] + + for line in Pacman.run(f'-Si {pkg_names}'): + dec_line = line.decode().strip() + current_package.append(dec_line) + + if dec_line.startswith('Validated'): + if current_package: + detailed = _parse_package_output(current_package, AvailablePackage) + # Find matching package and update it + for p in to_enrich: + if p.name == detailed.name: + _update_package(p, detailed) + break + current_package = [] except Exception: pass From fb766c2096995ae703118038a18b56924cf042ef Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 11:02:48 +0100 Subject: [PATCH 5/5] Feedback also iterate backwards and search aware --- archinstall/lib/interactions/general_conf.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index e6fc046b3e..ee0d8c2737 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -195,25 +195,31 @@ def ask_additional_packages_to_install( menu_group = MenuItemGroup(items, sort_items=True) menu_group.set_selected_by_value(preset_packages) - # Helper to prefetch next packages in background + # Helper to prefetch packages in both directions in background def _prefetch_packages(group: MenuItemGroup, current_item: MenuItem) -> None: try: filtered_items = group.items current_idx = filtered_items.index(current_item) - # Collect next 50 packages + # Collect next 50 packages (forward) prefetch = [] for i in range(current_idx + 1, min(current_idx + 51, len(filtered_items))): next_pkg = filtered_items[i].value if isinstance(next_pkg, AvailablePackage): prefetch.append(next_pkg) + # Collect previous 50 packages (backward) + for i in range(max(0, current_idx - 50), current_idx): + prev_pkg = filtered_items[i].value + if isinstance(prev_pkg, AvailablePackage): + prefetch.append(prev_pkg) + if prefetch: enrich_package_info(prefetch[0], prefetch=prefetch[1:]) except (ValueError, IndexError): pass - # Preview function for packages - enriches current and prefetches next 50 + # Preview function for packages - enriches current and prefetches ±50 packages def preview_package(item: MenuItem) -> str: pkg = item.value if isinstance(pkg, AvailablePackage):