diff --git a/CHANGELOG.md b/CHANGELOG.md index 987cfb0f..e0dfe756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Allows app uninstall and downgrade by right clicking it. ### Improvements +- Performance and memory usage - 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 +- Allowing to specify a custom app translation with the environment variable **FPAKMAN_LOCALE** +- Adding expiration time for cached app data. Default to 1 hour. The environment variable **FPAKMAN_CACHE_EXPIRATION** can change this value. +- Retrieving only the installed applications data from the origin API. ## [0.2.1] - 2019-06-24 ### Features diff --git a/README.md b/README.md index 99b65781..5e4efe8d 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,7 @@ 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. +- **FPAKMAN_CACHE_EXPIRATION**: define a custom expiration time in SECONDS for application data retrieved from the origin API. Default: 3600 (1 hour). ### Roadmap -- Search and install applications -- Uninstall applications -- Downgrade applications \ No newline at end of file +- Search and install applications \ No newline at end of file diff --git a/fpakman/app.py b/fpakman/app.py index 8700fc90..bac75a8a 100755 --- a/fpakman/app.py +++ b/fpakman/app.py @@ -8,8 +8,7 @@ from PyQt5.QtWidgets import QApplication from fpakman import __version__ from fpakman.core import resource from fpakman.core import util -from fpakman.core.controller import FlatpakController -from fpakman.core.model import FlatpakManager +from fpakman.core.controller import FlatpakManager from fpakman.view.qt.systray import TrayIcon parser = argparse.ArgumentParser(prog='fpakman', description="GUI for Flatpak applications management") @@ -20,12 +19,10 @@ 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'))) -manager = FlatpakManager() -manager.load_database_async() -controller = FlatpakController(manager) + +manager = FlatpakManager(cache_expire=int(os.getenv('FPAKMAN_CACHE_EXPIRATION', 60 * 60))) trayIcon = TrayIcon(locale_keys=locale_keys, - controller=controller, manager=manager, check_interval=int(os.getenv('FPAKMAN_CHECK_INTERVAL', 60))) trayIcon.show() diff --git a/fpakman/core/controller.py b/fpakman/core/controller.py index 4a05e3a2..d8ebddbb 100755 --- a/fpakman/core/controller.py +++ b/fpakman/core/controller.py @@ -1,25 +1,100 @@ +from datetime import datetime, timedelta +from threading import Lock, Thread from typing import List -from fpakman.core.model import FlatpakManager +import requests + +from fpakman.core import flatpak + +__FLATHUB_URL__ = 'https://flathub.org' +__FLATHUB_API_URL__ = __FLATHUB_URL__ + '/api/v1' -class FlatpakController: +class FlatpakManager: - def __init__(self, model: FlatpakManager): - self.model = model + def __init__(self, cache_expire: int = 60 * 60): + self.cache_apps = {} + self.cache_expire = cache_expire + self.http_session = requests.Session() + self.lock_db_read = Lock() + self.lock_read = Lock() - def refresh(self) -> List[dict]: - return self.model.read_installed() + # TODO remove if not necessary for future releases + def load_full_database_async(self): + Thread(target=self.load_full_database, daemon=True).start() - def update(self, app_ref: str): - return self.model.update_app(app_ref) + # TODO remove if not necessary for future releases + def load_full_database(self): - def check_installed(self) -> bool: - version = self.model.get_version() - return False if version is None else version + self.lock_db_read.acquire() - def get_version(self) -> str: - return self.model.get_version() + try: + res = self.http_session.get(__FLATHUB_API_URL__ + '/apps') - def uninstall(self, app_ref: str): - return self.model.remove_app(app_ref) + if res.status_code == 200: + for app in res.json(): + self.cache_apps[app['flatpakAppId']] = app + finally: + self.lock_db_read.release() + + def _request_app_data(self, app_id: str): + + try: + res = self.http_session.get('{}/apps/{}'.format(__FLATHUB_API_URL__, app_id), timeout=60) + + if res.status_code == 200: + return res.json() + else: + print("Could not retrieve app data for id '{}'. Server response: {}".format(app_id, res.status_code)) + except: + print("Could not retrieve app data for id '{}'. Timeout".format(app_id)) + return None + + def read_installed(self) -> List[dict]: + + self.lock_read.acquire() + + try: + installed = flatpak.list_installed() + + if installed: + installed.sort(key=lambda p: p['name'].lower()) + + available_updates = flatpak.list_updates_as_str() + + for app in installed: + + app_data = self.cache_apps.get(app['id']) + + if (not app['runtime'] and not app_data) or (app_data and app_data['expires_at'] <= datetime.utcnow()): # if data is not cached or expired, tries to retrieve it + app_data = self._request_app_data(app['id']) + app_data['expires_at'] = datetime.utcnow() + timedelta(seconds=self.cache_expire) + self.cache_apps[app['id']] = app_data + + if not app_data: + app['latest_version'] = None + app['icon'] = None + else: + app['latest_version'] = app_data['currentReleaseVersion'] + app['icon'] = app_data['iconMobileUrl'] + + if app['icon'].startswith('/'): + app['icon'] = __FLATHUB_URL__ + app['icon'] + + app['update'] = app['id'] in available_updates + + return installed + + finally: + self.lock_read.release() + + def downgrade_app(self, app: dict, root_password: str): + + commits = flatpak.get_app_commits(app['ref'], app['origin']) + commit_idx = commits.index(app['commit']) + + # downgrade is not possible if the app current commit in the first one: + if commit_idx == len(commits) - 1: + return None + + return flatpak.downgrade_and_stream(app['ref'], commits[commit_idx + 1], root_password) diff --git a/fpakman/core/flatpak.py b/fpakman/core/flatpak.py index f8ba10be..2c725a29 100755 --- a/fpakman/core/flatpak.py +++ b/fpakman/core/flatpak.py @@ -66,6 +66,11 @@ def get_app_info_fields(app_id: str, branch: str, fields: List[str] = [], check_ return data +def is_installed(): + version = get_version() + return False if version is None else True + + def get_version(): res = system.run_cmd('{} --version'.format(BASE_CMD)) return res.split(' ')[1].strip() if res else None @@ -86,10 +91,6 @@ def list_installed() -> List[dict]: return [] -def update(app_ref: str) -> bool: - return bool(system.run_cmd('{} update -y {}'.format(BASE_CMD, app_ref))) - - def update_and_stream(app_ref: str): """ Updates the app reference and streams Flatpak output, diff --git a/fpakman/core/model.py b/fpakman/core/model.py deleted file mode 100755 index 7b953806..00000000 --- a/fpakman/core/model.py +++ /dev/null @@ -1,142 +0,0 @@ -from threading import Lock, Thread -from typing import List - -import requests - -from fpakman.core import flatpak - -__FLATHUB_URL__ = 'https://flathub.org' -__FLATHUB_API_URL__ = __FLATHUB_URL__ + '/api/v1' - - -class ImpossibleDowngradeException(Exception): - pass - - -class FlatpakManager: - - def __init__(self): - self.apps = [] - self.apps_db = {} - self.http_session = requests.Session() - self.lock_db_read = Lock() - - def load_database_async(self): - Thread(target=self.load_database, daemon=True).start() - - def load_database(self): - - self.lock_db_read.acquire() - - try: - res = self.http_session.get(__FLATHUB_API_URL__ + '/apps') - - if res.status_code == 200: - for app in res.json(): - self.apps_db[app['flatpakAppId']] = app - finally: - self.lock_db_read.release() - - def get_version(self): - return flatpak.get_version() - - def read_installed(self) -> List[dict]: - - installed = flatpak.list_installed() - - if installed: - installed.sort(key=lambda p: p['name'].lower()) - - available_updates = flatpak.list_updates_as_str() - - for app in installed: - - if not self.apps_db: - self.load_database() - - if self.apps_db: - app_data = self.apps_db.get(app['id'], None) - else: - app_data = None - - if not app_data: - app['latest_version'] = None - app['icon'] = None - else: - app['latest_version'] = app_data['currentReleaseVersion'] - app['icon'] = app_data['iconMobileUrl'] - - if app['icon'].startswith('/'): - app['icon'] = __FLATHUB_URL__ + app['icon'] - - app['update'] = app['id'] in available_updates - - self.apps = installed - return [*self.apps] - - def update_apps(self, refs: List[str]) -> List[dict]: - - if self.apps: - - for ref in refs: - package_found = [app for app in self.apps if app['ref'] == ref] - - if package_found: - package_found = package_found[0] - updated = flatpak.update(ref) - - if updated: - package_found['update'] = not updated - - return [*self.apps] - - return [] - - def _find_by_ref(self, ref: str): - package_found = [app for app in self.apps if app['ref'] == ref] - - if package_found: - return package_found[0] - - def update_app(self, ref: str): - - """ - :param ref: - :return: the update command stream - """ - - if self.apps: - - app_found = self._find_by_ref(ref) - - if app_found: - return flatpak.update_and_stream(ref) - - def remove_app(self, ref: str): - """ - :param ref: - :return: the uninstall command stream - """ - if self.apps: - - app_found = self._find_by_ref(ref) - - 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) diff --git a/fpakman/resources/locale/en b/fpakman/resources/locale/en index b2a4787b..8ef2420d 100644 --- a/fpakman/resources/locale/en +++ b/fpakman/resources/locale/en @@ -24,8 +24,8 @@ 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.downgrade.impossible.title=Error +popup.downgrade.impossible.body=Impossible to downgrade: the app is in its first version popup.history.title=History popup.history.selected.tooltip=Current version popup.button.yes=Yes diff --git a/fpakman/resources/locale/es b/fpakman/resources/locale/es index e4c897fb..58e5a2cc 100644 --- a/fpakman/resources/locale/es +++ b/fpakman/resources/locale/es @@ -24,8 +24,8 @@ 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.downgrade.impossible.title=Error +popup.downgrade.impossible.body=Imposible revertir la versión: el aplicativo está en su primera versión popup.history.title=Historia popup.history.selected.tooltip=Versión actual popup.button.yes=Sí diff --git a/fpakman/resources/locale/pt b/fpakman/resources/locale/pt index 5775be5e..a20cd821 100644 --- a/fpakman/resources/locale/pt +++ b/fpakman/resources/locale/pt @@ -24,8 +24,8 @@ 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.downgrade.impossible.title=Erro +popup.downgrade.impossible.body=Impossível reverter a versão: o aplicativo está na sua primeira versão popup.history.title=Histórico popup.history.selected.tooltip=Versão atual popup.button.yes=Sim diff --git a/fpakman/view/qt/apps_table.py b/fpakman/view/qt/apps_table.py index 00b54437..675cebe3 100644 --- a/fpakman/view/qt/apps_table.py +++ b/fpakman/view/qt/apps_table.py @@ -1,14 +1,12 @@ -import json from typing import List -from PyQt5.QtCore import Qt, QUrl, QThread, pyqtSignal +from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QPixmap, QIcon, QColor, QCursor from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QToolButton, QWidget, \ - QMessageBox, QHeaderView, QDialog, QLineEdit, QVBoxLayout, QPlainTextEdit + QHeaderView -from fpakman.core import resource, flatpak -from fpakman.core.controller import FlatpakController +from fpakman.core import resource from fpakman.view.qt import dialog @@ -36,10 +34,9 @@ class UpdateToggleButton(QToolButton): class AppsTable(QTableWidget): - def __init__(self, parent: QWidget, controller: FlatpakController, columns: List[str]): + def __init__(self, parent: QWidget, columns: List[str]): super(AppsTable, self).__init__() self.parent = parent - self.controller = controller self.columns = columns self.setColumnCount(len(columns)) self.setFocusPolicy(Qt.NoFocus) @@ -105,7 +102,7 @@ class AppsTable(QTableWidget): 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']) + self.parent.downgrade_app(selected_app) def _get_app_info(self): self.parent.get_app_info(self.get_selected_app()) diff --git a/fpakman/view/qt/dialog.py b/fpakman/view/qt/dialog.py index ec9c8210..cc2847be 100644 --- a/fpakman/view/qt/dialog.py +++ b/fpakman/view/qt/dialog.py @@ -12,12 +12,13 @@ def show_error(title: str, body: str, icon: QIcon = QIcon(resource.get_path('img 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.setIcon(QMessageBox.Question) dialog_confirmation.setWindowTitle(title) dialog_confirmation.setText(body) bt_yes = dialog_confirmation.addButton(locale_keys['popup.button.yes'], QMessageBox.YesRole) diff --git a/fpakman/view/qt/root.py b/fpakman/view/qt/root.py index 0d68b3ea..483f04a2 100644 --- a/fpakman/view/qt/root.py +++ b/fpakman/view/qt/root.py @@ -1,3 +1,4 @@ +import io import os import subprocess @@ -32,8 +33,13 @@ def ask_root_password(locale_keys: dict): def validate_password(password: str) -> bool: - f = os.popen('echo {} | sudo -S whoami'.format(password)) - res = f.read() - f.close() + proc = subprocess.Popen('echo {} | sudo -S whoami'.format(password), + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + bufsize=-1) + stream = os._wrap_close(io.TextIOWrapper(proc.stdout), proc) + res = stream.read() + stream.close() return bool(res.strip()) diff --git a/fpakman/view/qt/systray.py b/fpakman/view/qt/systray.py index 00ff02ac..a888772d 100755 --- a/fpakman/view/qt/systray.py +++ b/fpakman/view/qt/systray.py @@ -4,10 +4,9 @@ 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.controller import FlatpakManager from fpakman.core import resource -from fpakman.core.controller import FlatpakController from fpakman.view.qt.window import ManageWindow @@ -15,16 +14,16 @@ class UpdateCheck(QThread): signal = pyqtSignal(int) - def __init__(self, check_interval: int, controller: FlatpakController, parent=None): + def __init__(self, manager: FlatpakManager, check_interval: int, parent=None): super(UpdateCheck, self).__init__(parent) - self.controller = controller self.check_interval = check_interval + self.manager = manager def run(self): while True: - apps = self.controller.refresh() + apps = self.manager.read_installed() updates = len([app for app in apps if app['update']]) @@ -35,10 +34,9 @@ class UpdateCheck(QThread): class TrayIcon(QSystemTrayIcon): - def __init__(self, locale_keys: dict, controller: FlatpakController, manager: FlatpakManager, check_interval: int = 60): + def __init__(self, locale_keys: dict, 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')) @@ -52,8 +50,8 @@ 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, manager=self.manager, tray_icon=self) - self.check_thread = UpdateCheck(check_interval=check_interval, controller=self.controller) + self.manage_window = ManageWindow(locale_keys=self.locale_keys, manager=self.manager, tray_icon=self) + self.check_thread = UpdateCheck(check_interval=check_interval, manager=self.manager) self.check_thread.signal.connect(self.notify_updates) self.check_thread.start() @@ -78,7 +76,9 @@ class TrayIcon(QSystemTrayIcon): def show_manage_window(self): if not self.manage_window: - self.manage_window = ManageWindow(controller=self.controller) + self.manage_window = ManageWindow(locale_keys=self.locale_keys, + manager=self.manager, + tray_icon=self) self.manage_window.refresh() self.manage_window.show() diff --git a/fpakman/view/qt/thread.py b/fpakman/view/qt/thread.py new file mode 100644 index 00000000..c00ccd5c --- /dev/null +++ b/fpakman/view/qt/thread.py @@ -0,0 +1,115 @@ +from PyQt5.QtCore import QThread, pyqtSignal + +from fpakman.core import flatpak +from fpakman.core.controller import FlatpakManager +from fpakman.view.qt import dialog + + +class UpdateSelectedApps(QThread): + + signal_finished = pyqtSignal() + signal_output = pyqtSignal(str) + + def __init__(self): + super(UpdateSelectedApps, self).__init__() + self.refs_to_update = [] + + def run(self): + + for app_ref in self.refs_to_update: + for output in flatpak.update_and_stream(app_ref): + line = output.decode().strip() + if line: + self.signal_output.emit(line) + + self.signal_finished.emit() + + +class RefreshApps(QThread): + + signal = pyqtSignal(list) + + def __init__(self, manager: FlatpakManager): + super(RefreshApps, self).__init__() + self.manager = manager + + def run(self): + self.signal.emit(self.manager.read_installed()) + + +class UninstallApp(QThread): + signal_finished = pyqtSignal() + signal_output = pyqtSignal(str) + + def __init__(self): + super(UninstallApp, self).__init__() + self.app_ref = None + + def run(self): + if self.app_ref: + for output in flatpak.uninstall_and_stream(self.app_ref): + line = output.decode().strip() + if line: + self.signal_output.emit(line) + + self.signal_finished.emit() + + +class DowngradeApp(QThread): + signal_finished = pyqtSignal() + signal_output = pyqtSignal(str) + + def __init__(self, manager: FlatpakManager, locale_keys: dict): + super(DowngradeApp, self).__init__() + self.manager = manager + self.app = None + self.root_password = None + self.locale_keys = locale_keys + + def run(self): + if self.app: + + stream = self.manager.downgrade_app(self.app['model'], self.root_password) + + if stream is None: + dialog.show_error(title=self.locale_keys['popup.downgrade.impossible.title'], + body=self.locale_keys['popup.downgrade.impossible.body']) + else: + for output in self.manager.downgrade_app(self.app['model'], self.root_password): + line = output.decode().strip() + if line: + self.signal_output.emit(line) + + self.app = None + self.root_password = None + self.signal_finished.emit() + + +class GetAppInfo(QThread): + signal_finished = pyqtSignal(dict) + + def __init__(self): + super(GetAppInfo, self).__init__() + self.app = None + + def run(self): + if self.app: + app_info = flatpak.get_app_info_fields(self.app['model']['id'], self.app['model']['branch']) + app_info['name'] = self.app['model']['name'] + app_info['type'] = 'runtime' if self.app['model']['runtime'] else 'app' + self.signal_finished.emit(app_info) + self.app = None + + +class GetAppHistory(QThread): + signal_finished = pyqtSignal(dict) + + def __init__(self): + super(GetAppHistory, self).__init__() + self.app = None + + def run(self): + if self.app: + commits = flatpak.get_app_commits_data(self.app['model']['ref'], self.app['model']['origin']) + self.signal_finished.emit({'model': self.app['model'], 'commits': commits}) + self.app = None diff --git a/fpakman/view/qt/window.py b/fpakman/view/qt/window.py index 137d9606..fe7313c3 100755 --- a/fpakman/view/qt/window.py +++ b/fpakman/view/qt/window.py @@ -1,30 +1,30 @@ -import json import operator from functools import reduce from threading import Lock from typing import List -from PyQt5.QtCore import QThread, pyqtSignal, QEvent, Qt +from PyQt5.QtCore import QEvent from PyQt5.QtGui import QIcon, QWindowStateChangeEvent from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QCheckBox, QHeaderView, QToolButton, QToolBar, \ - QSizePolicy, QLabel, QPlainTextEdit, QDialog, QGroupBox, QFormLayout, QLineEdit, QTableWidget, QTableWidgetItem + QSizePolicy, QLabel, QPlainTextEdit from fpakman import __version__ from fpakman.core import resource, flatpak -from fpakman.core.controller import FlatpakController -from fpakman.core.model import FlatpakManager, ImpossibleDowngradeException +from fpakman.core.controller import FlatpakManager from fpakman.view.qt import dialog from fpakman.view.qt.apps_table import AppsTable from fpakman.view.qt.history import HistoryDialog from fpakman.view.qt.info import InfoDialog from fpakman.view.qt.root import is_root, ask_root_password +from fpakman.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ + GetAppHistory class ManageWindow(QWidget): __BASE_HEIGHT__ = 400 - def __init__(self, locale_keys: dict, controller: FlatpakController, manager: FlatpakManager, tray_icon=None): + def __init__(self, locale_keys: dict, manager: FlatpakManager, tray_icon=None): super(ManageWindow, self).__init__() self.locale_keys = locale_keys self.column_names = [locale_keys[key].capitalize() for key in ['flatpak.info.name', @@ -35,7 +35,6 @@ class ManageWindow(QWidget): 'flatpak.info.ref', 'flatpak.info.origin', 'manage_window.columns.update']] - self.controller = controller self.manager = manager self.tray_icon = tray_icon self.thread_lock = Lock() @@ -88,7 +87,7 @@ class ManageWindow(QWidget): self.layout.addWidget(toolbar) - self.table_apps = AppsTable(self, self.controller, self.column_names) + self.table_apps = AppsTable(self, self.column_names) self.table_apps.change_headers_policy() self.layout.addWidget(self.table_apps) @@ -100,18 +99,18 @@ class ManageWindow(QWidget): self.textarea_output.setVisible(False) self.textarea_output.setReadOnly(True) - self.thread_update = UpdateSelectedApps(self.controller) + self.thread_update = UpdateSelectedApps() self.thread_update.signal_output.connect(self._update_action_output) self.thread_update.signal_finished.connect(self._finish_update_selected) - self.thread_refresh = RefreshApps(self.controller) + self.thread_refresh = RefreshApps(self.manager) self.thread_refresh.signal.connect(self._finish_refresh) - self.thread_uninstall = UninstallApp(self.controller) + self.thread_uninstall = UninstallApp() 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 = DowngradeApp(self.manager, self.locale_keys) self.thread_downgrade.signal_output.connect(self._update_action_output) self.thread_downgrade.signal_finished.connect(self._finish_downgrade) @@ -148,7 +147,7 @@ class ManageWindow(QWidget): def _check_flatpak_installed(self): - if not self.controller.check_installed(): + if not flatpak.is_installed(): 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) @@ -158,7 +157,7 @@ class ManageWindow(QWidget): self.label_flatpak.setText(self._get_flatpak_label()) def _get_flatpak_label(self): - return 'flatpak: ' + self.controller.get_version() + return 'flatpak: ' + flatpak.get_version() def _acquire_lock(self): @@ -191,9 +190,9 @@ class ManageWindow(QWidget): self.thread_refresh.start() - def _finish_refresh(self): + def _finish_refresh(self, apps: List[dict]): - self.update_apps(self.thread_refresh.apps) + self.update_apps(apps) self.finish_action() self._release_lock() @@ -337,7 +336,7 @@ class ManageWindow(QWidget): self.table_apps.setEnabled(True) self.label_status.setText('') - def downgrade_app(self, app_ref: str): + def downgrade_app(self, app: dict): self._check_flatpak_installed() @@ -356,7 +355,7 @@ class ManageWindow(QWidget): 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.app = app self.thread_downgrade.root_password = pwd self.thread_downgrade.start() @@ -391,116 +390,3 @@ class ManageWindow(QWidget): dialog_history = HistoryDialog(app, self.table_apps.get_selected_app_icon(), self.locale_keys) dialog_history.exec_() - - -# Threaded actions -class UpdateSelectedApps(QThread): - - signal_finished = pyqtSignal() - signal_output = pyqtSignal(str) - - def __init__(self, controller: FlatpakController): - super(UpdateSelectedApps, self).__init__() - self.controller = controller - self.refs_to_update = [] - - def run(self): - - for app_ref in self.refs_to_update: - for output in self.controller.update(app_ref): - line = output.decode().strip() - if line: - self.signal_output.emit(line) - - self.signal_finished.emit() - - -class RefreshApps(QThread): - - signal = pyqtSignal() - - def __init__(self, controller: FlatpakController): - super(RefreshApps, self).__init__() - self.controller = controller - self.apps = None - - def run(self): - self.apps = self.controller.refresh() - self.signal.emit() - - -class UninstallApp(QThread): - signal_finished = pyqtSignal() - signal_output = pyqtSignal(str) - - def __init__(self, controller: FlatpakController): - super(UninstallApp, self).__init__() - self.controller = controller - self.app_ref = None - - def run(self): - if self.app_ref: - for output in self.controller.uninstall(self.app_ref): - line = output.decode().strip() - if line: - 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() - - -class GetAppInfo(QThread): - signal_finished = pyqtSignal(dict) - - def __init__(self): - super(GetAppInfo, self).__init__() - self.app = None - - def run(self): - if self.app: - app_info = flatpak.get_app_info_fields(self.app['model']['id'], self.app['model']['branch']) - app_info['name'] = self.app['model']['name'] - app_info['type'] = 'runtime' if self.app['model']['runtime'] else 'app' - self.signal_finished.emit(app_info) - self.app = None - - -class GetAppHistory(QThread): - signal_finished = pyqtSignal(dict) - - def __init__(self): - super(GetAppHistory, self).__init__() - self.app = None - - def run(self): - if self.app: - commits = flatpak.get_app_commits_data(self.app['model']['ref'], self.app['model']['origin']) - self.signal_finished.emit({'model': self.app['model'], 'commits': commits}) - self.app = None