Downgrade

This commit is contained in:
Vinícius Moreira
2019-06-25 18:25:04 -03:00
committed by GitHub
parent 7d106c149b
commit f9f47d1429
15 changed files with 349 additions and 32 deletions

View File

@@ -6,13 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.3.0]
### Features
- Allows app uninstall by right clicking it.
- Allows app uninstall and downgrade by right clicking it.
### Improvements
- Adding tooltips to toolbar buttons
- "Update ?" column renamed to "Upgrade ?"
- Management panel title renamed
- Showing runtime apps when no app is available
- Allowing to specify a custom app translation with the environment variable FPAKMAN_LOCALE
## [0.2.1] - 2019-06-24
### Features

View File

@@ -42,6 +42,7 @@ In order to autostart the application, use your Desktop Environment settings to
You can change some application settings via environment variables:
- **FPAKMAN_UPDATE_NOTIFICATION**: enable or disable system updates notifications. Use **0** (disable) or **1** (enable, default).
- **FPAKMAN_CHECK_INTERVAL**: define the updates check interval in seconds. Default: 60.
- **FPAKMAN_LOCALE**: define a custom app translation for a given locale key (e.g: 'pt', 'en', 'es', ...). Default: system locale.
### Roadmap
- Search and install applications

View File

@@ -16,7 +16,7 @@ parser = argparse.ArgumentParser(prog='fpakman', description="GUI for Flatpak ap
parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(__version__))
args = parser.parse_args()
locale_keys = util.get_locale_keys()
locale_keys = util.get_locale_keys(os.getenv('FPAKMAN_LOCALE', None))
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(resource.get_path('img/flathub_45.svg')))
@@ -26,6 +26,7 @@ controller = FlatpakController(manager)
trayIcon = TrayIcon(locale_keys=locale_keys,
controller=controller,
manager=manager,
check_interval=int(os.getenv('FPAKMAN_CHECK_INTERVAL', 60)))
trayIcon.show()

View File

@@ -1,4 +1,5 @@
import re
import subprocess
from typing import List
from fpakman.core import system
@@ -32,20 +33,33 @@ def app_str_to_json(line: str, version: str) -> dict:
else:
raise Exception('Unsupported version')
info = re.findall(r'\w+:\s.+', get_app_info(app['id'], app['branch']))
fields_to_get = ['origin', 'arch', 'ref']
extra_fields = get_app_info_fields(app['id'], app['branch'], ['origin', 'arch', 'ref', 'commit'], check_runtime=True)
app.update(extra_fields)
return app
def get_app_info_fields(app_id: str, branch: str, fields: List[str], check_runtime: bool = False):
info = re.findall(r'\w+:\s.+', get_app_info(app_id, branch))
data = {}
fields_to_retrieve = len(fields) + (1 if check_runtime and 'ref' not in fields else 0)
for field in info:
if fields_to_retrieve == 0:
break
field_val = field.split(':')
field_name = field_val[0].lower()
if field_name in fields_to_get:
app[field_name] = field_val[1].strip()
if field_name in fields or (check_runtime and field_name == 'ref'):
data[field_name] = field_val[1].strip()
fields_to_retrieve -= 1
if field_name == 'ref':
app['runtime'] = app['ref'].startswith('runtime/')
if check_runtime and field_name == 'ref':
data['runtime'] = data['ref'].startswith('runtime/')
return app
return data
def get_version():
@@ -92,3 +106,20 @@ def uninstall_and_stream(app_ref: str):
def list_updates_as_str():
return system.run_cmd('flatpak update', ignore_return_code=True)
def downgrade_and_stream(app_ref: str, commit: str, root_password: str):
pwdin, downgrade_cmd = None, []
if root_password is not None:
downgrade_cmd.extend(['sudo', '-S'])
pwdin = subprocess.Popen(['echo', root_password], stdout=subprocess.PIPE).stdout
downgrade_cmd.extend(['flatpak', 'update', '--commit={}'.format(commit), app_ref, '-y'])
return subprocess.Popen(downgrade_cmd, stdin=pwdin, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout
def get_app_commits(app_ref: str, origin: str) -> List[str]:
log = system.run_cmd('flatpak remote-info --log {} {}'.format(origin, app_ref))
return re.findall(r'Commit+:\s(.+)', log)

View File

@@ -2,9 +2,6 @@ from threading import Lock, Thread
from typing import List
import requests
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from fpakman.core import flatpak
@@ -12,6 +9,10 @@ __FLATHUB_URL__ = 'https://flathub.org'
__FLATHUB_API_URL__ = __FLATHUB_URL__ + '/api/v1'
class ImpossibleDowngradeException(Exception):
pass
class FlatpakManager:
def __init__(self):
@@ -122,3 +123,20 @@ class FlatpakManager:
if app_found:
return flatpak.uninstall_and_stream(ref)
def downgrade_app(self, ref: str, root_password: str):
if self.apps:
app_found = self._find_by_ref(ref)
if app_found:
commits = flatpak.get_app_commits(app_found['ref'], app_found['origin'])
commit_idx = commits.index(app_found['commit'])
# downgrade is not possible if the app current commit in the first one:
if commit_idx == len(commits) - 1:
raise ImpossibleDowngradeException()
return flatpak.downgrade_and_stream(app_found['ref'], commits[commit_idx + 1], root_password)

View File

@@ -4,11 +4,15 @@ from fpakman.core import resource
import glob
def get_locale_keys():
def get_locale_keys(key: str = None):
current_locale = locale.getdefaultlocale()
locale_path = None
if key is None:
current_locale = locale.getdefaultlocale()
else:
current_locale = [key]
if current_locale:
current_locale = current_locale[0]

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"
viewBox="0 0 48 48"
enable-background="new 0 0 26 26"
id="svg8"
sodipodi:docname="downgrade.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
width="48"
height="48">
<metadata
id="metadata14">
<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="defs12" />
<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="namedview10"
showgrid="false"
showborder="false"
inkscape:zoom="9.0769231"
inkscape:cx="-0.33050843"
inkscape:cy="20.813559"
inkscape:window-x="0"
inkscape:window-y="260"
inkscape:window-maximized="1"
inkscape:current-layer="svg8"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g6"
transform="matrix(1.8461538,0,0,2,0,-2)"
style="fill:#ff0000">
<path
d="m 25,17 h -2 c -0.6,0 -1,0.4 -1,1 v 2.5 C 22,20.8 21.8,21 21.5,21 H 4.5 C 4.2,21 4,20.8 4,20.5 V 18 C 4,17.4 3.6,17 3,17 H 1 c -0.6,0 -1,0.4 -1,1 v 6 c 0,0.6 0.4,1 1,1 h 24 c 0.6,0 1,-0.4 1,-1 v -6 c 0,-0.6 -0.4,-1 -1,-1 z"
id="path2"
inkscape:connector-curvature="0"
style="fill:#ff0000" />
<path
d="m 12.3,16.7 c 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 l 6,-6 C 19.9,10.5 20,10.3 20,10 20,9.7 19.9,9.5 19.7,9.3 L 18.3,7.9 C 18.1,7.7 17.9,7.6 17.6,7.6 c -0.3,0 -0.5,0.1 -0.7,0.3 l -1,1 C 15.6,9.2 15,9 15,8.5 V 2 C 15,1.4 14.6,1 14,1 H 12 C 11.4,1 11,1.4 11,2 V 8.6 C 11,9 10.5,9.3 10.1,9 L 9.1,8 C 8.9,7.8 8.7,7.7 8.4,7.7 8.1,7.7 7.9,7.8 7.7,8 L 6.3,9.4 C 6.1,9.6 6,9.8 6,10.1 c 0,0.3 0.1,0.5 0.3,0.7 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#ff0000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -10,15 +10,27 @@ manage_window.columns.update=Upgrade ?
manage_window.apps_table.row.actions.uninstall=Uninstall
manage_window.apps_table.row.actions.uninstall.popup.title=Uninstall
manage_window.apps_table.row.actions.uninstall.popup.body=Remove {} ?
manage_window.apps_table.row.actions.downgrade=Downgrade
manage_window.apps_table.row.actions.downgrade.popup.body=Do you really want to downgrade {} ?
manage_window.checkbox.only_apps=Apps
manage_window.label.updates=Updates
manage_window.status.refreshing=Refreshing
manage_window.status.upgrading=Upgrading
manage_window.status.uninstalling=Uninstalling
manage_window.status.downgrading=Downgrading
manage_window.bt.refresh.tooltip=Reload the data about installed applications
manage_window.bt.upgrade.tooltip=Upgrade all selected applications
popup.flatpak_not_installed.title=Error
popup.flatpak_not_installed.msg=flatpak seems not to be installed. Exiting
popup.root.title=Requires root privileges
popup.root.password=Password
popup.root.bad_password.title=Error
popup.root.bad_password.body=Wrong password
popup.downgrade.impossible.title=Impossible to downgrade
popup.downgrade.impossible.body=The app is in its first version
popup.button.yes=Yes
popup.button.no=No
popup.button.cancel=Cancel
tray.action.manage=Manage applications
tray.action.exit=Exit
notification.new_updates=Updates found

View File

@@ -2,7 +2,7 @@ manage_window.title=Administrador de Aplicativos Flatpak
manage_window.columns.name=Nombre
manage_window.columns.version=Versión
manage_window.columns.latest_version=Ultima Versión
manage_window.columns.branch=Branch
manage_window.columns.branch=Rama
manage_window.columns.arch=Arch
manage_window.columns.ref=Ref
manage_window.columns.origin=Origen
@@ -10,15 +10,27 @@ manage_window.columns.update=¿Actualizar?
manage_window.apps_table.row.actions.uninstall=Desinstalar
manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalación
manage_window.apps_table.row.actions.uninstall.popup.body=¿Eliminar {}?
manage_window.apps_table.row.actions.downgrade=Revertir versión
manage_window.apps_table.row.actions.downgrade.popup.body=¿Realmente quieres revertir la versión actual de {}?
manage_window.checkbox.only_apps=Apps
manage_window.label.updates=Actualizaciones
manage_window.status.refreshing=Recargando
manage_window.status.upgrading=Actualizando
manage_window.status.uninstalling=Desinstalando
manage_window.status.downgrading=Revirtiendo
manage_window.bt.refresh.tooltip=Recarga los datos sobre los aplicativos instalados
manage_window.bt.upgrade.tooltip=Actualiza todos los aplicativos seleccionados
popup.flatpak_not_installed.title=Error
popup.flatpak_not_installed.msg=flatpak no parece estar instalado. Saliendo
popup.root.title=Requiere privilegios de root
popup.root.password=Contraseña
popup.root.bad_password.title=Error
popup.root.bad_password.body=Contraseña incorrecta
popup.downgrade.impossible.title=Imposible revertir la versión
popup.downgrade.impossible.body=El aplicativo está en su primera versión
popup.button.yes=Sí
popup.button.no=No
popup.button.cancel=Cancelar
tray.action.manage=Administrar aplicativos
tray.action.exit=Salir
notification.new_updates=Actualizaciones encontradas

View File

@@ -10,15 +10,27 @@ manage_window.columns.update=Atualizar ?
manage_window.apps_table.row.actions.uninstall=Desinstalar
manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalação
manage_window.apps_table.row.actions.uninstall.popup.body=Remover {}?
manage_window.apps_table.row.actions.downgrade=Reverter versão
manage_window.apps_table.row.actions.downgrade.popup.body=Você realmente quer reverter a versão atual de {} ?
manage_window.checkbox.only_apps=Apps
manage_window.label.updates=Atualizações
manage_window.status.refreshing=Recarregando
manage_window.status.upgrading=Atualizando
manage_window.status.uninstalling=Desinstalando
manage_window.status.downgrading=Revertendo
manage_window.bt.refresh.tooltip=Recarrega os dados sobre os aplicativos instalados
manage_window.bt.upgrade.tooltip=Atualiza todos os aplicativos selecionados
popup.flatpak_not_installed.title=Erro
popup.flatpak_not_installed.msg=flatpak não parece estar instalado. Saindo
popup.root.title=Requer privilégios de root
popup.root.password=Senha
popup.root.bad_password.title=Erro
popup.root.bad_password.body=Senha incorreta
popup.downgrade.impossible.title=Impossível reverter a versão
popup.downgrade.impossible.body=O aplicativo está na sua primeira versão
popup.button.yes=Sim
popup.button.no=Não
popup.button.cancel=Cancelar
tray.action.manage=Gerenciar aplicativos
tray.action.exit=Sair
notification.new_updates=Atualizações encontradas

View File

@@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidg
from fpakman.core import resource
from fpakman.core.controller import FlatpakController
from fpakman.view.qt import dialog
class UpdateToggleButton(QToolButton):
@@ -48,6 +49,7 @@ class AppsTable(QTableWidget):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.icon_flathub = QIcon(resource.get_path('img/flathub_45.svg'))
self.icon_uninstall = QIcon(resource.get_path('img/uninstall.svg'))
self.icon_downgrade = QIcon(resource.get_path('img/downgrade.svg'))
self.network_man = QNetworkAccessManager()
self.network_man.finished.connect(self._load_icon)
@@ -60,21 +62,39 @@ class AppsTable(QTableWidget):
action_uninstall = QAction(self.parent.locale_keys["manage_window.apps_table.row.actions.uninstall"])
action_uninstall.setIcon(self.icon_uninstall)
action_uninstall.triggered.connect(self._uninstall_app)
menu_row.addAction(action_uninstall)
app = self._get_selected_app()
if not app['model']['runtime']: # downgrade only allowed for apps
action_downgrade = QAction(self.parent.locale_keys["manage_window.apps_table.row.actions.downgrade"])
action_downgrade.triggered.connect(self._downgrade_app)
action_downgrade.setIcon(self.icon_downgrade)
menu_row.addAction(action_downgrade)
menu_row.adjustSize()
menu_row.popup(QCursor.pos())
menu_row.exec_()
def _uninstall_app(self):
selected_app = self.parent.apps[self.currentRow()]
def _get_selected_app(self):
return self.parent.apps[self.currentRow()]
confirmation = QMessageBox.question(self, self.parent.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.title'],
self.parent.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.body'].format(selected_app['model']['name']),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if confirmation == QMessageBox.Yes:
def _uninstall_app(self):
selected_app = self._get_selected_app()
if dialog.ask_confirmation(title=self.parent.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.title'],
body=self.parent.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.body'].format(selected_app['model']['name']),
locale_keys=self.parent.locale_keys):
self.parent.uninstall_app(selected_app['model']['ref'])
def _downgrade_app(self):
selected_app = self._get_selected_app()
if dialog.ask_confirmation(title=self.parent.locale_keys['manage_window.apps_table.row.actions.downgrade'],
body=self.parent.locale_keys['manage_window.apps_table.row.actions.downgrade.popup.body'].format(selected_app['model']['name']),
locale_keys=self.parent.locale_keys):
self.parent.downgrade_app(selected_app['model']['ref'])
def _load_icon(self, http_response):
icon_url = http_response.url().toString()
pixmap = QPixmap()
@@ -122,7 +142,7 @@ class AppsTable(QTableWidget):
col_release.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(idx, 2, col_release)
if app['model']['update']:
if app['model']['version'] and app['model']['latest_version'] and app['model']['version'] < app['model']['latest_version']:
col_release.setForeground(QColor('orange'))
col_branch = QTableWidgetItem()

31
fpakman/view/qt/dialog.py Normal file
View File

@@ -0,0 +1,31 @@
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QMessageBox
from fpakman.core import resource
def show_error(title: str, body: str, icon: QIcon = QIcon(resource.get_path('img/flathub_45.svg'))):
error_msg = QMessageBox()
error_msg.setIcon(QMessageBox.Critical)
error_msg.setWindowTitle(title)
error_msg.setText(body)
if icon:
error_msg.setWindowIcon(icon)
error_msg.exec_()
def ask_confirmation(title: str, body: str, locale_keys: dict, icon: QIcon = QIcon(resource.get_path('img/flathub_45.svg'))):
dialog_confirmation = QMessageBox()
dialog_confirmation.setIcon(QMessageBox.Critical)
dialog_confirmation.setWindowTitle(title)
dialog_confirmation.setText(body)
bt_yes = dialog_confirmation.addButton(locale_keys['popup.button.yes'], QMessageBox.YesRole)
dialog_confirmation.addButton(locale_keys['popup.button.no'], QMessageBox.NoRole)
if icon:
dialog_confirmation.setWindowIcon(icon)
dialog_confirmation.exec_()
return dialog_confirmation.clickedButton() == bt_yes

39
fpakman/view/qt/root.py Normal file
View File

@@ -0,0 +1,39 @@
import os
import subprocess
from PyQt5.QtWidgets import QInputDialog, QLineEdit
from fpakman.view.qt.dialog import show_error
def is_root():
return os.getuid() == 0
def ask_root_password(locale_keys: dict):
dialog_pwd = QInputDialog()
dialog_pwd.setInputMode(QInputDialog.TextInput)
dialog_pwd.setTextEchoMode(QLineEdit.Password)
dialog_pwd.setWindowTitle(locale_keys['popup.root.title'])
dialog_pwd.setLabelText(locale_keys['popup.root.password'] + ':')
dialog_pwd.setCancelButtonText(locale_keys['popup.button.cancel'])
dialog_pwd.resize(400, 200)
ok = dialog_pwd.exec_()
if ok:
if not validate_password(dialog_pwd.textValue()):
show_error(title=locale_keys['popup.root.bad_password.title'],
body=locale_keys['popup.root.bad_password.body'])
ok = False
return dialog_pwd.textValue(), ok
def validate_password(password: str) -> bool:
f = os.popen('echo {} | sudo -S whoami'.format(password))
res = f.read()
f.close()
return bool(res.strip())

View File

@@ -4,6 +4,7 @@ import time
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
from fpakman.core.model import FlatpakManager
from fpakman.core import resource
from fpakman.core.controller import FlatpakController
@@ -34,10 +35,11 @@ class UpdateCheck(QThread):
class TrayIcon(QSystemTrayIcon):
def __init__(self, locale_keys: dict, controller: FlatpakController, check_interval: int = 60):
def __init__(self, locale_keys: dict, controller: FlatpakController, manager: FlatpakManager, check_interval: int = 60):
super(TrayIcon, self).__init__()
self.locale_keys = locale_keys
self.controller = controller
self.manager = manager
self.icon_default = QIcon(resource.get_path('img/flathub_45.svg'))
self.icon_update = QIcon(resource.get_path('img/update_logo.svg'))
@@ -50,7 +52,7 @@ class TrayIcon(QSystemTrayIcon):
self.action_exit.triggered.connect(lambda: QCoreApplication.exit())
self.setContextMenu(self.menu)
self.manage_window = ManageWindow(locale_keys=self.locale_keys, controller=controller, tray_icon=self)
self.manage_window = ManageWindow(locale_keys=self.locale_keys, controller=controller, manager=self.manager, tray_icon=self)
self.check_thread = UpdateCheck(check_interval=check_interval, controller=self.controller)
self.check_thread.signal.connect(self.notify_updates)
self.check_thread.start()

View File

@@ -7,18 +7,21 @@ from PyQt5.QtCore import QThread, pyqtSignal, QEvent
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QCheckBox, QHeaderView, QToolButton, QToolBar, \
QSizePolicy, QLabel, QMessageBox, QPlainTextEdit
from fpakman.core.model import FlatpakManager, ImpossibleDowngradeException
from fpakman import __version__
from fpakman.core import resource
from fpakman.core.controller import FlatpakController
from fpakman.view.qt import dialog
from fpakman.view.qt.apps_table import AppsTable
from fpakman.view.qt.root import is_root, ask_root_password
class ManageWindow(QWidget):
__BASE_HEIGHT__ = 400
def __init__(self, locale_keys: dict, controller: FlatpakController, tray_icon = None):
def __init__(self, locale_keys: dict, controller: FlatpakController, manager: FlatpakManager, tray_icon = None):
super(ManageWindow, self).__init__()
self.locale_keys = locale_keys
self.column_names = [locale_keys['manage_window.columns.name'],
@@ -30,6 +33,7 @@ class ManageWindow(QWidget):
locale_keys['manage_window.columns.origin'],
locale_keys['manage_window.columns.update']]
self.controller = controller
self.manager = manager
self.tray_icon = tray_icon
self.thread_lock = Lock()
self.working = False # restrict the number of threaded actions
@@ -104,6 +108,10 @@ class ManageWindow(QWidget):
self.thread_uninstall.signal_output.connect(self._update_action_output)
self.thread_uninstall.signal_finished.connect(self._finish_uninstall)
self.thread_downgrade = DowngradeApp(self.manager)
self.thread_downgrade.signal_output.connect(self._update_action_output)
self.thread_downgrade.signal_finished.connect(self._finish_downgrade)
self.toolbar_bottom = QToolBar()
self.label_updates = QLabel('')
self.toolbar_bottom.addWidget(self.label_updates)
@@ -132,12 +140,9 @@ class ManageWindow(QWidget):
def _check_flatpak_installed(self):
if not self.controller.check_installed():
error_msg = QMessageBox()
error_msg.setIcon(QMessageBox.Critical)
error_msg.setWindowTitle(self.locale_keys['popup.flatpak_not_installed.title'])
error_msg.setText(self.locale_keys['popup.flatpak_not_installed.msg'] + '...')
error_msg.setWindowIcon(self.icon_flathub)
error_msg.exec_()
dialog.show_error(title=self.locale_keys['popup.flatpak_not_installed.title'],
body=self.locale_keys['popup.flatpak_not_installed.msg'] + '...',
icon=self.icon_flathub)
exit(1)
if self.label_flatpak:
@@ -199,6 +204,11 @@ class ManageWindow(QWidget):
self._release_lock()
self.refresh(clear_output=False)
def _finish_downgrade(self):
self.finish_action()
self._release_lock()
self.refresh(clear_output=False)
def filter_only_apps(self, only_apps: int):
if self.apps:
@@ -318,6 +328,29 @@ class ManageWindow(QWidget):
self.table_apps.setEnabled(True)
self.label_status.setText('')
def downgrade_app(self, app_ref: str):
self._check_flatpak_installed()
if self._acquire_lock():
pwd = None
if not is_root():
pwd, ok = ask_root_password(self.locale_keys)
if not ok:
self._release_lock()
return
self.textarea_output.clear()
self.textarea_output.setVisible(True)
self._begin_action(self.locale_keys['manage_window.status.downgrading'] + '...')
self.thread_downgrade.app_ref = app_ref
self.thread_downgrade.root_password = pwd
self.thread_downgrade.start()
# Threaded actions
class UpdateSelectedApps(QThread):
@@ -372,3 +405,31 @@ class UninstallApp(QThread):
self.signal_output.emit(line)
self.signal_finished.emit()
class DowngradeApp(QThread):
signal_finished = pyqtSignal()
signal_output = pyqtSignal(str)
def __init__(self, manager: FlatpakManager):
super(DowngradeApp, self).__init__()
self.manager = manager
self.app_ref = None
self.root_password = None
def run(self):
if self.app_ref:
try:
for output in self.manager.downgrade_app(self.app_ref, self.root_password):
line = output.decode().strip()
if line:
self.signal_output.emit(line)
except ImpossibleDowngradeException:
dialog.show_error(title=self.locale_keys['popup.downgrade.impossible.title'],
body=self.locale_keys['popup.downgrade.impossible.body'],
icon=self.icon_flathub)
self.app_ref = None
self.root_password = None
self.signal_finished.emit()