mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-09 10:54:16 +02:00
0.9.0
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
from glob import glob
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtGui import QPixmap, QIcon
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel, QWidget, QHBoxLayout
|
||||
|
||||
from bauh import __version__, __app_name__, ROOT_DIR
|
||||
from bauh.view.util import resource, util
|
||||
from bauh.view.util.translation import I18n
|
||||
from bauh.context import generate_i18n
|
||||
from bauh.view.util import resource
|
||||
|
||||
PROJECT_URL = 'https://github.com/vinifmor/' + __app_name__
|
||||
LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/{}/master/LICENSE'.format(__app_name__)
|
||||
@@ -14,14 +14,15 @@ LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/{}/master/LICENSE'.for
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
|
||||
def __init__(self, i18n: I18n):
|
||||
def __init__(self, app_config: dict):
|
||||
super(AboutDialog, self).__init__()
|
||||
self.setWindowTitle(i18n['tray.action.about'])
|
||||
i18n = generate_i18n(app_config, resource.get_path('locale/about'))
|
||||
self.setWindowTitle('{} ({})'.format(i18n['about.title'].capitalize(), __app_name__))
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
label_logo = QLabel()
|
||||
icon = util.get_default_icon()[1].pixmap(64, 64)
|
||||
icon = QIcon(resource.get_path('img/logo.svg')).pixmap(64, 64)
|
||||
label_logo.setPixmap(icon)
|
||||
label_logo.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_logo)
|
||||
@@ -31,7 +32,7 @@ class AboutDialog(QDialog):
|
||||
label_name.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_name)
|
||||
|
||||
label_version = QLabel(i18n['version'].lower() + ' ' + __version__)
|
||||
label_version = QLabel(i18n['about.version'].lower() + ' ' + __version__)
|
||||
label_version.setStyleSheet('QLabel { font-size: 11px; font-weight: bold }')
|
||||
label_version.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_version)
|
||||
|
||||
@@ -15,7 +15,7 @@ from bauh.api.abstract.model import PackageStatus
|
||||
from bauh.commons.html import strip_html
|
||||
from bauh.view.qt import dialog
|
||||
from bauh.view.qt.components import IconButton
|
||||
from bauh.view.qt.view_model import PackageView, PackageViewStatus
|
||||
from bauh.view.qt.view_model import PackageView
|
||||
from bauh.view.util import resource
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
@@ -28,10 +28,10 @@ PUBLISHER_MAX_SIZE = 25
|
||||
|
||||
class UpdateToggleButton(QWidget):
|
||||
|
||||
def __init__(self, app_view: PackageView, root: QWidget, i18n: I18n, checked: bool = True, clickable: bool = True):
|
||||
def __init__(self, pkg: PackageView, root: QWidget, i18n: I18n, checked: bool = True, clickable: bool = True):
|
||||
super(UpdateToggleButton, self).__init__()
|
||||
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
||||
self.app_view = app_view
|
||||
self.app_view = pkg
|
||||
self.root = root
|
||||
|
||||
layout = QHBoxLayout()
|
||||
@@ -40,22 +40,41 @@ class UpdateToggleButton(QWidget):
|
||||
self.setLayout(layout)
|
||||
|
||||
self.bt = QToolButton()
|
||||
self.bt.setCheckable(clickable)
|
||||
self.bt.setCheckable(True)
|
||||
self.bt.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
|
||||
if clickable:
|
||||
self.bt.clicked.connect(self.change_state)
|
||||
|
||||
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
|
||||
self.bt.setStyleSheet('QToolButton { background: #20A435 } ' +
|
||||
('QToolButton:checked { background: gray }' if clickable else ''))
|
||||
layout.addWidget(self.bt)
|
||||
'QToolButton:checked { background: gray } ' +
|
||||
('QToolButton:disabled { background: #d69003 }' if not clickable and not checked else ''))
|
||||
|
||||
self.setToolTip(i18n['manage_window.apps_table.upgrade_toggle.tooltip'])
|
||||
layout.addWidget(self.bt)
|
||||
|
||||
if not checked:
|
||||
self.bt.click()
|
||||
|
||||
if clickable:
|
||||
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
|
||||
self.setToolTip('{} {}'.format(i18n['manage_window.apps_table.upgrade_toggle.tooltip'],
|
||||
i18n['manage_window.apps_table.upgrade_toggle.enabled.tooltip']))
|
||||
else:
|
||||
if not checked:
|
||||
self.bt.setIcon(QIcon(resource.get_path('img/exclamation.svg')))
|
||||
self.bt.setEnabled(False)
|
||||
|
||||
tooltip = i18n['{}.update.disabled.tooltip'.format(pkg.model.gem_name)]
|
||||
|
||||
if tooltip:
|
||||
self.setToolTip(tooltip)
|
||||
else:
|
||||
self.setToolTip('{} {}'.format(i18n['manage_window.apps_table.upgrade_toggle.tooltip'],
|
||||
i18n['manage_window.apps_table.upgrade_toggle.disabled.tooltip']))
|
||||
else:
|
||||
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
|
||||
self.bt.setCheckable(False)
|
||||
|
||||
def change_state(self, not_checked: bool):
|
||||
self.app_view.update_checked = not not_checked
|
||||
self.root.update_bt_upgrade()
|
||||
@@ -65,11 +84,10 @@ class AppsTable(QTableWidget):
|
||||
|
||||
COL_NUMBER = 8
|
||||
|
||||
def __init__(self, parent: QWidget, icon_cache: MemoryCache, disk_cache: bool, download_icons: bool):
|
||||
def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool):
|
||||
super(AppsTable, self).__init__()
|
||||
self.setParent(parent)
|
||||
self.window = parent
|
||||
self.disk_cache = disk_cache
|
||||
self.download_icons = download_icons
|
||||
self.setColumnCount(self.COL_NUMBER)
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
@@ -150,22 +168,13 @@ class AppsTable(QTableWidget):
|
||||
def refresh(self, pkg: PackageView):
|
||||
self._update_row(pkg, update_check_enabled=False, change_update_col=False)
|
||||
|
||||
def fill_async_data(self):
|
||||
if self.window.pkgs:
|
||||
def update_package(self, pkg: PackageView):
|
||||
if self.download_icons and pkg.model.icon_url:
|
||||
icon_request = QNetworkRequest(QUrl(pkg.model.icon_url))
|
||||
icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
self.network_man.get(icon_request)
|
||||
|
||||
for idx, app_v in enumerate(self.window.pkgs):
|
||||
|
||||
if app_v.status == PackageViewStatus.LOADING and app_v.model.status == PackageStatus.READY:
|
||||
|
||||
if self.download_icons:
|
||||
icon_request = QNetworkRequest(QUrl(app_v.model.icon_url))
|
||||
icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
self.network_man.get(icon_request)
|
||||
|
||||
self._update_row(app_v, change_update_col=False)
|
||||
app_v.status = PackageViewStatus.READY
|
||||
|
||||
self.window.resize_and_center()
|
||||
self._update_row(pkg, change_update_col=False)
|
||||
|
||||
def _uninstall_app(self, app_v: PackageView):
|
||||
if dialog.ask_confirmation(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'],
|
||||
@@ -222,18 +231,24 @@ class AppsTable(QTableWidget):
|
||||
col_name = self.item(idx, 0)
|
||||
col_name.setIcon(icon_data['icon'])
|
||||
|
||||
if self.disk_cache and app.model.supports_disk_cache() and app.model.get_disk_icon_path():
|
||||
if app.model.supports_disk_cache() and app.model.get_disk_icon_path():
|
||||
if not icon_was_cached or not os.path.exists(app.model.get_disk_icon_path()):
|
||||
self.window.manager.cache_to_disk(pkg=app.model, icon_bytes=icon_data['bytes'], only_icon=True)
|
||||
|
||||
def update_pkgs(self, pkg_views: List[PackageView], update_check_enabled: bool = True):
|
||||
self.setRowCount(len(pkg_views) if pkg_views else 0)
|
||||
def update_packages(self, pkgs: List[PackageView], update_check_enabled: bool = True):
|
||||
self.setRowCount(len(pkgs) if pkgs else 0)
|
||||
self.setEnabled(True)
|
||||
|
||||
if pkg_views:
|
||||
for idx, pkgv in enumerate(pkg_views):
|
||||
pkgv.table_index = idx
|
||||
self._update_row(pkgv, update_check_enabled)
|
||||
if pkgs:
|
||||
for idx, pkg in enumerate(pkgs):
|
||||
pkg.table_index = idx
|
||||
|
||||
if self.download_icons and pkg.model.status == PackageStatus.READY and pkg.model.icon_url:
|
||||
icon_request = QNetworkRequest(QUrl(pkg.model.icon_url))
|
||||
icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
self.network_man.get(icon_request)
|
||||
|
||||
self._update_row(pkg, update_check_enabled)
|
||||
|
||||
def _update_row(self, pkg: PackageView, update_check_enabled: bool = True, change_update_col: bool = True):
|
||||
self._set_col_name(0, pkg)
|
||||
@@ -250,7 +265,11 @@ class AppsTable(QTableWidget):
|
||||
if update_check_enabled and pkg.model.update:
|
||||
col_update = QToolBar()
|
||||
col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
col_update.addWidget(UpdateToggleButton(pkg, self.window, self.i18n, pkg.update_checked))
|
||||
col_update.addWidget(UpdateToggleButton(pkg=pkg,
|
||||
root=self.window,
|
||||
i18n=self.i18n,
|
||||
checked=pkg.update_checked if pkg.model.can_be_updated() else False,
|
||||
clickable=pkg.model.can_be_updated()))
|
||||
|
||||
self.setCellWidget(pkg.table_index, 7, col_update)
|
||||
|
||||
@@ -301,17 +320,18 @@ class AppsTable(QTableWidget):
|
||||
self.setCellWidget(pkg.table_index, col, toolbar)
|
||||
|
||||
def _set_col_type(self, col: int, pkg: PackageView):
|
||||
icon_data = self.cache_type_icon.get(pkg.model.get_type())
|
||||
|
||||
pixmap = self.cache_type_icon.get(pkg.model.get_type())
|
||||
|
||||
if not pixmap:
|
||||
if icon_data is None:
|
||||
pixmap = QIcon(pkg.model.get_type_icon_path()).pixmap(QSize(16, 16))
|
||||
self.cache_type_icon[pkg.model.get_type()] = pixmap
|
||||
icon_data = {'px': pixmap, 'tip': '{}: {}'.format(self.i18n['type'], pkg.get_type_label())}
|
||||
self.cache_type_icon[pkg.model.get_type()] = icon_data
|
||||
|
||||
item = QLabel()
|
||||
item.setPixmap(pixmap)
|
||||
item.setPixmap(icon_data['px'])
|
||||
item.setAlignment(Qt.AlignCenter)
|
||||
item.setToolTip('{}: {}'.format(self.i18n['type'], pkg.model.get_type().capitalize()))
|
||||
|
||||
item.setToolTip(icon_data['tip'])
|
||||
self.setCellWidget(pkg.table_index, col, item)
|
||||
|
||||
def _set_col_version(self, col: int, pkg: PackageView):
|
||||
@@ -343,8 +363,8 @@ class AppsTable(QTableWidget):
|
||||
item = QTableWidgetItem()
|
||||
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
|
||||
if pkg.model.name:
|
||||
name = pkg.model.name
|
||||
name = pkg.model.get_display_name()
|
||||
if name:
|
||||
item.setToolTip('{}: {}'.format(self.i18n['app.name'].lower(), pkg.model.get_name_tooltip()))
|
||||
else:
|
||||
name = '...'
|
||||
@@ -359,7 +379,7 @@ class AppsTable(QTableWidget):
|
||||
item.setText(name)
|
||||
|
||||
icon_path = pkg.model.get_disk_icon_path()
|
||||
if self.disk_cache and pkg.model.supports_disk_cache() and icon_path and os.path.isfile(icon_path):
|
||||
if pkg.model.supports_disk_cache() and icon_path and os.path.isfile(icon_path):
|
||||
with open(icon_path, 'rb') as f:
|
||||
icon_bytes = f.read()
|
||||
pixmap = QPixmap()
|
||||
|
||||
@@ -15,7 +15,7 @@ def new_pkgs_info() -> dict:
|
||||
|
||||
|
||||
def update_info(pkgv: PackageView, pkgs_info: dict):
|
||||
pkgs_info['available_types'][pkgv.model.get_type()] = pkgv.model.get_type_icon_path()
|
||||
pkgs_info['available_types'][pkgv.model.get_type()] = {'icon': pkgv.model.get_type_icon_path(), 'label': pkgv.get_type_label()}
|
||||
|
||||
if pkgv.model.is_application():
|
||||
pkgs_info['apps_count'] += 1
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QIntValidator
|
||||
from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \
|
||||
QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget, QVBoxLayout, \
|
||||
QSlider
|
||||
QSlider, QScrollArea, QFrame
|
||||
|
||||
from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \
|
||||
TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent, \
|
||||
@@ -25,6 +27,9 @@ class RadioButtonQt(QRadioButton):
|
||||
self.model_parent = model_parent
|
||||
self.toggled.connect(self._set_checked)
|
||||
|
||||
if model.icon_path:
|
||||
self.setIcon(QIcon(model.icon_path))
|
||||
|
||||
if self.model.read_only:
|
||||
self.setAttribute(Qt.WA_TransparentForMouseEvents)
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
@@ -90,7 +95,8 @@ class FormComboBoxQt(QComboBox):
|
||||
self.setMaximumWidth(model.max_width)
|
||||
|
||||
for idx, op in enumerate(self.model.options):
|
||||
self.addItem(op.label, op.value)
|
||||
icon = QIcon(op.icon_path) if op.icon_path else QIcon()
|
||||
self.addItem(icon, op.label, op.value)
|
||||
|
||||
if op.tooltip:
|
||||
self.setItemData(idx, op.tooltip, Qt.ToolTipRole)
|
||||
@@ -178,7 +184,7 @@ class ComboSelectQt(QGroupBox):
|
||||
self.model = model
|
||||
self.setLayout(QGridLayout())
|
||||
self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}')
|
||||
self.layout().addWidget(QLabel(model.label + ' :'), 0, 0)
|
||||
self.layout().addWidget(QLabel(model.label + ' :' if model.label else ''), 0, 0)
|
||||
self.layout().addWidget(FormComboBoxQt(model), 0, 1)
|
||||
|
||||
|
||||
@@ -352,15 +358,27 @@ class InputFilter(QLineEdit):
|
||||
super(InputFilter, self).__init__()
|
||||
self.on_key_press = on_key_press
|
||||
self.last_text = ''
|
||||
self.typing = None
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
super(InputFilter, self).keyPressEvent(event)
|
||||
def notify_text_change(self):
|
||||
time.sleep(2)
|
||||
text = self.text().strip()
|
||||
|
||||
if text != self.last_text:
|
||||
self.last_text = text
|
||||
self.on_key_press()
|
||||
|
||||
self.typing = None
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
super(InputFilter, self).keyPressEvent(event)
|
||||
|
||||
if self.typing:
|
||||
return
|
||||
|
||||
self.typing = Thread(target=self.notify_text_change, daemon=True)
|
||||
self.typing.start()
|
||||
|
||||
def get_text(self):
|
||||
return self.last_text
|
||||
|
||||
@@ -464,11 +482,17 @@ class FormQt(QGroupBox):
|
||||
label_comp = QLabel()
|
||||
label.layout().addWidget(label_comp)
|
||||
|
||||
if hasattr(comp, 'size') and comp.size is not None:
|
||||
label_comp.setStyleSheet("QLabel { font-size: " + str(comp.size) + "px }")
|
||||
|
||||
attr = 'label' if hasattr(comp,'label') else 'value'
|
||||
text = getattr(comp, attr)
|
||||
|
||||
if text:
|
||||
label_comp.setText(text.capitalize())
|
||||
if hasattr(comp, 'capitalize_label') and getattr(comp, 'capitalize_label'):
|
||||
label_comp.setText(text.capitalize())
|
||||
else:
|
||||
label_comp.setText(text)
|
||||
|
||||
if comp.tooltip:
|
||||
label.layout().addWidget(self.gen_tip_icon(comp.tooltip))
|
||||
@@ -499,7 +523,7 @@ class FormQt(QGroupBox):
|
||||
line_edit.setPlaceholderText(c.placeholder)
|
||||
|
||||
if c.value:
|
||||
line_edit.setText(c.value)
|
||||
line_edit.setText(str(c.value) if c.value else '')
|
||||
line_edit.setCursorPosition(0)
|
||||
|
||||
if c.read_only:
|
||||
@@ -604,7 +628,11 @@ class TabGroupQt(QTabWidget):
|
||||
traceback.print_exc()
|
||||
icon = QIcon()
|
||||
|
||||
self.addTab(to_widget(c.content, i18n), icon, c.label)
|
||||
scroll = QScrollArea()
|
||||
scroll.setFrameShape(QFrame.NoFrame)
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(to_widget(c.content, i18n))
|
||||
self.addTab(scroll, icon, c.label)
|
||||
|
||||
|
||||
def new_single_select(model: SingleSelectComponent) -> QWidget:
|
||||
@@ -643,6 +671,10 @@ def to_widget(comp: ViewComponent, i18n: I18n, parent: QWidget = None) -> QWidge
|
||||
return TwoStateButtonQt(comp)
|
||||
elif isinstance(comp, TextComponent):
|
||||
label = QLabel(comp.value)
|
||||
|
||||
if comp.size is not None:
|
||||
label.setStyleSheet("QLabel { font-size: " + str(comp.size) + "px }")
|
||||
|
||||
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
return label
|
||||
elif isinstance(comp, SpacerComponent):
|
||||
|
||||
@@ -12,7 +12,7 @@ from bauh.view.util.translation import I18n
|
||||
class ConfirmationDialog(QMessageBox):
|
||||
|
||||
def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, components: List[ViewComponent] = None,
|
||||
confirmation_label: str = None, deny_label: str = None):
|
||||
confirmation_label: str = None, deny_label: str = None, deny_button: bool = True):
|
||||
super(ConfirmationDialog, self).__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.setStyleSheet('QLabel { margin-right: 25px; }')
|
||||
@@ -20,7 +20,8 @@ class ConfirmationDialog(QMessageBox):
|
||||
self.bt_yes.setStyleSheet(css.OK_BUTTON)
|
||||
self.setDefaultButton(self.bt_yes)
|
||||
|
||||
self.addButton(i18n['popup.button.no'] if not deny_label else deny_label.capitalize(), QMessageBox.NoRole)
|
||||
if deny_button:
|
||||
self.addButton(i18n['popup.button.no'] if not deny_label else deny_label.capitalize(), QMessageBox.NoRole)
|
||||
|
||||
label = None
|
||||
if body:
|
||||
|
||||
@@ -96,7 +96,7 @@ class GemSelectorPanel(QWidget):
|
||||
self.manager.prepare()
|
||||
self.window.verify_warnings()
|
||||
self.window.types_changed = True
|
||||
self.window.refresh_apps()
|
||||
self.window.refresh_packages()
|
||||
self.close()
|
||||
|
||||
def exit(self):
|
||||
|
||||
@@ -7,6 +7,7 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.model import PackageHistory
|
||||
from bauh.view.qt.view_model import get_type_label, PackageView
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
|
||||
@@ -16,7 +17,9 @@ class HistoryDialog(QDialog):
|
||||
super(HistoryDialog, self).__init__()
|
||||
self.setWindowFlags(self.windowFlags() | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint)
|
||||
|
||||
self.setWindowTitle('{} - {} ({})'.format(i18n['popup.history.title'], history.pkg.name, history.pkg.get_type()))
|
||||
view = PackageView(model=history.pkg, i18n=i18n)
|
||||
|
||||
self.setWindowTitle('{} - {}'.format(i18n['popup.history.title'], view))
|
||||
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
@@ -49,7 +49,7 @@ class InfoDialog(QDialog):
|
||||
|
||||
for idx, attr in enumerate(sorted(app.keys())):
|
||||
if attr not in IGNORED_ATTRS and app[attr]:
|
||||
i18n_key = app['__app__'].model.get_type().lower() + '.info.' + attr.lower()
|
||||
i18n_key = app['__app__'].model.gem_name + '.info.' + attr.lower()
|
||||
|
||||
if isinstance(app[attr], list):
|
||||
val = ' '.join([str(e).strip() for e in app[attr] if e])
|
||||
|
||||
311
bauh/view/qt/prepare.py
Normal file
311
bauh/view/qt/prepare.py
Normal file
@@ -0,0 +1,311 @@
|
||||
import datetime
|
||||
import operator
|
||||
import time
|
||||
from functools import reduce
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, QToolBar, \
|
||||
QProgressBar, QApplication
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh.api.abstract.handler import TaskManager
|
||||
from bauh.view.qt import root
|
||||
from bauh.view.qt.components import new_spacer
|
||||
from bauh.view.qt.qt_utils import centralize
|
||||
from bauh.view.qt.thread import AnimateProgress
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
|
||||
class Prepare(QThread, TaskManager):
|
||||
signal_register = pyqtSignal(str, str, object)
|
||||
signal_update = pyqtSignal(str, float, str)
|
||||
signal_finished = pyqtSignal(str)
|
||||
signal_started = pyqtSignal()
|
||||
signal_ask_password = pyqtSignal()
|
||||
|
||||
def __init__(self, context: ApplicationContext, manager: SoftwareManager, i18n: I18n):
|
||||
super(Prepare, self).__init__()
|
||||
self.manager = manager
|
||||
self.i18n = i18n
|
||||
self.context = context
|
||||
self.waiting_password = False
|
||||
self.password_response = None
|
||||
|
||||
def ask_password(self) -> Tuple[str, bool]:
|
||||
self.waiting_password = True
|
||||
self.signal_ask_password.emit()
|
||||
|
||||
while self.waiting_password:
|
||||
time.sleep(0.1) # waiting for user input
|
||||
|
||||
return self.password_response
|
||||
|
||||
def set_password_reply(self, password: str, valid: bool):
|
||||
self.password_response = password, valid
|
||||
self.waiting_password = False
|
||||
|
||||
def run(self):
|
||||
root_pwd = None
|
||||
if self.manager.requires_root('prepare', None):
|
||||
root_pwd, ok = self.ask_password()
|
||||
|
||||
if not ok:
|
||||
QCoreApplication.exit(1)
|
||||
|
||||
self.signal_started.emit()
|
||||
self.manager.prepare(self, root_pwd, None)
|
||||
|
||||
def update_progress(self, task_id: str, progress: float, substatus: str):
|
||||
self.signal_update.emit(task_id, progress, substatus)
|
||||
|
||||
def register_task(self, id_: str, label: str, icon_path: str):
|
||||
self.signal_register.emit(id_, label, icon_path)
|
||||
|
||||
def finish_task(self, task_id: str):
|
||||
self.signal_finished.emit(task_id)
|
||||
|
||||
|
||||
class CheckFinished(QThread):
|
||||
signal_finished = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(CheckFinished, self).__init__()
|
||||
self.total = None
|
||||
self.finished = None
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.total is not None and self.finished is not None:
|
||||
if self.total == self.finished:
|
||||
break
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
self.signal_finished.emit()
|
||||
|
||||
def update(self, total: int, finished: int):
|
||||
if total is not None:
|
||||
self.total = total
|
||||
|
||||
if finished is not None:
|
||||
self.finished = finished
|
||||
|
||||
|
||||
class EnableSkip(QThread):
|
||||
|
||||
signal_timeout = pyqtSignal()
|
||||
|
||||
def run(self):
|
||||
ti = datetime.datetime.now()
|
||||
|
||||
while True:
|
||||
if datetime.datetime.now() >= ti + datetime.timedelta(minutes=1.5):
|
||||
self.signal_timeout.emit()
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
class PreparePanel(QWidget, TaskManager):
|
||||
|
||||
signal_status = pyqtSignal(object, object)
|
||||
signal_password_response = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize, i18n: I18n, manage_window: QWidget):
|
||||
super(PreparePanel, self).__init__()
|
||||
self.i18n = i18n
|
||||
self.context = context
|
||||
self.manage_window = manage_window
|
||||
self.setWindowTitle(' ')
|
||||
self.setMinimumWidth(screen_size.width() * 0.5)
|
||||
self.setMinimumHeight(screen_size.height() * 0.35)
|
||||
self.setMaximumHeight(screen_size.height() * 0.95)
|
||||
self.setLayout(QVBoxLayout())
|
||||
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.manager = manager
|
||||
self.tasks = {}
|
||||
self.ntasks = 0
|
||||
self.ftasks = 0
|
||||
self.self_close = False
|
||||
|
||||
self.prepare_thread = Prepare(self.context, manager, self.i18n)
|
||||
self.prepare_thread.signal_register.connect(self.register_task)
|
||||
self.prepare_thread.signal_update.connect(self.update_progress)
|
||||
self.prepare_thread.signal_finished.connect(self.finish_task)
|
||||
self.prepare_thread.signal_started.connect(self.start)
|
||||
self.prepare_thread.signal_ask_password.connect(self.ask_root_password)
|
||||
self.signal_password_response.connect(self.prepare_thread.set_password_reply)
|
||||
|
||||
self.check_thread = CheckFinished()
|
||||
self.signal_status.connect(self.check_thread.update)
|
||||
self.check_thread.signal_finished.connect(self.finish)
|
||||
|
||||
self.skip_thread = EnableSkip()
|
||||
self.skip_thread.signal_timeout.connect(self._enable_skip_button)
|
||||
|
||||
self.progress_thread = AnimateProgress()
|
||||
self.progress_thread.signal_change.connect(self._change_progress)
|
||||
|
||||
self.label_top = QLabel()
|
||||
self.label_top.setText("{}...".format(self.i18n['prepare_panel.title.start'].capitalize()))
|
||||
self.label_top.setAlignment(Qt.AlignHCenter)
|
||||
self.label_top.setStyleSheet("QLabel { font-size: 14px; font-weight: bold; }")
|
||||
self.layout().addWidget(self.label_top)
|
||||
self.layout().addWidget(QLabel())
|
||||
|
||||
self.table = QTableWidget()
|
||||
self.table.setStyleSheet("QTableWidget { background-color: transparent; }")
|
||||
self.table.setFocusPolicy(Qt.NoFocus)
|
||||
self.table.setShowGrid(False)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
self.table.horizontalHeader().setVisible(False)
|
||||
self.table.horizontalHeader().setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
|
||||
self.table.setColumnCount(4)
|
||||
self.table.setHorizontalHeaderLabels(['' for _ in range(4)])
|
||||
self.layout().addWidget(self.table)
|
||||
|
||||
toolbar = QToolBar()
|
||||
self.bt_close = QPushButton(self.i18n['close'].capitalize())
|
||||
self.bt_close.clicked.connect(self.close)
|
||||
self.bt_close.setVisible(False)
|
||||
self.ref_bt_close = toolbar.addWidget(self.bt_close)
|
||||
|
||||
toolbar.addWidget(new_spacer())
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 4)
|
||||
self.progress_bar.setTextVisible(False)
|
||||
self.progress_bar.setVisible(False)
|
||||
self.ref_progress_bar = toolbar.addWidget(self.progress_bar)
|
||||
toolbar.addWidget(new_spacer())
|
||||
|
||||
self.bt_skip = QPushButton(self.i18n['prepare_panel.bt_skip.label'].capitalize())
|
||||
self.bt_skip.clicked.connect(self.finish)
|
||||
self.bt_skip.setEnabled(False)
|
||||
toolbar.addWidget(self.bt_skip)
|
||||
|
||||
self.layout().addWidget(toolbar)
|
||||
|
||||
def ask_root_password(self):
|
||||
root_pwd, ok = root.ask_root_password(self.context, self.i18n)
|
||||
self.signal_password_response.emit(root_pwd, ok)
|
||||
|
||||
def _enable_skip_button(self):
|
||||
self.bt_skip.setEnabled(True)
|
||||
|
||||
def _change_progress(self, value: int):
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
def get_table_width(self) -> int:
|
||||
return reduce(operator.add, [self.table.columnWidth(i) for i in range(self.table.columnCount())])
|
||||
|
||||
def _resize_columns(self):
|
||||
header_horizontal = self.table.horizontalHeader()
|
||||
for i in range(self.table.columnCount()):
|
||||
header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)
|
||||
|
||||
self.resize(self.get_table_width() * 1.05, self.sizeHint().height())
|
||||
|
||||
def show(self):
|
||||
super(PreparePanel, self).show()
|
||||
self.prepare_thread.start()
|
||||
centralize(self)
|
||||
|
||||
def start(self):
|
||||
self.ref_bt_close.setVisible(True)
|
||||
self.check_thread.start()
|
||||
self.skip_thread.start()
|
||||
|
||||
self.ref_progress_bar.setVisible(True)
|
||||
self.progress_thread.start()
|
||||
|
||||
def closeEvent(self, QCloseEvent):
|
||||
if not self.self_close:
|
||||
QCoreApplication.exit()
|
||||
|
||||
def register_task(self, id_: str, label: str, icon_path: str):
|
||||
self.ntasks += 1
|
||||
self.table.setRowCount(self.ntasks)
|
||||
|
||||
task_row = self.ntasks - 1
|
||||
|
||||
lb_icon = QLabel()
|
||||
lb_icon.setContentsMargins(0, 2, 0, 0)
|
||||
lb_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
|
||||
if icon_path:
|
||||
lb_icon.setPixmap(QIcon(icon_path).pixmap(12, 12))
|
||||
lb_icon.setAlignment(Qt.AlignHCenter)
|
||||
|
||||
self.table.setCellWidget(task_row, 0, lb_icon)
|
||||
|
||||
lb_status = QLabel(label)
|
||||
lb_status.setAlignment(Qt.AlignHCenter)
|
||||
lb_status.setContentsMargins(2, 0, 2, 0)
|
||||
lb_status.setMinimumWidth(50)
|
||||
lb_status.setAlignment(Qt.AlignHCenter)
|
||||
lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
lb_status.setStyleSheet("QLabel { color: blue; font-weight: bold; }")
|
||||
self.table.setCellWidget(task_row, 1, lb_status)
|
||||
|
||||
lb_sub = QLabel()
|
||||
lb_sub.setContentsMargins(2, 0, 2, 0)
|
||||
lb_sub.setAlignment(Qt.AlignHCenter)
|
||||
lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
lb_sub.setMinimumWidth(50)
|
||||
self.table.setCellWidget(task_row, 2, lb_sub)
|
||||
|
||||
lb_progress = QLabel('{0:.2f}'.format(0) + '%')
|
||||
lb_progress.setContentsMargins(2, 2, 2, 2)
|
||||
lb_progress.setStyleSheet("QLabel { color: blue; font-weight: bold; }")
|
||||
lb_progress.setAlignment(Qt.AlignHCenter)
|
||||
lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
|
||||
self.table.setCellWidget(task_row, 3, lb_progress)
|
||||
|
||||
self.tasks[id_] = {'lb_status': lb_status,
|
||||
'lb_prog': lb_progress,
|
||||
'progress': 0,
|
||||
'lb_sub': lb_sub,
|
||||
'finished': False}
|
||||
|
||||
self.signal_status.emit(self.ntasks, self.ftasks)
|
||||
|
||||
def update_progress(self, task_id: str, progress: float, substatus: str):
|
||||
task = self.tasks[task_id]
|
||||
|
||||
if progress != task['progress']:
|
||||
task['progress'] = progress
|
||||
task['lb_prog'].setText('{0:.2f}'.format(progress) + '%')
|
||||
|
||||
if substatus:
|
||||
task['lb_sub'].setText('( {} )'.format(substatus))
|
||||
else:
|
||||
task['lb_sub'].setText('')
|
||||
|
||||
self._resize_columns()
|
||||
|
||||
def finish_task(self, task_id: str):
|
||||
task = self.tasks[task_id]
|
||||
task['lb_sub'].setText('')
|
||||
|
||||
for key in ('lb_prog', 'lb_status'):
|
||||
task[key].setStyleSheet('QLabel { color: green; text-decoration: line-through; }')
|
||||
|
||||
task['finished'] = True
|
||||
self._resize_columns()
|
||||
|
||||
self.ftasks += 1
|
||||
self.signal_status.emit(self.ntasks, self.ftasks)
|
||||
|
||||
if self.ntasks == self.ftasks:
|
||||
self.label_top.setText("... {} ...".format(self.i18n['ready'].capitalize()))
|
||||
|
||||
def finish(self):
|
||||
if self.isVisible():
|
||||
self.manage_window.refresh_packages()
|
||||
self.manage_window.show()
|
||||
self.self_close = True
|
||||
self.close()
|
||||
@@ -1,9 +1,12 @@
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtWidgets import QInputDialog, QLineEdit
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.view import MessageType
|
||||
from bauh.commons.system import new_subprocess
|
||||
from bauh.view.core.config import read_config
|
||||
from bauh.view.qt.dialog import show_message
|
||||
from bauh.view.qt.view_utils import load_resource_icon
|
||||
from bauh.view.util.translation import I18n
|
||||
@@ -13,7 +16,14 @@ def is_root():
|
||||
return os.getuid() == 0
|
||||
|
||||
|
||||
def ask_root_password(i18n: I18n):
|
||||
def ask_root_password(context: ApplicationContext, i18n: I18n, app_config: dict = None) -> Tuple[str, bool]:
|
||||
|
||||
cur_config = read_config() if not app_config else app_config
|
||||
store_password = bool(cur_config['store_root_password'])
|
||||
|
||||
if store_password and context.root_password and validate_password(context.root_password):
|
||||
return context.root_password, True
|
||||
|
||||
diag = QInputDialog()
|
||||
diag.setStyleSheet("""QLineEdit { border-radius: 5px; font-size: 16px; border: 1px solid lightblue }""")
|
||||
diag.setInputMode(QInputDialog.TextInput)
|
||||
@@ -44,6 +54,9 @@ def ask_root_password(i18n: I18n):
|
||||
diag.setTextValue('')
|
||||
|
||||
if ok:
|
||||
if store_password:
|
||||
context.root_password = diag.textValue()
|
||||
|
||||
return diag.textValue(), ok
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
import gc
|
||||
from io import StringIO
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt
|
||||
from PyQt5.QtCore import QSize, Qt, QCoreApplication
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QPushButton
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh.api.abstract.view import MessageType
|
||||
from bauh.view.core.controller import GenericSoftwareManager
|
||||
from bauh.view.qt import dialog, css
|
||||
from bauh.view.qt.components import to_widget, new_spacer
|
||||
from bauh.view.qt.qt_utils import centralize
|
||||
from bauh.view.util import util
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
|
||||
class SettingsWindow(QWidget):
|
||||
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, tray, window: QWidget, parent: QWidget = None):
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, window: QWidget, parent: QWidget = None):
|
||||
super(SettingsWindow, self).__init__(parent=parent)
|
||||
self.setWindowTitle(i18n['settings'].capitalize())
|
||||
self.setWindowTitle('{} ({})'.format(i18n['settings'].capitalize(), __app_name__))
|
||||
self.setLayout(QVBoxLayout())
|
||||
self.manager = manager
|
||||
self.i18n = i18n
|
||||
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.tray = tray
|
||||
self.window = window
|
||||
|
||||
self.settings_model = self.manager.get_settings(screen_size.width(), screen_size.height())
|
||||
@@ -49,13 +50,16 @@ class SettingsWindow(QWidget):
|
||||
|
||||
self.layout().addWidget(action_bar)
|
||||
|
||||
def show(self):
|
||||
super(SettingsWindow, self).show()
|
||||
centralize(self)
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.window and self.window.settings_window == self:
|
||||
self.deleteLater()
|
||||
self.window.settings_window = None
|
||||
elif self.tray and self.tray.settings_window == self:
|
||||
self.deleteLater()
|
||||
self.tray.settings_window = None
|
||||
elif not self.window:
|
||||
QCoreApplication.exit()
|
||||
|
||||
gc.collect()
|
||||
|
||||
@@ -70,24 +74,29 @@ class SettingsWindow(QWidget):
|
||||
def _save_settings(self):
|
||||
success, warnings = self.manager.save_settings(self.settings_model)
|
||||
|
||||
# Configurações alteradas com sucesso, porém algumas delas só surtirão após a reinicialização
|
||||
|
||||
if success:
|
||||
if dialog.ask_confirmation(title=self.i18n['warning'].capitalize(),
|
||||
body="<p>{}</p><p>{}</p>".format(self.i18n['settings.changed.success.warning'],
|
||||
self.i18n['settings.changed.success.reboot']),
|
||||
i18n=self.i18n):
|
||||
util.restart_app(self.window and self.window.isVisible())
|
||||
if not self.window:
|
||||
dialog.show_message(title=self.i18n['success'].capitalize(),
|
||||
body=self.i18n['settings.changed.success.warning'],
|
||||
type_=MessageType.INFO)
|
||||
QCoreApplication.exit()
|
||||
elif dialog.ask_confirmation(title=self.i18n['warning'].capitalize(),
|
||||
body="<p>{}</p><p>{}</p>".format(self.i18n['settings.changed.success.warning'],
|
||||
self.i18n['settings.changed.success.reboot']),
|
||||
i18n=self.i18n):
|
||||
self.close()
|
||||
util.restart_app()
|
||||
else:
|
||||
if isinstance(self.manager, GenericSoftwareManager):
|
||||
self.manager.reset_cache()
|
||||
|
||||
self.manager.prepare()
|
||||
self.manager.prepare(task_manager=None, root_password=None, internet_available=None)
|
||||
|
||||
if self.window and self.window.isVisible():
|
||||
self.window.update_custom_actions()
|
||||
self.window.verify_warnings()
|
||||
self.window.types_changed = True
|
||||
self.window.refresh_apps()
|
||||
self.window.refresh_packages()
|
||||
self.close()
|
||||
else:
|
||||
msg = StringIO()
|
||||
|
||||
@@ -1,48 +1,82 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from io import StringIO
|
||||
from subprocess import Popen
|
||||
from threading import Lock, Thread
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, Qt, QSize
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, QSize
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QWidget, QSizePolicy
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh import __app_name__, ROOT_DIR
|
||||
from bauh.api.abstract.model import PackageUpdate
|
||||
from bauh.view.qt import qt_utils
|
||||
from bauh.commons.system import run_cmd
|
||||
from bauh.context import generate_i18n
|
||||
from bauh.view.core.tray_client import TRAY_CHECK_FILE
|
||||
from bauh.view.qt.about import AboutDialog
|
||||
from bauh.view.qt.settings import SettingsWindow
|
||||
from bauh.view.qt.view_utils import load_resource_icon
|
||||
from bauh.view.qt.window import ManageWindow
|
||||
from bauh.view.util import util
|
||||
from bauh.view.util.translation import I18n
|
||||
from bauh.view.util import util, resource
|
||||
|
||||
|
||||
def list_updates() -> List[PackageUpdate]:
|
||||
if run_cmd('which bauh-cli', print_error=False):
|
||||
output = run_cmd('bauh-cli updates -f json')
|
||||
|
||||
if output:
|
||||
return [PackageUpdate(pkg_id=o['id'], name=o['name'], version=o['version'], pkg_type=o['type']) for o in json.loads(output)]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class UpdateCheck(QThread):
|
||||
|
||||
signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: SoftwareManager, check_interval: int, parent=None):
|
||||
def __init__(self, check_interval: int, lock: Lock, check_file: bool, parent=None):
|
||||
super(UpdateCheck, self).__init__(parent)
|
||||
self.check_interval = check_interval
|
||||
self.manager = manager
|
||||
self.lock = lock
|
||||
self.check_file = check_file
|
||||
|
||||
def _notify_updates(self):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
updates = list_updates()
|
||||
|
||||
if updates is not None:
|
||||
self.signal.emit(updates)
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
def run(self):
|
||||
|
||||
while True:
|
||||
updates = self.manager.list_updates()
|
||||
self.signal.emit(updates)
|
||||
time.sleep(self.check_interval)
|
||||
if self.check_file:
|
||||
if os.path.exists(TRAY_CHECK_FILE):
|
||||
self._notify_updates()
|
||||
try:
|
||||
os.remove(TRAY_CHECK_FILE)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
time.sleep(self.check_interval)
|
||||
else:
|
||||
self._notify_updates()
|
||||
|
||||
|
||||
class TrayIcon(QSystemTrayIcon):
|
||||
|
||||
def __init__(self, i18n: I18n, manager: SoftwareManager, manage_window: ManageWindow, config: dict, screen_size: QSize):
|
||||
def __init__(self, config: dict, screen_size: QSize, manage_process: Popen = None, settings_process: Popen = None):
|
||||
super(TrayIcon, self).__init__()
|
||||
self.i18n = i18n
|
||||
self.manager = manager
|
||||
self.i18n = generate_i18n(config, resource.get_path('locale/tray'))
|
||||
self.screen_size = screen_size
|
||||
self.manage_process = manage_process
|
||||
self.settings_process = settings_process
|
||||
|
||||
if config['ui']['tray']['default_icon']:
|
||||
self.icon_default = QIcon(config['ui']['tray']['default_icon'])
|
||||
@@ -67,7 +101,7 @@ class TrayIcon(QSystemTrayIcon):
|
||||
self.action_manage = self.menu.addAction(self.i18n['tray.action.manage'])
|
||||
self.action_manage.triggered.connect(self.show_manage_window)
|
||||
|
||||
self.action_settings = self.menu.addAction(self.i18n['settings'].capitalize())
|
||||
self.action_settings = self.menu.addAction(self.i18n['tray.settings'].capitalize())
|
||||
self.action_settings.triggered.connect(self.show_settings_window)
|
||||
|
||||
self.action_about = self.menu.addAction(self.i18n['tray.action.about'])
|
||||
@@ -82,10 +116,15 @@ class TrayIcon(QSystemTrayIcon):
|
||||
self.dialog_about = None
|
||||
self.settings_window = None
|
||||
|
||||
self.check_thread = UpdateCheck(check_interval=int(config['updates']['check_interval']), manager=self.manager)
|
||||
self.check_lock = Lock()
|
||||
self.check_thread = UpdateCheck(check_interval=int(config['updates']['check_interval']), check_file=False, lock=self.check_lock)
|
||||
self.check_thread.signal.connect(self.notify_updates)
|
||||
self.check_thread.start()
|
||||
|
||||
self.recheck_thread = UpdateCheck(check_interval=2, check_file=True, lock=self.check_lock)
|
||||
self.recheck_thread.signal.connect(self.notify_updates)
|
||||
self.recheck_thread.start()
|
||||
|
||||
self.last_updates = set()
|
||||
self.update_notification = bool(config['system']['notifications'])
|
||||
self.lock_notify = Lock()
|
||||
@@ -93,10 +132,8 @@ class TrayIcon(QSystemTrayIcon):
|
||||
self.activated.connect(self.handle_click)
|
||||
self.set_default_tooltip()
|
||||
|
||||
self.manage_window = manage_window
|
||||
|
||||
def set_default_tooltip(self):
|
||||
self.setToolTip('{} ({})'.format(self.i18n['manage_window.title'], __app_name__).lower())
|
||||
self.setToolTip('{} ({})'.format(self.i18n['tray.action.manage'], __app_name__).lower())
|
||||
|
||||
def handle_click(self, reason):
|
||||
if reason == self.Trigger:
|
||||
@@ -109,7 +146,6 @@ class TrayIcon(QSystemTrayIcon):
|
||||
self.notify_updates(self.manager.list_updates(), notify_user=notify_user)
|
||||
|
||||
def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True):
|
||||
|
||||
self.lock_notify.acquire()
|
||||
|
||||
try:
|
||||
@@ -133,8 +169,8 @@ class TrayIcon(QSystemTrayIcon):
|
||||
msg.write(self.i18n['notification.update{}'.format('' if n_updates == 1 else 's')].format(n_updates))
|
||||
|
||||
if len(ups_by_type) > 1:
|
||||
for ptype, count in ups_by_type.items():
|
||||
msg.write('\n * {} ( {} )'.format(ptype.capitalize(), count))
|
||||
for ptype in sorted(ups_by_type):
|
||||
msg.write('\n * {} ( {} )'.format(ptype, ups_by_type[ptype]))
|
||||
|
||||
msg.seek(0)
|
||||
msg = msg.read()
|
||||
@@ -155,25 +191,18 @@ class TrayIcon(QSystemTrayIcon):
|
||||
self.lock_notify.release()
|
||||
|
||||
def show_manage_window(self):
|
||||
if self.manage_window.isMinimized():
|
||||
self.manage_window.setWindowState(Qt.WindowNoState)
|
||||
elif not self.manage_window.isVisible():
|
||||
self.manage_window.refresh_apps()
|
||||
self.manage_window.show()
|
||||
if self.manage_process is None:
|
||||
self.manage_process = Popen([sys.executable, '{}/app.py'.format(ROOT_DIR)])
|
||||
elif self.manage_process.poll() is not None: # it means it has finished
|
||||
self.manage_process = None
|
||||
self.show_manage_window()
|
||||
|
||||
def show_settings_window(self):
|
||||
if self.settings_window:
|
||||
self.settings_window.handle_display()
|
||||
else:
|
||||
self.settings_window = SettingsWindow(manager=self.manager,
|
||||
i18n=self.i18n,
|
||||
screen_size=self.screen_size,
|
||||
tray=self,
|
||||
window=self.manage_window)
|
||||
self.settings_window.setMinimumWidth(int(self.screen_size.width() / 4))
|
||||
self.settings_window.adjustSize()
|
||||
qt_utils.centralize(self.settings_window)
|
||||
self.settings_window.show()
|
||||
if self.settings_process is None:
|
||||
self.settings_process = Popen([sys.executable, '{}/app.py'.format(ROOT_DIR), '--settings'])
|
||||
elif self.settings_process.poll() is not None: # it means it has finished
|
||||
self.settings_process = None
|
||||
self.show_settings_window()
|
||||
|
||||
def show_about(self):
|
||||
if self.dialog_about is None:
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import List, Type, Set, Tuple
|
||||
|
||||
import requests
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
|
||||
from bauh import LOGS_PATH
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh.api.abstract.controller import SoftwareManager, UpgradeRequirement, UpgradeRequirements
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import PackageStatus, SoftwarePackage, PackageAction
|
||||
from bauh.api.abstract.view import InputViewComponent, MessageType, MultipleSelectComponent, InputOption
|
||||
from bauh.api.abstract.model import PackageStatus, SoftwarePackage, CustomSoftwareAction
|
||||
from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, TextComponent, \
|
||||
FormComponent, ViewComponent
|
||||
from bauh.api.exception import NoInternetException
|
||||
from bauh.commons import user
|
||||
from bauh.commons.html import bold
|
||||
from bauh.view.core import config
|
||||
from bauh.commons.system import get_human_size_str, ProcessHandler, SimpleProcess
|
||||
from bauh.view.core import timeshift
|
||||
from bauh.view.core.config import read_config
|
||||
from bauh.view.qt import commons
|
||||
from bauh.view.qt.view_model import PackageView
|
||||
from bauh.view.qt.view_model import PackageView, PackageViewStatus
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
RE_VERSION_IN_NAME = re.compile(r'\s+version\s+[\w\.]+\s*$')
|
||||
@@ -32,6 +40,7 @@ class AsyncAction(QThread, ProcessWatcher):
|
||||
signal_substatus = pyqtSignal(str) # changes the GUI substatus message
|
||||
signal_progress = pyqtSignal(int)
|
||||
signal_root_password = pyqtSignal()
|
||||
signal_progress_control = pyqtSignal(bool)
|
||||
|
||||
def __init__(self):
|
||||
super(AsyncAction, self).__init__()
|
||||
@@ -40,9 +49,9 @@ class AsyncAction(QThread, ProcessWatcher):
|
||||
self.root_password = None
|
||||
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[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None, deny_button: bool = True) -> bool:
|
||||
self.wait_confirmation = True
|
||||
self.signal_confirmation.emit({'title': title, 'body': body, 'components': components, 'confirmation_label': confirmation_label, 'deny_label': deny_label})
|
||||
self.signal_confirmation.emit({'title': title, 'body': body, 'components': components, 'confirmation_label': confirmation_label, 'deny_label': deny_label, 'deny_button': deny_button})
|
||||
self.wait_user()
|
||||
return self.confirmation_res
|
||||
|
||||
@@ -90,117 +99,374 @@ class AsyncAction(QThread, ProcessWatcher):
|
||||
def should_stop(self):
|
||||
return self.stop
|
||||
|
||||
def enable_progress_controll(self):
|
||||
self.signal_progress_control.emit(True)
|
||||
|
||||
class UpdateSelectedApps(AsyncAction):
|
||||
def disable_progress_controll(self):
|
||||
self.signal_progress_control.emit(False)
|
||||
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, pkgs: List[PackageView] = None):
|
||||
super(UpdateSelectedApps, self).__init__()
|
||||
self.pkgs = pkgs
|
||||
self.manager = manager
|
||||
self.root_password = None
|
||||
self.i18n = i18n
|
||||
def request_reboot(self, msg: str) -> bool:
|
||||
if self.request_confirmation(title=self.i18n['action.request_reboot.title'],
|
||||
body=msg,
|
||||
confirmation_label=self.i18n['yes'].capitalize(),
|
||||
deny_label=self.i18n['bt.not_now']):
|
||||
ProcessHandler(self).handle_simple(SimpleProcess(['reboot']))
|
||||
return True
|
||||
|
||||
def _pkg_as_option(self, pkg: SoftwarePackage, tooltip: bool = True) -> InputOption:
|
||||
if pkg.installed:
|
||||
icon_path = pkg.get_disk_icon_path()
|
||||
return False
|
||||
|
||||
if not icon_path or not os.path.isfile(icon_path):
|
||||
icon_path = pkg.get_type_icon_path()
|
||||
|
||||
else:
|
||||
icon_path = pkg.get_type_icon_path()
|
||||
|
||||
return InputOption(label='{}{}'.format(pkg.name, ' ( {} )'.format(pkg.latest_version) if pkg.latest_version else ''),
|
||||
value=None,
|
||||
tooltip=pkg.get_name_tooltip() if tooltip else None,
|
||||
read_only=True,
|
||||
icon_path=icon_path)
|
||||
|
||||
def _handle_update_requirements(self, pkgs: List[SoftwarePackage]) -> bool:
|
||||
self.change_substatus(self.i18n['action.update.requirements.status'])
|
||||
required_pkgs = self.manager.get_update_requirements(pkgs, self)
|
||||
|
||||
if required_pkgs:
|
||||
opts = [self._pkg_as_option(p) for p in required_pkgs]
|
||||
|
||||
if not self.request_confirmation(self.i18n['action.update.requirements.title'],
|
||||
self.i18n['action.update.requirements.body'].format(bold(str(len(opts)))),
|
||||
[MultipleSelectComponent(label='', options=opts, default_options=set(opts))],
|
||||
confirmation_label=self.i18n['continue'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize()):
|
||||
self.notify_finished({'success': False, 'updated': 0, 'types': set()})
|
||||
self.pkgs = None
|
||||
def _generate_backup(self, app_config: dict, i18n: I18n, root_password: str) -> bool:
|
||||
if timeshift.is_available():
|
||||
if app_config['backup']['mode'] not in ('only_one', 'incremental'):
|
||||
self.show_message(title=self.i18n['error'].capitalize(),
|
||||
body='{}: {}'.format(self.i18n['action.backup.invalid_mode'], bold(app_config['backup']['mode'])),
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
|
||||
if not user.is_root() and not root_password:
|
||||
root_pwd, valid = self.request_root_password()
|
||||
else:
|
||||
for pkg in required_pkgs:
|
||||
if not self.manager.install(pkg, self.root_password, self):
|
||||
self.notify_finished({'success': False, 'updated': 0, 'types': set()})
|
||||
self.pkgs = None
|
||||
label = '{}{}'.format(pkg.name, ' ( {} )'.format(pkg.version) if pkg.version else '')
|
||||
self.show_message(title=self.i18n['action.update.install_req.fail.title'],
|
||||
body=self.i18n['action.update.install_req.fail.body'].format(label),
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
root_pwd, valid = root_password, True
|
||||
|
||||
if not valid:
|
||||
return False
|
||||
|
||||
handler = ProcessHandler(self)
|
||||
if app_config['backup']['mode'] == 'only_one':
|
||||
self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.delete']))
|
||||
deleted, _ = handler.handle_simple(timeshift.delete_all_snapshots(root_pwd))
|
||||
|
||||
if not deleted and not self.request_confirmation(title=i18n['core.config.tab.backup'],
|
||||
body='{}. {}'.format(i18n['action.backup.error.delete'], i18n['action.backup.error.proceed']),
|
||||
confirmation_label=i18n['yes'].capitalize(),
|
||||
deny_label=i18n['no'].capitalize()):
|
||||
return False
|
||||
|
||||
self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.create']))
|
||||
created, _ = handler.handle_simple(timeshift.create_snapshot(root_pwd))
|
||||
|
||||
if not created and not self.request_confirmation(title=i18n['core.config.tab.backup'],
|
||||
body='{}. {}'.format(i18n['action.backup.error.create'],
|
||||
i18n['action.backup.error.proceed']),
|
||||
confirmation_label=i18n['yes'].capitalize(),
|
||||
deny_label=i18n['no'].capitalize()):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _sort_packages(self, pkgs: List[SoftwarePackage], app_config: dict) -> Tuple[bool, List[SoftwarePackage]]:
|
||||
if bool(app_config['updates']['sort_packages']):
|
||||
self.change_substatus(self.i18n['action.update.status.sorting'])
|
||||
sorted_pkgs = self.manager.sort_update_order([view.model for view in self.pkgs])
|
||||
else:
|
||||
sorted_pkgs = pkgs
|
||||
def request_backup(self, app_config: dict, key: str, i18n: I18n, root_password: str = None) -> bool:
|
||||
if bool(app_config['backup']['enabled']) and timeshift.is_available():
|
||||
val = app_config['backup'][key] if key else None
|
||||
|
||||
if len(sorted_pkgs) > 1:
|
||||
opts = [self._pkg_as_option(p, tooltip=False) for p in sorted_pkgs]
|
||||
proceed = self.request_confirmation(title=self.i18n['action.update.order.title'],
|
||||
body=self.i18n['action.update.order.body'] + ':',
|
||||
components=[MultipleSelectComponent(label='', options=opts, default_options=set(opts))],
|
||||
confirmation_label=self.i18n['continue'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize())
|
||||
else:
|
||||
proceed = True
|
||||
if val is None: # ask mode
|
||||
if self.request_confirmation(title=i18n['core.config.tab.backup'],
|
||||
body=i18n['action.backup.msg'],
|
||||
confirmation_label=i18n['yes'].capitalize(),
|
||||
deny_label=i18n['no'].capitalize()):
|
||||
res = self._generate_backup(app_config, i18n, root_password)
|
||||
self.change_substatus('')
|
||||
return res
|
||||
|
||||
return proceed, sorted_pkgs
|
||||
elif val is True: # direct mode
|
||||
res = self._generate_backup(app_config, i18n, root_password)
|
||||
self.change_substatus('')
|
||||
return res
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class UpgradeSelected(AsyncAction):
|
||||
|
||||
LOGS_DIR = '{}/upgrade'.format(LOGS_PATH)
|
||||
SUMMARY_FILE = LOGS_DIR + '/{}_summary.txt'
|
||||
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, pkgs: List[PackageView] = None):
|
||||
super(UpgradeSelected, self).__init__()
|
||||
self.pkgs = pkgs
|
||||
self.manager = manager
|
||||
self.i18n = i18n
|
||||
|
||||
def _req_as_option(self, req: UpgradeRequirement, tooltip: bool = True, custom_tooltip: str = None) -> InputOption:
|
||||
if req.pkg.installed:
|
||||
icon_path = req.pkg.get_disk_icon_path()
|
||||
|
||||
if not icon_path or not os.path.isfile(icon_path):
|
||||
icon_path = req.pkg.get_type_icon_path()
|
||||
|
||||
else:
|
||||
icon_path = req.pkg.get_type_icon_path()
|
||||
|
||||
size_str = '{}: {}'.format(self.i18n['size'].capitalize(),
|
||||
'?' if req.extra_size is None else get_human_size_str(req.extra_size))
|
||||
if req.extra_size != req.required_size:
|
||||
size_str += ' ( {}: {} )'.format(self.i18n['action.update.pkg.required_size'].capitalize(),
|
||||
'?' if req.required_size is None else get_human_size_str(req.required_size))
|
||||
|
||||
label = '{}{} - {}'.format(req.pkg.name,
|
||||
' ( {} )'.format(req.pkg.latest_version) if req.pkg.latest_version else '',
|
||||
size_str)
|
||||
|
||||
return InputOption(label=label,
|
||||
value=None,
|
||||
tooltip=custom_tooltip if custom_tooltip else (req.pkg.get_name_tooltip() if tooltip else None),
|
||||
read_only=True,
|
||||
icon_path=icon_path)
|
||||
|
||||
def filter_to_update(self) -> Tuple[List[PackageView], bool]: # packages to update and if they require root privileges
|
||||
to_update, requires_root = [], False
|
||||
root_user = user.is_root()
|
||||
|
||||
for pkg in self.pkgs:
|
||||
if pkg.model.update and pkg.update_checked:
|
||||
to_update.append(pkg)
|
||||
|
||||
if not root_user and not requires_root and self.manager.requires_root('update', pkg.model):
|
||||
requires_root = True
|
||||
|
||||
return to_update, requires_root
|
||||
|
||||
def _sum_pkgs_size(self, reqs: List[UpgradeRequirement]) -> Tuple[int, int]:
|
||||
required, extra = 0, 0
|
||||
for r in reqs:
|
||||
if r.required_size is not None:
|
||||
required += r.required_size
|
||||
|
||||
if r.extra_size is not None:
|
||||
extra += r.extra_size
|
||||
|
||||
return required, extra
|
||||
|
||||
def _gen_cannot_update_form(self, reqs: List[UpgradeRequirement]) -> FormComponent:
|
||||
opts = [self._req_as_option(r.pkg, False, r.reason) for r in reqs]
|
||||
comps = [MultipleSelectComponent(label='', options=opts, default_options=set(opts))]
|
||||
|
||||
return FormComponent(label=self.i18n['action.update.cannot_update_label'], components=comps)
|
||||
|
||||
def _gen_to_install_form(self, reqs: List[UpgradeRequirement]) -> Tuple[FormComponent, Tuple[int, int]]:
|
||||
opts = [self._req_as_option(r) for r in reqs]
|
||||
comps = [MultipleSelectComponent(label='', options=opts, default_options=set(opts))]
|
||||
required_size, extra_size = self._sum_pkgs_size(reqs)
|
||||
|
||||
lb = '{} ( {}: {}. {}: {}. {}: {} )'.format(self.i18n['action.update.required_label'].capitalize(),
|
||||
self.i18n['amount'].capitalize(),
|
||||
len(opts),
|
||||
self.i18n['size'].capitalize(),
|
||||
'?' if extra_size is None else get_human_size_str(extra_size),
|
||||
self.i18n['action.update.pkg.required_size'].capitalize(),
|
||||
'?' if required_size is None else get_human_size_str(required_size))
|
||||
return FormComponent(label=lb, components=comps), (required_size, extra_size)
|
||||
|
||||
def _gen_to_remove_form(self, reqs: List[UpgradeRequirement]) -> FormComponent:
|
||||
opts = [self._req_as_option(r, False, r.reason) for r in reqs]
|
||||
comps = [MultipleSelectComponent(label='', options=opts, default_options=set(opts))]
|
||||
required_size, extra_size = self._sum_pkgs_size(reqs)
|
||||
|
||||
lb = '{} ( {}: {}. {}: {} )'.format(self.i18n['action.update.label_to_remove'].capitalize(),
|
||||
self.i18n['amount'].capitalize(),
|
||||
len(opts),
|
||||
self.i18n['size'].capitalize(),
|
||||
'?' if extra_size is None else get_human_size_str(-extra_size))
|
||||
return FormComponent(label=lb, components=comps)
|
||||
|
||||
def _gen_to_update_form(self, reqs: List[UpgradeRequirement]) -> Tuple[FormComponent, Tuple[int, int]]:
|
||||
opts = [self._req_as_option(r, tooltip=False) for r in reqs]
|
||||
comps = [MultipleSelectComponent(label='', options=opts, default_options=set(opts))]
|
||||
required_size, extra_size = self._sum_pkgs_size(reqs)
|
||||
|
||||
lb = '{} ( {}: {}. {}: {}. {}: {} )'.format(self.i18n['action.update.label_to_upgrade'].capitalize(),
|
||||
self.i18n['amount'].capitalize(),
|
||||
len(opts),
|
||||
self.i18n['size'].capitalize(),
|
||||
'?' if extra_size is None else get_human_size_str(extra_size),
|
||||
self.i18n['action.update.pkg.required_size'].capitalize(),
|
||||
'?' if required_size is None else get_human_size_str(required_size))
|
||||
|
||||
return FormComponent(label=lb, components=comps), (required_size, extra_size)
|
||||
|
||||
def _trim_disk(self, root_password: str):
|
||||
if self.request_confirmation(title=self.i18n['confirmation'].capitalize(),
|
||||
body=self.i18n['action.trim_disk.ask']):
|
||||
|
||||
pwd = root_password
|
||||
|
||||
if not pwd and user.is_root():
|
||||
pwd, success = self.request_root_password()
|
||||
|
||||
if not success:
|
||||
return
|
||||
|
||||
self.change_substatus(self.i18n['action.disk_trim'].capitalize())
|
||||
|
||||
success, output = ProcessHandler(self).handle_simple(SimpleProcess(['fstrim', '/', '-v'], root_password=pwd))
|
||||
|
||||
if not success:
|
||||
self.show_message(title=self.i18n['success'].capitalize(),
|
||||
body=self.i18n['action.disk_trim.error'],
|
||||
type_=MessageType.ERROR)
|
||||
|
||||
def _write_summary_log(self, upgrade_id: str, requirements: UpgradeRequirements):
|
||||
try:
|
||||
Path(self.LOGS_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
summary_text = StringIO()
|
||||
summary_text.write('Upgrade summary ( id: {} )'.format(upgrade_id))
|
||||
|
||||
if requirements.cannot_upgrade:
|
||||
summary_text.write('\nCannot upgrade:')
|
||||
|
||||
for dep in requirements.cannot_upgrade:
|
||||
type_label = self.i18n.get('gem.{}.type.{}.label'.format(dep.pkg.gem_name, dep.pkg.get_type().lower()), dep.pkg.get_type().capitalize())
|
||||
summary_text.write('\n * Type:{}\tName: {}\tVersion: {}\tReason: {}'.format(type_label, dep.pkg.name, dep.pkg.version if dep.pkg.version else '?', dep.reason if dep.reason else '?'))
|
||||
|
||||
summary_text.write('\n')
|
||||
|
||||
if requirements.to_remove:
|
||||
summary_text.write('\nMust be removed:')
|
||||
|
||||
for dep in requirements.to_remove:
|
||||
type_label = self.i18n.get('gem.{}.type.{}.label'.format(dep.pkg.gem_name, dep.pkg.get_type().lower()), dep.pkg.get_type().capitalize())
|
||||
summary_text.write('\n * Type:{}\tName: {}\tVersion: {}\tReason: {}'.format(type_label, dep.pkg.name, dep.pkg.version if dep.pkg.version else '?', dep.reason if dep.reason else '?'))
|
||||
|
||||
summary_text.write('\n')
|
||||
|
||||
if requirements.to_install:
|
||||
summary_text.write('\nMust be installed:')
|
||||
|
||||
for dep in requirements.to_install:
|
||||
type_label = self.i18n.get(
|
||||
'gem.{}.type.{}.label'.format(dep.pkg.gem_name, dep.pkg.get_type().lower()),
|
||||
dep.pkg.get_type().capitalize())
|
||||
summary_text.write('\n * Type:{}\tName: {}\tVersion: {}\tReason: {}'.format(type_label,
|
||||
dep.pkg.name,
|
||||
dep.pkg.version if dep.pkg.version else '?',
|
||||
dep.reason if dep.reason else '?'))
|
||||
|
||||
summary_text.write('\n')
|
||||
|
||||
if requirements.to_upgrade:
|
||||
summary_text.write('\nWill be upgraded:')
|
||||
|
||||
for dep in requirements.to_upgrade:
|
||||
type_label = self.i18n.get('gem.{}.type.{}.label'.format(dep.pkg.gem_name, dep.pkg.get_type().lower()), dep.pkg.get_type().capitalize())
|
||||
summary_text.write(
|
||||
'\n * Type:{}\tName: {}\tVersion:{} \tNew version: {}'.format(type_label,
|
||||
dep.pkg.name,
|
||||
dep.pkg.version if dep.pkg.version else '?',
|
||||
dep.pkg.latest_version if dep.pkg.latest_version else '?'))
|
||||
|
||||
summary_text.write('\n')
|
||||
|
||||
summary_text.seek(0)
|
||||
|
||||
with open(self.SUMMARY_FILE.format(upgrade_id), 'w+') as f:
|
||||
f.write(summary_text.read())
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def run(self):
|
||||
to_update, requires_root = self.filter_to_update()
|
||||
|
||||
root_password = None
|
||||
|
||||
success = False
|
||||
if not user.is_root() and requires_root:
|
||||
root_password, ok = self.request_root_password()
|
||||
|
||||
if self.pkgs:
|
||||
updated, updated_types = 0, set()
|
||||
|
||||
app_config = config.read_config()
|
||||
|
||||
models = [view.model for view in self.pkgs]
|
||||
|
||||
if bool(app_config['updates']['pre_dependency_checking']) and not self._handle_update_requirements(models):
|
||||
return
|
||||
|
||||
proceed, sorted_pkgs = self._sort_packages(models, app_config)
|
||||
|
||||
if not proceed:
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})
|
||||
if not ok:
|
||||
self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None})
|
||||
self.pkgs = None
|
||||
return
|
||||
|
||||
for pkg in sorted_pkgs:
|
||||
self.change_substatus('')
|
||||
name = pkg.name if not RE_VERSION_IN_NAME.findall(pkg.name) else pkg.name.split('version')[0].strip()
|
||||
if len(to_update) > 1:
|
||||
self.disable_progress_controll()
|
||||
else:
|
||||
self.enable_progress_controll()
|
||||
|
||||
self.change_status('{} {} {}...'.format(self.i18n['manage_window.status.upgrading'], name, pkg.version))
|
||||
success = bool(self.manager.update(pkg, self.root_password, self))
|
||||
self.change_substatus('')
|
||||
success = False
|
||||
|
||||
if not success:
|
||||
updated, updated_types = 0, set()
|
||||
|
||||
models = [view.model for view in to_update]
|
||||
|
||||
self.change_substatus(self.i18n['action.update.requirements.status'])
|
||||
requirements = self.manager.get_upgrade_requirements(models, root_password, self)
|
||||
|
||||
if not requirements:
|
||||
self.pkgs = None
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None})
|
||||
return
|
||||
|
||||
comps, required_size, extra_size = [], 0, 0
|
||||
|
||||
if requirements.cannot_upgrade:
|
||||
comps.append(self._gen_cannot_update_form(requirements.cannot_upgrade))
|
||||
|
||||
if requirements.to_install:
|
||||
req_form, reqs_size = self._gen_to_install_form(requirements.to_install)
|
||||
required_size += reqs_size[0]
|
||||
extra_size += reqs_size[1]
|
||||
comps.append(req_form)
|
||||
|
||||
if requirements.to_remove:
|
||||
comps.append(self._gen_to_remove_form(requirements.to_remove))
|
||||
|
||||
updates_form, updates_size = self._gen_to_update_form(requirements.to_upgrade)
|
||||
required_size += updates_size[0]
|
||||
extra_size += updates_size[1]
|
||||
comps.append(updates_form)
|
||||
|
||||
extra_size_text = '{}: {}'.format(self.i18n['action.update.total_size'].capitalize(), get_human_size_str(extra_size))
|
||||
req_size_text = '{}: {}'.format(self.i18n['action.update.required_size'].capitalize(),
|
||||
get_human_size_str(required_size))
|
||||
comps.insert(0, TextComponent(bold('{} | {}').format(extra_size_text, req_size_text), size=14))
|
||||
comps.insert(1, TextComponent(''))
|
||||
|
||||
if not self.request_confirmation(title=self.i18n['action.update.summary'].capitalize(), body='', components=comps):
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None})
|
||||
self.pkgs = None
|
||||
return
|
||||
|
||||
self.change_substatus('')
|
||||
|
||||
app_config = read_config()
|
||||
|
||||
if bool(app_config['backup']['enabled']) and app_config['backup']['upgrade'] in (True, None) and timeshift.is_available():
|
||||
any_requires_bkp = False
|
||||
|
||||
for dep in requirements.to_upgrade:
|
||||
if dep.pkg.supports_backup():
|
||||
any_requires_bkp = True
|
||||
break
|
||||
else:
|
||||
updated += 1
|
||||
updated_types.add(pkg.__class__)
|
||||
self.signal_output.emit('\n')
|
||||
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})
|
||||
if any_requires_bkp:
|
||||
if not self.request_backup(app_config, 'upgrade', self.i18n, root_password):
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None})
|
||||
self.pkgs = None
|
||||
return
|
||||
|
||||
self.change_substatus('')
|
||||
|
||||
timestamp = datetime.now()
|
||||
upgrade_id = 'upgrade_{}{}{}_{}'.format(timestamp.year, timestamp.month, timestamp.day, int(time.time()))
|
||||
|
||||
self._write_summary_log(upgrade_id, requirements)
|
||||
|
||||
success = bool(self.manager.upgrade(requirements, root_password, self))
|
||||
self.change_substatus('')
|
||||
|
||||
if success:
|
||||
updated = len(requirements.to_upgrade)
|
||||
updated_types.update((req.pkg.__class__ for req in requirements.to_upgrade))
|
||||
|
||||
if bool(app_config['disk']['trim_after_update']):
|
||||
self._trim_disk(root_password)
|
||||
|
||||
msg = '<p>{}</p>{}</p><br/><p>{}</p>'.format(self.i18n['action.update.success.reboot.line1'],
|
||||
self.i18n['action.update.success.reboot.line2'],
|
||||
self.i18n['action.update.success.reboot.line3'])
|
||||
self.request_reboot(msg)
|
||||
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': upgrade_id})
|
||||
self.pkgs = None
|
||||
|
||||
|
||||
@@ -241,16 +507,24 @@ class RefreshApps(AsyncAction):
|
||||
|
||||
class UninstallApp(AsyncAction):
|
||||
|
||||
def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, app: PackageView = None):
|
||||
def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, app: PackageView = None):
|
||||
super(UninstallApp, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
self.icon_cache = icon_cache
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
self.i18n = i18n
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
success = self.manager.uninstall(self.app.model, self.root_password, self)
|
||||
if self.app.model.supports_backup():
|
||||
if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd):
|
||||
self.notify_finished(False)
|
||||
self.app = None
|
||||
self.root_pwd = None
|
||||
return
|
||||
|
||||
success = self.manager.uninstall(self.app.model, self.root_pwd, self)
|
||||
|
||||
if success:
|
||||
self.icon_cache.delete(self.app.model.icon_url)
|
||||
@@ -258,7 +532,7 @@ class UninstallApp(AsyncAction):
|
||||
|
||||
self.notify_finished(self.app if success else None)
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
|
||||
|
||||
class DowngradeApp(AsyncAction):
|
||||
@@ -268,20 +542,28 @@ class DowngradeApp(AsyncAction):
|
||||
self.manager = manager
|
||||
self.app = app
|
||||
self.i18n = i18n
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
success = False
|
||||
|
||||
if self.app.model.supports_backup():
|
||||
if not self.request_backup(read_config(), 'downgrade', self.i18n, self.root_pwd):
|
||||
self.notify_finished({'app': self.app, 'success': success})
|
||||
self.app = None
|
||||
self.root_pwd = None
|
||||
return
|
||||
|
||||
try:
|
||||
success = self.manager.downgrade(self.app.model, self.root_password, self)
|
||||
success = self.manager.downgrade(self.app.model, self.root_pwd, self)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException) as e:
|
||||
success = False
|
||||
self.print(self.i18n['internet.required'])
|
||||
finally:
|
||||
self.notify_finished({'app': self.app, 'success': success})
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
|
||||
|
||||
class GetAppInfo(AsyncAction):
|
||||
@@ -341,23 +623,27 @@ class SearchPackages(AsyncAction):
|
||||
|
||||
class InstallPackage(AsyncAction):
|
||||
|
||||
def __init__(self, manager: SoftwareManager, disk_cache: bool, icon_cache: MemoryCache, i18n: I18n, pkg: PackageView = None):
|
||||
def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, pkg: PackageView = None):
|
||||
super(InstallPackage, self).__init__()
|
||||
self.pkg = pkg
|
||||
self.manager = manager
|
||||
self.icon_cache = icon_cache
|
||||
self.disk_cache = disk_cache
|
||||
self.i18n = i18n
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
|
||||
def run(self):
|
||||
if self.pkg:
|
||||
success = False
|
||||
|
||||
try:
|
||||
success = self.manager.install(self.pkg.model, self.root_password, self)
|
||||
if self.pkg.model.supports_backup():
|
||||
if not self.request_backup(read_config(), 'install', self.i18n, self.root_pwd):
|
||||
self.signal_finished.emit({'success': False, 'pkg': self.pkg})
|
||||
return
|
||||
|
||||
if success and self.disk_cache:
|
||||
try:
|
||||
success = self.manager.install(self.pkg.model, self.root_pwd, self)
|
||||
|
||||
if success:
|
||||
self.pkg.model.installed = True
|
||||
|
||||
if self.pkg.model.supports_disk_cache():
|
||||
@@ -405,6 +691,9 @@ class AnimateProgress(QThread):
|
||||
self.increment = 0.5
|
||||
self.paused = False
|
||||
|
||||
if self.progress_value >= val:
|
||||
self.progress_value = val
|
||||
|
||||
def pause(self):
|
||||
self.paused = True
|
||||
|
||||
@@ -440,47 +729,62 @@ class AnimateProgress(QThread):
|
||||
self._reset()
|
||||
|
||||
|
||||
class VerifyModels(QThread):
|
||||
class NotifyPackagesReady(QThread):
|
||||
|
||||
signal_updates = pyqtSignal()
|
||||
signal_finished = pyqtSignal()
|
||||
signal_changed = pyqtSignal(int)
|
||||
|
||||
def __init__(self, apps: List[PackageView] = None):
|
||||
super(VerifyModels, self).__init__()
|
||||
self.apps = apps
|
||||
def __init__(self, pkgs: List[PackageView] = None):
|
||||
super(NotifyPackagesReady, self).__init__()
|
||||
self.pkgs = pkgs
|
||||
self.work = True
|
||||
|
||||
def run(self):
|
||||
timeout = datetime.now() + timedelta(seconds=15)
|
||||
|
||||
if self.apps:
|
||||
|
||||
stop_at = datetime.utcnow() + timedelta(seconds=30)
|
||||
last_ready = 0
|
||||
|
||||
while True:
|
||||
|
||||
to_verify = {p.table_index: p for p in self.pkgs}
|
||||
while self.work and datetime.now() < timeout:
|
||||
to_remove = []
|
||||
for idx, pkg in to_verify.items():
|
||||
if not self.work:
|
||||
break
|
||||
elif pkg.model.status == PackageStatus.READY:
|
||||
to_remove.append(idx)
|
||||
if pkg.status == PackageViewStatus.LOADING:
|
||||
self.signal_changed.emit(pkg.table_index)
|
||||
|
||||
current_ready = 0
|
||||
if not self.work:
|
||||
break
|
||||
|
||||
for app in self.apps:
|
||||
current_ready += 1 if app.model.status == PackageStatus.READY else 0
|
||||
if datetime.now() >= timeout:
|
||||
break
|
||||
|
||||
if current_ready > last_ready:
|
||||
last_ready = current_ready
|
||||
self.signal_updates.emit()
|
||||
for idx in to_remove:
|
||||
del to_verify[idx]
|
||||
|
||||
if current_ready == len(self.apps):
|
||||
self.signal_updates.emit()
|
||||
break
|
||||
if not to_verify:
|
||||
break
|
||||
|
||||
if stop_at <= datetime.utcnow():
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.1)
|
||||
|
||||
self.pkgs = None
|
||||
self.work = True
|
||||
self.apps = None
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
class NotifyInstalledLoaded(QThread):
|
||||
signal_loaded = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(NotifyInstalledLoaded, self).__init__()
|
||||
self.loaded = False
|
||||
|
||||
def notify_loaded(self):
|
||||
self.loaded = True
|
||||
|
||||
def run(self):
|
||||
time.sleep(0.1)
|
||||
self.signal_loaded.emit()
|
||||
|
||||
|
||||
class FindSuggestions(AsyncAction):
|
||||
@@ -560,30 +864,37 @@ class ApplyFilters(AsyncAction):
|
||||
|
||||
class CustomAction(AsyncAction):
|
||||
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, custom_action: PackageAction = None, pkg: PackageView = None, root_password: str = None):
|
||||
def __init__(self, manager: SoftwareManager, i18n: I18n, custom_action: CustomSoftwareAction = None, pkg: PackageView = None, root_password: str = None):
|
||||
super(CustomAction, self).__init__()
|
||||
self.manager = manager
|
||||
self.pkg = pkg
|
||||
self.custom_action = custom_action
|
||||
self.root_password = root_password
|
||||
self.root_pwd = root_password
|
||||
self.i18n = i18n
|
||||
|
||||
def run(self):
|
||||
success = True
|
||||
if self.pkg:
|
||||
try:
|
||||
success = self.manager.execute_custom_action(action=self.custom_action,
|
||||
pkg=self.pkg.model,
|
||||
root_password=self.root_password,
|
||||
watcher=self)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
success = False
|
||||
self.signal_output.emit(self.i18n['internet.required'])
|
||||
if self.custom_action.backup:
|
||||
app_config = read_config()
|
||||
if not self.request_backup(app_config, None, self.i18n, self.root_pwd):
|
||||
self.notify_finished({'success': False, 'pkg': self.pkg})
|
||||
self.pkg = None
|
||||
self.custom_action = None
|
||||
self.root_pwd = None
|
||||
return
|
||||
|
||||
try:
|
||||
success = self.manager.execute_custom_action(action=self.custom_action,
|
||||
pkg=self.pkg.model if self.pkg else None,
|
||||
root_password=self.root_pwd,
|
||||
watcher=self)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
success = False
|
||||
self.signal_output.emit(self.i18n['internet.required'])
|
||||
|
||||
self.notify_finished({'success': success, 'pkg': self.pkg})
|
||||
self.pkg = None
|
||||
self.custom_action = None
|
||||
self.root_password = None
|
||||
self.root_pwd = None
|
||||
|
||||
|
||||
class GetScreenshots(AsyncAction):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
from bauh.api.abstract.model import SoftwarePackage
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageStatus
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
|
||||
class PackageViewStatus(Enum):
|
||||
@@ -8,13 +9,22 @@ class PackageViewStatus(Enum):
|
||||
READY = 1
|
||||
|
||||
|
||||
def get_type_label(type_: str, gem: str, i18n: I18n) -> str:
|
||||
type_label = 'gem.{}.type.{}.label'.format(gem, type_)
|
||||
return i18n.get(type_label, type_.capitalize()).strip()
|
||||
|
||||
|
||||
class PackageView:
|
||||
|
||||
def __init__(self, model: SoftwarePackage):
|
||||
def __init__(self, model: SoftwarePackage, i18n: I18n):
|
||||
self.model = model
|
||||
self.update_checked = model.update
|
||||
self.status = PackageViewStatus.LOADING
|
||||
self.status = PackageViewStatus.LOADING if model.status == PackageStatus.LOADING_DATA else PackageViewStatus.READY
|
||||
self.table_index = -1
|
||||
self.i18n = i18n
|
||||
|
||||
def get_type_label(self) -> str:
|
||||
return get_type_label(self.model.get_type(), self.model.gem_name, self.i18n)
|
||||
|
||||
def __repr__(self):
|
||||
return '{} ( {} )'.format(self.model.name, self.model.get_type())
|
||||
return '{} ( {} )'.format(self.model.name, self.get_type_label())
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import List, Type, Set
|
||||
from typing import List, Type, Set, Tuple
|
||||
|
||||
from PyQt5.QtCore import QEvent, Qt, QSize, pyqtSignal, QCoreApplication
|
||||
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent
|
||||
from PyQt5.QtCore import QEvent, Qt, QSize, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
|
||||
QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy
|
||||
QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \
|
||||
QMenu, QAction
|
||||
|
||||
from bauh import LOGS_PATH
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageAction
|
||||
from bauh.api.abstract.model import SoftwarePackage
|
||||
from bauh.api.abstract.view import MessageType
|
||||
from bauh.api.http import HttpClient
|
||||
from bauh.commons import user
|
||||
from bauh.commons.html import bold
|
||||
from bauh.gems.web import TEMP_PATH
|
||||
from bauh.view.core.tray_client import notify_tray
|
||||
from bauh.view.qt import dialog, commons, qt_utils, root
|
||||
from bauh.view.qt.about import AboutDialog
|
||||
from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton
|
||||
@@ -27,10 +30,11 @@ from bauh.view.qt.info import InfoDialog
|
||||
from bauh.view.qt.root import ask_root_password
|
||||
from bauh.view.qt.screenshots import ScreenshotsDialog
|
||||
from bauh.view.qt.settings import SettingsWindow
|
||||
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
||||
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \
|
||||
AsyncAction, LaunchApp, ApplyFilters, CustomAction, GetScreenshots
|
||||
from bauh.view.qt.view_model import PackageView
|
||||
from bauh.view.qt.thread import UpgradeSelected, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
||||
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, NotifyPackagesReady, FindSuggestions, \
|
||||
ListWarnings, \
|
||||
AsyncAction, LaunchApp, ApplyFilters, CustomSoftwareAction, GetScreenshots, CustomAction, NotifyInstalledLoaded
|
||||
from bauh.view.qt.view_model import PackageView, PackageViewStatus
|
||||
from bauh.view.util import util, resource
|
||||
from bauh.view.util.translation import I18n
|
||||
|
||||
@@ -49,12 +53,11 @@ class ManageWindow(QWidget):
|
||||
signal_table_update = pyqtSignal()
|
||||
|
||||
def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: dict,
|
||||
context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, icon: QIcon, tray_icon=None):
|
||||
context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, icon: QIcon):
|
||||
super(ManageWindow, self).__init__()
|
||||
self.i18n = i18n
|
||||
self.logger = logger
|
||||
self.manager = manager
|
||||
self.tray_icon = tray_icon
|
||||
self.working = False # restrict the number of threaded actions
|
||||
self.pkgs = [] # packages current loaded in the table
|
||||
self.pkgs_available = [] # all packages loaded in memory
|
||||
@@ -188,7 +191,7 @@ class ManageWindow(QWidget):
|
||||
self.bt_installed.setToolTip(self.i18n['manage_window.bt.installed.tooltip'])
|
||||
self.bt_installed.setIcon(QIcon(resource.get_path('img/disk.svg')))
|
||||
self.bt_installed.setText(self.i18n['manage_window.bt.installed.text'].capitalize())
|
||||
self.bt_installed.clicked.connect(self._show_installed)
|
||||
self.bt_installed.clicked.connect(self._begin_loading_installed)
|
||||
self.bt_installed.setStyleSheet(toolbar_button_style('#A94E0A'))
|
||||
self.ref_bt_installed = self.toolbar.addWidget(self.bt_installed)
|
||||
toolbar_bts.append(self.bt_installed)
|
||||
@@ -211,7 +214,7 @@ class ManageWindow(QWidget):
|
||||
self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
|
||||
self.bt_refresh.setText(self.i18n['manage_window.bt.refresh.text'])
|
||||
self.bt_refresh.setStyleSheet(toolbar_button_style('#2368AD'))
|
||||
self.bt_refresh.clicked.connect(lambda: self.refresh_apps(keep_console=False))
|
||||
self.bt_refresh.clicked.connect(lambda: self.refresh_packages(keep_console=False))
|
||||
toolbar_bts.append(self.bt_refresh)
|
||||
self.ref_bt_refresh = self.toolbar.addWidget(self.bt_refresh)
|
||||
|
||||
@@ -238,9 +241,7 @@ class ManageWindow(QWidget):
|
||||
|
||||
self.layout.addWidget(self.toolbar)
|
||||
|
||||
self.table_apps = AppsTable(self, self.icon_cache,
|
||||
disk_cache=bool(self.config['disk_cache']['enabled']),
|
||||
download_icons=bool(self.config['download']['icons']))
|
||||
self.table_apps = AppsTable(self, self.icon_cache, download_icons=bool(self.config['download']['icons']))
|
||||
self.table_apps.change_headers_policy()
|
||||
|
||||
self.layout.addWidget(self.table_apps)
|
||||
@@ -275,9 +276,9 @@ class ManageWindow(QWidget):
|
||||
self.layout.addWidget(self.toolbar_substatus)
|
||||
self._change_label_substatus('')
|
||||
|
||||
self.thread_update = self._bind_async_action(UpdateSelectedApps(self.manager, self.i18n), finished_call=self._finish_update_selected)
|
||||
self.thread_update = self._bind_async_action(UpgradeSelected(self.manager, self.i18n), finished_call=self._finish_upgrade_selected)
|
||||
self.thread_refresh = self._bind_async_action(RefreshApps(self.manager), finished_call=self._finish_refresh_apps, only_finished=True)
|
||||
self.thread_uninstall = self._bind_async_action(UninstallApp(self.manager, self.icon_cache), finished_call=self._finish_uninstall)
|
||||
self.thread_uninstall = self._bind_async_action(UninstallApp(self.manager, self.icon_cache, self.i18n), finished_call=self._finish_uninstall)
|
||||
self.thread_get_info = self._bind_async_action(GetAppInfo(self.manager), finished_call=self._finish_get_info)
|
||||
self.thread_get_history = self._bind_async_action(GetAppHistory(self.manager, self.i18n), finished_call=self._finish_get_history)
|
||||
self.thread_search = self._bind_async_action(SearchPackages(self.manager), finished_call=self._finish_search, only_finished=True)
|
||||
@@ -292,14 +293,15 @@ class ManageWindow(QWidget):
|
||||
self.thread_apply_filters.signal_table.connect(self._update_table_and_upgrades)
|
||||
self.signal_table_update.connect(self.thread_apply_filters.stop_waiting)
|
||||
|
||||
self.thread_install = InstallPackage(manager=self.manager, disk_cache=bool(self.config['disk_cache']['enabled']), icon_cache=self.icon_cache, i18n=self.i18n)
|
||||
self.thread_install = InstallPackage(manager=self.manager, icon_cache=self.icon_cache, i18n=self.i18n)
|
||||
self._bind_async_action(self.thread_install, finished_call=self._finish_install)
|
||||
|
||||
self.thread_animate_progress = AnimateProgress()
|
||||
self.thread_animate_progress.signal_change.connect(self._update_progress)
|
||||
|
||||
self.thread_verify_models = VerifyModels()
|
||||
self.thread_verify_models.signal_updates.connect(self._notify_model_data_change)
|
||||
self.thread_notify_pkgs_ready = NotifyPackagesReady()
|
||||
self.thread_notify_pkgs_ready.signal_changed.connect(self._update_package_data)
|
||||
self.thread_notify_pkgs_ready.signal_finished.connect(self._update_state_when_pkgs_ready)
|
||||
|
||||
self.toolbar_bottom = QToolBar()
|
||||
self.toolbar_bottom.setIconSize(QSize(16, 16))
|
||||
@@ -315,6 +317,15 @@ class ManageWindow(QWidget):
|
||||
|
||||
self.toolbar_bottom.addWidget(new_spacer())
|
||||
|
||||
self.custom_actions = manager.get_custom_actions()
|
||||
bt_custom_actions = IconButton(QIcon(resource.get_path('img/custom_actions.svg')),
|
||||
action=self.show_custom_actions,
|
||||
background="#669900",
|
||||
i18n=self.i18n,
|
||||
tooltip=self.i18n['manage_window.bt_custom_actions.tip'])
|
||||
bt_custom_actions.setVisible(bool(self.custom_actions))
|
||||
self.ref_bt_custom_actions = self.toolbar_bottom.addWidget(bt_custom_actions)
|
||||
|
||||
bt_settings = IconButton(QIcon(resource.get_path('img/app_settings.svg')),
|
||||
action=self.show_settings,
|
||||
background='#12ABAB',
|
||||
@@ -351,15 +362,20 @@ class ManageWindow(QWidget):
|
||||
self.settings_window = None
|
||||
self.search_performed = False
|
||||
|
||||
def set_tray_icon(self, tray_icon):
|
||||
self.tray_icon = tray_icon
|
||||
# self.combo_styles.show_panel_after_restart = bool(tray_icon)
|
||||
self.thread_load_installed = NotifyInstalledLoaded()
|
||||
self.thread_load_installed.signal_loaded.connect(self._finish_loading_installed)
|
||||
|
||||
def update_custom_actions(self):
|
||||
self.custom_actions = self.manager.get_custom_actions()
|
||||
self.ref_bt_custom_actions.setVisible(bool(self.custom_actions))
|
||||
|
||||
def _update_process_progress(self, val: int):
|
||||
if self.progress_controll_enabled:
|
||||
self.thread_animate_progress.set_progress(val)
|
||||
|
||||
def apply_filters_async(self):
|
||||
self.thread_notify_pkgs_ready.work = False
|
||||
self.thread_notify_pkgs_ready.wait(5)
|
||||
self.label_status.setText(self.i18n['manage_window.status.filtering'] + '...')
|
||||
|
||||
self.ref_toolbar_search.setVisible(False)
|
||||
@@ -370,19 +386,32 @@ class ManageWindow(QWidget):
|
||||
self.thread_apply_filters.filters = self._gen_filters()
|
||||
self.thread_apply_filters.pkgs = self.pkgs_available
|
||||
self.thread_apply_filters.start()
|
||||
self.checkbox_only_apps.setEnabled(False)
|
||||
self.combo_categories.setEnabled(False)
|
||||
self.combo_filter_type.setEnabled(False)
|
||||
self.input_name_filter.setEnabled(False)
|
||||
self.checkbox_updates.setEnabled(False)
|
||||
self.table_apps.setEnabled(False)
|
||||
|
||||
def _update_table_and_upgrades(self, pkgs_info: dict):
|
||||
self._update_table(pkgs_info=pkgs_info, signal=True)
|
||||
self.update_bt_upgrade(pkgs_info)
|
||||
|
||||
if self.pkgs_available:
|
||||
self._notify_model_data_change()
|
||||
self.thread_verify_models.work = False
|
||||
self.thread_verify_models.wait(50)
|
||||
self.thread_verify_models.apps = self.pkgs_available
|
||||
self.thread_verify_models.start()
|
||||
if self.pkgs:
|
||||
self._update_state_when_pkgs_ready()
|
||||
self.thread_notify_pkgs_ready.work = False
|
||||
self.thread_notify_pkgs_ready.wait(5)
|
||||
self.thread_notify_pkgs_ready.pkgs = self.pkgs
|
||||
self.thread_notify_pkgs_ready.work = True
|
||||
self.thread_notify_pkgs_ready.start()
|
||||
|
||||
def _finish_apply_filters_async(self, success: bool):
|
||||
self.checkbox_only_apps.setEnabled(True)
|
||||
self.checkbox_updates.setEnabled(True)
|
||||
self.combo_categories.setEnabled(True)
|
||||
self.combo_filter_type.setEnabled(True)
|
||||
self.input_name_filter.setEnabled(True)
|
||||
self.table_apps.setEnabled(True)
|
||||
self.label_status.setText('')
|
||||
self.ref_toolbar_search.setVisible(True)
|
||||
|
||||
@@ -399,7 +428,8 @@ class ManageWindow(QWidget):
|
||||
action.signal_status.connect(self._change_label_status)
|
||||
action.signal_substatus.connect(self._change_label_substatus)
|
||||
action.signal_progress.connect(self._update_process_progress)
|
||||
action.signal_root_password.connect(self._ask_root_password)
|
||||
action.signal_progress_control.connect(self.set_progress_controll)
|
||||
action.signal_root_password.connect(self._pause_and_ask_root_password)
|
||||
|
||||
self.signal_user_res.connect(action.confirm)
|
||||
self.signal_root_password.connect(action.set_root_password)
|
||||
@@ -414,14 +444,16 @@ class ManageWindow(QWidget):
|
||||
components=msg['components'],
|
||||
confirmation_label=msg['confirmation_label'],
|
||||
deny_label=msg['deny_label'],
|
||||
deny_button=msg['deny_button'],
|
||||
screen_size=self.screen_size)
|
||||
res = diag.is_confirmed()
|
||||
self.thread_animate_progress.animate()
|
||||
self.signal_user_res.emit(res)
|
||||
|
||||
def _ask_root_password(self):
|
||||
def _pause_and_ask_root_password(self):
|
||||
self.thread_animate_progress.pause()
|
||||
password, valid = root.ask_root_password(self.i18n)
|
||||
password, valid = root.ask_root_password(self.context, self.i18n)
|
||||
|
||||
self.thread_animate_progress.animate()
|
||||
self.signal_root_password.emit(password, valid)
|
||||
|
||||
@@ -443,19 +475,23 @@ class ManageWindow(QWidget):
|
||||
def verify_warnings(self):
|
||||
self.thread_warnings.start()
|
||||
|
||||
def _show_installed(self):
|
||||
def _begin_loading_installed(self):
|
||||
if self.pkgs_installed:
|
||||
self.finish_action()
|
||||
self.search_performed = False
|
||||
self.ref_bt_upgrade.setVisible(True)
|
||||
self.ref_checkbox_only_apps.setVisible(True)
|
||||
self.input_search.setText('')
|
||||
self.input_name_filter.setText('')
|
||||
self.update_pkgs(new_pkgs=None, as_installed=True)
|
||||
self._begin_action(self.i18n['manage_window.status.installed'], keep_bt_installed=False, clear_filters=not self.recent_uninstall)
|
||||
self.thread_load_installed.start()
|
||||
|
||||
def _finish_loading_installed(self):
|
||||
self.finish_action()
|
||||
self.update_pkgs(new_pkgs=None, as_installed=True)
|
||||
|
||||
def _show_about(self):
|
||||
if self.dialog_about is None:
|
||||
self.dialog_about = AboutDialog(self.i18n)
|
||||
self.dialog_about = AboutDialog(self.config)
|
||||
|
||||
self.dialog_about.show()
|
||||
|
||||
@@ -476,12 +512,20 @@ class ManageWindow(QWidget):
|
||||
self.category_filter = self.combo_categories.itemData(idx)
|
||||
self.apply_filters_async()
|
||||
|
||||
def _notify_model_data_change(self):
|
||||
self.table_apps.fill_async_data()
|
||||
def _update_state_when_pkgs_ready(self):
|
||||
if self.progress_bar.isVisible():
|
||||
return
|
||||
|
||||
if not self.recent_installation:
|
||||
if not self.ref_progress_bar.isVisible():
|
||||
self._reload_categories()
|
||||
self._reload_categories()
|
||||
|
||||
self.resize_and_center()
|
||||
|
||||
def _update_package_data(self, idx: int):
|
||||
if self.table_apps.isEnabled():
|
||||
pkg = self.pkgs[idx]
|
||||
pkg.status = PackageViewStatus.READY
|
||||
self.table_apps.update_package(pkg)
|
||||
|
||||
def _reload_categories(self):
|
||||
categories = set()
|
||||
@@ -500,14 +544,6 @@ class ManageWindow(QWidget):
|
||||
policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
|
||||
self.table_apps.change_headers_policy(policy)
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.tray_icon:
|
||||
event.ignore()
|
||||
self.hide()
|
||||
self._handle_console_option(False)
|
||||
else:
|
||||
QCoreApplication.exit() # needed because QuitOnLastWindowClosed is disabled
|
||||
|
||||
def _handle_console(self, checked: bool):
|
||||
|
||||
if checked:
|
||||
@@ -524,7 +560,7 @@ class ManageWindow(QWidget):
|
||||
self.checkbox_console.setChecked(False)
|
||||
self.textarea_output.hide()
|
||||
|
||||
def refresh_apps(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None):
|
||||
def refresh_packages(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None):
|
||||
self.recent_installation = False
|
||||
self.input_search.clear()
|
||||
|
||||
@@ -561,20 +597,16 @@ class ManageWindow(QWidget):
|
||||
self._hide_fields_after_recent_installation()
|
||||
|
||||
def uninstall_app(self, app: PackageView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('uninstall', app.model)
|
||||
pwd, proceed = self._ask_root_password('uninstall', app)
|
||||
|
||||
if not user.is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.i18n)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
if not proceed:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.i18n['manage_window.status.uninstalling'], app.model.name), clear_filters=False)
|
||||
|
||||
self.thread_uninstall.app = app
|
||||
self.thread_uninstall.root_password = pwd
|
||||
self.thread_uninstall.root_pwd = pwd
|
||||
self.thread_uninstall.start()
|
||||
|
||||
def run_app(self, app: PackageView):
|
||||
@@ -595,10 +627,9 @@ class ManageWindow(QWidget):
|
||||
only_pkg_type = False
|
||||
|
||||
self.recent_uninstall = True
|
||||
self.refresh_apps(pkg_types={pkgv.model.__class__} if only_pkg_type else None)
|
||||
self.refresh_packages(pkg_types={pkgv.model.__class__} if only_pkg_type else None)
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.verify_updates()
|
||||
notify_tray()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
util.notify_user('{}: {}'.format(pkgv.model.name, self.i18n['notification.uninstall.failed']))
|
||||
@@ -615,10 +646,9 @@ class ManageWindow(QWidget):
|
||||
if self._can_notify_user():
|
||||
util.notify_user('{} {}'.format(res['app'], self.i18n['downgraded']))
|
||||
|
||||
self.refresh_apps(pkg_types={res['app'].model.__class__} if len(self.pkgs) > 1 else None)
|
||||
self.refresh_packages(pkg_types={res['app'].model.__class__} if len(self.pkgs) > 1 else None)
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.verify_updates(notify_user=False)
|
||||
notify_tray()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
util.notify_user(self.i18n['notification.downgrade.failed'])
|
||||
@@ -638,7 +668,7 @@ class ManageWindow(QWidget):
|
||||
def _update_table(self, pkgs_info: dict, signal: bool = False):
|
||||
self.pkgs = pkgs_info['pkgs_displayed']
|
||||
|
||||
self.table_apps.update_pkgs(self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0)
|
||||
self.table_apps.update_packages(self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0)
|
||||
|
||||
if not self._maximized:
|
||||
self.table_apps.change_headers_policy(QHeaderView.Stretch)
|
||||
@@ -692,20 +722,20 @@ class ManageWindow(QWidget):
|
||||
setattr(self, attr, checked)
|
||||
checkbox.blockSignals(False)
|
||||
|
||||
def _gen_filters(self, updates: int = 0, ignore_updates: bool = False) -> dict:
|
||||
def _gen_filters(self, ignore_updates: bool = False) -> dict:
|
||||
return {
|
||||
'only_apps': False if self.search_performed else self.filter_only_apps,
|
||||
'type': self.type_filter,
|
||||
'category': self.category_filter,
|
||||
'updates': False if ignore_updates else self.filter_updates,
|
||||
'name': self.input_name_filter.get_text().lower() if self.input_name_filter.get_text() else None,
|
||||
'display_limit': self.display_limit if updates <= 0 else None
|
||||
'display_limit': None if self.filter_updates else self.display_limit
|
||||
}
|
||||
|
||||
def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False, keep_filters: bool = False):
|
||||
self.input_name_filter.setText('')
|
||||
pkgs_info = commons.new_pkgs_info()
|
||||
filters = self._gen_filters(ignore_updates)
|
||||
filters = self._gen_filters(ignore_updates=ignore_updates)
|
||||
|
||||
if new_pkgs is not None:
|
||||
old_installed = None
|
||||
@@ -715,7 +745,7 @@ class ManageWindow(QWidget):
|
||||
self.pkgs_installed = []
|
||||
|
||||
for pkg in new_pkgs:
|
||||
app_model = PackageView(model=pkg)
|
||||
app_model = PackageView(model=pkg, i18n=self.i18n)
|
||||
commons.update_info(app_model, pkgs_info)
|
||||
commons.apply_filters(app_model, filters, pkgs_info)
|
||||
|
||||
@@ -764,8 +794,8 @@ class ManageWindow(QWidget):
|
||||
self._update_table(pkgs_info=pkgs_info)
|
||||
|
||||
if new_pkgs:
|
||||
self.thread_verify_models.apps = self.pkgs
|
||||
self.thread_verify_models.start()
|
||||
self.thread_notify_pkgs_ready.pkgs = self.pkgs
|
||||
self.thread_notify_pkgs_ready.start()
|
||||
|
||||
if self.pkgs_installed:
|
||||
self.ref_bt_installed.setVisible(not as_installed and not self.recent_installation)
|
||||
@@ -774,7 +804,7 @@ class ManageWindow(QWidget):
|
||||
|
||||
def _apply_filters(self, pkgs_info: dict, ignore_updates: bool):
|
||||
pkgs_info['pkgs_displayed'] = []
|
||||
filters = self._gen_filters(updates=pkgs_info['updates'], ignore_updates=ignore_updates)
|
||||
filters = self._gen_filters(ignore_updates=ignore_updates)
|
||||
for pkgv in pkgs_info['pkgs']:
|
||||
commons.apply_filters(pkgv, filters, pkgs_info)
|
||||
|
||||
@@ -795,7 +825,7 @@ class ManageWindow(QWidget):
|
||||
|
||||
sel_type = -1
|
||||
for idx, item in enumerate(available_types.items()):
|
||||
app_type, icon_path = item[0], item[1]
|
||||
app_type, icon_path, label = item[0], item[1]['icon'], item[1]['label']
|
||||
|
||||
icon = self.cache_type_filter_icons.get(app_type)
|
||||
|
||||
@@ -803,7 +833,7 @@ class ManageWindow(QWidget):
|
||||
icon = QIcon(icon_path)
|
||||
self.cache_type_filter_icons[app_type] = icon
|
||||
|
||||
self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type)
|
||||
self.combo_filter_type.addItem(icon, label, app_type)
|
||||
|
||||
if keeping_selected and app_type == self.type_filter:
|
||||
sel_type = idx + 1
|
||||
@@ -816,7 +846,6 @@ class ManageWindow(QWidget):
|
||||
self.ref_combo_filter_type.setVisible(False)
|
||||
|
||||
def _update_categories(self, categories: Set[str] = None, keep_selected: bool = False):
|
||||
|
||||
if categories is None:
|
||||
self.ref_combo_categories.setVisible(self.combo_categories.count() > 1)
|
||||
else:
|
||||
@@ -835,9 +864,8 @@ class ManageWindow(QWidget):
|
||||
cat_list.sort()
|
||||
|
||||
for idx, c in enumerate(cat_list):
|
||||
i18n_cat = self.i18n.get(c)
|
||||
cat_label = i18n_cat if i18n_cat else c
|
||||
self.combo_categories.addItem(cat_label.capitalize(), c)
|
||||
i18n_cat = self.i18n.get('category.{}'.format(c), self.i18n.get(c, c))
|
||||
self.combo_categories.addItem(i18n_cat.capitalize(), c)
|
||||
|
||||
if keeping_selected and c == self.category_filter:
|
||||
selected_cat = idx + 1
|
||||
@@ -863,49 +891,45 @@ class ManageWindow(QWidget):
|
||||
|
||||
qt_utils.centralize(self)
|
||||
|
||||
def set_progress_controll(self, enabled: bool):
|
||||
self.progress_controll_enabled = enumerate
|
||||
|
||||
def update_selected(self):
|
||||
if self.pkgs:
|
||||
requires_root = False
|
||||
if dialog.ask_confirmation(title=self.i18n['manage_window.upgrade_all.popup.title'],
|
||||
body=self.i18n['manage_window.upgrade_all.popup.body'],
|
||||
i18n=self.i18n,
|
||||
widgets=[UpdateToggleButton(None, self, self.i18n, clickable=False)]):
|
||||
|
||||
to_update = []
|
||||
self._handle_console_option(True)
|
||||
self._begin_action(self.i18n['manage_window.status.upgrading'])
|
||||
self.thread_update.pkgs = self.pkgs
|
||||
self.thread_update.start()
|
||||
|
||||
for app_v in self.pkgs:
|
||||
if app_v.update_checked:
|
||||
to_update.append(app_v)
|
||||
|
||||
if self.manager.requires_root('update', app_v.model):
|
||||
requires_root = True
|
||||
|
||||
if to_update and dialog.ask_confirmation(title=self.i18n['manage_window.upgrade_all.popup.title'],
|
||||
body=self.i18n['manage_window.upgrade_all.popup.body'],
|
||||
i18n=self.i18n,
|
||||
widgets=[UpdateToggleButton(None, self, self.i18n, clickable=False)]):
|
||||
pwd = None
|
||||
|
||||
if not user.is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.i18n)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self.progress_controll_enabled = len(to_update) == 1
|
||||
self._begin_action(self.i18n['manage_window.status.upgrading'])
|
||||
self.thread_update.pkgs = to_update
|
||||
self.thread_update.root_password = pwd
|
||||
self.thread_update.start()
|
||||
|
||||
def _finish_update_selected(self, res: dict):
|
||||
def _finish_upgrade_selected(self, res: dict):
|
||||
self.finish_action()
|
||||
|
||||
if res.get('id'):
|
||||
output = self.textarea_output.toPlainText()
|
||||
|
||||
if output:
|
||||
try:
|
||||
Path(UpgradeSelected.LOGS_DIR).mkdir(parents=True, exist_ok=True)
|
||||
logs_path = '{}/{}.log'.format(UpgradeSelected.LOGS_DIR, res['id'])
|
||||
with open(logs_path, 'w+') as f:
|
||||
f.write(output)
|
||||
|
||||
self.textarea_output.appendPlainText('\n*Upgrade summary generated at: {}'.format(UpgradeSelected.SUMMARY_FILE.format(res['id'])))
|
||||
self.textarea_output.appendPlainText('*Upgrade logs generated at: {}'.format(logs_path))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
if res['success']:
|
||||
if self._can_notify_user():
|
||||
util.notify_user('{} {}'.format(res['updated'], self.i18n['notification.update_selected.success']))
|
||||
|
||||
self.refresh_apps(pkg_types=res['types'])
|
||||
self.refresh_packages(pkg_types=res['types'])
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.verify_updates()
|
||||
notify_tray()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
util.notify_user(self.i18n['notification.update_selected.failed'])
|
||||
@@ -920,6 +944,7 @@ class ManageWindow(QWidget):
|
||||
self.ref_input_name_filter.setVisible(False)
|
||||
self.ref_combo_filter_type.setVisible(False)
|
||||
self.ref_combo_categories.setVisible(False)
|
||||
self.ref_bt_custom_actions.setVisible(False)
|
||||
self.ref_bt_settings.setVisible(False)
|
||||
self.ref_bt_about.setVisible(False)
|
||||
self.thread_animate_progress.stop = False
|
||||
@@ -961,6 +986,7 @@ class ManageWindow(QWidget):
|
||||
self.progress_bar.setTextVisible(False)
|
||||
|
||||
self._change_label_substatus('')
|
||||
self.ref_bt_custom_actions.setVisible(bool(self.custom_actions))
|
||||
self.ref_bt_settings.setVisible(True)
|
||||
self.ref_bt_about.setVisible(True)
|
||||
|
||||
@@ -997,20 +1023,16 @@ class ManageWindow(QWidget):
|
||||
self.ref_input_name_filter.setVisible(False)
|
||||
|
||||
def downgrade(self, pkgv: PackageView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('downgrade', pkgv.model)
|
||||
pwd, proceed = self._ask_root_password('downgrade', pkgv)
|
||||
|
||||
if not user.is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.i18n)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
if not proceed:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.i18n['manage_window.status.downgrading'], pkgv.model.name))
|
||||
|
||||
self.thread_downgrade.app = pkgv
|
||||
self.thread_downgrade.root_password = pwd
|
||||
self.thread_downgrade.root_pwd = pwd
|
||||
self.thread_downgrade.start()
|
||||
|
||||
def get_app_info(self, pkg: dict):
|
||||
@@ -1043,7 +1065,7 @@ class ManageWindow(QWidget):
|
||||
body=self.i18n['popup.screenshots.no_screenshot.body'].format(bold(res['pkg'].model.name)),
|
||||
type_=MessageType.ERROR)
|
||||
|
||||
def get_app_history(self, app: dict):
|
||||
def get_app_history(self, app: PackageView):
|
||||
self._handle_console_option(False)
|
||||
self._begin_action(self.i18n['manage_window.status.history'])
|
||||
|
||||
@@ -1062,6 +1084,10 @@ class ManageWindow(QWidget):
|
||||
self._handle_console_option(True)
|
||||
self.textarea_output.appendPlainText(res['error'])
|
||||
self.checkbox_console.setChecked(True)
|
||||
elif not res['history'].history:
|
||||
dialog.show_message(title=self.i18n['action.history.no_history.title'],
|
||||
body=self.i18n['action.history.no_history.body'].format(bold(res['history'].pkg.name)),
|
||||
type_=MessageType.WARNING)
|
||||
else:
|
||||
dialog_history = HistoryDialog(res['history'], self.icon_cache, self.i18n)
|
||||
dialog_history.exec_()
|
||||
@@ -1090,21 +1116,29 @@ class ManageWindow(QWidget):
|
||||
else:
|
||||
dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING)
|
||||
|
||||
def install(self, pkg: PackageView):
|
||||
def _ask_root_password(self, action: str, pkg: PackageView) -> Tuple[str, bool]:
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('install', pkg.model)
|
||||
requires_root = self.manager.requires_root(action, pkg.model)
|
||||
|
||||
if not user.is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.i18n)
|
||||
pwd, ok = ask_root_password(self.context, self.i18n)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
return pwd, False
|
||||
|
||||
return pwd, True
|
||||
|
||||
def install(self, pkg: PackageView):
|
||||
pwd, proceed = self._ask_root_password('install', pkg)
|
||||
|
||||
if not proceed:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.i18n['manage_window.status.installing'], pkg.model.name))
|
||||
|
||||
self.thread_install.pkg = pkg
|
||||
self.thread_install.root_password = pwd
|
||||
self.thread_install.root_pwd = pwd
|
||||
self.thread_install.start()
|
||||
|
||||
def _finish_install(self, res: dict):
|
||||
@@ -1114,7 +1148,7 @@ class ManageWindow(QWidget):
|
||||
console_output = self.textarea_output.toPlainText()
|
||||
|
||||
if console_output:
|
||||
log_path = '{}/logs/install/{}/{}'.format(TEMP_PATH, res['pkg'].model.get_type(), res['pkg'].model.name)
|
||||
log_path = '{}/install/{}/{}'.format(LOGS_PATH, res['pkg'].model.get_type(), res['pkg'].model.name)
|
||||
try:
|
||||
Path(log_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -1146,27 +1180,34 @@ class ManageWindow(QWidget):
|
||||
def _finish_run_app(self, success: bool):
|
||||
self.finish_action()
|
||||
|
||||
def execute_custom_action(self, pkg: PackageView, action: PackageAction):
|
||||
def execute_custom_action(self, pkg: PackageView, action: CustomSoftwareAction):
|
||||
|
||||
if pkg is None and not dialog.ask_confirmation(title=self.i18n['confirmation'].capitalize(),
|
||||
body=self.i18n['custom_action.proceed_with'].capitalize().format('"{}"'.format(self.i18n[action.i18_label_key].capitalize())),
|
||||
icon=QIcon(action.icon_path) if action.icon_path else QIcon(resource.get_path('img/logo.svg')),
|
||||
i18n=self.i18n):
|
||||
return False
|
||||
|
||||
pwd = None
|
||||
|
||||
if not user.is_root() and action.requires_root:
|
||||
pwd, ok = ask_root_password(self.i18n)
|
||||
pwd, ok = ask_root_password(self.context, self.i18n)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.i18n[action.i18n_status_key], pkg.model.name))
|
||||
self._begin_action('{}{}'.format(self.i18n[action.i18n_status_key], ' {}'.format(pkg.model.name) if pkg else ''))
|
||||
|
||||
self.thread_custom_action.pkg = pkg
|
||||
self.thread_custom_action.root_password = pwd
|
||||
self.thread_custom_action.root_pwd = pwd
|
||||
self.thread_custom_action.custom_action = action
|
||||
self.thread_custom_action.start()
|
||||
|
||||
def _finish_custom_action(self, res: dict):
|
||||
self.finish_action()
|
||||
if res['success']:
|
||||
self.refresh_apps(pkg_types={res['pkg'].model.__class__})
|
||||
self.refresh_packages(pkg_types={res['pkg'].model.__class__} if res['pkg'] else None)
|
||||
else:
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
@@ -1174,9 +1215,24 @@ class ManageWindow(QWidget):
|
||||
if self.settings_window:
|
||||
self.settings_window.handle_display()
|
||||
else:
|
||||
self.settings_window = SettingsWindow(self.manager, self.i18n, self.screen_size, self.tray_icon, self)
|
||||
self.settings_window = SettingsWindow(self.manager, self.i18n, self.screen_size, self)
|
||||
self.settings_window.setMinimumWidth(int(self.screen_size.width() / 4))
|
||||
self.settings_window.resize(self.size())
|
||||
self.settings_window.adjustSize()
|
||||
qt_utils.centralize(self.settings_window)
|
||||
self.settings_window.show()
|
||||
|
||||
def _map_custom_action(self, action: CustomSoftwareAction) -> QAction:
|
||||
custom_action = QAction(self.i18n[action.i18_label_key])
|
||||
custom_action.setIcon(QIcon(action.icon_path))
|
||||
custom_action.triggered.connect(lambda: self.execute_custom_action(None, action))
|
||||
return custom_action
|
||||
|
||||
def show_custom_actions(self):
|
||||
if self.custom_actions:
|
||||
menu_row = QMenu()
|
||||
actions = [self._map_custom_action(a) for a in self.custom_actions]
|
||||
menu_row.addActions(actions)
|
||||
menu_row.adjustSize()
|
||||
menu_row.popup(QCursor.pos())
|
||||
menu_row.exec_()
|
||||
|
||||
Reference in New Issue
Block a user