Merge branch 'fpakpart' into staging

This commit is contained in:
Vinícius Moreira
2020-01-13 16:12:21 -03:00
29 changed files with 412 additions and 107 deletions

View File

@@ -5,9 +5,20 @@ 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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.8.1] ## [0.8.1]
### Features:
- Flatpak:
- allow the user to choose the application installation level: **user** or **system**
- able to deal with user and system applications / runtimes
- able to list partial updates for Flatpak >= 1.4
- new configuration file located at **~/.config/bauh/flatpak.yml** ( it allows to define a default installation level )
### Improvements ### Improvements
- All icons are now SVG files - All icons are now SVG files
- HDPI support improvements ( by [octopusSD](https://github.com/octopusSD) ) - HDPI support improvements ( by [octopusSD](https://github.com/octopusSD) )
- Flatpak:
- the application name tooltip now displays the installation level. e.g: **gedit ( system )**
- info window displaying the installation level
- "remote not set" warning dropped in favor of the new behavior: automatically adds Flathub as the default remote at the user level
- Web: - Web:
- not using HTTP sessions anymore to perform the searches. It seems to avoid URLs not being found after an internet drop event. - not using HTTP sessions anymore to perform the searches. It seems to avoid URLs not being found after an internet drop event.
- supporting JPEG images as custom icons - supporting JPEG images as custom icons
@@ -16,17 +27,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- scaling - scaling
### Fixes ### Fixes
- missing categories i18n [48](https://github.com/vinifmor/bauh/issues/48) - missing categories i18n [#48](https://github.com/vinifmor/bauh/issues/48)
- Flatpak:
- updating application dependencies during updating and downgrading
- Web: - Web:
- not handling HTTP connection issues - not handling HTTP connection issues
- not passing the Home path as a String ( does not work in Python 3.5 ) - not passing the Home path as a String ( an exception happens for Python 3.5 )
- UI: - UI:
- not verifying if an icon path is a file - not verifying if an icon path is a file
- minor fixes - minor fixes
- minor bug fixes
### UI ### UI
- Default **Type** icon removed from the Type filter to make the design more consistent with the Category filter. - Default **Type** icon removed from the Type filter to make the design more consistent.
## [0.8.0] 2019-12-24 ## [0.8.0] 2019-12-24
### Features ### Features

View File

@@ -21,6 +21,7 @@ To contribute with this project, have a look at [CONTRIBUTING.md](https://github
- **pip3** - **pip3**
- **python3-requests** - **python3-requests**
- **python-yaml** - **python-yaml**
- **qt5dxcb-plugin**
- **python3-venv** ( only for [Manual installation](https://github.com/vinifmor/bauh/tree/wgem#manual-installation) ) - **python3-venv** ( only for [Manual installation](https://github.com/vinifmor/bauh/tree/wgem#manual-installation) )
- **libappindicator3** ( for the **tray mode** in GTK3 desktop environments ) - **libappindicator3** ( for the **tray mode** in GTK3 desktop environments )
@@ -99,6 +100,11 @@ Before uninstalling bauh via your package manager, consider executing `bauh --re
![flatpak_search](https://raw.githubusercontent.com/vinifmor/bauh/staging/pictures/flatpak/search.gif) ![flatpak_search](https://raw.githubusercontent.com/vinifmor/bauh/staging/pictures/flatpak/search.gif)
- The configuration file is located at **~/.config/bauh/flatpak.yml** and it allows the following customizations:
```
installation_level: null # defines a default installation level: user or system. ( the popup will not be displayed if a value is defined )
```
- Required dependencies: - Required dependencies:
- Any distro: **flatpak** - Any distro: **flatpak**

View File

@@ -1,4 +1,4 @@
from typing import List from typing import List, Tuple
from bauh.api.abstract.view import MessageType, ViewComponent from bauh.api.abstract.view import MessageType, ViewComponent
@@ -65,3 +65,9 @@ class ProcessWatcher:
""" """
:return: if the use requested to stop the process. :return: if the use requested to stop the process.
""" """
def request_root_password(self) -> Tuple[str, bool]:
"""
asks the root password for the user
:return: a tuple with the typed password and if it is valid
"""

View File

@@ -175,6 +175,12 @@ class SoftwarePackage(ABC):
""" """
return not self.installed return not self.installed
def get_name_tooltip(self) -> str:
"""
:return: the application name that should be displayed on the UI tooltips.
"""
return self.name
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

@@ -4,7 +4,6 @@ from threading import Thread
import urllib3 import urllib3
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from bauh import __version__, __app_name__, app_args, ROOT_DIR from bauh import __version__, __app_name__, app_args, ROOT_DIR
@@ -15,7 +14,7 @@ from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.core.downloader import AdaptableFileDownloader from bauh.view.core.downloader import AdaptableFileDownloader
from bauh.view.qt.systray import TrayIcon from bauh.view.qt.systray import TrayIcon
from bauh.view.qt.window import ManageWindow from bauh.view.qt.window import ManageWindow
from bauh.view.util import util, logs, resource, translation from bauh.view.util import util, logs, translation
from bauh.view.util.cache import DefaultMemoryCacheFactory, CacheCleaner from bauh.view.util.cache import DefaultMemoryCacheFactory, CacheCleaner
from bauh.view.util.disk import DefaultDiskCacheLoaderFactory from bauh.view.util.disk import DefaultDiskCacheLoaderFactory
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n

5
bauh/commons/user.py Normal file
View File

@@ -0,0 +1,5 @@
import os
def is_root():
return os.getuid() == 0

View File

@@ -1,3 +1,6 @@
import os import os
from pathlib import Path
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/flatpak/suggestions.txt' SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/flatpak/suggestions.txt'
CONFIG_FILE = '{}/.config/bauh/flatpak.yml'.format(Path.home())

View File

@@ -0,0 +1,7 @@
from bauh.commons.config import read_config as read
from bauh.gems.flatpak import CONFIG_FILE
def read_config(update_file: bool = False) -> dict:
template = {'installation_level': None}
return read(CONFIG_FILE, template, update_file=update_file)

View File

@@ -9,9 +9,11 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \ from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \
SuggestionPriority SuggestionPriority
from bauh.api.abstract.view import MessageType from bauh.api.abstract.view import MessageType
from bauh.commons import user
from bauh.commons.html import strip_html, bold from bauh.commons.html import strip_html, bold
from bauh.commons.system import SystemProcess, ProcessHandler from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess
from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE, CONFIG_FILE
from bauh.gems.flatpak.config import read_config
from bauh.gems.flatpak.constants import FLATHUB_API_URL from bauh.gems.flatpak.constants import FLATHUB_API_URL
from bauh.gems.flatpak.model import FlatpakApplication from bauh.gems.flatpak.model import FlatpakApplication
from bauh.gems.flatpak.worker import FlatpakAsyncDataLoader, FlatpakUpdateLoader from bauh.gems.flatpak.worker import FlatpakAsyncDataLoader, FlatpakUpdateLoader
@@ -37,7 +39,7 @@ class FlatpakManager(SoftwareManager):
def _map_to_model(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader, internet: bool = True) -> FlatpakApplication: def _map_to_model(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader, internet: bool = True) -> FlatpakApplication:
app = FlatpakApplication(**app_json) app = FlatpakApplication(**app_json, i18n=self.i18n)
app.installed = installed app.installed = installed
api_data = self.api_cache.get(app_json['id']) api_data = self.api_cache.get(app_json['id'])
@@ -57,12 +59,27 @@ class FlatpakManager(SoftwareManager):
return app return app
def _get_search_remote(self) -> str:
remotes = flatpak.list_remotes()
if remotes['system']:
remote_level = 'system'
elif remotes['user']:
remote_level = 'user'
else:
remote_level = 'user'
ProcessHandler().handle_simple(flatpak.set_default_remotes(remote_level))
return remote_level
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
if is_url: if is_url:
return SearchResult([], [], 0) return SearchResult([], [], 0)
remote_level = self._get_search_remote()
res = SearchResult([], [], 0) res = SearchResult([], [], 0)
apps_found = flatpak.search(flatpak.get_version(), words) apps_found = flatpak.search(flatpak.get_version(), words, remote_level)
if apps_found: if apps_found:
already_read = set() already_read = set()
@@ -101,19 +118,36 @@ class FlatpakManager(SoftwareManager):
models = [] models = []
if installed: if installed:
update_map = None
if thread_updates: if thread_updates:
thread_updates.join() thread_updates.join()
update_map = updates[0]
for app_json in installed: for app_json in installed:
model = self._map_to_model(app_json=app_json, installed=True, model = self._map_to_model(app_json=app_json, installed=True,
disk_loader=disk_loader, internet=internet_available) disk_loader=disk_loader, internet=internet_available)
if version >= '1.5.0': model.update = None
model.update = '{}/{}'.format(app_json['id'], app_json['branch']) in updates[0] if updates else None
else:
model.update = app_json['ref'] in updates[0] if updates else None
models.append(model) models.append(model)
if update_map and (update_map['full'] or update_map['partial']):
if version >= '1.4.0':
update_id = '{}/{}/{}'.format(app_json['id'], app_json['branch'], app_json['installation'])
if update_map['full'] and update_id in update_map['full']:
model.update = True
if update_map['partial']:
for partial in update_map['partial']:
partial_data = partial.split('/')
if app_json['id'] in partial_data[0] and\
app_json['branch'] == partial_data[1] and\
app_json['installation'] == partial_data[2]:
partial_model = model.gen_partial(partial.split('/')[0])
partial_model.update = True
models.append(partial_model)
else:
model.update = '{}/{}'.format(app_json['installation'], app_json['ref']) in update_map['full']
return SearchResult(models, None, len(models)) return SearchResult(models, None, len(models))
def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
@@ -121,7 +155,7 @@ class FlatpakManager(SoftwareManager):
watcher.change_progress(10) watcher.change_progress(10)
watcher.change_substatus(self.i18n['flatpak.downgrade.commits']) watcher.change_substatus(self.i18n['flatpak.downgrade.commits'])
commits = flatpak.get_app_commits(pkg.ref, pkg.origin) commits = flatpak.get_app_commits(pkg.ref, pkg.origin, pkg.installation)
commit_idx = commits.index(pkg.commit) commit_idx = commits.index(pkg.commit)
@@ -133,7 +167,7 @@ class FlatpakManager(SoftwareManager):
commit = commits[commit_idx + 1] commit = commits[commit_idx + 1]
watcher.change_substatus(self.i18n['flatpak.downgrade.reverting']) watcher.change_substatus(self.i18n['flatpak.downgrade.reverting'])
watcher.change_progress(50) watcher.change_progress(50)
success = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.downgrade(pkg.ref, commit, root_password), success = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.downgrade(pkg.ref, commit, pkg.installation, root_password),
success_phrases=['Changes complete.', 'Updates complete.'], success_phrases=['Changes complete.', 'Updates complete.'],
wrong_error_phrase='Warning')) wrong_error_phrase='Warning'))
watcher.change_progress(100) watcher.change_progress(100)
@@ -144,10 +178,10 @@ class FlatpakManager(SoftwareManager):
self.api_cache.delete(pkg.id) self.api_cache.delete(pkg.id)
def update(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: def update(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
return ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.update(pkg.ref))) return ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.update(pkg.ref, pkg.installation)))
def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.uninstall(pkg.ref))) uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.uninstall(pkg.ref, pkg.installation)))
if self.suggestions_cache: if self.suggestions_cache:
self.suggestions_cache.delete(pkg.id) self.suggestions_cache.delete(pkg.id)
@@ -161,6 +195,9 @@ class FlatpakManager(SoftwareManager):
app_info['type'] = 'runtime' if app.runtime else 'app' app_info['type'] = 'runtime' if app.runtime else 'app'
app_info['description'] = strip_html(app.description) if app.description else '' app_info['description'] = strip_html(app.description) if app.description else ''
if app.installation:
app_info['installation'] = app.installation
if app_info.get('installed'): if app_info.get('installed'):
app_info['installed'] = app_info['installed'].replace('?', ' ') app_info['installed'] = app_info['installed'].replace('?', ' ')
@@ -194,7 +231,7 @@ class FlatpakManager(SoftwareManager):
def get_history(self, pkg: FlatpakApplication) -> PackageHistory: def get_history(self, pkg: FlatpakApplication) -> PackageHistory:
pkg.commit = flatpak.get_commit(pkg.id, pkg.branch) pkg.commit = flatpak.get_commit(pkg.id, pkg.branch)
commits = flatpak.get_app_commits_data(pkg.ref, pkg.origin) commits = flatpak.get_app_commits_data(pkg.ref, pkg.origin, pkg.installation)
status_idx = 0 status_idx = 0
for idx, data in enumerate(commits): for idx, data in enumerate(commits):
@@ -205,11 +242,56 @@ class FlatpakManager(SoftwareManager):
return PackageHistory(pkg=pkg, history=commits, pkg_status_idx=status_idx) return PackageHistory(pkg=pkg, history=commits, pkg_status_idx=status_idx)
def install(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: def install(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
res = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.install(pkg.id, pkg.origin), wrong_error_phrase='Warning'))
config = read_config()
install_level = config['installation_level']
if install_level is not None:
self.logger.info("Default Flaptak installation level defined: {}".format(install_level))
if install_level not in ('user', 'system'):
watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['flatpak.install.bad_install_level.body'].format(field=bold('installation_level'),
file=bold(CONFIG_FILE)),
type_=MessageType.ERROR)
return False
pkg.installation = install_level
else:
user_level = watcher.request_confirmation(title=self.i18n['flatpak.install.install_level.title'],
body=self.i18n['flatpak.install.install_level.body'].format(bold(pkg.name)),
confirmation_label=self.i18n['no'].capitalize(),
deny_label=self.i18n['yes'].capitalize())
pkg.installation = 'user' if user_level else 'system'
remotes = flatpak.list_remotes()
handler = ProcessHandler(watcher)
if pkg.installation == 'user' and not remotes['user']:
handler.handle_simple(flatpak.set_default_remotes('user'))
elif pkg.installation == 'system' and not remotes['system']:
if user.is_root():
handler.handle_simple(flatpak.set_default_remotes('system'))
else:
user_password, valid = watcher.request_root_password()
if not valid:
watcher.print('Operation aborted')
return False
else:
if not handler.handle_simple(flatpak.set_default_remotes('system', user_password)):
watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['flatpak.remotes.system_flathub.error'],
type_=MessageType.ERROR)
watcher.print("Operation cancelled")
return False
res = handler.handle(SystemProcess(subproc=flatpak.install(str(pkg.id), pkg.origin, pkg.installation), wrong_error_phrase='Warning'))
if res: if res:
try: try:
fields = flatpak.get_fields(pkg.id, pkg.branch, ['Ref', 'Branch']) fields = flatpak.get_fields(str(pkg.id), pkg.branch, ['Ref', 'Branch'])
if fields: if fields:
pkg.ref = fields[0] pkg.ref = fields[0]
@@ -229,7 +311,7 @@ class FlatpakManager(SoftwareManager):
return flatpak.is_installed() return flatpak.is_installed()
def requires_root(self, action: str, pkg: FlatpakApplication): def requires_root(self, action: str, pkg: FlatpakApplication):
return action == 'downgrade' return action == 'downgrade' and pkg.installation == 'system'
def prepare(self): def prepare(self):
pass pass
@@ -253,17 +335,14 @@ class FlatpakManager(SoftwareManager):
loader.join() loader.join()
for app in to_update: for app in to_update:
updates.append(PackageUpdate(pkg_id='{}:{}'.format(app.id, app.branch), updates.append(PackageUpdate(pkg_id='{}:{}:{}'.format(app.id, app.branch, app.installation),
pkg_type='flatpak', pkg_type='flatpak',
version=app.version)) version=app.version))
return updates return updates
def list_warnings(self, internet_available: bool) -> List[str]: def list_warnings(self, internet_available: bool) -> List[str]:
if flatpak.is_installed(): return []
if not flatpak.has_remotes_set():
return [self.i18n['flatpak.notification.no_remotes'],
self.i18n['flatpak.notification.disable'].format(bold('Flatpak'), bold(self.i18n['manage_window.settings.gems']))]
def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]: def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]:
cli_version = flatpak.get_version() cli_version = flatpak.get_version()
@@ -277,6 +356,7 @@ class FlatpakManager(SoftwareManager):
return res return res
else: else:
self.logger.info("Mapping suggestions") self.logger.info("Mapping suggestions")
remote_level = self._get_search_remote()
installed = {i.id for i in self.read_installed(disk_loader=None).installed} if filter_installed else None installed = {i.id for i in self.read_installed(disk_loader=None).installed} if filter_installed else None
for line in file.text.split('\n'): for line in file.text.split('\n'):
@@ -295,7 +375,7 @@ class FlatpakManager(SoftwareManager):
if cached_sug: if cached_sug:
res.append(cached_sug) res.append(cached_sug)
else: else:
app_json = flatpak.search(cli_version, appid, app_id=True) app_json = flatpak.search(cli_version, appid, remote_level, app_id=True)
if app_json: if app_json:
model = PackageSuggestion(self._map_to_model(app_json[0], False, None), priority) model = PackageSuggestion(self._map_to_model(app_json[0], False, None), priority)
@@ -310,7 +390,7 @@ class FlatpakManager(SoftwareManager):
def is_default_enabled(self) -> bool: def is_default_enabled(self) -> bool:
return True return True
def launch(self, pkg: SoftwarePackage): def launch(self, pkg: FlatpakApplication):
flatpak.run(str(pkg.id)) flatpak.run(str(pkg.id))
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:

View File

@@ -1,11 +1,12 @@
import re import re
import subprocess import subprocess
import traceback
from datetime import datetime from datetime import datetime
from io import StringIO from io import StringIO
from typing import List from typing import List, Dict, Set
from bauh.api.exception import NoInternetException from bauh.api.exception import NoInternetException
from bauh.commons.system import new_subprocess, run_cmd, new_root_subprocess from bauh.commons.system import new_subprocess, run_cmd, new_root_subprocess, SimpleProcess, SystemProcess
BASE_CMD = 'flatpak' BASE_CMD = 'flatpak'
RE_SEVERAL_SPACES = re.compile(r'\s+') RE_SEVERAL_SPACES = re.compile(r'\s+')
@@ -96,6 +97,7 @@ def list_installed(version: str) -> List[dict]:
'description': None, 'description': None,
'origin': data[1], 'origin': data[1],
'runtime': runtime, 'runtime': runtime,
'installation': 'user' if 'user' in data[5] else 'system',
'version': ref_split[2] if runtime else None 'version': ref_split[2] if runtime else None
}) })
@@ -135,52 +137,100 @@ def list_installed(version: str) -> List[dict]:
'description': data[4], 'description': data[4],
'origin': data[5], 'origin': data[5],
'runtime': runtime, 'runtime': runtime,
'installation': 'user' if 'user' in data[6] else 'system',
'version': app_ver}) 'version': app_ver})
return apps return apps
def update(app_ref: str): def update(app_ref: str, installation: str):
""" """
Updates the app reference Updates the app reference
:param app_ref: :param app_ref:
:return: :return:
""" """
return new_subprocess([BASE_CMD, 'update', '--no-related', '-y', app_ref]) return new_subprocess([BASE_CMD, 'update', '--no-related', '--no-deps', '-y', app_ref, '--{}'.format(installation)])
def uninstall(app_ref: str): def register_flathub(installation: str) -> SimpleProcess:
return SimpleProcess([BASE_CMD,
'remote-add',
'--if-not-exists',
'flathub',
'https://flathub.org/repo/flathub.flatpakrepo',
'--{}'.format(installation)])
def uninstall(app_ref: str, installation: str):
""" """
Removes the app by its reference Removes the app by its reference
:param app_ref: :param app_ref:
:return: :return:
""" """
return new_subprocess([BASE_CMD, 'uninstall', app_ref, '-y']) return new_subprocess([BASE_CMD, 'uninstall', app_ref, '-y', '--{}'.format(installation)])
def list_updates_as_str(version: str): def list_updates_as_str(version: str) -> Dict[str, set]:
updates = read_updates(version, 'system')
user_updates = read_updates(version, 'user')
for attr in ('full', 'partial'):
updates[attr].update(user_updates[attr])
return updates
def read_updates(version: str, installation: str) -> Dict[str, set]:
res = {'partial': set(), 'full': set()}
if version < '1.2': if version < '1.2':
return run_cmd('{} update --no-related'.format(BASE_CMD), ignore_return_code=True) try:
output = run_cmd('{} update --no-related --no-deps --{}'.format(BASE_CMD, installation), ignore_return_code=True)
if 'Updating in {}'.format(installation) in output:
for line in output.split('Updating in {}:\n'.format(installation))[1].split('\n'):
if not line.startswith('Is this ok'):
res['full'].add('{}/{}'.format(installation, line.split('\t')[0].strip()))
except:
traceback.print_exc()
else: else:
updates = new_subprocess([BASE_CMD, 'update']).stdout updates = new_subprocess([BASE_CMD, 'update', '--{}'.format(installation)]).stdout
out = StringIO() reg = r'[0-9]+\.\s+.+'
reg = r'[0-9]+\.\s+(\w+|\.)+\s+(\w|\.)+' if version >= '1.5.0' else r'[0-9]+\.\s+(\w+|\.)+\s+\w+\s+(\w|\.)+' try:
for o in new_subprocess(['grep', '-E', reg, '-o', '--color=never'], stdin=updates).stdout:
if o:
line_split = o.decode().strip().split('\t')
for o in new_subprocess(['grep', '-E', reg, '-o', '--color=never'], stdin=updates).stdout: if version >= '1.5.0':
if o: update_id = '{}/{}/{}'.format(line_split[2], line_split[3], installation)
out.write('/'.join(o.decode().strip().split('\t')[2:]) + '\n') else:
update_id = '{}/{}/{}'.format(line_split[2], line_split[4], installation)
out.seek(0) if len(line_split) >= 6:
return out.read() if line_split[4] != 'i':
if '(partial)' in line_split[-1]:
res['partial'].add(update_id)
else:
res['full'].add(update_id)
else:
res['full'].add(update_id)
except:
traceback.print_exc()
return res
def downgrade(app_ref: str, commit: str, root_password: str) -> subprocess.Popen: def downgrade(app_ref: str, commit: str, installation: str, root_password: str) -> subprocess.Popen:
return new_root_subprocess([BASE_CMD, 'update', '--no-related', '--commit={}'.format(commit), app_ref, '-y'], root_password) cmd = [BASE_CMD, 'update', '--no-related', '--no-deps', '--commit={}'.format(commit), app_ref, '-y', '--{}'.format(installation)]
if installation == 'system':
return new_root_subprocess(cmd, root_password)
else:
return new_subprocess(cmd)
def get_app_commits(app_ref: str, origin: str) -> List[str]: def get_app_commits(app_ref: str, origin: str, installation: str) -> List[str]:
log = run_cmd('{} remote-info --log {} {}'.format(BASE_CMD, origin, app_ref)) log = run_cmd('{} remote-info --log {} {} --{}'.format(BASE_CMD, origin, app_ref, installation))
if log: if log:
return re.findall(r'Commit+:\s(.+)', log) return re.findall(r'Commit+:\s(.+)', log)
@@ -188,8 +238,8 @@ def get_app_commits(app_ref: str, origin: str) -> List[str]:
raise NoInternetException() raise NoInternetException()
def get_app_commits_data(app_ref: str, origin: str) -> List[dict]: def get_app_commits_data(app_ref: str, origin: str, installation: str) -> List[dict]:
log = run_cmd('{} remote-info --log {} {}'.format(BASE_CMD, origin, app_ref)) log = run_cmd('{} remote-info --log {} {} --{}'.format(BASE_CMD, origin, app_ref, installation))
if not log: if not log:
raise NoInternetException() raise NoInternetException()
@@ -214,9 +264,9 @@ def get_app_commits_data(app_ref: str, origin: str) -> List[dict]:
return commits return commits
def search(version: str, word: str, app_id: bool = False) -> List[dict]: def search(version: str, word: str, installation: str, app_id: bool = False) -> List[dict]:
res = run_cmd('{} search {}'.format(BASE_CMD, word)) res = run_cmd('{} search {} --{}'.format(BASE_CMD, word, installation))
found = [] found = []
@@ -293,17 +343,36 @@ def search(version: str, word: str, app_id: bool = False) -> List[dict]:
return found return found
def install(app_id: str, origin: str): def install(app_id: str, origin: str, installation: str):
return new_subprocess([BASE_CMD, 'install', origin, app_id, '-y']) return new_subprocess([BASE_CMD, 'install', origin, app_id, '-y', '--{}'.format(installation)])
def set_default_remotes(): def set_default_remotes(installation: str, root_password: str = None) -> SimpleProcess:
run_cmd('{} remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo'.format(BASE_CMD)) cmd = [BASE_CMD, 'remote-add', '--if-not-exists', 'flathub', 'https://flathub.org/repo/flathub.flatpakrepo', '--{}'.format(installation)]
return SimpleProcess(cmd, root_password=root_password)
def has_remotes_set() -> bool: def has_remotes_set() -> bool:
return bool(run_cmd('{} remotes'.format(BASE_CMD)).strip()) return bool(run_cmd('{} remotes'.format(BASE_CMD)).strip())
def list_remotes() -> Dict[str, Set[str]]:
res = {'system': set(), 'user': set()}
output = run_cmd('{} remotes'.format(BASE_CMD)).strip()
if output:
lines = output.split('\n')
for line in lines:
remote = line.split('\t')
if 'system' in remote[1]:
res['system'].add(remote[0].strip())
elif 'user' in remote[1]:
res['user'].add(remote[0].strip())
return res
def run(app_id: str): def run(app_id: str):
subprocess.Popen([BASE_CMD, 'run', app_id]) subprocess.Popen([BASE_CMD, 'run', app_id])

View File

@@ -1,13 +1,16 @@
from bauh.api.abstract.model import SoftwarePackage import copy
from bauh.api.abstract.model import SoftwarePackage
from bauh.commons import resource from bauh.commons import resource
from bauh.gems.flatpak import ROOT_DIR from bauh.gems.flatpak import ROOT_DIR
from bauh.view.util.translation import I18n
class FlatpakApplication(SoftwarePackage): class FlatpakApplication(SoftwarePackage):
def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None, description: str = None, def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None, description: str = None,
branch: str = None, arch: str = None, origin: str = None, runtime: bool = False, ref: str = None, commit: str = None): branch: str = None, arch: str = None, origin: str = None, runtime: bool = False, ref: str = None, commit: str = None,
installation: str = None, i18n: I18n = None):
super(FlatpakApplication, self).__init__(id=id, name=name, version=version, super(FlatpakApplication, self).__init__(id=id, name=name, version=version,
latest_version=latest_version, description=description) latest_version=latest_version, description=description)
self.ref = ref self.ref = ref
@@ -16,6 +19,9 @@ class FlatpakApplication(SoftwarePackage):
self.origin = origin self.origin = origin
self.runtime = runtime self.runtime = runtime
self.commit = commit self.commit = commit
self.partial = False
self.installation = installation if installation else 'system'
self.i18n = i18n
if runtime: if runtime:
self.categories = ['runtime'] self.categories = ['runtime']
@@ -23,14 +29,14 @@ class FlatpakApplication(SoftwarePackage):
def is_incomplete(self): def is_incomplete(self):
return self.description is None and self.icon_url return self.description is None and self.icon_url
def has_history(self): def has_history(self) -> bool:
return self.installed and self.ref return not self.partial and self.installed and self.ref
def has_info(self): def has_info(self):
return bool(self.id) return bool(self.id)
def can_be_downgraded(self): def can_be_downgraded(self):
return self.installed and self.ref return not self.partial and self.installed and self.ref
def get_type(self): def get_type(self):
return 'flatpak' return 'flatpak'
@@ -63,7 +69,23 @@ class FlatpakApplication(SoftwarePackage):
setattr(self, attr, data[attr]) setattr(self, attr, data[attr])
def can_be_run(self) -> bool: def can_be_run(self) -> bool:
return self.installed and not self.runtime return self.installed and not self.runtime and not self.partial
def get_publisher(self): def get_publisher(self):
return self.origin return self.origin
def gen_partial(self, partial_id: str) -> "FlatpakApplication":
partial = copy.deepcopy(self)
partial.id = partial_id
if self.ref:
partial.ref = '/'.join((partial_id, *self.ref.split('/')[1:]))
partial.partial = True
return partial
def get_name_tooltip(self) -> str:
if self.installation and self.i18n is not None:
return '{} ( {} )'.format(self.name, self.i18n[self.installation.lower()])
return self.name

View File

@@ -7,6 +7,8 @@ flatpak.info.date=data
flatpak.info.description=descripció flatpak.info.description=descripció
flatpak.info.id=id flatpak.info.id=id
flatpak.info.installation=instal·lació flatpak.info.installation=instal·lació
flatpak.info.installation.user=usuari
flatpak.info.installation.system=sistema
flatpak.info.installed=instal·lat flatpak.info.installed=instal·lat
flatpak.info.license=llicència flatpak.info.license=llicència
flatpak.info.name=nom flatpak.info.name=nom
@@ -40,3 +42,7 @@ flatpak.info.instoresincedate=afegit el
flatpak.info.projectlicense=llicència flatpak.info.projectlicense=llicència
flatpak.info.translateurl=Traducció flatpak.info.translateurl=Traducció
flatpak.info.developername=desenvolupador flatpak.info.developername=desenvolupador
flatpak.install.install_level.title=Tipus d'instal·lació
flatpak.install.install_level.body=S'ha d'instal·lar {} per a tots els usuaris del dispositiu ( sistema ) ?
flatpak.install.bad_install_level.body=Valor invàlid per a {field} al fitxer de configuració {file}
flatpak.remotes.system_flathub.error=No s'ha pogut afegir Flathub com a dipòsit del sistema ( remote )

View File

@@ -26,6 +26,8 @@ flatpak.info.date=Datum
flatpak.info.description=Beschreibung flatpak.info.description=Beschreibung
flatpak.info.id=Id flatpak.info.id=Id
flatpak.info.installation=Installation flatpak.info.installation=Installation
flatpak.info.installation.user=Benutzer
flatpak.info.installation.system=System
flatpak.info.installed=installiert flatpak.info.installed=installiert
flatpak.info.license=Lizenz flatpak.info.license=Lizenz
flatpak.info.name=Name flatpak.info.name=Name
@@ -38,4 +40,8 @@ flatpak.info.subject=Thema
flatpak.info.type=Typ flatpak.info.type=Typ
flatpak.info.version=Version flatpak.info.version=Version
flatpak.history.date=Datum flatpak.history.date=Datum
flatpak.history.commit=Commit flatpak.history.commit=Commit
flatpak.install.install_level.title=Installationstyp
flatpak.install.install_level.body=Sollte {} für alle Gerätebenutzer installiert werden ( system ) ?
flatpak.install.bad_install_level.body=Ungültiger Wert für {field} in der Konfigurationsdatei {file}
flatpak.remotes.system_flathub.error=Flathub konnte nicht als System-Repository ( remote ) hinzugefügt werden

View File

@@ -26,6 +26,8 @@ flatpak.info.date=date
flatpak.info.description=description flatpak.info.description=description
flatpak.info.id=id flatpak.info.id=id
flatpak.info.installation=installation flatpak.info.installation=installation
flatpak.info.installation.user=user
flatpak.info.installation.system=system
flatpak.info.installed=installed flatpak.info.installed=installed
flatpak.info.license=license flatpak.info.license=license
flatpak.info.name=name flatpak.info.name=name
@@ -38,4 +40,8 @@ flatpak.info.subject=subject
flatpak.info.type=type flatpak.info.type=type
flatpak.info.version=version flatpak.info.version=version
flatpak.history.date=date flatpak.history.date=date
flatpak.history.commit=commit flatpak.history.commit=commit
flatpak.install.install_level.title=Installation type
flatpak.install.install_level.body=Should {} be installed for all the device users ( system ) ?
flatpak.install.bad_install_level.body=Invalid value for {field} in the configuration file {file}
flatpak.remotes.system_flathub.error=It was not possible to add Flathub as a system repository ( remote )

View File

@@ -7,6 +7,8 @@ flatpak.info.date=fecha
flatpak.info.description=descripción flatpak.info.description=descripción
flatpak.info.id=id flatpak.info.id=id
flatpak.info.installation=instalación flatpak.info.installation=instalación
flatpak.info.installation.user=usuario
flatpak.info.installation.system=sistema
flatpak.info.installed=instalado flatpak.info.installed=instalado
flatpak.info.license=licencia flatpak.info.license=licencia
flatpak.info.name=nombre flatpak.info.name=nombre
@@ -39,4 +41,8 @@ flatpak.info.homepageurl=página
flatpak.info.instoresincedate=agregado en flatpak.info.instoresincedate=agregado en
flatpak.info.projectlicense=licencia flatpak.info.projectlicense=licencia
flatpak.info.translateurl=Traducción flatpak.info.translateurl=Traducción
flatpak.info.developername=desarrollador flatpak.info.developername=desarrollador
flatpak.install.install_level.title=Tipo de instalación
flatpak.install.install_level.body=¿Debería {} estar instalado para todos los usuarios del dispositivo ( sistema )?
flatpak.install.bad_install_level.body=Valor inválido para {field} en el archivo de configuración {file}
flatpak.remotes.system_flathub.error=No fue posible agregar Flathub como repositorio del sistema ( remote )

View File

@@ -18,3 +18,10 @@ flatpak.info.instoresincedate=aggiunto in
flatpak.info.projectlicense=licenza flatpak.info.projectlicense=licenza
flatpak.info.translateurl=traduci url flatpak.info.translateurl=traduci url
flatpak.info.developername=sviluppatore flatpak.info.developername=sviluppatore
flatpak.info.installation=installazione
flatpak.info.installation.user=utente
flatpak.info.installation.system=sistema
flatpak.install.install_level.title=Tipo di installazione
flatpak.install.install_level.body={} deve essere installato per tutti gli utenti del dispositivo ( sistema ) ?
flatpak.install.bad_install_level.body=Valore non valido per {field} nel file di configurazione {file}
flatpak.remotes.system_flathub.error=Non è stato possibile aggiungere Flathub come repository di sistema ( remote )

View File

@@ -7,6 +7,8 @@ flatpak.info.date=data
flatpak.info.description=descrição flatpak.info.description=descrição
flatpak.info.id=id flatpak.info.id=id
flatpak.info.installation=instalação flatpak.info.installation=instalação
flatpak.info.installation.user=usuário
flatpak.info.installation.system=sistema
flatpak.info.installed=instalado flatpak.info.installed=instalado
flatpak.info.license=licença flatpak.info.license=licença
flatpak.info.name=nome flatpak.info.name=nome
@@ -39,4 +41,8 @@ flatpak.info.homepageurl=página
flatpak.info.instoresincedate=adicionado em flatpak.info.instoresincedate=adicionado em
flatpak.info.projectlicense=licença flatpak.info.projectlicense=licença
flatpak.info.translateurl=tradução flatpak.info.translateurl=tradução
flatpak.info.developername=desenvolvedor flatpak.info.developername=desenvolvedor
flatpak.install.install_level.title=Tipo de instalação
flatpak.install.install_level.body={} deve ser instalado para todos os usuários desse dispositivo ( sistema ) ?
flatpak.install.bad_install_level.body=Valor inválido para {field} no arquivo de configuração {file}
flatpak.remotes.system_flathub.error=Não foi possível adicionar o Flathub como um repositório do sistema ( remote )

View File

@@ -97,7 +97,7 @@ class WebApplication(SoftwarePackage):
self.set_custom_icon(self.custom_icon) self.set_custom_icon(self.custom_icon)
def can_be_run(self) -> bool: def can_be_run(self) -> bool:
return self.installed and self.installation_dir return self.installed and bool(self.installation_dir)
def is_trustable(self) -> bool: def is_trustable(self) -> bool:
return False return False

View File

@@ -333,7 +333,7 @@ class AppsTable(QTableWidget):
if pkg.model.name: if pkg.model.name:
name = pkg.model.name name = pkg.model.name
item.setToolTip('{}: {}'.format(self.i18n['app.name'].lower(), name)) item.setToolTip('{}: {}'.format(self.i18n['app.name'].lower(), pkg.model.get_name_tooltip()))
else: else:
name = '...' name = '...'
item.setToolTip(self.i18n['app.name'].lower()) item.setToolTip(self.i18n['app.name'].lower())
@@ -428,31 +428,36 @@ class AppsTable(QTableWidget):
item = QToolBar() item = QToolBar()
item.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) item.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
if pkg.model.can_be_run(): if pkg.model.installed:
def run(): def run():
self.window.run_app(pkg) self.window.run_app(pkg)
item.addWidget(IconButton(QIcon(resource.get_path('img/app_play.svg')), action=run, background='#088A08', tooltip=self.i18n['action.run.tooltip'])) bt = IconButton(QIcon(resource.get_path('img/app_play.svg')), i18n=self.i18n, action=run, background='#088A08', tooltip=self.i18n['action.run.tooltip'])
bt.setEnabled(pkg.model.can_be_run())
item.addWidget(bt)
if pkg.model.has_info(): def get_info():
self.window.get_app_info(pkg)
def get_info(): bt = IconButton(QIcon(resource.get_path('img/app_info.svg')), i18n=self.i18n, action=get_info, background='#2E68D3', tooltip=self.i18n['action.info.tooltip'])
self.window.get_app_info(pkg) bt.setEnabled(bool(pkg.model.has_info()))
item.addWidget(bt)
item.addWidget(IconButton(QIcon(resource.get_path('img/app_info.svg')), action=get_info, background='#2E68D3', tooltip=self.i18n['action.info.tooltip'])) if not pkg.model.installed:
if pkg.model.has_screenshots():
def get_screenshots(): def get_screenshots():
self.window.get_screenshots(pkg) self.window.get_screenshots(pkg)
item.addWidget(IconButton(QIcon(resource.get_path('img/camera.svg')), action=get_screenshots, background='purple', tooltip=self.i18n['action.screenshots.tooltip'])) bt = IconButton(QIcon(resource.get_path('img/camera.svg')), i18n=self.i18n, action=get_screenshots, background='purple', tooltip=self.i18n['action.screenshots.tooltip'])
bt.setEnabled(bool(pkg.model.has_screenshots()))
item.addWidget(bt)
def handle_click(): def handle_click():
self.show_pkg_settings(pkg) self.show_pkg_settings(pkg)
if self.has_any_settings(pkg): settings = self.has_any_settings(pkg)
bt = IconButton(QIcon(resource.get_path('img/app_settings.svg')), action=handle_click, background='#12ABAB', tooltip=self.i18n['action.settings.tooltip']) if pkg.model.installed:
bt = IconButton(QIcon(resource.get_path('img/app_settings.svg')), i18n=self.i18n, action=handle_click, background='#12ABAB', tooltip=self.i18n['action.settings.tooltip'])
bt.setEnabled(bool(settings))
item.addWidget(bt) item.addWidget(bt)
self.setCellWidget(pkg.table_index, col, item) self.setCellWidget(pkg.table_index, col, item)

View File

@@ -241,14 +241,20 @@ class InputFilter(QLineEdit):
class IconButton(QWidget): class IconButton(QWidget):
def __init__(self, icon: QIcon, action, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None): def __init__(self, icon: QIcon, action, i18n: I18n, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None):
super(IconButton, self).__init__() super(IconButton, self).__init__()
self.bt = QToolButton() self.bt = QToolButton()
self.bt.setIcon(icon) self.bt.setIcon(icon)
self.bt.clicked.connect(action) self.bt.clicked.connect(action)
self.i18n = i18n
self.default_tootip = tooltip
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.bt.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
if background: if background:
self.bt.setStyleSheet('QToolButton { color: white; background: ' + background + '}') style = 'QToolButton { color: white; background: ' + background + '} '
style += 'QToolButton:disabled { color: white; background: grey }'
self.bt.setStyleSheet(style)
if tooltip: if tooltip:
self.bt.setToolTip(tooltip) self.bt.setToolTip(tooltip)
@@ -259,6 +265,14 @@ class IconButton(QWidget):
layout.addWidget(self.bt) layout.addWidget(self.bt)
self.setLayout(layout) self.setLayout(layout)
def setEnabled(self, enabled):
super(IconButton, self).setEnabled(enabled)
if not enabled:
self.bt.setToolTip(self.i18n['icon_button.tooltip.disabled'])
else:
self.bt.setToolTip(self.default_tootip)
class FormQt(QGroupBox): class FormQt(QGroupBox):

View File

@@ -53,7 +53,7 @@ class ScreenshotsDialog(QDialog):
self.bottom_bar = QToolBar() self.bottom_bar = QToolBar()
self.bt_back = QPushButton(self.i18n['screenshots.bt_back.label'].capitalize()) self.bt_back = QPushButton(' < ' + self.i18n['screenshots.bt_back.label'].capitalize())
self.bt_back.clicked.connect(self.back) self.bt_back.clicked.connect(self.back)
self.ref_bt_back = self.bottom_bar.addWidget(self.bt_back) self.ref_bt_back = self.bottom_bar.addWidget(self.bt_back)
self.bottom_bar.addWidget(new_spacer(50)) self.bottom_bar.addWidget(new_spacer(50))
@@ -65,7 +65,7 @@ class ScreenshotsDialog(QDialog):
self.ref_progress_bar = self.bottom_bar.addWidget(self.progress_bar) self.ref_progress_bar = self.bottom_bar.addWidget(self.progress_bar)
self.bottom_bar.addWidget(new_spacer(50)) self.bottom_bar.addWidget(new_spacer(50))
self.bt_next = QPushButton(self.i18n['screenshots.bt_next.label'].capitalize()) self.bt_next = QPushButton(self.i18n['screenshots.bt_next.label'].capitalize() + ' > ')
self.bt_next.clicked.connect(self.next) self.bt_next.clicked.connect(self.next)
self.ref_bt_next = self.bottom_bar.addWidget(self.bt_next) self.ref_bt_next = self.bottom_bar.addWidget(self.bt_next)

View File

@@ -1,7 +1,7 @@
import re import re
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Type, Set from typing import List, Type, Set, Tuple
import requests import requests
from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtCore import QThread, pyqtSignal
@@ -28,11 +28,13 @@ class AsyncAction(QThread, ProcessWatcher):
signal_status = pyqtSignal(str) # changes the GUI status message signal_status = pyqtSignal(str) # changes the GUI status message
signal_substatus = pyqtSignal(str) # changes the GUI substatus message signal_substatus = pyqtSignal(str) # changes the GUI substatus message
signal_progress = pyqtSignal(int) signal_progress = pyqtSignal(int)
signal_root_password = pyqtSignal()
def __init__(self): def __init__(self):
super(AsyncAction, self).__init__() super(AsyncAction, self).__init__()
self.wait_confirmation = False self.wait_confirmation = False
self.confirmation_res = None self.confirmation_res = None
self.root_password = None
self.stop = False self.stop = False
def request_confirmation(self, title: str, body: str, components: List[InputViewComponent] = None, confirmation_label: str = None, deny_label: str = None) -> bool: def request_confirmation(self, title: str, body: str, components: List[InputViewComponent] = None, confirmation_label: str = None, deny_label: str = None) -> bool:
@@ -41,10 +43,22 @@ class AsyncAction(QThread, ProcessWatcher):
self.wait_user() self.wait_user()
return self.confirmation_res return self.confirmation_res
def request_root_password(self) -> Tuple[str, bool]:
self.wait_confirmation = True
self.signal_root_password.emit()
self.wait_user()
res = self.root_password
self.root_password = None
return res
def confirm(self, res: bool): def confirm(self, res: bool):
self.confirmation_res = res self.confirmation_res = res
self.wait_confirmation = False self.wait_confirmation = False
def set_root_password(self, password: str, valid: bool):
self.root_password = (password, valid)
self.wait_confirmation = False
def wait_user(self): def wait_user(self):
while self.wait_confirmation: while self.wait_confirmation:
time.sleep(0.01) time.sleep(0.01)

View File

@@ -1,7 +1,5 @@
import logging import logging
import operator
import time import time
from functools import reduce
from pathlib import Path from pathlib import Path
from typing import List, Type, Set from typing import List, Type, Set
@@ -9,7 +7,7 @@ from PyQt5.QtCore import QEvent, Qt, QSize, pyqtSignal
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QMenu, QAction, QApplication, QListView, \ QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QMenu, QAction, QApplication, QListView, \
QSizePolicy, QMainWindow QSizePolicy
from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.context import ApplicationContext
@@ -17,9 +15,10 @@ 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.api.http import HttpClient
from bauh.commons import user
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.view.core.controller import GenericSoftwareManager from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.qt import dialog, commons, qt_utils from bauh.view.qt import dialog, commons, qt_utils, root
from bauh.view.qt.about import AboutDialog from bauh.view.qt.about import AboutDialog
from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton
from bauh.view.qt.components import new_spacer, InputFilter, IconButton from bauh.view.qt.components import new_spacer, InputFilter, IconButton
@@ -27,7 +26,7 @@ from bauh.view.qt.confirmation import ConfirmationDialog
from bauh.view.qt.gem_selector import GemSelectorPanel from bauh.view.qt.gem_selector import GemSelectorPanel
from bauh.view.qt.history import HistoryDialog from bauh.view.qt.history import HistoryDialog
from bauh.view.qt.info import InfoDialog from bauh.view.qt.info import InfoDialog
from bauh.view.qt.root import is_root, ask_root_password from bauh.view.qt.root import ask_root_password
from bauh.view.qt.screenshots import ScreenshotsDialog from bauh.view.qt.screenshots import ScreenshotsDialog
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, \
@@ -49,6 +48,7 @@ class ManageWindow(QWidget):
__BASE_HEIGHT__ = 400 __BASE_HEIGHT__ = 400
signal_user_res = pyqtSignal(bool) signal_user_res = pyqtSignal(bool)
signal_root_password = pyqtSignal(str, bool)
signal_table_update = pyqtSignal() signal_table_update = pyqtSignal()
def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: dict, def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: dict,
@@ -324,6 +324,7 @@ class ManageWindow(QWidget):
bt_settings = IconButton(QIcon(resource.get_path('img/app_settings.svg')), bt_settings = IconButton(QIcon(resource.get_path('img/app_settings.svg')),
action=self._show_settings_menu, action=self._show_settings_menu,
background='#12ABAB', background='#12ABAB',
i18n=self.i18n,
tooltip=self.i18n['manage_window.bt_settings.tooltip']) tooltip=self.i18n['manage_window.bt_settings.tooltip'])
self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings) self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings)
@@ -395,8 +396,10 @@ class ManageWindow(QWidget):
action.signal_status.connect(self._change_label_status) action.signal_status.connect(self._change_label_status)
action.signal_substatus.connect(self._change_label_substatus) action.signal_substatus.connect(self._change_label_substatus)
action.signal_progress.connect(self._update_process_progress) action.signal_progress.connect(self._update_process_progress)
action.signal_root_password.connect(self._ask_root_password)
self.signal_user_res.connect(action.confirm) self.signal_user_res.connect(action.confirm)
self.signal_root_password.connect(action.set_root_password)
return action return action
@@ -413,6 +416,12 @@ class ManageWindow(QWidget):
self.thread_animate_progress.animate() self.thread_animate_progress.animate()
self.signal_user_res.emit(res) self.signal_user_res.emit(res)
def _ask_root_password(self):
self.thread_animate_progress.pause()
password, valid = root.ask_root_password(self.i18n)
self.thread_animate_progress.animate()
self.signal_root_password.emit(password, valid)
def _show_message(self, msg: dict): def _show_message(self, msg: dict):
self.thread_animate_progress.pause() self.thread_animate_progress.pause()
dialog.show_message(title=msg['title'], body=msg['body'], type_=msg['type']) dialog.show_message(title=msg['title'], body=msg['body'], type_=msg['type'])
@@ -548,7 +557,7 @@ class ManageWindow(QWidget):
pwd = None pwd = None
requires_root = self.manager.requires_root('uninstall', app.model) requires_root = self.manager.requires_root('uninstall', app.model)
if not is_root() and requires_root: if not user.is_root() and requires_root:
pwd, ok = ask_root_password(self.i18n) pwd, ok = ask_root_password(self.i18n)
if not ok: if not ok:
@@ -838,13 +847,12 @@ class ManageWindow(QWidget):
new_width = max(table_width, toolbar_width, topbar_width) new_width = max(table_width, toolbar_width, topbar_width)
if self.bt_upgrade.isVisible(): if self.bt_upgrade.isVisible():
new_width *= 1.03 # this extra size is not because of the toolbar button, but the table upgrade buttons new_width *= 1.05 # this extra size is not because of the toolbar button, but the table upgrade buttons
if (self.pkgs and accept_lower_width) or new_width > self.width(): if (self.pkgs and accept_lower_width) or new_width > self.width():
self.resize(new_width, self.height()) self.resize(new_width, self.height())
if self.first_refresh: qt_utils.centralize(self)
qt_utils.centralize(self)
def update_selected(self): def update_selected(self):
if self.pkgs: if self.pkgs:
@@ -865,7 +873,7 @@ class ManageWindow(QWidget):
widgets=[UpdateToggleButton(None, self, self.i18n, clickable=False)]): widgets=[UpdateToggleButton(None, self, self.i18n, clickable=False)]):
pwd = None pwd = None
if not is_root() and requires_root: if not user.is_root() and requires_root:
pwd, ok = ask_root_password(self.i18n) pwd, ok = ask_root_password(self.i18n)
if not ok: if not ok:
@@ -983,7 +991,7 @@ class ManageWindow(QWidget):
pwd = None pwd = None
requires_root = self.manager.requires_root('downgrade', pkgv.model) requires_root = self.manager.requires_root('downgrade', pkgv.model)
if not is_root() and requires_root: if not user.is_root() and requires_root:
pwd, ok = ask_root_password(self.i18n) pwd, ok = ask_root_password(self.i18n)
if not ok: if not ok:
@@ -1076,7 +1084,7 @@ class ManageWindow(QWidget):
pwd = None pwd = None
requires_root = self.manager.requires_root('install', pkg.model) requires_root = self.manager.requires_root('install', pkg.model)
if not is_root() and requires_root: if not user.is_root() and requires_root:
pwd, ok = ask_root_password(self.i18n) pwd, ok = ask_root_password(self.i18n)
if not ok: if not ok:
@@ -1131,7 +1139,7 @@ class ManageWindow(QWidget):
def execute_custom_action(self, pkg: PackageView, action: PackageAction): def execute_custom_action(self, pkg: PackageView, action: PackageAction):
pwd = None pwd = None
if not is_root() and action.requires_root: if not user.is_root() and action.requires_root:
pwd, ok = ask_root_password(self.i18n) pwd, ok = ask_root_password(self.i18n)
if not ok: if not ok:

View File

@@ -217,4 +217,6 @@ files=fitxers
all_files=tots els fitxers all_files=tots els fitxers
file_chooser.title=Selector de fitxers file_chooser.title=Selector de fitxers
message.file.not_exist=Fitxer no existeix message.file.not_exist=Fitxer no existeix
message.file.not_exist.body=El fitxer {} sembla no existir message.file.not_exist.body=El fitxer {} sembla no existir
icon_button.tooltip.disabled=Aquesta acció no està disponible
user=usuari

View File

@@ -172,4 +172,6 @@ all_files=Alle Dateien
file_chooser.title=Dateiauswahl file_chooser.title=Dateiauswahl
message.file.not_exist=Datei existiert nicht message.file.not_exist=Datei existiert nicht
message.file.not_exist.body=Die Datei {} scheint nicht zu existieren message.file.not_exist.body=Die Datei {} scheint nicht zu existieren
development=Entwicklung development=Entwicklung
icon_button.tooltip.disabled=Diese Aktion ist nicht verfügbar
user=Benutzer

View File

@@ -176,4 +176,6 @@ all_files=all files
file_chooser.title=File selector file_chooser.title=File selector
message.file.not_exist=File does not exist message.file.not_exist=File does not exist
message.file.not_exist.body=The file {} seems not to exist message.file.not_exist.body=The file {} seems not to exist
development=development development=development
icon_button.tooltip.disabled=This action is unavailable
user=user

View File

@@ -216,4 +216,6 @@ files=archivos
all_files=todos los archivos all_files=todos los archivos
file_chooser.title=Selector de archivos file_chooser.title=Selector de archivos
message.file.not_exist=Archivo no existe message.file.not_exist=Archivo no existe
message.file.not_exist.body=El archivo {} parece no existir message.file.not_exist.body=El archivo {} parece no existir
icon_button.tooltip.disabled=This action is unavailable
user=usuario

View File

@@ -173,4 +173,6 @@ all_files=tutti i files
file_chooser.title=Selettore file file_chooser.title=Selettore file
message.file.not_exist=File non esiste message.file.not_exist=File non esiste
message.file.not_exist.body=Il file {} sembra non esistere message.file.not_exist.body=Il file {} sembra non esistere
development=sviluppo development=sviluppo
icon_button.tooltip.disabled=Questa azione non è disponibile
user=utente

View File

@@ -219,4 +219,6 @@ files=arquivos
all_files=todos os arquivos all_files=todos os arquivos
file_chooser.title=Seletor arquivos file_chooser.title=Seletor arquivos
message.file.not_exist=Arquivo não existe message.file.not_exist=Arquivo não existe
message.file.not_exist.body=O arquivo {} parece não existir message.file.not_exist.body=O arquivo {} parece não existir
icon_button.tooltip.disabled=Esta ação está indisponível
user=usuário