mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 01:14:15 +02:00
AUR bearhub: fix stale source cache collision (pkgrel=10)
Use tag-specific tarball name bearhub-0.10.7-bearhub.6.tar.gz so makepkg and AUR helpers do not reuse an outdated bearhub-0.10.7.tar.gz from cache. Ignore makepkg artifacts under packaging/aur/.
This commit is contained in:
@@ -1,432 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Set, Type, Tuple, Optional, Generator, TypeVar
|
||||
|
||||
import yaml
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher, TaskManager
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, \
|
||||
CustomSoftwareAction
|
||||
from bauh.api.abstract.view import ViewComponent
|
||||
|
||||
P = TypeVar('P', bound=SoftwarePackage)
|
||||
|
||||
|
||||
class SearchResult:
|
||||
|
||||
def __init__(self, installed: Optional[List[P]], new: Optional[List[P]], total: int):
|
||||
"""
|
||||
:param installed: already installed packages
|
||||
:param new: new packages found
|
||||
:param total: total number of applications actually found
|
||||
"""
|
||||
self.installed = installed
|
||||
self.new = new
|
||||
self.total = total
|
||||
|
||||
def update_total(self):
|
||||
total = 0
|
||||
|
||||
if self.installed:
|
||||
total += len(self.installed)
|
||||
|
||||
if self.new:
|
||||
total += len(self.new)
|
||||
|
||||
self.total = total
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "SearchResult":
|
||||
return cls(installed=[], new=[], total=0)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, SearchResult):
|
||||
if self.installed == other.installed:
|
||||
return self.new == other.new
|
||||
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.installed) + hash(self.new)
|
||||
|
||||
|
||||
class UpgradeRequirement:
|
||||
|
||||
def __init__(self, pkg: SoftwarePackage, reason: Optional[str] = None, required_size: Optional[float] = None, extra_size: Optional[float] = None, sorting_priority: int = 0):
|
||||
"""
|
||||
|
||||
:param pkg:
|
||||
:param reason:
|
||||
:param required_size: size in BYTES required to upgrade the package
|
||||
:param extra_size: the extra size IN BYTES the upgrade will allocate in relation to the already allocated
|
||||
:param sorting_priority: an int representing the sorting priority (higher numbers = higher priority)
|
||||
"""
|
||||
self.pkg = pkg
|
||||
self.reason = reason
|
||||
self.required_size = required_size
|
||||
self.extra_size = extra_size
|
||||
self.sorting_priority = sorting_priority
|
||||
|
||||
@staticmethod
|
||||
def sort_by_priority(req: "UpgradeRequirement") -> Tuple[int, str]:
|
||||
return -req.sorting_priority, req.pkg.name
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, UpgradeRequirement):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return sum((hash(k) + hash(v) for k, v in self.__dict__.items()))
|
||||
|
||||
|
||||
class UpgradeRequirements:
|
||||
|
||||
def __init__(self, to_install: Optional[List[UpgradeRequirement]], to_remove: Optional[List[UpgradeRequirement]],
|
||||
to_upgrade: Optional[List[UpgradeRequirement]], cannot_upgrade: Optional[List[UpgradeRequirement]]):
|
||||
"""
|
||||
:param to_install: additional packages that must be installed with the upgrade
|
||||
:param to_remove: non upgrading packages that should be removed due to conflicts with upgrading packages
|
||||
:param to_upgrade: the final packages to update
|
||||
:param cannot_upgrade: packages which conflict with each other
|
||||
"""
|
||||
self.to_install = to_install
|
||||
self.to_remove = to_remove # when an upgrading package conflicts with a not upgrading package ( check all the non-upgrading packages deps an add here [including those selected to upgrade as well]
|
||||
self.to_upgrade = to_upgrade
|
||||
self.cannot_upgrade = cannot_upgrade
|
||||
self.context = {} # caches relevant data to actually perform the upgrade
|
||||
|
||||
|
||||
class TransactionResult:
|
||||
"""
|
||||
The result of a given operation
|
||||
"""
|
||||
|
||||
def __init__(self, success: bool, installed: Optional[List[SoftwarePackage]], removed: Optional[List[SoftwarePackage]]):
|
||||
self.success = success
|
||||
self.installed = installed
|
||||
self.removed = removed
|
||||
|
||||
@staticmethod
|
||||
def fail() -> "TransactionResult":
|
||||
return TransactionResult(success=False, installed=None, removed=None)
|
||||
|
||||
|
||||
class SoftwareAction(Enum):
|
||||
PREPARE = 0
|
||||
SEARCH = 1
|
||||
INSTALL = 2
|
||||
UNINSTALL = 3
|
||||
UPGRADE = 4
|
||||
DOWNGRADE = 5
|
||||
|
||||
|
||||
class SettingsController(ABC):
|
||||
|
||||
def save_settings(self, component: ViewComponent) -> Tuple[bool, Optional[List[str]]]:
|
||||
"""
|
||||
:return: a tuple with a bool informing if the settings were saved and a list of error messages
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class SettingsView:
|
||||
|
||||
def __init__(self, controller: SettingsController, component: ViewComponent, label: Optional[str] = None,
|
||||
icon_path: Optional[str] = None):
|
||||
|
||||
self.controller = controller
|
||||
self.component = component
|
||||
self.label = label
|
||||
self.icon_path = icon_path
|
||||
|
||||
def save(self) -> Tuple[bool, Optional[List[str]]]:
|
||||
return self.controller.save_settings(self.component)
|
||||
|
||||
|
||||
class SoftwareManager(ABC):
|
||||
|
||||
"""
|
||||
Base controller class that will be called by the graphical interface to execute operations.
|
||||
"""
|
||||
|
||||
def __init__(self, context: ApplicationContext):
|
||||
"""
|
||||
:param context:
|
||||
"""
|
||||
self.context = context
|
||||
|
||||
@abstractmethod
|
||||
def search(self, words: str, disk_loader: Optional[DiskCacheLoader], limit: int, is_url: bool) -> SearchResult:
|
||||
"""
|
||||
:param words: the words typed by the user
|
||||
:param disk_loader: a running disk loader thread that loads package data from the disk asynchronously
|
||||
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
||||
:param is_url: if "words" is a URL
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int, only_apps: bool, pkg_types: Optional[Set[Type[SoftwarePackage]]], internet_available: bool) -> SearchResult:
|
||||
"""
|
||||
:param disk_loader: a running disk loader thread that loads application data from the disk asynchronously
|
||||
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
||||
:param only_apps: if only application packages should be retrieved
|
||||
:param pkg_types: use 'None' to bring any or specify some
|
||||
:param internet_available: if there is internet connection
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def downgrade(self, pkg: SoftwarePackage, root_password: Optional[str], handler: ProcessWatcher) -> bool:
|
||||
"""
|
||||
downgrades a package version
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param handler: a subprocess handler
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def clean_cache_for(self, pkg: SoftwarePackage):
|
||||
"""
|
||||
Cleans cached package cached data. This default implementation only cleans the cached data from the heard disk
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
if pkg.supports_disk_cache() and os.path.exists(pkg.get_disk_cache_path()):
|
||||
shutil.rmtree(pkg.get_disk_cache_path())
|
||||
|
||||
def get_upgrade_requirements(self, pkgs: List[SoftwarePackage], root_password: Optional[str], watcher: ProcessWatcher) -> UpgradeRequirements:
|
||||
"""
|
||||
return additional required software that needs to be installed / removed / updated before updating a list of packages
|
||||
:param pkgs:
|
||||
:param watcher
|
||||
:return:
|
||||
"""
|
||||
return UpgradeRequirements(None, None, [UpgradeRequirement(p) for p in pkgs], None)
|
||||
|
||||
@abstractmethod
|
||||
def upgrade(self, requirements: UpgradeRequirements, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
:param requirements:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def uninstall(self, pkg: SoftwarePackage, root_password: Optional[str], watcher: ProcessWatcher, disk_loader: Optional[DiskCacheLoader]) -> TransactionResult:
|
||||
"""
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:param disk_loader:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
|
||||
"""
|
||||
:return: the managed package class type
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_info(self, pkg: SoftwarePackage) -> dict:
|
||||
"""
|
||||
retrieve the package information
|
||||
:param pkg:
|
||||
:return: a dictionary with the attributes to be shown
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_history(self, pkg: SoftwarePackage) -> PackageHistory:
|
||||
"""
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def install(self, pkg: SoftwarePackage, root_password: Optional[str], disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher) -> TransactionResult:
|
||||
"""
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param disk_loader
|
||||
:param watcher:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_enabled(self) -> bool:
|
||||
"""
|
||||
:return: if the instance is enabled
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_enabled(self, enabled: bool):
|
||||
"""
|
||||
:param enabled:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_work(self) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
:return: if the instance can work based on what is installed in the user's machine. If not, an optional string as a reason.
|
||||
"""
|
||||
|
||||
def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool):
|
||||
"""
|
||||
Saves the package data to the hard disk.
|
||||
:param pkg:
|
||||
:param icon_bytes:
|
||||
:param only_icon: if only the icon should be saved
|
||||
:return:
|
||||
"""
|
||||
if pkg.supports_disk_cache():
|
||||
self.serialize_to_disk(pkg, icon_bytes, only_icon)
|
||||
|
||||
def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool):
|
||||
"""
|
||||
Sames as above, but does not check if disk cache is enabled or supported by the package instance
|
||||
:param pkg:
|
||||
:param icon_bytes:
|
||||
:param only_icon:
|
||||
:return:
|
||||
"""
|
||||
if not only_icon:
|
||||
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
data = pkg.get_data_to_cache()
|
||||
|
||||
if data:
|
||||
disk_path = pkg.get_disk_data_path()
|
||||
ext = disk_path.split('.')[-1]
|
||||
|
||||
if ext == 'json':
|
||||
with open(disk_path, 'w+') as f:
|
||||
f.write(json.dumps(data))
|
||||
elif ext in ('yml', 'yaml'):
|
||||
with open(disk_path, 'w+') as f:
|
||||
f.write(yaml.dump(data))
|
||||
|
||||
if icon_bytes:
|
||||
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(pkg.get_disk_icon_path(), 'wb+') as f:
|
||||
f.write(icon_bytes)
|
||||
|
||||
@abstractmethod
|
||||
def requires_root(self, action: SoftwareAction, pkg: Optional[SoftwarePackage]) -> bool:
|
||||
"""
|
||||
if a given action requires root privileges to be executed. 'install', 'uninstall', 'downgrade', 'search', 'prepare'
|
||||
:param action:
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self, task_manager: Optional[TaskManager], root_password: Optional[str], internet_available: Optional[bool]):
|
||||
"""
|
||||
It prepares the manager to start working. It will be called by GUI. Do not call it within.
|
||||
:param task_manager: a task manager instance used to register ongoing tasks during prepare
|
||||
:param root_password
|
||||
:param internet_available: if there is internet connection available
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||
"""
|
||||
:param internet_available
|
||||
:return: available package updates
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_warnings(self, internet_available: bool) -> Optional[List[str]]:
|
||||
"""
|
||||
:param internet_available
|
||||
:return: a list of warnings to be shown to the user
|
||||
"""
|
||||
pass
|
||||
|
||||
def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[PackageSuggestion]]:
|
||||
"""
|
||||
:param limit: max suggestions to be returned. If limit < 0, it should not be considered
|
||||
:param filter_installed: if the installed suggestions should not be retrieved
|
||||
:return: a list of package suggestions
|
||||
"""
|
||||
pass
|
||||
|
||||
def execute_custom_action(self, action: CustomSoftwareAction, pkg: SoftwarePackage, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
At the moment the GUI implements this action. No need to implement it yourself.
|
||||
:param action:
|
||||
:param pkg:
|
||||
:param root_password:
|
||||
:param watcher:
|
||||
:return: if the action resulted in success
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_default_enabled(self) -> bool:
|
||||
"""
|
||||
:return: if the instance is enabled by default when there is no user settings defining which gems are enabled.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def launch(self, pkg: SoftwarePackage):
|
||||
pass
|
||||
|
||||
def get_screenshots(self, pkg: SoftwarePackage) -> Generator[str, None, None]:
|
||||
"""
|
||||
:return: yields screenshot urls for the given package
|
||||
"""
|
||||
pass
|
||||
|
||||
def clear_data(self, logs: bool = True):
|
||||
"""
|
||||
Removes all data created by the SoftwareManager instance
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_settings(self) -> Optional[Generator[SettingsView, None, None]]:
|
||||
"""
|
||||
:return: panel abstractions with optional icon paths associated with
|
||||
"""
|
||||
pass
|
||||
|
||||
def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]:
|
||||
"""
|
||||
:return: generates available custom actions
|
||||
"""
|
||||
yield from ()
|
||||
|
||||
def fill_sizes(self, pkgs: List[SoftwarePackage]):
|
||||
pass
|
||||
|
||||
def ignore_update(self, pkg: SoftwarePackage):
|
||||
pass
|
||||
|
||||
def revert_ignored_update(self, pkg: SoftwarePackage):
|
||||
pass
|
||||
Reference in New Issue
Block a user