Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions archinstall/lib/interactions/general_conf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from __future__ import annotations

import threading
from enum import Enum
from pathlib import Path
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
Expand Down Expand Up @@ -177,7 +178,7 @@ def ask_additional_packages_to_install(
MenuItem(
name,
value=pkg,
preview_action=lambda x: x.value.info(),
preview_action=None, # Will be set after menu_group is created
)
for name, pkg in packages.items()
]
Expand All @@ -194,6 +195,46 @@ def ask_additional_packages_to_install(
menu_group = MenuItemGroup(items, sort_items=True)
menu_group.set_selected_by_value(preset_packages)

# 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 (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 ±50 packages
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,
Expand Down
67 changes: 55 additions & 12 deletions archinstall/lib/packages/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,52 @@ def check_package_upgrade(package: str) -> str | None:
return None


def _create_package_stub(repo: str, name: str, version: str) -> AvailablePackage:
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_name in AvailablePackage.model_fields.keys():
setattr(pkg, field_name, getattr(detailed, field_name))


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

# 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


@lru_cache
def list_available_packages(
repositories: tuple[Repository, ...],
Expand All @@ -136,24 +182,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

Expand Down