Skip to content
Merged
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
499 changes: 499 additions & 0 deletions archpkg/cache.py

Large diffs are not rendered by default.

58 changes: 50 additions & 8 deletions archpkg/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from archpkg.command_gen import generate_command
from archpkg.logging_config import get_logger, PackageHelperLogger
from archpkg.suggest import suggest_apps, list_purposes
from archpkg.cache import get_cache_manager, CacheConfig

console = Console()
logger = get_logger(__name__)
Expand Down Expand Up @@ -323,6 +324,7 @@ def main() -> None:
search_parser = subparsers.add_parser('search', help='Search for packages by name')
search_parser.add_argument('query', type=str, nargs='*', help='Name of the software to search for')
search_parser.add_argument('--aur', action='store_true', help='Prefer AUR packages over Pacman when both are available')
search_parser.add_argument('--no-cache', action='store_true', help='Bypass cache and perform fresh search')

# Suggest command
suggest_parser = subparsers.add_parser('suggest', help='Get app suggestions based on purpose')
Expand All @@ -332,14 +334,53 @@ def main() -> None:
# Global arguments
parser.add_argument('--debug', action='store_true', help='Enable debug logging to console')
parser.add_argument('--log-info', action='store_true', help='Show logging configuration and exit')
parser.add_argument('--no-cache', action='store_true', help='Bypass cache and perform fresh search')
parser.add_argument('--cache-stats', action='store_true', help='Show cache statistics and exit')
parser.add_argument('--clear-cache', choices=['all', 'aur', 'pacman', 'apt', 'dnf', 'flatpak', 'snap'],
help='Clear cache for specified source or all sources')

args = parser.parse_args()

# Initialize cache manager
cache_config = CacheConfig(enabled=not args.no_cache)
cache_manager = get_cache_manager(cache_config)

# Enable debug mode if requested
if args.debug:
PackageHelperLogger.set_debug_mode(True)
logger.info("Debug mode enabled via command line argument")

# Handle cache-related commands
if args.cache_stats:
stats = cache_manager.get_stats()
console.print(Panel(
f"[bold cyan]Cache Statistics:[/bold cyan]\n"
f"Enabled: {'[green]Yes[/green]' if stats.get('enabled') else '[red]No[/red]'}\n"
f"Total entries: [yellow]{stats.get('total_entries', 0)}[/yellow]\n"
f"Valid entries: [green]{stats.get('valid_entries', 0)}[/green]\n"
f"Total accesses: [blue]{stats.get('total_accesses', 0)}[/blue]\n"
f"Average access count: [magenta]{stats.get('avg_access_count', 0)}[/magenta]\n"
f"Database path: [cyan]{stats.get('db_path', 'N/A')}[/cyan]\n"
f"TTL: [yellow]{stats.get('config', {}).get('ttl_seconds', 0)}s[/yellow]\n"
f"Max entries: [yellow]{stats.get('config', {}).get('max_entries', 0)}[/yellow]\n\n"
f"[bold]Source breakdown:[/bold]\n" +
'\n'.join([f" {source}: {count}" for source, count in stats.get('source_breakdown', {}).items()]),
title="Cache Statistics",
border_style="blue"
))
return

if args.clear_cache:
source = None if args.clear_cache == 'all' else args.clear_cache
cleared_count = cache_manager.clear(source)
target = args.clear_cache if args.clear_cache != 'all' else 'all sources'
console.print(Panel(
f"[green]Successfully cleared {cleared_count} cache entries for {target}.[/green]",
title="Cache Cleared",
border_style="green"
))
return

# Show logging info if requested
if args.log_info:
from archpkg.logging_config import get_log_info
Expand All @@ -361,7 +402,7 @@ def main() -> None:
return
elif args.command == 'search' or args.command is None:
# Default to search behavior for backward compatibility
handle_search_command(args)
handle_search_command(args, cache_manager)
return
else:
console.print(Panel(
Expand Down Expand Up @@ -417,7 +458,7 @@ def handle_suggest_command(args) -> None:
suggest_apps(purpose)


def handle_search_command(args) -> None:
def handle_search_command(args, cache_manager) -> None:
"""Handle the search command (original functionality)."""
if not args.query:
console.print(Panel(
Expand Down Expand Up @@ -455,14 +496,15 @@ def handle_search_command(args) -> None:

results = []
search_errors = []
use_cache = not args.no_cache

# Search based on detected distribution
if detected == "arch":
logger.info("Searching Arch-based repositories (AUR + pacman)")

try:
logger.debug("Starting AUR search")
aur_results = search_aur(query)
aur_results = search_aur(query, cache_manager if use_cache else None)
results.extend(aur_results)
logger.info(f"AUR search returned {len(aur_results)} results")
except Exception as e:
Expand All @@ -471,7 +513,7 @@ def handle_search_command(args) -> None:

try:
logger.debug("Starting pacman search")
pacman_results = search_pacman(query)
pacman_results = search_pacman(query, cache_manager if use_cache else None)
results.extend(pacman_results)
logger.info(f"Pacman search returned {len(pacman_results)} results")
except Exception as e:
Expand All @@ -483,7 +525,7 @@ def handle_search_command(args) -> None:

try:
logger.debug("Starting APT search")
apt_results = search_apt(query)
apt_results = search_apt(query, cache_manager if use_cache else None)
results.extend(apt_results)
logger.info(f"APT search returned {len(apt_results)} results")
except Exception as e:
Expand All @@ -495,7 +537,7 @@ def handle_search_command(args) -> None:

try:
logger.debug("Starting DNF search")
dnf_results = search_dnf(query)
dnf_results = search_dnf(query, cache_manager if use_cache else None)
results.extend(dnf_results)
logger.info(f"DNF search returned {len(dnf_results)} results")
except Exception as e:
Expand All @@ -507,7 +549,7 @@ def handle_search_command(args) -> None:

try:
logger.debug("Starting Flatpak search")
flatpak_results = search_flatpak(query)
flatpak_results = search_flatpak(query, cache_manager if use_cache else None)
results.extend(flatpak_results)
logger.info(f"Flatpak search returned {len(flatpak_results)} results")
except Exception as e:
Expand All @@ -516,7 +558,7 @@ def handle_search_command(args) -> None:

try:
logger.debug("Starting Snap search")
snap_results = search_snap(query)
snap_results = search_snap(query, cache_manager if use_cache else None)
results.extend(snap_results)
logger.info(f"Snap search returned {len(snap_results)} results")
except Exception as e:
Expand Down
27 changes: 27 additions & 0 deletions archpkg/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,31 @@
'log_slow_operations_threshold': 5.0, # seconds
'enable_memory_logging': False,
'enable_detailed_tracing': False
}

# Cache configuration settings
CACHE_CONFIG = {
'enabled': True,
'ttl_seconds': 24 * 60 * 60, # 24 hours default
'max_entries': 1000,
'cleanup_interval': 3600, # 1 hour
'cache_dir': None, # Auto-detect using XDG_CACHE_HOME
'db_name': 'cache.db',

# Privacy settings
'respect_privacy': True,
'exclude_sensitive_queries': True,
'max_description_length': 500,

# Performance settings
'enable_compression': False, # Future feature
'batch_size': 100, # For bulk operations
'connection_timeout': 5.0, # Database connection timeout
}

# Cache-related timeouts (separate from search timeouts)
CACHE_TIMEOUTS = {
'db_connection': 5.0,
'cleanup_operation': 30.0,
'stats_query': 10.0,
}
18 changes: 16 additions & 2 deletions archpkg/search_apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
IMPROVEMENTS: Standardized source name to lowercase, used config timeouts, unified exception handling."""

import subprocess
from typing import List, Tuple
from typing import List, Tuple, Optional
from archpkg.config import TIMEOUTS
from archpkg.exceptions import PackageManagerNotFound, PackageSearchException, TimeoutError, ValidationError
from archpkg.logging_config import get_logger, PackageHelperLogger

logger = get_logger(__name__)

def search_apt(query: str) -> List[Tuple[str, str, str]]:
def search_apt(query: str, cache_manager: Optional[object] = None) -> List[Tuple[str, str, str]]:
"""Search for packages using the APT package manager.

Args:
query: Search query string
cache_manager: Optional cache manager for storing/retrieving results

Returns:
List[Tuple[str, str, str]]: List of (name, description, source) tuples
Expand All @@ -32,6 +33,13 @@ def search_apt(query: str) -> List[Tuple[str, str, str]]:
logger.error("Empty search query provided to APT search")
raise ValidationError("Search query cannot be empty. Please provide a package name to search for.")

# Check cache first if available
if cache_manager:
cached_results = cache_manager.get(query, 'apt')
if cached_results is not None:
logger.info(f"Retrieved {len(cached_results)} APT results from cache")
return cached_results

# Check if apt-cache is available
logger.debug("Checking APT availability")
try:
Expand Down Expand Up @@ -108,6 +116,12 @@ def search_apt(query: str) -> List[Tuple[str, str, str]]:
logger.debug(f"Found APT package: {name.strip()}")

logger.info(f"APT search completed: {len(packages)} packages found from {lines_processed} lines")

# Cache results if cache manager is available
if cache_manager and packages:
cache_manager.set(query, 'apt', packages)
logger.debug(f"Cached {len(packages)} APT results")

return packages

except subprocess.TimeoutExpired:
Expand Down
18 changes: 16 additions & 2 deletions archpkg/search_aur.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

import requests
import json
from typing import List, Tuple
from typing import List, Tuple, Optional
from archpkg.config import TIMEOUTS
from archpkg.exceptions import NetworkError, TimeoutError, ValidationError, PackageSearchException
from archpkg.logging_config import get_logger, PackageHelperLogger

logger = get_logger(__name__)

def search_aur(query: str) -> List[Tuple[str, str, str]]:
def search_aur(query: str, cache_manager: Optional[object] = None) -> List[Tuple[str, str, str]]:
"""Search for packages in the Arch User Repository (AUR).

Args:
query: Search query string
cache_manager: Optional cache manager for storing/retrieving results

Returns:
List[Tuple[str, str, str]]: List of (name, description, source) tuples
Expand All @@ -33,6 +34,13 @@ def search_aur(query: str) -> List[Tuple[str, str, str]]:
logger.error("Empty search query provided to AUR search")
raise ValidationError("Empty search query provided")

# Check cache first if available
if cache_manager:
cached_results = cache_manager.get(query, 'aur')
if cached_results is not None:
logger.info(f"Retrieved {len(cached_results)} AUR results from cache")
return cached_results

# Construct AUR RPC search API URL
url = f"https://aur.archlinux.org/rpc/?v=5&type=search&arg={query.strip()}"
logger.debug(f"AUR API URL: {url}")
Expand Down Expand Up @@ -80,6 +88,12 @@ def search_aur(query: str) -> List[Tuple[str, str, str]]:

logger.info(f"AUR search completed: {len(processed_results)} valid packages found")
# IMPROVED: Standardized source name to lowercase

# Cache results if cache manager is available
if cache_manager and processed_results:
cache_manager.set(query, 'aur', processed_results)
logger.debug(f"Cached {len(processed_results)} AUR results")

return processed_results

except requests.exceptions.ConnectionError as e:
Expand Down
18 changes: 16 additions & 2 deletions archpkg/search_dnf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

import subprocess
import re
from typing import List, Tuple
from typing import List, Tuple, Optional
from archpkg.config import TIMEOUTS
from archpkg.exceptions import PackageManagerNotFound, PackageSearchException, TimeoutError, ValidationError, NetworkError
from archpkg.logging_config import get_logger, PackageHelperLogger

logger = get_logger(__name__)

def search_dnf(query: str) -> List[Tuple[str, str, str]]:
def search_dnf(query: str, cache_manager: Optional[object] = None) -> List[Tuple[str, str, str]]:
"""Search for packages using DNF package manager.

Args:
query: Search query string
cache_manager: Optional cache manager for storing/retrieving results

Returns:
List[Tuple[str, str, str]]: List of (name, description, source) tuples
Expand All @@ -33,6 +34,13 @@ def search_dnf(query: str) -> List[Tuple[str, str, str]]:
logger.error("Empty search query provided to DNF search")
raise ValidationError("Empty search query provided")

# Check cache first if available
if cache_manager:
cached_results = cache_manager.get(query, 'dnf')
if cached_results is not None:
logger.info(f"Retrieved {len(cached_results)} DNF results from cache")
return cached_results

# Check if DNF is available and working
logger.debug("Checking DNF availability")
try:
Expand Down Expand Up @@ -137,6 +145,12 @@ def search_dnf(query: str) -> List[Tuple[str, str, str]]:
logger.debug(f"Found DNF package: {name}")

logger.info(f"DNF search completed: {len(packages)} packages found from {lines_processed} lines")

# Cache results if cache manager is available
if cache_manager and packages:
cache_manager.set(query, 'dnf', packages)
logger.debug(f"Cached {len(packages)} DNF results")

return packages

except subprocess.TimeoutExpired:
Expand Down
18 changes: 16 additions & 2 deletions archpkg/search_flatpak.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
IMPROVEMENTS: Standardized source name to lowercase, used config timeouts, unified exception handling."""

import subprocess
from typing import List, Tuple
from typing import List, Tuple, Optional
from archpkg.config import TIMEOUTS
from archpkg.exceptions import PackageManagerNotFound, PackageSearchException, TimeoutError, ValidationError
from archpkg.logging_config import get_logger, PackageHelperLogger

logger = get_logger(__name__)

def search_flatpak(query: str) -> List[Tuple[str, str, str]]:
def search_flatpak(query: str, cache_manager: Optional[object] = None) -> List[Tuple[str, str, str]]:
"""Search for packages using the Flatpak package manager.

Args:
query: Search query string
cache_manager: Optional cache manager for storing/retrieving results

Returns:
List[Tuple[str, str, str]]: List of (name, description, source) tuples
Expand All @@ -31,6 +32,13 @@ def search_flatpak(query: str) -> List[Tuple[str, str, str]]:
logger.error("Empty search query provided to Flatpak search")
raise ValidationError("Empty search query provided")

# Check cache first if available
if cache_manager:
cached_results = cache_manager.get(query, 'flatpak')
if cached_results is not None:
logger.info(f"Retrieved {len(cached_results)} Flatpak results from cache")
return cached_results

# Check if flatpak is available and working
logger.debug("Checking Flatpak availability")
try:
Expand Down Expand Up @@ -115,6 +123,12 @@ def search_flatpak(query: str) -> List[Tuple[str, str, str]]:
logger.debug(f"Skipping malformed Flatpak result line: {line}")

logger.info(f"Flatpak search completed: {len(packages)} packages found from {lines_processed} lines")

# Cache results if cache manager is available
if cache_manager and packages:
cache_manager.set(query, 'flatpak', packages)
logger.debug(f"Cached {len(packages)} Flatpak results")

return packages

except subprocess.TimeoutExpired:
Expand Down
Loading
Loading