mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-08 20:14:14 +02:00
[arch] improvement: suggestions available for repository packages
This commit is contained in:
@@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- new parameter `--suggestions`: forces loading software suggestions after the initialization process [#260](https://github.com/vinifmor/bauh/issues/260)
|
- new parameter `--suggestions`: forces loading software suggestions after the initialization process [#260](https://github.com/vinifmor/bauh/issues/260)
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
- Arch
|
||||||
|
- suggestions available for repository packages (and the associated caching expiration property `suggestions_exp`. Default: 24 hours)
|
||||||
|
|
||||||
- General
|
- General
|
||||||
- preventing command injection through the search mechanism [#266](https://github.com/vinifmor/bauh/issues/266)
|
- preventing command injection through the search mechanism [#266](https://github.com/vinifmor/bauh/issues/266)
|
||||||
- code refactoring
|
- code refactoring
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ suggest_optdep_uninstall: false # if the optional dependencies associated with
|
|||||||
categories_exp: 24 # It defines the expiration time (in HOURS) of the packages categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
categories_exp: 24 # It defines the expiration time (in HOURS) of the packages categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
||||||
aur_rebuild_detector: true # it checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ('rebuild-detector' must be installed). Default: true.
|
aur_rebuild_detector: true # it checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ('rebuild-detector' must be installed). Default: true.
|
||||||
prefer_repository_provider: true # when there is just one repository provider for a given a dependency and several from AUR, it will be automatically picked.
|
prefer_repository_provider: true # when there is just one repository provider for a given a dependency and several from AUR, it will be automatically picked.
|
||||||
|
suggestions_exp: 24 # it defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
```
|
```
|
||||||
|
|
||||||
##### <a name="type_deb">Debian packages<a>
|
##### <a name="type_deb">Debian packages<a>
|
||||||
|
|||||||
@@ -299,6 +299,14 @@ class SuggestionPriority(Enum):
|
|||||||
HIGH = 2
|
HIGH = 2
|
||||||
TOP = 3
|
TOP = 3
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, SuggestionPriority):
|
||||||
|
return self.value > other.value
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, SuggestionPriority):
|
||||||
|
return self.value < other.value
|
||||||
|
|
||||||
|
|
||||||
class PackageSuggestion:
|
class PackageSuggestion:
|
||||||
|
|
||||||
|
|||||||
@@ -40,4 +40,5 @@ class ArchConfigManager(YAMLConfigManager):
|
|||||||
'categories_exp': 24,
|
'categories_exp': 24,
|
||||||
'aur_rebuild_detector': False,
|
'aur_rebuild_detector': False,
|
||||||
"aur_rebuild_detector_no_bin": True,
|
"aur_rebuild_detector_no_bin": True,
|
||||||
"prefer_repository_provider": True}
|
"prefer_repository_provider": True,
|
||||||
|
'suggestions_exp': 24}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from bauh.api.abstract.controller import SearchResult, SoftwareManager, Applicat
|
|||||||
from bauh.api.abstract.disk import DiskCacheLoader
|
from bauh.api.abstract.disk import DiskCacheLoader
|
||||||
from bauh.api.abstract.handler import ProcessWatcher, TaskManager
|
from bauh.api.abstract.handler import ProcessWatcher, TaskManager
|
||||||
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageStatus, \
|
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageStatus, \
|
||||||
CustomSoftwareAction
|
CustomSoftwareAction, PackageSuggestion
|
||||||
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
|
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
|
||||||
ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \
|
ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \
|
||||||
FileChooserComponent, TextComponent
|
FileChooserComponent, TextComponent
|
||||||
@@ -49,6 +49,7 @@ from bauh.gems.arch.model import ArchPackage
|
|||||||
from bauh.gems.arch.output import TransactionStatusHandler
|
from bauh.gems.arch.output import TransactionStatusHandler
|
||||||
from bauh.gems.arch.pacman import RE_DEP_OPERATORS
|
from bauh.gems.arch.pacman import RE_DEP_OPERATORS
|
||||||
from bauh.gems.arch.proc_util import write_as_user
|
from bauh.gems.arch.proc_util import write_as_user
|
||||||
|
from bauh.gems.arch.suggestions import RepositorySuggestionsDownloader
|
||||||
from bauh.gems.arch.updates import UpdatesSummarizer
|
from bauh.gems.arch.updates import UpdatesSummarizer
|
||||||
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, RefreshMirrors, \
|
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, RefreshMirrors, \
|
||||||
SyncDatabases
|
SyncDatabases
|
||||||
@@ -215,6 +216,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
self.re_file_conflict = re.compile(r'[\w\d\-_.]+:')
|
self.re_file_conflict = re.compile(r'[\w\d\-_.]+:')
|
||||||
self.disk_cache_updater = disk_cache_updater
|
self.disk_cache_updater = disk_cache_updater
|
||||||
self.pkgbuilder_user: Optional[str] = f'{__app_name__}-aur' if context.root_user else None
|
self.pkgbuilder_user: Optional[str] = f'{__app_name__}-aur' if context.root_user else None
|
||||||
|
self._suggestions_downloader: Optional[RepositorySuggestionsDownloader] = None
|
||||||
|
|
||||||
def refresh_mirrors(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
def refresh_mirrors(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
||||||
handler = ProcessHandler(watcher)
|
handler = ProcessHandler(watcher)
|
||||||
@@ -583,7 +585,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
rebuild_ignored = Thread(target=self.__fill_ignored_by_rebuild_detector, args=(rebuild_output, ), daemon=True)
|
rebuild_ignored = Thread(target=self.__fill_ignored_by_rebuild_detector, args=(rebuild_output, ), daemon=True)
|
||||||
rebuild_ignored.start()
|
rebuild_ignored.start()
|
||||||
|
|
||||||
installed = pacman.map_installed(names=names)
|
installed = pacman.map_packages(names=names)
|
||||||
|
|
||||||
aur_pkgs, repo_pkgs, aur_index = None, None, None
|
aur_pkgs, repo_pkgs, aur_index = None, None, None
|
||||||
|
|
||||||
@@ -630,7 +632,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
if pkgs:
|
if pkgs:
|
||||||
ignored = self._list_ignored_updates()
|
ignored = self._fill_ignored_updates(set())
|
||||||
|
|
||||||
if ignored:
|
if ignored:
|
||||||
for p in pkgs:
|
for p in pkgs:
|
||||||
@@ -1484,7 +1486,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
return uninstalled
|
return uninstalled
|
||||||
|
|
||||||
def _map_installed_data_for_removal(self, names: Iterable[str]) -> Optional[Dict[str, Dict[str, str]]]:
|
def _map_installed_data_for_removal(self, names: Iterable[str]) -> Optional[Dict[str, Dict[str, str]]]:
|
||||||
data = pacman.map_installed(names)
|
data = pacman.map_packages(names)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
remapped_data = {}
|
remapped_data = {}
|
||||||
@@ -2796,6 +2798,10 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
self.index_aur = AURIndexUpdater(context=self.context, taskman=task_manager, create_config=create_config) # must always execute to properly determine the installed packages (even that AUR is disabled)
|
self.index_aur = AURIndexUpdater(context=self.context, taskman=task_manager, create_config=create_config) # must always execute to properly determine the installed packages (even that AUR is disabled)
|
||||||
self.index_aur.start()
|
self.index_aur.start()
|
||||||
|
|
||||||
|
self.suggestions_downloader.create_config = create_config
|
||||||
|
self.suggestions_downloader.register_task(task_manager)
|
||||||
|
self.suggestions_downloader.start()
|
||||||
|
|
||||||
refresh_mirrors = RefreshMirrors(taskman=task_manager, i18n=self.i18n, root_password=root_password,
|
refresh_mirrors = RefreshMirrors(taskman=task_manager, i18n=self.i18n, root_password=root_password,
|
||||||
logger=self.logger, create_config=create_config)
|
logger=self.logger, create_config=create_config)
|
||||||
refresh_mirrors.start()
|
refresh_mirrors.start()
|
||||||
@@ -2938,6 +2944,12 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
only_int=True,
|
only_int=True,
|
||||||
capitalize_label=False,
|
capitalize_label=False,
|
||||||
value=arch_config['categories_exp'] if isinstance(arch_config['categories_exp'], int) else ''),
|
value=arch_config['categories_exp'] if isinstance(arch_config['categories_exp'], int) else ''),
|
||||||
|
TextInputComponent(id_='arch_sugs_exp',
|
||||||
|
label=self.i18n['arch.config.suggestions_exp'],
|
||||||
|
tooltip=self.i18n['arch.config.suggestions_exp.tip'],
|
||||||
|
only_int=True,
|
||||||
|
capitalize_label=False,
|
||||||
|
value=arch_config['suggestions_exp'] if isinstance(arch_config['suggestions_exp'], int) else '')
|
||||||
]
|
]
|
||||||
|
|
||||||
return SettingsView(self, PanelComponent([FormComponent(fields, spaces=False)], id_="repo"), icon_path=get_repo_icon_path())
|
return SettingsView(self, PanelComponent([FormComponent(fields, spaces=False)], id_="repo"), icon_path=get_repo_icon_path())
|
||||||
@@ -3045,6 +3057,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
arch_config['suggest_unneeded_uninstall'] = sug_unneeded_uni
|
arch_config['suggest_unneeded_uninstall'] = sug_unneeded_uni
|
||||||
|
|
||||||
arch_config['categories_exp'] = form.get_component('arch_cats_exp', TextInputComponent).get_int_value()
|
arch_config['categories_exp'] = form.get_component('arch_cats_exp', TextInputComponent).get_int_value()
|
||||||
|
arch_config['suggestions_exp'] = form.get_component('arch_sugs_exp', TextInputComponent).get_int_value()
|
||||||
|
|
||||||
def _fill_aur_settings(self, arch_config: dict, form: FormComponent):
|
def _fill_aur_settings(self, arch_config: dict, form: FormComponent):
|
||||||
arch_config['optimize'] = form.get_component('opts', SingleSelectComponent).get_selected()
|
arch_config['optimize'] = form.get_component('opts', SingleSelectComponent).get_selected()
|
||||||
@@ -3316,8 +3329,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _list_ignored_updates(self) -> Set[str]:
|
def _fill_ignored_updates(self, output: Set[str]) -> Set[str]:
|
||||||
ignored = set()
|
|
||||||
if os.path.exists(UPDATES_IGNORED_FILE):
|
if os.path.exists(UPDATES_IGNORED_FILE):
|
||||||
with open(UPDATES_IGNORED_FILE) as f:
|
with open(UPDATES_IGNORED_FILE) as f:
|
||||||
ignored_lines = f.readlines()
|
ignored_lines = f.readlines()
|
||||||
@@ -3327,9 +3339,9 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
line_clean = line.strip()
|
line_clean = line.strip()
|
||||||
|
|
||||||
if line_clean:
|
if line_clean:
|
||||||
ignored.add(line_clean)
|
output.add(line_clean)
|
||||||
|
|
||||||
return ignored
|
return output
|
||||||
|
|
||||||
def _write_ignored(self, names: Set[str]):
|
def _write_ignored(self, names: Set[str]):
|
||||||
Path(ARCH_CONFIG_DIR).mkdir(parents=True, exist_ok=True)
|
Path(ARCH_CONFIG_DIR).mkdir(parents=True, exist_ok=True)
|
||||||
@@ -3344,7 +3356,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
f.write('')
|
f.write('')
|
||||||
|
|
||||||
def ignore_update(self, pkg: ArchPackage):
|
def ignore_update(self, pkg: ArchPackage):
|
||||||
ignored = self._list_ignored_updates()
|
ignored = self._fill_ignored_updates(set())
|
||||||
|
|
||||||
if pkg.name not in ignored:
|
if pkg.name not in ignored:
|
||||||
ignored.add(pkg.name)
|
ignored.add(pkg.name)
|
||||||
@@ -3353,7 +3365,7 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
pkg.update_ignored = True
|
pkg.update_ignored = True
|
||||||
|
|
||||||
def _revert_ignored_updates(self, pkgs: Iterable[str]):
|
def _revert_ignored_updates(self, pkgs: Iterable[str]):
|
||||||
ignored = self._list_ignored_updates()
|
ignored = self._fill_ignored_updates(set())
|
||||||
|
|
||||||
for p in pkgs:
|
for p in pkgs:
|
||||||
if p in ignored:
|
if p in ignored:
|
||||||
@@ -3691,3 +3703,124 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
return added
|
return added
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _fill_available_packages(self, output: Dict[str, Set[str]]):
|
||||||
|
output.update(pacman.map_available_packages())
|
||||||
|
|
||||||
|
def _fill_suggestions(self, output: Dict[str, int]):
|
||||||
|
self.suggestions_downloader.register_task(None)
|
||||||
|
suggestions = self.suggestions_downloader.read(self.configman.read_config())
|
||||||
|
|
||||||
|
if suggestions:
|
||||||
|
output.update(suggestions)
|
||||||
|
|
||||||
|
def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[PackageSuggestion]]:
|
||||||
|
if limit == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
arch_config = self.configman.get_config()
|
||||||
|
|
||||||
|
if not arch_config['repositories']:
|
||||||
|
return
|
||||||
|
|
||||||
|
name_priority = dict()
|
||||||
|
|
||||||
|
fill_suggestions = Thread(target=self._fill_suggestions, args=(name_priority,))
|
||||||
|
fill_suggestions.start()
|
||||||
|
|
||||||
|
available_packages = dict()
|
||||||
|
fill_available = Thread(target=self._fill_available_packages, args=(available_packages,))
|
||||||
|
fill_available.start()
|
||||||
|
|
||||||
|
ignored_pkgs = set()
|
||||||
|
fill_ignored = Thread(target=pacman.fill_ignored_packages, args=(ignored_pkgs,))
|
||||||
|
fill_ignored.start()
|
||||||
|
|
||||||
|
fill_suggestions.join()
|
||||||
|
|
||||||
|
if not name_priority:
|
||||||
|
self.logger.info("No Arch package suggestions found")
|
||||||
|
return []
|
||||||
|
|
||||||
|
self.logger.info(f"Found {len(name_priority)} named Arch package suggestions")
|
||||||
|
|
||||||
|
if fill_available:
|
||||||
|
fill_available.join()
|
||||||
|
|
||||||
|
if not available_packages:
|
||||||
|
self.logger.error("No available Arch package found. It will not be possible to return suggestions")
|
||||||
|
return []
|
||||||
|
|
||||||
|
fill_ignored.join()
|
||||||
|
|
||||||
|
available_suggestions = dict()
|
||||||
|
|
||||||
|
for n in name_priority:
|
||||||
|
if n not in ignored_pkgs:
|
||||||
|
data = available_packages.get(n)
|
||||||
|
|
||||||
|
if data and (not filter_installed or not data['i']):
|
||||||
|
available_suggestions[n] = data
|
||||||
|
|
||||||
|
if not available_suggestions:
|
||||||
|
self.logger.info("No Arch package suggestion to return")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if filter_installed:
|
||||||
|
ignored_updates = set()
|
||||||
|
thread_fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,))
|
||||||
|
thread_fill_ignored_updates.start()
|
||||||
|
else:
|
||||||
|
ignored_updates, thread_fill_ignored_updates = None, None
|
||||||
|
|
||||||
|
# sorting by priority
|
||||||
|
suggestion_by_priority = tuple(pair[1] for pair in sorted(((name_priority[n], n) for n in available_suggestions),
|
||||||
|
reverse=True))
|
||||||
|
|
||||||
|
if 0 < limit < len(available_suggestions):
|
||||||
|
suggestion_by_priority = suggestion_by_priority[0:limit]
|
||||||
|
|
||||||
|
self.logger.info(f'Available Arch package suggestions: {len(suggestion_by_priority)}')
|
||||||
|
|
||||||
|
if thread_fill_ignored_updates:
|
||||||
|
thread_fill_ignored_updates.join()
|
||||||
|
|
||||||
|
full_data = pacman.map_packages(names=suggestion_by_priority, remote=filter_installed, not_signed=False,
|
||||||
|
skip_ignored=True)
|
||||||
|
|
||||||
|
if full_data and full_data.get('signed'):
|
||||||
|
full_data = full_data['signed']
|
||||||
|
|
||||||
|
suggestions = []
|
||||||
|
for name in suggestion_by_priority:
|
||||||
|
pkg_data = available_suggestions[name]
|
||||||
|
pkg_full_data = full_data.get(name)
|
||||||
|
description = None
|
||||||
|
|
||||||
|
if pkg_full_data:
|
||||||
|
description = pkg_full_data.get('description')
|
||||||
|
|
||||||
|
pkg_updates_ignored = pkg_data['i'] and ignored_updates and name in ignored_updates
|
||||||
|
|
||||||
|
suggestions.append(PackageSuggestion(package=ArchPackage(name=name,
|
||||||
|
version=pkg_data['v'],
|
||||||
|
latest_version=pkg_data['v'],
|
||||||
|
repository=pkg_data['r'],
|
||||||
|
installed=pkg_data['i'],
|
||||||
|
description=description,
|
||||||
|
categories=self.categories.get(name),
|
||||||
|
i18n=self.i18n,
|
||||||
|
maintainer=pkg_data['r'],
|
||||||
|
update_ignored=pkg_updates_ignored),
|
||||||
|
priority=name_priority[name]))
|
||||||
|
|
||||||
|
return suggestions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def suggestions_downloader(self) -> RepositorySuggestionsDownloader:
|
||||||
|
if not self._suggestions_downloader:
|
||||||
|
self._suggestions_downloader = RepositorySuggestionsDownloader(logger=self.logger,
|
||||||
|
http_client=self.http_client,
|
||||||
|
i18n=self.i18n)
|
||||||
|
|
||||||
|
return self._suggestions_downloader
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import traceback
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import List, Set, Tuple, Dict, Iterable, Optional
|
from typing import List, Set, Tuple, Dict, Iterable, Optional, Any
|
||||||
|
|
||||||
from colorama import Fore
|
from colorama import Fore
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-.]+\s+\[\w+]')
|
|||||||
RE_OPTDEPS = re.compile(r'[\w._\-]+\s*:')
|
RE_OPTDEPS = re.compile(r'[\w._\-]+\s*:')
|
||||||
RE_DEP_NOTFOUND = re.compile(r'error:.+\'(.+)\'')
|
RE_DEP_NOTFOUND = re.compile(r'error:.+\'(.+)\'')
|
||||||
RE_DEP_OPERATORS = re.compile(r'[<>=]')
|
RE_DEP_OPERATORS = re.compile(r'[<>=]')
|
||||||
RE_INSTALLED_FIELDS = re.compile(r'(Name|Description|Version|Install Date|Validated By)\s*:\s*(.+)')
|
RE_REPOSITORY_FIELDS = re.compile(r'(Repository|Name|Description|Version|Install Date|Validated By)\s*:\s*(.+)')
|
||||||
RE_INSTALLED_SIZE = re.compile(r'Installed Size\s*:\s*([0-9,.]+)\s(\w+)\n?', re.IGNORECASE)
|
RE_INSTALLED_SIZE = re.compile(r'Installed Size\s*:\s*([0-9,.]+)\s(\w+)\n?', re.IGNORECASE)
|
||||||
RE_DOWNLOAD_SIZE = re.compile(r'Download Size\s*:\s*([0-9,.]+)\s(\w+)\n?', re.IGNORECASE)
|
RE_DOWNLOAD_SIZE = re.compile(r'Download Size\s*:\s*([0-9,.]+)\s(\w+)\n?', re.IGNORECASE)
|
||||||
RE_UPDATE_REQUIRED_FIELDS = re.compile(r'(\bProvides\b|\bInstalled Size\b|\bConflicts With\b)\s*:\s(.+)\n')
|
RE_UPDATE_REQUIRED_FIELDS = re.compile(r'(\bProvides\b|\bInstalled Size\b|\bConflicts With\b)\s*:\s(.+)\n')
|
||||||
@@ -92,23 +93,31 @@ def check_installed(pkg: str) -> bool:
|
|||||||
return bool(res)
|
return bool(res)
|
||||||
|
|
||||||
|
|
||||||
def _fill_ignored(res: dict):
|
def fill_ignored_packages(output: Set[str]):
|
||||||
res['pkgs'] = list_ignored_packages()
|
output.update(list_ignored_packages())
|
||||||
|
|
||||||
|
|
||||||
def map_installed(names: Optional[Iterable[str]] = None) -> Dict[str, Dict[str, str]]:
|
def map_packages(names: Optional[Iterable[str]] = None, remote: bool = False, signed: bool = True,
|
||||||
ignored = {}
|
not_signed: bool = True, skip_ignored: bool = False) -> Dict[str, Dict[str, Dict[str, str]]]:
|
||||||
thread_ignored = Thread(target=_fill_ignored, args=(ignored,), daemon=True)
|
if not signed and not not_signed:
|
||||||
thread_ignored.start()
|
return {}
|
||||||
|
|
||||||
allinfo = run_cmd('pacman -Qi{}'.format(' ' + ' '.join(names) if names else ''), print_error=False)
|
ignored, thread_ignored = None, None
|
||||||
|
if not skip_ignored:
|
||||||
|
ignored = set()
|
||||||
|
thread_ignored = Thread(target=fill_ignored_packages, args=(ignored,), daemon=True)
|
||||||
|
thread_ignored.start()
|
||||||
|
|
||||||
|
allinfo = run_cmd(f"pacman -{'S' if remote else 'Q'}i {' '.join(names) if names else ''}", print_error=False)
|
||||||
|
|
||||||
pkgs = {'signed': {}, 'not_signed': {}}
|
pkgs = {'signed': {}, 'not_signed': {}}
|
||||||
current_pkg = {}
|
current_pkg = {}
|
||||||
|
|
||||||
if allinfo:
|
if allinfo:
|
||||||
for idx, field_tuple in enumerate(RE_INSTALLED_FIELDS.findall(allinfo)):
|
for idx, field_tuple in enumerate(RE_REPOSITORY_FIELDS.findall(allinfo)):
|
||||||
if field_tuple[0].startswith('N'):
|
if field_tuple[0].startswith('R'):
|
||||||
|
current_pkg['repository'] = field_tuple[1].strip()
|
||||||
|
elif field_tuple[0].startswith('N'):
|
||||||
current_pkg['name'] = field_tuple[1].strip()
|
current_pkg['name'] = field_tuple[1].strip()
|
||||||
elif field_tuple[0].startswith('Ve'):
|
elif field_tuple[0].startswith('Ve'):
|
||||||
current_pkg['version'] = field_tuple[1].strip()
|
current_pkg['version'] = field_tuple[1].strip()
|
||||||
@@ -117,25 +126,25 @@ def map_installed(names: Optional[Iterable[str]] = None) -> Dict[str, Dict[str,
|
|||||||
elif field_tuple[0].startswith('I'):
|
elif field_tuple[0].startswith('I'):
|
||||||
current_pkg['install_date'] = field_tuple[1].strip()
|
current_pkg['install_date'] = field_tuple[1].strip()
|
||||||
elif field_tuple[0].startswith('Va'):
|
elif field_tuple[0].startswith('Va'):
|
||||||
if field_tuple[1].strip().lower() == 'none':
|
if not_signed and field_tuple[1].strip().lower() == 'none':
|
||||||
pkgs['not_signed'][current_pkg['name']] = current_pkg
|
pkgs['not_signed'][current_pkg['name']] = current_pkg
|
||||||
del current_pkg['name']
|
del current_pkg['name']
|
||||||
else:
|
elif signed:
|
||||||
pkgs['signed'][current_pkg['name']] = current_pkg
|
pkgs['signed'][current_pkg['name']] = current_pkg
|
||||||
del current_pkg['name']
|
del current_pkg['name']
|
||||||
|
|
||||||
current_pkg = {}
|
current_pkg = {}
|
||||||
|
|
||||||
if pkgs['signed'] or pkgs['not_signed']:
|
if thread_ignored and (pkgs['signed'] or pkgs['not_signed']):
|
||||||
thread_ignored.join()
|
thread_ignored.join()
|
||||||
|
|
||||||
if ignored['pkgs']:
|
if ignored:
|
||||||
to_del = set()
|
to_del = set()
|
||||||
|
|
||||||
for key in ('signed', 'not_signed'):
|
for key in ('signed', 'not_signed'):
|
||||||
if pkgs.get(key):
|
if pkgs.get(key):
|
||||||
for pkg in pkgs[key].keys():
|
for pkg in pkgs[key].keys():
|
||||||
if pkg in ignored['pkgs']:
|
if pkg in ignored:
|
||||||
to_del.add(pkg)
|
to_del.add(pkg)
|
||||||
|
|
||||||
for pkg in to_del:
|
for pkg in to_del:
|
||||||
@@ -219,20 +228,23 @@ def sign_key(key: str, root_password: Optional[str]) -> SystemProcess:
|
|||||||
|
|
||||||
|
|
||||||
def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]:
|
def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]:
|
||||||
pacman_conf = new_subprocess(['cat', config_path])
|
|
||||||
|
|
||||||
ignored = set()
|
ignored = set()
|
||||||
grep = new_subprocess(['grep', '-Eo', r'\s*#*\s*ignorepkg\s*=\s*.+'], stdin=pacman_conf.stdout)
|
try:
|
||||||
for o in grep.stdout:
|
pacman_conf = new_subprocess(['cat', config_path])
|
||||||
if o:
|
grep = new_subprocess(['grep', '-Eo', r'\s*#*\s*ignorepkg\s*=\s*.+'], stdin=pacman_conf.stdout)
|
||||||
line = o.decode().strip()
|
for o in grep.stdout:
|
||||||
|
if o:
|
||||||
|
line = o.decode().strip()
|
||||||
|
|
||||||
if not line.startswith('#'):
|
if not line.startswith('#'):
|
||||||
ignored.add(line.split('=')[1].strip())
|
ignored.add(line.split('=')[1].strip())
|
||||||
|
|
||||||
pacman_conf.terminate()
|
pacman_conf.terminate()
|
||||||
grep.terminate()
|
grep.terminate()
|
||||||
return ignored
|
return ignored
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
return ignored
|
||||||
|
|
||||||
|
|
||||||
def check_missing(names: Set[str]) -> Set[str]:
|
def check_missing(names: Set[str]) -> Set[str]:
|
||||||
@@ -1037,3 +1049,24 @@ def find_one_match(name: str) -> Optional[str]:
|
|||||||
|
|
||||||
if matches and len(matches) == 1:
|
if matches and len(matches) == 1:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def map_available_packages() -> Optional[Dict[str, Any]]:
|
||||||
|
output = run_cmd('pacman -Sl')
|
||||||
|
|
||||||
|
if output:
|
||||||
|
res = dict()
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line_strip = line.strip()
|
||||||
|
|
||||||
|
if line_strip:
|
||||||
|
package_data = line.split(' ')
|
||||||
|
|
||||||
|
if len(package_data) >= 3:
|
||||||
|
pkgname = package_data[1].strip()
|
||||||
|
|
||||||
|
if pkgname:
|
||||||
|
res[pkgname] = {'v': package_data[2].strip(),
|
||||||
|
'r': package_data[0].strip(),
|
||||||
|
'i': len(package_data) == 4 and 'installed' in package_data[3]}
|
||||||
|
return res
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Synchronize packages databases
|
arch.config.sync_dbs=Synchronize packages databases
|
||||||
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
||||||
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Synchronize packages databases
|
arch.config.sync_dbs=Synchronize packages databases
|
||||||
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
||||||
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Synchronize packages databases
|
arch.config.sync_dbs=Synchronize packages databases
|
||||||
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
||||||
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Desinstalar dependencias opcionales
|
|||||||
arch.config.suggest_optdep_uninstall.tip=Si las dependencias opcionales asociadas con los paquetes desinstalados deben se sugeridas para desinstalación. Solo se sugerirán las dependencias opcionales que no sean dependencias de otros paquetes.
|
arch.config.suggest_optdep_uninstall.tip=Si las dependencias opcionales asociadas con los paquetes desinstalados deben se sugeridas para desinstalación. Solo se sugerirán las dependencias opcionales que no sean dependencias de otros paquetes.
|
||||||
arch.config.suggest_unneeded_uninstall=Desinstalar las dependencias innecesarias
|
arch.config.suggest_unneeded_uninstall=Desinstalar las dependencias innecesarias
|
||||||
arch.config.suggest_unneeded_uninstall.tip=Si las dependencias aparentemente ya no necesarias asociadas con los paquetes desinstalados deben ser sugeridas para desinstalación. Cuando esta propiedad está habilitada, automáticamente deshabilita la propiedad {}.
|
arch.config.suggest_unneeded_uninstall.tip=Si las dependencias aparentemente ya no necesarias asociadas con los paquetes desinstalados deben ser sugeridas para desinstalación. Cuando esta propiedad está habilitada, automáticamente deshabilita la propiedad {}.
|
||||||
|
arch.config.suggestions_exp=Expiración de sugerencias
|
||||||
|
arch.config.suggestions_exp.tip=Define el período (en horas) en el que la sugerencias almacenadas en disco seran consideradas actualizadas. Use 0 si desea siempre actualizarlas.
|
||||||
arch.config.sync_dbs=Sincronizar las bases de paquetes
|
arch.config.sync_dbs=Sincronizar las bases de paquetes
|
||||||
arch.config.sync_dbs.tip=Sincroniza las bases de paquetes una vez al día antes de la primera instalación, actualización o reversión de paquete. Esta opción ayuda a prevenir errores durante estas operaciones.
|
arch.config.sync_dbs.tip=Sincroniza las bases de paquetes una vez al día antes de la primera instalación, actualización o reversión de paquete. Esta opción ayuda a prevenir errores durante estas operaciones.
|
||||||
arch.config.sync_dbs_start.tip=Sincroniza las bases de paquetes durante la inicialización una vez al día
|
arch.config.sync_dbs_start.tip=Sincroniza las bases de paquetes durante la inicialización una vez al día
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Désinstaller les dépendances optionnelles
|
|||||||
arch.config.suggest_optdep_uninstall.tip=Si les dépendances optionnelles liées au paquets désinstallés dévraient être suggérés pour désinstallation. Seules les dépendances n'étant pas liées à d'autres paquets seront suggérées.
|
arch.config.suggest_optdep_uninstall.tip=Si les dépendances optionnelles liées au paquets désinstallés dévraient être suggérés pour désinstallation. Seules les dépendances n'étant pas liées à d'autres paquets seront suggérées.
|
||||||
arch.config.suggest_unneeded_uninstall=Désinstaller les dépendances inutiles
|
arch.config.suggest_unneeded_uninstall=Désinstaller les dépendances inutiles
|
||||||
arch.config.suggest_unneeded_uninstall.tip=Si les dépendances apparemment inutiles liées au paquets désinstallés dévraient être suggérés pour désinstallation. Activer cette propriété désactive automatiquement {}.
|
arch.config.suggest_unneeded_uninstall.tip=Si les dépendances apparemment inutiles liées au paquets désinstallés dévraient être suggérés pour désinstallation. Activer cette propriété désactive automatiquement {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Synchroniser les bases de données des paquets
|
arch.config.sync_dbs=Synchroniser les bases de données des paquets
|
||||||
arch.config.sync_dbs.tip=Synchronise les bases de données du paquet une fois par jour avant sa première installation, mise à jour ou downgrade. Cette option aide à éviter les erreurs durant ces opérations.
|
arch.config.sync_dbs.tip=Synchronise les bases de données du paquet une fois par jour avant sa première installation, mise à jour ou downgrade. Cette option aide à éviter les erreurs durant ces opérations.
|
||||||
arch.config.sync_dbs_start.tip=Synchronise les bases de données du paquet durant l'initialisation journalière
|
arch.config.sync_dbs_start.tip=Synchronise les bases de données du paquet durant l'initialisation journalière
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Synchronize packages databases
|
arch.config.sync_dbs=Synchronize packages databases
|
||||||
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
arch.config.sync_dbs.tip=Synchronizes the package databases once a day before the first package installation, upgrade or downgrade. This option helps to prevent errors during these operations.
|
||||||
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
arch.config.sync_dbs_start.tip=Synchronizes the package databases during the initialization once a day
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ arch.config.suggest_optdep_uninstall=Desinstalar dependências opcionais
|
|||||||
arch.config.suggest_optdep_uninstall.tip=Se as dependências opcionais associadas a pacotes desinstalados devem ser sugeridas para desinstalação. Somente as dependências opcionais que não sejam dependências de outros pacotes serão sugeridas.
|
arch.config.suggest_optdep_uninstall.tip=Se as dependências opcionais associadas a pacotes desinstalados devem ser sugeridas para desinstalação. Somente as dependências opcionais que não sejam dependências de outros pacotes serão sugeridas.
|
||||||
arch.config.suggest_unneeded_uninstall=Desinstalar dependências desnecessárias
|
arch.config.suggest_unneeded_uninstall=Desinstalar dependências desnecessárias
|
||||||
arch.config.suggest_unneeded_uninstall.tip=Se as dependências aparentemente não mais necessárias associadas aos pacotes desinstalados devem ser sugeridas para desinstalação. Quando essa proprieda está habilitada automaticamente desabilita a propriedade {}.
|
arch.config.suggest_unneeded_uninstall.tip=Se as dependências aparentemente não mais necessárias associadas aos pacotes desinstalados devem ser sugeridas para desinstalação. Quando essa proprieda está habilitada automaticamente desabilita a propriedade {}.
|
||||||
|
arch.config.suggestions_exp=Validade de sugestões
|
||||||
|
arch.config.suggestions_exp.tip=Define o período (em horas) no qual as sugestões armazenadas em disco serão consideradas atualizadas. Use 0 se quiser que elas sempre sejam atualizadas.
|
||||||
arch.config.sync_dbs=Sincronizar bases de pacotes
|
arch.config.sync_dbs=Sincronizar bases de pacotes
|
||||||
arch.config.sync_dbs.tip=Sincroniza as bases de pacotes uma vez ao dia antes da primeira instalação, atualização ou reversão de pacote. Essa opção ajuda a evitar erros durante essa operações.
|
arch.config.sync_dbs.tip=Sincroniza as bases de pacotes uma vez ao dia antes da primeira instalação, atualização ou reversão de pacote. Essa opção ajuda a evitar erros durante essa operações.
|
||||||
arch.config.sync_dbs_start.tip=Sincroniza as bases de pacotes durante a inicialização uma vez ao dia
|
arch.config.sync_dbs_start.tip=Sincroniza as bases de pacotes durante a inicialização uma vez ao dia
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Синхронизация баз данных пакетов
|
arch.config.sync_dbs=Синхронизация баз данных пакетов
|
||||||
arch.config.sync_dbs.tip=Синхронизируйте базы данных пакетов один раз в день перед первой установкой, обновлением или понижением версии пакета. Этот параметр помогает предотвратить ошибки во время этих операций.
|
arch.config.sync_dbs.tip=Синхронизируйте базы данных пакетов один раз в день перед первой установкой, обновлением или понижением версии пакета. Этот параметр помогает предотвратить ошибки во время этих операций.
|
||||||
arch.config.sync_dbs_start.tip=Синхронизирует базы данных пакетов во время инициализации один раз в день
|
arch.config.sync_dbs_start.tip=Синхронизирует базы данных пакетов во время инициализации один раз в день
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ arch.config.suggest_optdep_uninstall=Uninstall optional dependencies
|
|||||||
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
arch.config.suggest_optdep_uninstall.tip=If the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested.
|
||||||
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
arch.config.suggest_unneeded_uninstall=Uninstall unneeded dependencies
|
||||||
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
arch.config.suggest_unneeded_uninstall.tip=If the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property {}.
|
||||||
|
arch.config.suggestions_exp=Suggestions expiration
|
||||||
|
arch.config.suggestions_exp.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.
|
||||||
arch.config.sync_dbs=Paket veritabanlarını senkronize et
|
arch.config.sync_dbs=Paket veritabanlarını senkronize et
|
||||||
arch.config.sync_dbs.tip=İlk veritabanını kurmadan, yükseltmeden veya indirmeden önce paket veritabanlarını günde bir kez senkronize eder. Bu seçenek, bu işlemler sırasında hataların önlenmesine yardımcı olur.
|
arch.config.sync_dbs.tip=İlk veritabanını kurmadan, yükseltmeden veya indirmeden önce paket veritabanlarını günde bir kez senkronize eder. Bu seçenek, bu işlemler sırasında hataların önlenmesine yardımcı olur.
|
||||||
arch.config.sync_dbs_start.tip=Başlatma sırasında paket veritabanlarını günde bir kez senkronize eder
|
arch.config.sync_dbs_start.tip=Başlatma sırasında paket veritabanlarını günde bir kez senkronize eder
|
||||||
|
|||||||
211
bauh/gems/arch/suggestions.py
Normal file
211
bauh/gems/arch/suggestions.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from logging import Logger
|
||||||
|
from pathlib import Path
|
||||||
|
from threading import Thread
|
||||||
|
from typing import Optional, Dict
|
||||||
|
|
||||||
|
from bauh.api.abstract.handler import TaskManager
|
||||||
|
from bauh.api.abstract.model import SuggestionPriority
|
||||||
|
from bauh.api.http import HttpClient
|
||||||
|
from bauh.commons.boot import CreateConfigFile
|
||||||
|
from bauh.gems.arch import ARCH_CACHE_DIR, get_icon_path
|
||||||
|
from bauh.view.util.translation import I18n
|
||||||
|
|
||||||
|
|
||||||
|
class RepositorySuggestionsDownloader(Thread):
|
||||||
|
|
||||||
|
_file_suggestions: Optional[str] = None
|
||||||
|
_file_suggestions_ts: Optional[str] = None
|
||||||
|
_url_suggestions: Optional[str] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def file_suggestions(cls) -> str:
|
||||||
|
if cls._file_suggestions is None:
|
||||||
|
cls._file_suggestions = f'{ARCH_CACHE_DIR}/suggestions.txt'
|
||||||
|
|
||||||
|
return cls._file_suggestions
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def file_suggestions_timestamp(cls) -> str:
|
||||||
|
if cls._file_suggestions_ts is None:
|
||||||
|
cls._file_suggestions_ts = f'{cls.file_suggestions()}.ts'
|
||||||
|
|
||||||
|
return cls._file_suggestions_ts
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def url_suggestions(cls) -> str:
|
||||||
|
if cls._url_suggestions is None:
|
||||||
|
cls._url_suggestions = 'https://raw.githubusercontent.com/vinifmor/bauh-files' \
|
||||||
|
'/master/arch/suggestions.txt'
|
||||||
|
|
||||||
|
return cls._url_suggestions
|
||||||
|
|
||||||
|
def __init__(self, logger: Logger, http_client: HttpClient, i18n: I18n,
|
||||||
|
create_config: Optional[CreateConfigFile] = None):
|
||||||
|
super(RepositorySuggestionsDownloader, self).__init__()
|
||||||
|
self._log = logger
|
||||||
|
self.i18n = i18n
|
||||||
|
self.http_client = http_client
|
||||||
|
self._taskman: Optional[TaskManager] = None
|
||||||
|
self.create_config = create_config
|
||||||
|
self.task_id = 'arch.suggs'
|
||||||
|
|
||||||
|
def register_task(self, taskman: Optional[TaskManager]):
|
||||||
|
self._taskman = taskman
|
||||||
|
if taskman:
|
||||||
|
self._taskman.register_task(id_=self.task_id, label=self.i18n['task.download_suggestions'],
|
||||||
|
icon_path=get_icon_path())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def taskman(self) -> TaskManager:
|
||||||
|
if self._taskman is None:
|
||||||
|
self._taskman = TaskManager()
|
||||||
|
|
||||||
|
return self._taskman
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def should_download(cls, arch_config: dict, logger: Logger, only_positive_exp: bool = False) -> bool:
|
||||||
|
try:
|
||||||
|
exp_hours = int(arch_config['suggestions_exp'])
|
||||||
|
except ValueError:
|
||||||
|
logger.error(f"The Arch configuration property 'suggestions_exp' has a non int value set: "
|
||||||
|
f"{arch_config['suggestions']['expiration']}")
|
||||||
|
return not only_positive_exp
|
||||||
|
|
||||||
|
if exp_hours <= 0:
|
||||||
|
logger.info("Suggestions cache is disabled")
|
||||||
|
return not only_positive_exp
|
||||||
|
|
||||||
|
if not os.path.exists(cls.file_suggestions()):
|
||||||
|
logger.info(f"'{cls.file_suggestions()}' not found. It must be downloaded")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not os.path.exists(cls.file_suggestions()):
|
||||||
|
logger.info(f"'{cls.file_suggestions()}' not found. The suggestions file must be downloaded.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
with open(cls.file_suggestions_timestamp()) as f:
|
||||||
|
timestamp_str = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
suggestions_timestamp = datetime.fromtimestamp(float(timestamp_str))
|
||||||
|
except:
|
||||||
|
logger.error(f'Could not parse the Arch cached suggestions timestamp: {timestamp_str}')
|
||||||
|
traceback.print_exc()
|
||||||
|
return True
|
||||||
|
|
||||||
|
update = suggestions_timestamp + timedelta(hours=exp_hours) <= datetime.utcnow()
|
||||||
|
return update
|
||||||
|
|
||||||
|
def _save(self, text: str, timestamp: float):
|
||||||
|
self._log.info(f"Caching suggestions to '{self.file_suggestions()}'")
|
||||||
|
|
||||||
|
cache_dir = os.path.dirname(self.file_suggestions())
|
||||||
|
|
||||||
|
try:
|
||||||
|
Path(cache_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
cache_dir_ok = True
|
||||||
|
except OSError:
|
||||||
|
self._log.error(f"Could not create cache directory '{cache_dir}'")
|
||||||
|
traceback.print_exc()
|
||||||
|
cache_dir_ok = False
|
||||||
|
|
||||||
|
if cache_dir_ok:
|
||||||
|
try:
|
||||||
|
with open(self.file_suggestions(), 'w+') as f:
|
||||||
|
f.write(text)
|
||||||
|
except:
|
||||||
|
self._log.error(f"An exception happened while writing the file '{self.file_suggestions()}'")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.file_suggestions_timestamp(), 'w+') as f:
|
||||||
|
f.write(str(timestamp))
|
||||||
|
except:
|
||||||
|
self._log.error(f"An exception happened while writing the file '{self.file_suggestions_timestamp()}'")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def parse_suggestions(self, suggestions_str: str) -> Dict[str, SuggestionPriority]:
|
||||||
|
output = dict()
|
||||||
|
for line in suggestions_str.split('\n'):
|
||||||
|
clean_line = line.strip()
|
||||||
|
|
||||||
|
if clean_line:
|
||||||
|
line_split = clean_line.split(':', 1)
|
||||||
|
|
||||||
|
if len(line_split) == 2:
|
||||||
|
try:
|
||||||
|
prio = int(line_split[0])
|
||||||
|
except ValueError:
|
||||||
|
self._log.warning(f"Could not parse Arch package suggestion: {line}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
output[line_split[1]] = SuggestionPriority(prio)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def read_cached(self) -> Optional[Dict[str, SuggestionPriority]]:
|
||||||
|
self._log.info(f"Reading cached suggestions file '{self.file_suggestions()}'")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.file_suggestions()) as f:
|
||||||
|
sugs_str = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
self._log.warning(f"Cached suggestions file does not exist ({self.file_suggestions()})")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not sugs_str:
|
||||||
|
self._log.warning(f"Cached suggestions file '{self.file_suggestions()}' is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
return self.parse_suggestions(sugs_str)
|
||||||
|
|
||||||
|
def download(self) -> Optional[Dict[str, SuggestionPriority]]:
|
||||||
|
self.taskman.update_progress(self.task_id, progress=1, substatus=None)
|
||||||
|
|
||||||
|
self._log.info(f"Downloading suggestions from {self.url_suggestions()}")
|
||||||
|
res = self.http_client.get(self.url_suggestions())
|
||||||
|
|
||||||
|
suggestions = None
|
||||||
|
if res.status_code == 200 and res.text:
|
||||||
|
self.taskman.update_progress(self.task_id, progress=50, substatus=None)
|
||||||
|
suggestions = self.parse_suggestions(res.text)
|
||||||
|
|
||||||
|
if suggestions:
|
||||||
|
self._save(text=res.text, timestamp=datetime.utcnow().timestamp())
|
||||||
|
else:
|
||||||
|
self._log.warning("No Arch suggestions to cache")
|
||||||
|
else:
|
||||||
|
self._log.warning(f"Could not retrieve Arch suggestions. "
|
||||||
|
f"Response (status={res.status_code}, text={res.text})")
|
||||||
|
|
||||||
|
self.taskman.update_progress(self.task_id, progress=100, substatus=None)
|
||||||
|
self.taskman.finish_task(self.task_id)
|
||||||
|
return suggestions
|
||||||
|
|
||||||
|
def read(self, arch_config: dict) -> Optional[Dict[str, int]]:
|
||||||
|
if self.should_download(arch_config=arch_config, logger=self._log):
|
||||||
|
return self.download()
|
||||||
|
|
||||||
|
return self.read_cached()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.create_config:
|
||||||
|
if self.create_config.is_alive():
|
||||||
|
self.taskman.update_progress(self.task_id, 0,
|
||||||
|
self.i18n['task.waiting_task'].format(self.create_config.task_name))
|
||||||
|
self.create_config.join()
|
||||||
|
|
||||||
|
if not self.should_download(arch_config=self.create_config.config, logger=self._log,
|
||||||
|
only_positive_exp=False):
|
||||||
|
self.taskman.update_progress(self.task_id, 100, self.i18n['task.canceled'])
|
||||||
|
self.taskman.finish_task(self.task_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.download()
|
||||||
|
else:
|
||||||
|
self._log.error(f"No {CreateConfigFile.__class__.__name__} instance set. Aborting..")
|
||||||
|
self.taskman.update_progress(self.task_id, 100, self.i18n['error'])
|
||||||
|
self.taskman.finish_task(self.task_id)
|
||||||
Reference in New Issue
Block a user