Remove Debian support from Bearhub

This commit is contained in:
Sebastian Palencsar
2026-05-26 08:58:17 +02:00
parent 3d41d6a858
commit 68051b2b34
31 changed files with 2 additions and 3953 deletions

View File

@@ -1,12 +0,0 @@
import os
from bauh.api.paths import CACHE_DIR, CONFIG_DIR
from bauh.commons import resource
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
DEBIAN_CACHE_DIR = f'{CACHE_DIR}/debian'
APP_INDEX_FILE = f'{DEBIAN_CACHE_DIR}/apps_idx.json'
CONFIG_FILE = f'{CONFIG_DIR}/debian.yml'
PACKAGE_SYNC_TIMESTAMP_FILE = f'{DEBIAN_CACHE_DIR}/sync_pkgs.ts'
DEBIAN_ICON_PATH = resource.get_path('img/debian.svg', ROOT_DIR)

View File

@@ -1,430 +0,0 @@
import re
import time
from contextlib import contextmanager
from enum import Enum
from logging import Logger
from math import ceil
from queue import Queue
from threading import Thread
from typing import Iterable, Optional, Pattern, Dict, Set, Tuple, Generator, Collection
from bauh.api.abstract.handler import ProcessWatcher
from bauh.commons import system
from bauh.commons.html import bold
from bauh.commons.system import SimpleProcess
from bauh.commons.util import size_to_byte
from bauh.gems.debian.common import strip_maintainer_email, strip_section
from bauh.gems.debian.model import DebianPackage, DebianTransaction
from bauh.view.util.translation import I18n
def map_package_name(string: str):
name_split = string.split(':')
if len(name_split) > 2:
return ':'.join(name_split[0:-1])
return name_split[0]
class AptitudeAction(Enum):
INSTALL = 0
UPGRADE = 1
REMOVE = 3
class Aptitude:
def __init__(self, logger: Logger):
self._log = logger
self._re_show_attr: Optional[Pattern] = None
self._env: Optional[Dict[str, str]] = None
self._list_attrs: Optional[Set[str]] = None
self._re_to_install: Optional[Pattern] = None
self._re_transaction_pkg: Optional[Pattern] = None
self._size_attrs: Optional[Tuple[str]] = None
self._default_lang = ''
self._ignored_fields: Optional[Set[str]] = None
self._re_none: Optional[Pattern] = None
self._vars_fixes: Optional[Dict[str, str]] = None
self._preserve_env = {'DEBIAN_FRONTEND'}
def show(self, pkgs: Iterable[str], attrs: Optional[Collection[str]] = None, verbose: bool = False) \
-> Optional[Dict[str, Dict[str, object]]]:
if pkgs:
force_verbose = verbose if verbose else ('compressed size' in attrs if attrs else False)
code, output = system.execute(f"aptitude show -q {' '.join(pkgs)}{' -v' if force_verbose else ''}",
shell=True, custom_env=self.env)
if code == 0 and output:
info, pkg = dict(), None
for field, val in self.re_show_attr.findall('\n' + output):
final_field, final_val = field.strip().lower(), val.strip()
if final_field == 'package':
pkg = {}
info[final_val] = pkg
elif final_field not in self.ignored_fields and (not attrs or final_field in attrs):
if final_val:
if final_field in self.list_attrs:
final_val = tuple((v.strip() for v in final_val.split(',') if v))
elif final_field in self.size_attrs:
size_split = final_val.split(' ')
if len(size_split) >= 1:
unit = size_split[1].upper() if len(size_split) >= 2 else 'B'
final_val = size_to_byte(size_split[0], unit, self._log)
else:
self._log.warning(f"Unhandled value ({val}) for attribute '{field}'")
final_val = None
pkg[final_field] = final_val
return info
def simulate_removal(self, packages: Iterable[str], purge: bool = False) -> Optional[DebianTransaction]:
code, output = system.execute(self.gen_remove_cmd(packages, purge, simulate=True), shell=True,
custom_env=self.env)
if code == 0 and output:
return self.map_transaction_output(output)
def map_transaction_output(self, output: str) -> DebianTransaction:
to_install, to_upgrade, to_remove = None, None, None
current_collection = None
for line in output.split('\n'):
if line.startswith('The following NEW packages will be installed:'):
to_install = set()
current_collection = to_install
elif line.startswith('The following packages will be upgraded:'):
to_upgrade = set()
current_collection = to_upgrade
elif line.startswith('The following packages will be REMOVED:'):
to_remove = set()
current_collection = to_remove
elif line.startswith('Would download/install/remove packages'):
break
elif current_collection is not None and line.startswith(' '):
for n, _, v, __, lv, ___, size in self.re_transaction_pkg.findall(line):
pkg = DebianPackage(name=n, version=v, latest_version=lv if lv else v, transaction_size=0)
if size:
size_split = size.strip().split(' ')
unit = size_split[1][0].upper() if len(size_split) >= 2 else 'B'
pkg.transaction_size = size_to_byte(size_split[0], unit, self._log)
current_collection.add(pkg)
return DebianTransaction(to_install=tuple(to_install) if to_install else tuple(),
to_remove=tuple(to_remove) if to_remove else tuple(),
to_upgrade=tuple(to_upgrade) if to_upgrade else tuple())
def simulate_upgrade(self, packages: Iterable[str]) -> DebianTransaction:
code, output = system.execute(self.gen_transaction_cmd('upgrade', packages, simulate=True),
shell=True, custom_env=self.env)
if code == 0 and output:
return self.map_transaction_output(output)
def upgrade(self, packages: Iterable[str], root_password: Optional[str]) -> SimpleProcess:
cmd = self.gen_transaction_cmd('upgrade', packages).split(' ')
return SimpleProcess(cmd=cmd, shell=True, root_password=root_password, extra_env=self.vars_fixes,
preserve_env=self._preserve_env)
def update(self, root_password: Optional[str]) -> SimpleProcess:
return SimpleProcess(('aptitude', 'update'), root_password=root_password, shell=True)
def simulate_installation(self, packages: Iterable[str]) -> Optional[DebianTransaction]:
code, output = system.execute(self.gen_transaction_cmd('install', packages, simulate=True),
shell=True, custom_env=self.env)
if code == 0 and output:
return self.map_transaction_output(output)
def install(self, packages: Iterable[str], root_password: Optional[str]) -> SimpleProcess:
cmd = self.gen_transaction_cmd('install', packages).split(' ')
return SimpleProcess(cmd=cmd, root_password=root_password, extra_env=self.vars_fixes,
preserve_env=self._preserve_env)
def read_installed(self) -> Generator[DebianPackage, None, None]:
yield from self.search(query='~i')
def read_updates(self) -> Generator[Tuple[str, str], None, None]:
_, output = system.execute(f"aptitude search ~U -q -F '%p^%V' --disable-columns --no-gui",
shell=True,
custom_env=self.env)
if output:
for line in output.split('\n'):
line_split = line.strip().split('^')
if len(line_split) == 2 and line_split[1] != '<none>':
yield line_split[0], line_split[1]
def search(self, query: str, fill_size: bool = False) -> Generator[DebianPackage, None, None]:
attrs = f"%p^%v^%V^%m^%s^{'%I^' if fill_size else ''}%d"
_, output = system.execute(f"aptitude search {query} -q -F '{attrs}' --disable-columns", shell=True)
if output:
no_attrs = 7 if fill_size else 6
for line in output.split('\n'):
line_split = line.strip().split('^', maxsplit=no_attrs - 1)
if len(line_split) == no_attrs:
latest_version = line_split[2] if not self.re_none.match(line_split[2]) else None
size = None
if fill_size:
size_split = line_split[no_attrs - 2].split(' ')
unit = size_split[1][0].upper() if len(size_split) >= 2 else 'B'
size = size_to_byte(size_split[0], unit, self._log)
if latest_version is not None:
installed_version = line_split[1] if not self.re_none.match(line_split[1]) else None
section = strip_section(line_split[4])
yield DebianPackage(name=line_split[0],
version=installed_version if installed_version else latest_version,
latest_version=latest_version,
installed=bool(installed_version),
update=installed_version is not None and installed_version != latest_version,
maintainer=strip_maintainer_email(line_split[3]),
categories=(section,) if section else None,
uncompressed_size=size,
description=line_split[no_attrs - 1])
def search_by_name(self, names: Iterable[str], fill_size: bool = False) -> Generator[DebianPackage, None, None]:
query = f"'({'|'.join(f'?exact-name({n})' for n in names)})'"
yield from self.search(query=query, fill_size=fill_size)
def remove(self, packages: Iterable[str], root_password: Optional[str], purge: bool = False) -> SimpleProcess:
return SimpleProcess(cmd=self.gen_remove_cmd(packages, purge).split(' '), shell=True,
root_password=root_password, extra_env=self.vars_fixes, preserve_env=self._preserve_env)
def read_installed_names(self) -> Generator[str, None, None]:
code, output = system.execute("aptitude search ~i -q -F '%p' --disable-columns",
shell=True,
custom_env=self.env)
if output:
for line in output.split('\n'):
if line:
yield line
@property
def re_show_attr(self) -> Pattern:
if self._re_show_attr is None:
self._re_show_attr = re.compile(r'(\n\w+[a-zA-Z0-9\-\s]*):\s+(.+)')
return self._re_show_attr
@property
def env(self) -> Dict[str, str]:
if self._env is None:
self._env = system.gen_env(global_interpreter=system.USE_GLOBAL_INTERPRETER)
self._env['LC_NUMERIC'] = ''
return self._env
@property
def list_attrs(self) -> Set[str]:
if self._list_attrs is None:
self._list_attrs = {'depends', 'provides', 'replaces', 'recommends', 'suggests', 'conflicts',
'state', 'predepends', 'breaks'}
return self._list_attrs
@property
def re_transaction_pkg(self) -> Pattern:
if self._re_transaction_pkg is None:
self._re_transaction_pkg = re.compile(r'([a-zA-Z0-9\-_@~.+:]+)({\w+})?\s*\[([a-zA-Z0-9\-_@~.+:]+)'
r'(\s+->\s+([a-zA-Z0-9\-_@~.+:]+))?](\s*<([\-+]?[0-9.,]+\s+\w+)>)?')
return self._re_transaction_pkg
@property
def size_attrs(self) -> Tuple[str]:
if self._size_attrs is None:
self._size_attrs = {'compressed size', 'uncompressed size'}
return self._size_attrs
@property
def ignored_fields(self) -> Set[str]:
if self._ignored_fields is None:
self._ignored_fields = {'sha1', 'sha256', 'sha512', 'checksum-filesize'}
return self._ignored_fields
@classmethod
def gen_remove_cmd(cls, packages: Iterable[str], purge: bool, simulate: bool = False) -> str:
return cls.gen_transaction_cmd(type_='purge' if purge else 'remove', packages=packages,
simulate=simulate)
@staticmethod
def gen_transaction_cmd(type_: str, packages: Iterable[str], simulate: bool = False,
delete_unused: bool = False) -> str:
return f"aptitude {type_} -q -y --no-gui --full-resolver {' '.join(packages)}" \
f" -o Aptitude::ProblemResolver::RemoveScore=9999999" \
f" -o Aptitude::ProblemResolver::EssentialRemoveScore=9999999" \
f" -o Aptitude::Delete-Unused={str(delete_unused).lower()}" \
f"{' -V -s -Z' if simulate else ''}"
@property
def re_none(self) -> Pattern:
if self._re_none is None:
self._re_none = re.compile(r'^<\w+>$')
return self._re_none
@property
def vars_fixes(self) -> Dict[str, str]:
if self._vars_fixes is None:
self._vars_fixes = {'LC_NUMERIC': '', 'DEBIAN_FRONTEND': 'noninteractive'}
return self._vars_fixes
class AptitudeOutputHandler(Thread):
def __init__(self, i18n: I18n, targets: Iterable[str], re_download: Pattern, watcher: ProcessWatcher,
action: AptitudeAction):
super(AptitudeOutputHandler, self).__init__()
self._i18n = i18n
self._re_download = re_download
self._watcher = watcher
self._targets = set(targets) if targets is not None else None
self._unpacking = 0
self._removing = 0
self._downloading = 0
self._to_process = Queue()
self._work = True
self._action = action
def stop_working(self):
self._work = False
def handle(self, string: str):
self._to_process.put(string)
@property
def total_targets(self) -> int:
return len(self._targets) if self._targets else 0
@property
def processed(self) -> int:
return self._removing if self._action == AptitudeAction.REMOVE else self._unpacking
def _get_progress(self, current: int) -> str:
if self.total_targets > 0:
if self._action == AptitudeAction.REMOVE:
total = self._removing
else:
if self.total_targets == self._unpacking:
total = self.total_targets
else:
total = ceil((self._unpacking + self._downloading) / 2)
return f'({total / self.total_targets * 100:.2f}%) [{current}/{self.total_targets}] '
return ''
def run(self):
while self._work:
time.sleep(0.001)
if self._to_process.empty():
continue
string = self._to_process.get()
if self.total_targets > 0 and self.total_targets == self._unpacking:
self._watcher.change_substatus(self._i18n['debian.output.finishing'])
continue
if string:
if self._action != AptitudeAction.REMOVE and string.startswith('Unpacking '):
unpacking = string.split(' ')
if len(unpacking) >= 2 and unpacking[1]:
pkg = map_package_name(unpacking[1].strip())
if self._targets and pkg in self._targets:
self._unpacking += 1
msg = f"{self._get_progress(self._unpacking)}" \
f"{self._i18n['debian.output.unpacking'].format(pkg=bold(pkg))}"
self._watcher.change_substatus(msg)
continue
if self._action == AptitudeAction.REMOVE and string.startswith('Removing '):
unpacking = string.split(' ')
if len(unpacking) >= 2 and unpacking[1]:
pkg = unpacking[1].strip()
if self._targets and pkg in self._targets:
self._removing += 1
msg = f"{self._get_progress(self._removing)}" \
f"{self._i18n['debian.output.removing'].format(pkg=bold(pkg))}"
self._watcher.change_substatus(msg)
continue
download = self._re_download.findall(string)
if download:
data = download[0].split(' ')
if len(data) >= 4:
pkg = data[3].strip()
if self._targets and pkg in self._targets:
self._downloading += 1
msg = f"{self._get_progress(self._downloading)}" \
f"{self._i18n['debian.output.downloading'].format(pkg=bold(pkg))}"
self._watcher.change_substatus(msg)
continue
_processed = self.processed
if self._targets and _processed > 0:
self._watcher.change_substatus(self._get_progress(_processed).strip())
continue
self._watcher.change_substatus(' ')
class AptitudeOutputHandlerFactory:
def __init__(self, i18n: I18n):
self._i18n = i18n
self._re_download: Optional[Pattern] = None
@property
def re_download(self) -> Pattern:
if self._re_download is None:
self._re_download = re.compile(r'Get:\s+\d+\s+https?://(.+)')
return self._re_download
@contextmanager
def start(self, watcher: ProcessWatcher, targets: Iterable[str], action: AptitudeAction):
handler = AptitudeOutputHandler(i18n=self._i18n, re_download=self.re_download,
watcher=watcher, targets=targets, action=action)
handler.start()
yield handler.handle
handler.stop_working()
handler.join()

View File

@@ -1,32 +0,0 @@
from typing import Dict, Optional
from bauh.gems.debian.model import DebianPackage
def strip_maintainer_email(maintainer: str) -> str:
return maintainer.split('<')[0].strip()
def strip_section(section: str) -> Optional[str]:
if section:
section_split = section.split('/')
return section_split[1] if len(section_split) > 1 else section
def fill_show_data(pkg: DebianPackage, data: Dict[str, object]):
if data:
for attr, val in data.items():
final_attr = attr.replace(' ', '_')
if not val or val == '<none>':
final_val = None
else:
if attr == 'maintainer':
final_val = strip_maintainer_email(str(val))
elif attr == 'section':
final_attr = 'categories'
final_val = (strip_section(str(val)),)
else:
final_val = val
setattr(pkg, final_attr, final_val)

View File

@@ -1,17 +0,0 @@
from bauh.commons.config import YAMLConfigManager
from bauh.gems.debian import CONFIG_FILE
class DebianConfigManager(YAMLConfigManager):
def __init__(self):
super(DebianConfigManager, self).__init__(config_file_path=CONFIG_FILE)
def get_default_config(self) -> dict:
return {
'suggestions.exp': 24, # hours
'index_apps.exp': 1440, # 24 hours
'sync_pkgs.time': 1440, # 24 hours
'pkg_sources.app': None,
'remove.purge': False
}

View File

@@ -1,903 +0,0 @@
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, ViewComponentAlignment
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_,), daemon=True)
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_,), daemon=True)
fill_config.start()
ignored_updates = set()
fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,), daemon=True)
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,), daemon=True)
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 self.suggestions_downloader.should_download(deb_config, 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,), daemon=True)
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,), daemon=True)
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_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),
alignment=ViewComponentAlignment.CENTER,
type_=SelectViewType.COMBO)
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)
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)
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)
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 Exception:
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:
file_url = self.context.get_suggestion_url(self.__module__)
self._suggestions_downloader = DebianSuggestionsDownloader(i18n=self._i18n, logger=self._log,
http_client=self.context.http_client,
file_url=file_url)
if self._suggestions_downloader.is_local_suggestions_file():
self._log.info(f"Local Debian suggestions file mapped: {file_url}")
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

View File

@@ -1,142 +0,0 @@
from operator import attrgetter
from typing import Collection, Optional, Tuple, List
from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.view import InputOption, MultipleSelectComponent, TextComponent
from bauh.commons.html import bold
from bauh.commons.view_utils import get_human_size_str
from bauh.gems.debian import DEBIAN_ICON_PATH
from bauh.gems.debian.model import DebianPackage
from bauh.view.util.translation import I18n
class DebianViewBridge:
def __init__(self, screen_width: int, screen_heigth: int, i18n: I18n):
self._i18n = i18n
self._width = screen_width
self._height = screen_heigth
@staticmethod
def _map_to_install(pkgs: Optional[Collection[DebianPackage]]) -> Optional[Tuple[List[InputOption], str, str]]:
if pkgs:
download_size, install_size = 0, 0
views = []
for p in pkgs:
if p.compressed_size is not None and p.compressed_size >= 0:
compressed = get_human_size_str(p.compressed_size)
download_size += p.compressed_size
else:
compressed = '?'
if p.transaction_size is not None:
install_size += p.transaction_size
uncompressed = get_human_size_str(p.transaction_size)
else:
uncompressed = '?'
views.append(InputOption(label=f"{p.name} ({uncompressed} | {compressed})",
value=p.name, read_only=True, icon_path=DEBIAN_ICON_PATH,
tooltip=p.description if p.description else '?'))
dsize = get_human_size_str(download_size) if download_size > 0 else '?'
isize = get_human_size_str(install_size) if install_size > 0 else '?'
return views, isize, dsize
def _map_to_remove(self, pkgs: Optional[Collection[DebianPackage]]) -> Optional[Tuple[List[InputOption], str]]:
if pkgs:
freed_space = 0
views = []
for p in pkgs:
if p.transaction_size is not None:
size = p.transaction_size * (-1 if p.transaction_size < 0 else 1)
freed_space += size
uncompressed = get_human_size_str(size)
else:
uncompressed = '?'
views.append(InputOption(label=f"{p.name} (-{uncompressed})",
value=p.name, read_only=True, icon_path=DEBIAN_ICON_PATH,
tooltip=p.description if p.description else '?'))
return views, f'-{get_human_size_str(freed_space)}' if freed_space > 0 else '?'
def confirm_transaction(self, to_install: Optional[Collection[DebianPackage]],
removal: Optional[Collection[DebianPackage]],
watcher: ProcessWatcher) -> bool:
components = []
to_remove_data = self._map_to_remove(removal)
text_width, select_width = 672, 595
if to_remove_data:
to_remove_data[0].sort(key=attrgetter('label'))
lb_rem = self._i18n['debian.transaction.to_remove'].format(no=bold(str(len(to_remove_data[0]))),
fspace=bold(to_remove_data[1]))
components.append(TextComponent(html=lb_rem, min_width=text_width))
components.append(MultipleSelectComponent(id_='rem', options=to_remove_data[0], label=None,
default_options={*to_remove_data[0]},
max_width=select_width))
to_install_data = self._map_to_install(to_install)
if to_install_data:
to_install_data[0].sort(key=attrgetter('label'))
lb_deps = self._i18n['debian.transaction.to_install'].format(no=bold(str(len(to_install_data[0]))),
dsize=bold(to_install_data[2]),
isize=bold(to_install_data[1]))
components.append(TextComponent(html=f'<br/>{lb_deps}', min_width=text_width))
components.append(MultipleSelectComponent(id_='inst', label='', options=to_install_data[0],
default_options={*to_install_data[0]},
max_width=select_width))
return watcher.request_confirmation(title=self._i18n['debian.transaction.title'],
components=components,
confirmation_label=self._i18n['popup.button.continue'],
deny_label=self._i18n['popup.button.cancel'],
body=None,
min_width=text_width,
min_height=54)
def confirm_removal(self, source_pkg: str, dependencies: Collection[DebianPackage], watcher: ProcessWatcher) -> bool:
dep_views = []
freed_space = 0
for p in sorted(dependencies, key=attrgetter('name')):
if p.transaction_size is not None:
size = p.transaction_size * (-1 if p.transaction_size < 0 else 1)
freed_space += size
size_str = get_human_size_str(size)
else:
size_str = '?'
dep_views.append(InputOption(label=f"{p.name}: -{size_str}", value=p.name, read_only=True,
icon_path=DEBIAN_ICON_PATH, tooltip=p.description))
deps_container = MultipleSelectComponent(id_='deps', label='', options=dep_views, default_options={*dep_views},
max_width=537)
freed_space_str = bold('-' + get_human_size_str(freed_space))
body_text = TextComponent(html=self._i18n['debian.remove_deps'].format(no=bold(str(len(dependencies))),
pkg=bold(source_pkg),
fspace=freed_space_str),
min_width=653)
return watcher.request_confirmation(title=self._i18n['debian.transaction.title'],
components=[body_text, deps_container],
confirmation_label=self._i18n['popup.button.continue'],
deny_label=self._i18n['popup.button.cancel'],
min_height=200,
body=None)
def confirm_purge(self, pkg_name: str, watcher: ProcessWatcher) -> bool:
msg = self._i18n['debian.action.purge.confirmation'].format(pkg=bold(pkg_name))
return watcher.request_confirmation(title=self._i18n['debian.action.purge'],
body=msg,
confirmation_label=self._i18n['popup.button.continue'])

View File

@@ -1,223 +0,0 @@
import json
import os
import re
import traceback
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta, timezone
from json import JSONDecodeError
from logging import Logger
from pathlib import Path
from typing import Optional, Set, Generator, Iterable
from bauh.commons import system
from bauh.gems.debian import APP_INDEX_FILE
from bauh.gems.debian.model import DebianApplication
class ApplicationIndexError(Exception):
def __init__(self, cause: Optional[str] = None):
self.cause = cause
class ApplicationIndexer:
def __init__(self, logger: Logger, index_file_path: str = APP_INDEX_FILE):
self._log = logger
self._file_path = index_file_path
self._file_timestamp_path = f'{self._file_path}.ts'
def is_expired(self, deb_config: dict) -> bool:
try:
exp_minutes = int(deb_config.get('index_apps.exp', 0))
except ValueError:
self._log.error(f"Invalid value for Debian configuration property 'index_apps.exp': "
f"{deb_config['index_apps.exp']}")
return True
if exp_minutes <= 0:
self._log.warning("Debian applications index will always be updated ('index_apps.exp' <= 0 )'")
return True
if not os.path.exists(self._file_path):
self._log.info(f"Debian applications index not found. A new one must be generated ({self._file_path})")
return True
try:
with open(self._file_timestamp_path) as f:
timestamp_str = f.read().strip()
except FileNotFoundError:
self._log.info(f"Debian applications index timestamp not found ({self._file_timestamp_path})")
return True
try:
timestamp = datetime.fromtimestamp(float(timestamp_str), tz=timezone.utc)
except Exception:
self._log.error(f'Could not parse the Debian applications index timestamp: {timestamp_str} '
f'({self._file_timestamp_path})')
traceback.print_exc()
return True
expired = timestamp + timedelta(minutes=exp_minutes) <= datetime.now(timezone.utc)
if expired:
self._log.info("Debian applications index has expired. A new one must be generated.")
else:
self._log.info("Debian applications index is up-to-date")
return expired
def read_index(self) -> Generator[DebianApplication, None, None]:
try:
with open(self._file_path) as f:
idx_str = f.read().strip()
if not idx_str:
self._log.warning(f"Debian applications index is empty ({self._file_path})")
else:
try:
for name, data in json.loads(idx_str).items():
exe_path, icon_path = data.get('exe_path'), data.get('icon_path')
if not all((name, exe_path, icon_path)):
self._log.warning(f"Invalid entry in the Debian applications index ({self._file_path}): name={name}, exe_path={exe_path}, icon_path={icon_path}")
else:
categories = data.get('categories')
yield DebianApplication(name=name, exe_path=exe_path, icon_path=icon_path,
categories=tuple(categories) if categories else None)
except JSONDecodeError:
self._log.error(f"The Debian applications index is corrupted ({self._file_path}). "
f"Could not decode the JSON.")
except FileNotFoundError:
self._log.warning(f"Debian applications index not found ({self._file_path})")
except OSError as e:
self._log.error(f"Debian applications index could not be read ({self._file_path}). OSError: {e.errno}")
def update_index(self, apps: Set[DebianApplication], update_timestamp: bool = True):
idx_dir = os.path.dirname(self._file_path)
try:
Path(idx_dir).mkdir(exist_ok=True, parents=True)
except OSError:
self._log.error(f"Could not create directory '{idx_dir}'")
raise ApplicationIndexError()
idx = {}
if apps:
for app in apps:
idx.update(app.to_index())
try:
with open(self._file_path, 'w+') as f:
if idx:
f.write(json.dumps(idx, sort_keys=True, indent=4))
else:
f.write('')
except OSError:
self._log.error(f"Could not write to the Debian applications index file: {self._file_path}")
raise ApplicationIndexError()
if update_timestamp:
index_timestamp = datetime.now(timezone.utc).timestamp()
try:
with open(self._file_timestamp_path, 'w+') as f:
f.write(str(index_timestamp))
self._log.info("Debian applications index timestamp updated")
except OSError:
self._log.error(f"Could not write to the Debian applications index timestamp file: "
f"{self._file_timestamp_path}")
raise ApplicationIndexError()
class ApplicationsMapper:
def __init__(self, logger: Logger, workers: int = 10):
self._log = logger
self._re_desktop_file = re.compile(r'(.+):\s+(/usr/share/applications/.+\.desktop)')
self._re_desktop_file_fields = re.compile(r'(Exec|TryExec|Icon|Categories|NoDisplay|Terminal)\s*=\s*(.+)')
self._workers = workers
def _read_file(self, file_path: str) -> Optional[str]:
try:
with open(file_path) as file:
return file.read()
except (FileNotFoundError, OSError) as e:
self._log.error(f"Error when checking desktop file '{file_path}' ({file_path}):"
f" {e.__class__.__name__}")
def _add_if_application_desktop_file(self, pkg_name: str, desktop_files: Iterable[str], output: Set[DebianApplication]):
for file_path in sorted(desktop_files):
content = self._read_file(file_path)
if content:
data = {}
gui_app = True
for f, v in self._re_desktop_file_fields.findall(content):
if f in ('NoDisplay', 'Terminal') and v.strip().lower() == 'true':
gui_app = False
break
if f not in data:
data[f.strip()] = v.strip()
if not gui_app:
continue
exe = data.get('Exec')
if not exe:
exe = data.get('TryExec')
if not exe:
continue
icon = data.get('Icon')
if not icon:
continue
categories = data.get('Categories')
if categories:
categories = tuple(sorted({c.strip() for c in categories.split(';') if c}))
output.add(DebianApplication(name=pkg_name, exe_path=exe, icon_path=icon, categories=categories))
break
def map_executable_applications(self) -> Optional[Set[DebianApplication]]:
exitcode, output = system.execute('dpkg-query -S .desktop', shell=True)
if exitcode == 0 and output:
pkg_files = dict()
for found in self._re_desktop_file.findall(output):
pkg_name = found[0].strip()
files = pkg_files.get(pkg_name)
if files is None:
files = set()
pkg_files[pkg_name] = files
files.add(found[1].strip())
if pkg_files:
apps_found, check_jobs = set(), []
with ThreadPoolExecutor(self._workers) as pool:
for pkg_name, files in pkg_files.items():
check_jobs.append(pool.submit(self._add_if_application_desktop_file,
pkg_name, files, apps_found))
for job in check_jobs:
job.done()
return apps_found

View File

@@ -1,172 +0,0 @@
from typing import Optional, Tuple, Collection, Iterable, Generator
from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction
from bauh.commons import resource
from bauh.gems.debian import DEBIAN_ICON_PATH, ROOT_DIR
class DebianApplication:
"""
For packages that represent applications
"""
def __init__(self, name: str, exe_path: str, icon_path: str, categories: Optional[Tuple] = None):
self.name = name
self.exe_path = exe_path
self.icon_path = icon_path
self.categories = categories
def __eq__(self, other):
if isinstance(other, DebianApplication):
return self.__dict__ == other.__dict__
return False
def __hash__(self) -> int:
hash_sum = 0
for k, v in self.__dict__.items():
hash_sum += hash(v)
return hash_sum
def __repr__(self):
return f"{self.__class__.__name__} ({', '.join((f'{k}={v}' for k, v in self.__dict__.items()))})"
def to_index(self) -> dict:
return {self.name: {f: v for f, v in self.__dict__.items() if f != 'name'}}
class DebianPackage(SoftwarePackage):
__actions_purge: Optional[Tuple[CustomSoftwareAction, ...]] = None
@classmethod
def actions_purge(cls) -> Tuple[CustomSoftwareAction, ...]:
if cls.__actions_purge is None:
cls.__actions_purge = (CustomSoftwareAction(i18n_label_key='debian.action.purge',
i18n_status_key='debian.action.purge.status',
i18n_description_key='debian.action.purge.desc',
icon_path=resource.get_path('img/clean.svg', ROOT_DIR),
manager_method='purge',
requires_root=True,
requires_confirmation=False),)
return cls.__actions_purge
def __init__(self, name: str = None, version: Optional[str] = None, latest_version: Optional[str] = None,
description: Optional[str] = None, maintainer: Optional[str] = None, installed: bool = False,
update: bool = False, app: Optional[DebianApplication] = None, compressed_size: Optional[int] = None,
uncompressed_size: Optional[int] = None, categories: Tuple[str] = None,
updates_ignored: Optional[bool] = None, transaction_size: Optional[float] = None,
global_purge: bool = False):
super(DebianPackage, self).__init__(id=name, name=name, version=version, installed=installed,
description=description, update=update,
latest_version=latest_version if latest_version is not None else version)
self.maintainer = maintainer
self.compressed_size = compressed_size
self.uncompressed_size = uncompressed_size
self.categories = categories
self.app = app
self.bind_app(app)
self.updates_ignored = updates_ignored
self.transaction_size = transaction_size # size in bytes related to a transaction (install, upgrade, remove)
self.global_purge = global_purge # if global purge is already enabled
def bind_app(self, app: Optional[DebianApplication]):
self.app = app
if app and app.categories:
self.categories = app.categories
def has_history(self) -> bool:
return False
def has_screenshots(self) -> bool:
return False
def has_info(self) -> bool:
return True
def can_be_downgraded(self) -> bool:
return False
def get_type(self):
return 'debian'
def get_default_icon_path(self) -> str:
return self.get_type_icon_path()
def get_type_icon_path(self) -> str:
return DEBIAN_ICON_PATH
def is_application(self):
return bool(self.app)
def get_data_to_cache(self) -> dict:
pass
def fill_cached_data(self, data: dict):
pass
def can_be_run(self) -> bool:
return bool(self.app)
def get_publisher(self) -> str:
return self.maintainer
def supports_backup(self) -> bool:
return True
def get_disk_icon_path(self) -> Optional[str]:
if self.app:
return self.app.icon_path
def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]:
if self.installed and not self.global_purge:
return self.actions_purge()
def is_update_ignored(self) -> bool:
return bool(self.updates_ignored)
def supports_ignored_updates(self) -> bool:
return True
def is_trustable(self) -> bool:
return True
def __eq__(self, other):
if isinstance(other, DebianPackage):
return self.name == other.name
return False
def __hash__(self):
return hash(self.name)
def __repr__(self) -> str:
attrs = ', '.join((f'{p}={v}' for p, v in sorted(self.__dict__.items())))
return f"{self.__class__.__name__} ({attrs})"
class DebianTransaction:
def __init__(self, to_install: Optional[Collection[DebianPackage]],
to_remove: Optional[Collection[DebianPackage]],
to_upgrade: Optional[Collection[DebianPackage]]):
self.to_install = to_install
self.to_remove = to_remove
self.to_upgrade = to_upgrade
@property
def all_packages(self) -> Generator[DebianPackage, None, None]:
for pkgs in (self.to_install, self.to_remove, self.to_upgrade):
if pkgs:
yield from pkgs
def __eq__(self, other) -> bool:
return self.__dict__ == other.__dict__ if isinstance(other, DebianTransaction) else False
def __hash__(self) -> int:
return sum((hash(v)for v in self.__dict__.values()))

View File

@@ -1,158 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 512 512"
style="enable-background:new 0 0 512 512;"
xml:space="preserve"
sodipodi:docname="clean.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
id="metadata55"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs53" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="739"
id="namedview51"
showgrid="false"
inkscape:zoom="0.79135193"
inkscape:cx="322.4361"
inkscape:cy="286.90481"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1"
inkscape:document-rotation="0" />
<g
id="g4221"><g
id="g6"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
<g
id="g4"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1">
<path
d="m 496.89,15.011 c -20.015,-20.015 -52.583,-20.016 -72.598,0 L 308.353,130.95 380.951,203.548 496.89,87.609 c 20.015,-20.015 20.015,-52.583 0,-72.598 z"
id="path2"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</g><g
id="g12"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
<g
id="g10"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1">
<path
d="M 362.499,228.282 283.816,149.6 c -25.576,-25.576 -66.36,-28.074 -94.866,-5.81 l -3.621,2.828 180.151,180.151 2.828,-3.621 c 22.265,-28.505 19.766,-69.289 -5.809,-94.866 z"
id="path8"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</g><g
id="g18"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
<g
id="g16"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1">
<path
d="M 161.08,165.557 5.969,286.702 c -3.447,2.692 -5.574,6.734 -5.842,11.1 -0.267,4.367 1.351,8.637 4.444,11.731 l 49.873,49.873 82.911,-75.242 c 6.246,-5.667 15.921,-5.174 21.568,1.047 5.667,6.245 5.199,15.901 -1.046,21.568 l -81.815,74.247 55.012,55.012 74.247,-81.815 c 5.667,-6.245 15.324,-6.713 21.568,-1.046 6.247,5.692 6.713,15.324 1.046,21.568 l -75.242,82.911 49.873,49.873 c 3.093,3.093 7.364,4.711 11.731,4.444 4.366,-0.268 8.407,-2.395 11.1,-5.842 L 346.542,351.019 Z"
id="path14"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</g><g
id="g20"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g22"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g24"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g26"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g28"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g30"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g32"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g34"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g36"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g38"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g40"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g42"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g44"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g46"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g><g
id="g48"
style="fill:#ff6b68;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.98116917,0,0,0.98116917,4.8208209,4.8202575)">
</g></g>
</svg>

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" i:viewOrigin="262 450" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" width="87.041" height="108.445" viewBox="0 0 87.041 108.445" overflow="visible" enable-background="new 0 0 87.041 108.445" xml:space="preserve">
<metadata>
<variableSets xmlns="http://ns.adobe.com/Variables/1.0/">
<variableSet varSetName="binding1" locked="none">
<variables/>
<v:sampleDataSets xmlns="http://ns.adobe.com/GenericCustomNamespace/1.0/" xmlns:v="http://ns.adobe.com/Variables/1.0/"/>
</variableSet>
</variableSets>
<sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/">
<slices/>
<sliceSourceBounds y="341.555" x="262" width="87.041" height="108.445" bottomLeftOrigin="true"/>
</sfw>
</metadata>
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g>
<path i:knockout="Off" fill="#A80030" d="M51.986,57.297c-1.797,0.025,0.34,0.926,2.686,1.287 c0.648-0.506,1.236-1.018,1.76-1.516C54.971,57.426,53.484,57.434,51.986,57.297"/>
<path i:knockout="Off" fill="#A80030" d="M61.631,54.893c1.07-1.477,1.85-3.094,2.125-4.766c-0.24,1.192-0.887,2.221-1.496,3.307 c-3.359,2.115-0.316-1.256-0.002-2.537C58.646,55.443,61.762,53.623,61.631,54.893"/>
<path i:knockout="Off" fill="#A80030" d="M65.191,45.629c0.217-3.236-0.637-2.213-0.924-0.978 C64.602,44.825,64.867,46.932,65.191,45.629"/>
<path i:knockout="Off" fill="#A80030" d="M45.172,1.399c0.959,0.172,2.072,0.304,1.916,0.533 C48.137,1.702,48.375,1.49,45.172,1.399"/>
<path i:knockout="Off" fill="#A80030" d="M47.088,1.932l-0.678,0.14l0.631-0.056L47.088,1.932"/>
<path i:knockout="Off" fill="#A80030" d="M76.992,46.856c0.107,2.906-0.85,4.316-1.713,6.812l-1.553,0.776 c-1.271,2.468,0.123,1.567-0.787,3.53c-1.984,1.764-6.021,5.52-7.313,5.863c-0.943-0.021,0.639-1.113,0.846-1.541 c-2.656,1.824-2.131,2.738-6.193,3.846l-0.119-0.264c-10.018,4.713-23.934-4.627-23.751-17.371 c-0.107,0.809-0.304,0.607-0.526,0.934c-0.517-6.557,3.028-13.143,9.007-15.832c5.848-2.895,12.704-1.707,16.893,2.197 c-2.301-3.014-6.881-6.209-12.309-5.91c-5.317,0.084-10.291,3.463-11.951,7.131c-2.724,1.715-3.04,6.611-4.227,7.507 C31.699,56.271,36.3,61.342,44.083,67.307c1.225,0.826,0.345,0.951,0.511,1.58c-2.586-1.211-4.954-3.039-6.901-5.277 c1.033,1.512,2.148,2.982,3.589,4.137c-2.438-0.826-5.695-5.908-6.646-6.115c4.203,7.525,17.052,13.197,23.78,10.383 c-3.113,0.115-7.068,0.064-10.566-1.229c-1.469-0.756-3.467-2.322-3.11-2.615c9.182,3.43,18.667,2.598,26.612-3.771 c2.021-1.574,4.229-4.252,4.867-4.289c-0.961,1.445,0.164,0.695-0.574,1.971c2.014-3.248-0.875-1.322,2.082-5.609l1.092,1.504 c-0.406-2.696,3.348-5.97,2.967-10.234c0.861-1.304,0.961,1.403,0.047,4.403c1.268-3.328,0.334-3.863,0.66-6.609 c0.352,0.923,0.814,1.904,1.051,2.878c-0.826-3.216,0.848-5.416,1.262-7.285c-0.408-0.181-1.275,1.422-1.473-2.377 c0.029-1.65,0.459-0.865,0.625-1.271c-0.324-0.186-1.174-1.451-1.691-3.877c0.375-0.57,1.002,1.478,1.512,1.562 c-0.328-1.929-0.893-3.4-0.916-4.88c-1.49-3.114-0.527,0.415-1.736-1.337c-1.586-4.947,1.316-1.148,1.512-3.396 c2.404,3.483,3.775,8.881,4.404,11.117c-0.48-2.726-1.256-5.367-2.203-7.922c0.73,0.307-1.176-5.609,0.949-1.691 c-2.27-8.352-9.715-16.156-16.564-19.818c0.838,0.767,1.896,1.73,1.516,1.881c-3.406-2.028-2.807-2.186-3.295-3.043 c-2.775-1.129-2.957,0.091-4.795,0.002c-5.23-2.774-6.238-2.479-11.051-4.217l0.219,1.023c-3.465-1.154-4.037,0.438-7.782,0.004 c-0.228-0.178,1.2-0.644,2.375-0.815c-3.35,0.442-3.193-0.66-6.471,0.122c0.808-0.567,1.662-0.942,2.524-1.424 c-2.732,0.166-6.522,1.59-5.352,0.295c-4.456,1.988-12.37,4.779-16.811,8.943l-0.14-0.933c-2.035,2.443-8.874,7.296-9.419,10.46 l-0.544,0.127c-1.059,1.793-1.744,3.825-2.584,5.67c-1.385,2.36-2.03,0.908-1.833,1.278c-2.724,5.523-4.077,10.164-5.246,13.97 c0.833,1.245,0.02,7.495,0.335,12.497c-1.368,24.704,17.338,48.69,37.785,54.228c2.997,1.072,7.454,1.031,11.245,1.141 c-4.473-1.279-5.051-0.678-9.408-2.197c-3.143-1.48-3.832-3.17-6.058-5.102l0.881,1.557c-4.366-1.545-2.539-1.912-6.091-3.037 l0.941-1.229c-1.415-0.107-3.748-2.385-4.386-3.646l-1.548,0.061c-1.86-2.295-2.851-3.949-2.779-5.23l-0.5,0.891 c-0.567-0.973-6.843-8.607-3.587-6.83c-0.605-0.553-1.409-0.9-2.281-2.484l0.663-0.758c-1.567-2.016-2.884-4.6-2.784-5.461 c0.836,1.129,1.416,1.34,1.99,1.533c-3.957-9.818-4.179-0.541-7.176-9.994l0.634-0.051c-0.486-0.732-0.781-1.527-1.172-2.307 l0.276-2.75C4.667,58.121,6.719,47.409,7.13,41.534c0.285-2.389,2.378-4.932,3.97-8.92l-0.97-0.167 c1.854-3.234,10.586-12.988,14.63-12.486c1.959-2.461-0.389-0.009-0.772-0.629c4.303-4.453,5.656-3.146,8.56-3.947 c3.132-1.859-2.688,0.725-1.203-0.709c5.414-1.383,3.837-3.144,10.9-3.846c0.745,0.424-1.729,0.655-2.35,1.205 c4.511-2.207,14.275-1.705,20.617,1.225c7.359,3.439,15.627,13.605,15.953,23.17l0.371,0.1 c-0.188,3.802,0.582,8.199-0.752,12.238L76.992,46.856"/>
<path i:knockout="Off" fill="#A80030" d="M32.372,59.764l-0.252,1.26c1.181,1.604,2.118,3.342,3.626,4.596 C34.661,63.502,33.855,62.627,32.372,59.764"/>
<path i:knockout="Off" fill="#A80030" d="M35.164,59.654c-0.625-0.691-0.995-1.523-1.409-2.352 c0.396,1.457,1.207,2.709,1.962,3.982L35.164,59.654"/>
<path i:knockout="Off" fill="#A80030" d="M84.568,48.916l-0.264,0.662c-0.484,3.438-1.529,6.84-3.131,9.994 C82.943,56.244,84.088,52.604,84.568,48.916"/>
<path i:knockout="Off" fill="#A80030" d="M45.527,0.537C46.742,0.092,48.514,0.293,49.803,0c-1.68,0.141-3.352,0.225-5.003,0.438 L45.527,0.537"/>
<path i:knockout="Off" fill="#A80030" d="M2.872,23.219c0.28,2.592-1.95,3.598,0.494,1.889 C4.676,22.157,2.854,24.293,2.872,23.219"/>
<path i:knockout="Off" fill="#A80030" d="M0,35.215c0.563-1.728,0.665-2.766,0.88-3.766C-0.676,33.438,0.164,33.862,0,35.215"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Index applications
debian.action.index_apps.desc=Identifies all installed packages associated with .desktop files and maps them as applications
debian.action.purge=Remove completely
debian.action.purge.confirmation=The complete removal of {pkg} will erase the software files and also the configuration ones
debian.action.purge.desc=Removes all software and configuration files associated with the package
debian.action.purge.status=Removing {} completely
debian.action.sync_pkgs=Synchronize packages
debian.action.sync_pkgs.desc=Synchronizes the available software packages on the repositories
debian.action.sync_pkgs.status=Synchronizing packages
debian.action.sources=Software sources
debian.action.sources.desc=Starts the application for managing software sources
debian.action.sources.not_installed=The application for managing software sources is not installed
debian.action.sources.status=Launching application
debian.action.sources.unsupported=The application {app} is not supported and will not be started
debian.app_index.checking=Checking the apps index
debian.app_index.updating=Updating the apps index
debian.config.index_apps.exp=Apps cache expiration
debian.config.index_apps.exp.tip=Time period in MINUTES in which the installed applications cache is considered up-to-date during startup
debian.config.pkg_sources.app=Software sources
debian.config.pkg_sources.app.tip=Application for managing the software sources. {auto} detects one of the supported applications automatically.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Complete removal
debian.config.remove.purge.tip=If the package configurations should be removed during the uninstallation process
debian.config.suggestions.exp=Suggestions expiration
debian.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.
debian.config.sync_pkgs.time=Packages synchronization period
debian.config.sync_pkgs.time.tip=Time period in MINUTES in which the packages synchronization must be done on startup
debian.info.00.name=name
debian.info.01.version=version
debian.info.02.description=description
debian.info.03.exec=executable
debian.info.04.architecture=architecture
debian.info.04.architecture.all=all
debian.info.04.archive=archive
debian.info.04.automatically installed=automatically installed
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=yes
debian.info.04.breaks=breaks
debian.info.04.compressed size=Download size
debian.info.04.conflicts=conflicts
debian.info.04.depends=dependencies
debian.info.04.filename=file
debian.info.04.homepage=web page
debian.info.04.maintainer=maintainer
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-architecture
debian.info.04.multi-arch.foreign=foreign
debian.info.04.multi-arch.same=same
debian.info.04.replaces=replaces
debian.info.04.predepends=pre dependencies
debian.info.04.priority=priority
debian.info.04.priority.important=important
debian.info.04.priority.optional=optional
debian.info.04.provided by=provided by
debian.info.04.provides=provides
debian.info.04.recommends=recommends
debian.info.04.section=section
debian.info.04.state=state
debian.info.04.state.installed=installed
debian.info.04.state.not installed=not installed
debian.info.04.state.not installed (configuration files remain)=not installed (configuration files installed)
debian.info.04.suggests=suggests
debian.info.04.uncompressed size=installation size
debian.install.validating=Validating installed packages
debian.installing_pkgs=Installing packages
debian.output.downloading=Downloading {pkg}
debian.output.removing=Removing {pkg}
debian.output.finishing=Finishing
debian.output.unpacking=Unpacking {pkg}
debian.remove.impossible=It is not possible to remove the package {pkg}
debian.remove_deps=The following {no} dependent packages of {pkg} will be removed as well ({fspace})
debian.simulate_operation=Simulating the operation
debian.task.app_index.status=Indexing installed applications
debian.task.map_apps.check_files=Checking {type} files
debian.task.map_apps.read_cache=Reading apps from the index (cache)
debian.task.map_apps.status=Mapping installed applications
debian.task.update_apps_idx.status=Updating index
debian.task.sync_pkgs.status=Synchronizing packages
debian.transaction.dependency_of=Dependency of {pkgs}
debian.transaction.get_data=Retrieving the operation's data
debian.transaction.title=Operation details
debian.transaction.to_install=The following additional packages ({no}) will be installed (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=The following packages ({no}) will be removed (Freed space: {fspace})
debian.uninstall.failed_to_remove=It was not possible to remove the following {no} packages: {pkgs}
debian.uninstall.removing=Removing packages
debian.uninstall.validating=Validating removed packages
gem.debian.info=Software packages for Debian-based systems

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Anwendungen indexieren
debian.action.index_apps.desc=Identifiziert alle installierten Pakete, die mit .desktop-Dateien verbunden sind, und ordnet sie als Anwendungen zu
debian.action.purge=Vollständig entfernen
debian.action.purge.confirmation=Das vollständige Entfernen von {pkg} löscht sowohl die Software- als auch die Konfigurationsdateien
debian.action.purge.desc=Entfernt alle Software- und Konfigurationsdateien, die mit dem Paket zusammenhängen
debian.action.purge.status=Vollständiges Entfernen von {}
debian.action.sync_pkgs=Pakete synchronisieren
debian.action.sync_pkgs.desc=Synchronisiert die verfügbaren Softwarepakete mit den Repositories
debian.action.sync_pkgs.status=Synchronisierung der Pakete
debian.action.sources=Software-Quellen
debian.action.sources.desc=Startet die Anwendung zur Verwaltung von Softwarequellen
debian.action.sources.not_installed=Die Anwendung zur Verwaltung von Softwarequellen ist nicht installiert
debian.action.sources.status=Starten der Anwendung
debian.action.sources.unsupported=Die Anwendung {app} wird nicht unterstützt und kann nicht gestartet werden
debian.app_index.checking=Überprüfen des Apps-Index
debian.app_index.updating=Aktualisieren des Apps-Index
debian.config.index_apps.exp=Ablauf des Apps-Caches
debian.config.index_apps.exp.tip=Zeitspanne in MINUTEN, in der der Cache der installierten Anwendungen beim Starten als aktuell angesehen wird
debian.config.pkg_sources.app=Softwarequellen
debian.config.pkg_sources.app.tip=Anwendung zur Verwaltung der Softwarequellen. {auto} erkennt eine der unterstützten Anwendungen automatisch.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Vollständige Entfernung
debian.config.remove.purge.tip=Ob die Paketkonfigurationen während des Deinstallationsvorgangs entfernt werden sollen
debian.config.suggestions.exp=Ablauf der Vorschläge
debian.config.suggestions.exp.tip=Er definiert den Zeitraum (in Stunden), in dem die, auf dem Datenträger gespeicherten, Vorschläge als aktuell angesehen werden. Verwenden Sie 0, wenn Sie sie immer aktualisieren wollen.
debian.config.sync_pkgs.time=Zeitraum für die Synchronisierung der Pakete
debian.config.sync_pkgs.time.tip=Zeitspanne in MINUTEN, in der die Pakete beim Start synchronisiert werden müssen
debian.info.00.name=Name
debian.info.01.version=Version
debian.info.02.description=Beschreibung
debian.info.03.exec=ausführbar
debian.info.04.architecture=Architektur
debian.info.04.architecture.all=alle
debian.info.04.archive=Archiv
debian.info.04.automatically installed=automatisch installiert
debian.info.04.automatically installed.no=nein
debian.info.04.automatically installed.yes=ja
debian.info.04.breaks=bricht
debian.info.04.compressed size=Download-Größe
debian.info.04.conflicts=Konflikte
debian.info.04.depends=Abhängigkeiten
debian.info.04.filename=Datei
debian.info.04.homepage=Internetseite
debian.info.04.maintainer=Verwalter
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=Multi-Architektur
debian.info.04.multi-arch.foreign=fremd
debian.info.04.multi-arch.same=gleich
debian.info.04.replaces=ersetzt
debian.info.04.predepends=Vorabhängigkeiten
debian.info.04.priority=Priorität
debian.info.04.priority.important=wichtig
debian.info.04.priority.optional=optional
debian.info.04.provided by=bereitgestellt von
debian.info.04.provides=bietet
debian.info.04.recommends=empfiehlt
debian.info.04.section=Abschnitt
debian.info.04.state=Status
debian.info.04.state.installed=installiert
debian.info.04.state.not installed=nicht installiert
debian.info.04.state.not installed (configuration files remain)=nicht installiert (Konfigurationsdateien installiert)
debian.info.04.suggests=empfiehlt
debian.info.04.uncompressed size=Installationsgröße
debian.install.validating=Überprüfung der installierten Pakete
debian.installing_pkgs=Installieren von Paketen
debian.output.downloading=Herunterladen von {pkg}
debian.output.finishing=Fertigstellung
debian.output.removing=Entfernen von {pkg}
debian.output.unpacking=Entpacken {pkg}
debian.remove.impossible=Es ist nicht möglich, das Paket {pkg} zu entfernen
debian.remove_deps=Die folgenden {no} abhängigen Pakete von {pkg} werden ebenfalls entfernt ({fspace})
debian.simulate_operation=Simulation des Vorgangs
debian.task.app_index.status=Indexierung der installierten Anwendungen
debian.task.map_apps.check_files=Überprüfung von {type} Dateien
debian.task.map_apps.read_cache=Lesen von Anwendungen aus dem Index (Cache)
debian.task.map_apps.status=Zuordnung der installierten Anwendungen
debian.task.update_apps_idx.status=Aktualisieren des Index
debian.task.sync_pkgs.status=Synchronisierung der Pakete
debian.transaction.dependency_of=Abhängigkeit von {pkgs}
debian.transaction.get_data=Abrufen der Daten des Vorgangs
debian.transaction.title=Details zum Vorgang
debian.transaction.to_install=Die folgenden zusätzlichen Pakete ({no}) werden installiert (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=Die folgenden Pakete ({no}) werden entfernt (Freigegebener Speicherplatz: {fspace})
debian.uninstall.failed_to_remove=Es war nicht möglich, die folgenden Pakete ({no}) zu entfernen: {pkgs}
debian.uninstall.removing=Entfernen von Paketen
debian.uninstall.validating=Validierung entfernter Pakete
gem.debian.info=Softwarepakete für Debian-basierte Systeme

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Index applications
debian.action.index_apps.desc=Identifies all installed packages associated with .desktop files and maps them as applications
debian.action.purge=Remove completely
debian.action.purge.confirmation=The complete removal of {pkg} will erase the software files and also the configuration ones
debian.action.purge.desc=Removes all software and configuration files associated with the package
debian.action.purge.status=Removing {} completelydebian.config.pkg_sources.app.auto=
debian.action.sync_pkgs=Synchronize packages
debian.action.sync_pkgs.desc=Synchronizes the available software packages on the repositories
debian.action.sync_pkgs.status=Synchronizing packages
debian.action.sources=Software sources
debian.action.sources.desc=Starts the application for managing software sources
debian.action.sources.not_installed=The application for managing software sources is not installed
debian.action.sources.status=Launching application
debian.action.sources.unsupported=The application {app} is not supported and will not be started
debian.app_index.checking=Checking the apps index
debian.app_index.updating=Updating the apps index
debian.config.index_apps.exp=Apps cache expiration
debian.config.index_apps.exp.tip=Time period in MINUTES in which the installed applications cache is considered up-to-date during startup
debian.config.pkg_sources.app=Software sources
debian.config.pkg_sources.app.tip=Application for managing the software sources. {auto} detects one of the supported applications automatically.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Complete removal
debian.config.remove.purge.tip=If the package configurations should be removed during the uninstallation process
debian.config.suggestions.exp=Suggestions expiration
debian.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.
debian.config.sync_pkgs.time=Packages synchronization period
debian.config.sync_pkgs.time.tip=Time period in MINUTES in which the packages synchronization must be done on startup
debian.info.00.name=name
debian.info.01.version=version
debian.info.02.description=description
debian.info.03.exec=executable
debian.info.04.architecture=architecture
debian.info.04.architecture.all=all
debian.info.04.archive=archive
debian.info.04.automatically installed=automatically installed
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=yes
debian.info.04.breaks=breaks
debian.info.04.compressed size=Download size
debian.info.04.conflicts=conflicts
debian.info.04.depends=dependencies
debian.info.04.filename=file
debian.info.04.homepage=web page
debian.info.04.maintainer=maintainer
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-architecture
debian.info.04.multi-arch.foreign=foreign
debian.info.04.multi-arch.same=same
debian.info.04.replaces=replaces
debian.info.04.predepends=pre dependencies
debian.info.04.priority=priority
debian.info.04.priority.important=important
debian.info.04.priority.optional=optional
debian.info.04.provided by=provided by
debian.info.04.provides=provides
debian.info.04.recommends=recommends
debian.info.04.section=section
debian.info.04.state=state
debian.info.04.state.installed=installed
debian.info.04.state.not installed=not installed
debian.info.04.state.not installed (configuration files remain)=not installed (configuration files installed)
debian.info.04.suggests=suggests
debian.info.04.uncompressed size=installation size
debian.install.validating=Validating installed packages
debian.installing_pkgs=Installing packages
debian.output.downloading=Downloading {pkg}
debian.output.finishing=Finishing
debian.output.removing=Removing {pkg}
debian.output.unpacking=Unpacking {pkg}
debian.remove.impossible=It is not possible to remove the package {pkg}
debian.remove_deps=The following {no} dependent packages of {pkg} will be removed as well ({fspace})
debian.simulate_operation=Simulating the operation
debian.task.app_index.status=Indexing installed applications
debian.task.map_apps.check_files=Checking {type} files
debian.task.map_apps.read_cache=Reading apps from the index (cache)
debian.task.map_apps.status=Mapping installed applications
debian.task.update_apps_idx.status=Updating index
debian.task.sync_pkgs.status=Synchronizing packages
debian.transaction.dependency_of=Dependency of {pkgs}
debian.transaction.get_data=Retrieving the operation's data
debian.transaction.title=Operation details
debian.transaction.to_install=The following additional packages ({no}) will be installed (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=The following packages ({no}) will be removed (Freed space: {fspace})
debian.uninstall.failed_to_remove=It was not possible to remove the following {no} packages: {pkgs}
debian.uninstall.removing=Removing packages
debian.uninstall.validating=Validating removed packages
gem.debian.info=Software packages for Debian-based systems

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Indexar aplicaciones
debian.action.index_apps.desc=Identifica todos los paquetes instalados asociados con archivos .desktop y los mapea como aplicaciones
debian.action.purge=Eliminar completamente
debian.action.purge.confirmation=La eliminación completa de {pkg} borrará los archivos de software y también los de configuración
debian.action.purge.desc=Elimina los archivos de software y configuración asociados al paquete
debian.action.purge.status=Eliminando {} completamente
debian.action.sync_pkgs=Sincronizar paquetes
debian.action.sync_pkgs.desc=Sincroniza los paquetes de software disponibles en los repositorios
debian.action.sync_pkgs.status=Sincronizando paquetes
debian.action.sources=Fuentes de software
debian.action.sources.desc=Inicia la aplicación para administrar fuentes de software
debian.action.sources.not_installed=La aplicación para administrar fuentes de software no está instalada
debian.action.sources.status=Iniciando la aplicación
debian.action.sources.unsupported=La aplicación {app} no es compatible y no se iniciará
debian.app_index.checking=Verificando el índice de applicationes
debian.app_index.updating=Actualizando el índice de aplicaciones
debian.config.index_apps.exp=Caducidad de caché de aplicaciones
debian.config.index_apps.exp.tip=Periodo de tiempo en MINUTOS en que el cache de las aplicaciones instaladas es considerado actualizado durante la inicialización
debian.config.pkg_sources.app=Fuentes de software
debian.config.pkg_sources.app.tip=Aplicación para administrar las fuentes de software. {auto} detecta una de las aplicaciones compatibles automáticamente.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Eliminación completa
debian.config.remove.purge.tip=Si las configuraciones del paquete deben ser eliminadas durante el proceso de desinstalación
debian.config.suggestions.exp=Expiración de sugerencias
debian.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.
debian.config.sync_pkgs.time=Período de sincronización de paquetes
debian.config.sync_pkgs.time.tip=Período de tiempo en MINUTOS en que se debe realizar la sincronización de paquetes durante la inicialización
debian.info.00.name=nombre
debian.info.01.version=versión
debian.info.02.description=descripción
debian.info.03.exec=ejecutable
debian.info.04.architecture=arquitectura
debian.info.04.architecture.all=todas
debian.info.04.archive=archivamiento
debian.info.04.automatically installed=instalado automáticamente
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=si
debian.info.04.breaks=roturas
debian.info.04.compressed size=Tamaño de descarga
debian.info.04.conflicts=conflictos
debian.info.04.depends=dependencias
debian.info.04.filename=archivo
debian.info.04.homepage=página web
debian.info.04.maintainer=mantenedor
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-arquitectura
debian.info.04.multi-arch.foreign=diferente
debian.info.04.multi-arch.same=misma
debian.info.04.replaces=reemplaza
debian.info.04.predepends=pre dependencias
debian.info.04.priority=prioridad
debian.info.04.priority.important=importante
debian.info.04.priority.opcional=opcional
debian.info.04.provided by=proporcionado por
debian.info.04.provides=proporciona
debian.info.04.recommends=recomienda
debian.info.04.section=sección
debian.info.04.state=estado
debian.info.04.state.installed=instalado
debian.info.04.state.not installed=no instalado
debian.info.04.state.not installed (configuration files remain)=no instalado (archivos de configuración instalados)
debian.info.04.suggests=sugiere
debian.info.04.uncompressed size=tamaño de instalación
debian.install.validating=Validando paquetes instalados
debian.installing_pkgs=Instalando paquetes
debian.output.downloading=Descargando {pkg}
debian.output.finishing=Finalizando
debian.output.removing=Eliminando {pkg}
debian.output.unpacking=Desempacando {pkg}
debian.remove.impossible=No es posible eliminar el paquete {pkg}
debian.remove_deps=Los siguientes {no} paquetes dependientes de {pkg} serán eliminados también ({fspace})
debian.simulate_operation=Simulando la operación
debian.task.app_index.status=Indexando aplicaciones instaladas
debian.task.map_apps.check_files=Comprobando archivos {type}
debian.task.map_apps.read_cache=Leyendo aplicaciones del índice (cache)
debian.task.map_apps.status=Mapeando las aplicaciones instaladas
debian.task.update_apps_idx.status=Actualizando índice
debian.task.sync_pkgs.status=Sincronizando paquetes
debian.transaction.dependency_of=Dependencia de {pkgs}
debian.transaction.get_data=Recuperando datos de la operación
debian.transaction.title=Detalles de la operación
debian.transaction.to_install=Los siguientes paquetes adicionales ({no}) serán instalados (Instalación: {isize} | Descarga: {dsize})
debian.transaction.to_remove=Los siguientes paquetes ({no}) serán eliminados (Espacio liberado: {fspace})
debian.uninstall.failed_to_remove=No fue posible eliminar los siguientes {no} paquetes: {pkgs}
debian.uninstall.removing=Removendo paquetes
debian.uninstall.validating=Validando paquetes eliminados
gem.debian.info=Paquetes de software para sistemas basados en Debian

View File

@@ -1,86 +0,0 @@
debian.action.index_apps=Index applications
debian.action.index_apps.desc=Identifies all installed packages associated with .desktop files and maps them as applications
debian.action.purge=Remove completely
debian.action.purge.confirmation=The complete removal of {pkg} will erase the software files and also the configuration ones
debian.action.purge.desc=Removes all software and configuration files associated with the package
debian.action.purge.status=Removing {} completely
debian.action.sync_pkgs=Synchronize packages
debian.action.sync_pkgs.desc=Synchronizes the available software packages on the repositories
debian.action.sync_pkgs.status=Synchronizing packages
debian.action.sources=Software sources
debian.action.sources.desc=Starts the application for managing software sources
debian.action.sources.not_installed=The application for managing software sources is not installed
debian.action.sources.status=Launching application
debian.action.sources.unsupported=The application {app} is not supported and will not be started
debian.app_index.checking=Checking the apps index
debian.app_index.updating=Updating the apps index
debian.config.index_apps.exp=Apps cache expiration
debian.config.index_apps.exp.tip=Time period in MINUTES in which the installed applications cache is considered up-to-date during startup
debian.config.pkg_sources.app=Software sources
debian.config.pkg_sources.app.tip=Application for managing the software sources. {auto} detects one of the supported applications automatically.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Complete removal
debian.config.remove.purge.tip=If the package configurations should be removed during the uninstallation process
debian.config.suggestions.exp=Suggestions expiration
debian.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.
debian.config.sync_pkgs.time=Packages synchronization period
debian.config.sync_pkgs.time.tip=Time period in MINUTES in which the packages synchronization must be done on startup
debian.info.00.name=name
debian.info.01.version=version
debian.info.02.description=description
debian.info.03.exec=executable
debian.info.04.architecture=architecture
debian.info.04.architecture.all=all
debian.info.04.archive=archive
debian.info.04.automatically installed=automatically installed
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=yes
debian.info.04.breaks=breaks
debian.info.04.compressed size=Download size
debian.info.04.conflicts=conflicts
debian.info.04.depends=dependencies
debian.info.04.filename=file
debian.info.04.homepage=web page
debian.info.04.maintainer=maintainer
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-architecture
debian.info.04.multi-arch.foreign=foreign
debian.info.04.multi-arch.same=same
debian.info.04.replaces=replaces
debian.info.04.predepends=pre dependencies
debian.info.04.priority=priority
debian.info.04.priority.important=important
debian.info.04.priority.optional=optional
debian.info.04.provided by=provided by
debian.info.04.provides=provides
debian.info.04.recommends=recommends
debian.info.04.section=section
debian.info.04.state=state
debian.info.04.state.installed=installed
debian.info.04.state.not installed=not installed
debian.info.04.state.not installed (configuration files remain)=not installed (configuration files installed)
debian.info.04.suggests=suggests
debian.info.04.uncompressed size=installation size
debian.install.validating=Validating installed packages
debian.output.downloading=Downloading {pkg}
debian.output.finishing=Finishing
debian.output.removing=Removing {pkg}
debian.output.unpacking=Unpacking {pkg}
debian.remove.impossible=It is not possible to remove the package {pkg}
debian.remove_deps=The following {no} dependent packages of {pkg} will be removed as well ({fspace})
debian.simulate_operation=Simulating the operation
debian.task.app_index.status=Indexing installed applications
debian.task.map_apps.check_files=Checking {type} files
debian.task.map_apps.read_cache=Reading apps from the index (cache)
debian.task.map_apps.status=Mapping installed applications
debian.task.update_apps_idx.status=Updating index
debian.task.sync_pkgs.status=Synchronizing packages
debian.transaction.dependency_of=Dependency of {pkgs}
debian.transaction.get_data=Retrieving the operation's data
debian.transaction.title=Operation details
debian.transaction.to_install=The following additional packages ({no}) will be installed (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=The following packages ({no}) will be removed (Freed space: {fspace})
debian.uninstall.failed_to_remove=It was not possible to remove the following {no} packages: {pkgs}
debian.uninstall.removing=Removing packages
debian.uninstall.validating=Validating removed packages
gem.debian.info=Software packages for Debian-based systems

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Index applications
debian.action.index_apps.desc=Identifies all installed packages associated with .desktop files and maps them as applications
debian.action.purge=Remove completely
debian.action.purge.confirmation=The complete removal of {pkg} will erase the software files and also the configuration ones
debian.action.purge.desc=Removes all software and configuration files associated with the package
debian.action.purge.status=Removing {} completely
debian.action.sync_pkgs=Synchronize packages
debian.action.sync_pkgs.desc=Synchronizes the available software packages on the repositories
debian.action.sync_pkgs.status=Synchronizing packages
debian.action.sources=Software sources
debian.action.sources.desc=Starts the application for managing software sources
debian.action.sources.not_installed=The application for managing software sources is not installed
debian.action.sources.status=Launching application
debian.action.sources.unsupported=The application {app} is not supported and will not be started
debian.app_index.checking=Checking the apps index
debian.app_index.updating=Updating the apps index
debian.config.index_apps.exp=Apps cache expiration
debian.config.index_apps.exp.tip=Time period in MINUTES in which the installed applications cache is considered up-to-date during startup
debian.config.pkg_sources.app=Software sources
debian.config.pkg_sources.app.tip=Application for managing the software sources. {auto} detects one of the supported applications automatically.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Complete removal
debian.config.remove.purge.tip=If the package configurations should be removed during the uninstallation process
debian.config.suggestions.exp=Suggestions expiration
debian.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.
debian.config.sync_pkgs.time=Packages synchronization period
debian.config.sync_pkgs.time.tip=Time period in MINUTES in which the packages synchronization must be done on startup
debian.info.00.name=name
debian.info.01.version=version
debian.info.02.description=description
debian.info.03.exec=executable
debian.info.04.architecture=architecture
debian.info.04.architecture.all=all
debian.info.04.archive=archive
debian.info.04.automatically installed=automatically installed
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=yes
debian.info.04.breaks=breaks
debian.info.04.compressed size=Download size
debian.info.04.conflicts=conflicts
debian.info.04.depends=dependencies
debian.info.04.filename=file
debian.info.04.homepage=web page
debian.info.04.maintainer=maintainer
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-architecture
debian.info.04.multi-arch.foreign=foreign
debian.info.04.multi-arch.same=same
debian.info.04.replaces=replaces
debian.info.04.predepends=pre dependencies
debian.info.04.priority=priority
debian.info.04.priority.important=important
debian.info.04.priority.optional=optional
debian.info.04.provided by=provided by
debian.info.04.provides=provides
debian.info.04.recommends=recommends
debian.info.04.section=section
debian.info.04.state=state
debian.info.04.state.installed=installed
debian.info.04.state.not installed=not installed
debian.info.04.state.not installed (configuration files remain)=not installed (configuration files installed)
debian.info.04.suggests=suggests
debian.info.04.uncompressed size=installation size
debian.install.validating=Validating installed packages
debian.installing_pkgs=Installing packages
debian.output.downloading=Downloading {pkg}
debian.output.finishing=Finishing
debian.output.removing=Removing {pkg}
debian.output.unpacking=Unpacking {pkg}
debian.remove.impossible=It is not possible to remove the package {pkg}
debian.remove_deps=The following {no} dependent packages of {pkg} will be removed as well ({fspace})
debian.simulate_operation=Simulating the operation
debian.task.app_index.status=Indexing installed applications
debian.task.map_apps.check_files=Checking {type} files
debian.task.map_apps.read_cache=Reading apps from the index (cache)
debian.task.map_apps.status=Mapping installed applications
debian.task.update_apps_idx.status=Updating index
debian.task.sync_pkgs.status=Synchronizing packages
debian.transaction.dependency_of=Dependency of {pkgs}
debian.transaction.get_data=Retrieving the operation's data
debian.transaction.title=Operation details
debian.transaction.to_install=The following additional packages ({no}) will be installed (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=The following packages ({no}) will be removed (Freed space: {fspace})
debian.uninstall.failed_to_remove=It was not possible to remove the following {no} packages: {pkgs}
debian.uninstall.removing=Removing packages
debian.uninstall.validating=Validating removed packages
gem.debian.info=Software packages for Debian-based systems

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Indexar aplicações
debian.action.index_apps.desc=Identifica todos os pacotes instalados associados a arquivos .desktop e os mapeia como aplicações
debian.action.purge=Remover completamente
debian.action.purge.confirmation=A remoção completa de {pkg} apagará os arquivos de software e também os de configuração
debian.action.purge.desc=Remove todos os arquivos de software e configuração associados ao pacote
debian.action.purge.status=Removendo {} completamente
debian.action.sync_pkgs=Sincronizar pacotes
debian.action.sync_pkgs.desc=Sincroniza os pacotes de software disponíveis nos repositórios
debian.action.sync_pkgs.status=Sincronizando pacotes
debian.action.sources=Fontes de software
debian.action.sources.desc=Inicia a aplicação detectada para gerenciamento de fontes de software
debian.action.sources.not_installed=A aplicação para gerenciamento de fontes de software não está instalada
debian.action.sources.status=Iniciando aplicação
debian.action.sources.unsupported=A aplicação {app} não é suportada e não será inicializada
debian.app_index.checking=Veriricando o índice de aplicações
debian.app_index.updating=Atualizando o índice de aplicações
debian.config.index_apps.exp=Validade do cache de aplicações
debian.config.index_apps.exp.tip=Período de tempo em MINUTOS em que o cache das aplicações instaladas é considerado válido durante a inicialização
debian.config.pkg_sources.app=Fontes de software
debian.config.pkg_sources.app.tip=Aplicação para gerenciamento de fontes de software. {auto} detecta uma das aplicações suportadas automaticamente.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Remoção completa
debian.config.remove.purge.tip=Se as configurações do pacote devem ser removidas durante o processo de desinstalação
debian.config.suggestions.exp=Validade de sugestões
debian.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.
debian.config.sync_pkgs.time=Período de sincronização de pacotes
debian.config.sync_pkgs.time.tip=Período de tempo em MINUTOS em que a sincronização de pacotes deve ocorrer durante a inicialização
debian.info.00.name=nome
debian.info.01.version=versão
debian.info.02.description=descrição
debian.info.03.exec=executável
debian.info.04.architecture=arquitetura
debian.info.04.architecture.all=todas
debian.info.04.archive=arquivamento
debian.info.04.automatically installed=instalado automaticamente
debian.info.04.automatically installed.no=não
debian.info.04.automatically installed.yes=sim
debian.info.04.breaks=Quebra
debian.info.04.compressed size=Tamanho de download
debian.info.04.conflicts=Conflita
debian.info.04.depends=dependências
debian.info.04.filename=arquivo
debian.info.04.homepage=página web
debian.info.04.maintainer=mantenedor
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-arquitetura
debian.info.04.multi-arch.foreign=diferente
debian.info.04.multi-arch.same=mesma
debian.info.04.replaces=substitui
debian.info.04.predepends=pré-dependências
debian.info.04.priority=prioridade
debian.info.04.priority.important=importante
debian.info.04.priority.optional=opcional
debian.info.04.provided by=provido por
debian.info.04.provides=provê
debian.info.04.recommends=recomenda
debian.info.04.section=seção
debian.info.04.state=estado
debian.info.04.state.installed=instalado
debian.info.04.state.not installed=não instalado
debian.info.04.state.not installed (configuration files remain)=não instalado (arquivos de configuração instalados)
debian.info.04.suggests=sugere
debian.info.04.uncompressed size=tamanho de instalação
debian.install.validating=Validando pacotes instalados
debian.installing_pkgs=Instalando pacotes
debian.output.downloading=Baixando {pkg}
debian.output.finishing=Finalizando
debian.output.removing=Removendo {pkg}
debian.output.unpacking=Desempacotando {pkg}
debian.remove.impossible=Não é possível remover o pacote {pkg}
debian.remove_deps=Os seguintes {no} pacotes dependentes de {pkg} serão removidos também ({fspace})
debian.simulate_operation=Simulando a operação
debian.task.app_index.status=Indexando aplicações instaladas
debian.task.map_apps.check_files=Verificando arquivos {type}
debian.task.map_apps.read_cache=Lendo aplicações do índice (cache)
debian.task.map_apps.status=Mapeando aplicações instaladas
debian.task.update_apps_idx.status=Atualizando índice
debian.task.sync_pkgs.status=Sincronizando pacotes
debian.transaction.dependency_of=Dependência de {pkgs}
debian.transaction.get_data=Obtendo dados da operação
debian.transaction.title=Detalhes da operação
debian.transaction.to_install=Os seguintes pacotes adicionais ({no}) serão instaladas (Instalação: {isize} | Download: {dsize})
debian.transaction.to_remove=Os seguintes pacotes ({no}) serão removidos (Espaço liberado: {fspace})
debian.uninstall.failed_to_remove=Não foi possível remover os seguintes {no} pacotes: {pkgs}
debian.uninstall.removing=Removendo pacotes
debian.uninstall.validating=Validando pacotes removidos
gem.debian.info=Pacotes de software para sistemas baseados em Debian

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=Сопоставлять приложения
debian.action.index_apps.desc=Идентифицирует все установленные пакеты, связанные с файлами .desktop, и сопоставляет их с приложениями
debian.action.purge=Удалить полностью
debian.action.purge.confirmation=Полное удаление {pkg} приведет к удалению программных файлов, а также файлов конфигурации
debian.action.purge.desc=Удаление всех программных и конфигурационных файлов, связанных с пакетом
debian.action.purge.status=Полное удаление {}
debian.action.sync_pkgs=Синхронизация пакетов
debian.action.sync_pkgs.desc=Синхронизация доступных программных пакетов в репозиториях
debian.action.sync_pkgs.status=Синхронизация пакетов
debian.action.sources=Источники ПО
debian.action.sources.desc=Запуск приложения для управления источниками ПО
debian.action.sources.not_installed=Приложение для управления источниками ПО не установлено
debian.action.sources.status=Запуск приложения
debian.action.sources.unsupported=Приложение {app} не поддерживается и не запускается
debian.app_index.checking=Проверка индекса приложений
debian.app_index.updating=Обновление индекса приложений
debian.config.index_apps.exp=Истечение срока действия кэша приложений
debian.config.index_apps.exp.tip=Период времени (в минутах) в течение которого кэш установленных приложений считается обновленным при запуске
debian.config.pkg_sources.app=Источники ПО
debian.config.pkg_sources.app.tip=Приложение для управления источниками ПО. {auto} автоматически определяет одно из поддерживаемых приложений.
debian.config.pkg_sources.app.auto=Автоматически
debian.config.remove.purge=Полное удаление
debian.config.remove.purge.tip=Если конфигурации пакета должны быть удалены в процессе удаления
debian.config.suggestions.exp=Срок действия предложений
debian.config.suggestions.exp.tip=Он определяет период (в часах), в течение которого предложения, хранящиеся на диске, будут считаться актуальными. Используйте 0, если вы хотите обновлять их всегда.
debian.config.sync_pkgs.time=Период синхронизации пакетов
debian.config.sync_pkgs.time.tip=Период времени (в минутах) в течение которого должна быть выполнена синхронизация пакетов при запуске
debian.info.00.name=имя
debian.info.01.version=версия
debian.info.02.description=описание
debian.info.03.exec=исполняемый файл
debian.info.04.architecture=архитектура
debian.info.04.architecture.all=все
debian.info.04.archive=архив
debian.info.04.automatically installed=автоматически установленный
debian.info.04.automatically installed.no=нет
debian.info.04.automatically installed.yes=да
debian.info.04.breaks=нарушает
debian.info.04.compressed size=Размер загрузки
debian.info.04.conflicts=конфликты
debian.info.04.depends=зависимости
debian.info.04.filename=файл
debian.info.04.homepage=веб-страница
debian.info.04.maintainer=сопровождающий
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=мультиархитектура
debian.info.04.multi-arch.foreign=внешний
debian.info.04.multi-arch.same=тот же
debian.info.04.replaces=заменяет
debian.info.04.predepends=предзависимости
debian.info.04.priority=приоритет
debian.info.04.priority.important=важно
debian.info.04.priority.optional=опционально
debian.info.04.provided by=предоставленный
debian.info.04.provides=предоставляет
debian.info.04.recommends=рекомендует
debian.info.04.section=раздел
debian.info.04.state=состояние
debian.info.04.state.installed=установлен
debian.info.04.state.not installed=не установлен
debian.info.04.state.not installed (configuration files remain)=не установлен (файлы конфигурации установлены)
debian.info.04.suggests=предлагает
debian.info.04.uncompressed size=установочный размер
debian.install.validating=Проверка установленных пакетов
debian.installing_pkgs=Установка пакетов
debian.output.downloading=Загрузка {pkg}
debian.output.finishing=Завершение
debian.output.removing=Удаление {pkg}
debian.output.unpacking=Распаковка {pkg}
debian.remove.impossible=Невозможно удалить пакет {pkg}
debian.remove_deps=Также будут удалены следующие {no} зависимые пакеты {pkg} ({fspace})
debian.simulate_operation=Симулирование работы
debian.task.app_index.status=Индексирование установленных приложений
debian.task.map_apps.check_files=Проверка {type} файлов
debian.task.map_apps.read_cache=Чтение приложений из индекса (кэша)
debian.task.map_apps.status=Сопоставление установленных приложений
debian.task.update_apps_idx.status=Обновление индекса
debian.task.sync_pkgs.status=Синхронизация пакетов
debian.transaction.dependency_of=Зависимость от {pkgs}
debian.transaction.get_data=Получение данных операции
debian.transaction.title=Детали операции
debian.transaction.to_install=Будут установлены следующие дополнительные пакеты ({no}) (Установка: {isize} | Загрузка: {dsize})
debian.transaction.to_remove=Следующие пакеты ({no}) будут удалены (Освобожденное пространство: {fspace})
debian.uninstall.failed_to_remove=Не удалось удалить следующие пакеты {no}: {pkgs}
debian.uninstall.removing=Удаление пакетов
debian.uninstall.validating=Проверка подлинности удаленных пакетов
gem.debian.info=Пакеты ПО для систем на базе Debian

View File

@@ -1,86 +0,0 @@
debian.action.index_apps=Index applications
debian.action.index_apps.desc=Identifies all installed packages associated with .desktop files and maps them as applications
debian.action.purge=Remove completely
debian.action.purge.confirmation=The complete removal of {pkg} will erase the software files and also the configuration ones
debian.action.purge.desc=Removes all software and configuration files associated with the package
debian.action.purge.status=Removing {} completely
debian.action.sync_pkgs=Synchronize packages
debian.action.sync_pkgs.desc=Synchronizes the available software packages on the repositories
debian.action.sync_pkgs.status=Synchronizing packages
debian.action.sources=Software sources
debian.action.sources.desc=Starts the application for managing software sources
debian.action.sources.not_installed=The application for managing software sources is not installed
debian.action.sources.status=Launching application
debian.action.sources.unsupported=The application {app} is not supported and will not be started
debian.app_index.checking=Checking the apps index
debian.app_index.updating=Updating the apps index
debian.config.index_apps.exp=Apps cache expiration
debian.config.index_apps.exp.tip=Time period in MINUTES in which the installed applications cache is considered up-to-date during startup
debian.config.pkg_sources.app=Software sources
debian.config.pkg_sources.app.tip=Application for managing the software sources. {auto} detects one of the supported applications automatically.
debian.config.pkg_sources.app.auto=Auto
debian.config.remove.purge=Complete removal
debian.config.remove.purge.tip=If the package configurations should be removed during the uninstallation process
debian.config.suggestions.exp=Suggestions expiration
debian.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.
debian.config.sync_pkgs.time=Packages synchronization period
debian.config.sync_pkgs.time.tip=Time period in MINUTES in which the packages synchronization must be done on startup
debian.info.00.name=name
debian.info.01.version=version
debian.info.02.description=description
debian.info.03.exec=executable
debian.info.04.architecture=architecture
debian.info.04.architecture.all=all
debian.info.04.archive=archive
debian.info.04.automatically installed=automatically installed
debian.info.04.automatically installed.no=no
debian.info.04.automatically installed.yes=yes
debian.info.04.breaks=breaks
debian.info.04.compressed size=Download size
debian.info.04.conflicts=conflicts
debian.info.04.depends=dependencies
debian.info.04.homepage=web page
debian.info.04.maintainer=maintainer
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=multi-architecture
debian.info.04.multi-arch.foreign=foreign
debian.info.04.multi-arch.same=same
debian.info.04.replaces=replaces
debian.info.04.predepends=pre dependencies
debian.info.04.priority=priority
debian.info.04.priority.important=important
debian.info.04.priority.optional=optional
debian.info.04.provided by=provided by
debian.info.04.provides=provides
debian.info.04.recommends=recommends
debian.info.04.section=section
debian.info.04.state=state
debian.info.04.state.installed=installed
debian.info.04.state.not installed=not installed
debian.info.04.state.not installed (configuration files remain)=not installed (configuration files installed)
debian.info.04.suggests=suggests
debian.info.04.uncompressed size=installation size
debian.install.validating=Validating installed packages
debian.installing_pkgs=Installing packages
debian.output.downloading=Downloading {pkg}
debian.output.finishing=Finishing
debian.output.removing=Removing {pkg}
debian.output.unpacking=Unpacking {pkg}
debian.remove.impossible=It is not possible to remove the package {pkg}
debian.remove_deps=The following {no} dependent packages of {pkg} will be removed as well ({fspace})
debian.simulate_operation=Simulating the operation
debian.task.app_index.status=Indexing installed applications
debian.task.map_apps.check_files=Checking {type} files
debian.task.map_apps.read_cache=Reading apps from the index (cache)
debian.task.map_apps.status=Mapping installed applications
debian.task.update_apps_idx.status=Updating index
debian.task.sync_pkgs.status=Synchronizing packages
debian.transaction.dependency_of=Dependency of {pkgs}
debian.transaction.get_data=Retrieving the operation's data
debian.transaction.title=Operation details
debian.transaction.to_install=The following additional packages ({no}) will be installed (Installation: {isize} | Download: {dsize})
debian.transaction.to_remove=The following packages ({no}) will be removed (Freed space: {fspace})
debian.uninstall.failed_to_remove=It was not possible to remove the following {no} packages: {pkgs}
debian.uninstall.removing=Removing packages
debian.uninstall.validating=Validating removed packages
gem.debian.info=Software packages for Debian-based systems

View File

@@ -1,87 +0,0 @@
debian.action.index_apps=索引应用程序
debian.action.index_apps.desc=识别与 .desktop 文件关联的所有已安装软件包,并将它们映射为应用程序
debian.action.purge=完全删除
debian.action.purge.confirmation=完全删除 {pkg} 将擦除软件文件以及配置文件
debian.action.purge.desc=删除与软件包关联的所有软件和配置文件
debian.action.purge.status=完全删除 {}
debian.action.sync_pkgs=同步软件包
debian.action.sync_pkgs.desc=同步可用的软件包在仓库中
debian.action.sync_pkgs.status=正在同步软件包
debian.action.sources=软件源
debian.action.sources.desc=启动管理软件源的应用程序
debian.action.sources.not_installed=未安装管理软件源的应用程序
debian.action.sources.status=启动应用程序
debian.action.sources.unsupported=不支持应用程序 {app},将不会启动
debian.app_index.checking=检查应用程序索引
debian.app_index.updating=正在更新应用程序索引
debian.config.index_apps.exp=应用程序缓存过期
debian.config.index_apps.exp.tip=启动期间安装的应用程序缓存被视为最新的分钟数
debian.config.pkg_sources.app=软件源
debian.config.pkg_sources.app.tip=管理软件源的应用程序。 {auto} 自动检测支持的应用程序之一。
debian.config.pkg_sources.app.auto=自动
debian.config.remove.purge=完全删除
debian.config.remove.purge.tip=是否应在卸载过程中删除软件包配置
debian.config.suggestions.exp=建议过期时间
debian.config.suggestions.exp.tip=定义建议在磁盘上存储的期间(以小时为单位)被视为最新的时间。如果始终希望更新它们,请使用 0。
debian.config.sync_pkgs.time=软件包同步周期
debian.config.sync_pkgs.time.tip=启动时进行软件包同步的分钟数
debian.info.00.name=名称
debian.info.01.version=版本
debian.info.02.description=描述
debian.info.03.exec=可执行文件
debian.info.04.architecture=架构
debian.info.04.architecture.all=全部
debian.info.04.archive=存档
debian.info.04.automatically installed=自动安装
debian.info.04.automatically installed.no=否
debian.info.04.automatically installed.yes=是
debian.info.04.breaks=破坏
debian.info.04.compressed size=下载大小
debian.info.04.conflicts=冲突
debian.info.04.depends=依赖项
debian.info.04.filename=文件名
debian.info.04.homepage=网页
debian.info.04.maintainer=维护者
debian.info.04.md5sum=MD5
debian.info.04.multi-arch=多架构
debian.info.04.multi-arch.foreign=外部
debian.info.04.multi-arch.same=相同
debian.info.04.replaces=替代
debian.info.04.predepends=预先依赖
debian.info.04.priority=优先级
debian.info.04.priority.important=重要
debian.info.04.priority.optional=可选
debian.info.04.provided by=提供者
debian.info.04.provides=提供
debian.info.04.recommends=建议
debian.info.04.section=部分
debian.info.04.state=状态
debian.info.04.state.installed=已安装
debian.info.04.state.not installed=未安装
debian.info.04.state.not installed (configuration files remain)=未安装 (配置文件已安装)
debian.info.04.suggests=建议
debian.info.04.uncompressed size=安装大小
debian.install.validating=验证已安装的软件包
debian.installing_pkgs=正在安装软件包
debian.output.downloading=下载 {pkg}
debian.output.finishing=完成
debian.output.removing=删除 {pkg}
debian.output.unpacking=解压 {pkg}
debian.remove.impossible=无法删除软件包 {pkg}
debian.remove_deps=以下 {no} 个与 {pkg} 有关的依赖软件包也将被删除(已释放空间:{fspace})
debian.simulate_operation=模拟操作
debian.task.app_index.status=正在索引已安装的应用程序
debian.task.map_apps.check_files=检查 {type} 文件
debian.task.map_apps.read_cache=从索引 (缓存) 读取应用程序
debian.task.map_apps.status=映射已安装的应用程序
debian.task.update_apps_idx.status=更新索引
debian.task.sync_pkgs.status=正在同步软件包
debian.transaction.dependency_of={pkgs} 的依赖项
debian.transaction.get_data=检索操作的数据
debian.transaction.title=操作详细信息
debian.transaction.to_install=将安装以下额外的软件包({no})(安装:{isize} | 下载:{dsize})
debian.transaction.to_remove=将删除以下软件包({no})(释放空间:{fspace})
debian.uninstall.failed_to_remove=无法删除以下 {no} 个软件包:{pkgs}
debian.uninstall.removing=正在删除软件包
debian.uninstall.validating=正在验证已删除的软件包
gem.debian.info=基于 Debian 的系统的软件包

View File

@@ -1,209 +0,0 @@
import os
import traceback
from datetime import datetime, timedelta, timezone
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.suggestions import parse
from bauh.gems.debian import DEBIAN_ICON_PATH, DEBIAN_CACHE_DIR
from bauh.view.util.translation import I18n
class DebianSuggestionsDownloader(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'{DEBIAN_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
def __init__(self, logger: Logger, http_client: HttpClient, i18n: I18n, file_url: Optional[str]):
super(DebianSuggestionsDownloader, self).__init__()
self._log = logger
self.i18n = i18n
self.http_client = http_client
self._taskman: Optional[TaskManager] = None
if file_url:
self._file_url = file_url
else:
self._file_url = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/debian/suggestions_v1.txt'
self.task_id = 'debian.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=DEBIAN_ICON_PATH)
def is_local_suggestions_file(self) -> bool:
return self._file_url and self._file_url.startswith('/')
@property
def taskman(self) -> TaskManager:
if self._taskman is None:
self._taskman = TaskManager()
return self._taskman
def should_download(self, debian_config: dict, only_positive_exp: bool = False) -> bool:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return False
if self.is_local_suggestions_file():
return False
try:
exp_hours = int(debian_config['suggestions.exp'])
except ValueError:
self._log.error(f"The Debian configuration property 'suggestions.expiration' has a non int value set: "
f"{debian_config['suggestions']['expiration']}")
return not only_positive_exp
if exp_hours <= 0:
self._log.info("Suggestions cache is disabled")
return not only_positive_exp
if not os.path.exists(self.file_suggestions()):
self._log.info(f"'{self.file_suggestions()}' not found. It must be downloaded")
return True
if not os.path.exists(self.file_suggestions()):
self._log.info(f"'{self.file_suggestions()}' not found. The suggestions file must be downloaded.")
return True
with open(self.file_suggestions_timestamp()) as f:
timestamp_str = f.read()
try:
suggestions_timestamp = datetime.fromtimestamp(float(timestamp_str), tz=timezone.utc)
except Exception:
self._log.error(f'Could not parse the Debian cached suggestions timestamp: {timestamp_str}')
traceback.print_exc()
return True
update = suggestions_timestamp + timedelta(hours=exp_hours) <= datetime.now(timezone.utc)
return update
def _save(self, text: str, timestamp: float):
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
if self.is_local_suggestions_file():
return False
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 Exception:
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 Exception:
self._log.error(f"An exception happened while writing the file '{self.file_suggestions_timestamp()}'")
traceback.print_exc()
def read_cached(self) -> Optional[Dict[str, SuggestionPriority]]:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
if self.is_local_suggestions_file():
file_path, log_ref = self._file_url, 'local'
else:
file_path, log_ref = self.file_suggestions(), 'cached'
self._log.info(f"Reading {log_ref} suggestions file {file_path}")
try:
with open(file_path) as f:
sugs_str = f.read()
except FileNotFoundError:
self._log.warning(f"The {log_ref} suggestions file does not exist ({file_path})")
return
except OSError:
self._log.warning(f"Could not read from the {log_ref} suggestions file ({file_path})")
traceback.print_exc()
return
if not sugs_str:
self._log.warning(f"The {log_ref} suggestions file '{file_path}' is empty")
return
return parse(sugs_str, self._log, 'Debian')
def download(self) -> Optional[Dict[str, SuggestionPriority]]:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
self.taskman.update_progress(self.task_id, progress=1, substatus=None)
self._log.info(f"Downloading Debian suggestions from {self._file_url}")
res = self.http_client.get(self._file_url)
suggestions = None
if res.status_code == 200 and res.text:
self.taskman.update_progress(self.task_id, progress=50, substatus=None)
suggestions = parse(res.text, self._log, 'Debian')
if suggestions:
self._save(text=res.text, timestamp=datetime.now(timezone.utc).timestamp())
else:
self._log.warning("No Debian suggestions to cache")
else:
self._log.warning(f"Could not retrieve Debian 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, debian_config: dict) -> Optional[Dict[str, int]]:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
if self.is_local_suggestions_file() or not self.should_download(debian_config=debian_config):
return self.read_cached()
return self.download()
def run(self):
self.download()

View File

@@ -1,196 +0,0 @@
import time
import traceback
from datetime import datetime, timedelta, timezone
from logging import Logger
from threading import Thread
from typing import Optional, Set
from bauh.api.abstract.handler import TaskManager, ProcessWatcher
from bauh.commons.html import bold
from bauh.commons.system import ProcessHandler
from bauh.gems.debian import DEBIAN_ICON_PATH, PACKAGE_SYNC_TIMESTAMP_FILE
from bauh.gems.debian.aptitude import Aptitude
from bauh.gems.debian.index import ApplicationIndexer, ApplicationsMapper
from bauh.gems.debian.model import DebianApplication
from bauh.view.util.translation import I18n
class MapApplications(Thread):
def __init__(self, taskman: TaskManager, app_indexer: ApplicationIndexer, i18n: I18n, logger: Logger,
deb_config: dict, app_mapper: ApplicationsMapper, check_expiration: bool = True,
watcher: Optional[ProcessWatcher] = None):
super(MapApplications, self).__init__()
self._taskman = taskman
self._i18n = i18n
self._log = logger
self._id = 'debian.map_apps'
self._indexer = app_indexer
self._config = deb_config
self._app_mapper = app_mapper
self._taskman.register_task(self._id, self._i18n['debian.task.map_apps.status'], DEBIAN_ICON_PATH)
self.apps: Optional[Set[DebianApplication]] = None
self.cached: Optional[bool] = None
self._check_expiration = check_expiration
self._watcher = watcher
def _change_substatus(self, msg: str):
if self._watcher:
self._watcher.change_substatus(msg)
def run(self):
ti = time.time()
self._log.info("Begin: mapping Debian applications")
self._taskman.update_progress(self._id, 1, None)
if not self._check_expiration or self._indexer.is_expired(self._config):
status = self._i18n['debian.task.map_apps.check_files'].format(type="'.desktop")
self._taskman.update_progress(self._id, 1, status)
self._change_substatus(status)
self.apps = self._app_mapper.map_executable_applications()
self.cached = False
else:
status = self._i18n['debian.task.map_apps.read_cache']
self._taskman.update_progress(self._id, 1, status)
self._change_substatus(status)
self.apps = {app for app in self._indexer.read_index()}
self.cached = True
self._log.info(f"Number of Debian applications found: {len(self.apps) if self.apps else 0}")
self._taskman.update_progress(self._id, 100, None)
self._taskman.finish_task(self._id)
tf = time.time()
self._log.info(f"Finish: mapping Debian applications ({tf - ti:.4f} seconds)")
class UpdateApplicationIndex(Thread):
def __init__(self, taskman: TaskManager, app_indexer: ApplicationIndexer,
i18n: I18n, logger: Logger, mapping_apps: MapApplications,
watcher: Optional[ProcessWatcher] = None):
super(UpdateApplicationIndex, self).__init__()
self._taskman = taskman
self._i18n = i18n
self._log = logger
self._id = 'debian.app_idx'
self._indexer = app_indexer
self._mapping_apps = mapping_apps
self._taskman.register_task(self._id, self._i18n['debian.task.app_index.status'], DEBIAN_ICON_PATH)
self._watcher = watcher
def _change_substatus(self, msg: str):
if self._watcher:
self._watcher.change_substatus(msg)
def run(self):
ti = time.time()
self._log.info("Begin: Debian applications indexation")
self._taskman.update_progress(self._id, 1, self._i18n['task.waiting_task'].format(bold(self._i18n['debian.task.map_apps.status'])))
self._mapping_apps.join()
finish_msg = None
if self._mapping_apps.cached:
finish_msg = self._i18n['task.canceled']
else:
status = self._i18n['debian.task.update_apps_idx.status']
self._taskman.update_progress(self._id, 50, status)
self._change_substatus(status)
try:
self._indexer.update_index(self._mapping_apps.apps)
except Exception:
finish_msg = self._i18n['error']
self._taskman.update_progress(self._id, 100, finish_msg)
self._taskman.finish_task(self._id)
tf = time.time()
self._log.info(f"Finish: Debian applications indexation ({tf - ti:.4f} seconds)")
class SynchronizePackages(Thread):
def __init__(self, taskman: TaskManager, i18n: I18n, logger: Logger, root_password: Optional[str],
aptitude: Aptitude, watcher: Optional[ProcessWatcher] = None):
super(SynchronizePackages, self).__init__()
self._id = 'debian.sync_pkgs'
self._taskman = taskman
self._i18n = i18n
self._log = logger
self._root_password = root_password
self._watcher = watcher
self._aptitude = aptitude
self._taskman.register_task(self._id, self._i18n['debian.task.sync_pkgs.status'], DEBIAN_ICON_PATH)
def _notify_output(self, output: str):
self._taskman.update_output(self._id, output)
@staticmethod
def should_synchronize(deb_config: dict, logger: Logger) -> bool:
try:
period = int(deb_config.get('sync_pkgs.time', 0))
except ValueError:
logger.error(f"Invalid value for Debian configuration property 'sync_pkgs.time': "
f"{deb_config['sync_pkgs.time']}")
return True
if period <= 0:
logger.warning("Packages synchronization will always be done ('sync_pkgs.time' <= 0 )'")
return True
try:
with open(PACKAGE_SYNC_TIMESTAMP_FILE) as f:
timestamp_str = f.read().strip()
except FileNotFoundError:
logger.info(f"No packages synchronization timestamp found ({PACKAGE_SYNC_TIMESTAMP_FILE})")
return True
try:
last_timestamp = datetime.fromtimestamp(float(timestamp_str), tz=timezone.utc)
except Exception:
logger.error(f'Could not parse the packages synchronization timestamp: {timestamp_str} '
f'({PACKAGE_SYNC_TIMESTAMP_FILE})')
traceback.print_exc()
return True
expired = last_timestamp + timedelta(minutes=period) <= datetime.now(timezone.utc)
if expired:
logger.info("Packages synchronization is outdated")
else:
logger.info("Packages synchronization is up-to-date")
return expired
def run(self) -> bool:
ti = time.time()
self._log.info("Begin: packages synchronization")
self._taskman.update_progress(self._id, 1, None)
handler = ProcessHandler(self._watcher)
updated, _ = handler.handle_simple(self._aptitude.update(self._root_password),
output_handler=self._notify_output)
self._taskman.update_progress(self._id, 99, None)
if updated:
index_timestamp = datetime.now(timezone.utc).timestamp()
finish_msg = None
try:
with open(PACKAGE_SYNC_TIMESTAMP_FILE, 'w+') as f:
f.write(str(index_timestamp))
except OSError:
finish_msg = self._i18n['error']
self._log.error(f"Could not write the packages synchronization timestamp to file "
f"'{PACKAGE_SYNC_TIMESTAMP_FILE}'")
else:
finish_msg = self._i18n['error']
self._taskman.update_progress(self._id, 100, finish_msg)
self._taskman.finish_task(self._id)
tf = time.time()
self._log.info(f"Finish: packages synchronization ({tf - ti:.4f} seconds)")
return updated