mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-06 21:44:16 +02:00
Remove Debian support from Bearhub
This commit is contained in:
71
README.md
71
README.md
@@ -35,9 +35,9 @@ Bearhub is currently focused on:
|
||||
|
||||
Large architectural changes or feature redesigns are not the immediate priority.
|
||||
|
||||
Backends outside the Arch-focused scope remain in the codebase for now, but they are not the primary maintenance target of this fork.
|
||||
Debian / Apt support is intentionally removed from Bearhub. Other non-Arch backends that remain in the codebase are secondary to the Arch-focused maintenance target.
|
||||
|
||||
**bauh** (baoo), formerly known as [fpakman](https://github.com/vinifmor/fpakman), is a graphical interface for managing your Linux software (packages/applications). It currently supports the following formats: AppImage, Debian and Arch Linux packages (including AUR), Flatpak, Snap and Web applications.
|
||||
**bauh** (baoo), formerly known as [fpakman](https://github.com/vinifmor/fpakman), is a graphical interface for managing your Linux software (packages/applications). In Bearhub, the maintained package formats are Arch Linux packages (including AUR), Flatpak, Snap, AppImage and Web applications.
|
||||
|
||||
Key features
|
||||
- A management panel where you can: search, install, uninstall, upgrade, downgrade and launch your applications
|
||||
@@ -54,7 +54,6 @@ Key features
|
||||
## Index
|
||||
1. [Installation](#installation)
|
||||
- [AppImage](#inst_appimage)
|
||||
- [Ubuntu 20.04 based distros (Linux Mint, PopOS, ...)](#inst_ubuntu)
|
||||
- [Arch-based distros](#inst_arch)
|
||||
2. [Isolated installation](#inst_iso)
|
||||
3. [Desktop entry / menu shortcut](#desk_entry)
|
||||
@@ -63,7 +62,6 @@ Key features
|
||||
6. [Supported types](#types)
|
||||
- [AppImage](#type_appimage)
|
||||
- [Arch packages/AUR](#type_arch)
|
||||
- [Debian](#type_deb)
|
||||
- [Flatpak](#type_flatpak)
|
||||
- [Snap](#type_snap)
|
||||
- [Native Web applications](#type_web)
|
||||
@@ -103,52 +101,6 @@ Key features
|
||||
</p>
|
||||
|
||||
|
||||
#### <a name="inst_ubuntu">Ubuntu 20.04 based distros (Linux Mint, PopOS, ...)</a>
|
||||
|
||||
##### Required dependencies
|
||||
|
||||
`sudo apt-get install python3 python3-pip python3-yaml python3-dateutil python3-pyqt5 python3-packaging python3-requests`
|
||||
|
||||
##### Installing bauh
|
||||
|
||||
`sudo pip3 install bauh`
|
||||
|
||||
|
||||
##### Optional dependencies (they should be installed with apt-get/apt)
|
||||
|
||||
- `aptitude`: Debian package management
|
||||
- `timeshift`: system backup
|
||||
- `aria2`: multi-threaded downloads
|
||||
- `axel`: multi-threaded downloads alternative
|
||||
- `libappindicator3-1`: tray-mode
|
||||
- `sqlite3`, `fuse`: AppImage support
|
||||
- `flatpak`: Flatpaks support
|
||||
- `snapd`: Snaps support
|
||||
- `python3-lxml`, `python3-bs4`: Web apps support
|
||||
- `python3-venv`: [isolated installation](#inst_iso)
|
||||
- `xdg-utils`: to open URLs in the browsers (`xdg-open`)
|
||||
|
||||
##### Updating bauh
|
||||
|
||||
Method 1
|
||||
|
||||
`sudo pip3 install bauh --upgrade`
|
||||
|
||||
Method 2
|
||||
|
||||
```
|
||||
sudo pip3 uninstall bauh
|
||||
sudo pip3 install bauh
|
||||
```
|
||||
|
||||
##### Uninstalling bauh
|
||||
|
||||
```
|
||||
bauh --reset # removes cache and configurations files from HOME
|
||||
sudo pip3 uninstall bauh
|
||||
```
|
||||
|
||||
|
||||
#### <a name="inst_arch">Arch-based distros</a>
|
||||
|
||||
##### Using yay
|
||||
@@ -341,24 +293,6 @@ suggestions_exp: 24 # it defines the period (in hours) in which the suggestions
|
||||
```
|
||||
- Cached package suggestions: `~/.cache/bauh/arch/suggestions.txt` (or `/var/cache/bauh/arch/suggestions.yml` for **root**)
|
||||
|
||||
##### <a name="type_deb">Debian packages<a>
|
||||
- Basic actions supported: **search**, **install**, **uninstall**, **upgrade**
|
||||
- Custom actions supported:
|
||||
- **synchronize packages**: synchronize the available packages on the repository (`aptitude update`)
|
||||
- **index applications**: maps runnable installed packages (automatically done during initialization)
|
||||
- **software sources**: launches the application responsible for managing software sources (at the moment only `software-properties-gtk` is supported)
|
||||
- Custom package actions supported:
|
||||
- **purge**: removes the packages and all related configuration files
|
||||
- Files:
|
||||
- runnable applications index: `~/.cache/bauh/debian/apps_idx.json` (or `/var/cache/bauh/debian/apps_idx.json` for **root**)
|
||||
- cached package suggestions: `~/.cache/bauh/debian/suggestions.txt` (or `/var/cache/bauh/debian/suggestions.txt` for **root**)
|
||||
- configuration: `~/.config/bauh/debian.yml` or `/etc/bauh/debian.yml`
|
||||
- `index_apps.exp`: time period (**in minutes**) in which the installed applications cache is considered up-to-date during startup (default: `1440` -> 24 hours)
|
||||
- `sync_pkgs.time`: time period (**in minutes**) in which the packages synchronization must be done on startup (default: `1440` -> 24 hours)
|
||||
- `suggestions.exp`: 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.
|
||||
- `pkg_sources.app`: it defines the application for managing the software sources. A `null` value detects one of the supported applications automatically.
|
||||
- `remove.purge`: if the package configurations should be removed during the uninstallation process (purge). Default: false.
|
||||
|
||||
##### <a name="type_flatpak">Flatpak</a>
|
||||
|
||||
- Applications with ignored updates are defined at `~/.config/bauh/flatpak/updates_ignored.txt`
|
||||
@@ -517,7 +451,6 @@ appimage
|
||||
- The software suggestions are download from [bauh-files](https://github.com/vinifmor/bauh-files) by default
|
||||
- [appimage](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
||||
- [arch](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
||||
- [debian](https://github.com/vinifmor/bauh-files/blob/master/debian/suggestions_v1.txt)
|
||||
- [flatpak](https://github.com/vinifmor/bauh-files/blob/master/flatpak/suggestions.txt)
|
||||
- [snap](https://github.com/vinifmor/bauh-files/blob/master/snap/suggestions.txt)
|
||||
- [web](https://github.com/vinifmor/bauh-files/blob/master/web/env/v2/suggestions.yml)
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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'])
|
||||
@@ -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
|
||||
@@ -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()))
|
||||
@@ -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 |
@@ -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 |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 的系统的软件包
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
import os
|
||||
|
||||
DEBIAN_TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"firefox": {
|
||||
"categories": [
|
||||
"GNOME",
|
||||
"GTK",
|
||||
"Network",
|
||||
"WebBrowser"
|
||||
],
|
||||
"exe_path": "firefox %u",
|
||||
"icon_path": "firefox"
|
||||
},
|
||||
|
||||
"synaptic": {
|
||||
"exe_path": "synaptic-pkexec",
|
||||
"icon_path": "synaptic"
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.commons import system
|
||||
from bauh.commons.system import USE_GLOBAL_INTERPRETER
|
||||
from bauh.gems.debian.aptitude import Aptitude, map_package_name
|
||||
from bauh.gems.debian.model import DebianPackage
|
||||
|
||||
|
||||
class MapPackageNameTest(TestCase):
|
||||
|
||||
def test__it_must_return_the_same_input_when_no_colon(self):
|
||||
self.assertEqual('my_package', map_package_name('my_package'))
|
||||
|
||||
def test__it_must_return_the_only_the_word_before_the_first_colon(self):
|
||||
self.assertEqual('my_package', map_package_name('my_package:i386'))
|
||||
|
||||
def test__it_must_return_a_name_with_colon_when_several_colons_are_present(self):
|
||||
self.assertEqual('my_package:i386', map_package_name('my_package:i386:amd64'))
|
||||
|
||||
|
||||
class AptitudeTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.aptitude = Aptitude(Mock())
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.aptitude.system.execute', return_value=(0, """
|
||||
gimp-cbmplugs^<none>^1.2.2-1build1^Distro Developers <distro-devel-discuss@lists.distro.com>^universe/graphics^plugins for The GIMP to import/export Commodore 64 files
|
||||
gimp-gmic^2.4.5-1.0^2.4.5-1.1^Distro Developers <distro-devel-discuss@lists.distro.com>^plugin^GREYC's Magic for Image Computing - GIMP Plugin
|
||||
gimp-gutenprint^5.3.3-4^5.3.3-4^Distro Developers <distro-devel-discuss@lists.distro.com>^plugin^print plugin for the GIMP
|
||||
gimp-help^<none>^<none>^<none>^^
|
||||
"""))
|
||||
def test_search__must_return_installed_and_not_installed_packages_with_updates(self, execute: Mock):
|
||||
query = 'gimp'
|
||||
res = [p for p in self.aptitude.search(query=query)]
|
||||
|
||||
execute.assert_called_once_with(f"aptitude search {query} -q -F '%p^%v^%V^%m^%s^%d' --disable-columns", shell=True)
|
||||
|
||||
exp = [
|
||||
DebianPackage(name='gimp-cbmplugs', version='1.2.2-1build1', latest_version='1.2.2-1build1',
|
||||
maintainer='Distro Developers',
|
||||
description='plugins for The GIMP to import/export Commodore 64 files',
|
||||
categories=('graphics',),
|
||||
installed=False, update=False),
|
||||
DebianPackage(name='gimp-gmic', version='2.4.5-1.0', latest_version='2.4.5-1.1',
|
||||
maintainer='Distro Developers',
|
||||
description="GREYC's Magic for Image Computing - GIMP Plugin",
|
||||
categories=('plugin',),
|
||||
installed=True, update=True),
|
||||
DebianPackage(name='gimp-gutenprint', version='5.3.3-4', latest_version='5.3.3-4',
|
||||
maintainer='Distro Developers',
|
||||
description="print plugin for the GIMP",
|
||||
categories=('plugin',),
|
||||
installed=True, update=False),
|
||||
]
|
||||
|
||||
self.assertEqual([p.__dict__ for p in exp], [p.__dict__ for p in res])
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.aptitude.system.execute', return_value=(0, """
|
||||
Package: firefox
|
||||
Version: 97.0+distro1+una
|
||||
State: installed (95.0.1+distro1.1+una), upgrade available (97.0+distro1+una)
|
||||
Automatically installed: no
|
||||
Priority: optional
|
||||
Section: web
|
||||
Maintainer: Distro Dev <root@distro.com>
|
||||
Architecture: amd64
|
||||
Uncompressed Size: 236 M
|
||||
PreDepends: distro-system-adjustments (>= 2021.12.16)
|
||||
Breaks: firefox-dbg (< 95.0.1+distro1+una), firefox-dev (< 95.0.1+distro1+una), firefox-geckodriver (< 95.0.1+distro1+una), firefox-mozsymbols (< 95.0.1+distro1+una)
|
||||
Replaces: firefox-dbg (< 95.0.1+distro1+una), firefox-dev (< 95.0.1+distro1+una), firefox-geckodriver (< 95.0.1+distro1+una), firefox-mozsymbols (< 95.0.1+distro1+una)
|
||||
Provides: gnome-www-browser, www-browser
|
||||
Description: The Firefox web browser
|
||||
The Mozilla Firefox Web Browser.
|
||||
|
||||
Package: gcc
|
||||
Version: 4:9.3.0-1distro2
|
||||
State: installed
|
||||
Automatically installed: no
|
||||
Priority: optional
|
||||
Section: devel
|
||||
Maintainer: Distro Developers <distro-devel-discuss@lists.distro.com>
|
||||
Architecture: amd64
|
||||
Uncompressed Size: 51,2 k
|
||||
Depends: cpp (= 4:9.3.0-1distro2), gcc-9 (>= 9.3.0-3~)
|
||||
Recommends: libc6-dev | libc-dev
|
||||
Suggests: gcc-multilib, make, manpages-dev, autoconf, automake, libtool, flex, bison, gdb, gcc-doc
|
||||
Conflicts: gcc-doc (< 1:2.95.3), gcc-doc:i386 (< 1:2.95.3), gcc:i386
|
||||
Provides: c-compiler, gcc-x86-64-linux-gnu (= 4:9.3.0-1distro2), gcc:amd64 (= 4:9.3.0-1distro2)
|
||||
Description: GNU C compiler
|
||||
This is the GNU C compiler, a fairly portable optimizing compiler for C.
|
||||
|
||||
This is a dependency package providing the default GNU C compiler.
|
||||
|
||||
"""))
|
||||
def test_show__all_attributes(self, execute: Mock):
|
||||
info = self.aptitude.show(('firefox', 'gcc'))
|
||||
execute.assert_called_once_with('aptitude show -q firefox gcc', shell=True,
|
||||
custom_env={**system.gen_env(global_interpreter=system.USE_GLOBAL_INTERPRETER,
|
||||
lang=''), 'LC_NUMERIC': ''})
|
||||
|
||||
expected = {
|
||||
'firefox': {
|
||||
'version': '97.0+distro1+una',
|
||||
'state': ('installed (95.0.1+distro1.1+una)', 'upgrade available (97.0+distro1+una)'),
|
||||
'automatically installed': 'no',
|
||||
'priority': 'optional',
|
||||
'section': 'web',
|
||||
'maintainer': 'Distro Dev <root@distro.com>',
|
||||
'architecture': 'amd64',
|
||||
'uncompressed size': 236000000,
|
||||
'predepends': ('distro-system-adjustments (>= 2021.12.16)', ),
|
||||
'breaks': ('firefox-dbg (< 95.0.1+distro1+una)', 'firefox-dev (< 95.0.1+distro1+una)',
|
||||
'firefox-geckodriver (< 95.0.1+distro1+una)', 'firefox-mozsymbols (< 95.0.1+distro1+una)'),
|
||||
'replaces': ('firefox-dbg (< 95.0.1+distro1+una)', 'firefox-dev (< 95.0.1+distro1+una)',
|
||||
'firefox-geckodriver (< 95.0.1+distro1+una)', 'firefox-mozsymbols (< 95.0.1+distro1+una)'),
|
||||
'provides': ('gnome-www-browser', 'www-browser'),
|
||||
'description': 'The Firefox web browser'
|
||||
},
|
||||
'gcc': {
|
||||
'version': '4:9.3.0-1distro2',
|
||||
'state': ('installed', ),
|
||||
'automatically installed': 'no',
|
||||
'priority': 'optional',
|
||||
'section': 'devel',
|
||||
'maintainer': 'Distro Developers <distro-devel-discuss@lists.distro.com>',
|
||||
'architecture': 'amd64',
|
||||
'uncompressed size': 51200,
|
||||
'depends': ('cpp (= 4:9.3.0-1distro2)', 'gcc-9 (>= 9.3.0-3~)'),
|
||||
'recommends': ('libc6-dev | libc-dev', ),
|
||||
'suggests': ('gcc-multilib', 'make', 'manpages-dev', 'autoconf', 'automake',
|
||||
'libtool', 'flex', 'bison', 'gdb', 'gcc-doc'),
|
||||
'conflicts': ('gcc-doc (< 1:2.95.3)', 'gcc-doc:i386 (< 1:2.95.3)', 'gcc:i386'),
|
||||
'provides': ('c-compiler', 'gcc-x86-64-linux-gnu (= 4:9.3.0-1distro2)', 'gcc:amd64 (= 4:9.3.0-1distro2)'),
|
||||
'description': 'GNU C compiler'
|
||||
}
|
||||
}
|
||||
|
||||
self.assertEqual(expected, info)
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.aptitude.system.execute', return_value=(0, """
|
||||
gir1.2-javascriptcoregtk-4.0^2.34.1-0distro0.20.04.1^2.34.4-0distro0.20.04.1^Distro Developers <distro-devel-discuss@lists.distro.com>^library^JavaScript engine library from WebKitGTK - GObject introspection data
|
||||
gir1.2-nm-1.0^1.22.10-1distro2.2^1.22.10-1distro2.3^Distro Developers <distro-devel-discuss@lists.distro.com>^library^GObject introspection data for the libnm library
|
||||
xwayland^2:1.20.13-1distro1~20.04.2^2:1.20.13-1distro1~20.04.2^Distro X-SWAT <distro-x@lists.distro.com>^X11^Xwayland X server
|
||||
"""))
|
||||
def test_read_installed__with_updates_available(self, execute: Mock):
|
||||
returned = [p for p in self.aptitude.read_installed()]
|
||||
execute.assert_called_once()
|
||||
|
||||
expected = [DebianPackage(name='gir1.2-javascriptcoregtk-4.0', version='2.34.1-0distro0.20.04.1',
|
||||
latest_version='2.34.4-0distro0.20.04.1',
|
||||
maintainer='Distro Developers', update=True, installed=True,
|
||||
categories=('library',),
|
||||
description='JavaScript engine library from WebKitGTK - GObject introspection data'),
|
||||
DebianPackage(name='gir1.2-nm-1.0', version='1.22.10-1distro2.2',
|
||||
latest_version='1.22.10-1distro2.3',
|
||||
maintainer='Distro Developers', update=True, installed=True,
|
||||
categories=('library',),
|
||||
description='GObject introspection data for the libnm library'),
|
||||
DebianPackage(name='xwayland', version='2:1.20.13-1distro1~20.04.2',
|
||||
latest_version='2:1.20.13-1distro1~20.04.2',
|
||||
maintainer='Distro X-SWAT', update=False, installed=True,
|
||||
categories=('X11',),
|
||||
description='Xwayland X server')
|
||||
]
|
||||
|
||||
self.assertEqual([p.__dict__ for p in expected], [p.__dict__ for p in returned])
|
||||
|
||||
def test_map_transaction_output__it_should_map_i386_packages(self):
|
||||
output = "\nThe following NEW packages will be installed:\n" \
|
||||
" gcc-12-base:i386{a} [12.1.0-2distro~22.04] <+272 kB> glib-networking:i386{a} [2.72.0-1] <+242 kB>\n" \
|
||||
"\nThe following packages will be REMOVED:\n" \
|
||||
" celluloid{a} [0.21-linux+distro] <-1066 kB> libpcre3:i386{a} [2:8.39-13distro0.22.04.1] <-714 kB>"
|
||||
|
||||
transaction = self.aptitude.map_transaction_output(output)
|
||||
to_install = {
|
||||
DebianPackage(name="gcc-12-base:i386",
|
||||
version="12.1.0-2distro~22.04",
|
||||
latest_version="12.1.0-2distro~22.04",
|
||||
transaction_size=272000.0
|
||||
),
|
||||
DebianPackage(name="glib-networking:i386", version="2.72.0-1",
|
||||
latest_version="2.72.0-1", transaction_size=242000.0)
|
||||
}
|
||||
self.assertEqual(to_install, {*transaction.to_install})
|
||||
|
||||
to_remove = {
|
||||
DebianPackage(name="celluloid",
|
||||
version="0.21-linux+distro",
|
||||
latest_version="0.21-linux+distro",
|
||||
transaction_size=-1066000.0
|
||||
),
|
||||
DebianPackage(name="libpcre3:i386", version="2:8.39-13distro0.22.04.1",
|
||||
latest_version="2:8.39-13distro0.22.04.1", transaction_size=-714000.0)
|
||||
}
|
||||
|
||||
self.assertEqual(to_remove, {*transaction.to_remove})
|
||||
@@ -1,99 +0,0 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch, Mock
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.api.abstract.controller import SearchResult
|
||||
from bauh.gems.debian.controller import DebianPackageManager
|
||||
from bauh.gems.debian.model import DebianPackage, DebianApplication
|
||||
|
||||
|
||||
class DebianPackageManagerTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.controller = DebianPackageManager(MagicMock())
|
||||
self.controller._apps_index = {}
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.controller.Aptitude.read_installed', return_value=iter((
|
||||
DebianPackage(name='gir1.2-javascriptcoregtk-4.0', version='2.34.1-0distro0.20.04.1',
|
||||
latest_version='2.34.1-0distro0.20.04.1',
|
||||
maintainer='Distro Developers', update=False, installed=True,
|
||||
description='JavaScript engine library from WebKitGTK - GObject introspection data'),
|
||||
DebianPackage(name='xwayland', version='2:1.20.13-1distro1~20.04.2',
|
||||
latest_version='2:1.20.13-1distro1~20.04.2',
|
||||
maintainer='Distro X-SWAT', update=False, installed=True,
|
||||
description='Xwayland X server')
|
||||
)))
|
||||
def test_read_installed__must_associated_packages_found_to_applications_if_appliable(self, read_installed: Mock):
|
||||
app = DebianApplication(name='xwayland', exe_path='xwayland', icon_path='xwayland', categories=('app',))
|
||||
|
||||
self.controller.__apps_index = {'xwayland': app}
|
||||
|
||||
result = self.controller.read_installed(disk_loader=None, pkg_types=None, internet_available=False)
|
||||
read_installed.assert_called_once()
|
||||
|
||||
self.assertIsNone(result.new)
|
||||
|
||||
expected = [DebianPackage(name='gir1.2-javascriptcoregtk-4.0', version='2.34.1-0distro0.20.04.1', latest_version='2.34.1-0distro0.20.04.1',
|
||||
maintainer='Distro Developers', update=False, installed=True,
|
||||
description='JavaScript engine library from WebKitGTK - GObject introspection data'),
|
||||
DebianPackage(name='xwayland', version='2:1.20.13-1distro1~20.04.2', latest_version='2:1.20.13-1distro1~20.04.2',
|
||||
maintainer='Distro X-SWAT', update=False, installed=True,
|
||||
description='Xwayland X server', app=app)
|
||||
]
|
||||
|
||||
self.assertEqual(expected, result.installed)
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.controller.Aptitude.read_installed', return_value=iter((
|
||||
DebianPackage(name='gir1.2-javascriptcoregtk-4.0', version='2.34.1-0distro0.20.04.1',
|
||||
latest_version='2.34.1-0distro0.20.04.1',
|
||||
maintainer='Distro Developers', update=False, installed=True,
|
||||
description='JavaScript engine library from WebKitGTK - GObject introspection data'),
|
||||
DebianPackage(name='xwayland', version='2:1.20.13-1distro1~20.04.2',
|
||||
latest_version='2:1.20.13-1distro1~20.04.2',
|
||||
maintainer='Distro X-SWAT', update=False, installed=True,
|
||||
description='Xwayland X server')
|
||||
)))
|
||||
def test_read_installed__internet_not_available(self, read_installed: Mock):
|
||||
result = self.controller.read_installed(disk_loader=None, pkg_types=None, internet_available=False)
|
||||
read_installed.assert_called_once()
|
||||
|
||||
self.assertIsNone(result.new)
|
||||
|
||||
expected = [DebianPackage(name='gir1.2-javascriptcoregtk-4.0', version='2.34.1-0distro0.20.04.1', latest_version='2.34.1-0distro0.20.04.1',
|
||||
maintainer='Distro Developers', update=False, installed=True,
|
||||
description='JavaScript engine library from WebKitGTK - GObject introspection data'),
|
||||
DebianPackage(name='xwayland', version='2:1.20.13-1distro1~20.04.2', latest_version='2:1.20.13-1distro1~20.04.2',
|
||||
maintainer='Distro X-SWAT', update=False, installed=True,
|
||||
description='Xwayland X server')
|
||||
]
|
||||
|
||||
self.assertEqual(expected, result.installed)
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.controller.Aptitude.read_installed')
|
||||
def test_search__must_return_empty_result_when_url(self, read_installed: Mock):
|
||||
words = 'i'
|
||||
res = self.controller.search(words=words, disk_loader=None, limit=-1, is_url=True)
|
||||
read_installed.assert_not_called()
|
||||
self.assertEqual(SearchResult.empty(), res)
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.controller.Aptitude.search', return_value=iter((
|
||||
DebianPackage(name='xpto', version='1.0', latest_version='1.0', installed=True, update=False, description=''),
|
||||
DebianPackage(name='test', version='1.0', latest_version='1.0', installed=False, update=False, description=''),
|
||||
DebianPackage(name='myapp', version='1.0', latest_version='1.0', installed=True, update=False, description=''),
|
||||
)))
|
||||
def test_search__returned_packages_should_be_associated_with_apps_if_appliable(self, search: Mock):
|
||||
app = DebianApplication(name='myapp', exe_path='myapp', icon_path='myapp',
|
||||
categories=('app',))
|
||||
self.controller._apps_index = {'myapp': app}
|
||||
|
||||
words = 'test'
|
||||
res = self.controller.search(words=words, disk_loader=None, limit=-1, is_url=False)
|
||||
search.assert_called_once_with(words)
|
||||
|
||||
self.assertEqual([DebianPackage(name='xpto', version='1.0', latest_version='1.0', installed=True, update=False, description=''),
|
||||
DebianPackage(name='myapp', version='1.0', latest_version='1.0', installed=True, update=False,
|
||||
description='', app=app)
|
||||
], res.installed)
|
||||
|
||||
self.assertEqual([DebianPackage(name='test', version='1.0', latest_version='1.0', installed=False, update=False, description='')],
|
||||
res.new)
|
||||
@@ -1,172 +0,0 @@
|
||||
import json
|
||||
import os.path
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock, patch, call
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.gems.debian.index import ApplicationsMapper, ApplicationIndexer
|
||||
from bauh.gems.debian.model import DebianApplication
|
||||
from tests.gems.debian import DEBIAN_TESTS_DIR
|
||||
|
||||
|
||||
def mock_read_file(fpath: str):
|
||||
if fpath.endswith('firefox.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=Firefox Web Browser
|
||||
Comment=Browse the World Wide Web
|
||||
GenericName=Web Browser
|
||||
Keywords=Internet;WWW;Browser;Web;Explorer
|
||||
Exec=firefox %u
|
||||
Terminal=false
|
||||
X-MultipleArgs=false
|
||||
Type=Application
|
||||
Icon=firefox
|
||||
Categories=GNOME;GTK;Network;WebBrowser;
|
||||
"""
|
||||
elif fpath.endswith('synaptic.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Name=Synaptic Package Manager
|
||||
GenericName=Package Manager
|
||||
Comment=Install, remove and upgrade software packages
|
||||
Exec=synaptic-pkexec
|
||||
Icon=synaptic
|
||||
Terminal=false
|
||||
Type=Application
|
||||
"""
|
||||
elif fpath.endswith('no-icon.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Name=No-icon
|
||||
Exec=no-icon
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=PackageManager;GTK;System;Settings;
|
||||
"""
|
||||
elif fpath.endswith('no-exe.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Name=No-exe
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=no-exe
|
||||
Categories=PackageManager;GTK;System;Settings;
|
||||
"""
|
||||
elif fpath.endswith('no-display.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Name=No-display
|
||||
Exec=no-display
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=no-display
|
||||
NoDisplay=true
|
||||
Categories=PackageManager;GTK;System;Settings;
|
||||
"""
|
||||
elif fpath.endswith('terminal-app.desktop'):
|
||||
return """
|
||||
[Desktop Entry]
|
||||
Name=Terminal-App
|
||||
Exec=terminal-app
|
||||
Terminal=true
|
||||
Type=Application
|
||||
Icon=terminal-app
|
||||
Categories=PackageManager;GTK;System;Settings;
|
||||
"""
|
||||
|
||||
|
||||
class ApplicationsMapperTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mapper = ApplicationsMapper(logger=Mock(), workers=1)
|
||||
|
||||
@patch(f'{__app_name__}.gems.debian.index.ApplicationsMapper._read_file', side_effect=mock_read_file)
|
||||
@patch(f'{__app_name__}.gems.debian.index.system.execute', return_value=(0, """
|
||||
firefox: /usr/share/applications/firefox.desktop
|
||||
app-install-data: /usr/share/app-install/desktop/firefox-launchpad-plugin.desktop
|
||||
app-install-data: /usr/share/app-install/desktop/firefox:firefox.desktop
|
||||
xfce4-helpers: /usr/share/xfce4/helpers/firefox.desktop
|
||||
synaptic: /usr/share/applications/synaptic.desktop
|
||||
no-icon: /usr/share/applications/no-icon.desktop
|
||||
no-exe: /usr/share/applications/no-exe.desktop
|
||||
no-display: /usr/share/applications/no-display.desktop
|
||||
terminal-app: /usr/share/applications/terminal-app.desktop
|
||||
"""))
|
||||
def test_map_executable_applications__return_applications_with_exec_and_icon(self, execute: Mock, read_file: Mock):
|
||||
apps = self.mapper.map_executable_applications()
|
||||
execute.assert_called_once_with('dpkg-query -S .desktop', shell=True)
|
||||
read_file.assert_has_calls([call('/usr/share/applications/firefox.desktop'),
|
||||
call('/usr/share/applications/synaptic.desktop'),
|
||||
call('/usr/share/applications/no-icon.desktop'),
|
||||
call('/usr/share/applications/no-exe.desktop')], any_order=True)
|
||||
|
||||
self.assertEqual({
|
||||
DebianApplication(name='firefox', exe_path='firefox %u', icon_path='firefox',
|
||||
categories=('GNOME', 'GTK', 'Network', 'WebBrowser')),
|
||||
DebianApplication(name='synaptic', exe_path='synaptic-pkexec', icon_path='synaptic',
|
||||
categories=None)
|
||||
}, apps)
|
||||
|
||||
|
||||
class ApplicationIndexerTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.update_idx_file_path = f'{DEBIAN_TESTS_DIR}/resources/apps_idx.json'
|
||||
self.update_idx_ts_file_path = f'{self.update_idx_file_path}.ts'
|
||||
self.app_indexer = ApplicationIndexer(logger=Mock(),
|
||||
index_file_path=self.update_idx_file_path)
|
||||
|
||||
if os.path.exists(self.update_idx_file_path):
|
||||
os.remove(self.update_idx_file_path)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if os.path.exists(self.update_idx_file_path):
|
||||
os.remove(self.update_idx_file_path)
|
||||
|
||||
if os.path.exists(self.update_idx_ts_file_path):
|
||||
os.remove(self.update_idx_ts_file_path)
|
||||
|
||||
def test_update_index(self):
|
||||
apps = {
|
||||
DebianApplication(name='firefox', exe_path='firefox %u', icon_path='firefox',
|
||||
categories=('GNOME', 'GTK', 'Network', 'WebBrowser')),
|
||||
DebianApplication(name='synaptic', exe_path='synaptic-pkexec', icon_path='synaptic',
|
||||
categories=None)
|
||||
}
|
||||
|
||||
self.app_indexer.update_index(apps)
|
||||
|
||||
self.assertTrue(os.path.exists(self.update_idx_file_path))
|
||||
|
||||
with open(self.update_idx_file_path) as f:
|
||||
index_content = f.read()
|
||||
|
||||
self.assertEqual({'firefox': {'exe_path': 'firefox %u', 'icon_path': 'firefox',
|
||||
'categories': ['GNOME', 'GTK', 'Network', 'WebBrowser']},
|
||||
'synaptic': {'exe_path': 'synaptic-pkexec', 'icon_path': 'synaptic',
|
||||
'categories': None}
|
||||
}, json.loads(index_content))
|
||||
|
||||
self.assertTrue(os.path.isfile(self.update_idx_ts_file_path))
|
||||
|
||||
with open(self.update_idx_ts_file_path) as f:
|
||||
ts_str = f.read()
|
||||
|
||||
try:
|
||||
float(ts_str)
|
||||
except ValueError:
|
||||
self.assertFalse(False, "index timestamp must be a float number")
|
||||
|
||||
def test_read_index(self):
|
||||
self.app_indexer._file_path = f'{DEBIAN_TESTS_DIR}/resources/app_idx_full.json'
|
||||
|
||||
expected = {
|
||||
DebianApplication(name='firefox', exe_path='firefox %u', icon_path='firefox',
|
||||
categories=('GNOME', 'GTK', 'Network', 'WebBrowser')),
|
||||
DebianApplication(name='synaptic', exe_path='synaptic-pkexec', icon_path='synaptic',
|
||||
categories=None)
|
||||
}
|
||||
|
||||
self.assertEqual(expected, {app for app in self.app_indexer.read_index()})
|
||||
Reference in New Issue
Block a user