mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 02:24:16 +02:00
0.9.0
This commit is contained in:
@@ -9,23 +9,24 @@ import traceback
|
||||
from datetime import datetime
|
||||
from math import floor
|
||||
from pathlib import Path
|
||||
from threading import Lock, Thread
|
||||
from threading import Lock
|
||||
from typing import Set, Type, List, Tuple
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.controller import SoftwareManager, SearchResult
|
||||
from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, UpgradeRequirement
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.handler import ProcessWatcher, TaskManager
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
|
||||
SuggestionPriority
|
||||
SuggestionPriority, CustomSoftwareAction
|
||||
from bauh.api.abstract.view import MessageType, ViewComponent, FormComponent, InputOption, SingleSelectComponent, \
|
||||
SelectViewType, TextInputComponent, PanelComponent
|
||||
SelectViewType, TextInputComponent, PanelComponent, FileChooserComponent
|
||||
from bauh.commons import resource
|
||||
from bauh.commons.config import save_config
|
||||
from bauh.commons.html import bold
|
||||
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
|
||||
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE
|
||||
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE, ROOT_DIR
|
||||
from bauh.gems.appimage.config import read_config
|
||||
from bauh.gems.appimage.model import AppImage
|
||||
from bauh.gems.appimage.worker import DatabaseUpdater
|
||||
@@ -52,6 +53,86 @@ class AppImageManager(SoftwareManager):
|
||||
self.logger = context.logger
|
||||
self.file_downloader = context.file_downloader
|
||||
self.db_locks = {DB_APPS_PATH: Lock(), DB_RELEASES_PATH: Lock()}
|
||||
self.custom_actions = [CustomSoftwareAction(i18_label_key='appimage.custom_action.install_file',
|
||||
i18n_status_key='appimage.custom_action.install_file.status',
|
||||
manager=self,
|
||||
manager_method='install_file',
|
||||
icon_path=resource.get_path('img/appimage.svg', ROOT_DIR),
|
||||
requires_root=False)]
|
||||
|
||||
def install_file(self, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), allowed_extensions={'AppImage'})
|
||||
input_name = TextInputComponent(label=self.i18n['name'].capitalize())
|
||||
input_version = TextInputComponent(label=self.i18n['version'].capitalize())
|
||||
input_description = TextInputComponent(label=self.i18n['description'].capitalize())
|
||||
|
||||
cat_ops = [InputOption(label=self.i18n['category.none'].capitalize(), value=0)]
|
||||
cat_ops.extend([InputOption(label=self.i18n[c.lower()].capitalize(), value=c) for c in self.context.default_categories])
|
||||
inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops,
|
||||
default_option=cat_ops[0])
|
||||
|
||||
form = FormComponent(label='', components=[file_chooser, input_name, input_version, input_description, inp_cat], spaces=False)
|
||||
|
||||
while True:
|
||||
if watcher.request_confirmation(title=self.i18n['appimage.custom_action.install_file.details'], body=None,
|
||||
components=[form],
|
||||
confirmation_label=self.i18n['proceed'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize()):
|
||||
if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path):
|
||||
watcher.request_confirmation(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['appimage.custom_action.install_file.invalid_file'],
|
||||
deny_button=False)
|
||||
elif not input_name.get_value() or not input_name.get_value().strip():
|
||||
watcher.request_confirmation(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['appimage.custom_action.install_file.invalid_name'],
|
||||
deny_button=False)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
|
||||
appim = AppImage(i18n=self.i18n, imported=True)
|
||||
appim.name = input_name.get_value().strip()
|
||||
appim.local_file_path = file_chooser.file_path
|
||||
appim.version = input_version.get_value()
|
||||
appim.latest_version = input_version.get_value()
|
||||
appim.description = input_description.get_value()
|
||||
appim.categories = ['Imported']
|
||||
|
||||
if inp_cat.get_selected() != cat_ops[0].value:
|
||||
appim.categories.append(inp_cat.get_selected())
|
||||
|
||||
installed = self.install(root_password=root_password, pkg=appim, watcher=watcher)
|
||||
|
||||
if installed:
|
||||
appim.installed = True
|
||||
self.cache_to_disk(appim, None, False)
|
||||
|
||||
return installed
|
||||
|
||||
def update_file(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher):
|
||||
file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), allowed_extensions={'AppImage'})
|
||||
input_version = TextInputComponent(label=self.i18n['version'].capitalize())
|
||||
|
||||
while True:
|
||||
if watcher.request_confirmation(title=self.i18n['appimage.custom_action.manual_update.details'], body=None,
|
||||
components=[FormComponent(label='', components=[file_chooser, input_version], spaces=False)],
|
||||
confirmation_label=self.i18n['proceed'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize()):
|
||||
if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path):
|
||||
watcher.request_confirmation(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['appimage.custom_action.install_file.invalid_file'],
|
||||
deny_button=False)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
|
||||
pkg.local_file_path = file_chooser.file_path
|
||||
pkg.version = input_version.get_value()
|
||||
|
||||
reqs = UpgradeRequirements(to_install=None, to_remove=None, to_upgrade=[UpgradeRequirement(pkg=pkg)], cannot_upgrade=None)
|
||||
return self.upgrade(reqs, root_password=root_password, watcher=watcher)
|
||||
|
||||
def _get_db_connection(self, db_path: str) -> sqlite3.Connection:
|
||||
if os.path.exists(db_path):
|
||||
@@ -82,7 +163,7 @@ class AppImageManager(SoftwareManager):
|
||||
found_map = {}
|
||||
idx = 0
|
||||
for l in cursor.fetchall():
|
||||
app = AppImage(*l)
|
||||
app = AppImage(*l, i18n=self.i18n)
|
||||
res.new.append(app)
|
||||
found_map[self._gen_app_key(app)] = {'app': app, 'idx': idx}
|
||||
idx += 1
|
||||
@@ -118,7 +199,7 @@ class AppImageManager(SoftwareManager):
|
||||
for path in installed.split('\n'):
|
||||
if path:
|
||||
with open(path) as f:
|
||||
app = AppImage(installed=True, **json.loads(f.read()))
|
||||
app = AppImage(installed=True, i18n=self.i18n, **json.loads(f.read()))
|
||||
app.icon_url = app.icon_path
|
||||
|
||||
res.installed.append(app)
|
||||
@@ -189,18 +270,25 @@ class AppImageManager(SoftwareManager):
|
||||
|
||||
return False
|
||||
|
||||
def update(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
if not self.uninstall(pkg, root_password, watcher):
|
||||
watcher.show_message(title=self.i18n['error'],
|
||||
body=self.i18n['appimage.error.uninstall_current_version'],
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
for req in requirements.to_upgrade:
|
||||
watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version))
|
||||
|
||||
if self.install(pkg, root_password, watcher):
|
||||
self.cache_to_disk(pkg, None, False)
|
||||
return True
|
||||
if not self.uninstall(req.pkg, root_password, watcher):
|
||||
watcher.show_message(title=self.i18n['error'],
|
||||
body=self.i18n['appimage.error.uninstall_current_version'],
|
||||
type_=MessageType.ERROR)
|
||||
watcher.change_substatus('')
|
||||
return False
|
||||
|
||||
return False
|
||||
if not self.install(req.pkg, root_password, watcher):
|
||||
watcher.change_substatus('')
|
||||
return False
|
||||
|
||||
self.cache_to_disk(req.pkg, None, False)
|
||||
|
||||
watcher.change_substatus('')
|
||||
return True
|
||||
|
||||
def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
if os.path.exists(pkg.get_disk_cache_path()):
|
||||
@@ -290,17 +378,47 @@ class AppImageManager(SoftwareManager):
|
||||
|
||||
def install(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
handler = ProcessHandler(watcher)
|
||||
|
||||
out_dir = INSTALLATION_PATH + pkg.name.lower()
|
||||
counter = 0
|
||||
while True:
|
||||
if os.path.exists(out_dir):
|
||||
self.logger.info("Installation dir '{}' already exists. Generating a different one".format(out_dir))
|
||||
out_dir += '-{}'.format(counter)
|
||||
counter += 1
|
||||
else:
|
||||
break
|
||||
|
||||
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
||||
pkg.install_dir = out_dir
|
||||
|
||||
appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download
|
||||
file_name = appimage_url.split('/')[-1]
|
||||
pkg.version = pkg.latest_version
|
||||
pkg.url_download = appimage_url
|
||||
if pkg.imported:
|
||||
|
||||
file_path = out_dir + '/' + file_name
|
||||
downloaded = self.file_downloader.download(file_url=pkg.url_download, watcher=watcher,
|
||||
output_path=file_path, cwd=str(Path.home()))
|
||||
downloaded, file_name = True, pkg.local_file_path.split('/')[-1]
|
||||
|
||||
file_path = out_dir + '/' + file_name
|
||||
|
||||
try:
|
||||
moved, output = handler.handle_simple(SimpleProcess(['mv', pkg.local_file_path, file_path]))
|
||||
except:
|
||||
self.logger.error("Could not rename file '' as '{}'".format(pkg.local_file_path, file_path))
|
||||
moved = False
|
||||
|
||||
if not moved:
|
||||
watcher.show_message(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]), bold(output)),
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
|
||||
else:
|
||||
appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download
|
||||
file_name = appimage_url.split('/')[-1]
|
||||
pkg.version = pkg.latest_version
|
||||
pkg.url_download = appimage_url
|
||||
|
||||
file_path = out_dir + '/' + file_name
|
||||
downloaded = self.file_downloader.download(file_url=pkg.url_download, watcher=watcher,
|
||||
output_path=file_path, cwd=str(Path.home()))
|
||||
|
||||
if downloaded:
|
||||
watcher.change_substatus(self.i18n['appimage.install.permission'].format(bold(file_name)))
|
||||
@@ -392,20 +510,19 @@ class AppImageManager(SoftwareManager):
|
||||
def requires_root(self, action: str, pkg: AppImage):
|
||||
return False
|
||||
|
||||
def _start_updater(self):
|
||||
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool):
|
||||
local_config = read_config(update_file=True)
|
||||
interval = local_config['db_updater']['interval'] or 20 * 60
|
||||
|
||||
updater = DatabaseUpdater(http_client=self.context.http_client, logger=self.context.logger,
|
||||
updater = DatabaseUpdater(task_man=task_manager,
|
||||
i18n=self.context.i18n,
|
||||
http_client=self.context.http_client, logger=self.context.logger,
|
||||
db_locks=self.db_locks, interval=interval)
|
||||
if local_config['db_updater']['enabled']:
|
||||
updater.start()
|
||||
else:
|
||||
elif internet_available:
|
||||
updater.download_databases() # only once
|
||||
|
||||
def prepare(self):
|
||||
Thread(target=self._start_updater, daemon=True).start()
|
||||
|
||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||
res = self.read_installed(disk_loader=None, internet_available=internet_available)
|
||||
|
||||
@@ -413,7 +530,7 @@ class AppImageManager(SoftwareManager):
|
||||
if res.installed:
|
||||
for app in res.installed:
|
||||
if app.update:
|
||||
updates.append(PackageUpdate(pkg_id=app.name, pkg_type='appimage', version=app.latest_version))
|
||||
updates.append(PackageUpdate(pkg_id=app.name, pkg_type='AppImage', version=app.latest_version, name=app.name))
|
||||
|
||||
return updates
|
||||
|
||||
@@ -458,7 +575,7 @@ class AppImageManager(SoftwareManager):
|
||||
cursor.execute(query.FIND_APPS_BY_NAME_FULL.format(','.join(["'{}'".format(s) for s in sugs_map.keys()])))
|
||||
|
||||
for t in cursor.fetchall():
|
||||
app = AppImage(*t)
|
||||
app = AppImage(*t, i18n=self.i18n)
|
||||
res.append(PackageSuggestion(app, sugs_map[app.name.lower()]))
|
||||
self.logger.info("Mapped {} suggestions".format(len(res)))
|
||||
except:
|
||||
@@ -538,3 +655,22 @@ class AppImageManager(SoftwareManager):
|
||||
return True, None
|
||||
except:
|
||||
return False, [traceback.format_exc()]
|
||||
|
||||
def get_custom_actions(self) -> List[CustomSoftwareAction]:
|
||||
return self.custom_actions
|
||||
|
||||
def get_upgrade_requirements(self, pkgs: List[AppImage], root_password: str, watcher: ProcessWatcher) -> UpgradeRequirements:
|
||||
to_update = []
|
||||
|
||||
for pkg in pkgs:
|
||||
requirement = UpgradeRequirement(pkg)
|
||||
installed_size = self.http_client.get_content_length_in_bytes(pkg.url_download)
|
||||
upgrade_size = self.http_client.get_content_length_in_bytes(pkg.url_download_latest_version)
|
||||
requirement.required_size = upgrade_size
|
||||
|
||||
if upgrade_size and installed_size:
|
||||
requirement.extra_size = upgrade_size - installed_size
|
||||
|
||||
to_update.append(requirement)
|
||||
|
||||
return UpgradeRequirements([], [], to_update, [])
|
||||
|
||||
Reference in New Issue
Block a user