diff --git a/CHANGELOG.md b/CHANGELOG.md index 5845d960..fa45bd9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ 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.6.1] 2019-09-26 +### Improvements: +- Better warning presentation when there are several messages +- Better AUR update check handling +- "Show" button available for all information fields + +### Fixes: +- Error when retrieving suggestions +- snapd health check when snapd.service is available +- AUR: not showing all optional dependencies ( Info ) + ## [0.6.0] 2019-09-25 ### Features @@ -33,7 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Environment variable / parameter **BAUH_UPDATE_NOTIFICATION** renamed to **BAUH_SYSTEM_NOTIFICATIONS** and now works for any system notification - Environment variable / parameter **BAUH_DOWNLOAD_MULTITHREAD**: if source files should be downloaded using multi-threads (not supported by all **gems**). - Environment variables / parameter **BAUH_MAX_DISPLAYED**: controls the maximum number of displayed apps ( default to 50 ) -- Environment variables / parameter **BAUH_LOGS**: controls the maximum number of displayed apps ( default to 50 ) +- Environment variables / parameter **BAUH_LOGS**: activates console logging. - small UI improvements ### UI Changes diff --git a/bauh/__init__.py b/bauh/__init__.py index 6dfd2e60..68a0a495 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.6.0' +__version__ = '0.6.1' __app_name__ = 'bauh' import os diff --git a/bauh/gems/arch/mapper.py b/bauh/gems/arch/mapper.py index cdaa1828..72db9469 100644 --- a/bauh/gems/arch/mapper.py +++ b/bauh/gems/arch/mapper.py @@ -1,3 +1,4 @@ +import re from datetime import datetime from bauh.api.abstract.model import PackageStatus @@ -5,6 +6,16 @@ from bauh.api.http import HttpClient from bauh.gems.arch.model import ArchPackage URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' +RE_LETTERS = re.compile(r'\.([a-zA-Z]+)-\d+$') + +RE_SFX = ('r', 're', 'release') +GA_SFX = ('ga', 'ge') +RC_SFX = ('rc',) +BETA_SFX = ('b', 'beta') +AL_SFX = ('alpha', 'alfa') +DEV_SFX = ('dev', 'devel', 'development') + +V_SUFFIX_MAP = {s: {'c': sfxs[0], 'p': idx} for idx, sfxs in enumerate([RE_SFX, GA_SFX, RC_SFX, BETA_SFX, AL_SFX, DEV_SFX]) for s in sfxs} class ArchDataMapper: @@ -36,7 +47,36 @@ class ArchDataMapper: pkg.url_download = URL_PKG_DOWNLOAD.format(package['URLPath']) if package.get('URLPath') else None pkg.first_submitted = datetime.fromtimestamp(package['FirstSubmitted']) if package.get('FirstSubmitted') else None pkg.last_modified = datetime.fromtimestamp(package['LastModified']) if package.get('LastModified') else None - pkg.update = pkg.version and pkg.latest_version and pkg.latest_version > pkg.version + pkg.update = self.check_update(pkg.version, pkg.latest_version) + + @staticmethod + def check_update(version: str, latest_version: str) -> bool: + if version and latest_version: + current_sfx = RE_LETTERS.findall(version) + latest_sf = RE_LETTERS.findall(latest_version) + + if latest_sf and current_sfx: + current_sfx = current_sfx[0] + latest_sf = latest_sf[0] + + current_sfx_data = V_SUFFIX_MAP.get(current_sfx.lower()) + latest_sfx_data = V_SUFFIX_MAP.get(latest_sf.lower()) + + if current_sfx_data and latest_sfx_data: + nversion = version.split(current_sfx)[0] + nlatest = latest_version.split(latest_sf)[0] + + if nversion == nlatest: + if current_sfx_data['c'] != latest_sfx_data['c']: + return latest_sfx_data['p'] < current_sfx_data['p'] + else: + return ''.join(latest_version.split(latest_sf)) > ''.join(version.split(current_sfx)) + + return nlatest > nversion + + return latest_version > version + + return False def fill_package_build(self, pkg: ArchPackage): res = self.http_client.get(pkg.get_pkg_build_url()) diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index d2c85d27..a08c51b4 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -6,7 +6,7 @@ from bauh.api.abstract.handler import ProcessWatcher from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess, ProcessHandler RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-\.]+\s+\[\w+\]') - +RE_OPTDEPS = re.compile(r'[\w\._\-]+\s*:') def is_enabled() -> bool: try: @@ -43,7 +43,7 @@ def get_info(pkg_name) -> str: def get_info_list(pkg_name: str) -> List[tuple]: info = get_info(pkg_name) if info: - return re.findall(r'(\w+\s?\w+)\s+:\s+(.+(\n\s+.+)*)', info) + return re.findall(r'(\w+\s?\w+)\s*:\s*(.+(\n\s+.+)*)', info) def get_info_dict(pkg_name: str) -> dict: @@ -53,14 +53,13 @@ def get_info_dict(pkg_name: str) -> dict: info_dict = {} for info_data in info_list: attr = info_data[0].lower().strip() - info_dict[attr] = info_data[1] if '\n' not in info_data[1] else ' '.join( - [l.strip() for l in info_data[1].split('\n')]) + info_dict[attr] = info_data[1] if info_dict[attr] == 'None': info_dict[attr] = None if attr == 'optional deps' and info_dict[attr]: - info_dict[attr] = RE_DEPS.findall(info_dict[attr]) + info_dict[attr] = info_dict[attr].split('\n') elif attr == 'depends on' and info_dict[attr]: info_dict[attr] = [d.strip() for d in info_dict[attr].split(' ') if d] diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 70a11c8a..1bcbd3a4 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -8,7 +8,7 @@ from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ SuggestionPriority -from bauh.commons import internet +from bauh.commons.html import bold from bauh.commons.system import SystemProcess, ProcessHandler from bauh.gems.snap import snap, suggestions from bauh.gems.snap.constants import SNAP_API_URL @@ -83,7 +83,6 @@ class SnapManager(SoftwareManager): def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: if snap.is_snapd_running(): - internet_available = internet.is_available(self.http_client, self.logger) installed = [self.map_json(app_json, installed=True, disk_loader=disk_loader, internet=internet_available) for app_json in snap.read_installed()] return SearchResult(installed, None, len(installed)) else: @@ -145,8 +144,10 @@ class SnapManager(SoftwareManager): pass def list_warnings(self) -> List[str]: - if not snap.is_snapd_running(): - return [self.i18n['snap.notification.snapd_unavailable']] + if snap.is_installed() and not snap.is_snapd_running(): + snap_bold = bold('Snap') + return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold), + self.i18n['snap.notification.snap.disable'].format(snap_bold, bold(self.i18n['manage_window.settings.gems']))] def _fill_suggestion(self, pkg_name: str, priority: SuggestionPriority, out: List[PackageSuggestion]): res = self.http_client.get_json(SNAP_API_URL + '/search?q=package_name:{}'.format(pkg_name)) @@ -163,24 +164,25 @@ class SnapManager(SoftwareManager): def list_suggestions(self, limit: int) -> List[PackageSuggestion]: res = [] - sugs = [(i, p) for i, p in suggestions.ALL.items()] - sugs.sort(key=lambda t: t[1].value, reverse=True) + if snap.is_snapd_running(): + sugs = [(i, p) for i, p in suggestions.ALL.items()] + sugs.sort(key=lambda t: t[1].value, reverse=True) - threads = [] - for sug in sugs: + threads = [] + for sug in sugs: - if limit <= 0 or len(res) < limit: - t = Thread(target=self._fill_suggestion, args=(sug[0], sug[1], res)) - t.start() - threads.append(t) - time.sleep(0.001) # to avoid being blocked - else: - break + if limit <= 0 or len(res) < limit: + t = Thread(target=self._fill_suggestion, args=(sug[0], sug[1], res)) + t.start() + threads.append(t) + time.sleep(0.001) # to avoid being blocked + else: + break - for t in threads: - t.join() + for t in threads: + t.join() - res.sort(key=lambda s: s.priority.value, reverse=True) + res.sort(key=lambda s: s.priority.value, reverse=True) return res def is_default_enabled(self) -> bool: diff --git a/bauh/gems/snap/resources/locale/en b/bauh/gems/snap/resources/locale/en index f2994234..8d514136 100644 --- a/bauh/gems/snap/resources/locale/en +++ b/bauh/gems/snap/resources/locale/en @@ -1,3 +1,4 @@ -snap.notification.snapd_unavailable=snapd seems not to be installed or enabled. snap packages will not be available. +snap.notification.snapd_unavailable={} seems not to be installed or enabled. {} packages will not be available. +snap.notification.snap.disable=If you do not want to use Snap applications, uncheck {} in {} snap.action.refresh.status=Refreshing snap.action.refresh.label=Refresh \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/es b/bauh/gems/snap/resources/locale/es index 43f2c667..1d31611a 100644 --- a/bauh/gems/snap/resources/locale/es +++ b/bauh/gems/snap/resources/locale/es @@ -7,6 +7,7 @@ snap.info.revision=revisión snap.info.tracking=tracking snap.info.installed=instalado snap.info.publisher=publicador -snap.notification.snapd_unavailable=snapd no parece estar instalado o habilitado. los paquetes snap estarán indisponibles. +snap.notification.snapd_unavailable={} no parece estar instalado o habilitado. Los paquetes {} estarán indisponibles. +snap.notification.snap.disable=Si no desea usar aplicativos Snap, desmarque {} en {} snap.action.refresh.status=Actualizando snap.action.refresh.label=Actualizar \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/pt b/bauh/gems/snap/resources/locale/pt index 2e675732..efd0aa98 100644 --- a/bauh/gems/snap/resources/locale/pt +++ b/bauh/gems/snap/resources/locale/pt @@ -7,6 +7,7 @@ snap.info.revision=revisão snap.info.tracking=tracking snap.info.installed=instalado snap.info.publisher=publicador -snap.notification.snapd_unavailable=snapd não parece estar instalado ou habilitado. os pacotes snap estarão indisponíveis. +snap.notification.snapd_unavailable={} não parece estar instalado ou habilitado. Os pacotes {} estarão indisponíveis. +snap.notification.snap.disable=Se não deseja usar aplicativos Snap, desmarque {} em {} snap.action.refresh.status=Atualizando snap.action.refresh.label=Atualizar \ No newline at end of file diff --git a/bauh/gems/snap/snap.py b/bauh/gems/snap/snap.py index 02288aa0..04e20de2 100644 --- a/bauh/gems/snap/snap.py +++ b/bauh/gems/snap/snap.py @@ -7,6 +7,8 @@ from bauh.commons.system import new_root_subprocess, run_cmd, new_subprocess from bauh.gems.snap.model import SnapApplication BASE_CMD = 'snap' +RE_SNAPD_STATUS = re.compile('\s+') +SNAPD_RUNNING_STATUS = {'listening', 'running'} def is_installed(): @@ -17,12 +19,24 @@ def is_installed(): def is_snapd_running() -> bool: services = new_subprocess(['systemctl', 'list-units']) - for o in new_subprocess(['grep', '-oP', 'snapd.socket.+\K(listening|running)'], stdin=services.stdout).stdout: + service, service_running = False, False + socket, socket_running = False, False + for o in new_subprocess(['grep', '-Eo', 'snapd.+'], stdin=services.stdout).stdout: if o: line = o.decode().strip() - return bool(line) - return False + if line: + line_split = RE_SNAPD_STATUS.split(line) + running = line_split[3] in SNAPD_RUNNING_STATUS + + if line_split[0] == 'snapd.service': + service = True + service_running = running + elif line_split[0] == 'snapd.socket': + socket = True + socket_running = running + + return socket and socket_running and (not service or (service and service_running)) def app_str_to_json(app: str) -> dict: diff --git a/bauh/view/qt/info.py b/bauh/view/qt/info.py index f4cdf3ea..54f1d923 100644 --- a/bauh/view/qt/info.py +++ b/bauh/view/qt/info.py @@ -3,7 +3,6 @@ from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \ QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QToolBar from bauh.api.abstract.cache import MemoryCache -from bauh.commons.html import strip_html IGNORED_ATTRS = {'name', '__app__'} @@ -54,8 +53,6 @@ class InfoDialog(QDialog): else: val = str(app[attr]).strip() - full_val = None - i18n_val = locale_keys.get('{}.{}'.format(i18n_key, val.lower())) if i18n_val: @@ -63,13 +60,6 @@ class InfoDialog(QDialog): text = QLineEdit() text.setToolTip(val) - - if len(val) > 80: - full_val = val - self.full_vals.append(full_val) - val = strip_html(val) - val = val[0:80] + '...' - text.setText(val) text.setCursorPosition(0) text.setStyleSheet("width: 400px") @@ -80,9 +70,7 @@ class InfoDialog(QDialog): self.gbox_info_layout.addWidget(label, idx, 0) self.gbox_info_layout.addWidget(text, idx, 1) - - if full_val is not None: - self._gen_show_button(idx, full_val) + self._gen_show_button(idx, val) self.adjustSize() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 1595cd52..c631dc99 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -389,7 +389,7 @@ class FindSuggestions(AsyncAction): def run(self): sugs = self.man.list_suggestions(limit=-1) - self.notify_finished([s.package for s in sugs] if sugs is not None else []) + self.notify_finished({'pkgs_found': [s.package for s in sugs] if sugs is not None else [], 'error': None}) class ListWarnings(QThread): diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 498acabf..05693f66 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -333,7 +333,7 @@ class ManageWindow(QWidget): def _show_warnings(self, warnings: List[str]): if warnings: - dialog.show_message(title=self.i18n['warning'].capitalize(), body='
{}
'.format('{}
'.format('