mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 01:14:15 +02:00
388 lines
12 KiB
Python
388 lines
12 KiB
Python
import subprocess
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Type, Set
|
|
|
|
import requests
|
|
from PyQt5.QtCore import QThread, pyqtSignal
|
|
from bauh_api.abstract.cache import MemoryCache
|
|
from bauh_api.abstract.controller import SoftwareManager
|
|
from bauh_api.abstract.handler import ProcessWatcher
|
|
from bauh_api.abstract.model import PackageStatus, SoftwarePackage
|
|
from bauh_api.abstract.view import InputViewComponent, MessageType
|
|
from bauh_api.exception import NoInternetException
|
|
|
|
from bauh.view.qt.view_model import PackageView
|
|
|
|
|
|
class AsyncAction(QThread, ProcessWatcher):
|
|
|
|
signal_output = pyqtSignal(str) # print messages to the terminal widget
|
|
signal_confirmation = pyqtSignal(dict) # asks the users to confirm something
|
|
signal_finished = pyqtSignal(object) # informs the main window that the action has finished
|
|
signal_message = pyqtSignal(dict) # asks the GUI to show an error popup
|
|
signal_status = pyqtSignal(str) # changes the GUI status message
|
|
signal_substatus = pyqtSignal(str) # changes the GUI substatus message
|
|
|
|
def __init__(self):
|
|
super(AsyncAction, self).__init__()
|
|
self.wait_confirmation = False
|
|
self.confirmation_res = None
|
|
|
|
def request_confirmation(self, title: str, body: str, components: List[InputViewComponent] = None, confirmation_label: str = None, deny_label: str = None) -> bool:
|
|
self.wait_confirmation = True
|
|
self.signal_confirmation.emit({'title': title, 'body': body, 'components': components, 'confirmation_label': confirmation_label, 'deny_label': deny_label})
|
|
self.wait_user()
|
|
return self.confirmation_res
|
|
|
|
def confirm(self, res: bool):
|
|
self.confirmation_res = res
|
|
self.wait_confirmation = False
|
|
|
|
def wait_user(self):
|
|
while self.wait_confirmation:
|
|
time.sleep(0.01)
|
|
|
|
def print(self, msg: str):
|
|
if msg:
|
|
self.signal_output.emit(msg)
|
|
|
|
def show_message(self, title: str, body: str, type_: MessageType = MessageType.INFO):
|
|
self.signal_message.emit({'title': title, 'body': body, 'type': type_})
|
|
|
|
def notify_finished(self, res: object):
|
|
self.signal_finished.emit(res)
|
|
|
|
def change_status(self, status: str):
|
|
self.signal_status.emit(status)
|
|
|
|
def change_substatus(self, substatus: str):
|
|
self.signal_substatus.emit(substatus)
|
|
|
|
|
|
class UpdateSelectedApps(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, locale_keys: dict, apps_to_update: List[PackageView] = None):
|
|
super(UpdateSelectedApps, self).__init__()
|
|
self.apps_to_update = apps_to_update
|
|
self.manager = manager
|
|
self.root_password = None
|
|
self.locale_keys = locale_keys
|
|
|
|
def run(self):
|
|
|
|
success = False
|
|
|
|
if self.apps_to_update:
|
|
updated, updated_types = 0, set()
|
|
for app in self.apps_to_update:
|
|
self.change_status('{} {}...'.format(self.locale_keys['manage_window.status.upgrading'], app.model.base_data.name))
|
|
success = bool(self.manager.update(app.model, self.root_password, self))
|
|
|
|
if not success:
|
|
break
|
|
else:
|
|
updated += 1
|
|
updated_types.add(app.model.__class__)
|
|
self.signal_output.emit('\n')
|
|
|
|
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})
|
|
|
|
self.apps_to_update = None
|
|
|
|
|
|
class RefreshApps(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None):
|
|
super(RefreshApps, self).__init__()
|
|
self.manager = manager
|
|
self.app = app # app that should be on list top
|
|
self.pkg_types = pkg_types
|
|
|
|
def run(self):
|
|
res = self.manager.read_installed(pkg_types=self.pkg_types)
|
|
refreshed_types = set()
|
|
|
|
if res:
|
|
idx_found, app_found = None, None
|
|
for idx, ins in enumerate(res.installed):
|
|
if self.pkg_types:
|
|
refreshed_types.add(ins.__class__)
|
|
|
|
if self.app and ins.get_type() == self.app.model.get_type() and ins.base_data.id == self.app.model.base_data.id:
|
|
idx_found = idx
|
|
app_found = ins
|
|
break
|
|
|
|
if app_found:
|
|
del res.installed[idx_found]
|
|
res.installed.insert(0, app_found)
|
|
elif self.pkg_types:
|
|
refreshed_types = self.pkg_types
|
|
|
|
self.notify_finished({'installed': res.installed, 'total': res.total, 'types': refreshed_types})
|
|
self.app = None
|
|
self.pkg_types = None
|
|
|
|
|
|
class UninstallApp(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, app: PackageView = 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:
|
|
success = self.manager.uninstall(self.app.model, self.root_password, self)
|
|
|
|
if success:
|
|
self.icon_cache.delete(self.app.model.base_data.icon_url)
|
|
self.manager.clean_cache_for(self.app.model)
|
|
|
|
self.notify_finished(self.app if success else None)
|
|
self.app = None
|
|
self.root_password = None
|
|
|
|
|
|
class DowngradeApp(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, locale_keys: dict, app: PackageView = 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:
|
|
success = self.manager.downgrade(self.app.model, self.root_password, self)
|
|
except (requests.exceptions.ConnectionError, NoInternetException):
|
|
success = False
|
|
self.print(self.locale_keys['internet.required'])
|
|
finally:
|
|
self.notify_finished({'app': self.app, 'success': success})
|
|
self.app = None
|
|
self.root_password = None
|
|
|
|
|
|
class GetAppInfo(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, app: PackageView = 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.notify_finished(info)
|
|
self.app = None
|
|
|
|
|
|
class GetAppHistory(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, locale_keys: dict, app: PackageView = None):
|
|
super(GetAppHistory, self).__init__()
|
|
self.app = app
|
|
self.manager = manager
|
|
self.locale_keys = locale_keys
|
|
|
|
def run(self):
|
|
if self.app:
|
|
try:
|
|
self.notify_finished({'history': self.manager.get_history(self.app.model)})
|
|
except (requests.exceptions.ConnectionError, NoInternetException):
|
|
self.notify_finished({'error': self.locale_keys['internet.required']})
|
|
finally:
|
|
self.app = None
|
|
|
|
|
|
class SearchApps(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager):
|
|
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.notify_finished(apps_found)
|
|
self.word = None
|
|
|
|
|
|
class InstallApp(AsyncAction):
|
|
|
|
def __init__(self, manager: SoftwareManager, disk_cache: bool, icon_cache: MemoryCache, locale_keys: dict, pkg: PackageView = None):
|
|
super(InstallApp, self).__init__()
|
|
self.pkg = pkg
|
|
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.pkg:
|
|
success = False
|
|
|
|
try:
|
|
success = self.manager.install(self.pkg.model, self.root_password, self)
|
|
|
|
if success and self.disk_cache:
|
|
self.pkg.model.installed = True
|
|
|
|
if self.pkg.model.supports_disk_cache():
|
|
icon_data = self.icon_cache.get(self.pkg.model.base_data.icon_url)
|
|
self.manager.cache_to_disk(app=self.pkg.model,
|
|
icon_bytes=icon_data.get('bytes') if icon_data else None,
|
|
only_icon=False)
|
|
except (requests.exceptions.ConnectionError, NoInternetException):
|
|
success = False
|
|
self.print(self.locale_keys['internet.required'])
|
|
finally:
|
|
self.signal_finished.emit(self.pkg if success else None)
|
|
self.pkg = 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[PackageView] = 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 == PackageStatus.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):
|
|
|
|
def __init__(self, manager: SoftwareManager, app: PackageView = None):
|
|
super(RefreshApp, self).__init__()
|
|
self.app = app
|
|
self.manager = manager
|
|
self.root_password = None
|
|
|
|
def run(self):
|
|
|
|
if self.app:
|
|
success = False
|
|
|
|
try:
|
|
success = self.manager.refresh(self.app.model, self.root_password, self)
|
|
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):
|
|
|
|
def __init__(self, man: SoftwareManager):
|
|
super(FindSuggestions, self).__init__()
|
|
self.man = man
|
|
|
|
def run(self):
|
|
sugs = self.man.list_suggestions(limit=-1)
|
|
self.notify_finished(sugs if sugs is not None else [])
|
|
|
|
|
|
class ListWarnings(QThread):
|
|
|
|
signal_warnings = pyqtSignal(list)
|
|
|
|
def __init__(self, man: SoftwareManager, 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)
|
|
|
|
|
|
class RunApp(AsyncAction):
|
|
|
|
def __init__(self, app: PackageView = None):
|
|
super(RunApp, self).__init__()
|
|
self.app = app
|
|
|
|
def run(self):
|
|
|
|
if self.app:
|
|
try:
|
|
time.sleep(0.5)
|
|
subprocess.Popen(self.app.model.get_command().split(' '))
|
|
self.notify_finished(True)
|
|
except:
|
|
self.notify_finished(False)
|