diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5dcf73..9af53481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.7.0] ### Features - AppImage support ( see below ) +- "Screenshots" button ### Improvements - Flatpak: diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index d27f0ea3..2a00373a 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -247,3 +247,10 @@ class SoftwareManager(ABC): @abstractmethod def launch(self, pkg: SoftwarePackage): pass + + @abstractmethod + def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: + """ + :return: screenshot urls for the given package + """ + pass diff --git a/bauh/api/abstract/model.py b/bauh/api/abstract/model.py index 5503fdcd..7bcc2781 100644 --- a/bauh/api/abstract/model.py +++ b/bauh/api/abstract/model.py @@ -169,6 +169,12 @@ class SoftwarePackage(ABC): """ pass + def has_screenshots(self) -> bool: + """ + :return: if there are screenshots to be displayed + """ + return not self.installed + def __str__(self): return '{} (id={}, name={})'.format(self.__class__.__name__, self.id, self.name) diff --git a/bauh/app.py b/bauh/app.py index d33e07d0..5ec98824 100755 --- a/bauh/app.py +++ b/bauh/app.py @@ -72,6 +72,7 @@ def main(): display_limit=args.max_displayed, config=user_config, context=context, + http_client=http_client, notifications=bool(args.system_notifications)) if args.tray: diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index c4f97609..d066bf23 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -364,3 +364,9 @@ class AppImageManager(SoftwareManager): def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): self.serialize_to_disk(pkg, icon_bytes, only_icon) + + def get_screenshots(self, pkg: AppImage) -> List[str]: + if pkg.has_screenshots(): + return [pkg.url_screenshot] + + return [] diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py index bbafbf07..3cc8d7fe 100644 --- a/bauh/gems/appimage/model.py +++ b/bauh/gems/appimage/model.py @@ -10,7 +10,7 @@ CACHED_ATTRS = {'name', 'description', 'version', 'url_download', 'author', 'lic class AppImage(SoftwarePackage): def __init__(self, name: str = None, description: str = None, github: str = None, source: str = None, version: str = None, - url_download: str = None, url_icon: str = None, license: str = None, author: str = None, + url_download: str = None, url_icon: str = None, url_screenshot: str = None, license: str = None, author: str = None, pictures: List[str] = None, icon_path: str = None, installed: bool = False, url_download_latest_version: str = None): super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version, @@ -21,6 +21,7 @@ class AppImage(SoftwarePackage): self.pictures = pictures self.url_download = url_download self.icon_path = icon_path + self.url_screenshot = url_screenshot self.author = author self.url_download_latest_version = url_download_latest_version @@ -80,3 +81,6 @@ class AppImage(SoftwarePackage): def get_disk_icon_path(self): return self.icon_path + + def has_screenshots(self): + return not self.installed and self.url_screenshot diff --git a/bauh/gems/appimage/query.py b/bauh/gems/appimage/query.py index d9ba4b8c..2147f705 100644 --- a/bauh/gems/appimage/query.py +++ b/bauh/gems/appimage/query.py @@ -1,4 +1,4 @@ -APP_ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'license', 'author') +APP_ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'url_screenshot', 'license', 'author') RELEASE_ATTRS = ('version', 'url_download', 'published_at') diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 40623c39..0ffb069a 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -731,3 +731,6 @@ class ArchManager(SoftwareManager): def launch(self, pkg: ArchPackage): if pkg.command: subprocess.Popen(pkg.command.split(' ')) + + def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: + pass diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 61d8e529..5352e82c 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -272,4 +272,14 @@ class FlatpakManager(SoftwareManager): return True def launch(self, pkg: SoftwarePackage): - flatpak.run(pkg.id) + flatpak.run(str(pkg.id)) + + def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: + res = self.http_client.get_json('{}/apps/{}'.format(FLATHUB_API_URL, pkg.id)) + urls = [] + if res and res.get('screenshots'): + for s in res['screenshots']: + if s.get('imgDesktopUrl'): + urls.append(s['imgDesktopUrl']) + + return urls diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 0527605c..1e30b67b 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -191,3 +191,21 @@ class SnapManager(SoftwareManager): def launch(self, pkg: SnapApplication): snap.run(pkg, self.context.logger) + + def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: + res = self.http_client.get_json('{}/search?q={}'.format(SNAP_API_URL, pkg.name)) + + if res: + if res.get('_embedded') and res['_embedded'].get('clickindex:package'): + snap_data = res['_embedded']['clickindex:package'][0] + + if snap_data.get('screenshot_urls'): + return snap_data['screenshot_urls'] + else: + self.logger.warning("No 'screenshots_urls' defined for {}".format(pkg)) + else: + self.logger.error('It seems the API is returning a different response: {}'.format(res)) + else: + self.logger.warning('Could not retrieve data for {}'.format(pkg)) + + return [] diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 1ce55f1e..ca236331 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -369,3 +369,9 @@ class GenericSoftwareManager(SoftwareManager): if man: self.logger.info('Launching {}'.format(pkg)) man.launch(pkg) + + def get_screenshots(self, pkg: SoftwarePackage): + man = self._get_manager_for(pkg) + + if man: + return man.get_screenshots(pkg) diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py index 88a6f06b..c46df7de 100644 --- a/bauh/view/qt/apps_table.py +++ b/bauh/view/qt/apps_table.py @@ -417,6 +417,12 @@ class AppsTable(QTableWidget): item.addWidget(IconButton(icon_path=resource.get_path('img/app_info.svg'), action=get_info, background='#2E68D3', tooltip=self.i18n['action.info.tooltip'])) + if pkg.model.has_screenshots(): + def get_screenshots(): + self.window.get_screenshots(pkg) + + item.addWidget(IconButton(icon_path=resource.get_path('img/camera.svg'), action=get_screenshots, background='purple', tooltip=self.i18n['action.screenshots.tooltip'])) + def handle_click(): self.show_pkg_settings(pkg) diff --git a/bauh/view/qt/screenshots.py b/bauh/view/qt/screenshots.py new file mode 100644 index 00000000..ce803155 --- /dev/null +++ b/bauh/view/qt/screenshots.py @@ -0,0 +1,30 @@ +from typing import List + +from PyQt5.QtGui import QIcon, QPixmap +from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout + +from bauh.api.abstract.cache import MemoryCache +from bauh.view.qt.view_model import PackageView + + +class ScreenshotsDialog(QDialog): + + def __init__(self, pkg: PackageView, icon_cache: MemoryCache, i18n: dict, screenshots: List[QPixmap]): + super(ScreenshotsDialog, self).__init__() + self.setWindowTitle(str(pkg)) + + icon_data = icon_cache.get(pkg.model.icon_url) + + if icon_data and icon_data.get('icon'): + self.setWindowIcon(icon_data.get('icon')) + else: + self.setWindowIcon(QIcon(pkg.model.get_type_icon_path())) + + self.setLayout(QVBoxLayout()) + + if screenshots: + lb = QLabel() + lb.setPixmap(screenshots[0]) + self.layout().addWidget(lb) + + self.adjustSize() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index c631dc99..245c15d2 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -5,6 +5,7 @@ from typing import List, Type, Set import requests from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtGui import QImage, QPixmap from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.controller import SoftwareManager @@ -12,6 +13,7 @@ from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import PackageStatus, SoftwarePackage, PackageAction from bauh.api.abstract.view import InputViewComponent, MessageType from bauh.api.exception import NoInternetException +from bauh.api.http import HttpClient from bauh.view.qt import commons from bauh.view.qt.view_model import PackageView @@ -480,3 +482,32 @@ class CustomAction(AsyncAction): self.pkg = None self.custom_action = None self.root_password = None + + +class GetScreenshots(AsyncAction): + + def __init__(self, manager: SoftwareManager, http_client: HttpClient, pkg: PackageView = None): + super(GetScreenshots, self).__init__() + self.pkg = pkg + self.manager = manager + self.http_client = http_client + + def run(self): + if self.pkg: + imgs = [] + screenshots = self.manager.get_screenshots(self.pkg.model) + + if screenshots: + for url in screenshots: + res = self.http_client.get(url) + + if res and res.status_code == 200: + pixmap = QPixmap() + pixmap.loadFromData(res.content) + imgs.append(pixmap) + break # TODO load all + + self.notify_finished({'pkg': self.pkg, 'screenshots': imgs}) + + self.pkg = None + diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 0613dc35..a88cec63 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -14,8 +14,11 @@ from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.controller import SoftwareManager from bauh.api.abstract.model import SoftwarePackage, PackageAction from bauh.api.abstract.view import MessageType +from bauh.api.http import HttpClient +from bauh.commons.html import bold from bauh.view.core.config import Configuration from bauh.view.core.controller import GenericSoftwareManager +from bauh.view.qt.screenshots import ScreenshotsDialog from bauh.view.util import util, resource from bauh.view.qt import dialog, commons, qt_utils from bauh.view.qt.about import AboutDialog @@ -29,7 +32,7 @@ from bauh.view.qt.root import is_root, ask_root_password from bauh.view.qt.styles import StylesComboBox from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \ - AsyncAction, LaunchApp, ApplyFilters, CustomAction + AsyncAction, LaunchApp, ApplyFilters, CustomAction, GetScreenshots from bauh.view.qt.view_model import PackageView from bauh.view.qt.view_utils import load_icon @@ -48,7 +51,7 @@ class ManageWindow(QWidget): def __init__(self, i18n: dict, icon_cache: MemoryCache, manager: SoftwareManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, display_limit: int, config: Configuration, - context: ApplicationContext, notifications: bool, tray_icon=None): + context: ApplicationContext, notifications: bool, http_client: HttpClient, tray_icon=None): super(ManageWindow, self).__init__() self.i18n = i18n self.manager = manager @@ -65,6 +68,7 @@ class ManageWindow(QWidget): self.config = config self.context = context self.notifications = notifications + self.http_client = http_client self.icon_app = QIcon(resource.get_path('img/logo.svg')) self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__) @@ -213,6 +217,7 @@ class ManageWindow(QWidget): self.thread_suggestions = self._bind_async_action(FindSuggestions(man=self.manager), finished_call=self._finish_search, only_finished=True) self.thread_run_app = self._bind_async_action(LaunchApp(self.manager), finished_call=self._finish_run_app, only_finished=False) self.thread_custom_action = self._bind_async_action(CustomAction(manager=self.manager), finished_call=self._finish_custom_action) + self.thread_screenshots = self._bind_async_action(GetScreenshots(self.manager, http_client), finished_call=self._finish_get_screenshots) self.thread_apply_filters = ApplyFilters() self.thread_apply_filters.signal_finished.connect(self._finish_apply_filters_async) @@ -811,6 +816,24 @@ class ManageWindow(QWidget): self.thread_get_info.app = pkg self.thread_get_info.start() + def get_screenshots(self, pkg: PackageView): + self._handle_console_option(False) + self._begin_action(self.i18n['manage_window.status.screenshots'].format(bold(pkg.model.name))) + + self.thread_screenshots.pkg = pkg + self.thread_screenshots.start() + + def _finish_get_screenshots(self, res: dict): + self.finish_action() + + if res.get('screenshots'): + diag = ScreenshotsDialog(pkg=res['pkg'], icon_cache=self.icon_cache, i18n=self.i18n, screenshots=res['screenshots']) + diag.exec_() + else: + dialog.show_message(title=self.i18n['error'], + body=self.i18n['popup.screenshots.no_screenshot.body'].format(bold(res['pkg'].model.name)), + type_=MessageType.ERROR) + def get_app_history(self, app: dict): self._handle_console_option(False) self._begin_action(self.i18n['manage_window.status.history']) diff --git a/bauh/view/resources/img/camera.svg b/bauh/view/resources/img/camera.svg new file mode 100755 index 00000000..24067e82 --- /dev/null +++ b/bauh/view/resources/img/camera.svg @@ -0,0 +1,126 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index ee30ae8a..1bc605e9 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -27,6 +27,7 @@ manage_window.status.installing=Installing manage_window.status.refreshing_app=Refreshing manage_window.status.running_app=Launching {} manage_window.status.filtering=Filtering +manage_window.status.screenshots=Retrieving {} screenshots manage_window.bt.refresh.text=Refresh manage_window.bt.refresh.tooltip=Reload the data about installed applications manage_window.bt.upgrade.text=Upgrade @@ -47,6 +48,7 @@ tray.action.exit=Quit tray.action.about=About tray.action.refreshing=Refreshing action.info.tooltip=Click here to see information about this application +action.screenshots.tooltip=Click here to see some pictures of this application action.settings.tooltip=Click here to open extra actions notification.update={} available update notification.updates={} available updates @@ -121,4 +123,5 @@ author=author source=source size=size categories=categories -summary=summary \ No newline at end of file +summary=summary +popup.screenshots.no_screenshot.body=it was not possible to retrieve {} screenshots \ No newline at end of file diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index f29bf5a8..e109b7bb 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -123,4 +123,7 @@ author=autor source=origen size=tamaño categories=categorías -summary=sumario \ No newline at end of file +summary=sumario +manage_window.status.screenshots=Obteniendo imágenes de {} +action.screenshots.tooltip=Haga clic aquí para ver algunas fotos de esta aplicación +popup.screenshots.no_screenshot.body=No fue posible recuperar las fotos de {} \ No newline at end of file diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt index e16addef..75e7e1fb 100644 --- a/bauh/view/resources/locale/pt +++ b/bauh/view/resources/locale/pt @@ -123,4 +123,7 @@ author=autor source=fonte size=tamanho categories=categorias -summary=resumo \ No newline at end of file +summary=resumo +manage_window.status.screenshots=Obtendo imagens de {} +action.screenshots.tooltip=Clique aqui para ver algumas fotos desse aplicativo +popup.screenshots.no_screenshot.body=Não foi possível obter as fotos de {} \ No newline at end of file