Files
bearhub/bauh/gems/debian/controller.py

903 lines
39 KiB
Python

import os.path
import shutil
import traceback
from operator import attrgetter
from pathlib import Path
from shutil import which
from subprocess import Popen
from threading import Thread
from typing import List, Optional, Tuple, Set, Type, Dict, Iterable, Generator
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager, SoftwareAction, TransactionResult, UpgradeRequirements, \
SearchResult, UpgradeRequirement, SettingsView, SettingsController
from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import TaskManager, ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageSuggestion, PackageUpdate, PackageHistory, \
CustomSoftwareAction
from bauh.api.abstract.view import TextInputComponent, PanelComponent, FormComponent, MessageType, \
SingleSelectComponent, InputOption, SelectViewType
from bauh.api.paths import CONFIG_DIR
from bauh.commons.html import bold
from bauh.commons.system import ProcessHandler
from bauh.commons.util import NullLoggerFactory
from bauh.commons.view_utils import get_human_size_str
from bauh.gems.debian import DEBIAN_ICON_PATH
from bauh.gems.debian.aptitude import Aptitude, AptitudeOutputHandlerFactory, AptitudeAction
from bauh.gems.debian.common import fill_show_data
from bauh.gems.debian.config import DebianConfigManager
from bauh.gems.debian.gui import DebianViewBridge
from bauh.gems.debian.index import ApplicationIndexer, ApplicationIndexError, ApplicationsMapper
from bauh.gems.debian.model import DebianPackage, DebianApplication
from bauh.gems.debian.suggestions import DebianSuggestionsDownloader
from bauh.gems.debian.tasks import UpdateApplicationIndex, MapApplications, SynchronizePackages
class DebianPackageManager(SoftwareManager, SettingsController):
def __init__(self, context: ApplicationContext):
super(DebianPackageManager, self).__init__(context)
self._i18n = context.i18n
self._log = context.logger
self._types: Optional[Set[Type[SoftwarePackage]]] = None
self._enabled = True
self._app_indexer: Optional[ApplicationIndexer] = None
self._apps_index: Optional[Dict[str, DebianApplication]] = None
self._configman: Optional[DebianConfigManager] = None
self._action_launch_sources: Optional[CustomSoftwareAction] = None
self._default_actions: Optional[Iterable[CustomSoftwareAction]] = None
self._view: Optional[DebianViewBridge] = None
self._app_mapper: Optional[ApplicationsMapper] = None
self._aptitude: Optional[Aptitude] = None
self._output_handler: Optional[AptitudeOutputHandlerFactory] = None
self._known_sources_apps: Optional[Tuple[str, ...]] = None
self._install_show_attrs: Optional[Set[str]] = None
self._file_ignored_updates: Optional[str] = None
self._suggestions_downloader: Optional[DebianSuggestionsDownloader] = None
def _update_apps_index(self, apps: Iterable[DebianApplication]):
self._apps_index = {app.name: app for app in apps} if apps else dict()
def search(self, words: str, disk_loader: Optional[DiskCacheLoader], limit: int, is_url: bool) -> SearchResult:
config_ = dict()
fill_config = Thread(target=self._fill_config, args=(config_,))
fill_config.start()
res = SearchResult.empty()
if not is_url:
for pkg in self.aptitude.search(words):
if fill_config.is_alive():
fill_config.join()
pkg.global_purge = bool(config_.get('remove.purge', False))
if pkg.installed:
pkg.bind_app(self.apps_index.get(pkg.name))
res.installed.append(pkg)
else:
res.new.append(pkg)
return res
def _fill_ignored_updates(self, output: Set[str]):
try:
with open(self.file_ignored_updates) as f:
ignored_str = f.read()
except FileNotFoundError:
return
if ignored_str:
for line in ignored_str.split('\n'):
line_clean = line.strip()
if line_clean:
output.add(line_clean)
def _fill_config(self, config_: dict):
config_.update(self.configman.get_config())
def read_installed(self, disk_loader: Optional[DiskCacheLoader], pkg_types: Optional[Set[Type[SoftwarePackage]]],
internet_available: bool, limit: int = -1, only_apps: bool = False,
names: Optional[Iterable[str]] = None) -> SearchResult:
config_ = dict()
fill_config = Thread(target=self._fill_config, args=(config_,))
fill_config.start()
ignored_updates = set()
fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,))
fill_ignored_updates.start()
threads = (fill_config, fill_ignored_updates)
res = SearchResult(installed=[], new=None, total=0)
for pkg in self.aptitude.read_installed():
for t in threads:
if t.is_alive():
t.join()
pkg.bind_app(self.apps_index.get(pkg.name))
pkg.global_purge = bool(config_.get('remove.purge', False))
pkg.updates_ignored = bool(ignored_updates and pkg.name in ignored_updates)
res.installed.append(pkg)
return res
def downgrade(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool:
return False
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool:
handler = ProcessHandler(watcher)
targets = (r.pkg.name for r in (*requirements.to_upgrade, *(requirements.to_install or ())))
with self.output_handler.start(watcher=watcher, targets=targets, action=AptitudeAction.UPGRADE) as handle:
to_upgrade = (r.pkg.name for r in requirements.to_upgrade)
success, _ = handler.handle_simple(self.aptitude.upgrade(packages=to_upgrade,
root_password=root_password),
output_handler=handle)
return success
def _fill_updates(self, output: Dict[str, str]):
for name, version in self.aptitude.read_updates():
output[name] = version
def uninstall(self, pkg: DebianPackage, root_password: str, watcher: ProcessWatcher,
disk_loader: Optional[DiskCacheLoader], purge: bool = False) -> TransactionResult:
config_ = self.configman.get_config()
purge_ = purge or config_.get('remove.purge', False)
watcher.change_substatus(self._i18n['debian.simulate_operation'])
transaction = self.aptitude.simulate_removal((pkg.name,), purge=purge_)
if not transaction or not transaction.to_remove:
return TransactionResult.fail()
if pkg not in transaction.to_remove:
watcher.show_message(title=self._i18n['popup.title.error'],
body=self._i18n['debian.remove.impossible'].format(pkg=bold(pkg.name)),
type_=MessageType.ERROR)
return TransactionResult.fail()
watcher.change_substatus('')
deps = tuple(p for p in transaction.to_remove if p.name != pkg.name)
if deps:
# updates are required to be filled in case the dependencies are currently displayed on the view
updates = dict()
fill_updates = Thread(target=self._fill_updates, args=(updates,))
fill_updates.start()
deps_data = self.aptitude.show((p.name for p in deps), attrs=('description', 'maintainer', 'section'))
if deps_data:
for p in deps:
fill_show_data(p, deps_data.get(p.name))
if not self.view.confirm_removal(source_pkg=pkg.name, dependencies=deps, watcher=watcher):
return TransactionResult.fail()
fill_updates.join()
if updates:
for p in deps:
latest_version = updates.get(p.name)
if latest_version is not None and p.version != latest_version:
p.latest_version = latest_version
p.update = True
watcher.change_substatus(self._i18n['debian.uninstall.removing'])
handler = ProcessHandler(watcher)
to_remove = tuple(p.name for p in transaction.to_remove)
with self.output_handler.start(watcher=watcher, targets=to_remove, action=AptitudeAction.REMOVE) as handle:
removed, _ = handler.handle_simple(self.aptitude.remove(packages=to_remove, root_password=root_password,
purge=purge_),
output_handler=handle)
if not removed:
return TransactionResult.fail()
watcher.change_substatus(self._i18n['debian.uninstall.validating'])
current_installed_names = set(self.aptitude.read_installed_names())
watcher.change_substatus('')
all_removed, apps_removed, not_removed_names = [], set(), set()
for p in transaction.to_remove:
if p.name not in current_installed_names:
instance = p if p != pkg else pkg
all_removed.append(instance)
if instance.app:
apps_removed.add(instance.app)
instance.installed = False
instance.version = instance.latest_version
instance.update = False
instance.bind_app(None)
else:
not_removed_names.add(p.name)
if apps_removed: # updating apps index
watcher.print(self._i18n['debian.app_index.updating'] + ' ...')
watcher.change_substatus(self._i18n['debian.app_index.updating'])
indexed_apps = set(self.app_indexer.read_index())
if indexed_apps:
new_index = indexed_apps.difference(apps_removed)
try:
self.app_indexer.update_index(new_index, update_timestamp=False)
self._update_apps_index(new_index)
self._log.info(f"Debian applications removed from the index: "
f"{', '.join((a.name for a in apps_removed))}")
except ApplicationIndexError:
pass
watcher.change_substatus('')
success = True
if not_removed_names:
success = pkg.name not in not_removed_names
not_removed_str = ', '.join((bold(p) for p in sorted(not_removed_names)))
watcher.show_message(title=self._i18n[f"popup.title.{'warning' if success else 'error'}"],
body=self._i18n['debian.uninstall.failed_to_remove'].format(no=len(not_removed_names),
pkgs=not_removed_str),
type_=MessageType.WARNING if success else MessageType.ERROR)
return TransactionResult(success=success, installed=None, removed=all_removed)
def _map_dependents(self, packages_data: Dict[str, Dict[str, object]]) -> Optional[Dict[str, Set[str]]]:
if packages_data:
dependents = None
if packages_data:
dependents = dict()
for p, data in packages_data.items():
for attr in ('depends', 'predepends'):
dep_exps = data.get(attr)
if isinstance(dep_exps, tuple):
for exp in dep_exps:
dep = exp.split(' ')[0].strip()
dep_dependents = dependents.get(dep)
if dep_dependents is None:
dep_dependents = set()
dependents[dep] = dep_dependents
dep_dependents.add(p)
return dependents
def get_upgrade_requirements(self, pkgs: List[DebianPackage], root_password: str, watcher: ProcessWatcher) \
-> UpgradeRequirements:
transaction = self.aptitude.simulate_upgrade((p.name for p in pkgs))
if transaction:
size_to_query = (f'{p.name}={p.latest_version}' for p in (*transaction.to_upgrade,
*transaction.to_install,
*transaction.to_remove))
update_extra_attrs = ('compressed size', *(('depends', 'predepends') if transaction.to_install else ()))
update_data = self.aptitude.show(pkgs=size_to_query, attrs=update_extra_attrs)
to_install = None
if transaction.to_install:
dependents = self._map_dependents(update_data)
to_install = []
for p in transaction.to_install:
p_data = update_data.get(p.name) if update_data else None
req_size = p_data.get('compressed size') if p_data else None
reason = None
if dependents:
p_dependents = dependents.get(p.name)
if p_dependents:
deps_str = ', '.join((bold(d) for d in p_dependents))
reason = self._i18n['debian.transaction.dependency_of'].format(pkgs=deps_str)
to_install.append(UpgradeRequirement(pkg=p, reason=reason, required_size=req_size,
extra_size=p.transaction_size))
to_install.sort(key=attrgetter('pkg.name'))
to_install = tuple(to_install)
to_remove = None
if transaction.to_remove:
to_remove = []
for p in transaction.to_remove:
to_remove.append(UpgradeRequirement(pkg=p, required_size=0, extra_size=p.transaction_size))
to_remove.sort(key=attrgetter('pkg.name'))
to_remove = tuple(to_remove)
to_upgrade = None
if transaction.to_upgrade:
to_upgrade = []
for p in transaction.to_upgrade:
p_data = update_data.get(p.name) if update_data else None
if p_data:
req_size = p_data.get('compressed size')
else:
req_size = None
to_upgrade.append(UpgradeRequirement(pkg=p, required_size=req_size, extra_size=p.transaction_size))
to_upgrade.sort(key=attrgetter('pkg.name'))
to_upgrade = tuple(to_upgrade)
return UpgradeRequirements(to_install=to_install, to_upgrade=to_upgrade,
to_remove=to_remove, cannot_upgrade=None)
else:
cannot_upgrade = [UpgradeRequirement(pkg=p, reason=self._i18n['error']) for p in pkgs]
cannot_upgrade.sort(key=attrgetter('pkg.name'))
return UpgradeRequirements(cannot_upgrade=cannot_upgrade, to_install=None, to_remove=None,
to_upgrade=[])
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
if self._types is None:
self._types = {DebianPackage}
return self._types
def get_info(self, pkg: DebianPackage) -> Optional[dict]:
info = {'00.name': pkg.name, '01.version': pkg.version,
"02.description": pkg.description}
if pkg.installed and pkg.app:
info['03.exec'] = pkg.app.exe_path
extra_info = self.aptitude.show((f'{pkg.name}={pkg.version}',), verbose=True)
if extra_info:
extra_info = extra_info.get(pkg.name)
if extra_info:
ignored_fields = {'package', 'version', 'description'}
for field, val in extra_info.items():
if field not in ignored_fields and not field.startswith('description'):
final_val = get_human_size_str(val) if field in self.aptitude.size_attrs else val
final_field = f'04.{field}'
if final_field not in self._i18n:
self._i18n.default[final_field] = field
info[final_field] = final_val # for sorting
return info
def get_history(self, pkg: SoftwarePackage) -> PackageHistory:
pass
def install(self, pkg: SoftwarePackage, root_password: str, disk_loader: Optional[DiskCacheLoader],
watcher: ProcessWatcher) -> TransactionResult:
watcher.change_substatus(self._i18n['debian.simulate_operation'])
transaction = self.aptitude.simulate_installation((pkg.name, ))
if transaction is None or not transaction.to_install:
return TransactionResult.fail()
if transaction.to_remove or (transaction.to_install and len(transaction.to_install) > 1):
watcher.change_substatus(self._i18n['debian.transaction.get_data'])
deps = tuple(p for p in transaction.to_install or () if p.name != pkg.name)
removal = tuple(p for p in transaction.to_remove or ())
all_pkgs = [*deps, *removal]
pkgs_data = self.aptitude.show(pkgs=(f'{d.name}={d.version}' for d in all_pkgs),
attrs=self.install_show_attrs)
if pkgs_data:
for p in all_pkgs:
fill_show_data(p, pkgs_data.get(p.name))
if not self.view.confirm_transaction(to_install=deps, removal=removal, watcher=watcher):
return TransactionResult.fail()
watcher.change_substatus(self._i18n['debian.installing_pkgs'])
handler = ProcessHandler(watcher)
targets = (p.name for p in transaction.to_install)
with self.output_handler.start(watcher=watcher, targets=targets, action=AptitudeAction.INSTALL) as handle:
installed, _ = handler.handle_simple(self.aptitude.install(packages=(pkg.name,),
root_password=root_password),
output_handler=handle)
if installed:
self._refresh_apps_index(watcher)
watcher.change_substatus(self._i18n['debian.install.validating'])
currently_installed = set(self.aptitude.read_installed_names())
installed_instances = []
if currently_installed:
for p in transaction.to_install:
instance = p if p != pkg else pkg
if instance.name in currently_installed:
instance.installed = True
instance.bind_app(self.apps_index.get(instance.name))
installed_instances.append(instance)
removed = None
if transaction.to_remove:
removed = [p for p in transaction.to_remove if p.name not in currently_installed]
not_removed = set(transaction.to_remove).difference(removed)
if not_removed:
not_removed_str = ' '.join(p.name for p in not_removed)
self._log.warning(f"The following packages were not removed: {not_removed_str}")
return TransactionResult(installed=installed_instances, removed=removed,
success=bool(installed_instances and pkg in installed_instances))
else:
watcher.change_substatus('')
return TransactionResult.fail()
def _refresh_apps_index(self, watcher: ProcessWatcher):
watcher.change_substatus(self._i18n['debian.app_index.checking'])
self._log.info("Reading the cached Debian applications")
indexed_apps = self.app_indexer.read_index()
self._log.info("Mapping the Debian applications")
current_apps = self.app_mapper.map_executable_applications()
if current_apps != indexed_apps:
watcher.print(self._i18n['debian.app_index.updating'] + '...')
watcher.change_substatus(self._i18n['debian.app_index.updating'])
try:
self.app_indexer.update_index(current_apps)
self._update_apps_index(current_apps)
if indexed_apps is not None:
new_apps = current_apps.difference(indexed_apps)
if new_apps:
self._log.info(f"Debian applications added to the index: "
f"{','.join((a.name for a in new_apps))}")
except ApplicationIndexError:
pass
def is_enabled(self) -> bool:
return self._enabled
def set_enabled(self, enabled: bool):
self._enabled = enabled
def can_work(self) -> Tuple[bool, Optional[str]]:
if not which('aptitude'):
return False, self._i18n['missing_dep'].format(dep=bold('aptitude'))
return True, None
def requires_root(self, action: SoftwareAction, pkg: Optional[SoftwarePackage]) -> bool:
if action == action.PREPARE:
deb_config = self.configman.get_config()
return SynchronizePackages.should_synchronize(deb_config, NullLoggerFactory.logger())
return action != SoftwareAction.SEARCH
def prepare(self, task_manager: Optional[TaskManager], root_password: Optional[str],
internet_available: Optional[bool]):
deb_config = self.configman.get_config()
if SynchronizePackages.should_synchronize(deb_config, self._log):
sync_pkgs = SynchronizePackages(taskman=task_manager, i18n=self._i18n, logger=self._log,
root_password=root_password, aptitude=self.aptitude)
sync_pkgs.start()
if DebianSuggestionsDownloader.should_download(deb_config, self._log, only_positive_exp=True):
self.suggestions_downloader.register_task(task_manager)
self.suggestions_downloader.start()
self.index_apps(root_password=root_password, watcher=None, taskman=task_manager,
deb_config=deb_config, check_expiration=True)
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
ignored_updates = set()
fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,))
fill_ignored_updates.start()
updates = list()
for name, version in self.aptitude.read_updates():
if fill_ignored_updates.is_alive():
fill_ignored_updates.join()
if name not in ignored_updates:
updates.append(PackageUpdate(pkg_id=name, name=name, version=version, pkg_type='debian'))
return updates
def list_warnings(self, internet_available: bool) -> Optional[List[str]]:
pass
def _fill_installed_names(self, output: Set[str]):
output.update(self.aptitude.read_installed_names())
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]]:
name_priority = dict()
fill_suggestions = Thread(target=self._fill_suggestions, args=(name_priority,))
fill_suggestions.start()
if filter_installed:
installed = set()
fill_installed = Thread(target=self._fill_installed_names, args=(installed, ))
fill_installed.start()
else:
installed, fill_installed = None, None
fill_suggestions.join()
if fill_installed:
fill_installed.join()
if not name_priority:
self._log.info("No Debian package suggestions found")
return []
self._log.info(f"Found {len(name_priority)} Debian package suggestions")
to_load = tuple(name_priority.keys()) if not installed else {*name_priority.keys()}.difference(installed)
if not to_load:
return []
suggestions = []
for pkg in self.aptitude.search_by_name(to_load):
prio = name_priority.get(pkg.name)
if prio:
suggestions.append(PackageSuggestion(package=pkg, priority=prio))
return suggestions
def is_default_enabled(self) -> bool:
return True
def launch(self, pkg: SoftwarePackage):
if isinstance(pkg, DebianPackage):
final_cmd = pkg.app.exe_path.replace('%U', '')
Popen(final_cmd, shell=True)
def get_settings(self) -> Optional[Generator[SettingsView, None, None]]:
config_ = self.configman.get_config()
purge_opts = [InputOption(label=self._i18n['yes'].capitalize(), value=True),
InputOption(label=self._i18n['no'].capitalize(), value=False)]
purge_current = tuple(o for o in purge_opts if o.value == bool(config_['remove.purge']))[0]
sel_purge = SingleSelectComponent(id_='remove.purge',
label=self._i18n['debian.config.remove.purge'],
tooltip=self._i18n['debian.config.remove.purge.tip'],
options=purge_opts,
default_option=purge_current,
type_=SelectViewType.RADIO,
max_width=200,
max_per_line=2)
sources_app = config_.get('pkg_sources.app')
if isinstance(sources_app, str) and sources_app not in self.known_sources_apps:
self._log.warning(f"'pkg_sources.app' ({sources_app}) is not supported. A 'None' value will be considered")
sources_app = None
lb_source_auto = self._i18n['debian.config.pkg_sources.app.auto']
source_opts = [InputOption(id_='auto', value=None, label=lb_source_auto)]
source_opts.extend((InputOption(id_=a, value=a, label=a) for a in self.known_sources_apps if which(a)))
source_auto_tip = self._i18n['debian.config.pkg_sources.app.tip'].format(auto=f'"{lb_source_auto}"')
input_sources = SingleSelectComponent(id_='pkg_sources.app',
label=self._i18n['debian.config.pkg_sources.app'],
tooltip=source_auto_tip,
options=source_opts,
default_option=next(o for o in source_opts if o.value == sources_app),
type_=SelectViewType.COMBO,
max_width=200)
try:
app_cache_exp = int(config_.get('index_apps.exp', 0))
except ValueError:
self._log.error(f"Unexpected value form Debian configuration property 'index_apps.exp': "
f"{config_['index_apps.exp']}. Zero (0) will be considered instead.")
app_cache_exp = 0
ti_index_apps_exp = TextInputComponent(id_='index_apps.exp',
label=self._i18n['debian.config.index_apps.exp'],
tooltip=self._i18n['debian.config.index_apps.exp.tip'],
value=str(app_cache_exp), only_int=True,
max_width=60)
try:
sync_pkgs_time = int(config_.get('sync_pkgs.time', 0))
except ValueError:
self._log.error(f"Unexpected value form Debian configuration property 'sync_pkgs.time': {config_['sync_pkgs.time']}. "
f"Zero (0) will be considered instead.")
sync_pkgs_time = 0
ti_sync_pkgs = TextInputComponent(id_='sync_pkgs.time',
label=self._i18n['debian.config.sync_pkgs.time'],
tooltip=self._i18n['debian.config.sync_pkgs.time.tip'],
value=str(sync_pkgs_time), only_int=True,
max_width=60)
try:
suggestions_exp = int(config_.get('suggestions.exp', 0))
except ValueError:
self._log.error(f"Unexpected value form Debian configuration property 'suggestions.exp': {config_['suggestions.exp']}. "
f"Zero (0) will be considered instead.")
suggestions_exp = 0
ti_suggestions_exp = TextInputComponent(id_='suggestions.exp',
label=self._i18n['debian.config.suggestions.exp'],
tooltip=self._i18n['debian.config.suggestions.exp.tip'],
value=str(suggestions_exp), only_int=True,
max_width=60)
panel = PanelComponent([FormComponent([input_sources, sel_purge, ti_sync_pkgs, ti_index_apps_exp,
ti_suggestions_exp])])
yield SettingsView(self, panel)
def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]:
config_ = self.configman.get_config()
container = component.get_component_by_idx(0, FormComponent)
for prop, type_ in {'remove.purge': SingleSelectComponent,
'pkg_sources.app': SingleSelectComponent,
'index_apps.exp': TextInputComponent,
'sync_pkgs.time': TextInputComponent,
'suggestions.exp': TextInputComponent}.items():
comp = container.get_component(prop, type_)
val = None
if isinstance(comp, SingleSelectComponent):
val = comp.get_selected()
elif isinstance(comp, TextInputComponent):
val = comp.get_int_value()
config_[prop] = val
try:
self.configman.save_config(config_)
return True, None
except:
return False, [traceback.format_exc()]
def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]:
if self._default_actions is None:
self._default_actions = (CustomSoftwareAction(i18n_label_key='debian.action.sync_pkgs',
i18n_status_key='debian.task.sync_pkgs.status',
i18n_description_key='debian.action.sync_pkgs.desc',
icon_path=DEBIAN_ICON_PATH,
manager_method='synchronize_packages',
requires_root=True),
CustomSoftwareAction(i18n_label_key='debian.action.index_apps',
i18n_status_key='debian.task.app_index.status',
i18n_description_key='debian.action.index_apps.desc',
icon_path=DEBIAN_ICON_PATH,
manager_method='index_apps',
requires_root=False)
)
yield from self._default_actions
for _ in self.get_installed_source_apps():
yield self.action_launch_sources
break
def _write_ignored_updates(self, packages: Iterable[str]):
Path(os.path.dirname(self.file_ignored_updates)).mkdir(parents=True, exist_ok=True)
with open(self.file_ignored_updates, 'w+') as f:
f.write('\n'.join(n for n in sorted(packages)))
def ignore_update(self, pkg: DebianPackage):
ignored_packages = set()
self._fill_ignored_updates(ignored_packages)
if pkg.name not in ignored_packages:
pkg.updates_ignored = True
ignored_packages.add(pkg.name)
self._write_ignored_updates(ignored_packages)
def revert_ignored_update(self, pkg: DebianPackage):
ignored_packages = set()
self._fill_ignored_updates(ignored_packages)
if pkg.name in ignored_packages:
pkg.updates_ignored = False
ignored_packages.remove(pkg.name)
self._write_ignored_updates(ignored_packages)
def synchronize_packages(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
return SynchronizePackages(i18n=self._i18n, logger=self._log, root_password=root_password, watcher=watcher,
aptitude=self.aptitude, taskman=TaskManager()).run()
def index_apps(self, root_password: Optional[str], watcher: Optional[ProcessWatcher],
taskman: Optional[TaskManager] = None, deb_config: Optional[dict] = None,
check_expiration: bool = False) -> bool:
_config = deb_config if deb_config else self.configman.get_config()
_taskman = taskman if taskman else TaskManager()
map_apps = MapApplications(taskman=_taskman, app_indexer=self.app_indexer,
i18n=self._i18n, logger=self._log, deb_config=deb_config,
app_mapper=self.app_mapper, check_expiration=check_expiration,
watcher=watcher)
map_apps.start()
gen_app_index = UpdateApplicationIndex(taskman=_taskman, app_indexer=self.app_indexer,
i18n=self._i18n, logger=self._log,
mapping_apps=map_apps)
gen_app_index.start()
map_apps.join()
self._update_apps_index(map_apps.apps)
gen_app_index.join()
return True
def purge(self, pkg: DebianPackage, root_password: str, watcher: ProcessWatcher) -> bool:
if not self.view.confirm_purge(pkg.name, watcher):
return False
res = self.uninstall(pkg=pkg, root_password=root_password, watcher=watcher, disk_loader=None, purge=True)
return res.success
def launch_sources_app(self, root_password: str, watcher: ProcessWatcher) -> bool:
deb_config = self.configman.get_config()
sources_app = deb_config.get('pkg_sources.app')
if isinstance(sources_app, str) and sources_app not in self.known_sources_apps:
watcher.show_message(title=self._i18n['popup.title.error'],
body=self._i18n['debian.action.sources.unsupported'].format(app=bold(sources_app)))
return False
if sources_app:
if not which(sources_app):
watcher.show_message(title=self._i18n['popup.title.error'],
body=self._i18n['debian.action.sources.not_installed'],
type_=MessageType.ERROR)
return False
Popen(sources_app, shell=True)
return True
for app in self.get_installed_source_apps():
Popen(app, shell=True)
return True
watcher.show_message(title=self._i18n['popup.title.error'],
body=self._i18n['debian.action.sources.not_installed'],
type_=MessageType.ERROR)
return False
@property
def app_indexer(self) -> ApplicationIndexer:
if self._app_indexer is None:
self._app_indexer = ApplicationIndexer(self._log)
return self._app_indexer
@property
def apps_index(self) -> Dict[str, DebianApplication]:
if self._apps_index is None:
self._update_apps_index(self.app_indexer.read_index())
return self._apps_index
@property
def install_show_attrs(self) -> Set[str]:
if self._install_show_attrs is None:
self._install_show_attrs = {'description', 'maintainer', 'section', 'compressed size'}
return self._install_show_attrs
@property
def configman(self) -> DebianConfigManager:
if self._configman is None:
self._configman = DebianConfigManager()
return self._configman
@property
def view(self) -> DebianViewBridge:
if self._view is None:
self._view = DebianViewBridge(screen_width=self.context.screen_width,
screen_heigth=self.context.screen_height,
i18n=self._i18n)
return self._view
@property
def app_mapper(self) -> ApplicationsMapper:
if self._app_mapper is None:
self._app_mapper = ApplicationsMapper(logger=self._log)
return self._app_mapper
@property
def aptitude(self) -> Aptitude:
if self._aptitude is None:
self._aptitude = Aptitude(self._log)
return self._aptitude
@property
def output_handler(self) -> AptitudeOutputHandlerFactory:
if self._output_handler is None:
self._output_handler = AptitudeOutputHandlerFactory(i18n=self._i18n)
return self._output_handler
@property
def action_launch_sources(self) -> CustomSoftwareAction:
if self._action_launch_sources is None:
self._action_launch_sources = CustomSoftwareAction(i18n_label_key='debian.action.sources',
i18n_status_key='debian.task.sources.status',
i18n_description_key='debian.action.sources.desc',
icon_path=DEBIAN_ICON_PATH,
manager_method='launch_sources_app',
requires_confirmation=False,
requires_root=False)
return self._action_launch_sources
@property
def known_sources_apps(self) -> Tuple[str, ...]:
if self._known_sources_apps is None:
self._known_sources_apps = ('software-properties-gtk',)
return self._known_sources_apps
@property
def file_ignored_updates(self) -> str:
if self._file_ignored_updates is None:
self._file_ignored_updates = f'{CONFIG_DIR}/debian/updates_ignored.txt'
return self._file_ignored_updates
@property
def suggestions_downloader(self) -> DebianSuggestionsDownloader:
if not self._suggestions_downloader:
self._suggestions_downloader = DebianSuggestionsDownloader(i18n=self._i18n, logger=self._log,
http_client=self.context.http_client)
return self._suggestions_downloader
def get_installed_source_apps(self) -> Generator[str, None, None]:
for app in self.known_sources_apps:
if shutil.which(app):
yield app