%PDF- %PDF-
Direktori : /lib/python3/dist-packages/nala/ |
Current File : //lib/python3/dist-packages/nala/cache.py |
# __ # ____ _____ | | _____ # / \\__ \ | | \__ \ # | | \/ __ \| |__/ __ \_ # |___| (____ /____(____ / # \/ \/ \/ # # Copyright (C) 2021, 2022 Blake Lee # # This file is part of nala # # nala is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # nala is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with nala. If not, see <https://www.gnu.org/licenses/>. """The Cache subclass module.""" from __future__ import annotations import contextlib import fnmatch import sys from typing import TYPE_CHECKING, Generator import apt_pkg from apt.cache import Cache as _Cache from apt.package import Package from nala import _, color, color_version from nala.constants import ERROR_PREFIX, NOTICE_PREFIX, WARNING_PREFIX from nala.options import arguments from nala.rich import Columns, from_ansi from nala.utils import dprint, eprint, term if TYPE_CHECKING: from nala.debfile import NalaDebPackage from nala.dpkg import InstallProgress, UpdateProgress PACKAGES_CAN_BE_UPGRADED = "\n" + _("The following {total} packages can be upgraded:") NOT_CANDIDATE = color("[") + color(_("Not candidate version"), "YELLOW") + color("]") class Cache(_Cache): """Subclass of apt.cache to add features.""" def commit_pkgs( self, install_progress: InstallProgress, update_progress: UpdateProgress, local_debs: list[NalaDebPackage] | None = None, ) -> bool: """Apply the marked changes to the cache.""" if local_debs: res = install_archives( [pkg.filename for pkg in local_debs if pkg.filename], install_progress ) install_progress.finish_update() return res == 0 with apt_pkg.SystemLock(): apt = apt_pkg.PackageManager(self._depcache) fetcher = apt_pkg.Acquire(update_progress) with self._archive_lock: while True: self._fetch_archives(fetcher, apt) res = install_archives(apt, install_progress) if res == apt.RESULT_COMPLETED: break if res == apt.RESULT_FAILED: raise SystemError("installArchives() failed") if res != apt.RESULT_INCOMPLETE: raise SystemError( f"internal-error: unknown result code from InstallArchives: {res}" ) # reload the fetcher for media swapping fetcher.shutdown() return res == apt.RESULT_COMPLETED def is_secret_virtual(self, pkg_name: str) -> bool: """Return True if the package is secret virtual. Secret virtual packages provide nothing, and have no versions. cache.is_virtual_package() returns True only if the virtual package has something that it can provide. """ try: pkg = self._cache[pkg_name] if not pkg.has_provides and not pkg.has_versions: return True return False except KeyError: return False def is_any_virtual(self, pkgname: str) -> bool: """Return whether the package is a virtual package. This is used if we only care if it's virtual, but not what type. """ try: pkg = self._cache[pkgname] except KeyError: return False else: return not pkg.has_versions def glob_filter(self, pkg_names: list[str], show: bool = False) -> list[str]: """Filter provided packages and glob *. Returns a new list of packages matching the glob. If there is nothing to glob it returns the original list. """ if "*" not in f"{pkg_names}": return pkg_names new_packages: list[str] = [] glob_failed = False for pkg_name in pkg_names: if "*" in pkg_name: dprint(f"Globbing: {pkg_name}") glob = fnmatch.filter(self.get_pkg_names(show), pkg_name) if not glob: glob_failed = True eprint( _( "{error} unable to find any packages by globbing {pkg}" ).format(error=ERROR_PREFIX, pkg=color(pkg_name, "YELLOW")) ) continue new_packages.extend(glob) else: new_packages.append(pkg_name) if glob_failed: sys.exit(1) new_packages.sort() dprint(f"List after globbing: {new_packages}") return new_packages def get_pkg_names(self, show: bool = False) -> Generator[str, None, None]: """Generate all real packages, or packages that can provide something.""" for pkg in self._cache.packages: # pylint: disable=not-an-iterable pretty_name = pkg.get_fullname(pretty=True) if not show and self.is_virtual_package(pretty_name): provides = self.get_providing_packages(pretty_name) if not provides or len(provides) > 1: continue # For some reason a virtual package $kernel exists and can't be accessed. if pretty_name.startswith("$"): continue if pkg.has_versions or pkg.has_provides: yield pretty_name def virtual_filter(self, pkg_names: list[str], remove: bool = False) -> list[str]: """Filter package to check if they're virtual.""" new_names = set() for pkg_name in pkg_names: if pkg_name in self: new_names.add(pkg_name) continue if (vpkg := self.check_virtual(pkg_name, remove)) and isinstance( vpkg, Package ): new_names.add(vpkg.name) continue new_names.add(pkg_name) dprint(f"Virtual Filter: {new_names}") return sorted(new_names) def what_replaces(self, pkg_name: str) -> Generator[str, None, None]: """Generate packages that replace the given name.""" for pkg in self._cache.packages: # pylint: disable=not-an-iterable if cand := self._depcache.get_candidate_ver(pkg): with contextlib.suppress(KeyError): replaces = cand.depends_list["Replaces"] target = replaces[0][0].target_pkg if pkg_name == target.name: yield pkg.get_fullname(pretty=True) def check_virtual(self, pkg_name: str, remove: bool = False) -> Package | bool: """Check if the package is virtual.""" if not self.is_virtual_package(pkg_name): return False if len(provides := self.get_providing_packages(pkg_name)) == 1: print_selecting_pkg(provides[0].name, pkg_name) return self[provides[0]] if remove: eprint( _("{warn} Virtual Packages like {package} can't be removed.").format( warn=WARNING_PREFIX, package=color(pkg_name, "YELLOW") ) ) return True if not provides: if provides := self.get_providing_packages(pkg_name, candidate_only=False): print_virtual_pkg(pkg_name, provides, not_candidate=True) return True print_virtual_pkg(pkg_name, provides) return True def purge_removed(self) -> None: """Make sure everything marked as removed is getting purged.""" if not arguments.is_purge(): return for pkg in self: if pkg.marked_delete: pkg.mark_delete(purge=True) def protect_upgrade_pkgs(self, exclude: list[str] | None) -> set[Package]: """Mark excluded packages as protected.""" protected: set[Package] = set() if not exclude: return protected resolver = apt_pkg.ProblemResolver(self._depcache) for pkg_name in self.glob_filter(exclude): if pkg_name in self: pkg = self[pkg_name] if pkg.is_upgradable: print( _("Protecting {package} from upgrade").format( package=color(pkg_name, "GREEN") ) ) resolver.protect(self._cache[pkg_name]) protected.add(pkg) elif pkg.is_auto_removable: print( _("Protecting {package} from auto-removal").format( package=color(pkg_name, "GREEN") ) ) resolver.protect(self._cache[pkg_name]) protected.add(pkg) return protected def upgradable_pkgs(self) -> Generator[Package, None, None]: """Generate upgradable packages.""" return (pkg for pkg in self if pkg.is_upgradable) def print_upgradable(self) -> None: """Print packages that are upgradable.""" if arguments.config.get_bool("update_show_packages"): if upgradable := [ # format will look like "python3-pip (22.1.1+dfsg-1) -> (22.2+dfsg-1)" from_ansi( f"{color(pkg.name, 'GREEN')} " f"{color_version(pkg.installed.version)} -> {color_version(pkg.candidate.version)}" ) for pkg in self.upgradable_pkgs() if pkg.installed and pkg.candidate ]: print(PACKAGES_CAN_BE_UPGRADED.format(total=color(len(upgradable)))) term.console.print(Columns(upgradable, padding=(0, 2), equal=True)) return elif total_pkgs := len(tuple(self.upgradable_pkgs())): print( _( "{total} packages can be upgraded. Run '{command}' to see them." ).format( total=color(total_pkgs, "YELLOW"), command=color("nala list --upgradable", "GREEN"), ) ) return print(color(_("All packages are up to date."))) def install_archives( apt: apt_pkg.PackageManager | list[str], install_progress: InstallProgress ) -> int: """Install the archives.""" install_progress.start_update() if did_unlock := apt_pkg.pkgsystem_is_locked(): apt_pkg.pkgsystem_unlock_inner() try: res = install_progress.run_install(apt) finally: if did_unlock: apt_pkg.pkgsystem_lock_inner() install_progress.finish_update() return res def print_virtual_pkg( pkg_name: str, provides: list[Package], not_candidate: bool = False ) -> None: """Print the virtual package string.""" print( _("{package} is a virtual package provided by:").format( package=color(pkg_name, "GREEN") ) ) print( "".join( [ f"\n {color(pkg.name, 'GREEN')} {color_version(pkg.candidate.version)} " f"{NOT_CANDIDATE if not_candidate else ''}" for pkg in provides if pkg.candidate ] ).strip("\n") ) print(_("You should select just one.")) def print_selecting_pkg(provider: str, pkg_name: str) -> None: """Print that we are selecting a different package.""" print( _( "{notice} Selecting {provider}\n Instead of virtual package {package}" ).format( notice=NOTICE_PREFIX, provider=color(provider, "GREEN"), package=color(pkg_name, "GREEN"), ), end="\n\n", )