screenshots button

This commit is contained in:
Vinicius Moreira
2019-10-10 12:37:43 -03:00
parent 64a23a11be
commit 6023075b65
19 changed files with 295 additions and 8 deletions

View File

@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.7.0] ## [0.7.0]
### Features ### Features
- AppImage support ( see below ) - AppImage support ( see below )
- "Screenshots" button
### Improvements ### Improvements
- Flatpak: - Flatpak:

View File

@@ -247,3 +247,10 @@ class SoftwareManager(ABC):
@abstractmethod @abstractmethod
def launch(self, pkg: SoftwarePackage): def launch(self, pkg: SoftwarePackage):
pass pass
@abstractmethod
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
"""
:return: screenshot urls for the given package
"""
pass

View File

@@ -169,6 +169,12 @@ class SoftwarePackage(ABC):
""" """
pass pass
def has_screenshots(self) -> bool:
"""
:return: if there are screenshots to be displayed
"""
return not self.installed
def __str__(self): def __str__(self):
return '{} (id={}, name={})'.format(self.__class__.__name__, self.id, self.name) return '{} (id={}, name={})'.format(self.__class__.__name__, self.id, self.name)

View File

@@ -72,6 +72,7 @@ def main():
display_limit=args.max_displayed, display_limit=args.max_displayed,
config=user_config, config=user_config,
context=context, context=context,
http_client=http_client,
notifications=bool(args.system_notifications)) notifications=bool(args.system_notifications))
if args.tray: if args.tray:

View File

@@ -364,3 +364,9 @@ class AppImageManager(SoftwareManager):
def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool):
self.serialize_to_disk(pkg, icon_bytes, only_icon) 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 []

View File

@@ -10,7 +10,7 @@ CACHED_ATTRS = {'name', 'description', 'version', 'url_download', 'author', 'lic
class AppImage(SoftwarePackage): class AppImage(SoftwarePackage):
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, 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, pictures: List[str] = None, icon_path: str = None, installed: bool = False,
url_download_latest_version: str = None): url_download_latest_version: str = None):
super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version, super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version,
@@ -21,6 +21,7 @@ class AppImage(SoftwarePackage):
self.pictures = pictures self.pictures = pictures
self.url_download = url_download self.url_download = url_download
self.icon_path = icon_path self.icon_path = icon_path
self.url_screenshot = url_screenshot
self.author = author self.author = author
self.url_download_latest_version = url_download_latest_version self.url_download_latest_version = url_download_latest_version
@@ -80,3 +81,6 @@ class AppImage(SoftwarePackage):
def get_disk_icon_path(self): def get_disk_icon_path(self):
return self.icon_path return self.icon_path
def has_screenshots(self):
return not self.installed and self.url_screenshot

View File

@@ -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') RELEASE_ATTRS = ('version', 'url_download', 'published_at')

View File

@@ -731,3 +731,6 @@ class ArchManager(SoftwareManager):
def launch(self, pkg: ArchPackage): def launch(self, pkg: ArchPackage):
if pkg.command: if pkg.command:
subprocess.Popen(pkg.command.split(' ')) subprocess.Popen(pkg.command.split(' '))
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
pass

View File

@@ -272,4 +272,14 @@ class FlatpakManager(SoftwareManager):
return True return True
def launch(self, pkg: SoftwarePackage): 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

View File

@@ -191,3 +191,21 @@ class SnapManager(SoftwareManager):
def launch(self, pkg: SnapApplication): def launch(self, pkg: SnapApplication):
snap.run(pkg, self.context.logger) 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 []

View File

@@ -369,3 +369,9 @@ class GenericSoftwareManager(SoftwareManager):
if man: if man:
self.logger.info('Launching {}'.format(pkg)) self.logger.info('Launching {}'.format(pkg))
man.launch(pkg) man.launch(pkg)
def get_screenshots(self, pkg: SoftwarePackage):
man = self._get_manager_for(pkg)
if man:
return man.get_screenshots(pkg)

View File

@@ -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'])) 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(): def handle_click():
self.show_pkg_settings(pkg) self.show_pkg_settings(pkg)

View File

@@ -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()

View File

@@ -5,6 +5,7 @@ from typing import List, Type, Set
import requests import requests
from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.controller import SoftwareManager 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.model import PackageStatus, SoftwarePackage, PackageAction
from bauh.api.abstract.view import InputViewComponent, MessageType from bauh.api.abstract.view import InputViewComponent, MessageType
from bauh.api.exception import NoInternetException from bauh.api.exception import NoInternetException
from bauh.api.http import HttpClient
from bauh.view.qt import commons from bauh.view.qt import commons
from bauh.view.qt.view_model import PackageView from bauh.view.qt.view_model import PackageView
@@ -480,3 +482,32 @@ class CustomAction(AsyncAction):
self.pkg = None self.pkg = None
self.custom_action = None self.custom_action = None
self.root_password = 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

View File

@@ -14,8 +14,11 @@ from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.model import SoftwarePackage, PackageAction from bauh.api.abstract.model import SoftwarePackage, PackageAction
from bauh.api.abstract.view import MessageType 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.config import Configuration
from bauh.view.core.controller import GenericSoftwareManager from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.qt.screenshots import ScreenshotsDialog
from bauh.view.util import util, resource from bauh.view.util import util, resource
from bauh.view.qt import dialog, commons, qt_utils from bauh.view.qt import dialog, commons, qt_utils
from bauh.view.qt.about import AboutDialog 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.styles import StylesComboBox
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \ 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_model import PackageView
from bauh.view.qt.view_utils import load_icon 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, 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, 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__() super(ManageWindow, self).__init__()
self.i18n = i18n self.i18n = i18n
self.manager = manager self.manager = manager
@@ -65,6 +68,7 @@ class ManageWindow(QWidget):
self.config = config self.config = config
self.context = context self.context = context
self.notifications = notifications self.notifications = notifications
self.http_client = http_client
self.icon_app = QIcon(resource.get_path('img/logo.svg')) self.icon_app = QIcon(resource.get_path('img/logo.svg'))
self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__) 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_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_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_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 = ApplyFilters()
self.thread_apply_filters.signal_finished.connect(self._finish_apply_filters_async) 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.app = pkg
self.thread_get_info.start() 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): def get_app_history(self, app: dict):
self._handle_console_option(False) self._handle_console_option(False)
self._begin_action(self.i18n['manage_window.status.history']) self._begin_action(self.i18n['manage_window.status.history'])

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 47.999996 44"
xml:space="preserve"
sodipodi:docname="camera_2.svg"
width="47.999996"
height="44"
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
id="metadata945"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs943">
</defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="739"
id="namedview941"
showgrid="false"
showborder="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.78647591"
inkscape:cx="-389.13442"
inkscape:cy="-153.68157"
inkscape:window-x="0"
inkscape:window-y="432"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g903"><path
style="fill:#ffffff;stroke-width:1.88976378;stroke:#ffffff;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0"
id="path900"
d="M 0,13.46442 V 37.38396 C 0,41.03495 2.625306,44 5.857959,44 h 36.28408 c 3.232653,0 5.857959,-2.96505 5.857959,-6.61604 V 13.46442 c 0,-3.47398 -2.497959,-6.2952 -5.573877,-6.2952 H 34.62857 L 34.442448,6.25094 C 33.688162,2.56676 30.817958,0 27.467754,0 H 20.522448 C 17.18204,0 14.311836,2.56676 13.547755,6.25094 L 13.361632,7.16922 H 5.5738773 C 2.4979591,7.16922 0,10.00151 0,13.46442 Z M 14.321632,9.87981 c 0.558367,0 1.038367,-0.43148 1.165714,-1.05105 l 0.401633,-1.96932 c 0.509388,-2.44506 2.409796,-4.14886 4.633469,-4.14886 h 6.945306 c 2.223673,0 4.124082,1.7038 4.633469,4.14886 l 0.401633,1.96932 c 0.127347,0.6085 0.607347,1.05105 1.165714,1.05105 h 8.747755 c 1.753469,0 3.173877,1.60422 3.173877,3.58461 v 23.91954 c 0,2.1574 -1.547755,3.90545 -3.457959,3.90545 H 5.857959 c -1.910204,0 -3.4579591,-1.74805 -3.4579591,-3.90545 V 13.46442 c 0,-1.98039 1.4204081,-3.58461 3.1738774,-3.58461 z" /><path
style="fill:#ffffff;stroke-width:1.88976378;stroke:#ffffff;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0"
id="path904"
d="m 23.999999,37.04099 c 5.554286,0 10.08,-5.1114 10.08,-11.38447 0,-6.27307 -4.525714,-11.38446 -10.08,-11.38446 -5.554285,0 -10.08,5.10033 -10.08,11.38446 0,6.28414 4.525715,11.38447 10.08,11.38447 z m 0,-20.05834 c 4.231837,0 7.68,3.89439 7.68,8.67387 0,4.77949 -3.448163,8.67388 -7.68,8.67388 -4.231836,0 -7.68,-3.89439 -7.68,-8.67388 0,-4.77948 3.448164,-8.67387 7.68,-8.67387 z" /></g>
<g
id="g910"
transform="translate(0,-410.99999)">
</g>
<g
id="g912"
transform="translate(0,-410.99999)">
</g>
<g
id="g914"
transform="translate(0,-410.99999)">
</g>
<g
id="g916"
transform="translate(0,-410.99999)">
</g>
<g
id="g918"
transform="translate(0,-410.99999)">
</g>
<g
id="g920"
transform="translate(0,-410.99999)">
</g>
<g
id="g922"
transform="translate(0,-410.99999)">
</g>
<g
id="g924"
transform="translate(0,-410.99999)">
</g>
<g
id="g926"
transform="translate(0,-410.99999)">
</g>
<g
id="g928"
transform="translate(0,-410.99999)">
</g>
<g
id="g930"
transform="translate(0,-410.99999)">
</g>
<g
id="g932"
transform="translate(0,-410.99999)">
</g>
<g
id="g934"
transform="translate(0,-410.99999)">
</g>
<g
id="g936"
transform="translate(0,-410.99999)">
</g>
<g
id="g938"
transform="translate(0,-410.99999)">
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -27,6 +27,7 @@ manage_window.status.installing=Installing
manage_window.status.refreshing_app=Refreshing manage_window.status.refreshing_app=Refreshing
manage_window.status.running_app=Launching {} manage_window.status.running_app=Launching {}
manage_window.status.filtering=Filtering manage_window.status.filtering=Filtering
manage_window.status.screenshots=Retrieving {} screenshots
manage_window.bt.refresh.text=Refresh manage_window.bt.refresh.text=Refresh
manage_window.bt.refresh.tooltip=Reload the data about installed applications manage_window.bt.refresh.tooltip=Reload the data about installed applications
manage_window.bt.upgrade.text=Upgrade manage_window.bt.upgrade.text=Upgrade
@@ -47,6 +48,7 @@ tray.action.exit=Quit
tray.action.about=About tray.action.about=About
tray.action.refreshing=Refreshing tray.action.refreshing=Refreshing
action.info.tooltip=Click here to see information about this application 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 action.settings.tooltip=Click here to open extra actions
notification.update={} available update notification.update={} available update
notification.updates={} available updates notification.updates={} available updates
@@ -121,4 +123,5 @@ author=author
source=source source=source
size=size size=size
categories=categories categories=categories
summary=summary summary=summary
popup.screenshots.no_screenshot.body=it was not possible to retrieve {} screenshots

View File

@@ -123,4 +123,7 @@ author=autor
source=origen source=origen
size=tamaño size=tamaño
categories=categorías categories=categorías
summary=sumario 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 {}

View File

@@ -123,4 +123,7 @@ author=autor
source=fonte source=fonte
size=tamanho size=tamanho
categories=categorias categories=categorias
summary=resumo 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 {}