diff --git a/.gitignore b/.gitignore
index c9e6723fd..2c17b5015 100755
--- a/.gitignore
+++ b/.gitignore
@@ -15,5 +15,6 @@ linux_dist/appimage/appimage-builder-cache
linux_dist/appimage/*.zsync
linux_dist/appimage/*.AppImage
linux_dist/appimage/*.gz
+linux_dist/appimage/*.gz.*
linux_dist/appimage/bauh-*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98e0f9cc5..af1dbf1ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [0.10.6] 2023-12-23
+### Features
+ - new **verified** filter for the management table
+
+### Improvements
+- AppImage
+ - supporting AppImages from GitLab repositories
+ - info window: displaying "unknown" when there is no mapped license
+- Arch
+ - parallelized installed packages reading
+ - parallelized search
+ - adding the AUR's URL on the package information dialog [#339](https://github.com/vinifmor/bauh/issues/339)
+- General
+ - new download implementation replaces `wget` (implemented in Python using the `requests` library)
+ - replaced the internet checking old host (`google.com`) by a more general one (`w3.org`) [#300](https://github.com/vinifmor/bauh/issues/300)
+ - faster exit (threaded calls won't be waited)
+ - adding a new project definition/setup (`pyproject.toml`) file to comply with the latest standards
+- UI
+ - faster package icons download
+ - faster packages filtering (`type`, `category`, `name`, etc... up to **95% less time**)
+ - the "Skip" button on the initialization panel is now enabled after 10 seconds [#310](https://github.com/vinifmor/bauh/issues/310)
+ - displaying the download progress for screenshots
+ - on the package information dialog is now possible to open fields associated with URLs in the browser [#340](https://github.com/vinifmor/bauh/issues/340)
+ - displaying a text warning before installing an unverified package (unverified = not verified by the system maintainers or a trustable source)
+ - at the moment the following packaging formats are considered completely **unverified**: AppImage, AUR, Web
+ - Snap supports both verified and unverified software
+ - minor reduction in the table loading time
+ - improvements to help with random widget centralisation issues
+ - more translations
+
+### Fixes
+- AppImage
+ - upgrade fails when the package was initially imported, but later available on bauh's database [#321](https://github.com/vinifmor/bauh/issues/321)
+- General
+ - random segmentation fault errors associated with threads and caching
+- Web
+ - search not working for some typed addresses (e.g: those returning 403)
+
+### Contributions
+- German translations by [Mape6](https://github.com/Mape6)
+- Russian translations by [KoromeloDev](https://github.com/KoromeloDev)
+- Fix [#329](https://github.com/vinifmor/bauh/issues/329) by [w568w](https://github.com/w568w)
+
+### Distribution
+- bauh's AppImage is now based on Debian bullseye and had a small size reduction
+
+
## [0.10.5] 2022-12-17
### Fixes
diff --git a/LICENSE b/LICENSE
index b3fc2d4a3..b9f066892 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
- Copyright (c) 2019-2023 Vinícius Moreira
+ Copyright (c) 2019 Vinícius Moreira
zlib/libpng license
diff --git a/MANIFEST.in b/MANIFEST.in
index f75b1cd09..73d75a301 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,7 @@
-include LICENSE CHANGELOG.md README.md
-include requirements.txt
+include LICENSE CHANGELOG.md README.md requirements.txt
recursive-include bauh/desktop *
+recursive-exclude bauh/desktop *.orig
recursive-include bauh/view/resources *
+recursive-exclude bauh/view/resources *.orig
recursive-include bauh/gems/*/resources *
-recursive-include tests *
+recursive-exclude bauh/gems/*/resources *.orig
diff --git a/README.md b/README.md
index edec81c50..ebfaf99aa 100644
--- a/README.md
+++ b/README.md
@@ -55,9 +55,16 @@ Key features
- `qt5dxcb-plugin` (or equivalent): the package name may vary from distribution
##### Steps
-- Download the .AppImage file attached with the latest release from https://github.com/vinifmor/bauh/releases.
-- Run the following command through a terminal: `chmod a+x bauh-${version}-x86_64.AppImage` (replace `${version}` by the respective downloaded version)
-- Launch it: `./bauh-${version}-x86_64.AppImage`
+- Download the .AppImage file attached with the latest release from https://github.com/vinifmor/bauh/releases
+- Generally desktop environment allow you to double-click the downloaded file to execute it. If that's not your case, follow the steps below from a console:
+ - `chmod a+x bauh-${version}-x86_64.AppImage` (replace `${version}` by the respective downloaded version)
+ - `./bauh-${version}-x86_64.AppImage`
+- If you want to integrate bauh to your desktop: click on bauh's settings menu ("sandwich") and then `Install bauh`
+
+
+
+
+
#### Ubuntu 20.04 based distros (Linux Mint, PopOS, ...)
@@ -77,11 +84,12 @@ Key features
- `aria2`: multi-threaded downloads
- `axel`: multi-threaded downloads alternative
- `libappindicator3-1`: tray-mode
-- `wget`, `sqlite3`, `fuse`: AppImage support
+- `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
@@ -125,8 +133,9 @@ makepkg -si
- `aria2`: multi-threaded downloads
- `axel`: multi-threaded downloads alternative
- `libappindicator-gtk2`: tray-mode (GTK2 desktop environments)
-- `libappindicator-gtk3`: tray-mode (GTK3 desktop environments)
-- `wget`, `sqlite`, `fuse2`, `fuse3`: AppImage support
+- `libappindicator-gtk3`: tray-mode (GTK3 desktop environments)
+- `xdg-utils`: to open URLs in the browser (`xdg-open`)
+- `sqlite`, `fuse2`, `fuse3`: AppImage support
- `flatpak`: Flatpak support
- `snapd`: Snap support
- `pacman`: ArchLinux package management support
diff --git a/bauh/__init__.py b/bauh/__init__.py
index f8fef96bf..ee5e95c4c 100644
--- a/bauh/__init__.py
+++ b/bauh/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '0.10.5'
+__version__ = '0.10.6'
__app_name__ = 'bauh'
import os
diff --git a/bauh/api/abstract/download.py b/bauh/api/abstract/download.py
index eead54d74..1eef87d49 100644
--- a/bauh/api/abstract/download.py
+++ b/bauh/api/abstract/download.py
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
-from typing import Iterable, List, Optional
+from typing import Iterable, List, Optional, Tuple
from bauh.api.abstract.handler import ProcessWatcher
@@ -31,7 +31,7 @@ def can_work(self) -> bool:
pass
@abstractmethod
- def get_supported_multithreaded_clients(self) -> Iterable[str]:
+ def get_supported_multithreaded_clients(self) -> Tuple[str, ...]:
pass
@abstractmethod
@@ -39,9 +39,9 @@ def is_multithreaded_client_available(self, name: str) -> bool:
pass
@abstractmethod
- def list_available_multithreaded_clients(self) -> List[str]:
+ def list_available_multithreaded_clients(self) -> Tuple[str, ...]:
pass
@abstractmethod
- def get_supported_clients(self) -> tuple:
+ def get_supported_clients(self) -> Tuple[str, ...]:
pass
diff --git a/bauh/api/http.py b/bauh/api/http.py
index 882b81f3f..f325acb04 100644
--- a/bauh/api/http.py
+++ b/bauh/api/http.py
@@ -19,14 +19,15 @@ def __init__(self, logger: logging.Logger, max_attempts: int = 2, timeout: int =
self.sleep = sleep
self.logger = logger
- def get(self, url: str, params: dict = None, headers: dict = None, allow_redirects: bool = True, ignore_ssl: bool = False, single_call: bool = False, session: bool = True) -> Optional[requests.Response]:
+ def get(self, url: str, params: dict = None, headers: dict = None, allow_redirects: bool = True, ignore_ssl: bool = False, single_call: bool = False,
+ session: bool = True, stream: bool = False) -> Optional[requests.Response]:
cur_attempts = 1
while cur_attempts <= self.max_attempts:
cur_attempts += 1
try:
- args = {'timeout': self.timeout, 'allow_redirects': allow_redirects}
+ args = {'timeout': self.timeout, 'allow_redirects': allow_redirects, 'stream': stream}
if params:
args['params'] = params
@@ -42,7 +43,7 @@ def get(self, url: str, params: dict = None, headers: dict = None, allow_redirec
else:
res = requests.get(url, **args)
- if res.status_code == 200:
+ if 200 <= res.status_code < 300:
return res
if single_call:
diff --git a/bauh/commons/internet.py b/bauh/commons/internet.py
index 4245191c7..e52f17021 100644
--- a/bauh/commons/internet.py
+++ b/bauh/commons/internet.py
@@ -11,7 +11,7 @@ def is_available(self) -> bool:
return False
try:
- socket.gethostbyname('google.com')
+ socket.gethostbyname("w3.org")
return True
except Exception:
return False
diff --git a/bauh/commons/regex.py b/bauh/commons/regex.py
new file mode 100644
index 000000000..f2fecbe21
--- /dev/null
+++ b/bauh/commons/regex.py
@@ -0,0 +1,3 @@
+import re
+
+RE_URL = re.compile(r"^https?://.+$")
diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py
index b25d09954..123707e6d 100644
--- a/bauh/gems/appimage/controller.py
+++ b/bauh/gems/appimage/controller.py
@@ -79,7 +79,7 @@ def __init__(self, context: ApplicationContext):
self.configman = AppImageConfigManager()
self._custom_actions: Optional[Iterable[CustomSoftwareAction]] = None
self._action_self_install: Optional[CustomSoftwareAction] = None
- self._app_github: Optional[str] = None
+ self._app_repository: Optional[str] = None
self._search_unfilled_attrs: Optional[Tuple[str, ...]] = None
self._suggestions_downloader: Optional[AppImageSuggestionsDownloader] = None
@@ -122,7 +122,7 @@ def install_file(self, root_password: Optional[str], watcher: ProcessWatcher) ->
else:
return False
- appim = AppImage(i18n=self.i18n, imported=True)
+ appim = AppImage(i18n=self.i18n, imported=True, manual_update=True)
appim.name = input_name.get_value().strip()
appim.local_file_path = file_chooser.file_path
appim.version = input_version.get_value()
@@ -168,6 +168,7 @@ def update_file(self, pkg: AppImage, root_password: Optional[str], watcher: Proc
pkg.local_file_path = file_chooser.file_path
pkg.version = input_version.get_value()
+ pkg.manual_update = True
reqs = UpgradeRequirements(to_install=None, to_remove=None, to_upgrade=[UpgradeRequirement(pkg=pkg)], cannot_upgrade=None)
return self.upgrade(reqs, root_password=root_password, watcher=watcher)
@@ -183,7 +184,7 @@ def _get_db_connection(self, db_path: str) -> sqlite3.Connection:
self.logger.warning(f"Could not get a connection for database '{db_path}'")
def _gen_app_key(self, app: AppImage):
- return f"{app.name.lower()}{app.github.lower() if app.github else ''}"
+ return f"{app.name.lower()}{app.repository.lower() if app.repository else ''}"
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
if is_url:
@@ -275,7 +276,7 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1
for tup in cursor.fetchall():
for app in installed_apps:
- if app.name.lower() == tup[0].lower() and (not app.github or app.github.lower() == tup[1].lower()):
+ if app.name.lower() == tup[0].lower() and (not app.repository or app.repository.lower() == tup[1].lower()):
continuous_version = app.version == 'continuous'
continuous_update = tup[2] == 'continuous'
@@ -368,6 +369,16 @@ def upgrade(self, requirements: UpgradeRequirements, root_password: Optional[str
download_data = None
+ # always changing the 'imported' field based on the 'manual_update' flag that means
+ # "the user is manually installing/updating the AppImage"
+ if not req.pkg.manual_update:
+ req.pkg.imported = False
+
+ if req.pkg.categories and "Imported" in req.pkg.categories:
+ # removing the imported category in case the file is not considered imported anymore
+ req.pkg.categories.remove("Imported")
+
+ # manual file updates do not required download it
if not req.pkg.imported:
download_data = self._download(req.pkg, watcher)
@@ -430,7 +441,7 @@ def uninstall(self, pkg: AppImage, root_password: Optional[str], watcher: Proces
return TransactionResult(success=True, installed=None, removed=[pkg])
def _add_self_latest_version(self, app: AppImage):
- if app.name == self.context.app_name and app.github == self.app_github and not app.url_download_latest_version:
+ if app.name == self.context.app_name and app.repository == self.app_repository and not app.url_download_latest_version:
history = self.get_history(app)
if not history or not history.history:
@@ -469,6 +480,9 @@ def get_info(self, pkg: AppImage) -> dict:
if data.get('symlink') and not os.path.islink(data['symlink']):
del data['symlink']
+ if not data.get("license"):
+ data["license"] = self.i18n["unknown"].lower()
+
return data
def _sort_release(self, rel: tuple):
@@ -486,7 +500,7 @@ def get_history(self, pkg: AppImage) -> PackageHistory:
try:
cursor = app_con.cursor()
- cursor.execute(query.FIND_APP_ID_BY_NAME_AND_GITHUB.format(pkg.name.lower(), pkg.github.lower() if pkg.github else ''))
+ cursor.execute(query.FIND_APP_ID_BY_REPO_AND_NAME.format(pkg.repository.lower() if pkg.repository else '', pkg.name.lower()))
app_tuple = cursor.fetchone()
if not app_tuple:
@@ -593,8 +607,8 @@ def _install(self, pkg: AppImage, watcher: ProcessWatcher, pre_downloaded_file:
Path(out_dir).mkdir(parents=True, exist_ok=True)
pkg.install_dir = out_dir
- if pkg.imported:
-
+ # when the package is being imported/upgraded there is no need to download it
+ if pkg.manual_update:
downloaded, file_name = True, pkg.local_file_path.split('/')[-1]
install_file_path = out_dir + '/' + file_name
@@ -725,10 +739,6 @@ def can_work(self) -> Tuple[bool, Optional[str]]:
if not self._is_sqlite3_available():
return False, self.i18n['missing_dep'].format(dep=bold('sqlite3'))
- if not self.file_downloader.can_work():
- download_clients = ', '.join(self.file_downloader.get_supported_clients())
- return False, self.i18n['appimage.missing_downloader'].format(clients=download_clients)
-
return True, None
def requires_root(self, action: SoftwareAction, pkg: AppImage) -> bool:
@@ -984,7 +994,7 @@ def update_database(self, root_password: Optional[str], watcher: ProcessWatcher)
@property
def search_unfilled_attrs(self) -> Tuple[str, ...]:
if self._search_unfilled_attrs is None:
- self._search_unfilled_attrs = ('icon_url', 'url_download_latest_version', 'author', 'license', 'github',
+ self._search_unfilled_attrs = ('icon_url', 'url_download_latest_version', 'author', 'license', 'repository',
'source', 'url_screenshot')
return self._search_unfilled_attrs
@@ -999,7 +1009,7 @@ def self_install(self, root_password: Optional[str], watcher: ProcessWatcher) ->
return False
app = AppImage(name=self.context.app_name, version=self.context.app_version,
- categories=['system'], author=self.context.app_name, github=self.app_github,
+ categories=['system'], author=self.context.app_name, repository=self.app_repository,
license='zlib/libpng')
res = self._install(pkg=app, watcher=watcher,
@@ -1080,11 +1090,11 @@ def action_self_install(self) -> CustomSoftwareAction:
return self._action_self_install
@property
- def app_github(self) -> str:
- if self._app_github is None:
- self._app_github = f'vinifmor/{self.context.app_name}'
+ def app_repository(self) -> str:
+ if self._app_repository is None:
+ self._app_repository = f"https://github.com/vinifmor/{self.context.app_name}"
- return self._app_github
+ return self._app_repository
@property
def suggestions_downloader(self) -> AppImageSuggestionsDownloader:
diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py
index 7afa291f3..927300c21 100644
--- a/bauh/gems/appimage/model.py
+++ b/bauh/gems/appimage/model.py
@@ -32,7 +32,8 @@ def actions_local_installation(cls) -> Tuple[CustomSoftwareAction, ...]:
def cached_attrs(cls) -> Tuple[str, ...]:
if cls.__cached_attrs is None:
cls.__cached_attrs = ('name', 'description', 'version', 'url_download', 'author', 'license', 'source',
- 'icon_path', 'github', 'categories', 'imported', 'install_dir', 'symlink')
+ 'icon_path', 'repository', 'categories', 'imported', 'install_dir',
+ 'symlink')
return cls.__cached_attrs
@@ -43,17 +44,17 @@ def re_many_spaces(cls) -> Pattern:
return cls.__re_many_spaces
- def __init__(self, name: str = None, description: str = None, github: str = None, source: str = None, version: str = None,
+ def __init__(self, name: str = None, description: str = None, repository: str = None, source: str = None, version: str = None,
url_download: str = None, url_icon: str = None, url_screenshot: str = None, license: str = None, author: str = None,
categories=None, icon_path: str = None, installed: bool = False,
url_download_latest_version: str = None, local_file_path: str = None, imported: bool = False,
i18n: I18n = None, install_dir: str = None, updates_ignored: bool = False,
- symlink: str = None, **kwargs):
+ symlink: str = None, manual_update: bool = False, github: str = None, **kwargs):
super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version,
icon_url=url_icon, license=license, description=description,
installed=installed)
self.source = source
- self.github = github
+ self.repository = repository if repository else (github if github else repository)
self.categories = (categories.split(',') if isinstance(categories, str) else categories) if categories else None
self.url_download = url_download
self.icon_path = icon_path
@@ -66,9 +67,10 @@ def __init__(self, name: str = None, description: str = None, github: str = None
self.install_dir = install_dir
self.updates_ignored = updates_ignored
self.symlink = symlink
+ self.manual_update = manual_update # True when the user is manually installing/upgrading an AppImage file
def __repr__(self):
- return "{} (name={}, github={})".format(self.__class__.__name__, self.name, self.github)
+ return f"{self.__class__.__name__} (name={self.name}, repository={self.repository})"
def can_be_installed(self):
return not self.installed and self.url_download
diff --git a/bauh/gems/appimage/query.py b/bauh/gems/appimage/query.py
index 6c39c5578..b641b3385 100644
--- a/bauh/gems/appimage/query.py
+++ b/bauh/gems/appimage/query.py
@@ -1,10 +1,12 @@
-APP_ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'url_screenshot', 'license', 'author', 'categories')
+APP_ATTRS = ('name', 'description', 'repository', 'source', 'version', 'url_download', 'url_icon',
+ 'url_screenshot', 'license', 'author', 'categories')
RELEASE_ATTRS = ('version', 'url_download', 'published_at')
-SEARCH_APPS_BY_NAME_OR_DESCRIPTION = "SELECT {} FROM apps".format(','.join(APP_ATTRS)) + " WHERE lower(name) LIKE '%{}%' or lower(description) LIKE '%{}%'"
-FIND_APP_ID_BY_NAME_AND_GITHUB = "SELECT id FROM apps WHERE lower(name) = '{}' and lower(github) = '{}'"
-FIND_APPS_BY_NAME = "SELECT name, github, version, url_download FROM apps WHERE lower(name) IN ({})"
+SEARCH_APPS_BY_NAME_OR_DESCRIPTION = f"SELECT {','.join(APP_ATTRS)} FROM apps" + \
+ " WHERE lower(name) LIKE '%{}%' or lower(description) LIKE '%{}%'"
+FIND_APP_ID_BY_REPO_AND_NAME = "SELECT id FROM apps WHERE lower(repository) = '{}' and lower(name) = '{}'"
+FIND_APPS_BY_NAME = "SELECT name, repository, version, url_download FROM apps WHERE lower(name) IN ({})"
FIND_APPS_BY_NAME_ONLY_NAME = "SELECT name FROM apps WHERE lower(name) IN ({})"
FIND_APPS_BY_NAME_FULL = "SELECT {} FROM apps".format(','.join(APP_ATTRS)) + " WHERE lower(name) IN ({})"
FIND_RELEASES_BY_APP_ID = "SELECT {} FROM releases".format(','.join(RELEASE_ATTRS)) + " WHERE app_id = {}"
diff --git a/bauh/gems/appimage/resources/__init__.py b/bauh/gems/appimage/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/appimage/resources/img/__init__.py b/bauh/gems/appimage/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/appimage/resources/locale/__init__.py b/bauh/gems/appimage/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca
index 49bc7487b..f726bc89d 100644
--- a/bauh/gems/appimage/resources/locale/ca
+++ b/bauh/gems/appimage/resources/locale/ca
@@ -33,6 +33,7 @@ appimage.info.icon_path=icona
appimage.info.imported.false=No
appimage.info.imported.true=Yes
appimage.info.install_dir=Installation directory
+appimage.info.repository=Repositori
appimage.info.symlink=Symlink
appimage.info.url_download=URL del fitxer
appimage.install.appimagelauncher.error={appimgl} no permet la instal·lació de {app}. Desinstal·leu {appimgl}, reinicieu el sistema i torneu a provar d’instal·lar {app}.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=S’està creant una drecera del menú
appimage.install.extract=S’està extraient el contingut de {}
appimage.install.imported.rename_error=It was not possible to move the file {} to {}
appimage.install.permission=S’està concedint el permís d’execució a {}
-appimage.missing_downloader=No download client installed ({clients})
appimage.update_database.deleting_old=Removing old files
appimage.update_database.downloading=Downloading database files
appimage.update_database.uncompressing=Uncompressing files
diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de
index 631ab3e93..a7221a622 100644
--- a/bauh/gems/appimage/resources/locale/de
+++ b/bauh/gems/appimage/resources/locale/de
@@ -33,6 +33,7 @@ appimage.info.icon_path=Symbol
appimage.info.imported.false=Nein
appimage.info.imported.true=Ja
appimage.info.install_dir=Installationsverzeichnis
+appimage.info.repository=Repository
appimage.info.symlink=Symlink
appimage.info.url_download=Datei-URL
appimage.install.appimagelauncher.error={appimgl} lässt nicht zu, dass {app} installiert wird. Deinstallieren Sie {appimgl}, starten Sie Ihr System neu und versuchen Sie erneut, {app} zu installieren.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Ein Tastenkürzel für das Menü festlegen
appimage.install.extract=Extrahieren des Inhalts von {}
appimage.install.imported.rename_error=Es war nicht möglich, die Datei {} nach {} zu verschieben
appimage.install.permission=Erteilung der Ausführungsberechtigung für {}
-appimage.missing_downloader=Kein Download-Client installiert ({clients})
appimage.update_database.deleting_old=Entfernen von alten Dateien
appimage.update_database.downloading=Herunterladen von Datenbankdateien
appimage.update_database.uncompressing=Dekomprimierung von Dateien
diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en
index 487426a9a..609bd590f 100644
--- a/bauh/gems/appimage/resources/locale/en
+++ b/bauh/gems/appimage/resources/locale/en
@@ -33,6 +33,7 @@ appimage.info.icon_path=icon
appimage.info.imported.false=No
appimage.info.imported.true=Yes
appimage.info.install_dir=Installation directory
+appimage.info.repository=Repository
appimage.info.symlink=Symlink
appimage.info.url_download=File URL
appimage.install.appimagelauncher.error={appimgl} is not allowing {app} to be installed. Uninstall {appimgl}, reboot your system and try to install {app} again.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Generating a menu shortcut
appimage.install.extract=Extracting the content from {}
appimage.install.imported.rename_error=It was not possible to move the file {} to {}
appimage.install.permission=Giving execution permission to {}
-appimage.missing_downloader=No download client installed ({clients})
appimage.update_database.deleting_old=Removing old files
appimage.update_database.downloading=Downloading database files
appimage.update_database.uncompressing=Uncompressing files
diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es
index e98a0ccb1..b8b038daa 100644
--- a/bauh/gems/appimage/resources/locale/es
+++ b/bauh/gems/appimage/resources/locale/es
@@ -33,6 +33,7 @@ appimage.info.icon_path=icono
appimage.info.imported.false=No
appimage.info.imported.true=Sí
appimage.info.install_dir=Directorio de instalación
+appimage.info.repository=Repositorio
appimage.info.symlink=Link simbólico
appimage.info.url_download=URL del archivo
appimage.install.appimagelauncher.error={appimgl} no permite la instalación de {app}. Desinstale {appimgl}, reinicie su sistema e intente instalar {app} nuevamente.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Creando un atajo en el menú
appimage.install.extract=Extrayendo el contenido de {}
appimage.install.imported.rename_error=No fue posible mover el archivo {} a {}
appimage.install.permission=Concediendo permiso de ejecución a {}
-appimage.missing_downloader=No hay ningún cliente de descarga instalado ({clients})
appimage.update_database.deleting_old=Eliminando archivos antiguos
appimage.update_database.downloading=Descargando archivos de la base de datos
appimage.update_database.uncompressing=Descomprindo archivos
diff --git a/bauh/gems/appimage/resources/locale/fr b/bauh/gems/appimage/resources/locale/fr
index 996bf8f9f..7019c30ec 100644
--- a/bauh/gems/appimage/resources/locale/fr
+++ b/bauh/gems/appimage/resources/locale/fr
@@ -33,6 +33,7 @@ appimage.info.icon_path=icône
appimage.info.imported.false=Non
appimage.info.imported.true=Oui
appimage.info.install_dir=Répertoire d'installation
+appimage.info.repository=Dépôt
appimage.info.symlink=Symlink
appimage.info.url_download=URL du fichier
appimage.install.appimagelauncher.error={appimgl} n'autorise pas {app} à être installé. Désinstallez {appimgl}, redemarrez, puis essayez d'installer {app} de nouveau.
@@ -41,7 +42,6 @@ appimage.install.download.error=Échec du téléchargement du fichier {}. Le ser
appimage.install.extract=Extractiion du contenu de {}
appimage.install.imported.rename_error=Impossible de déplacer {} vers {}
appimage.install.permission={} est maintenant exécutable
-appimage.missing_downloader=No download client installed ({clients})
appimage.update_database.deleting_old=Removing old files
appimage.update_database.downloading=Downloading database files
appimage.update_database.uncompressing=Uncompressing files
diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it
index 9ce0ce39d..2900fc126 100644
--- a/bauh/gems/appimage/resources/locale/it
+++ b/bauh/gems/appimage/resources/locale/it
@@ -33,6 +33,7 @@ appimage.info.icon_path=icona
appimage.info.imported.false=No
appimage.info.imported.true=Yes
appimage.info.install_dir=Installation directory
+appimage.info.repository=Deposito
appimage.info.symlink=Symlink
appimage.info.url_download=File URL
appimage.install.appimagelauncher.error={appimgl} non consente l'installazione di {app}. Disinstallare {appimgl}, riavviare il sistema e provare a installare nuovamente {app}.
@@ -41,7 +42,6 @@ appimage.install.download.error=Non è stato possibile scaricare il file {}. Il
appimage.install.extract=Estrarre il contenuto da {}
appimage.install.imported.rename_error=It was not possible to move the file {} to {}
appimage.install.permission=Gdare il permesso di esecuzione a {}
-appimage.missing_downloader=No download client installed ({clients})
appimage.update_database.deleting_old=Removing old files
appimage.update_database.downloading=Downloading database files
appimage.update_database.uncompressing=Uncompressing files
diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt
index 572d8d05a..d421aacdd 100644
--- a/bauh/gems/appimage/resources/locale/pt
+++ b/bauh/gems/appimage/resources/locale/pt
@@ -33,6 +33,7 @@ appimage.info.icon_path=ícone
appimage.info.imported.false=Não
appimage.info.imported.true=Sim
appimage.info.install_dir=Diretório de instalação
+appimage.info.repository=Repositório
appimage.info.symlink=Link simbólico
appimage.info.url_download=URL do arquivo
appimage.install.appimagelauncher.error={appimgl} não está permitindo que o {app} seja instalado. Desinstale o {appimgl}, reinicie o sistema e tente instalar o {app} novamente.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Criando um atalho no menu
appimage.install.extract=Extraindo o conteúdo de {}
appimage.install.imported.rename_error=Não foi possível mover o arquivo {} para {}
appimage.install.permission=Concedendo permissão de execução para {}
-appimage.missing_downloader=Nenhum cliente de para download está instalado ({clients})
appimage.update_database.deleting_old=Removendo arquicos antigos
appimage.update_database.downloading=Baixando arquivos do banco de dados
appimage.update_database.uncompressing=Descomprimindo arquivos
diff --git a/bauh/gems/appimage/resources/locale/ru b/bauh/gems/appimage/resources/locale/ru
index c48754fb5..f6fb3c99d 100644
--- a/bauh/gems/appimage/resources/locale/ru
+++ b/bauh/gems/appimage/resources/locale/ru
@@ -33,6 +33,7 @@ appimage.info.icon_path=Значок
appimage.info.imported.false=Нет
appimage.info.imported.true=Да
appimage.info.install_dir=Директория установки
+appimage.info.repository=Pепозиторий
appimage.info.symlink=Символическая ссылка
appimage.info.url_download=URL Файла
appimage.install.appimagelauncher.error={appimgl} не позволяет установить {app} . Удалите {appimgl}, перезагрузите ваш компьютер и попробуйте снова установить {app}.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Создать ярлык в меню
appimage.install.extract=Извлечь содержимое из {}
appimage.install.imported.rename_error=Не удалось переместить файл {} в {}
appimage.install.permission=Установить права на выполнение для {}
-appimage.missing_downloader=Не установлен клиент загрузки ({clients})
appimage.update_database.deleting_old=Удаление старых файлов
appimage.update_database.downloading=Загрузка файлов базы данных
appimage.update_database.uncompressing=Распаковка файлов
diff --git a/bauh/gems/appimage/resources/locale/tr b/bauh/gems/appimage/resources/locale/tr
index a2b83e0fe..c3d54667d 100644
--- a/bauh/gems/appimage/resources/locale/tr
+++ b/bauh/gems/appimage/resources/locale/tr
@@ -33,6 +33,7 @@ appimage.info.icon_path=simge
appimage.info.imported.false=Hayır
appimage.info.imported.true=Evet
appimage.info.install_dir=Kurulum dizini
+appimage.info.repository=Depo
appimage.info.symlink=Symlink
appimage.info.url_download=Dosya Bağlantısı
appimage.install.appimagelauncher.error={appimgl}, {app} uygulamasının yüklenmesine izin vermiyor. {appimgl} yazılımını kaldırın, sisteminizi yeniden başlatın ve {app} yazılımını tekrar yüklemeyi deneyin.
@@ -40,7 +41,6 @@ appimage.install.desktop_entry=Bir menü kısayolu oluşturuluyor
appimage.install.extract={} 'den içerik ayıklanıyor
appimage.install.imported.rename_error={} dosyasını {} klasörüne taşımak mümkün değildi
appimage.install.permission={} için yürütme izni veriliyor
-appimage.missing_downloader=No download client installed ({clients})
appimage.update_database.deleting_old=Removing old files
appimage.update_database.downloading=Downloading database files
appimage.update_database.uncompressing=Uncompressing files
diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py
index 22c982b9c..02b43bba7 100644
--- a/bauh/gems/arch/controller.py
+++ b/bauh/gems/arch/controller.py
@@ -11,7 +11,7 @@
from pathlib import Path
from pwd import getpwnam
from threading import Thread
-from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection, Generator
+from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection, Generator, Any
from dateutil.parser import parse as parse_date
@@ -1332,7 +1332,7 @@ def _map_actual_replacers(self, names: Set[str], context: TransactionContext) ->
thread_fill_aur = None
if aur_replacers:
- thread_fill_aur = Thread(target=self._fill_aur_providers, args=(aur_replacers, actual_replacers))
+ thread_fill_aur = Thread(target=self._fill_aur_providers, args=(aur_replacers, actual_replacers), daemon=True)
thread_fill_aur.start()
if repo_replacers:
@@ -1551,84 +1551,95 @@ def uninstall(self, pkg: ArchPackage, root_password: Optional[str], watcher: Pro
def get_managed_types(self) -> Set["type"]:
return {ArchPackage}
- def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict:
- fill_pkgbuild = Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True)
- fill_pkgbuild.start()
-
- if pkg.installed:
- info = pacman.get_info_dict(pkg.name)
-
- if info is not None:
- self._parse_dates_string_from_info(pkg.name, info)
-
- info['04_orphan'] = pkg.orphan
- info['04_out_of_date'] = pkg.out_of_date
+ def _map_info_aur_installed(self, pkg: ArchPackage, pkgbuild_thread: Thread) -> Dict[str, Any]:
+ info = pacman.get_info_dict(pkg.name)
- if pkg.commit:
- info['commit'] = pkg.commit
+ if info is not None:
+ self._parse_dates_string_from_info(pkg.name, info)
- if pkg.last_modified:
- info['last_modified'] = self._parse_timestamp(ts=pkg.last_modified,
- error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified))
+ info['04_orphan'] = pkg.orphan
+ info['04_out_of_date'] = pkg.out_of_date
- info['14_installed_files'] = pacman.list_installed_files(pkg.name)
+ if pkg.commit:
+ info['commit'] = pkg.commit
- fill_pkgbuild.join()
+ if pkg.last_modified:
+ err_msg = f"Could not parse AUR package '{pkg.name}' 'last_modified' field ({pkg.last_modified})"
+ info['last_modified'] = self._parse_timestamp(ts=pkg.last_modified, error_msg=err_msg)
- if pkg.pkgbuild:
- info['13_pkg_build'] = pkg.pkgbuild
+ info['14_installed_files'] = pacman.list_installed_files(pkg.name)
- return info
- else:
- info = {
- '01_id': pkg.id,
- '02_name': pkg.name,
- '03_description': pkg.description,
- '03_version': pkg.version,
- '04_orphan': pkg.orphan,
- '04_out_of_date': pkg.out_of_date,
- '04_popularity': pkg.popularity,
- '05_votes': pkg.votes,
- '06_package_base': pkg.package_base,
- '07_maintainer': pkg.maintainer,
- '10_url': pkg.url_download
- }
+ pkgbuild_thread.join()
- if pkg.first_submitted:
- info['08_first_submitted'] = self._parse_timestamp(ts=pkg.first_submitted,
- error_msg="Could not parse AUR package '{}' 'first_submitted' field ({})".format(pkg.name, pkg.first_submitted))
+ if pkg.pkgbuild:
+ info['13_pkg_build'] = pkg.pkgbuild
- if pkg.last_modified:
- info['09_last_modified'] = self._parse_timestamp(ts=pkg.last_modified,
- error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified))
+ return info
- srcinfo = self.aur_client.get_src_info(pkg.name)
+ def _map_info_aur_uninstalled(self, pkg: ArchPackage, pkgbuild_thread: Thread) -> Dict[str, Any]:
+ info = {
+ '01_id': pkg.id,
+ '02_name': pkg.name,
+ '03_description': pkg.description,
+ '03_version': pkg.version,
+ '04_orphan': pkg.orphan,
+ '04_out_of_date': pkg.out_of_date,
+ '04_popularity': pkg.popularity,
+ '05_votes': pkg.votes,
+ '06_package_base': pkg.package_base,
+ '07_maintainer': pkg.maintainer,
+ '10_url': pkg.url_download
+ }
+
+ if pkg.first_submitted:
+ info['08_first_submitted'] = self._parse_timestamp(ts=pkg.first_submitted,
+ error_msg="Could not parse AUR package '{}' 'first_submitted' field ({})".format(
+ pkg.name, pkg.first_submitted))
+
+ if pkg.last_modified:
+ info['09_last_modified'] = self._parse_timestamp(ts=pkg.last_modified,
+ error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(
+ pkg.name, pkg.last_modified))
+
+ srcinfo = self.aur_client.get_src_info(pkg.name)
+
+ if srcinfo:
+ arch_str = 'x86_64' if self.context.is_system_x86_64() else 'i686'
+ for info_attr, src_attr in {'12_makedepends': 'makedepends',
+ '13_dependson': 'depends',
+ '14_optdepends': 'optdepends',
+ 'checkdepends': '15_checkdepends'}.items():
+ if srcinfo.get(src_attr):
+ info[info_attr] = [*srcinfo[src_attr]]
+
+ arch_attr = f"{src_attr}_{arch_str}"
+
+ if srcinfo.get(arch_attr):
+ if not info.get(info_attr):
+ info[info_attr] = [*srcinfo[arch_attr]]
+ else:
+ info[info_attr].extend(srcinfo[arch_attr])
- if srcinfo:
- arch_str = 'x86_64' if self.context.is_system_x86_64() else 'i686'
- for info_attr, src_attr in {'12_makedepends': 'makedepends',
- '13_dependson': 'depends',
- '14_optdepends': 'optdepends',
- 'checkdepends': '15_checkdepends'}.items():
- if srcinfo.get(src_attr):
- info[info_attr] = [*srcinfo[src_attr]]
+ pkgbuild_thread.join()
- arch_attr = '{}_{}'.format(src_attr, arch_str)
+ if pkg.pkgbuild:
+ info['00_pkg_build'] = pkg.pkgbuild
+ else:
+ info['11_pkg_build_url'] = pkg.get_pkg_build_url()
- if srcinfo.get(arch_attr):
- if not info.get(info_attr):
- info[info_attr] = [*srcinfo[arch_attr]]
- else:
- info[info_attr].extend(srcinfo[arch_attr])
+ return info
- fill_pkgbuild.join()
+ def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict:
+ fill_pkgbuild = Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True)
+ fill_pkgbuild.start()
- if pkg.pkgbuild:
- info['00_pkg_build'] = pkg.pkgbuild
- else:
- info['11_pkg_build_url'] = pkg.get_pkg_build_url()
+ if pkg.installed:
+ info = self._map_info_aur_installed(pkg, fill_pkgbuild)
+ else:
+ info = self._map_info_aur_uninstalled(pkg, fill_pkgbuild)
- return info
+ info["00_url"] = f"https://aur.archlinux.org/packages/{pkg.get_base_name()}"
+ return info
def _parse_dates_string_from_info(self, pkgname: str, info: dict):
for date_attr in ('install date', 'build date'):
@@ -3763,15 +3774,15 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
name_priority = dict()
- fill_suggestions = Thread(target=self._fill_suggestions, args=(name_priority,))
+ fill_suggestions = Thread(target=self._fill_suggestions, args=(name_priority,), daemon=True)
fill_suggestions.start()
available_packages = dict()
- fill_available = Thread(target=self._fill_available_packages, args=(available_packages,))
+ fill_available = Thread(target=self._fill_available_packages, args=(available_packages,), daemon=True)
fill_available.start()
ignored_pkgs = set()
- fill_ignored = Thread(target=pacman.fill_ignored_packages, args=(ignored_pkgs,))
+ fill_ignored = Thread(target=pacman.fill_ignored_packages, args=(ignored_pkgs,), daemon=True)
fill_ignored.start()
fill_suggestions.join()
@@ -3806,7 +3817,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
if filter_installed:
ignored_updates = set()
- thread_fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,))
+ thread_fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,), daemon=True)
thread_fill_ignored_updates.start()
else:
ignored_updates, thread_fill_ignored_updates = None, None
@@ -3854,7 +3865,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
update_ignored=pkg_updates_ignored)
if disk_loader:
- t = Thread(target=self._fill_cached_if_unset, args=(pkg, disk_loader))
+ t = Thread(target=self._fill_cached_if_unset, args=(pkg, disk_loader), daemon=True)
t.start()
caching_threads.append(t)
diff --git a/bauh/gems/arch/dependencies.py b/bauh/gems/arch/dependencies.py
index f0b2f2eae..7ae70db88 100644
--- a/bauh/gems/arch/dependencies.py
+++ b/bauh/gems/arch/dependencies.py
@@ -68,7 +68,7 @@ def get_missing_packages(self, names: Set[str], repository: Optional[str] = None
if not repository:
for name in missing_names:
- t = Thread(target=self._fill_repository, args=(name, missing_root))
+ t = Thread(target=self._fill_repository, args=(name, missing_root), daemon=True)
t.start()
threads.append(t)
@@ -518,7 +518,8 @@ def _fill_single_providers_data(self, all_missing_deps: Iterable[Tuple[str, str]
if aur_providers_no_data:
aur_providers_data = dict()
aur_data_filler = Thread(target=self._fill_aur_updates_data,
- args=(aur_providers_no_data, aur_providers_data))
+ args=(aur_providers_no_data, aur_providers_data),
+ daemon=True)
aur_data_filler.start()
if repo_providers_no_data:
diff --git a/bauh/gems/arch/download.py b/bauh/gems/arch/download.py
index 09513473b..e4e2d21bc 100644
--- a/bauh/gems/arch/download.py
+++ b/bauh/gems/arch/download.py
@@ -88,7 +88,9 @@ def download_package(self, pkg: Dict[str, str], root_password: Optional[str], su
watcher.print("Could not download '{}' from mirror '{}'".format(pkgname, mirror))
else:
self.logger.info("Package '{}' successfully downloaded".format(pkg['n']))
- t = Thread(target=self.download_package_signature, args=(pkg, url, output_path, root_password, watcher), daemon=True)
+ t = Thread(target=self.download_package_signature,
+ args=(pkg, url, output_path, root_password, watcher),
+ daemon=True)
t.start()
self.async_downloads_lock.acquire()
self.async_downloads.append(t)
diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py
index d7e368867..fd09525e8 100644
--- a/bauh/gems/arch/model.py
+++ b/bauh/gems/arch/model.py
@@ -288,3 +288,6 @@ def __hash__(self):
@property
def orphan(self) -> bool:
return self.maintainer is None
+
+ def is_trustable(self) -> bool:
+ return self.repository and self.repository != "aur"
diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py
index d9572d691..00cef089e 100644
--- a/bauh/gems/arch/pacman.py
+++ b/bauh/gems/arch/pacman.py
@@ -146,15 +146,14 @@ def map_packages(names: Optional[Iterable[str]] = None, remote: bool = False, si
if ignored:
for key in ('signed', 'not_signed'):
- to_del = set()
-
if pkgs.get(key):
+ to_del = set()
for pkg in pkgs[key].keys():
if pkg in ignored:
to_del.add(pkg)
- for pkg in to_del:
- del pkgs[key][pkg]
+ for pkg in to_del:
+ del pkgs[key][pkg]
return pkgs
diff --git a/bauh/gems/arch/resources/__init__.py b/bauh/gems/arch/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/arch/resources/img/__init__.py b/bauh/gems/arch/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/arch/resources/locale/__init__.py b/bauh/gems/arch/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca
index d1995e915..1cf570e4e 100644
--- a/bauh/gems/arch/resources/locale/ca
+++ b/bauh/gems/arch/resources/locale/ca
@@ -143,6 +143,7 @@ arch.downgrade.version_found=S’ha trobat la versió actual del paquet
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=PKGBUILD
+arch.info.00_url=URL
arch.info.01_id=identificació
arch.info.02_name=nom
arch.info.03_description=descripció
@@ -313,4 +314,3 @@ gem.arch.info=Software packages available for distributions based on Arch Linux
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Dipòsit
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=Els paquets AUR són mantinguts per una comunitat d’usuaris independent. No hi ha cap garantia que funcionin o que no danyin el vostre sistema.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de
index 1c0c9f96f..74bbc6e87 100644
--- a/bauh/gems/arch/resources/locale/de
+++ b/bauh/gems/arch/resources/locale/de
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Aktuelle Paketversion gefunden
arch.aur.error.missing_root_dep={dep} ist nicht installiert und wird für die Installation von {aur} Paketen als {root} Benutzer benötigt
arch.aur.error.add_builder_user=Es war nicht möglich, den Benutzer {user} für die Erzeugung von {aur}-Paketen zu erstellen
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=ID
arch.info.02_name=Name
arch.info.03_description=Beschreibung
@@ -313,4 +314,3 @@ gem.arch.info=Verfügbare Softwarepakete für Distributionen, die auf Arch Linux
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Repository
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=AUR-Pakete werden von einer unabhängigen Benutzergemeinschaft gepflegt. Es gibt keine Garantie, dass sie funktionieren oder Ihr System nicht beschädigen.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en
index d68de2e81..e75fd066e 100644
--- a/bauh/gems/arch/resources/locale/en
+++ b/bauh/gems/arch/resources/locale/en
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Current package version found
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=id
arch.info.02_name=name
arch.info.03_description=description
@@ -313,4 +314,3 @@ gem.arch.info=Software packages available for distributions based on Arch Linux
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Repository
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=AUR packages are maintained by an independent user community. There is no warranty they will work or not harm your system.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es
index 4a9faebdb..7cdb770f5 100644
--- a/bauh/gems/arch/resources/locale/es
+++ b/bauh/gems/arch/resources/locale/es
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Version actual del paquete encontrada
arch.aur.error.missing_root_dep={dep} no está instalado y es necesario para la instalación de paquetes del {aur} como el usuario {root}
arch.aur.error.add_builder_user=No fue posible crear el usuario {user} para construir paquetes del {aur}
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=id
arch.info.02_name=nombre
arch.info.03_description=descripción
@@ -313,4 +314,3 @@ gem.arch.info=Paquetes de software disponibles para distribuciones basadas en Ar
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Repositorio
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=Los paquetes AUR son mantenidos por una comunidad de usuarios independiente. No hay garantía de que funcionen o que no dañen su sistema.
diff --git a/bauh/gems/arch/resources/locale/fr b/bauh/gems/arch/resources/locale/fr
index d9e513428..47e819e50 100644
--- a/bauh/gems/arch/resources/locale/fr
+++ b/bauh/gems/arch/resources/locale/fr
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Version actuelle du paquet trouvée
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=id
arch.info.02_name=nom
arch.info.03_description=description
@@ -313,4 +314,3 @@ gem.arch.info=Paquets logiciels disponibles pour les distributions basées sur A
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Dépôt
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=Les paquets AUR sont maintenus par une communauté indépendante. Il n'y a aucune garantie qu'ils fonctionneront et qu'ils n'endommageront pas votre système.
diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it
index 7dd9e9f79..6b05004d3 100644
--- a/bauh/gems/arch/resources/locale/it
+++ b/bauh/gems/arch/resources/locale/it
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Trovata la versione del pacchetto corrente
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=id
arch.info.02_name=nome
arch.info.03_description=descrizione
@@ -313,4 +314,3 @@ gem.arch.info=Software packages available for distributions based on Arch Linux
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Deposito
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=I pacchetti AUR sono gestiti da una comunità di utenti indipendenti. Non esiste alcuna garanzia che funzionino o non danneggino il sistema.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt
index 9823ae760..6c0512726 100644
--- a/bauh/gems/arch/resources/locale/pt
+++ b/bauh/gems/arch/resources/locale/pt
@@ -142,6 +142,7 @@ arch.downgrade.version_found=Versão atual do pacote encontrada
arch.aur.error.missing_root_dep={dep} não está instalado e é necessário para a instalação de pacotes do {aur} como o usuário {root}
arch.aur.error.add_builder_user=Não foi possível criar o usuário {user} para a construção de pacotes do {aur}
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=id
arch.info.02_name=nome
arch.info.03_description=descrição
@@ -312,4 +313,3 @@ gem.arch.info=Pacotes de software disponíveis para distribuições baseadas em
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Repositório
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=Pacotes do AUR são mantidos por uma comunidade de usuários independente. Não há garantia que funcionarão ou que não prejudicarão o seus sistema.
diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru
index ba5d74489..c6cd95dde 100644
--- a/bauh/gems/arch/resources/locale/ru
+++ b/bauh/gems/arch/resources/locale/ru
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Найдена текущая версия паке
arch.aur.error.missing_root_dep={dep} не установлен и необходим для установки пакетов {aur} от имени пользователя {root}
arch.aur.error.add_builder_user=Не удалось создать пользователя {user} для сборки пакетов {aur}.
arch.info.00_pkg_build=PKGBUILD
+arch.info.00_url=URL
arch.info.01_id=Идентификатор
arch.info.02_name=Имя
arch.info.03_description=Описание
@@ -313,4 +314,3 @@ gem.arch.info=Пакеты программного обеспечения, до
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Репозиторий
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=AUR-пакеты поддерживаются независимым сообществом пользователей. Нет никакой гарантии, что они будут работать или не навредят вашей системе.
diff --git a/bauh/gems/arch/resources/locale/tr b/bauh/gems/arch/resources/locale/tr
index 9a9431283..8aeb0f48d 100644
--- a/bauh/gems/arch/resources/locale/tr
+++ b/bauh/gems/arch/resources/locale/tr
@@ -143,6 +143,7 @@ arch.downgrade.version_found=Geçerli paket sürümü bulundu
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild
+arch.info.00_url=URL
arch.info.01_id=kimlik
arch.info.02_name=isim
arch.info.03_description=açıklama
@@ -313,4 +314,3 @@ gem.arch.info=Arch Linux tabanlı dağıtımlar için mevcut yazılım paketleri
gem.arch.label=Arch
gem.arch.type.arch_repo.label=Arch - Depoları
gem.arch.type.aur.label=Arch - AUR
-gem.aur.install.warning=AUR paketleri bağımsız bir Arch Kullanıcı Topluluğu tarafından sağlanır. Çalışacaklarının veya sisteminize zarar vermeyeceklerinin garantisi yoktur.
diff --git a/bauh/gems/debian/controller.py b/bauh/gems/debian/controller.py
index 6708aeb14..5f1182a5c 100644
--- a/bauh/gems/debian/controller.py
+++ b/bauh/gems/debian/controller.py
@@ -60,7 +60,7 @@ def _update_apps_index(self, apps: Iterable[DebianApplication]):
def search(self, words: str, disk_loader: Optional[DiskCacheLoader], limit: int, is_url: bool) -> SearchResult:
config_ = dict()
- fill_config = Thread(target=self._fill_config, args=(config_,))
+ fill_config = Thread(target=self._fill_config, args=(config_,), daemon=True)
fill_config.start()
res = SearchResult.empty()
@@ -102,11 +102,11 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], pkg_types: Opti
names: Optional[Iterable[str]] = None) -> SearchResult:
config_ = dict()
- fill_config = Thread(target=self._fill_config, args=(config_,))
+ 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,))
+ fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,), daemon=True)
fill_ignored_updates.start()
threads = (fill_config, fill_ignored_updates)
@@ -170,7 +170,7 @@ def uninstall(self, pkg: DebianPackage, root_password: str, watcher: ProcessWatc
if deps:
# updates are required to be filled in case the dependencies are currently displayed on the view
updates = dict()
- fill_updates = Thread(target=self._fill_updates, args=(updates,))
+ fill_updates = 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'))
@@ -517,7 +517,7 @@ def prepare(self, task_manager: Optional[TaskManager], root_password: Optional[s
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
ignored_updates = set()
- fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,))
+ fill_ignored_updates = Thread(target=self._fill_ignored_updates, args=(ignored_updates,), daemon=True)
fill_ignored_updates.start()
updates = list()
@@ -551,7 +551,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
if filter_installed:
installed = set()
- fill_installed = Thread(target=self._fill_installed_names, args=(installed, ))
+ fill_installed = Thread(target=self._fill_installed_names, args=(installed,), daemon=True)
fill_installed.start()
else:
installed, fill_installed = None, None
diff --git a/bauh/gems/debian/model.py b/bauh/gems/debian/model.py
index a41888433..e506a373b 100644
--- a/bauh/gems/debian/model.py
+++ b/bauh/gems/debian/model.py
@@ -132,6 +132,9 @@ def is_update_ignored(self) -> bool:
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
diff --git a/bauh/gems/debian/resources/__init__.py b/bauh/gems/debian/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/debian/resources/img/__init__.py b/bauh/gems/debian/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/debian/resources/locale/__init__.py b/bauh/gems/debian/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py
index e1258c591..ac9bdebb7 100644
--- a/bauh/gems/flatpak/controller.py
+++ b/bauh/gems/flatpak/controller.py
@@ -135,7 +135,7 @@ def _fill_required_runtime_updates(self, output: Dict[str, List[Tuple[str, str]]
for installation in ('system', 'user'):
runtimes = list()
output[installation] = runtimes
- t = Thread(target=self._fill_required_runtimes, args=(installation, runtimes))
+ t = Thread(target=self._fill_required_runtimes, args=(installation, runtimes), daemon=True)
t.start()
threads.append(t)
@@ -150,11 +150,13 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1
thread_updates, thread_runtimes = None, None
if internet_available:
- thread_updates = Thread(target=self._add_updates, args=(version, updates))
+ thread_updates = Thread(target=self._add_updates, args=(version, updates), daemon=True)
thread_updates.start()
if version >= VERSION_1_12:
- thread_runtimes = Thread(target=self._fill_required_runtime_updates, args=(required_runtimes,))
+ thread_runtimes = Thread(target=self._fill_required_runtime_updates,
+ args=(required_runtimes,),
+ daemon=True)
thread_runtimes.start()
installed = flatpak.list_installed(version)
@@ -665,7 +667,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
cached_count += 1
else:
fill = Thread(target=self._fill_suggestion, args=(appid, ids_prios[appid], flatpak_version,
- remote, res))
+ remote, res), daemon=True)
fill.start()
fill_suggestions.append(fill)
diff --git a/bauh/gems/flatpak/flatpak.py b/bauh/gems/flatpak/flatpak.py
index dbc4d133b..bbb851135 100755
--- a/bauh/gems/flatpak/flatpak.py
+++ b/bauh/gems/flatpak/flatpak.py
@@ -195,7 +195,7 @@ def list_updates_as_str(version: Tuple[str, ...]) -> Dict[str, Set[str]]:
threads = []
for type_, output in (('system', sys_updates), ('user', user_updates)):
- fill = Thread(target=fill_updates, args=(version, type_, output))
+ fill = Thread(target=fill_updates, args=(version, type_, output), daemon=True)
fill.start()
threads.append(fill)
diff --git a/bauh/gems/flatpak/model.py b/bauh/gems/flatpak/model.py
index c44ff456b..381386e2f 100644
--- a/bauh/gems/flatpak/model.py
+++ b/bauh/gems/flatpak/model.py
@@ -159,6 +159,9 @@ def update_ref(self):
if self.id and self.arch and self.branch:
self.ref = f'{self.id}/{self.arch}/{self.branch}'
+ def is_trustable(self) -> bool:
+ return False
+
def __repr__(self) -> str:
return f'Flatpak (id={self.id}, branch={self.branch}, origin={self.origin}, installation={self.installation},' \
f' partial={self.partial}, update_component={self.update_component})'
diff --git a/bauh/gems/flatpak/resources/__init__.py b/bauh/gems/flatpak/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/flatpak/resources/img/__init__.py b/bauh/gems/flatpak/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/flatpak/resources/locale/__init__.py b/bauh/gems/flatpak/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py
index 03468e2a1..beb07b677 100644
--- a/bauh/gems/snap/controller.py
+++ b/bauh/gems/snap/controller.py
@@ -434,7 +434,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
res.append(cached_sug)
cached_count += 1
else:
- t = Thread(target=self._fill_suggestion, args=(name, ids_prios[name], snapd_client, res))
+ t = Thread(target=self._fill_suggestion, args=(name, ids_prios[name], snapd_client, res), daemon=True)
t.start()
threads.append(t)
time.sleep(0.001) # to avoid being blocked
diff --git a/bauh/gems/snap/resources/__init__.py b/bauh/gems/snap/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/snap/resources/img/__init__.py b/bauh/gems/snap/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/snap/resources/locale/__init__.py b/bauh/gems/snap/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py
index 02ab6f1f2..7e7d8ca35 100644
--- a/bauh/gems/web/controller.py
+++ b/bauh/gems/web/controller.py
@@ -253,12 +253,12 @@ def _request_url(self, url: str) -> Optional[Response]:
def _map_url(self, url: str) -> Tuple["BeautifulSoup", requests.Response]:
url_res = self._request_url(url)
- if url_res:
+ if url_res is not None and url_res.status_code != 404:
return BeautifulSoup(url_res.text, 'lxml', parse_only=SoupStrainer('head')), url_res
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
web_config = {}
- thread_config = Thread(target=self._fill_config_async, args=(web_config,))
+ thread_config = Thread(target=self._fill_config_async, args=(web_config,), daemon=True)
thread_config.start()
res = SearchResult([], [], 0)
@@ -1069,7 +1069,7 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[
web_config = {}
- thread_config = Thread(target=self._fill_config_async, args=(web_config,))
+ thread_config = Thread(target=self._fill_config_async, args=(web_config,), daemon=True)
thread_config.start()
if self.suggestions:
diff --git a/bauh/gems/web/environment.py b/bauh/gems/web/environment.py
index d32e004be..37c59e08f 100644
--- a/bauh/gems/web/environment.py
+++ b/bauh/gems/web/environment.py
@@ -421,11 +421,13 @@ def check_environment(self, env: dict, local_config: dict, app: WebApplication,
if system_env:
self.logger.warning(f"Using system's nativefier to install {app.url}")
else:
- node_check = Thread(target=self._check_and_fill_node, args=(env, components))
+ node_check = Thread(target=self._check_and_fill_node, args=(env, components), daemon=True)
node_check.start()
check_threads.append(node_check)
- elec_check = Thread(target=self._check_and_fill_electron, args=(app, env, local_config, is_x86_x64_arch, widevine, components))
+ elec_check = Thread(target=self._check_and_fill_electron,
+ args=(app, env, local_config, is_x86_x64_arch, widevine, components),
+ daemon=True)
elec_check.start()
check_threads.append(elec_check)
diff --git a/bauh/gems/web/resources/__init__.py b/bauh/gems/web/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/web/resources/img/__init__.py b/bauh/gems/web/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/gems/web/resources/locale/__init__.py b/bauh/gems/web/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/manage.py b/bauh/manage.py
index 495148b34..469aefb5e 100644
--- a/bauh/manage.py
+++ b/bauh/manage.py
@@ -18,16 +18,14 @@
from bauh.view.qt.settings import SettingsWindow
from bauh.view.qt.window import ManageWindow
from bauh.view.util import resource, util
-from bauh.view.util.cache import CacheCleaner, DefaultMemoryCacheFactory
+from bauh.view.util.cache import DefaultMemoryCacheFactory
from bauh.view.util.disk import DefaultDiskCacheLoaderFactory
def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logger) -> Tuple[QApplication, QWidget]:
i18n = generate_i18n(app_config, resource.get_path('locale'))
- cache_cleaner = CacheCleaner()
-
- cache_factory = DefaultMemoryCacheFactory(expiration_time=int(app_config['memory_cache']['data_expiration']), cleaner=cache_cleaner)
+ cache_factory = DefaultMemoryCacheFactory(expiration_time=int(app_config['memory_cache']['data_expiration']))
icon_cache = cache_factory.new(int(app_config['memory_cache']['icon_expiration']))
http_client = HttpClient(logger)
@@ -83,12 +81,9 @@ def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logg
force_suggestions=force_suggestions,
logger=logger)
- prepare = PreparePanel(context=context,
- manager=manager,
- i18n=i18n,
- manage_window=manage_window,
- app_config=app_config,
- force_suggestions=force_suggestions)
- cache_cleaner.start()
-
- return app, prepare
+ return app, PreparePanel(context=context,
+ manager=manager,
+ i18n=i18n,
+ manage_window=manage_window,
+ app_config=app_config,
+ force_suggestions=force_suggestions)
diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py
index f1987914c..4da5652b2 100755
--- a/bauh/view/core/controller.py
+++ b/bauh/view/core/controller.py
@@ -1,11 +1,9 @@
-import os
-import re
import shutil
import time
import traceback
from subprocess import Popen, STDOUT
from threading import Thread
-from typing import List, Set, Type, Tuple, Dict, Optional, Generator, Callable, Pattern
+from typing import List, Set, Type, Tuple, Dict, Optional, Generator, Callable
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \
UpgradeRequirement, TransactionResult, SoftwareAction, SettingsView, SettingsController
@@ -13,10 +11,11 @@
from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, \
CustomSoftwareAction
-from bauh.api.abstract.view import ViewComponent, TabGroupComponent, MessageType, PanelComponent
+from bauh.api.abstract.view import TabGroupComponent, MessageType
from bauh.api.exception import NoInternetException
from bauh.commons.boot import CreateConfigFile
from bauh.commons.html import bold
+from bauh.commons.regex import RE_URL
from bauh.commons.util import sanitize_command_input
from bauh.view.core.config import CoreConfigManager
from bauh.view.core.settings import GenericSettingsManager
@@ -25,8 +24,6 @@
from bauh.view.util.resource import get_path
from bauh.view.util.util import clean_app_files, restart_app
-RE_IS_URL = re.compile(r'^https?://.+')
-
class GenericUpgradeRequirements(UpgradeRequirements):
@@ -144,14 +141,13 @@ def _can_work(self, man: SoftwareManager):
return available
def _search(self, word: str, is_url: bool, man: SoftwareManager, disk_loader, res: SearchResult):
- if self._can_work(man):
- mti = time.time()
- apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url, limit=-1)
- mtf = time.time()
- self.logger.info(f'{man.__class__.__name__} took {mtf - mti:.8f} seconds')
+ mti = time.time()
+ apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url, limit=-1)
+ mtf = time.time()
+ self.logger.info(f'{man.__class__.__name__} took {mtf - mti:.8f} seconds')
- res.installed.extend(apps_found.installed)
- res.new.extend(apps_found.new)
+ res.installed.extend(apps_found.installed)
+ res.new.extend(apps_found.new)
def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult:
ti = time.time()
@@ -164,16 +160,17 @@ def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = -
self.logger.info(f"Search query: {norm_query}")
if norm_query:
- is_url = bool(RE_IS_URL.match(norm_query))
+ is_url = bool(RE_URL.match(norm_query))
disk_loader = self.disk_loader_factory.new()
disk_loader.start()
threads = []
for man in self.managers:
- t = Thread(target=self._search, args=(norm_query, is_url, man, disk_loader, res))
- t.start()
- threads.append(t)
+ if self._can_work(man):
+ t = Thread(target=self._search, args=(norm_query, is_url, man, disk_loader, res), daemon=True)
+ t.start()
+ threads.append(t)
for t in threads:
t.join()
@@ -181,9 +178,6 @@ def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = -
if disk_loader:
disk_loader.stop_working()
disk_loader.join()
-
- # res.installed = self._sort(res.installed, norm_word)
- # res.new = self._sort(res.new, norm_word)
else:
raise NoInternetException()
@@ -206,6 +200,15 @@ def can_work(self) -> Tuple[bool, Optional[str]]:
def _get_package_lower_name(self, pkg: SoftwarePackage):
return pkg.name.lower()
+ def _fill_read_installed(self, man: SoftwareManager, disk_loader: DiskCacheLoader, internet_available: bool,
+ output: List[SearchResult]):
+ mti = time.time()
+ man_res = man.read_installed(disk_loader=disk_loader, pkg_types=None, internet_available=internet_available,
+ limit=-1, only_apps=False)
+ mtf = time.time()
+ self.logger.info(f'{man.__class__.__name__} took {mtf - mti:.4f} seconds')
+ output.append(man_res)
+
def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult:
ti = time.time()
self._wait_to_be_ready()
@@ -215,6 +218,9 @@ def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, o
disk_loader = None
net_available = self.context.is_internet_available()
+ read_threads = list()
+ results = list()
+
if not pkg_types: # any type
for man in self.managers:
if self._can_work(man):
@@ -222,13 +228,11 @@ def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, o
disk_loader = self.disk_loader_factory.new()
disk_loader.start()
- mti = time.time()
- man_res = man.read_installed(disk_loader=disk_loader, pkg_types=None, internet_available=net_available)
- mtf = time.time()
- self.logger.info(f'{man.__class__.__name__} took {mtf - mti:.2f} seconds')
-
- res.installed.extend(man_res.installed)
- res.total += man_res.total
+ t = Thread(target=self._fill_read_installed,
+ args=(man, disk_loader, net_available, results),
+ daemon=True)
+ t.start()
+ read_threads.append(t)
else:
man_already_used = []
@@ -240,18 +244,24 @@ def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, o
disk_loader = self.disk_loader_factory.new()
disk_loader.start()
- mti = time.time()
- man_res = man.read_installed(disk_loader=disk_loader, pkg_types=None, internet_available=net_available)
- mtf = time.time()
- self.logger.info(f'{man.__class__.__name__} took {mtf - mti:.2f} seconds')
+ t = Thread(target=self._fill_read_installed,
+ args=(man, disk_loader, net_available, results),
+ daemon=True)
+ t.start()
+ read_threads.append(t)
- res.installed.extend(man_res.installed)
- res.total += man_res.total
+ for t in read_threads:
+ t.join()
if disk_loader:
disk_loader.stop_working()
disk_loader.join()
+ for result in results:
+ if result.installed:
+ res.installed.extend(result.installed)
+ res.total += result.total
+
if res.installed:
for p in res.installed:
if p.is_update_ignored():
@@ -498,7 +508,8 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSu
suggestions, threads = [], []
for man in self.managers:
t = Thread(target=self._fill_suggestions,
- args=(suggestions, man, int(self.config['suggestions']['by_type']), filter_installed))
+ args=(suggestions, man, int(self.config['suggestions']['by_type']), filter_installed),
+ daemon=True)
t.start()
threads.append(t)
diff --git a/bauh/view/core/downloader.py b/bauh/view/core/downloader.py
index 313fd865c..982d12e4e 100644
--- a/bauh/view/core/downloader.py
+++ b/bauh/view/core/downloader.py
@@ -1,14 +1,14 @@
-import logging
import os
import re
import shutil
import time
import traceback
-from io import StringIO
+from io import StringIO, BytesIO
+from logging import Logger
from math import floor
from pathlib import Path
from threading import Thread
-from typing import Iterable, List, Optional
+from typing import Optional, Tuple
from bauh.api.abstract.download import FileDownloader
from bauh.api.abstract.handler import ProcessWatcher
@@ -21,17 +21,97 @@
RE_HAS_EXTENSION = re.compile(r'.+\.\w+$')
+class SelfFileDownloader(FileDownloader):
+
+ def __init__(self, logger: Logger, i18n: I18n, http_client: HttpClient,
+ check_ssl: bool):
+ self._logger = logger
+ self._i18n = i18n
+ self._client = http_client
+ self._ssl = check_ssl
+
+ def is_multithreaded(self) -> bool:
+ return False
+
+ def can_work(self) -> bool:
+ return True
+
+ def get_supported_multithreaded_clients(self) -> Tuple[str, ...]:
+ return tuple()
+
+ def is_multithreaded_client_available(self, name: str) -> bool:
+ return False
+
+ def list_available_multithreaded_clients(self) -> Tuple[str, ...]:
+ return tuple()
+
+ def get_supported_clients(self) -> Tuple[str, ...]:
+ return tuple()
+
+ def download(self, file_url: str, watcher: Optional[ProcessWatcher], output_path: str, cwd: str,
+ root_password: Optional[str] = None, substatus_prefix: str = None, display_file_size: bool = True,
+ max_threads: int = None, known_size: int = None) -> bool:
+ try:
+ res = self._client.get(url=file_url, ignore_ssl=not self._ssl, stream=True)
+ except Exception:
+ return False
+
+ try:
+ content_length = int(res.headers.get("content-length", 0))
+ except Exception:
+ content_length = 0
+ self._logger.warning(f"Could not retrieve the content-length for file '{file_url}'")
+
+ file_name = file_url.split("/")[-1]
+ msg = StringIO()
+ msg.write(f"{substatus_prefix} " if substatus_prefix else "")
+ msg.write(f"{self._i18n['downloading']} {bold(file_name)}")
+ base_msg = msg.getvalue()
+
+ byte_stream = BytesIO()
+ total_downloaded = 0
+ known_size = content_length and content_length > 0
+ total_size_str = get_human_size_str(content_length) if known_size > 0 else "?"
+
+ try:
+ for data in res.iter_content(chunk_size=1024):
+ byte_stream.write(data)
+ total_downloaded += len(data)
+ perc = f"({(total_downloaded / content_length) * 100:.2f}%) " if known_size > 0 else ""
+ watcher.change_substatus(f"{perc}{base_msg} ({get_human_size_str(total_downloaded)} / {total_size_str})")
+ except Exception:
+ self._logger.error(f"Unexpected exception while downloading file from '{file_url}'")
+ traceback.print_exc()
+ return False
+
+ self._logger.info(f"Writing downloaded file content to disk: {output_path}")
+
+ try:
+ with open(output_path, "wb+") as f:
+ f.write(byte_stream.getvalue())
+ except Exception:
+ self._logger.error(f"Unexpected exception when saving downloaded content to disk: {output_path}")
+ traceback.print_exc()
+ return False
+
+ return True
+
+
class AdaptableFileDownloader(FileDownloader):
- def __init__(self, logger: logging.Logger, multithread_enabled: bool, i18n: I18n, http_client: HttpClient,
+ def __init__(self, logger: Logger, multithread_enabled: bool, i18n: I18n, http_client: HttpClient,
multithread_client: str, check_ssl: bool):
self.logger = logger
self.multithread_enabled = multithread_enabled
self.i18n = i18n
self.http_client = http_client
- self.supported_multithread_clients = ['aria2', 'axel']
+ self.supported_multithread_clients = ("aria2", "axel")
self.multithread_client = multithread_client
self.check_ssl = check_ssl
+ self._self_downloader = SelfFileDownloader(logger=logger,
+ i18n=i18n,
+ http_client=http_client,
+ check_ssl=check_ssl)
@staticmethod
def is_aria2c_available() -> bool:
@@ -41,10 +121,6 @@ def is_aria2c_available() -> bool:
def is_axel_available() -> bool:
return bool(shutil.which('axel'))
- @staticmethod
- def is_wget_available() -> bool:
- return bool(shutil.which('wget'))
-
def _get_aria2c_process(self, url: str, output_path: str, cwd: str, root_password: Optional[str], threads: int) -> SimpleProcess:
cmd = ['aria2c', url,
'--no-conf',
@@ -85,18 +161,6 @@ def _get_axel_process(self, url: str, output_path: str, cwd: str, root_password:
return SimpleProcess(cmd=cmd, cwd=cwd, root_password=root_password)
- def _get_wget_process(self, url: str, output_path: str, cwd: str, root_password: Optional[str]) -> SimpleProcess:
- cmd = ['wget', url, '-c', '--retry-connrefused', '-t', '10', '-nc']
-
- if not self.check_ssl:
- cmd.append('--no-check-certificate')
-
- if output_path:
- cmd.append('-O')
- cmd.append(output_path)
-
- return SimpleProcess(cmd=cmd, cwd=cwd, root_password=root_password)
-
def _rm_bad_file(self, file_name: str, output_path: str, cwd, handler: ProcessHandler, root_password: Optional[str]):
to_delete = output_path if output_path else f'{cwd}/{file_name}'
@@ -130,6 +194,45 @@ def _get_appropriate_threads_number(self, max_threads: int, known_size: int) ->
return threads
+ def _download_with_threads(self, client: str, file_url: str, output_path: str, cwd: str,
+ max_threads: int, known_size: int, display_file_size: bool, handler: ProcessHandler,
+ root_password: Optional[str] = None, substatus_prefix: Optional[str] = None) \
+ -> Tuple[float, bool]:
+
+ threads = self._get_appropriate_threads_number(max_threads, known_size)
+
+ if client == 'aria2':
+ start_time = time.time()
+ process = self._get_aria2c_process(file_url, output_path, cwd, root_password, threads)
+ downloader = 'aria2'
+ else:
+ start_time = time.time()
+ process = self._get_axel_process(file_url, output_path, cwd, root_password, threads)
+ downloader = 'axel'
+
+ name = file_url.split('/')[-1]
+
+ if output_path and not RE_HAS_EXTENSION.match(name) and RE_HAS_EXTENSION.match(output_path):
+ name = output_path.split('/')[-1]
+
+ if handler.watcher:
+ msg = StringIO()
+ msg.write(f'{substatus_prefix} ' if substatus_prefix else '')
+ msg.write(f"{bold('[{}]'.format(downloader))} {self.i18n['downloading']} {bold(name)}")
+
+ if display_file_size:
+ if known_size:
+ msg.write(f' ( {get_human_size_str(known_size)} )')
+ handler.watcher.change_substatus(msg.getvalue())
+ else:
+ Thread(target=self._concat_file_size, args=(file_url, msg, handler.watcher), daemon=True).start()
+ else:
+ msg.write(' ( ? Mb )')
+ handler.watcher.change_substatus(msg.getvalue())
+
+ success, _ = handler.handle_simple(process)
+ return start_time, success
+
def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = None, cwd: str = None, root_password: Optional[str] = None, substatus_prefix: str = None, display_file_size: bool = True, max_threads: int = None, known_size: int = None) -> bool:
self.logger.info(f'Downloading {file_url}')
handler = ProcessHandler(watcher)
@@ -138,7 +241,7 @@ def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = No
final_cwd = cwd if cwd else '.'
success = False
- ti = time.time()
+ start_time = time.time()
try:
if output_path:
if os.path.exists(output_path):
@@ -155,50 +258,27 @@ def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = No
watcher.print(self.i18n['error.mkdir'].format(dir=output_dir))
return False
- client = self.get_available_multithreaded_tool()
- if client:
- threads = self._get_appropriate_threads_number(max_threads, known_size)
-
- if client == 'aria2':
- ti = time.time()
- process = self._get_aria2c_process(file_url, output_path, final_cwd, root_password, threads)
- downloader = 'aria2'
- else:
- ti = time.time()
- process = self._get_axel_process(file_url, output_path, final_cwd, root_password, threads)
- downloader = 'axel'
+ threaded_client = self.get_available_multithreaded_tool()
+ if threaded_client:
+ start_time, success = self._download_with_threads(client=threaded_client, file_url=file_url,
+ output_path=output_path,
+ cwd=final_cwd, max_threads=max_threads,
+ known_size=known_size, handler=handler,
+ display_file_size=display_file_size,
+ root_password=root_password)
else:
- ti = time.time()
- process = self._get_wget_process(file_url, output_path, final_cwd, root_password)
- downloader = 'wget'
-
- name = file_url.split('/')[-1]
-
- if output_path and not RE_HAS_EXTENSION.match(name) and RE_HAS_EXTENSION.match(output_path):
- name = output_path.split('/')[-1]
-
- if watcher:
- msg = StringIO()
- msg.write(f'{substatus_prefix} ' if substatus_prefix else '')
- msg.write(f"{bold('[{}]'.format(downloader))} {self.i18n['downloading']} {bold(name)}")
-
- if display_file_size:
- if known_size:
- msg.write(f' ( {get_human_size_str(known_size)} )')
- watcher.change_substatus(msg.getvalue())
- else:
- Thread(target=self._concat_file_size, args=(file_url, msg, watcher)).start()
- else:
- msg.write(' ( ? Mb )')
- watcher.change_substatus(msg.getvalue())
-
- success, _ = handler.handle_simple(process)
+ start_time = time.time()
+ success = self._self_downloader.download(file_url=file_url, watcher=watcher, output_path=output_path,
+ cwd=cwd, root_password=root_password,
+ substatus_prefix=substatus_prefix,
+ display_file_size=display_file_size, max_threads=max_threads,
+ known_size=known_size)
except Exception:
traceback.print_exc()
self._rm_bad_file(file_name, output_path, final_cwd, handler, root_password)
- tf = time.time()
- self.logger.info(f'{file_name} download took {(tf - ti) / 60:.2f} minutes')
+ final_time = time.time()
+ self.logger.info(f'{file_name} download took {(final_time - start_time) / 60:.4f} minutes')
if not success:
self.logger.error(f"Could not download '{file_name}'")
@@ -228,9 +308,9 @@ def get_available_multithreaded_tool(self) -> str:
return client
def can_work(self) -> bool:
- return self.is_wget_available() or self.is_multithreaded()
+ return True
- def get_supported_multithreaded_clients(self) -> Iterable[str]:
+ def get_supported_multithreaded_clients(self) -> Tuple[str, ...]:
return self.supported_multithread_clients
def is_multithreaded_client_available(self, name: str) -> bool:
@@ -241,8 +321,8 @@ def is_multithreaded_client_available(self, name: str) -> bool:
else:
return False
- def list_available_multithreaded_clients(self) -> List[str]:
- return [c for c in self.supported_multithread_clients if self.is_multithreaded_client_available(c)]
+ def list_available_multithreaded_clients(self) -> Tuple[str, ...]:
+ return tuple(c for c in self.supported_multithread_clients if self.is_multithreaded_client_available(c))
- def get_supported_clients(self) -> tuple:
- return 'wget', 'aria2', 'axel'
+ def get_supported_clients(self) -> Tuple[str, ...]:
+ return "self", "aria2", "axel"
diff --git a/bauh/view/core/settings.py b/bauh/view/core/settings.py
index dc1b69f53..ae276e566 100644
--- a/bauh/view/core/settings.py
+++ b/bauh/view/core/settings.py
@@ -146,7 +146,7 @@ def _gen_adv_settings(self, core_config: dict) -> TabComponent:
return TabComponent(self.i18n['core.config.tab.advanced'].capitalize(), panel, None, 'core.adv')
def _gen_multithread_client_select(self, core_config: dict) -> SingleSelectComponent:
- available_mthread_clients = self.file_downloader.list_available_multithreaded_clients()
+ available_mthread_clients = [*self.file_downloader.list_available_multithreaded_clients()]
available_mthread_clients.sort()
default_i18n_key = 'default' if available_mthread_clients else 'core.config.download.multithreaded_client.none'
@@ -533,14 +533,14 @@ def save_settings(self, component: TabGroupComponent) -> Tuple[bool, Optional[Li
ti = time.time()
save_threads, warnings, success_list = [], [], []
- save_core = Thread(target=self._save_core_settings, args=(component, success_list, warnings))
+ save_core = Thread(target=self._save_core_settings, args=(component, success_list, warnings), daemon=True)
save_core.start()
save_threads.append(save_core)
if self._settings_views:
for views in self._settings_views.values():
- save_view = Thread(target=self._save_views, args=(views, success_list, warnings))
+ save_view = Thread(target=self._save_views, args=(views, success_list, warnings), daemon=True)
save_view.start()
save_threads.append(save_view)
diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py
index 29c2922d4..3fe9a5834 100644
--- a/bauh/view/qt/apps_table.py
+++ b/bauh/view/qt/apps_table.py
@@ -1,21 +1,24 @@
import operator
import os
from functools import reduce
+from logging import Logger
from threading import Lock
-from typing import List, Optional
+from typing import List, Optional, Dict
-from PyQt5.QtCore import Qt, QUrl, QSize
+from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QPixmap, QIcon, QCursor
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QToolButton, QWidget, \
QHeaderView, QLabel, QHBoxLayout, QToolBar, QSizePolicy
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.model import PackageStatus, CustomSoftwareAction
+from bauh.api.abstract.view import MessageType
from bauh.commons.html import strip_html, bold
+from bauh.commons.regex import RE_URL
from bauh.view.qt.components import IconButton, QCustomMenuAction, QCustomToolbar
from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.qt_utils import get_current_screen_geometry
+from bauh.view.qt.thread import URLFileDownloader
from bauh.view.qt.view_model import PackageView
from bauh.view.util.translation import I18n
@@ -69,12 +72,13 @@ class PackagesTable(QTableWidget):
COL_NUMBER = 9
DEFAULT_ICON_SIZE = QSize(16, 16)
- def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool):
+ def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool, logger: Logger):
super(PackagesTable, self).__init__()
self.setObjectName('table_packages')
self.setParent(parent)
self.window = parent
self.download_icons = download_icons
+ self.logger = logger
self.setColumnCount(self.COL_NUMBER)
self.setFocusPolicy(Qt.NoFocus)
self.setShowGrid(False)
@@ -88,13 +92,13 @@ def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: boo
self.horizontalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
self.verticalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
- self.network_man = QNetworkAccessManager()
- self.network_man.finished.connect(self._load_icon_and_cache)
+ self.file_downloader: Optional[URLFileDownloader] = None
self.icon_cache = icon_cache
self.lock_async_data = Lock()
self.setRowHeight(80, 80)
self.cache_type_icon = {}
+ self.cache_default_icon: Dict[str, QIcon] = dict()
self.i18n = self.window.i18n
def has_any_settings(self, pkg: PackageView):
@@ -183,10 +187,9 @@ def refresh(self, pkg: PackageView):
self._update_row(pkg, screen_width, update_check_enabled=False, change_update_col=False)
def update_package(self, pkg: PackageView, screen_width: int, change_update_col: bool = False):
- if self.download_icons and pkg.model.icon_url:
- icon_request = QNetworkRequest(QUrl(pkg.model.icon_url))
- icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
- self.network_man.get(icon_request)
+ if self.download_icons and pkg.model.icon_url and pkg.model.icon_url.startswith("http"):
+ self._setup_file_downloader(max_workers=1, max_downloads=1)
+ self.file_downloader.get(pkg.model.icon_url, pkg.table_index)
self._update_row(pkg, screen_width, change_update_col=change_update_col)
@@ -208,25 +211,28 @@ def _install_app(self, pkgv: PackageView):
body = self.i18n['manage_window.apps_table.row.actions.install.popup.body'].format(self._bold(str(pkgv)))
- warning = self.i18n.get('gem.{}.install.warning'.format(pkgv.model.get_type().lower()))
-
- if warning:
+ confirm_icon = MessageType.INFO
+ if not pkgv.model.is_trustable():
+ warning = self.i18n["action.install.unverified.warning"]
+ confirm_icon = MessageType.WARNING
body += '
{}'.format(
' '.join(('{}.'.format(phrase) for phrase in warning.split('.') if phrase)))
if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.install.popup.title'],
body=self._parag(body),
- i18n=self.i18n).ask():
+ i18n=self.i18n,
+ confirmation_icon_type=confirm_icon).ask():
self.window.install(pkgv)
- def _load_icon_and_cache(self, http_response: QNetworkReply):
- icon_url = http_response.request().url().toString()
+ def _update_pkg_icon(self, url_: str, content: Optional[bytes], table_idx: int):
+ if not content:
+ return content
- icon_data = self.icon_cache.get(icon_url)
+ icon_data = self.icon_cache.get(url_)
icon_was_cached = True
if not icon_data:
- icon_bytes = http_response.readAll()
+ icon_bytes = content
if not icon_bytes:
return
@@ -238,16 +244,16 @@ def _load_icon_and_cache(self, http_response: QNetworkReply):
if not pixmap.isNull():
icon = QIcon(pixmap)
icon_data = {'icon': icon, 'bytes': icon_bytes}
- self.icon_cache.add(icon_url, icon_data)
+ self.icon_cache.add(url_, icon_data)
if icon_data:
- for idx, app in enumerate(self.window.pkgs):
- if app.model.icon_url == icon_url:
- self._update_icon(self.cellWidget(idx, 0), icon_data['icon'])
+ for pkg in self.window.pkgs:
+ if pkg.table_index == table_idx:
+ self._update_icon(self.cellWidget(table_idx, 0), icon_data['icon'])
- if app.model.supports_disk_cache() and app.model.get_disk_icon_path() and icon_data['bytes']:
- if not icon_was_cached or not os.path.exists(app.model.get_disk_icon_path()):
- self.window.manager.cache_to_disk(pkg=app.model, icon_bytes=icon_data['bytes'],
+ if pkg.model.supports_disk_cache() and pkg.model.get_disk_icon_path() and icon_data['bytes']:
+ if not icon_was_cached or not os.path.exists(pkg.model.get_disk_icon_path()):
+ self.window.manager.cache_to_disk(pkg=pkg.model, icon_bytes=icon_data['bytes'],
only_icon=True)
def update_packages(self, pkgs: List[PackageView], update_check_enabled: bool = True):
@@ -259,13 +265,18 @@ def update_packages(self, pkgs: List[PackageView], update_check_enabled: bool =
self.setColumnCount(self.COL_NUMBER if update_check_enabled else self.COL_NUMBER - 1)
self.setRowCount(len(pkgs))
+ file_downloader_defined = False
+
for idx, pkg in enumerate(pkgs):
pkg.table_index = idx
- if self.download_icons and pkg.model.status == PackageStatus.READY and pkg.model.icon_url:
- icon_request = QNetworkRequest(QUrl(pkg.model.icon_url))
- icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
- self.network_man.get(icon_request)
+ if self.download_icons and pkg.model.status == PackageStatus.READY and pkg.model.icon_url \
+ and RE_URL.match(pkg.model.icon_url):
+ if not file_downloader_defined:
+ self._setup_file_downloader()
+ file_downloader_defined = True
+
+ self.file_downloader.get(pkg.model.icon_url, idx)
self._update_row(pkg, screen_width, update_check_enabled)
@@ -391,6 +402,16 @@ def _set_col_version(self, col: int, pkg: PackageView, screen_width: int):
item.setToolTip(tooltip)
self.setCellWidget(pkg.table_index, col, item)
+ def _read_default_icon(self, pkgv: PackageView):
+ icon_path = pkgv.model.get_default_icon_path()
+ icon = self.cache_default_icon.get(icon_path)
+
+ if not icon:
+ icon = QIcon(icon_path)
+ self.cache_default_icon[icon_path] = icon
+
+ return icon
+
def _set_col_icon(self, col: int, pkg: PackageView):
icon_path = pkg.model.get_disk_icon_path()
if pkg.model.installed and pkg.model.supports_disk_cache() and icon_path:
@@ -403,24 +424,24 @@ def _set_col_icon(self, col: int, pkg: PackageView):
icon = QIcon(pixmap)
self.icon_cache.add_non_existing(pkg.model.icon_url, {'icon': icon, 'bytes': icon_bytes})
else:
- icon = QIcon(pkg.model.get_default_icon_path())
+ icon = self._read_default_icon(pkg)
else:
try:
icon = QIcon.fromTheme(icon_path)
if icon.isNull():
- icon = QIcon(pkg.model.get_default_icon_path())
+ icon = self._read_default_icon(pkg)
elif pkg.model.icon_url:
self.icon_cache.add_non_existing(pkg.model.icon_url, {'icon': icon, 'bytes': None})
except Exception:
- icon = QIcon(pkg.model.get_default_icon_path())
+ icon = self._read_default_icon(pkg)
elif not pkg.model.icon_url:
- icon = QIcon(pkg.model.get_default_icon_path())
+ icon = self._read_default_icon(pkg)
else:
icon_data = self.icon_cache.get(pkg.model.icon_url)
- icon = icon_data['icon'] if icon_data else QIcon(pkg.model.get_default_icon_path())
+ icon = icon_data['icon'] if icon_data else self._read_default_icon(pkg)
col_icon = QLabel()
col_icon.setProperty('icon', 'true')
@@ -593,3 +614,18 @@ def change_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToConten
def get_width(self):
return reduce(operator.add, [self.columnWidth(i) for i in range(self.columnCount())])
+
+ def _setup_file_downloader(self, max_workers: int = 50, max_downloads: int = -1) -> None:
+ self.file_downloader = URLFileDownloader(logger=self.logger,
+ max_workers=max_workers,
+ max_downloads=max_downloads,
+ parent=self)
+ self.file_downloader.signal_downloaded.connect(self._update_pkg_icon)
+ self.file_downloader.start()
+
+ def stop_file_downloader(self, wait: bool = False) -> None:
+ if self.file_downloader:
+ self.file_downloader.stop()
+
+ if wait:
+ self.file_downloader.wait()
diff --git a/bauh/view/qt/commons.py b/bauh/view/qt/commons.py
index e5384a13c..919239b99 100644
--- a/bauh/view/qt/commons.py
+++ b/bauh/view/qt/commons.py
@@ -1,10 +1,31 @@
-from typing import Iterable, List
+from typing import List, Dict, Any, NamedTuple, Optional, Union, Collection, Iterable
from bauh.api.abstract.model import SoftwarePackage
from bauh.view.qt.view_model import PackageView
-def new_pkgs_info() -> dict:
+class PackageFilters(NamedTuple):
+ """
+ It represents the manage window selected filters
+ """
+
+ display_limit: int
+ category: str
+ name: Optional[str]
+ only_apps: bool
+ only_installed: bool
+ only_updates: bool
+ only_verified: bool
+ search: Optional[str] # initial search term
+ type: str
+
+ @property
+ def anything(self) -> bool:
+ return not self.only_installed and not self.only_updates and not self.only_apps \
+ and not self.only_verified and not self.name and self.type == "any" and self.category == "any"
+
+
+def new_pkgs_info() -> Dict[str, Any]:
return {'apps_count': 0, # number of application packages
'napps_count': 0, # number of not application packages (libraries, runtimes or something else)
'available_types': {}, # available package types in 'new_pkgs'
@@ -15,10 +36,11 @@ def new_pkgs_info() -> dict:
'not_installed': 0,
'installed': 0,
'categories': set(),
+ 'verified': 0,
'pkgs': []} # total packages
-def update_info(pkgv: PackageView, pkgs_info: dict):
+def update_info(pkgv: PackageView, pkgs_info: Dict[str, Any]):
pkgs_info['available_types'][pkgv.model.get_type()] = {'icon': pkgv.model.get_type_icon_path(), 'label': pkgv.get_type_label()}
if pkgv.model.is_application():
@@ -26,6 +48,9 @@ def update_info(pkgv: PackageView, pkgs_info: dict):
else:
pkgs_info['napps_count'] += 1
+ if pkgv.model.is_trustable():
+ pkgs_info['verified'] += 1
+
if pkgv.model.update and not pkgv.model.is_update_ignored():
if pkgv.model.is_application():
pkgs_info['app_updates'] += 1
@@ -49,8 +74,8 @@ def update_info(pkgv: PackageView, pkgs_info: dict):
pkgs_info['not_installed'] += 1
-def apply_filters(pkg: PackageView, filters: dict, info: dict, limit: bool = True):
- if not limit or not filters['display_limit'] or len(info['pkgs_displayed']) < filters['display_limit']:
+def apply_filters(pkg: PackageView, filters: PackageFilters, info: dict, limit: bool = True):
+ if not limit or not filters.display_limit or len(info['pkgs_displayed']) < filters.display_limit:
if not is_package_hidden(pkg, filters):
info['pkgs_displayed'].append(pkg)
@@ -65,41 +90,51 @@ def sum_updates_displayed(info: dict) -> int:
return updates
-def is_package_hidden(pkg: PackageView, filters: dict) -> bool:
- hidden = filters['only_installed'] and not pkg.model.installed
+def is_package_hidden(pkg: PackageView, filters: PackageFilters) -> bool:
+ hidden = filters.only_installed and not pkg.model.installed
- if not hidden and filters['only_apps']:
+ if not hidden and filters.only_apps:
hidden = pkg.model.installed and not pkg.model.is_application()
- if not hidden and filters['updates']:
+ if not hidden and filters.only_updates:
hidden = not pkg.model.update or pkg.model.is_update_ignored()
- if not hidden and filters['type'] is not None and filters['type'] != 'any':
- hidden = pkg.model.get_type() != filters['type']
+ if not hidden and filters.only_verified:
+ hidden = not pkg.model.is_trustable()
+
+ if not hidden and filters.type is not None and filters.type != "any":
+ hidden = pkg.model.get_type() != filters.type
- if not hidden and filters['category'] is not None and filters['category'] != 'any':
- hidden = not pkg.model.categories or not [c for c in pkg.model.categories if c.lower() == filters['category']]
+ if not hidden and filters.category is not None and filters.category != "any":
+ hidden = not pkg.model.categories or not next((c for c in pkg.model.categories if c.lower() == filters.category), None)
- if not hidden and filters['name']:
- hidden = not filters['name'] in pkg.model.name.lower()
+ if not hidden and filters.name:
+ hidden = not filters.name.startswith(pkg.model.name.lower())
return hidden
-def sort_packages(pkgs: Iterable[SoftwarePackage], word: str, limit: int = 0) -> List[SoftwarePackage]:
- exact, starts_with, contains, others = [], [], [], []
+def _by_name(pkg: Union[SoftwarePackage, PackageView]):
+ return pkg.name.lower()
- for p in pkgs:
- lower_name = p.name.lower()
- if word == lower_name:
- exact.append(p)
- elif lower_name.startswith(word):
- starts_with.append(p)
- elif word in lower_name:
- contains.append(p)
- else:
- others.append(p)
+def sort_packages(pkgs: Iterable[Union[SoftwarePackage, PackageView]], word: Optional[str],
+ limit: int = 0) -> List[SoftwarePackage]:
+ exact, starts_with, contains, others = [], [], [], []
+
+ if not word:
+ others.extend(pkgs)
+ else:
+ for p in pkgs:
+ lower_name = p.name.lower()
+ if word == lower_name:
+ exact.append(p)
+ elif lower_name.startswith(word):
+ starts_with.append(p)
+ elif word in lower_name:
+ contains.append(p)
+ else:
+ others.append(p)
res = []
for app_list in (exact, starts_with, contains, others):
@@ -110,7 +145,7 @@ def sort_packages(pkgs: Iterable[SoftwarePackage], word: str, limit: int = 0) ->
break
to_add = app_list[0:last]
- to_add.sort(key=lambda a: a.name.lower())
+ to_add.sort(key=_by_name)
res.extend(to_add)
return res
diff --git a/bauh/view/qt/dialog.py b/bauh/view/qt/dialog.py
index 13ffc0f56..94de3e112 100644
--- a/bauh/view/qt/dialog.py
+++ b/bauh/view/qt/dialog.py
@@ -35,7 +35,8 @@ def __init__(self, title: str, body: Optional[str], i18n: I18n, icon: QIcon = QI
widgets: Optional[List[QWidget]] = None, confirmation_button: bool = True, deny_button: bool = True,
window_cancel: bool = False, confirmation_label: Optional[str] = None, deny_label: Optional[str] = None,
confirmation_icon: bool = True, min_width: Optional[int] = None,
- min_height: Optional[int] = None, max_width: Optional[int] = None):
+ min_height: Optional[int] = None, max_width: Optional[int] = None,
+ confirmation_icon_type: MessageType = MessageType.INFO):
super(ConfirmationDialog, self).__init__()
if not window_cancel:
@@ -79,6 +80,7 @@ def __init__(self, title: str, body: Optional[str], i18n: I18n, icon: QIcon = QI
if confirmation_icon:
lb_icon = QLabel()
lb_icon.setObjectName("confirm_dialog_icon")
+ lb_icon.setProperty("type", confirmation_icon_type.name.lower())
container_body.layout().addWidget(lb_icon)
if body:
diff --git a/bauh/view/qt/info.py b/bauh/view/qt/info.py
index f12f2d290..01abcacc0 100644
--- a/bauh/view/qt/info.py
+++ b/bauh/view/qt/info.py
@@ -1,4 +1,8 @@
+import shutil
+import subprocess
from collections.abc import Iterable
+from subprocess import Popen
+from typing import Optional
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QCursor
@@ -7,6 +11,7 @@
QHBoxLayout
from bauh.api.abstract.cache import MemoryCache
+from bauh.commons.regex import RE_URL
from bauh.view.qt.components import new_spacer
from bauh.view.qt.qt_utils import get_current_screen_geometry
from bauh.view.util.translation import I18n
@@ -16,10 +21,11 @@
class InfoDialog(QDialog):
- def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n):
+ def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n, can_open_url: bool):
super(InfoDialog, self).__init__()
self.setWindowTitle(str(pkg_info['__app__']))
self.i18n = i18n
+ self._can_open_url = can_open_url
layout = QVBoxLayout()
self.setLayout(layout)
@@ -64,7 +70,7 @@ def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n):
val = str(pkg_info[attr]).strip()
show_val = val
- i18n_val = i18n.get('{}.{}'.format(i18n_key, val.lower()))
+ i18n_val = i18n.get(f"{i18n_key}.{val.lower()}")
if i18n_val:
val = i18n_val
@@ -82,7 +88,8 @@ def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n):
self.gbox_info.layout().addWidget(label, idx, 0)
self.gbox_info.layout().addWidget(text, idx, 1)
- self._gen_show_button(idx, show_val)
+
+ self._gen_show_button(idx=idx, val=show_val)
layout.addWidget(scroll)
@@ -113,18 +120,35 @@ def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n):
self.setMaximumHeight(int(screen_height * 0.8))
self.adjustSize()
- def _gen_show_button(self, idx: int, val):
+ @staticmethod
+ def open_url(url: str):
+ Popen(["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
+
+ def _show_full_field_val(self, val: str):
+ self.gbox_info.hide()
+ self.bt_back.setVisible(True)
+ self.text_field.show()
+ self.text_field.setPlainText(val)
+
+ def _gen_show_button(self, idx: int, val: str):
+
+ is_url = self._can_open_url and bool(RE_URL.match(val)) if val else False
+
+ if is_url:
+ bt_label = self.i18n["manage_window.info.open_url"]
+
+ def _show_field():
+ self.open_url(val)
+ else:
+ bt_label = self.i18n["show"].capitalize()
- def show_full_field():
- self.gbox_info.hide()
- self.bt_back.setVisible(True)
- self.text_field.show()
- self.text_field.setPlainText(val)
+ def _show_field():
+ self._show_full_field_val(val)
- bt_full_field = QPushButton(self.i18n['show'].capitalize())
- bt_full_field.setObjectName('show')
+ bt_full_field = QPushButton(bt_label)
+ bt_full_field.setObjectName("show")
bt_full_field.setCursor(QCursor(Qt.PointingHandCursor))
- bt_full_field.clicked.connect(show_full_field)
+ bt_full_field.clicked.connect(_show_field)
self.gbox_info.layout().addWidget(bt_full_field, idx, 2)
def back_to_info(self):
diff --git a/bauh/view/qt/prepare.py b/bauh/view/qt/prepare.py
index 7b5384552..fc80fd839 100644
--- a/bauh/view/qt/prepare.py
+++ b/bauh/view/qt/prepare.py
@@ -5,7 +5,7 @@
from typing import Tuple, Optional
from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication, QMutex
-from PyQt5.QtGui import QIcon, QCursor, QCloseEvent
+from PyQt5.QtGui import QIcon, QCursor, QCloseEvent, QShowEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, \
QProgressBar, QPlainTextEdit, QToolButton, QHBoxLayout
@@ -133,7 +133,7 @@ def run(self):
ti = datetime.datetime.now()
while True:
- if datetime.datetime.now() >= ti + datetime.timedelta(minutes=1.5):
+ if datetime.datetime.now() >= ti + datetime.timedelta(seconds=10):
self.signal_timeout.emit()
break
@@ -253,6 +253,7 @@ def __init__(self, context: ApplicationContext, manager: SoftwareManager,
self.bt_bar.add_widget(self.bt_skip)
self.layout().addWidget(self.bt_bar)
+ centralize(self)
def hide_output(self):
self.current_output_task = None
@@ -286,14 +287,14 @@ def _resize_columns(self):
self.resize(int(self.get_table_width() * 1.05), self.sizeHint().height())
- def show(self):
- super(PreparePanel, self).show()
+ def showEvent(self, event: Optional[QShowEvent]) -> None:
+ super().showEvent(event)
self.prepare_thread.start()
screen_size = get_current_screen_geometry()
- self.setMinimumWidth(int(screen_size.width() * 0.5))
+ self.setMinimumWidth(int(screen_size.width() * 0.25))
self.setMinimumHeight(int(screen_size.height() * 0.35))
self.setMaximumHeight(int(screen_size.height() * 0.95))
- centralize(self, align_top_left=False)
+ centralize(self)
def start(self, tasks: int):
self.started_at = time.time()
diff --git a/bauh/view/qt/screenshots.py b/bauh/view/qt/screenshots.py
index 5d52aaf77..08a30fe0c 100644
--- a/bauh/view/qt/screenshots.py
+++ b/bauh/view/qt/screenshots.py
@@ -1,6 +1,8 @@
import logging
+import traceback
+from io import BytesIO
from threading import Thread
-from typing import List
+from typing import List, Dict
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap, QCursor
@@ -95,6 +97,7 @@ def __init__(self, pkg: PackageView, http_client: HttpClient, icon_cache: Memory
self.bt_next.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_next.clicked.connect(self.next)
self.container_buttons.layout().addWidget(self.bt_next)
+ self.download_progress: Dict[int, float] = dict()
self.layout().addWidget(self.container_buttons)
@@ -107,13 +110,16 @@ def __init__(self, pkg: PackageView, http_client: HttpClient, icon_cache: Memory
t.start()
self.resize(self.max_img_width + 5, self.max_img_height + 5)
- self._load_img()
+ self._load_img(self.img_idx)
qt_utils.centralize(self)
def _update_progress(self, val: int):
self.progress_bar.setValue(val)
- def _load_img(self):
+ def _load_img(self, img_idx: int):
+ if img_idx != self.img_idx:
+ return
+
if len(self.loaded_imgs) > self.img_idx:
img = self.loaded_imgs[self.img_idx]
@@ -131,7 +137,10 @@ def _load_img(self):
else:
self.img.setPixmap(QPixmap())
self.img.setCursor(QCursor(Qt.WaitCursor))
- self.img.setText('{} {}/{}...'.format(self.i18n['screenshots.image.loading'], self.img_idx + 1, len(self.screenshots)))
+
+ progress = self.download_progress.get(self.img_idx, 0)
+ self.img.setText(f"{self.i18n['screenshots.image.loading']} "
+ f"{self.img_idx + 1}/{len(self.screenshots)} ({progress:.2f}%)")
self.progress_bar.setVisible(True)
self.thread_progress.start()
@@ -142,36 +151,66 @@ def _load_img(self):
self.bt_back.setEnabled(self.img_idx != 0)
self.bt_next.setEnabled(self.img_idx != len(self.screenshots) - 1)
- def _download_img(self, idx: int, url: str):
- self.logger.info('Downloading image [{}] from {}'.format(idx, url))
- res = self.http_client.get(url)
-
- if res:
- if not res.content:
- self.logger.warning('Image [{}] from {} has no content'.format(idx, url))
- self.loaded_imgs.append(self.i18n['screenshots.download.no_content'])
- self._load_img()
- else:
- self.logger.info('Image [{}] successfully downloaded'.format(idx))
- pixmap = QPixmap()
- pixmap.loadFromData(res.content)
+ def _handle_download_exception(self, idx: int, url: str):
+ self.logger.error(f"Unexpected exception while downloading screenshot from '{url}'")
+ traceback.print_exc()
+ self.loaded_imgs.append(self.i18n["screenshots.download.no_response"])
+ self._load_img(idx)
- if pixmap.size().height() > self.max_img_height or pixmap.size().width() > self.max_img_width:
- pixmap = pixmap.scaled(self.max_img_width, self.max_img_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
-
- self.loaded_imgs.append(pixmap)
-
- if self.img_idx == idx:
- self._load_img()
+ def _download_img(self, idx: int, url: str):
+ self.logger.info(f"Downloading image [{idx}] from {url}")
+
+ try:
+ res = self.http_client.get(url=url, stream=True)
+ except Exception:
+ self._handle_download_exception(idx, url)
+ return
+
+ if not res:
+ self.logger.info(f"Could not retrieve image [{idx}] from '{url}'")
+ self.loaded_imgs.append(self.i18n["screenshots.download.no_response"])
+ self._load_img(idx)
+ return
+
+ try:
+ content_length = int(res.headers.get("content-length", 0))
+ except Exception:
+ content_length = 0
+ self.logger.warning(f"Could not retrieve the content-length for file '{url}'")
+
+ if content_length <= 0:
+ self.logger.warning(f"Image [{idx}] has no content ({url})")
+ self.loaded_imgs.append(self.i18n['screenshots.download.no_content'])
+ self._load_img(idx)
else:
- self.logger.info("Could not retrieve image [{}] from {}".format(idx, url))
- self.loaded_imgs.append(self.i18n['screenshots.download.no_response'])
- self._load_img()
+ byte_stream = BytesIO()
+
+ total_downloaded = 0
+ try:
+ for data in res.iter_content(chunk_size=1024):
+ byte_stream.write(data)
+ total_downloaded += len(data)
+ self.download_progress[idx] = (total_downloaded / content_length) * 100
+ self._load_img(idx)
+ except Exception:
+ self._handle_download_exception(idx, url)
+ return
+
+ self.logger.info(f"Image [{idx}] successfully downloaded ({url})")
+ pixmap = QPixmap()
+ pixmap.loadFromData(byte_stream.getvalue())
+
+ if pixmap.size().height() > self.max_img_height or pixmap.size().width() > self.max_img_width:
+ pixmap = pixmap.scaled(self.max_img_width, self.max_img_height, Qt.KeepAspectRatio,
+ Qt.SmoothTransformation)
+
+ self.loaded_imgs.append(pixmap)
+ self._load_img(idx)
def back(self):
self.img_idx -= 1
- self._load_img()
+ self._load_img(self.img_idx)
def next(self):
self.img_idx += 1
- self._load_img()
+ self._load_img(self.img_idx)
diff --git a/bauh/view/qt/settings.py b/bauh/view/qt/settings.py
index ae732c7cd..2b1d9bdda 100644
--- a/bauh/view/qt/settings.py
+++ b/bauh/view/qt/settings.py
@@ -3,7 +3,7 @@
from typing import Optional
from PyQt5.QtCore import Qt, QCoreApplication, QThread, pyqtSignal
-from PyQt5.QtGui import QCursor
+from PyQt5.QtGui import QCursor, QShowEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QPushButton, QHBoxLayout, QApplication
from bauh import __app_name__
@@ -79,11 +79,12 @@ def __init__(self, manager: SoftwareManager, i18n: I18n, window: QWidget, parent
self.thread_reload_panel = ReloadManagePanel(manager=manager)
self.thread_reload_panel.signal_finished.connect(self._reload_manage_panel)
-
- def show(self):
- super(SettingsWindow, self).show()
centralize(self)
+
+ def showEvent(self, event: Optional[QShowEvent]):
+ super(SettingsWindow, self).showEvent(event)
self.setMinimumWidth(int(self.sizeHint().width()))
+ centralize(self)
def closeEvent(self, event):
if self.window and self.window.settings_window == self:
diff --git a/bauh/view/qt/systray.py b/bauh/view/qt/systray.py
index 1d39829f1..44228971e 100755
--- a/bauh/view/qt/systray.py
+++ b/bauh/view/qt/systray.py
@@ -198,7 +198,7 @@ def handle_click(self, reason):
self.show_manage_window()
def verify_updates(self, notify_user: bool = True):
- Thread(target=self._verify_updates, args=(notify_user,)).start()
+ Thread(target=self._verify_updates, args=(notify_user,), daemon=True).start()
def _verify_updates(self, notify_user: bool):
self.notify_updates(self.manager.list_updates(), notify_user=notify_user)
diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py
index 6f38ecaea..445844188 100644
--- a/bauh/view/qt/thread.py
+++ b/bauh/view/qt/thread.py
@@ -1,11 +1,15 @@
import os
import re
+import sys
import time
import traceback
+from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta
from io import StringIO
+from logging import Logger
from pathlib import Path
-from typing import List, Type, Set, Tuple, Optional
+from queue import Queue
+from typing import List, Type, Set, Tuple, Optional, Pattern
import requests
from PyQt5.QtCore import QThread, pyqtSignal, QObject
@@ -23,13 +27,15 @@
from bauh.api.paths import LOGS_DIR
from bauh.commons.html import bold
from bauh.commons.internet import InternetChecker
+from bauh.commons.regex import RE_URL
from bauh.commons.system import ProcessHandler, SimpleProcess
from bauh.commons.view_utils import get_human_size_str
from bauh.view.core import timeshift
from bauh.view.core.config import CoreConfigManager, BACKUP_REMOVE_METHODS, BACKUP_DEFAULT_REMOVE_METHOD
from bauh.view.qt import commons
-from bauh.view.qt.commons import sort_packages
+from bauh.view.qt.commons import sort_packages, PackageFilters
from bauh.view.qt.qt_utils import get_current_screen_geometry
+from bauh.view.qt.view_index import query_packages
from bauh.view.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util.translation import I18n
@@ -956,37 +962,42 @@ def run(self):
class ApplyFilters(AsyncAction):
- signal_table = pyqtSignal(object)
+ signal_table = pyqtSignal(list)
- def __init__(self, i18n: I18n, filters: dict = None, pkgs: List[PackageView] = None):
+ def __init__(self, i18n: I18n, logger: Logger, filters: Optional[PackageFilters] = None,
+ pkgs: Optional[List[PackageView]] = None, index: Optional[dict] = None):
super(ApplyFilters, self).__init__(i18n=i18n)
- self.pkgs = pkgs
+ self.logger = logger
+ self.index = index
self.filters = filters
+ self.pkgs = pkgs
self.wait_table_update = False
def stop_waiting(self):
self.wait_table_update = False
def run(self):
- if self.pkgs:
- pkgs_info = commons.new_pkgs_info()
-
- name_filtering = bool(self.filters['name'])
-
- for pkgv in self.pkgs:
- commons.update_info(pkgv, pkgs_info)
- commons.apply_filters(pkgv, self.filters, pkgs_info, limit=not name_filtering)
-
- if name_filtering and pkgs_info['pkgs_displayed']:
- pkgs_info['pkgs_displayed'] = sort_packages(word=self.filters['name'],
- pkgs=pkgs_info['pkgs_displayed'],
- limit=self.filters['display_limit'])
+ if self.index and self.filters and self.pkgs:
+ if self.filters.anything:
+ # it means no filter should be applied, and when can rely on the firstly displayed packages
+ sorted_pkgs = self.pkgs
+
+ if self.filters.display_limit > 0:
+ sorted_pkgs = sorted_pkgs[0:self.filters.display_limit]
+
+ else:
+ ti = time.time()
+ sort_term = self.filters.name or self.filters.search # improves displayed matches when no name typed
+ matched_pkgs = query_packages(index=self.index, filters=self.filters)
+ sorted_pkgs = commons.sort_packages(pkgs=matched_pkgs, word=sort_term)
+ tf = time.time()
+ self.logger.info(f"Took {tf - ti:.9f} seconds to filter and sort packages")
self.wait_table_update = True
- self.signal_table.emit(pkgs_info)
+ self.signal_table.emit(sorted_pkgs)
while self.wait_table_update:
- super(ApplyFilters, self).msleep(5)
+ super(ApplyFilters, self).msleep(1)
self.notify_finished()
@@ -1100,3 +1111,67 @@ def run(self):
self.msleep(self.delay)
self.signal_start.emit()
+
+
+class URLFileDownloader(QThread):
+
+ signal_downloaded = pyqtSignal(str, bytes, object)
+
+ def __init__(self, logger: Logger, max_workers: int = 50, request_timeout: int = 30, inactivity_timeout: int = 3,
+ max_downloads: int = -1, parent: Optional[QWidget] = None):
+ super().__init__(parent)
+ self._logger = logger
+ self._queue = Queue()
+ self._max_workers = max_workers
+ self._request_timeout = request_timeout
+ self._inactivity_timeout = inactivity_timeout
+ self._max_downloads = max_downloads
+ self._stop = False
+
+ def _get(self, url_: str, id_: Optional[object]):
+ if self._stop:
+ self._logger.info(f"File '{url_}' download cancelled")
+ return
+
+ try:
+ res = requests.get(url=url_, timeout=self._request_timeout)
+ content = res.content if res.status_code == 200 else None
+ self.signal_downloaded.emit(url_, content, id_)
+ except Exception as e:
+ self._logger.error(f"[ERROR] could not download file from '{url_}': "
+ f"{e.__class__.__name__}({str(e.args)})\n")
+
+ def run(self) -> None:
+ download_count = 0
+ futures = []
+ with ThreadPoolExecutor(max_workers=self._max_workers) as executor:
+ while not self._stop and (self._max_downloads <= 0 or download_count < self._max_downloads):
+ try:
+ url_, id_ = self._queue.get(timeout=self._inactivity_timeout)
+ futures.append(executor.submit(self._get, url_, id_))
+ download_count += 1
+ except Exception:
+ self._stop = True
+
+ cancelled_count = 0
+ for f in futures:
+ if not f.done():
+ f.cancel()
+ cancelled_count += 1
+
+ if cancelled_count > 0:
+ self._logger.info(f"{cancelled_count} file downloads cancelled")
+
+ self._logger.info(f"Finished to download files (count={download_count})")
+
+ def stop(self) -> None:
+ self._stop = True
+
+ def get(self, url: str, id_: Optional[object]):
+ final_url = url.strip() if url else None
+
+ if final_url:
+ if RE_URL.match(final_url):
+ self._queue.put((final_url, id_))
+ else:
+ self.signal_downloaded.emit(final_url, None, id_)
diff --git a/bauh/view/qt/view_index.py b/bauh/view/qt/view_index.py
new file mode 100644
index 000000000..0a64657ae
--- /dev/null
+++ b/bauh/view/qt/view_index.py
@@ -0,0 +1,139 @@
+from collections import defaultdict
+from typing import Dict, List, Generator, Tuple, Optional, Union
+
+from bauh.view.qt.commons import PackageFilters
+from bauh.view.qt.view_model import PackageView
+
+
+def new_character_idx() -> Dict[str, List[PackageView]]:
+ return defaultdict(list)
+
+
+def new_category_idx() -> Dict[str, Dict[str, List[PackageView]]]:
+ return defaultdict(new_character_idx)
+
+
+def new_type_index() -> Dict[str, Dict[str, Dict[str, List[PackageView]]]]:
+ return defaultdict(new_category_idx)
+
+
+def new_verified_index() -> Dict[int, Dict[str, Dict[str, Dict[str, List[PackageView]]]]]:
+ return defaultdict(new_type_index)
+
+
+def new_update_index() -> Dict[int, Dict[int, Dict[str, Dict[str, Dict[str, List[PackageView]]]]]]:
+ return defaultdict(new_verified_index)
+
+
+def new_app_index() -> Dict[int, Dict[int, Dict[int, Dict[str, Dict[str, Dict[str, List[PackageView]]]]]]]:
+ return defaultdict(new_update_index)
+
+
+def new_package_index() -> Dict[int, Dict[int, Dict[int, Dict[int, Dict[str, Dict[str, Dict[str, List[PackageView]]]]]]]]:
+ return defaultdict(new_app_index)
+
+
+def add_to_index(pkgv: PackageView, index: dict) -> None:
+ # root keys: (1) installed | (0) not installed
+ root_idx = index[1 if pkgv.model.installed else 0]
+
+ # app keys: (1) app | (0) not app
+ app_lvl = root_idx[1 if pkgv.model.is_application() else 0]
+
+ # update keys: (1) update | (0) no update
+ if pkgv.model.installed and pkgv.model.update and not pkgv.model.is_update_ignored():
+ update_lvl = app_lvl[1]
+ else:
+ update_lvl = app_lvl[0]
+
+ # verified keys: (1) verified | (0) unverified
+ verified_lvl = update_lvl[1 if pkgv.model.is_trustable() else 0]
+
+ norm_name = pkgv.name.strip().lower()
+ starts_with_chars = tuple(norm_name[0:i] for i in range(1, len(norm_name) + 1))
+
+ for cat in ("any", *(pkgv.model.categories if pkgv.model.categories else tuple())):
+ category = cat.lower().strip()
+
+ # any type > specific category > any character (None)
+ verified_lvl["any"][category][None].append(pkgv)
+
+ # any type > specific category > characters (start
+ for chars in starts_with_chars:
+ verified_lvl["any"][category][chars].append(pkgv)
+
+ type_lvl = verified_lvl[pkgv.model.get_type()]
+
+ # specific type > specific category > any character (None)
+ type_lvl[category][None].append(pkgv)
+
+ # specific type > any category > first character
+ for chars in starts_with_chars:
+ type_lvl[category][chars].append(pkgv)
+
+
+def generate_queries(filters: PackageFilters) -> Generator[Tuple[Optional[Union[int, str]], ...], None, None]:
+ chars_query = None
+
+ if filters.name:
+ chars_query = filters.name.lower()
+
+ installed_queries = (1,) if filters.only_installed else (1, 0)
+ apps_queries = (1,) if filters.only_apps else (1, 0)
+ updates_queries = (1,) if filters.only_updates else (1, 0)
+ verified_queries = (1,) if filters.only_verified else (1, 0)
+
+ for installed in installed_queries:
+ for app in apps_queries:
+ for update in updates_queries:
+ for verified in verified_queries:
+ yield installed, app, update, verified, filters.type, filters.category, chars_query
+
+
+def query_packages(index: dict, filters: PackageFilters) -> Generator[PackageView, None, None]:
+ yield_count = 0
+ yield_limit = filters.display_limit if filters.display_limit and filters.display_limit > 0 else -1
+
+ queries = tuple(generate_queries(filters))
+
+ yielded_pkgs = defaultdict(set)
+
+ for query in queries:
+ packages = index[query[0]][query[1]][query[2]][query[3]][query[4]][query[5]][query[6]]
+
+ for pkgv in packages:
+ yield pkgv
+ yield_count += 1
+ yielded_pkgs[pkgv.model.get_type()].add(pkgv.model.id)
+
+ # checking if the package display limit has been reached
+ if 0 < yield_limit <= yield_count:
+ break
+
+ # if there is a limit and the number of yielded packages is not reached, performs also a "contains" query
+ # checking if the queries target "any character" (none), if so, there is no need to perform the "contains" query
+ any_char_query = next((True for q in queries if q[-1] is None), False)
+
+ if not any_char_query and 0 < yield_limit > yield_count:
+ for query in queries:
+ # checking if the package display limit has been reached
+ if 0 < yield_limit <= yield_count:
+ break
+
+ packages = index[query[0]][query[1]][query[2]][query[3]][query[4]][query[5]][None]
+
+ for pkgv in packages:
+ # checking if the package has already been yielded
+ yield_type_idx = yielded_pkgs.get(pkgv.model.get_type())
+ if yield_type_idx and pkgv.model.id in yield_type_idx:
+ continue
+
+ # checking if the package name contains the chars query
+ if query[5] in pkgv.model.name:
+ yield pkgv
+ yield_count += 1
+ yielded_pkgs[pkgv.model.get_type()].add(pkgv.model.id)
+
+ # checking if the package display limit has been reached
+ if 0 < yield_limit <= yield_count:
+ break
diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py
index 7a061f9c5..7fb06c86c 100755
--- a/bauh/view/qt/window.py
+++ b/bauh/view/qt/window.py
@@ -1,12 +1,13 @@
import logging
import operator
import os.path
+import shutil
import time
from pathlib import Path
-from typing import List, Type, Set, Tuple, Optional
+from typing import List, Type, Set, Tuple, Optional, Dict, Any
from PyQt5.QtCore import QEvent, Qt, pyqtSignal, QRect
-from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor, QMoveEvent
+from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor, QCloseEvent, QShowEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
QLabel, QPlainTextEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \
QMenu, QHBoxLayout
@@ -27,7 +28,7 @@
from bauh.view.qt import dialog, commons, qt_utils
from bauh.view.qt.about import AboutDialog
from bauh.view.qt.apps_table import PackagesTable, UpgradeToggleButton
-from bauh.view.qt.commons import sum_updates_displayed
+from bauh.view.qt.commons import sum_updates_displayed, PackageFilters
from bauh.view.qt.components import new_spacer, IconButton, QtComponentsManager, to_widget, QSearchBar, \
QCustomMenuAction, QCustomToolbar
from bauh.view.qt.dialog import ConfirmationDialog
@@ -43,6 +44,7 @@
AsyncAction, LaunchPackage, ApplyFilters, CustomSoftwareAction, ShowScreenshots, CustomAction, \
NotifyInstalledLoaded, \
IgnorePackageUpdates, SaveTheme, StartAsyncAction
+from bauh.view.qt.view_index import add_to_index, new_package_index
from bauh.view.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util import util, resource
from bauh.view.util.translation import I18n
@@ -74,13 +76,14 @@
CHECK_UPDATES = 7
CHECK_APPS = 8
COMBO_TYPES = 9
-COMBO_CATEGORIES = 10
-INP_NAME = 11
-CHECK_DETAILS = 12
-BT_SETTINGS = 13
-BT_CUSTOM_ACTIONS = 14
-BT_ABOUT = 15
-BT_THEMES = 16
+CHECK_VERIFIED = 10
+COMBO_CATEGORIES = 11
+INP_NAME = 12
+CHECK_DETAILS = 13
+BT_SETTINGS = 14
+BT_CUSTOM_ACTIONS = 15
+BT_ABOUT = 16
+BT_THEMES = 17
# component groups ids
GROUP_FILTERS = 1
@@ -110,6 +113,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.pkgs = [] # packages current loaded in the table
self.pkgs_available = [] # all packages loaded in memory
self.pkgs_installed = [] # cached installed packages
+ self.pkg_idx: Optional[Dict[str, Any]] = None # all packages available indexed by the available filters
self.display_limit = config['ui']['table']['max_displayed']
self.icon_cache = icon_cache
self.config = config
@@ -175,6 +179,16 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.toolbar_filters.layout().addWidget(self.check_apps)
self.comp_manager.register_component(CHECK_APPS, self.check_apps)
+ self.check_verified = QCheckBox()
+ self.check_verified.setObjectName('check_verified')
+ self.check_verified.setCursor(QCursor(Qt.PointingHandCursor))
+ self.check_verified.setText(self.i18n['manage_window.checkbox.only_verified'])
+ self.check_verified.setChecked(False)
+ self.check_verified.stateChanged.connect(self._handle_filter_only_verified)
+ self.check_verified.sizePolicy().setRetainSizeWhenHidden(True)
+ self.toolbar_filters.layout().addWidget(self.check_verified)
+ self.comp_manager.register_component(CHECK_VERIFIED, self.check_verified)
+
self.any_type_filter = 'any'
self.cache_type_filter_icons = {}
self.combo_filter_type = QComboBox()
@@ -277,8 +291,10 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.table_container.setLayout(QVBoxLayout())
self.table_container.layout().setContentsMargins(0, 0, 0, 0)
- self.table_apps = PackagesTable(self, self.icon_cache,
- download_icons=bool(self.config['download']['icons']))
+ self.table_apps = PackagesTable(parent=self,
+ icon_cache=self.icon_cache,
+ download_icons=bool(self.config['download']['icons']),
+ logger=logger)
self.table_apps.change_headers_policy()
self.table_container.layout().addWidget(self.table_apps)
@@ -344,7 +360,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.thread_custom_action = self._bind_async_action(CustomAction(manager=self.manager, i18n=self.i18n), finished_call=self._finish_execute_custom_action)
self.thread_screenshots = self._bind_async_action(ShowScreenshots(i18n, self.manager), finished_call=self._finish_show_screenshots)
- self.thread_apply_filters = ApplyFilters(i18n)
+ self.thread_apply_filters = ApplyFilters(i18n=i18n, logger=logger)
self.thread_apply_filters.signal_finished.connect(self._finish_apply_filters)
self.thread_apply_filters.signal_table.connect(self._update_table_and_upgrades)
self.signal_table_update.connect(self.thread_apply_filters.stop_waiting)
@@ -432,6 +448,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.layout.addWidget(self.container_progress)
self.filter_only_apps = True
+ self.filter_only_verified = False
self.type_filter = self.any_type_filter
self.category_filter = self.any_category_filter
self.filter_updates = False
@@ -455,24 +472,29 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.thread_load_installed.signal_loaded.connect(self._finish_loading_installed)
self._register_groups()
self._screen_geometry: Optional[QRect] = None
+ self.searched_term: Optional[str] = None # last searched term
+ self._can_open_urls: Optional[bool] = None # whether URLs can be opened in the browser
+
+ qt_utils.centralize(self)
def _register_groups(self):
- common_filters = (CHECK_APPS, CHECK_UPDATES, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME)
+ common_filters = (CHECK_APPS, CHECK_VERIFIED, CHECK_UPDATES, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME)
self.comp_manager.register_group(GROUP_FILTERS, False, CHECK_INSTALLED, *common_filters)
self.comp_manager.register_group(GROUP_VIEW_SEARCH, False,
- COMBO_CATEGORIES, COMBO_TYPES, INP_NAME, # filters
- BT_INSTALLED, BT_SUGGESTIONS, CHECK_INSTALLED) # buttons
+ CHECK_INSTALLED, CHECK_VERIFIED, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME,
+ BT_INSTALLED, BT_SUGGESTIONS)
self.comp_manager.register_group(GROUP_VIEW_INSTALLED, False,
BT_REFRESH, BT_UPGRADE, # buttons
*common_filters)
self.comp_manager.register_group(GROUP_UPPER_BAR, False,
- CHECK_APPS, CHECK_UPDATES, CHECK_INSTALLED, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME,
- BT_INSTALLED, BT_SUGGESTIONS, BT_REFRESH, BT_UPGRADE)
+ CHECK_APPS, CHECK_VERIFIED, CHECK_UPDATES, CHECK_INSTALLED, COMBO_CATEGORIES,
+ COMBO_TYPES, INP_NAME, BT_INSTALLED, BT_SUGGESTIONS, BT_REFRESH, BT_UPGRADE)
- self.comp_manager.register_group(GROUP_LOWER_BTS, False, BT_SUGGESTIONS, BT_THEMES, BT_CUSTOM_ACTIONS, BT_SETTINGS, BT_ABOUT)
+ self.comp_manager.register_group(GROUP_LOWER_BTS, False, BT_SUGGESTIONS, BT_THEMES, BT_CUSTOM_ACTIONS,
+ BT_SETTINGS, BT_ABOUT)
def update_custom_actions(self):
self.custom_actions = [a for a in self.manager.gen_custom_actions()]
@@ -498,13 +520,15 @@ def _set_table_enabled(self, enabled: bool):
def begin_apply_filters(self):
self.stop_notifying_package_states()
+
self._begin_action(action_label=self.i18n['manage_window.status.filtering'],
action_id=ACTION_APPLY_FILTERS)
self.comp_manager.disable_visible_from_groups(GROUP_UPPER_BAR, GROUP_LOWER_BTS)
self.comp_manager.set_component_read_only(INP_NAME, True)
- self.thread_apply_filters.filters = self._gen_filters()
self.thread_apply_filters.pkgs = self.pkgs_available
+ self.thread_apply_filters.index = self.pkg_idx
+ self.thread_apply_filters.filters = self._gen_filters()
self.thread_apply_filters.start()
self.setFocus(Qt.NoFocusReason)
@@ -518,8 +542,13 @@ def stop_notifying_package_states(self):
self.signal_stop_notifying.emit()
self.thread_notify_pkgs_ready.wait(1000)
- def _update_table_and_upgrades(self, pkgs_info: dict):
- self._update_table(pkgs_info=pkgs_info, signal=True)
+ def _update_table_and_upgrades(self, packages_displayed: List[PackageView]):
+ # generating a mocked info to keep compatibility with '_update_table' inputs.
+ # 'not_installed' is only used for a quick check and does not require an exact number (only 0 or 1)
+ info = {"pkgs_displayed": packages_displayed,
+ "not_installed": 1 if len(self.pkgs_installed) != len(self.pkgs_available) else 0}
+
+ self._update_table(pkgs_info=info, signal=True)
if self.pkgs:
self._update_state_when_pkgs_ready()
@@ -583,21 +612,21 @@ def _show_warnings(self, warnings: List[str]):
if warnings:
dialog.show_message(title=self.i18n['warning'].capitalize(), body='
{}
'.format('
'.join(warnings)), type_=MessageType.WARNING)
- def show(self):
- super(ManageWindow, self).show()
-
+ def showEvent(self, event: Optional[QShowEvent]) -> None:
+ super().showEvent(event)
if not self.thread_warnings.isFinished():
self.thread_warnings.start()
- qt_utils.centralize(self)
self._screen_geometry = get_current_screen_geometry()
self._update_size_limits()
+ qt_utils.centralize(self)
def verify_warnings(self):
self.thread_warnings.start()
def _begin_loading_installed(self):
if self.installed_loaded:
+ self.table_apps.stop_file_downloader()
self.search_bar.clear()
self.input_name.set_text('')
self._begin_action(self.i18n['manage_window.status.installed'])
@@ -642,6 +671,10 @@ def _handle_filter_only_apps(self, status: int):
self.filter_only_apps = status == 2
self.begin_apply_filters()
+ def _handle_filter_only_verified(self, status: int):
+ self.filter_only_verified = status == 2
+ self.begin_apply_filters()
+
def _handle_filter_only_installed(self, status: int):
self.filter_installed = status == 2
self.begin_apply_filters()
@@ -725,6 +758,7 @@ def _handle_console_option(self, enable: bool):
self.textarea_details.hide()
def begin_refresh_packages(self, pkg_types: Optional[Set[Type[SoftwarePackage]]] = None):
+ self.table_apps.stop_file_downloader()
self.search_bar.clear()
self._begin_action(self.i18n['manage_window.status.refreshing'])
@@ -752,6 +786,10 @@ def _finish_refresh_packages(self, res: dict, as_installed: bool = True):
self._update_bts_installed_and_suggestions()
self._reorganize()
+ if self.first_refresh:
+ self.first_refresh = False
+ qt_utils.centralize(self)
+
self.load_suggestions = False
self.types_changed = False
@@ -761,6 +799,7 @@ def load_without_packages(self):
self._finish_refresh_packages({'installed': None, 'types': None}, as_installed=False)
def begin_load_suggestions(self, filter_installed: bool):
+ self.table_apps.stop_file_downloader()
self.search_bar.clear()
self._begin_action(self.i18n['manage_window.status.suggestions'])
self._handle_console_option(False)
@@ -838,6 +877,7 @@ def _finish_uninstall(self, res: dict):
self.update_custom_actions()
self._show_console_checkbox_if_output()
self._update_installed_filter()
+ self._update_index()
self.begin_apply_filters()
self.table_apps.change_headers_policy(policy=QHeaderView.Stretch, maximized=self._maximized)
self.table_apps.change_headers_policy(policy=QHeaderView.ResizeToContents, maximized=self._maximized)
@@ -854,6 +894,13 @@ def _update_table_indexes(self):
for new_idx, pkgv in enumerate(self.pkgs): # updating the package indexes
pkgv.table_index = new_idx
+ def _update_index(self):
+ if self.pkgs_available:
+ idx = new_package_index()
+ for pkgv in self.pkgs_available:
+ add_to_index(pkgv, idx)
+ self.pkg_idx = idx
+
def begin_launch_package(self, pkg: PackageView):
self._begin_action(action_label=self.i18n['manage_window.status.running_app'].format(pkg.model.name),
action_id=ACTION_LAUNCH)
@@ -884,9 +931,9 @@ def _reorganize(self):
self._resize(accept_lower_width=len(self.pkgs) > 0)
def _update_table(self, pkgs_info: dict, signal: bool = False):
- self.pkgs = pkgs_info['pkgs_displayed']
+ self.pkgs = pkgs_info["pkgs_displayed"]
- if pkgs_info['not_installed'] == 0:
+ if pkgs_info["not_installed"] == 0:
update_check = sum_updates_displayed(pkgs_info) > 0
else:
update_check = False
@@ -900,9 +947,9 @@ def _update_table(self, pkgs_info: dict, signal: bool = False):
self._resize(accept_lower_width=len(self.pkgs) > 0)
if len(self.pkgs) == 0 and len(self.pkgs_available) == 0:
- self.label_displayed.setText('')
+ self.label_displayed.setText("")
else:
- self.label_displayed.setText('{} / {}'.format(len(self.pkgs), len(self.pkgs_available)))
+ self.label_displayed.setText(f"{len(self.pkgs)} / {len(self.pkgs_available)}")
else:
self.label_displayed.hide()
@@ -952,20 +999,21 @@ def _change_checkbox(self, checkbox: QCheckBox, checked: bool, attr: str = None,
setattr(self, attr, checked)
checkbox.blockSignals(False)
- def _gen_filters(self, ignore_updates: bool = False) -> dict:
- return {
- 'only_apps': False if self.search_performed else self.filter_only_apps,
- 'type': self.type_filter,
- 'category': self.category_filter,
- 'updates': False if ignore_updates else self.filter_updates,
- 'name': self.input_name.text().lower() if self.input_name.text() else None,
- 'display_limit': None if self.filter_updates else self.display_limit,
- 'only_installed': self.filter_installed
- }
+ def _gen_filters(self, ignore_updates: bool = False) -> PackageFilters:
+ return PackageFilters(category=self.category_filter,
+ display_limit=0 if self.filter_updates else self.display_limit,
+ name=self.input_name.text().strip(),
+ only_apps=False if self.search_performed else self.filter_only_apps,
+ only_verified=self.filter_only_verified,
+ only_updates=False if ignore_updates else self.filter_updates,
+ only_installed=self.filter_installed,
+ search=self.searched_term,
+ type=self.type_filter)
def update_pkgs(self, new_pkgs: Optional[List[SoftwarePackage]], as_installed: bool, types: Optional[Set[type]] = None, ignore_updates: bool = False, keep_filters: bool = False) -> bool:
self.input_name.set_text('')
pkgs_info = commons.new_pkgs_info()
+ pkg_idx = new_package_index()
filters = self._gen_filters(ignore_updates=ignore_updates)
if new_pkgs is not None:
@@ -976,19 +1024,22 @@ def update_pkgs(self, new_pkgs: Optional[List[SoftwarePackage]], as_installed: b
self.pkgs_installed = []
for pkg in new_pkgs:
- app_model = PackageView(model=pkg, i18n=self.i18n)
- commons.update_info(app_model, pkgs_info)
- commons.apply_filters(app_model, filters, pkgs_info)
+ pkgv = PackageView(model=pkg, i18n=self.i18n)
+ commons.update_info(pkgv, pkgs_info)
+ add_to_index(pkgv, pkg_idx)
+ commons.apply_filters(pkgv, filters, pkgs_info)
if old_installed and types:
for pkgv in old_installed:
if pkgv.model.__class__ not in types:
commons.update_info(pkgv, pkgs_info)
+ add_to_index(pkgv, pkg_idx)
commons.apply_filters(pkgv, filters, pkgs_info)
else: # use installed
for pkgv in self.pkgs_installed:
commons.update_info(pkgv, pkgs_info)
+ add_to_index(pkgv, pkg_idx)
commons.apply_filters(pkgv, filters, pkgs_info)
if pkgs_info['apps_count'] == 0 and not self.suggestions_requested:
@@ -1009,6 +1060,7 @@ def update_pkgs(self, new_pkgs: Optional[List[SoftwarePackage]], as_installed: b
self.check_apps.setCheckable(True)
self._change_checkbox(self.check_apps, True, 'filter_only_apps', trigger=False)
+ self._update_verified_filter(verified_available=pkgs_info['verified'] > 0, keep_state=keep_filters)
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False, keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
self._update_categories(pkgs_info['categories'], keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
self._update_type_filters(pkgs_info['available_types'], keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
@@ -1016,6 +1068,7 @@ def update_pkgs(self, new_pkgs: Optional[List[SoftwarePackage]], as_installed: b
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False, keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
self.pkgs_available = pkgs_info['pkgs']
+ self.pkg_idx = pkg_idx
if as_installed:
self.pkgs_installed = pkgs_info['pkgs']
@@ -1034,10 +1087,6 @@ def update_pkgs(self, new_pkgs: Optional[List[SoftwarePackage]], as_installed: b
self._resize(accept_lower_width=bool(self.pkgs_installed))
- if self.first_refresh:
- qt_utils.centralize(self)
- self.first_refresh = False
-
if not self.installed_loaded and as_installed:
self.installed_loaded = True
@@ -1064,6 +1113,19 @@ def _update_installed_filter(self, keep_state: bool = True, hide: bool = False,
else:
self.comp_manager.set_component_visible(CHECK_INSTALLED, has_installed)
+ def _update_verified_filter(self, keep_state: bool = True, verified_available: Optional[bool] = None):
+ if verified_available is not None:
+ has_verified = verified_available
+ else:
+ has_verified = False
+ if self.pkgs_available:
+ has_verified = next((True for p in self.pkgs_available if p.model.is_trustable()), False)
+
+ if not keep_state or not has_verified:
+ self._change_checkbox(self.check_verified, False, 'filter_only_verified', trigger=False)
+
+ self.comp_manager.set_component_visible(CHECK_VERIFIED, has_verified)
+
def _apply_filters(self, pkgs_info: dict, ignore_updates: bool):
pkgs_info['pkgs_displayed'] = []
filters = self._gen_filters(ignore_updates=ignore_updates)
@@ -1265,8 +1327,9 @@ def begin_downgrade(self, pkg: PackageView):
if not proceed:
return
- self._begin_action(action_label='{} {}'.format(self.i18n['manage_window.status.downgrading'], pkg.model.name),
- action_id=ACTION_DOWNGRADE)
+ self.table_apps.stop_file_downloader()
+ label = f"{self.i18n['manage_window.status.downgrading']} {pkg.model.name}"
+ self._begin_action(action_label=label, action_id=ACTION_DOWNGRADE)
self.comp_manager.set_components_visible(False)
self._handle_console_option(True)
@@ -1307,7 +1370,8 @@ def _finish_show_info(self, pkg_info: dict):
if pkg_info:
if len(pkg_info) > 1:
- dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, i18n=self.i18n)
+ dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, i18n=self.i18n,
+ can_open_url=self.can_open_urls)
dialog_info.exec_()
else:
dialog.show_message(title=self.i18n['warning'].capitalize(),
@@ -1360,17 +1424,17 @@ def _finish_show_history(self, res: dict):
dialog_history = HistoryDialog(res['history'], self.icon_cache, self.i18n)
dialog_history.exec_()
- def _begin_search(self, word, action_id: int = None):
- self.filter_updates = False
- self.filter_installed = False
- self._begin_action('{} {}'.format(self.i18n['manage_window.status.searching'], word if word else ''), action_id=action_id)
-
def search(self):
word = self.search_bar.text().strip()
if word:
+ self.table_apps.stop_file_downloader()
self._handle_console(False)
- self._begin_search(word, action_id=ACTION_SEARCH)
+ self.filter_updates = False
+ self.filter_installed = False
+ label = f"{self.i18n['manage_window.status.searching']} {word if word else ''}"
+ self._begin_action(action_label=label, action_id=ACTION_SEARCH)
self.comp_manager.set_components_visible(False)
+ self.searched_term = word
self.thread_search.word = word
self.thread_search.start()
@@ -1510,6 +1574,7 @@ def _finish_install(self, res: dict):
self.update_custom_actions()
self._update_installed_filter(installed_available=True, keep_state=True)
+ self._update_index()
self.table_apps.change_headers_policy(policy=QHeaderView.Stretch, maximized=self._maximized)
self.table_apps.change_headers_policy(policy=QHeaderView.ResizeToContents, maximized=self._maximized)
self._resize(accept_lower_width=False)
@@ -1545,6 +1610,9 @@ def begin_execute_custom_action(self, pkg: Optional[PackageView], action: Custom
else:
action_label += f' {pkg.model.name}'
+ if action.refresh:
+ self.table_apps.stop_file_downloader()
+
self._begin_action(action_label=action_label, action_id=ACTION_CUSTOM_ACTION)
self.comp_manager.set_components_visible(False)
self._handle_console_option(True)
@@ -1590,7 +1658,6 @@ def show_settings(self):
self.settings_window.setMinimumWidth(int(screen_width / 4))
self.settings_window.resize(self.size())
self.settings_window.adjustSize()
- qt_utils.centralize(self.settings_window)
self.settings_window.show()
def _map_custom_action(self, action: CustomSoftwareAction, parent: QWidget) -> QCustomMenuAction:
@@ -1666,6 +1733,7 @@ def finish_ignore_updates(self, res: dict):
break
self._add_pkg_categories(res['pkg'])
+ self._update_index()
dialog.show_message(title=self.i18n['success'].capitalize(),
body=self.i18n['action.{}.success'.format(res['action'])].format(bold(res['pkg'].model.name)),
@@ -1747,3 +1815,14 @@ def _reload(self):
self.verify_warnings()
self.types_changed = True
self.begin_refresh_packages()
+
+ def closeEvent(self, event: QCloseEvent) -> None:
+ # needs to be stopped to avoid a Qt exception/crash
+ self.table_apps.stop_file_downloader(wait=True)
+
+ @property
+ def can_open_urls(self) -> bool:
+ if self._can_open_urls is None:
+ self._can_open_urls = shutil.which("xdg-open")
+
+ return self._can_open_urls
diff --git a/bauh/view/resources/__init__.py b/bauh/view/resources/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/img/__init__.py b/bauh/view/resources/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/locale/__init__.py b/bauh/view/resources/locale/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/locale/about/__init__.py b/bauh/view/resources/locale/about/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca
index 50abfaf6e..a6c8be57d 100644
--- a/bauh/view/resources/locale/ca
+++ b/bauh/view/resources/locale/ca
@@ -66,6 +66,7 @@ action.ignore_updates.success=Updates from {} will be ignored from now on
action.ignore_updates_reverse.fail=It was not possible to revert ignored updates from {}
action.ignore_updates_reverse.success=Updates from {} will be displayed again from now on
action.info.tooltip=Feu clic aquí per a veure informació sobre aquesta aplicació
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Action not allowed
action.request_reboot.title=System restart
action.reset=Restore
@@ -112,50 +113,101 @@ cancel=cancel·la
categories=categories
category=categoria
category.2dgraphics=gràfics 2d
+category.3dgraphics=gràfics 3d
+category.accessibility=accessibility
category.acessories=acessories
+category.actiongame=action game
category.admin=admin
category.administration=administració
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=diversió
category.application=application
category.arcadegame=joc «arcade»
category.archiving=archiving
category.art=art
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=àudio
category.audiovideo=àudio/vídeo
+category.audiovideoediting=àudio/vídeo editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=llibres
category.browser=navegador
+category.building=building
+category.calendar=calendar
category.calculator=calculator
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=núvol
category.communication=comunicació
category.compression=compression
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=core
category.database=base de dades
category.databases=bases de dades
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=disseny
category.desktopsettings=paràmetres de l’escriptori
category.devel=desenvolupament
category.development=desenvolupament
+category.dialup=dial-up
category.devices=dispositius
category.dictionary=dictionary
+category.discburning=disc burning
category.doc=documentation
+category.documentation=documentation
+category.economy=economy
category.education=educació
+category.electricity=electricity
+category.electronics=electronics
category.emulator=emulador
+category.engineering=engineering
category.entertainment=entreteniment
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=transferència de fitxers
category.finance=finances
category.fitness=condició física
+category.flowchart=flow chart
category.fonts=tipus de lletra
category.game=joc
category.games=jocs
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
category.graphics=gràfics
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.health=salut
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=missatgeria
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=biblioteca
category.library=biblioteca
+category.literature=literature
category.localization=localization
+category.logicgame=logic game
+category.maps=maps
category.math=math
category.media=mitjans
+category.medicalsoftware=medical
category.messaging=missatgeria
+category.mixer=mixer
category.monitor=monitoring
category.movie=pel·lícula
category.movies=pel·lícules
@@ -165,37 +217,67 @@ category.network=xarxa
category.networks=xarxes
category.news=notícies
category.none=None
+category.numericalanalysis=numerical analysis
category.office=oficina
category.packagemanager=gestor de paquets
+category.parallelcomputing=parallel computing
category.personalisation=personalització
category.photo=foto
category.photography=foto
-category.player=jugador
+category.physics=physics
+category.player=reproductor
category.presentation=presentation
+category.printing=printing
category.productivity=productivitat
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=referència
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=ciència
+category.screensaver=screensaver
category.security=seguretat
+category.sequencer=sequencer
category.server=servidor
category.settings=configuració
+category.shooter=shooter game
+category.simulation=simulation game
category.social=social
category.sound=sound
+category.spirituality=spirituality
+category.sports=esport
category.sportsgame=joc d’esport
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=sistema
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=terminal
category.text editor=editor de text
category.texteditor=editor de text
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Updates ignored
category.utilities=utilitats
category.utility=utilitat
category.vectorgraphics=gràfics vectorials
category.video=vídeo
+category.videoconference=video conference
category.videoeditor=editor de vídeo
category.viewer=visor
category.weather=el temps
category.web=web
category.webbrowser=navegador
category.webdevelopment=desenvolupament web
+category.wordprocessor=word processor
change=modifica
clean=netejar
close=tanca
@@ -344,11 +426,13 @@ manage_window.bt_themes.tip=Click here to choose a theme
manage_window.bt_themes.option.invalid=Invalid
manage_window.checkbox.only_apps=Aplicacions
manage_window.checkbox.only_installed=Instal·lats
+manage_window.checkbox.only_verified=Verificats
manage_window.checkbox.show_details=Mostra detalls
manage_window.columns.installed=Installed
manage_window.columns.latest_version=Versió més recent
manage_window.columns.update=Voleu actualitzar?
manage_window.info.no_info=There is no information available for {}
+manage_window.info.open_url=Obrir
manage_window.label.apps_displayed.tip=Applications displayed / available
manage_window.label.updates=Actualitzacions
manage_window.name_filter.button_tooltip=Click here to filter apps by name
diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de
index 6826140db..98aeccb1d 100644
--- a/bauh/view/resources/locale/de
+++ b/bauh/view/resources/locale/de
@@ -66,6 +66,7 @@ action.ignore_updates.success=Updates von {} werden an jetzt ignoriert
action.ignore_updates_reverse.fail=Es war nicht möglich, ignorierte Aktualisierungen von {} rückgängig zu machen
action.ignore_updates_reverse.success=Aktualisierungen von {} werden ab jetzt wieder angezeigt
action.info.tooltip=Klicken Sie hier, um Informationen über diese Anwendung anzuzeigen
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Aktion nicht erlaubt
action.request_reboot.title=System neustarten
action.reset=Wiederherstellen
@@ -111,51 +112,102 @@ bt.not_now=Nicht jetzt
cancel=Abbrechen
categories=Kategorien
category.2dgraphics=2D-Grafik
+category.3dgraphics=3D-Grafik
+category.accessibility=accessibility
category.acessories=Zubehör
+category.actiongame=action game
category.admin=Administrator
category.administration=Verwaltung
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=Vergnügen
category.application=Anwendung
category.arcadegame=Arcade-Spiel
category.archiving=Archivierung
category.art=Kunst
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=Audio
category.audiovideo=Audio/Video
+category.audiovideoediting=audio/video editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=Bücher
category.browser=Browser
+category.building=building
+category.calendar=calendar
category.calculator=Taschenrechner
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=Cloud
category.communication=Kommunikation
category.compression=Komprimierung
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=Kern
category.database=Datenbank
category.databases=Datenbanken
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=Design
category.desktopsettings=Desktop-Einstellungen
category.devel=Entwicklung
category.development=Entwicklung
+category.dialup=dial-up
category.dictionary=Wörterbuch
-category.doc=Dokumentation
+category.discburning=disc burning
+category.doc=dokumentation
+category.documentation=dokumentation
+category.economy=economy
category.education=Bildung
+category.electricity=electricity
+category.electronics=electronics
category.emulator=Emulator
+category.engineering=engineering
category.entertainment=Unterhaltung
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=Dateiübertragung
category.finance=Finanzen
category.fitness=Fitness
+category.flowchart=flow chart
category.fonts=Schriftarten
category.game=Spiel
category.games=Spiele
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.graphics=Graphiken
category.hardwaresettings=Hardware
category.health=Gesundheit
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=Nachrichtensofortversand
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=Bibliothek
category.library=Bibliothek
+category.literature=literature
category.localization=Lokalisation
+category.logicgame=logic game
+category.maps=maps
category.math=Mathematik
category.mail=E-Mail
category.media=Medien
+category.medicalsoftware=medical
category.messaging=Nachrichtenübermittlung
+category.mixer=mixer
category.monitor=Überwachung
category.movie=Film
category.movies=Filme
@@ -165,37 +217,67 @@ category.network=Netzwerk
category.networks=Netzwerke
category.news=Nachrichten
category.none=Keine
+category.numericalanalysis=numerical analysis
category.office=Büro
category.packagemanager=Paketmanager
+category.parallelcomputing=parallel computing
category.personalisation=Personalisierung
category.photo=Foto
category.photography=Foto
+category.physics=physics
category.player=Spieler
category.presentation=Präsentation
+category.printing=printing
category.productivity=Produktivität
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=Referenz
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=Wissenschaft
+category.screensaver=screensaver
category.security=Sicherheit
+category.sequencer=sequencer
category.server=Server
category.settings=Einstellungen
+category.shooter=shooter game
+category.simulation=simulation game
category.social=Sozial
category.sound=Sound
+category.spirituality=spirituality
+category.sports=sports
category.sportsgame=Sportspiel
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=System
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=Terminal
category.text editor=Textverarbeitung
category.texteditor=Textverarbeitung
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Updates ignoriert
category.utilities=Dienstprogramme
category.utility=Dienstprogramm
category.vectorgraphics=Vektorgrafik
category.video=Video
+category.videoconference=video conference
category.videoeditor=Videoverarbeitung
category.viewer=Betrachter
category.weather=Wetter
category.web=Web
category.webbrowser=Browser
category.webdevelopment=Web-Entwicklung
+category.wordprocessor=word processor
category=Kategorie
change=Ändern
clean=Bereinigen
@@ -346,11 +428,13 @@ manage_window.bt_themes.tip=Klicken Sie hier, um ein Thema auszuwählen
manage_window.bt_themes.option.invalid=Ungültig
manage_window.checkbox.only_apps=Anwendungen
manage_window.checkbox.only_installed=Installierte
+manage_window.checkbox.only_verified=Verifiziert
manage_window.checkbox.show_details=Details anzeigen
manage_window.columns.installed=Installiert
manage_window.columns.latest_version=Neueste Version
manage_window.columns.update=Upgraden?
manage_window.info.no_info=Es sind keine Informationen verfügbar für {}
+manage_window.info.open_url=Öffnen
manage_window.label.apps_displayed.tip=Anwendungen angezeigt / verfügbar
manage_window.label.updates=Aktualisierungen
manage_window.name_filter.button_tooltip=Klicken Sie hier, um Anwendungen nach Namen zu filtern
diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en
index caff3dc1f..113f6cfb7 100644
--- a/bauh/view/resources/locale/en
+++ b/bauh/view/resources/locale/en
@@ -66,6 +66,7 @@ action.ignore_updates.success=Updates from {} will be ignored from now on
action.ignore_updates_reverse.fail=It was not possible to revert ignored updates from {}
action.ignore_updates_reverse.success=Updates from {} will be displayed again from now on
action.info.tooltip=Click here to see information about this application
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Action not allowed
action.request_reboot.title=System restart
action.reset=Restore
@@ -111,51 +112,102 @@ bt.not_now=Not now
cancel=cancel
categories=categories
category.2dgraphics=2d graphics
+category.3dgraphics=3d graphics
+category.accessibility=accessibility
category.acessories=acessories
+category.actiongame=action game
category.admin=admin
category.administration=administration
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=amusement
category.application=application
category.arcadegame=arcade game
category.archiving=archiving
category.art=art
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=audio
-category.audiovideo=audio / video
+category.audiovideo=audio/video
+category.audiovideoediting=audio/video editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=books
category.browser=browser
+category.building=building
+category.calendar=calendar
category.calculator=calculator
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=cloud
category.communication=communication
category.compression=compression
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=core
category.database=database
category.databases=databases
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=design
category.desktopsettings=desktop settings
category.devel=development
category.development=development
+category.dialup=dial-up
category.dictionary=dictionary
+category.discburning=disc burning
category.doc=documentation
+category.documentation=documentation
+category.economy=economy
category.education=education
+category.electricity=electricity
+category.electronics=electronics
category.emulator=emulator
+category.engineering=engineering
category.entertainment=entertainment
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=file transfer
category.finance=finance
category.fitness=fitness
+category.flowchart=flow chart
category.fonts=fonts
category.game=game
category.games=games
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.graphics=graphics
category.hardwaresettings=hardware
category.health=health
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=messaging
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=library
category.library=library
+category.literature=literature
category.localization=localization
+category.logicgame=logic game
+category.maps=maps
category.math=math
category.mail=e-mail
category.media=media
+category.medicalsoftware=medical
category.messaging=messaging
+category.mixer=mixer
category.monitor=monitoring
category.movie=movie
category.movies=movies
@@ -164,38 +216,68 @@ category.net=network
category.network=network
category.networks=networks
category.news=news
-category.none=None
+category.none=none
+category.numericalanalysis=numerical analysis
category.office=office
category.packagemanager=package manager
+category.parallelcomputing=parallel computing
category.personalisation=personalization
category.photo=photography
category.photography=photography
+category.physics=physics
category.player=player
category.presentation=presentation
+category.printing=printing
category.productivity=productivity
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=reference
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=science
+category.screensaver=screensaver
category.security=security
+category.sequencer=sequencer
category.server=server
category.settings=settings
+category.shooter=shooter game
+category.simulation=simulation game
category.social=social
category.sound=sound
+category.spirituality=spirituality
+category.sports=sports
category.sportsgame=sports game
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=system
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=terminal
category.text editor=text editor
category.texteditor=text editor
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Updates ignored
category.utilities=utilities
category.utility=utility
category.vectorgraphics=vector graphics
category.video=video
+category.videoconference=video conference
category.videoeditor=video editor
category.viewer=viewer
category.weather=weather
category.web=web
category.webbrowser=browser
category.webdevelopment=web development
+category.wordprocessor=word processor
category=category
change=change
clean=clean
@@ -346,11 +428,13 @@ manage_window.bt_themes.tip=Click here to choose a theme
manage_window.bt_themes.option.invalid=Invalid
manage_window.checkbox.only_apps=Apps
manage_window.checkbox.only_installed=Installed
+manage_window.checkbox.only_verified=Verified
manage_window.checkbox.show_details=Show details
manage_window.columns.installed=Installed
manage_window.columns.latest_version=Latest Version
manage_window.columns.update=Upgrade ?
manage_window.info.no_info=There is no information available for {}
+manage_window.info.open_url=Open
manage_window.label.apps_displayed.tip=Applications displayed / available
manage_window.label.updates=Updates
manage_window.name_filter.button_tooltip=Click here to filter apps by name
diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es
index 9ac06a98f..31b21e6c6 100644
--- a/bauh/view/resources/locale/es
+++ b/bauh/view/resources/locale/es
@@ -66,6 +66,7 @@ action.ignore_updates.success=Las actualizaciones de {} serán ignoradas de ahor
action.ignore_updates_reverse.fail=No fue posible revertir las actualizaciones ignoradas de {}
action.ignore_updates_reverse.success=Las actualizaciones de {} se mostrarán nuevamente a partir de ahora
action.info.tooltip=Pulse aquí para ver información sobre esta aplicación
+action.install.unverified.warning=Este ap no ha sido verificado por los mantenedores de este sistema ni por una fuente confiable. Verifique su información antes de continuar.
action.not_allowed=Acción no permitida
action.request_reboot.title=Reinicio de sistema
action.reset=Restaurar
@@ -112,50 +113,101 @@ cancel=cancelar
categories=categorías
category=categoría
category.2dgraphics=gráficos 2d
+category.3dgraphics=gráficos 3d
+category.accessibility=accesibilidad
category.acessories=accesorios
+category.actiongame=juego de acción
category.admin=administrador
category.administration=administración
+category.adult=adulto
+category.adventuregame=juego de aventura
category.amusement=diversión
category.application=aplicación
category.applications=aplicaciones
category.arcadegame=juego de máquina
category.archiving=archivo
category.art=arte
+category.artificialintelligence=inteligencia artificial
+category.astronomy=astronomía
category.audio=audio
-category.audiovideo=audio y vídeo
+category.audiovideo=audio/vídeo
+category.audiovideoediting=edición de audio/vídeo
+category.biology=biología
+category.blocksgame=juego de bloques
+category.boardgame=juego de mesa
category.books=libros
category.browser=navegador
+category.building=compilación
+category.calendar=calendario
category.calculator=calculadora
+category.cardgame=juego de cartas
+category.chart=gráfico
+category.chemistry=química
+category.clock=reloj
category.cloud=nube
category.communication=comunicación
category.compression=compresión
+category.computerscience=informática
+category.consoleonly=solo consola
+category.construction=construcción
+category.contactmanagement=contactos
category.core=núcleo
category.database=base de datos
category.databases=bases de datos
+category.datavisualization=visualización de datos
+category.debugger=depurador
category.design=diseño
category.desktopsettings=configuraciones
category.devel=desarrollo
category.development=desarrollo
+category.dialup=marcación
category.dictionary=diccionario
+category.discburning=grabador de disco
category.doc=documentación
+category.documentation=documentación
+category.economy=economía
category.education=educación
+category.electricity=electricidad
+category.electronics=electrónica
category.emulator=emulador
+category.engineering=ingeniería
category.entertainment=entretenimiento
+category.feed=feed
+category.filemanager=administrador de archivos
+category.filesystem=sistema de archivos
+category.filetools=herramientas de archivo
category.filetransfer=transferencia de archivos
category.finance=finanzas
category.fitness=condición física
+category.flowchart=diagrama de flujo
category.fonts=tipos de letra
category.game=juego
category.games=juegos
+category.geography=geografía
+category.geology=geología
+category.geoscience=geociencia
category.graphics=gráficos
+category.hamradio=radioaficionado
+category.hardwaresettings=configuración de hardware
category.health=salud
+category.history=historia
+category.humanities=humanidades
+category.imageprocessing=procesamiento de imágenes
category.instantmessaging=mensajería
+category.ircclient=IRC
+category.kidsgame=juego de niños
+category.languages=idiomas
category.libs=biblioteca
category.library=biblioteca
+category.literature=literatura
category.localization=localización
+category.logicgame=juego de lógica
+category.maps=mapas
category.math=matemáticas
category.media=media
+category.medicalsoftware=médico
category.messaging=mensajería
+category.mixer=mixer
category.monitor=monitoreo
category.movie=película
category.movies=películas
@@ -165,37 +217,67 @@ category.network=red
category.networks=redes
category.news=noticias
category.none=ninguna
+category.numericalanalysis=análisis numérico
category.office=oficina
category.packagemanager=administrador de paquetes
+category.parallelcomputing=computación paralela
category.personalisation=personalización
category.photo=fotografía
category.photography=fotografía
-category.player=jugador
+category.physics=física
+category.player=reproductor
category.presentation=presentación
+category.printing=impresión
category.productivity=productividad
+category.profiling=desempeño
+category.projectmanagement=proyectos
+category.publishing=publicación
+category.rastergraphics=gráficos rasterizados
+category.recorder=grabador
+category.remoteaccess=accesso remoto
category.reference=referencia
+category.revisioncontrol=control de versión
+category.robotics=robótica
+category.roleplaying=juego de RPG
+category.scanning=escaneo
category.science=ciencias
+category.screensaver=protector de pantalla
category.security=seguridad
+category.sequencer=secuenciador
category.server=servidor
category.settings=configuraciones
+category.shooter=juego de disparos
+category.simulation=juego de simulación
category.social=social
category.sound=sonido
+category.spirituality=espiritualidad
+category.sports=deportes
category.sportsgame=juego de deportes
+category.spreadsheet=hoja de cálculo
+category.strategygame=jogo de estrategia
category.system=sistema
+category.telephony=telefonía
+category.telephonytools=herramientas de telefonía
category.terminalemulator=terminal
category.text editor=text editor
category.texteditor=editor de texto
+category.texttools=herramientas de texto
+category.translation=traducción
+category.trayicon=icono de bandeja
+category.tuner=sintonizador
category.updates_ignored=Actualizaciones ignoradas
category.utilities=utilidades
category.utility=utilidad
category.vectorgraphics=gráficos vectoriales
category.video=vídeo
+category.videoconference=video conferencia
category.videoeditor=editor de vídeos
category.viewer=viewer
category.weather=tiempo
category.web=web
category.webbrowser=navegador
category.webdevelopment=desarrollo web
+category.wordprocessor=editor de texto
change=cambiar
clean=limpiar
close=cerrar
@@ -345,11 +427,13 @@ manage_window.bt_themes.tip=Pulse aquí para elegir un tema
manage_window.bt_themes.option.invalid=Inválido
manage_window.checkbox.only_apps=Aplicaciones
manage_window.checkbox.only_installed=Instalados
+manage_window.checkbox.only_verified=Verificados
manage_window.checkbox.show_details=Mostrar detalles
manage_window.columns.installed=Instaladas
manage_window.columns.latest_version=Versión más reciente
manage_window.columns.update=¿Quiere actualizar?
manage_window.info.no_info=No hay información disponible para {}
+manage_window.info.open_url=Abrir
manage_window.label.apps_displayed.tip=Applicaciones mostradas / disponibles
manage_window.label.updates=Actualizaciones
manage_window.name_filter.button_tooltip=Haga clic aquí para filtrar aplicaciones por nombre
diff --git a/bauh/view/resources/locale/fr b/bauh/view/resources/locale/fr
index 5b0a2a727..62bc472e7 100644
--- a/bauh/view/resources/locale/fr
+++ b/bauh/view/resources/locale/fr
@@ -66,6 +66,7 @@ action.ignore_updates.success=Les mises à jour de {} seront désormais ignorée
action.ignore_updates_reverse.fail=Il était impossible d'annuler les mises à jour de {}
action.ignore_updates_reverse.success=Les mises à jour de {} sont désormais de retour
action.info.tooltip=Cliquez ici pour plus d'informations sur cette application
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Action non permise
action.request_reboot.title=Redémarrage du système
action.reset=Restaurer
@@ -111,49 +112,100 @@ bt.not_now=Pas maintenant
cancel=Annuler
categories=categories
category.2dgraphics=graphisqme 2d
+category.3dgraphics=graphisqme 3d
+category.accessibility=accessibility
category.acessories=acessories
+category.actiongame=action game
category.admin=admin
category.administration=administration
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=amusement
category.application=application
category.arcadegame=jeu d'arcade
category.archiving=archiving
category.art=art
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=son
-category.audiovideo=son / video
+category.audiovideo=son/video
+category.audiovideoediting=son/video editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=livres
category.browser=navigateur
+category.building=building
+category.calendar=calendar
category.calculator=calculator
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=cloud
category.communication=communication
category.compression=compression
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=core
category.database=base de données
category.databases=bases de données
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=design
category.desktopsettings=configuration
category.devel=développement
category.development=développement
+category.dialup=dial-up
category.dictionary=dictionary
+category.discburning=disc burning
category.doc=documentation
+category.documentation=documentation
+category.economy=economy
category.education=éducation
+category.electricity=electricity
+category.electronics=electronics
category.emulator=émulateur
+category.engineering=engineering
category.entertainment=divertissement
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=transfert de fichiers
category.finance=finances
category.fitness=sport
+category.flowchart=flow chart
category.fonts=polices
category.game=jeu
category.games=jeux
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
category.graphics=graphiques
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.health=santé
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=messagerie instantanée
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=bibliothèque
category.library=bibliothèque
+category.literature=literature
category.localization=localization
+category.logicgame=logic game
+category.maps=maps
category.math=math
category.media=media
+category.medicalsoftware=medical
category.messaging=messagerie
+category.mixer=mixer
category.monitor=monitoring
category.movie=film
category.movies=films
@@ -163,37 +215,67 @@ category.network=réseau
category.networks=réseaux
category.news=presse
category.none=Aucun
+category.numericalanalysis=numerical analysis
category.office=bureautique
category.packagemanager=gestionnaire de paquets
+category.parallelcomputing=parallel computing
category.personalisation=personalisation
category.photo=photo
category.photography=photo
+category.physics=physics
category.player=lecteur
category.presentation=presentation
+category.printing=printing
category.productivity=productivité
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=reference
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=science
+category.screensaver=screensaver
category.security=securité
+category.sequencer=sequencer
category.server=serveur
category.settings=paramères
+category.shooter=shooter game
+category.simulation=simulation game
category.social=social
category.sound=sound
+category.spirituality=spirituality
+category.sports=sports
category.sportsgame=jeu de sports
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=système
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=terminal
category.text editor=editeur de texte
category.texteditor=editeur de texte
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Mises à jour ignorées
category.utilities=utilitaires
category.utility=utilitaire
category.vectorgraphics=graphisme vectoriel
category.video=vidéo
+category.videoconference=video conference
category.videoeditor=éditeur vidéo
category.viewer=viewer
category.weather=météo
category.web=web
category.webbrowser=navigateur
category.webdevelopment=développement web
+category.wordprocessor=word processor
category=categorie
change=changement
clean=vider
@@ -342,11 +424,13 @@ manage_window.bt_themes.tip=Click here to choose a theme
manage_window.bt_themes.option.invalid=Invalid
manage_window.checkbox.only_apps=Apps
manage_window.checkbox.only_installed=Installées
+manage_window.checkbox.only_verified=Vérifiés
manage_window.checkbox.show_details=Détails
manage_window.columns.installed=Installé
manage_window.columns.latest_version=Dernière Version
manage_window.columns.update=Mettre à jour ?
manage_window.info.no_info=There is no information available for {}
+manage_window.info.open_url=Ouvrir
manage_window.label.updates=Mises à jour
manage_window.name_filter.placeholder=Filtrer par nom
manage_window.name_filter.tooltip=Taper ici pour filtrer les applications par nom
diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it
index 30617852b..e57602128 100644
--- a/bauh/view/resources/locale/it
+++ b/bauh/view/resources/locale/it
@@ -66,6 +66,7 @@ action.ignore_updates.success=Updates from {} will be ignored from now on
action.ignore_updates_reverse.fail=It was not possible to revert ignored updates from {}
action.ignore_updates_reverse.success=Updates from {} will be displayed again from now on
action.info.tooltip=Clicca qui per vedere informazioni su questa applicazione
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Action not allowed
action.request_reboot.title=System restart
action.reset=Restore
@@ -112,49 +113,100 @@ cancel=cancella
categories=categorie
category=categoria
category.2dgraphics=Grafica 2D
+category.3dgraphics=Grafica 3D
+category.accessibility=accessibility
category.acessories=acessories
+category.actiongame=action game
category.admin=admin
category.administration=amministrazione
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=amusement
category.application=application
category.arcadegame=Gioco arcade
category.archiving=archiving
category.art=art
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=audio
-category.audiovideo=audio / video
+category.audiovideo=audio/video
+category.audiovideoediting=audio/video editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=books
category.browser=browser
+category.building=building
+category.calendar=calendar
category.calculator=calculator
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=cloud
category.communication=comunicazione
category.compression=compression
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=core
category.database=database
category.databases=databases
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=design
category.desktopsettings=impostazioni del desktop
category.devel=development
category.development=development
+category.dialup=dial-up
category.dictionary=dictionary
+category.discburning=disc burning
category.doc=documentation
+category.documentation=documentation
+category.economy=economy
category.education=education
+category.electricity=electricity
+category.electronics=electronics
category.emulator=emulatore
+category.engineering=engineering
category.entertainment=entertainment
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=trasferimento di file
category.finance=finance
category.fitness=fitness
+category.flowchart=flow chart
category.fonts=fonts
category.game=game
category.games=games
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
category.graphics=graphics
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.health=health
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=messaggistica
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=library
category.library=library
+category.literature=literature
category.localization=localization
+category.logicgame=logic game
+category.maps=maps
category.math=math
category.media=media
+category.medicalsoftware=medical
category.messaging=messaggistica
+category.mixer=mixer
category.monitor=monitoring
category.movie=movie
category.movies=movies
@@ -164,37 +216,67 @@ category.network=network
category.networks=networks
category.news=news
category.none=None
+category.numericalanalysis=numerical analysis
category.office=office
category.packagemanager=gestore pacchetti
+category.parallelcomputing=parallel computing
category.personalisation=personalization
category.photo=photo
category.photography=photo
-category.player=giocatore
+category.physics=physics
+category.player=lettore
category.presentation=presentation
+category.printing=printing
category.productivity=productivity
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=reference
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=science
+category.screensaver=screensaver
category.security=security
+category.sequencer=sequencer
category.server=server
category.settings=impostazioni
+category.shooter=shooter game
+category.simulation=simulation game
category.social=social
category.sound=sound
+category.spirituality=spirituality
+category.sports=sport
category.sportsgame=gioco di sport
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=system
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=terminale
category.text editor=text editor
category.texteditor=editor di testo
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Updates ignored
category.utilities=utilities
category.utility=utility
category.vectorgraphics=grafica vettoriale
category.video=video
+category.videoconference=video conference
category.videoeditor=video editor
category.viewer=viewer
category.weather=weather
category.web=web
category.webbrowser=browser
category.webdevelopment=sviluppo web
+category.wordprocessor=word processor
change=Cambia
clean=pulire
close=vicino
@@ -345,11 +427,13 @@ manage_window.bt_themes.tip=Click here to choose a theme
manage_window.bt_themes.option.invalid=Invalid
manage_window.checkbox.only_apps=Apps
manage_window.checkbox.only_installed=Installate
+manage_window.checkbox.only_verified=Verificati
manage_window.checkbox.show_details=Mostra dettagli
manage_window.columns.installed=Installed
manage_window.columns.latest_version=Ultima Versione
manage_window.columns.update=Upgrade ?
manage_window.info.no_info=There is no information available for {}
+manage_window.info.open_url=Aprire
manage_window.label.apps_displayed.tip=Applications displayed / available
manage_window.label.updates=Aggiornamenti
manage_window.name_filter.button_tooltip=Click here to filter apps by name
diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt
index 17e898f70..7536607b3 100644
--- a/bauh/view/resources/locale/pt
+++ b/bauh/view/resources/locale/pt
@@ -66,6 +66,7 @@ action.ignore_updates.success=As atualizações de {} serão ignoradas a partir
action.ignore_updates_reverse.fail=Não foi possível reverter as atualizações ignoradas de {}
action.ignore_updates_reverse.success=As atualizações de {} serão exibidas novamente a partir de agora
action.info.tooltip=Clique aqui para ver informações sobre este aplicativo
+action.install.unverified.warning=Este app não foi verificado pelos mantenedores desse sistema ou uma fonte confiável. Confira suas informações antes de prosseguir.
action.not_allowed=Ação não permitida
action.request_reboot.title=Reinicialização de sistema
action.reset=Restaurar
@@ -111,49 +112,100 @@ bt.not_now=Agora não
cancel=cancelar
categories=categorias
category.2dgraphics=gŕaficos 2d
+category.3dgraphics=gŕaficos 3d
+category.accessibility=acessibilidade
category.acessories=acessórios
+category.actiongame=jogo de ação
category.admin=administrador
category.administration=administração
+category.adult=adulto
+category.adventuregame=jogo de aventura
category.amusement=diversão
category.application=aplicação
category.arcadegame=jogo arcade
category.archiving=arquivamento
category.art=arte
+category.artificialintelligence=inteligência artificial
+category.astronomy=astronomia
category.audio=áudio
-category.audiovideo=áudio / vídeo
+category.audiovideo=áudio/vídeo
+category.audiovideoediting=edição de áudio/video
+category.biology=biologia
+category.blocksgame=jogo de blocos
+category.boardgame=jogo de tabuleiro
category.books=livros
category.browser=navegador
+category.building=compilação
+category.calendar=calendário
category.calculator=calculadora
+category.cardgame=jogo de cartas
+category.chart=gráfico
+category.chemistry=química
+category.clock=relógio
category.cloud=nuvem
category.communication=comunicação
category.compression=compressão
+category.computerscience=ciências da computação
+category.consoleonly=somente console
+category.construction=construção
+category.contactmanagement=contatos
category.core=núcleo
category.database=banco de dados
category.databases=bancos de dados
+category.datavisualization=visualização de dados
+category.debugger=depurador
category.design=desenho
category.desktopsettings=configurações
category.devel=desenvolvimento
category.development=desenvolvimento
+category.dialup=discagem
category.dictionary=dicionário
+category.discburning=gravador de disco
category.doc=documentação
+category.documentation=documentação
+category.economy=economia
category.education=educação
+category.electricity=eletricidade
+category.electronics=electrônica
category.emulator=emulador
+category.engineering=engenharia
category.entertainment=entretenimento
+category.feed=feed
+category.filemanager=gerenciador de arquivos
+category.filesystem=sistema de arquivos
+category.filetools=ferramenta de arquivo
category.filetransfer=transferência de arquivos
category.finance=finanças
category.fitness=fitness
+category.flowchart=diagrama de fluxo
category.fonts=fontes
category.game=jogo
category.games=jogos
+category.geography=geografia
+category.geology=geologia
+category.geoscience=geociências
category.graphics=gráficos
+category.hamradio=rádio amador
+category.hardwaresettings=configuração de hardware
category.health=saúde
+category.history=história
+category.humanities=humanidades
+category.imageprocessing=processamento de imagem
category.instantmessaging=mensagem
+category.ircclient=IRC
+category.kidsgame=jogo infantil
+category.languages=idiomas
category.libs=biblioteca
category.library=biblioteca
+category.literature=literatura
category.localization=localização
+category.logicgame=jogo de lógica
+category.maps=mapas
category.math=matemática
category.media=mídia
+category.medicalsoftware=médico
category.messaging=mensagem
+category.mixer=mixer
category.monitor=monitoria
category.movie=filme
category.movies=filmes
@@ -163,37 +215,68 @@ category.network=rede
category.networks=redes
category.news=notícias
category.none=Nenhuma
+category.numericalanalysis=análise numérica
category.office=escritório
category.packagemanager=gerenciador de pacotes
+category.parallelcomputing=computação paralela
category.personalisation=personalização
category.photo=fotografia
category.photography=fotografia
-category.player=jogador
+category.physics=física
+category.player=tocador
category.presentation=apresentação
+category.printing=impressão
category.productivity=produtividade
+category.profiling=performance
+category.projectmanagement=projetos
+category.publishing=publicação
+category.rastergraphics=gráficos rasterizados
+category.recorder=gravador
+category.remoteaccess=acesso remoto
category.reference=referência
+category.revisioncontrol=controle de versão
+category.robotics=robótica
+category.roleplaying=jogo de RPG
+category.scanning=digitalização
+category.scanning=scanning
category.science=ciências
+category.screensaver=protetor de tela
category.security=segurança
+category.sequencer=sequenciador
category.server=servidor
category.settings=configuração
+category.shooter=jogo de tiro
+category.simulation=jogo de simulação
category.social=social
category.sound=som
+category.spirituality=espiritualidade
+category.sports=esportes
category.sportsgame=jogo de esportes
+category.spreadsheet=planilha
+category.strategygame=jogo de estratégia
category.system=sistema
+category.telephony=telefonia
+category.telephonytools=ferammentas de telefonia
category.terminalemulator=terminal
category.text editor=editor de texto
category.texteditor=editor de texto
+category.texttools=ferramentas de texto
+category.translation=tradução
+category.trayicon=ícone de bandeja
+category.tuner=sintonizador
category.updates_ignored=Atualizações ignoradas
category.utilities=utilidades
category.utility=utilidade
category.vectorgraphics=gráficos vetoriais
category.video=vídeo
+category.videoconference=videoconferência
category.videoeditor=edito de vídeo
category.viewer=visualizador
category.weather=tempo
category.web=web
category.webbrowser=navegador
category.webdevelopment=desenvolvimento web
+category.wordprocessor=editor de texto
category=categoria
change=alterar
clean=limpar
@@ -344,11 +427,13 @@ manage_window.bt_themes.tip=Clique aqui para escolher um tema
manage_window.bt_themes.option.invalid=Inválido
manage_window.checkbox.only_apps=Aplicativos
manage_window.checkbox.only_installed=Instalados
+manage_window.checkbox.only_verified=Verificados
manage_window.checkbox.show_details=Mostrar detalhes
manage_window.columns.installed=Instalado
manage_window.columns.latest_version=Última Versão
manage_window.columns.update=Atualizar ?
manage_window.info.no_info=Não há informação disponível para {}
+manage_window.info.open_url=Abrir
manage_window.label.apps_displayed.tip=Aplicações exibidas / disponíveis
manage_window.label.updates=Atualizações
manage_window.name_filter.button_tooltip=Clique aqui para filtrar aplicativos por nome
diff --git a/bauh/view/resources/locale/ru b/bauh/view/resources/locale/ru
index 3ab9e2d98..af9c4da69 100644
--- a/bauh/view/resources/locale/ru
+++ b/bauh/view/resources/locale/ru
@@ -66,6 +66,7 @@ action.ignore_updates.success=Обновления от {} теперь буду
action.ignore_updates_reverse.fail=Не удалось вернуть игнорируемые обновления из {}
action.ignore_updates_reverse.success=Теперь обновления из {} будут отображаться снова
action.info.tooltip=Нажмите здесь, чтобы посмотреть информацию об этом приложении
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Действие не допускается
action.request_reboot.title=Перезапуск системы
action.reset=Восстановить
@@ -111,50 +112,99 @@ bt.not_now=Не сейчас
cancel=Отмена
categories=категории
category.2dgraphics=2D-графика
+category.3dgraphics=3D-графика
+category.accessibility=accessibility
category.acessories=Аксессуары
+category.actiongame=action game
category.admin=Админ
category.administration=Администрирование
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=Развлечения
category.application=Приложение
category.arcadegame=Аркада
category.archiving=Архивирование
category.art=Искусство
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=Аудио
category.audiovideo=Аудио/Видео
+category.audiovideoediting=Аудио/Видео editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=Книги
category.browser=Браузер
+category.building=building
+category.calendar=calendar
category.calculator=Калькулятор
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=Облако
category.communication=Общение
category.compression=Сжатие
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
category.core=Ядро
category.database=База данных
category.databases=Базы данных
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=Дизайн
category.desktopsettings=Настройки рабочего стола
category.devel=Разработка
category.development=Разработка
+category.dialup=dial-up
category.dictionary=Словарь
+category.discburning=disc burning
category.doc=Документация
+category.documentation=Документация
+category.economy=economy
category.education=Образование
+category.electricity=electricity
+category.electronics=electronics
category.emulator=Эмуляторы
+category.engineering=engineering
category.entertainment=Развлечения
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=Передача файлов
category.finance=Финансы
category.fitness=Фитнес
+category.flowchart=flow chart
category.fonts=Шрифты
category.game=Игра
category.games=Игры
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
category.graphics=Графика
+category.hamradio=ham radio
category.hardwaresettings=Оборудование
category.health=Здоровье
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=Мессенджер
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=Библиотека
category.library=Библиотека
+category.literature=literature
category.localization=Локализация
+category.logicgame=logic game
+category.maps=maps
category.math=Математика
category.media=Медиа
+category.medicalsoftware=medical
category.messaging=Сообщения
+category.mixer=mixer
category.monitor=Мониторинг
category.movie=Фильм
category.movies=Фильмы
@@ -164,38 +214,67 @@ category.network=Сеть
category.networks=Сети
category.news=Новости
category.none=Нет
+category.numericalanalysis=numerical analysis
category.office=Офис
category.packagemanager=Пакетный менеджер
+category.parallelcomputing=parallel computing
category.personalisation=Персонализация
category.photo=Фото
category.photography=Фото
-category.player=Проигрыватель
+category.physics=physics
category.player=Проигрыватель
category.presentation=Презентация
+category.printing=printing
category.productivity=Производительность
+category.profiling=profiling
+category.projectmanagement=projects
+category.publishing=publishing
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=Справка
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=Наука
+category.screensaver=screensaver
category.security=Безопасность
+category.sequencer=sequencer
category.server=Сервер
category.settings=Настройки
+category.shooter=shooter game
+category.simulation=simulation game
category.social=Социальные
category.sound=Звук
+category.spirituality=spirituality
+category.sports=sports
category.sportsgame=Спортивная игра
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=Система
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=Эмулятор терминала
category.text editor=Текстовый редактор
category.texteditor=Текстовый редактор
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Обновления игнорируются
category.utilities=Утилиты
category.utility=Утилита
category.vectorgraphics=Векторная графика
category.video=Видео
+category.videoconference=video conference
category.videoeditor=Видеоредактор
category.viewer=Просмотр
category.weather=Погода
category.web=Сеть
category.webbrowser=Браузер
category.webdevelopment=Web-разработка
+category.wordprocessor=word processor
category=Категория
change=Изменить
clean=Очистка
@@ -346,11 +425,13 @@ manage_window.bt_themes.tip=Нажмите здесь, чтобы выбрать
manage_window.bt_themes.option.invalid=Недопустимый
manage_window.checkbox.only_apps=Приложения
manage_window.checkbox.only_installed=Установленные
+manage_window.checkbox.only_verified=Проверено
manage_window.checkbox.show_details=Показать детали
manage_window.columns.installed=Установленно
manage_window.columns.latest_version=Последняя версия
manage_window.columns.update=Обновить?
manage_window.info.no_info=Информация не доступна для {}
+manage_window.info.open_url=Открыть
manage_window.label.apps_displayed.tip=Отображаемые / доступные приложения
manage_window.label.updates=Обновления
manage_window.name_filter.button_tooltip=Нажмите здесь, чтобы отфильтровать приложения по названию
diff --git a/bauh/view/resources/locale/tr b/bauh/view/resources/locale/tr
index 750b26e5a..7b14c93d5 100644
--- a/bauh/view/resources/locale/tr
+++ b/bauh/view/resources/locale/tr
@@ -66,6 +66,7 @@ action.ignore_updates.success=Updates from {} will be ignored from now on
action.ignore_updates_reverse.fail=It was not possible to revert ignored updates from {}
action.ignore_updates_reverse.success=Updates from {} will be displayed again from now on
action.info.tooltip=Uygulama bilgilerini görmek için buraya tıkla
+action.install.unverified.warning=This app has not been verified by this system maintainers or a trusted source. Check its information before proceeding.
action.not_allowed=Eyleme izin verilmiyor
action.request_reboot.title=Sistemi yeniden başlat
action.reset=Restore
@@ -111,49 +112,100 @@ bt.not_now=Şimdi değil
cancel=vazgeç
categories=Kategoriler
category.2dgraphics=2d grafikler
+category.3dgraphics=3d grafikler
+category.accessibility=accessibility
category.acessories=donatılar
+category.actiongame=action game
category.admin=admin
category.administration=yönetim
+category.adult=adult
+category.adventuregame=adventure game
category.amusement=eğlence
category.application=application
category.arcadegame=arcade oyunlar
category.archiving=arşivleme
category.art=sanat
+category.artificialintelligence=artificial intelligence
+category.astronomy=astronomy
category.audio=ses
-category.audiovideo=ses / video
+category.audiovideo=ses/video
+category.audiovideoediting=ses/video editing
+category.biology=biology
+category.blocksgame=blocks game
+category.boardgame=board game
category.books=kitaplar
category.browser=tarayıcı
+category.building=building
+category.calendar=calendar
category.calculator=hesap makinesi
+category.cardgame=card game
+category.chart=chart
+category.chemistry=chemistry
+category.clock=clock
category.cloud=bulut
category.communication=iletişim
category.compression=sıkıştırma
+category.computerscience=computer science
+category.consoleonly=console only
+category.construction=construction
+category.contactmanagement=contacts
category.core=core
category.database=veritabanı
category.databases=veritabanları
+category.datavisualization=data visualization
+category.debugger=debugger
category.design=tasarım
category.desktopsettings=masaüstü ayarları
category.devel=gelişim
category.development=gelişim
+category.dialup=dial-up
category.dictionary=dictionary
+category.discburning=disc burning
category.doc=documentation
+category.documentation=documentation
+category.economy=economy
category.education=eğitim
+category.electricity=electricity
+category.electronics=electronics
category.emulator=öykünücü
+category.engineering=engineering
category.entertainment=eğlence
+category.feed=feed
+category.filemanager=file manager
+category.filesystem=file system
+category.filetools=file tools
category.filetransfer=dosya transferi
category.finance=finans
category.fitness=sağlık
+category.flowchart=flow chart
category.fonts=fontlar
category.game=oyun
category.games=oyunlar
+category.geography=geography
+category.geology=geology
+category.geoscience=geoscience
category.graphics=grafikler
+category.hamradio=ham radio
+category.hardwaresettings=hardware settings
category.health=sağlık
+category.history=history
+category.humanities=humanities
+category.imageprocessing=image processing
category.instantmessaging=anlık mesajlaşma
+category.ircclient=IRC
+category.kidsgame=kids game
+category.languages=languages
category.libs=kütüphane
category.library=kütüphane
+category.literature=literature
category.localization=localization
+category.logicgame=logic game
+category.maps=maps
category.math=math
category.media=medya
+category.medicalsoftware=medical
category.messaging=mesajlaşma
+category.mixer=mixer
category.monitor=monitoring
category.movie=film
category.movies=filmler
@@ -163,37 +215,67 @@ category.network=ağ
category.networks=ağlar
category.news=haberler
category.none=hiçbiri
+category.numericalanalysis=numerical analysis
category.office=ofis
category.packagemanager=paket yönetici
+category.parallelcomputing=parallel computing
category.personalisation=kişiselleştirme
category.photo=fotoğraf
category.photography=fotoğraf
+category.physics=physics
category.player=oyuncu
category.presentation=sunum
+category.printing=printing
category.productivity=verimlilik
+category.projectmanagement=projects
+category.publishing=publishing
+category.profiling=profiling
+category.rastergraphics=raster graphics
+category.recorder=recorder
+category.remoteaccess=remote access
category.reference=referans
+category.revisioncontrol=version control
+category.robotics=robotics
+category.roleplaying=RPG game
+category.scanning=scanning
category.science=bilim
+category.screensaver=screensaver
category.security=güvenlik
+category.sequencer=sequencer
category.server=sunucu
category.settings=ayarlar
+category.shooter=shooter game
+category.simulation=simulation game
category.social=sosyal
category.sound=sound
+category.spirituality=spirituality
+category.sports=sports
category.sportsgame=spor oyunları
+category.spreadsheet=spreadsheet
+category.strategygame=strategy game
category.system=sistem
+category.telephony=telephony
+category.telephonytools=telephony tools
category.terminalemulator=uçbirim
category.text editor=metin düzenleyici
category.texteditor=metin düzenleyici
+category.texttools=text tools
+category.translation=translation
+category.trayicon=tray icon
+category.tuner=tuner
category.updates_ignored=Görmezden gelinen güncellemeler
category.utilities=bileşenler
category.utility=bileşenler
category.vectorgraphics=vektör grafikler
category.video=video
+category.videoconference=video conference
category.videoeditor=video düzenleyici
category.viewer=gösterici
category.weather=hava durumu
category.web=web
category.webbrowser=tarayıcı
category.webdevelopment=web geliştirme
+category.wordprocessor=word processor
category=kategori
change=değiştir
clean=temizle
@@ -343,11 +425,13 @@ manage_window.bt_themes.tip=Tema seçmek için burayı tıkla
manage_window.bt_themes.option.invalid=Invalid
manage_window.checkbox.only_apps=Uygulamalar
manage_window.checkbox.only_installed=Yüklü
+manage_window.checkbox.only_verified=Doğrulandı
manage_window.checkbox.show_details=Detayları göster
manage_window.columns.installed=Yüklü
manage_window.columns.latest_version=Son sürüm
manage_window.columns.update=Yükselt ?
manage_window.info.no_info={} için bilgi yok
+manage_window.info.open_url=Açmak
manage_window.label.apps_displayed.tip=Görüntülenen uygulamalar / mevcut olanlar
manage_window.label.updates=Güncellemeler
manage_window.name_filter.button_tooltip=Click here to filter apps by name
diff --git a/bauh/view/resources/locale/tray/__init__.py b/bauh/view/resources/locale/tray/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/darcula/__init__.py b/bauh/view/resources/style/darcula/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/darcula/darcula.qss b/bauh/view/resources/style/darcula/darcula.qss
index cb948165d..ad703a5cb 100644
--- a/bauh/view/resources/style/darcula/darcula.qss
+++ b/bauh/view/resources/style/darcula/darcula.qss
@@ -424,6 +424,10 @@ QLabel#confirm_dialog_icon {
qproperty-pixmap: url("@style_dir/img/question.svg");
}
+QLabel#confirm_dialog_icon[type = "warning"] {
+ qproperty-pixmap: url("@style_dir/img/warning.svg");
+}
+
FormQt IconButton#clean_field {
qproperty-icon: url("@style_dir/img/clean.svg");
}
diff --git a/bauh/view/resources/style/darcula/img/__init__.py b/bauh/view/resources/style/darcula/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/default/__init__.py b/bauh/view/resources/style/default/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/knight/__init__.py b/bauh/view/resources/style/knight/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/knight/img/__init__.py b/bauh/view/resources/style/knight/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/knight/img/warning.svg b/bauh/view/resources/style/knight/img/warning.svg
new file mode 100644
index 000000000..31c505ed6
--- /dev/null
+++ b/bauh/view/resources/style/knight/img/warning.svg
@@ -0,0 +1,122 @@
+
+
diff --git a/bauh/view/resources/style/knight/knight.qss b/bauh/view/resources/style/knight/knight.qss
index cf42fe6b0..2ef532315 100644
--- a/bauh/view/resources/style/knight/knight.qss
+++ b/bauh/view/resources/style/knight/knight.qss
@@ -417,6 +417,10 @@ QLabel#confirm_dialog_icon {
qproperty-pixmap: url("@style_dir/img/help.svg");
}
+QLabel#confirm_dialog_icon[type = "warning"] {
+ qproperty-pixmap: url("@style_dir/img/warning.svg");
+}
+
FormQt IconButton#clean_field {
qproperty-icon: url("@style_dir/img/clean.svg");
}
diff --git a/bauh/view/resources/style/light/__init__.py b/bauh/view/resources/style/light/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/light/img/__init__.py b/bauh/view/resources/style/light/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/light/light.qss b/bauh/view/resources/style/light/light.qss
index 18e3f2240..dc78d424d 100644
--- a/bauh/view/resources/style/light/light.qss
+++ b/bauh/view/resources/style/light/light.qss
@@ -83,6 +83,10 @@ QLabel#confirm_dialog_icon {
qproperty-pixmap: url("@style_dir/img/question.svg");
}
+QLabel#confirm_dialog_icon[type = "warning"] {
+ qproperty-pixmap: url("@style_dir/img/warning.svg");
+}
+
QPlainTextEdit[console = 'true'] {
background: @console.background.color;
color: @console.font.color;
diff --git a/bauh/view/resources/style/sublime/__init__.py b/bauh/view/resources/style/sublime/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/sublime/img/__init__.py b/bauh/view/resources/style/sublime/img/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/bauh/view/resources/style/sublime/sublime.qss b/bauh/view/resources/style/sublime/sublime.qss
index df3e63535..41dc257b5 100644
--- a/bauh/view/resources/style/sublime/sublime.qss
+++ b/bauh/view/resources/style/sublime/sublime.qss
@@ -131,6 +131,10 @@ QLabel#confirm_dialog_icon {
qproperty-pixmap: url("@style_dir/img/question.svg");
}
+QLabel#confirm_dialog_icon[type = "warning"] {
+ qproperty-pixmap: url("@style_dir/img/warning.svg");
+}
+
QPlainTextEdit {
background: @texteditor.background.color;
color: @texteditor.font.color;
diff --git a/bauh/view/util/cache.py b/bauh/view/util/cache.py
index 09b8cfdd1..d5e26251e 100644
--- a/bauh/view/util/cache.py
+++ b/bauh/view/util/cache.py
@@ -1,6 +1,5 @@
import datetime
-import time
-from threading import Lock, Thread
+from threading import Lock
from typing import Optional
from bauh.api.abstract.cache import MemoryCache, MemoryCacheFactory
@@ -76,41 +75,14 @@ def clean_expired(self):
self.get(key)
-class CacheCleaner(Thread):
-
- def __init__(self, check_interval: int = 15):
- super(CacheCleaner, self).__init__(daemon=True)
- self.caches = []
- self.check_interval = check_interval
-
- def register(self, cache: MemoryCache):
- if cache.is_enabled():
- self.caches.append(cache)
-
- def run(self):
- if self.caches:
- while True:
- for cache in self.caches:
- cache.clean_expired()
-
- time.sleep(self.check_interval)
-
-
class DefaultMemoryCacheFactory(MemoryCacheFactory):
- def __init__(self, expiration_time: int, cleaner: CacheCleaner = None):
+ def __init__(self, expiration_time: int):
"""
:param expiration_time: default expiration time for all instantiated caches
- :param cleaner
"""
super(DefaultMemoryCacheFactory, self).__init__()
self.expiration_time = expiration_time
- self.cleaner = cleaner
def new(self, expiration: Optional[int] = None) -> MemoryCache:
- instance = DefaultMemoryCache(expiration if expiration is not None else self.expiration_time)
-
- if self.cleaner:
- self.cleaner.register(instance)
-
- return instance
+ return DefaultMemoryCache(expiration if expiration is not None else self.expiration_time)
diff --git a/linux_dist/appimage/AppImageBuilder.yml b/linux_dist/appimage/AppImageBuilder.yml
index 58fed2c02..f0b5ad5ce 100755
--- a/linux_dist/appimage/AppImageBuilder.yml
+++ b/linux_dist/appimage/AppImageBuilder.yml
@@ -1,13 +1,14 @@
version: 1
script:
- rm -rf AppDir appimage-builder-cache *.AppImage *.zsync |true
- - mkdir -p AppDir/usr
- mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps
- mkdir -p AppDir/usr/share/applications
- wget https://github.com/vinifmor/bauh/archive/${BAUH_VERSION}.tar.gz || exit 1
- - BAUH_SETUP_NO_REQS=1 pip3 install ${BAUH_VERSION}.tar.gz --prefix=/usr --root=AppDir || exit 1
- tar -xf ${BAUH_VERSION}.tar.gz || exit 1
- cd bauh-${BAUH_VERSION} || exit 1
+ - rm pyproject.toml || exit 1 # not using the new project file definition for now
+ - BAUH_SETUP_NO_REQS=1 pip3 install . --prefix=/usr --root=../AppDir || exit 1
+ - test -e ../AppDir/usr/bin/bauh || exit 1
- cp bauh/view/resources/img/logo.svg ../AppDir/usr/share/icons/hicolor/scalable/apps/bauh.svg || exit 1
- cp bauh/desktop/bauh.desktop ../AppDir/usr/share/applications || exit 1
@@ -27,8 +28,8 @@ AppDir:
apt:
arch: amd64
sources:
- - sourceline: 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse'
- key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
+ - sourceline: 'deb http://deb.debian.org/debian/ bullseye main contrib non-free'
+ key_url: 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xa4285295fc7b1a81600062a9605c66f00d6c9793'
include:
- python3
@@ -39,9 +40,7 @@ AppDir:
- python3-lxml
- python3-bs4
- sqlite3
- - wget
- # - libfreetype6
- # - libfontconfig1
+ - xdg-utils
exclude:
- dpkg
- apt
@@ -51,20 +50,27 @@ AppDir:
- python3-distutils
after_bundle:
- - pip3 install pyqt5 --prefix=/usr --root=AppDir || exit 1
- - cd AppDir/usr/lib/python3.8/site-packages/PyQt5/Qt5/plugins || exit 1
- - rm -rf audio gamepad gamepads geoservices printsupport sceneparsers sensorgestures sensors sqldrivers texttospeech webview || exit 1
+ - pip3 install pyqt5==5.15.10 --prefix=/usr --root=AppDir || exit 1
+ - rm -rf AppDir/usr/share/doc || exit 1
+ - cd AppDir/usr/lib/python3.9/site-packages/PyQt5/Qt5/plugins || exit 1
+ - rm -rf audio gamepad gamepads geoservices printsupport sceneparsers sensorgestures sensors sqldrivers texttospeech webview mediaservice playlistformats || exit 1
- cd ../lib/ || exit 1
- rm libQt5Bluetooth.so.5 libQt5Designer.so.5 libQt5Multimedia.so.5 libQt5MultimediaGstTools.so.5 libQt5MultimediaWidgets.so.5 || exit 1
- rm libQt5Quick3D.so.5 libQt5Quick3DAssetImport.so.5 libQt5Quick3DRender.so.5 libQt5Quick3DRuntimeRender.so.5 libQt5QuickTest.so.5 || exit 1
- rm libQt5Quick3DUtils.so.5 libQt5PrintSupport.so.5 libQt5SerialPort.so.5 libQt5Sql.so.5 libQt5Sensors.so.5 libQt5Test.so.5 libQt5WebView.so.5 || exit 1
- rm libQt5Quick.so.5 libQt5Location.so.5 libQt5QuickTemplates2.so.5 || exit 1
-
+ - cd ../qml || exit 1
+ - rm -rf QtBluetooth QtMultimedia QtQuick QtQuick.2 QtQuick3D QtRemoteObjects QtSensors QtTest
+ - cd ../../ || exit 1
+ - rm QtBluetooth.abi3.so QtMultimedia.abi3.so QtMultimediaWidgets.abi3.so QtPrintSupport.abi3.so QtQuick.abi3.so QtQuick3D.abi3.so QtQuickWidgets.abi3.so QtRemoteObjects.abi3.so QtSensors.abi3.so QtSerialPort.abi3.so QtSql.abi3.so QtTest.abi3.so QtTextToSpeech.abi3.so QtWebSockets.abi3.so|| exit 1
+ - rm QtBluetooth.pyi QtMultimedia.pyi QtMultimediaWidgets.pyi QtPrintSupport.pyi QtQuick3D.pyi QtQuickWidgets.pyi QtSensors.pyi QtSerialPort.pyi QtSql.pyi
+ - cd bindings || exit 1
+ - rm -rf QtBluetooth QtMultimedia QtMultimediaWidgets QtPrintSupport QtSensors QtSerialPort QtTextToSpeech QtQuick QtQuick3D QtQuickWidgets QtSql QtTest || exit 1
runtime:
version: "v1.2.5"
env:
PYTHONHOME: '${APPDIR}/usr'
- PYTHONPATH: '${APPDIR}/usr/lib/python3.8/site-packages'
+ PYTHONPATH: '${APPDIR}/usr/lib/python3.9/site-packages'
test:
fedora:
diff --git a/linux_dist/appimage/Dockerfile b/linux_dist/appimage/Dockerfile
index a8d9f40ea..8c77f71be 100644
--- a/linux_dist/appimage/Dockerfile
+++ b/linux_dist/appimage/Dockerfile
@@ -1,12 +1,10 @@
-FROM ubuntu:20.04
+FROM debian:bullseye-slim
-ARG bauh_commit
-ENV BAUH_VERSION=$bauh_commit
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && \
apt-get upgrade -y && \
- apt-get install python3-pip python3-setuptools python3-wheel wget fuse binutils coreutils desktop-file-utils fakeroot patchelf squashfs-tools strace zsync libgdk-pixbuf2.0-dev gtk-update-icon-cache -y && \
+ apt-get install --no-install-recommends python3-pip python3-setuptools python3-wheel wget fuse binutils coreutils desktop-file-utils fakeroot patchelf squashfs-tools strace zsync libgdk-pixbuf2.0-dev gtk-update-icon-cache file -y && \
mkdir /build && cd /build && \
wget https://github.com/AppImageCrafters/appimage-builder/releases/download/v0.9.2/appimage-builder-0.9.2-35e3eab-x86_64.AppImage -O appimage-builder && \
wget https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -O appimage-tool && \
@@ -17,6 +15,4 @@ RUN apt-get update -y && \
WORKDIR /build
-COPY AppImageBuilder.yml /build
-
CMD [ "appimage-builder", "--skip-tests"]
diff --git a/linux_dist/appimage/build.sh b/linux_dist/appimage/build.sh
index b871db60e..ffe65632a 100755
--- a/linux_dist/appimage/build.sh
+++ b/linux_dist/appimage/build.sh
@@ -1,3 +1,6 @@
#!/bin/bash
-docker build -t bauh-appimage --build-arg bauh_commit=$BAUH_COMMIT . && \
-docker run --cap-add=SYS_ADMIN --device /dev/fuse --mount type=bind,source="$(pwd)",target=/build bauh-appimage
+set -Ceufox pipefail
+
+docker build -t bauh-appimage .
+docker run -e BAUH_VERSION=$BAUH_VERSION -v ./AppImageBuilder.yml:/build/AppImageBuilder.yml --rm --cap-add=SYS_ADMIN --device /dev/fuse --mount type=bind,source="$(pwd)",target=/build bauh-appimage
+# volume required to run tests: -v /var/run/docker.sock:/var/run/docker.sock
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..11897aa94
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,53 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "bauh"
+description = "Graphical interface to manage Linux applications (AppImage, Arch / AUR, Flatpak, Snap and Web)"
+license = {file = "LICENSE"}
+requires-python = ">=3.6"
+dynamic = ["version"]
+readme = "README.md"
+authors = [{name = "Vinicius Moreira", email = "vinicius_fmoreira@hotmail.com"}]
+classifiers = [
+ 'Topic :: Utilities',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11'
+]
+
+dependencies = [
+ "pyqt5 >= 5.12",
+ "requests >= 2.18",
+ "colorama >= 0.3.8",
+ "pyyaml >= 3.13",
+ "python-dateutil >= 2.7"
+]
+
+[project.optional-dependencies]
+web = [
+ "lxml >= 4.2.0",
+ "beautifulsoup4 >= 4.7.0"
+]
+
+[project.scripts]
+bauh = "bauh.app:main"
+bauh-tray = "bauh.app:tray"
+bauh-cli = "bauh.cli.app:main"
+
+[project.urls]
+Repository = "https://github.com/vinifmor/bauh"
+
+[tool.setuptools]
+license-files = ["LICENSE"]
+
+[tool.setuptools.dynamic]
+version = {attr = "bauh.__version__"}
+
+[tool.setuptools.packages.find]
+exclude = ["tests.*", "tests"]
diff --git a/setup.cfg b/setup.cfg
index b88034e41..08aedd7e6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,2 @@
[metadata]
-description-file = README.md
+description_file = README.md
diff --git a/setup.py b/setup.py
index 2f3cbae46..744e2e41f 100644
--- a/setup.py
+++ b/setup.py
@@ -7,8 +7,8 @@
"Graphical interface to manage Linux applications (AppImage, Arch / AUR, Flatpak, Snap and Web)"
)
-AUTHOR = "bauh developers"
-AUTHOR_EMAIL = "bauh4linux@gmail.com"
+AUTHOR = "Vinicius Moreira"
+AUTHOR_EMAIL = "vinicius_fmoreira@hotmail.com"
NAME = 'bauh'
URL = "https://github.com/vinifmor/" + NAME