From 3ffe2ad96f6d4a0dfef40a01029c079f1f3ddc65 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Tue, 18 Jun 2019 17:01:13 -0300 Subject: [PATCH] Management panel shows update commands streams --- CHANGELOG.md | 4 +++ README.md | 9 +++++-- aur/register_app.py | 27 +++++++------------- fpakman/core/controller.py | 4 +-- fpakman/core/flatpak.py | 13 ++++++++-- fpakman/core/model.py | 16 ++++++++++++ fpakman/core/resource.py | 8 +----- fpakman/core/system.py | 5 ++++ fpakman/view/qt/window.py | 51 +++++++++++++++++++++++++++----------- 9 files changed, 92 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0046e94..78711cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ 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/). +## [0.2.0] - 2019-06-18 +### Features + - Management panel shows update commands streams + ## [0.1.0] - 2019-06-14 ### Features - System tray icon. diff --git a/README.md b/README.md index 817b6a35..f9895877 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,13 @@ It has also a management window allowing the user to see all installed applicati - python-pyqt5 ### Distribution -Currently available via **PyPi** (sudo pip3 install fpakman) or **AUR** (for Arch Linux users. **fpakman-staging** is just for testing, do not install it.) +**PyPi** +``` +sudo pip3 install fpakman +``` + +**AUR** +As **fpakman** package. There is also a staging version (**fpakman-staging**) but is intended for testing and it may not work properly. ### Manual installation: @@ -38,6 +44,5 @@ You can change some application settings via environment variables: - **FPAKMAN_CHECK_INTERVAL**: define the updates check interval in seconds. Default: 60. ### Roadmap -- Show updates being applied - Search and install applications - Uninstall applications diff --git a/aur/register_app.py b/aur/register_app.py index eeaed2e4..bc1dce71 100644 --- a/aur/register_app.py +++ b/aur/register_app.py @@ -1,38 +1,29 @@ # used for AUR installation import os -import shutil import subprocess +import sys from pathlib import Path -share_path = str(Path.home()) + '/.local/share' -local_resources_path = share_path + '/fpakman' - -if os.path.exists(local_resources_path): - shutil.rmtree(local_resources_path) - -os.mkdir(local_resources_path) - desktop_file = """ [Desktop Entry] Type = Application Name = fpakman -Categories=System; +Categories = System; Comment = Manage your Flatpak applications Exec = /usr/bin/fpakman -Icon = {resource_path}/resources/img/flathub_45.svg -""".format(resource_path=local_resources_path) +Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/flathub_45.svg +""".format(version="{}.{}".format(sys.version_info.major, sys.version_info.minor)) -apps_path = share_path + '/applications' +apps_path = '{}/.local/share/applications'.format(str(Path.home())) if not os.path.exists(apps_path): os.mkdir(apps_path) -file_path = apps_path + '/fpakman.desktop' - -if os.path.exists(file_path): - os.remove(file_path) +file_path = '{}/fpakman.desktop'.format(apps_path) with open(file_path, 'w+') as f: f.write(desktop_file) -subprocess.run('desktop-file-install {}'.format(file_path), shell=True) +res = subprocess.run('desktop-file-install {}'.format(file_path), shell=True) + +exit(res.returncode) diff --git a/fpakman/core/controller.py b/fpakman/core/controller.py index 3e43630b..84ed8233 100755 --- a/fpakman/core/controller.py +++ b/fpakman/core/controller.py @@ -11,8 +11,8 @@ class FlatpakController: def refresh(self) -> List[dict]: return self.model.read_installed() - def update(self, package_refs: List[str]) -> List[dict]: - return self.model.update_apps(package_refs) + def update(self, app_ref: str): + return self.model.update_app(app_ref) def check_installed(self) -> bool: version = self.model.get_version() diff --git a/fpakman/core/flatpak.py b/fpakman/core/flatpak.py index 823c146a..b9fe8dfd 100755 --- a/fpakman/core/flatpak.py +++ b/fpakman/core/flatpak.py @@ -68,8 +68,17 @@ def list_installed() -> List[dict]: return [] -def update(ref: str): - return bool(system.run_cmd('flatpak update -y ' + ref)) +def update(app_ref: str) -> bool: + return bool(system.run_cmd('flatpak update -y ' + app_ref)) + + +def update_and_stream(app_ref: str): + """ + Updates the app reference and streams Flatpak output, + :param app_ref: + :return: + """ + return system.stream_cmd(['flatpak', 'update', '-y', app_ref]) def list_updates_as_str(): diff --git a/fpakman/core/model.py b/fpakman/core/model.py index 115976c6..8346a4ad 100755 --- a/fpakman/core/model.py +++ b/fpakman/core/model.py @@ -90,3 +90,19 @@ class FlatpakManager: return [*self.apps] return [] + + def update_app(self, ref: str): + + """ + :param ref: + :return: the update command stream + """ + + if self.apps: + + package_found = [app for app in self.apps if app['ref'] == ref] + + if package_found: + return flatpak.update_and_stream(ref) + + return None diff --git a/fpakman/core/resource.py b/fpakman/core/resource.py index efcb482e..e9378c6d 100755 --- a/fpakman/core/resource.py +++ b/fpakman/core/resource.py @@ -1,13 +1,7 @@ import os -from pathlib import Path -local_resource_path = '{}/.local/share/fpakman/resources'.format(str(Path.home())) app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def get_path(resource_path): - - if os.path.exists(local_resource_path): - return local_resource_path + '/' + resource_path - else: - return app_dir + '/resources/' + resource_path + return app_dir + '/resources/' + resource_path diff --git a/fpakman/core/system.py b/fpakman/core/system.py index dcd9a138..ddd5edfa 100644 --- a/fpakman/core/system.py +++ b/fpakman/core/system.py @@ -1,6 +1,11 @@ import subprocess +from typing import List def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False) -> str: res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE) return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None + + +def stream_cmd(cmd: List[str]): + return subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout diff --git a/fpakman/view/qt/window.py b/fpakman/view/qt/window.py index 2c3a1dc4..f197c9b9 100755 --- a/fpakman/view/qt/window.py +++ b/fpakman/view/qt/window.py @@ -8,7 +8,7 @@ from PyQt5.QtGui import QIcon, QColor, QPixmap, QWindowStateChangeEvent from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QTableWidget, \ QTableWidgetItem, QTableView, QCheckBox, QHeaderView, QToolButton, QToolBar, \ - QSizePolicy, QLabel, QMessageBox + QSizePolicy, QLabel, QMessageBox, QPlainTextEdit from fpakman.core import __version__ from fpakman.core import resource @@ -94,7 +94,7 @@ class ManageWindow(QWidget): self.bt_refresh = QToolButton() self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg'))) - self.bt_refresh.clicked.connect(self.refresh) + self.bt_refresh.clicked.connect(lambda: self.refresh(clear_output=True)) toolbar.addWidget(self.bt_refresh) self.bt_update = QToolButton() @@ -118,8 +118,16 @@ class ManageWindow(QWidget): self.layout.addWidget(self.table_apps) + self.textarea_output = QPlainTextEdit(self) + self.textarea_output.resize(self.table_apps.size()) + self.textarea_output.setStyleSheet("background: black; color: white;") + self.layout.addWidget(self.textarea_output) + self.textarea_output.setVisible(False) + self.textarea_output.setReadOnly(True) + self.thread_update = UpdateSelectedApps(self.controller) - self.thread_update.signal.connect(self._finish_update_selected) + 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.signal.connect(self._finish_refresh) @@ -202,11 +210,16 @@ class ManageWindow(QWidget): self.thread_lock.release() - def refresh(self): + def refresh(self, clear_output: bool = True): if self._acquire_lock(): self._check_flatpak_installed() self._begin_action(self.locale_keys['manage_window.status.refreshing'] + '...') + + if clear_output: + self.textarea_output.clear() + self.textarea_output.hide() + self.thread_refresh.start() def _finish_refresh(self): @@ -248,6 +261,8 @@ class ManageWindow(QWidget): self.bt_update.setEnabled(enable_bt_update) + self.tray_icon.notify_updates(updates) + def centralize(self): geo = self.frameGeometry() screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos()) @@ -338,21 +353,24 @@ class ManageWindow(QWidget): if self._acquire_lock(): if self.apps: + to_update = [pak['model']['ref'] for pak in self.apps if pak['visible'] and pak['update_checked']] if to_update: + self.textarea_output.clear() + self.textarea_output.setVisible(True) + self._begin_action(self.locale_keys['manage_window.status.updating'] + '...') self.thread_update.refs_to_update = to_update self.thread_update.start() def _finish_update_selected(self): - self.update_apps(self.thread_update.updated_apps) self.finish_action() - - if self.tray_icon and self.thread_update.updated_apps: - self.tray_icon.notify_updates(len([app for app in self.thread_update.updated_apps if app['update']])) - self._release_lock() + self.refresh(clear_output=False) + + def _update_action_output(self, output: str): + self.textarea_output.appendPlainText(output) def _begin_action(self, action_label: str): self.label_status.setText(action_label) @@ -369,20 +387,25 @@ class ManageWindow(QWidget): # Threaded actions - class UpdateSelectedApps(QThread): - signal = pyqtSignal() + signal_finished = pyqtSignal() + signal_output = pyqtSignal(str) def __init__(self, controller: FlatpakController): super(UpdateSelectedApps, self).__init__() self.controller = controller self.refs_to_update = [] - self.updated_apps = None def run(self): - self.updated_apps = self.controller.update(self.refs_to_update) - self.signal.emit() + + 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):