mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 02:24:16 +02:00
0.9.0
This commit is contained in:
@@ -10,11 +10,10 @@ from bauh.view.util.translation import I18n
|
||||
|
||||
class ApplicationContext:
|
||||
|
||||
def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: I18n,
|
||||
def __init__(self, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: I18n,
|
||||
cache_factory: MemoryCacheFactory, disk_loader_factory: DiskCacheLoaderFactory,
|
||||
logger: logging.Logger, file_downloader: FileDownloader, distro: str, app_name: str):
|
||||
"""
|
||||
:param disk_cache: if package data should be cached to disk
|
||||
:param download_icons: if packages icons should be downloaded
|
||||
:param http_client: a shared instance of http client
|
||||
:param app_root_dir: GUI root dir
|
||||
@@ -25,8 +24,8 @@ class ApplicationContext:
|
||||
:param file_downloader
|
||||
:param distro
|
||||
:param app_name
|
||||
:param root_password
|
||||
"""
|
||||
self.disk_cache = disk_cache
|
||||
self.download_icons = download_icons
|
||||
self.http_client = http_client
|
||||
self.app_root_dir = app_root_dir
|
||||
@@ -40,6 +39,7 @@ class ApplicationContext:
|
||||
self.default_categories = ('AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game',
|
||||
'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System', 'Utility')
|
||||
self.app_name = app_name
|
||||
self.root_password = None
|
||||
|
||||
def is_system_x86_64(self):
|
||||
return self.arch_x86_64
|
||||
|
||||
@@ -9,8 +9,9 @@ import yaml
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction
|
||||
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
|
||||
|
||||
|
||||
@@ -27,6 +28,38 @@ class SearchResult:
|
||||
self.total = total
|
||||
|
||||
|
||||
class UpgradeRequirement:
|
||||
|
||||
def __init__(self, pkg: SoftwarePackage, reason: str = None, required_size: int = None, extra_size: int = None):
|
||||
"""
|
||||
|
||||
: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
|
||||
"""
|
||||
self.pkg = pkg
|
||||
self.reason = reason
|
||||
self.required_size = required_size
|
||||
self.extra_size = extra_size
|
||||
|
||||
|
||||
class UpgradeRequirements:
|
||||
|
||||
def __init__(self, to_install: List[UpgradeRequirement], to_remove: List[UpgradeRequirement],
|
||||
to_upgrade: List[UpgradeRequirement], cannot_upgrade: 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
|
||||
|
||||
|
||||
class SoftwareManager(ABC):
|
||||
|
||||
"""
|
||||
@@ -82,27 +115,19 @@ class SoftwareManager(ABC):
|
||||
if pkg.supports_disk_cache() and os.path.exists(pkg.get_disk_cache_path()):
|
||||
shutil.rmtree(pkg.get_disk_cache_path())
|
||||
|
||||
def sort_update_order(self, pkgs: List[SoftwarePackage]) -> List[SoftwarePackage]:
|
||||
def get_upgrade_requirements(self, pkgs: List[SoftwarePackage], root_password: str, watcher: ProcessWatcher) -> UpgradeRequirements:
|
||||
"""
|
||||
sorts the best order to perform the update of some packages
|
||||
:param pkgs:
|
||||
:return:
|
||||
"""
|
||||
return pkgs
|
||||
|
||||
def get_update_requirements(self, pkgs: List[SoftwarePackage], watcher: ProcessWatcher) -> List[SoftwarePackage]:
|
||||
"""
|
||||
return additional required software that needs to be installed before updating a list of packages
|
||||
return additional required software that needs to be installed / removed / updated before updating a list of packages
|
||||
:param pkgs:
|
||||
:param watcher
|
||||
:return:
|
||||
"""
|
||||
return []
|
||||
return UpgradeRequirements(None, None, [UpgradeRequirement(p) for p in pkgs], None)
|
||||
|
||||
@abstractmethod
|
||||
def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
:param pkg:
|
||||
:param requirements:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:return:
|
||||
@@ -182,7 +207,7 @@ class SoftwareManager(ABC):
|
||||
:param only_icon: if only the icon should be saved
|
||||
:return:
|
||||
"""
|
||||
if self.context.disk_cache and pkg.supports_disk_cache():
|
||||
if pkg.supports_disk_cache():
|
||||
self.serialize_to_disk(pkg, icon_bytes, only_icon)
|
||||
|
||||
def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool):
|
||||
@@ -217,7 +242,7 @@ class SoftwareManager(ABC):
|
||||
@abstractmethod
|
||||
def requires_root(self, action: str, pkg: SoftwarePackage):
|
||||
"""
|
||||
if a given action requires root privileges to be executed. Current actions are: 'install', 'uninstall', 'downgrade', 'search', 'refresh'
|
||||
if a given action requires root privileges to be executed. Current actions are: 'install', 'uninstall', 'downgrade', 'search', 'refresh', 'prepare'
|
||||
:param action:
|
||||
:param pkg:
|
||||
:return:
|
||||
@@ -225,9 +250,11 @@ class SoftwareManager(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self):
|
||||
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: 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
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
@@ -257,7 +284,7 @@ class SoftwareManager(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
def execute_custom_action(self, action: PackageAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
def execute_custom_action(self, action: CustomSoftwareAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
At the moment the GUI implements this action. No need to implement it yourself.
|
||||
:param action:
|
||||
@@ -304,3 +331,12 @@ class SoftwareManager(ABC):
|
||||
:return: a tuple with a bool informing if the settings were saved and a list of error messages
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_custom_actions(self) -> List[CustomSoftwareAction]:
|
||||
"""
|
||||
:return: custom actions
|
||||
"""
|
||||
pass
|
||||
|
||||
def fill_sizes(self, pkgs: List[SoftwarePackage]):
|
||||
pass
|
||||
|
||||
@@ -17,7 +17,7 @@ class ProcessWatcher:
|
||||
"""
|
||||
pass
|
||||
|
||||
def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None)-> bool:
|
||||
def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None, deny_button: bool = True)-> bool:
|
||||
"""
|
||||
request a user confirmation. In the current GUI implementation, it shows a popup to the user.
|
||||
:param title: popup title
|
||||
@@ -25,10 +25,17 @@ class ProcessWatcher:
|
||||
:param components: extra view components that will be rendered to the confirmation popup.
|
||||
:param confirmation_label: optional confirmation button label (default to 'yes')
|
||||
:param deny_label: optional deny button label (default to 'no')
|
||||
:param deny_button: if the deny button should be displayed
|
||||
:return: if the request was confirmed by the user
|
||||
"""
|
||||
pass
|
||||
|
||||
def request_reboot(self, msg: str) -> bool:
|
||||
"""
|
||||
:return: requests a system reboot
|
||||
"""
|
||||
pass
|
||||
|
||||
def show_message(self, title: str, body: str, type_: MessageType = MessageType.INFO):
|
||||
"""
|
||||
shows a message to the user. In the current GUI implementation, it shows a popup.
|
||||
@@ -70,4 +77,33 @@ class ProcessWatcher:
|
||||
"""
|
||||
asks the root password for the user
|
||||
:return: a tuple with the typed password and if it is valid
|
||||
"""
|
||||
"""
|
||||
|
||||
|
||||
class TaskManager:
|
||||
|
||||
def register_task(self, id_: str, label: str, icon_path: str):
|
||||
"""
|
||||
:param id_: an unique identifier for the task
|
||||
:param label: an i18n label
|
||||
:param icon_path: str
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def update_progress(self, task_id: str, progress: float, substatus: str):
|
||||
"""
|
||||
:param task_id:
|
||||
:param progress: a float between 0 and 100.
|
||||
:param substatus: a substatus string representing the current state
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def finish_task(self, task_id: str):
|
||||
"""
|
||||
marks a task as finished
|
||||
:param task_id:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -5,22 +5,31 @@ from typing import List
|
||||
from bauh.api.constants import CACHE_PATH
|
||||
|
||||
|
||||
class PackageAction:
|
||||
class CustomSoftwareAction:
|
||||
|
||||
def __init__(self, i18_label_key: str, i18n_status_key: str, icon_path: str, manager_method: str, requires_root: bool):
|
||||
def __init__(self, i18_label_key: str, i18n_status_key: str, icon_path: str, manager_method: str, requires_root: bool, manager: "SoftwareManager" = None, backup: bool = False):
|
||||
"""
|
||||
:param i18_label_key: the i18n key that will be used to display the action name
|
||||
:param i18n_status_key: the i18n key that will be used to display the action name being executed
|
||||
:param icon_path: the action icon path. Use None for no icon
|
||||
:param manager_method: the SoftwareManager method name that should be called. The method must has the following parameters: (pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher)
|
||||
:param manager: the instance that will execute the action ( optional )
|
||||
:param backup: if a system backup should be performed before executing the action
|
||||
:param requires_root:
|
||||
"""
|
||||
self.id = id
|
||||
self.i18_label_key = i18_label_key
|
||||
self.i18n_status_key = i18n_status_key
|
||||
self.icon_path = icon_path
|
||||
self.manager_method = manager_method
|
||||
self.requires_root = requires_root
|
||||
self.manager = manager
|
||||
self.backup = backup
|
||||
|
||||
def __hash__(self):
|
||||
return self.i18_label_key.__hash__() + self.i18n_status_key.__hash__() + self.manager_method.__hash__()
|
||||
|
||||
def __repr__(self):
|
||||
return "CustomAction (label={}, method={})".format(self.i18_label_key, self.manager_method)
|
||||
|
||||
|
||||
class PackageStatus(Enum):
|
||||
@@ -58,6 +67,7 @@ class SoftwarePackage(ABC):
|
||||
self.size = size
|
||||
self.categories = categories
|
||||
self.license = license
|
||||
self.gem_name = self.__module__.split('.')[2]
|
||||
|
||||
@abstractmethod
|
||||
def has_history(self):
|
||||
@@ -122,11 +132,21 @@ class SoftwarePackage(ABC):
|
||||
"""
|
||||
return CACHE_PATH + '/' + self.get_type()
|
||||
|
||||
def can_be_updated(self) -> bool:
|
||||
"""
|
||||
:return: if the package can be updated.
|
||||
"""
|
||||
return self.installed and self.update
|
||||
|
||||
def get_disk_icon_path(self):
|
||||
return '{}/icon.png'.format(self.get_disk_cache_path())
|
||||
path = self.get_disk_cache_path()
|
||||
if path:
|
||||
return '{}/icon.png'.format(path)
|
||||
|
||||
def get_disk_data_path(self):
|
||||
return '{}/data.json'.format(self.get_disk_cache_path())
|
||||
path = self.get_disk_cache_path()
|
||||
if path:
|
||||
return '{}/data.json'.format(path)
|
||||
|
||||
@abstractmethod
|
||||
def get_data_to_cache(self) -> dict:
|
||||
@@ -163,7 +183,7 @@ class SoftwarePackage(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_custom_supported_actions(self) -> List[PackageAction]:
|
||||
def get_custom_supported_actions(self) -> List[CustomSoftwareAction]:
|
||||
"""
|
||||
:return: custom supported actions
|
||||
"""
|
||||
@@ -181,24 +201,35 @@ class SoftwarePackage(ABC):
|
||||
"""
|
||||
return self.name
|
||||
|
||||
def get_display_name(self) -> str:
|
||||
"""
|
||||
:return: name displayed on the table
|
||||
"""
|
||||
return self.name
|
||||
|
||||
@abstractmethod
|
||||
def supports_backup(self) -> bool:
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, name={})'.format(self.__class__.__name__, self.id, self.name)
|
||||
|
||||
|
||||
class PackageUpdate:
|
||||
|
||||
def __init__(self, pkg_id: str, version: str, pkg_type: str):
|
||||
def __init__(self, pkg_id: str, version: str, pkg_type: str, name: str):
|
||||
"""
|
||||
:param pkg_id: an unique package identifier
|
||||
:param version: the new version
|
||||
:param pkg_type: the package type
|
||||
"""
|
||||
self.id = pkg_id
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.type = pkg_type
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, type={}, new_version={})'.format(self.__class__.__name__, self.id, self.type, self.type)
|
||||
return '{} (id={}, name={}, version={}, type={})'.format(self.__class__.__name__, self.id, self.name, self.version, self.type)
|
||||
|
||||
|
||||
class PackageHistory:
|
||||
|
||||
@@ -75,7 +75,8 @@ class SelectViewType(Enum):
|
||||
class SingleSelectComponent(InputViewComponent):
|
||||
|
||||
def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None,
|
||||
max_per_line: int = 1, tooltip: str = None, max_width: int = -1, id_: str = None):
|
||||
max_per_line: int = 1, tooltip: str = None, max_width: int = -1, id_: str = None,
|
||||
capitalize_label: bool = True):
|
||||
super(SingleSelectComponent, self).__init__(id_=id_)
|
||||
self.type = type_
|
||||
self.label = label
|
||||
@@ -84,6 +85,7 @@ class SingleSelectComponent(InputViewComponent):
|
||||
self.max_per_line = max_per_line
|
||||
self.tooltip = tooltip
|
||||
self.max_width = max_width
|
||||
self.capitalize_label = capitalize_label
|
||||
|
||||
def get_selected(self):
|
||||
if self.value:
|
||||
@@ -119,11 +121,12 @@ class MultipleSelectComponent(InputViewComponent):
|
||||
|
||||
class TextComponent(ViewComponent):
|
||||
|
||||
def __init__(self, html: str, max_width: int = -1, tooltip: str = None, id_: str = None):
|
||||
def __init__(self, html: str, max_width: int = -1, tooltip: str = None, id_: str = None, size: int = None):
|
||||
super(TextComponent, self).__init__(id_=id_)
|
||||
self.value = html
|
||||
self.max_width = max_width
|
||||
self.tooltip = tooltip
|
||||
self.size = size
|
||||
|
||||
|
||||
class TwoStateButtonComponent(ViewComponent):
|
||||
@@ -156,8 +159,10 @@ class TextInputComponent(ViewComponent):
|
||||
|
||||
def get_int_value(self) -> int:
|
||||
if self.value is not None:
|
||||
return int(self.value)
|
||||
return None
|
||||
val = self.value.strip() if isinstance(self.value, str) else self.value
|
||||
|
||||
if val:
|
||||
return int(self.value)
|
||||
|
||||
|
||||
class FormComponent(ViewComponent):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
CACHE_PATH = '{}/.cache/bauh'.format(Path.home())
|
||||
CONFIG_PATH = '{}/.config/bauh'.format(Path.home())
|
||||
DESKTOP_ENTRIES_DIR = '{}/.local/share/applications'.format(Path.home())
|
||||
CACHE_PATH = '{}/.cache/bauh'.format(str(Path.home()))
|
||||
CONFIG_PATH = '{}/.config/bauh'.format(str(Path.home()))
|
||||
DESKTOP_ENTRIES_DIR = '{}/.local/share/applications'.format(str(Path.home()))
|
||||
TEMP_DIR = '/tmp/bauh{}'.format('_root' if os.getuid() == 0 else '')
|
||||
|
||||
@@ -67,7 +67,7 @@ class HttpClient:
|
||||
res = self.get(url, params=params, headers=headers, allow_redirects=allow_redirects, session=session)
|
||||
return yaml.safe_load(res.text) if res else None
|
||||
|
||||
def get_content_length(self, url: str, session: bool = True) -> str:
|
||||
def get_content_length_in_bytes(self, url: str, session: bool = True) -> int:
|
||||
params = {'url': url, 'allow_redirects': True, 'stream': True}
|
||||
if session:
|
||||
res = self.session.get(**params)
|
||||
@@ -77,8 +77,17 @@ class HttpClient:
|
||||
if res.status_code == 200:
|
||||
size = res.headers.get('Content-Length')
|
||||
|
||||
if size is not None:
|
||||
return system.get_human_size_str(size)
|
||||
if size:
|
||||
try:
|
||||
return int(size)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_content_length(self, url: str, session: bool = True) -> str:
|
||||
size = self.get_content_length_in_bytes(url, session)
|
||||
|
||||
if size:
|
||||
return system.get_human_size_str(size)
|
||||
|
||||
def exists(self, url: str, session: bool = True) -> bool:
|
||||
params = {'url': url, 'allow_redirects': True, 'verify': False, 'timeout': 5}
|
||||
|
||||
Reference in New Issue
Block a user