import subprocess import time from datetime import datetime, timedelta from typing import List import requests from PyQt5.QtCore import QThread, pyqtSignal from bauh.core.controller import ApplicationManager from bauh.core.exception import NoInternetException from bauh.core.model import ApplicationStatus from bauh.core.system import BauhProcess from bauh.util.cache import Cache from bauh.view.qt import dialog from bauh.view.qt.view_model import ApplicationView class AsyncAction(QThread): def notify_subproc_outputs(self, proc: BauhProcess, signal) -> bool: """ :param subproc: :param signal: :param success: :return: if the subprocess succeeded """ signal.emit(' '.join(proc.subproc.args) + '\n') success, already_succeeded = True, False for output in proc.subproc.stdout: line = output.decode().strip() if line: signal.emit(line) if proc.success_pgrase and proc.success_pgrase in line: already_succeeded = True if already_succeeded: return True for output in proc.subproc.stderr: line = output.decode().strip() if line: if proc.wrong_error_phrase and proc.wrong_error_phrase in line: continue else: success = False signal.emit(line) return success class UpdateSelectedApps(AsyncAction): signal_finished = pyqtSignal(bool, int) signal_status = pyqtSignal(str) signal_output = pyqtSignal(str) def __init__(self, manager: ApplicationManager, apps_to_update: List[ApplicationView] = None): super(UpdateSelectedApps, self).__init__() self.apps_to_update = apps_to_update self.manager = manager def run(self): success = False for app in self.apps_to_update: self.signal_status.emit(app.model.base_data.name) process = self.manager.update_and_stream(app.model) success = self.notify_subproc_outputs(process, self.signal_output) if not success: break else: self.signal_output.emit('\n') self.signal_finished.emit(success, len(self.apps_to_update)) self.apps_to_update = None class RefreshApps(QThread): signal = pyqtSignal(list) def __init__(self, manager: ApplicationManager): super(RefreshApps, self).__init__() self.manager = manager def run(self): self.signal.emit(self.manager.read_installed()) class UninstallApp(AsyncAction): signal_finished = pyqtSignal(object) signal_output = pyqtSignal(str) def __init__(self, manager: ApplicationManager, icon_cache: Cache, app: ApplicationView = None): super(UninstallApp, self).__init__() self.app = app self.manager = manager self.icon_cache = icon_cache self.root_password = None def run(self): if self.app: process = self.manager.uninstall_and_stream(self.app.model, self.root_password) success = self.notify_subproc_outputs(process, self.signal_output) if success: self.icon_cache.delete(self.app.model.base_data.icon_url) self.manager.clean_cache_for(self.app.model) self.signal_finished.emit(self.app if success else None) self.app = None self.root_password = None class DowngradeApp(AsyncAction): signal_finished = pyqtSignal(bool) signal_output = pyqtSignal(str) def __init__(self, manager: ApplicationManager, locale_keys: dict, app: ApplicationView = None): super(DowngradeApp, self).__init__() self.manager = manager self.app = app self.locale_keys = locale_keys self.root_password = None def run(self): if self.app: success = False try: process = self.manager.downgrade_app(self.app.model, self.root_password) if process is None: dialog.show_error(title=self.locale_keys['popup.downgrade.impossible.title'], body=self.locale_keys['popup.downgrade.impossible.body']) else: success = self.notify_subproc_outputs(process, self.signal_output) except (requests.exceptions.ConnectionError, NoInternetException): success = False self.signal_output.emit(self.locale_keys['internet.required']) finally: self.app = None self.root_password = None self.signal_finished.emit(success) class GetAppInfo(QThread): signal_finished = pyqtSignal(dict) def __init__(self, manager: ApplicationManager, app: ApplicationView = None): super(GetAppInfo, self).__init__() self.app = app self.manager = manager def run(self): if self.app: info = {'__app__': self.app} info.update(self.manager.get_info(self.app.model)) self.signal_finished.emit(info) self.app = None class GetAppHistory(QThread): signal_finished = pyqtSignal(dict) def __init__(self, manager: ApplicationManager, locale_keys: dict, app: ApplicationView = None): super(GetAppHistory, self).__init__() self.app = app self.manager = manager self.locale_keys = locale_keys def run(self): if self.app: try: res = {'model': self.app.model, 'history': self.manager.get_history(self.app.model)} self.signal_finished.emit(res) except (requests.exceptions.ConnectionError, NoInternetException): self.signal_finished.emit({'error': self.locale_keys['internet.required']}) finally: self.app = None class SearchApps(QThread): signal_finished = pyqtSignal(list) def __init__(self, manager: ApplicationManager): super(SearchApps, self).__init__() self.word = None self.manager = manager def run(self): apps_found = [] if self.word: res = self.manager.search(self.word) apps_found.extend(res['installed']) apps_found.extend(res['new']) self.signal_finished.emit(apps_found) self.word = None class InstallApp(AsyncAction): signal_finished = pyqtSignal(object) signal_output = pyqtSignal(str) def __init__(self, manager: ApplicationManager, disk_cache: bool, icon_cache: Cache, locale_keys: dict, app: ApplicationView = None): super(InstallApp, self).__init__() self.app = app self.manager = manager self.icon_cache = icon_cache self.disk_cache = disk_cache self.locale_keys = locale_keys self.root_password = None def run(self): if self.app: success = False try: process = self.manager.install_and_stream(self.app.model, self.root_password) success = self.notify_subproc_outputs(process, self.signal_output) if success and self.disk_cache: self.app.model.installed = True if self.app.model.supports_disk_cache(): icon_data = self.icon_cache.get(self.app.model.base_data.icon_url) self.manager.cache_to_disk(app=self.app.model, icon_bytes=icon_data.get('bytes') if icon_data else None, only_icon=False) except (requests.exceptions.ConnectionError, NoInternetException): success = False self.signal_output.emit(self.locale_keys['internet.required']) finally: self.signal_finished.emit(self.app if success else None) self.app = None class AnimateProgress(QThread): signal_change = pyqtSignal(int) def __init__(self): super(AnimateProgress, self).__init__() self.progress_value = 0 self.increment = 5 self.stop = False def run(self): current_increment = self.increment while not self.stop: self.signal_change.emit(self.progress_value) if self.progress_value == 100: current_increment = -current_increment if self.progress_value == 0: current_increment = self.increment self.progress_value += current_increment time.sleep(0.05) self.progress_value = 0 class VerifyModels(QThread): signal_updates = pyqtSignal() def __init__(self, apps: List[ApplicationView] = None): super(VerifyModels, self).__init__() self.apps = apps def run(self): if self.apps: stop_at = datetime.utcnow() + timedelta(seconds=30) last_ready = 0 while True: current_ready = 0 for app in self.apps: current_ready += 1 if app.model.status == ApplicationStatus.READY else 0 if current_ready > last_ready: last_ready = current_ready self.signal_updates.emit() if current_ready == len(self.apps): self.signal_updates.emit() break if stop_at <= datetime.utcnow(): break time.sleep(0.1) self.apps = None class RefreshApp(AsyncAction): signal_finished = pyqtSignal(bool) signal_output = pyqtSignal(str) def __init__(self, manager: ApplicationManager, app: ApplicationView = None): super(RefreshApp, self).__init__() self.app = app self.manager = manager self.root_password = None def run(self): if self.app: success = False try: process = self.manager.refresh(self.app.model, self.root_password) success = self.notify_subproc_outputs(process, self.signal_output) except (requests.exceptions.ConnectionError, NoInternetException): success = False self.signal_output.emit(self.locale_keys['internet.required']) finally: self.app = None self.signal_finished.emit(success) class FindSuggestions(AsyncAction): signal_finished = pyqtSignal(list) def __init__(self, man: ApplicationManager): super(FindSuggestions, self).__init__() self.man = man def run(self): self.signal_finished.emit(self.man.list_suggestions(limit=-1)) class ListWarnings(QThread): signal_warnings = pyqtSignal(list) def __init__(self, man: ApplicationManager, locale_keys: dict): super(QThread, self).__init__() self.locale_keys = locale_keys self.man = man def run(self): warnings = self.man.list_warnings() if warnings: self.signal_warnings.emit(warnings)