mirror of
https://github.com/0x5t4l1n/AURHub.git
synced 2026-05-26 11:25:50 +00:00
229 lines
6.6 KiB
Python
229 lines
6.6 KiB
Python
"""
|
|
Unified package service for ArchStore.
|
|
Merges pacman and AUR results, handles deduplication and ranking.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Optional
|
|
from services import pacman_service, aur_service
|
|
from database.db import db
|
|
from utils.sanitize import sanitize_search_query, sanitize_package_name, validate_source_filter
|
|
|
|
|
|
# Category definitions — maps friendly names to pacman groups
|
|
CATEGORIES = {
|
|
"Development": {
|
|
"icon": "code",
|
|
"description": "Programming tools, compilers, and IDEs",
|
|
"groups": ["base-devel"],
|
|
"keywords": ["gcc", "git", "python", "nodejs", "rust", "go", "vim", "neovim", "code"],
|
|
},
|
|
"System": {
|
|
"icon": "monitor",
|
|
"description": "Core system utilities and tools",
|
|
"groups": ["base", "sys-utils"],
|
|
"keywords": ["systemd", "kernel", "grub", "filesystem", "coreutils"],
|
|
},
|
|
"Network": {
|
|
"icon": "wifi",
|
|
"description": "Networking tools, browsers, and servers",
|
|
"groups": ["network"],
|
|
"keywords": ["firefox", "chromium", "curl", "wget", "nginx", "ssh"],
|
|
},
|
|
"Multimedia": {
|
|
"icon": "music",
|
|
"description": "Audio, video, and image tools",
|
|
"groups": ["multimedia"],
|
|
"keywords": ["vlc", "mpv", "ffmpeg", "gimp", "audacity", "obs"],
|
|
},
|
|
"Games": {
|
|
"icon": "gamepad-2",
|
|
"description": "Games and gaming tools",
|
|
"groups": ["games"],
|
|
"keywords": ["steam", "lutris", "wine", "gamemode"],
|
|
},
|
|
"Desktop": {
|
|
"icon": "layout-dashboard",
|
|
"description": "Desktop environments and window managers",
|
|
"groups": ["gnome", "kde-applications", "xfce4"],
|
|
"keywords": ["gnome", "kde", "xfce", "i3", "sway", "hyprland"],
|
|
},
|
|
"Fonts": {
|
|
"icon": "type",
|
|
"description": "Fonts and typography",
|
|
"groups": ["fonts"],
|
|
"keywords": ["ttf", "otf", "nerd-fonts", "noto"],
|
|
},
|
|
"Security": {
|
|
"icon": "shield",
|
|
"description": "Security and privacy tools",
|
|
"groups": [],
|
|
"keywords": ["firewall", "gpg", "openssl", "wireguard", "tor"],
|
|
},
|
|
}
|
|
|
|
|
|
async def search_packages(query: str, source: str = "all") -> list[dict]:
|
|
"""
|
|
Search packages from pacman, AUR, or both.
|
|
Results are deduplicated, merged, and ranked.
|
|
"""
|
|
query = sanitize_search_query(query)
|
|
source = validate_source_filter(source)
|
|
|
|
# Check cache first
|
|
cached = await db.get_cached_search(query, source)
|
|
if cached:
|
|
return cached
|
|
|
|
results = []
|
|
|
|
if source in ("all", "pacman"):
|
|
pacman_task = pacman_service.search_packages(query)
|
|
else:
|
|
pacman_task = asyncio.coroutine(lambda: [])()
|
|
|
|
if source in ("all", "aur"):
|
|
aur_task = aur_service.search_packages(query)
|
|
else:
|
|
aur_task = asyncio.coroutine(lambda: [])()
|
|
|
|
# Run both searches concurrently
|
|
if source == "all":
|
|
pacman_results, aur_results = await asyncio.gather(
|
|
pacman_service.search_packages(query),
|
|
aur_service.search_packages(query),
|
|
return_exceptions=True,
|
|
)
|
|
if isinstance(pacman_results, Exception):
|
|
pacman_results = []
|
|
if isinstance(aur_results, Exception):
|
|
aur_results = []
|
|
results = _merge_results(pacman_results, aur_results)
|
|
elif source == "pacman":
|
|
results = await pacman_service.search_packages(query)
|
|
elif source == "aur":
|
|
results = await aur_service.search_packages(query)
|
|
|
|
# Cache results
|
|
await db.set_cached_search(query, source, results)
|
|
|
|
return results
|
|
|
|
|
|
async def get_package_info(name: str) -> Optional[dict]:
|
|
"""Get detailed package info, trying pacman first then AUR."""
|
|
name = sanitize_package_name(name)
|
|
|
|
# Check cache
|
|
cached = await db.get_cached_package(name)
|
|
if cached:
|
|
return cached
|
|
|
|
# Try pacman first
|
|
info = await pacman_service.get_package_info(name)
|
|
if not info:
|
|
# Try AUR
|
|
info = await aur_service.get_package_info(name)
|
|
if not info:
|
|
# Check if installed locally
|
|
info = await pacman_service.get_installed_info(name)
|
|
|
|
if info:
|
|
await db.set_cached_package(name, info)
|
|
|
|
return info
|
|
|
|
|
|
async def install_package(name: str) -> dict:
|
|
"""Install a package, auto-detecting source."""
|
|
name = sanitize_package_name(name)
|
|
|
|
# Try pacman first
|
|
info = await pacman_service.get_package_info(name)
|
|
if info:
|
|
return await pacman_service.install_package(name)
|
|
|
|
# Fall back to AUR via yay
|
|
return await aur_service.install_package(name)
|
|
|
|
|
|
async def remove_package(name: str) -> dict:
|
|
"""Remove an installed package."""
|
|
name = sanitize_package_name(name)
|
|
return await pacman_service.remove_package(name)
|
|
|
|
|
|
async def list_installed() -> list[dict]:
|
|
"""List all installed packages."""
|
|
return await pacman_service.list_installed()
|
|
|
|
|
|
async def check_updates() -> list[dict]:
|
|
"""Check for all available updates (pacman + AUR)."""
|
|
pacman_updates, aur_updates = await asyncio.gather(
|
|
pacman_service.check_updates(),
|
|
aur_service.check_updates(),
|
|
return_exceptions=True,
|
|
)
|
|
if isinstance(pacman_updates, Exception):
|
|
pacman_updates = []
|
|
if isinstance(aur_updates, Exception):
|
|
aur_updates = []
|
|
|
|
return pacman_updates + aur_updates
|
|
|
|
|
|
async def get_categories() -> list[dict]:
|
|
"""Get list of package categories."""
|
|
return [
|
|
{"name": name, **data}
|
|
for name, data in CATEGORIES.items()
|
|
]
|
|
|
|
|
|
async def get_category_packages(category: str) -> list[dict]:
|
|
"""Get packages for a specific category using search."""
|
|
cat = CATEGORIES.get(category)
|
|
if not cat:
|
|
return []
|
|
|
|
# Search using category keywords
|
|
all_results = []
|
|
for keyword in cat.get("keywords", [])[:5]:
|
|
try:
|
|
results = await search_packages(keyword, "all")
|
|
all_results.extend(results)
|
|
except Exception:
|
|
continue
|
|
|
|
# Deduplicate by name
|
|
seen = set()
|
|
unique = []
|
|
for pkg in all_results:
|
|
if pkg["name"] not in seen:
|
|
seen.add(pkg["name"])
|
|
unique.append(pkg)
|
|
|
|
return unique[:50]
|
|
|
|
|
|
def _merge_results(pacman: list[dict], aur: list[dict]) -> list[dict]:
|
|
"""Merge and deduplicate pacman + AUR results."""
|
|
seen = {}
|
|
|
|
# Pacman results take priority
|
|
for pkg in pacman:
|
|
seen[pkg["name"]] = pkg
|
|
|
|
# Add AUR results if not already from pacman
|
|
for pkg in aur:
|
|
if pkg["name"] not in seen:
|
|
seen[pkg["name"]] = pkg
|
|
|
|
# Sort: installed first, then by name
|
|
results = list(seen.values())
|
|
results.sort(key=lambda p: (not p.get("installed", False), p.get("name", "")))
|
|
|
|
return results
|