mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-06 21:44:16 +02:00
installing appimage
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ __pycache__
|
||||
dist
|
||||
*.egg-info
|
||||
build
|
||||
*.db
|
||||
@@ -32,7 +32,7 @@ class SoftwarePackage(ABC):
|
||||
|
||||
def __init__(self, id: str = None, version: str = None, name: str = None, description: str = None, latest_version: str = None,
|
||||
icon_url: str = None, status: PackageStatus = PackageStatus.READY, installed: bool = False, update: bool = False,
|
||||
size: int = None, categories: List[str] = None):
|
||||
size: int = None, categories: List[str] = None, license: str = None):
|
||||
"""
|
||||
:param id:
|
||||
:param version:
|
||||
@@ -57,6 +57,7 @@ class SoftwarePackage(ABC):
|
||||
self.update = update
|
||||
self.size = size
|
||||
self.categories = categories
|
||||
self.license = license
|
||||
|
||||
@abstractmethod
|
||||
def has_history(self):
|
||||
|
||||
@@ -31,15 +31,17 @@ def main():
|
||||
cache_factory = DefaultMemoryCacheFactory(expiration_time=args.cache_exp, cleaner=cache_cleaner)
|
||||
icon_cache = cache_factory.new(args.icon_exp)
|
||||
|
||||
http_client = HttpClient(logger)
|
||||
context = ApplicationContext(i18n=i18n,
|
||||
http_client=HttpClient(logger),
|
||||
http_client=http_client,
|
||||
disk_cache=args.disk_cache,
|
||||
download_icons=args.download_icons,
|
||||
app_root_dir=ROOT_DIR,
|
||||
cache_factory=cache_factory,
|
||||
disk_loader_factory=DefaultDiskCacheLoaderFactory(disk_cache_enabled=args.disk_cache, logger=logger),
|
||||
logger=logger,
|
||||
file_downloader=AdaptableFileDownloader(logger, bool(args.download_mthread)))
|
||||
file_downloader=AdaptableFileDownloader(logger, bool(args.download_mthread),
|
||||
i18n, http_client))
|
||||
user_config = config.read()
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
6
bauh/gems/appimage/__init__.py
Normal file
6
bauh/gems/appimage/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
|
||||
from bauh.api.constants import HOME_PATH
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
INSTALLATION_PATH = '{}/.local/share/bauh/appimage/installed/'.format(HOME_PATH)
|
||||
129
bauh/gems/appimage/controller.py
Normal file
129
bauh/gems/appimage/controller.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Set, Type, List
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.controller import SoftwareManager, SearchResult
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion
|
||||
from bauh.api.constants import HOME_PATH
|
||||
from bauh.commons.html import bold
|
||||
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler
|
||||
from bauh.gems.appimage import query, INSTALLATION_PATH
|
||||
from bauh.gems.appimage.model import AppImage
|
||||
|
||||
DB_PATH = '{}/{}'.format(HOME_PATH, '.cache/bauh/appimage/appimage.db')
|
||||
|
||||
|
||||
class AppImageManager(SoftwareManager):
|
||||
|
||||
def __init__(self, context: ApplicationContext):
|
||||
super(AppImageManager, self).__init__(context=context)
|
||||
self.i18n = context.i18n
|
||||
self.api_cache = context.cache_factory.new()
|
||||
context.disk_loader_factory.map(AppImageManager, self.api_cache)
|
||||
self.enabled = True
|
||||
self.http_client = context.http_client
|
||||
self.logger = context.logger
|
||||
self.file_downloader = context.file_downloader
|
||||
|
||||
def _get_db_connection(self) -> sqlite3.Connection:
|
||||
if os.path.exists(DB_PATH):
|
||||
return sqlite3.connect(DB_PATH)
|
||||
|
||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult:
|
||||
res = SearchResult([], [], 0)
|
||||
connection = self._get_db_connection()
|
||||
|
||||
if connection:
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query.SEARCH_BY_NAME_OR_DESCRIPTION.format(words, words))
|
||||
|
||||
for l in cursor.fetchall():
|
||||
app = AppImage(*l)
|
||||
res.new.append(app)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
res.total = len(res.installed) + len(res.new)
|
||||
return res
|
||||
|
||||
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult:
|
||||
return SearchResult([], [], 0)
|
||||
|
||||
def downgrade(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
pass
|
||||
|
||||
def update(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> SystemProcess:
|
||||
pass
|
||||
|
||||
def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
pass
|
||||
|
||||
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
|
||||
return {AppImage}
|
||||
|
||||
def clean_cache_for(self, pkg: AppImage):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def get_info(self, pkg: AppImage) -> dict:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def get_history(self, pkg: AppImage) -> PackageHistory:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def install(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
out_dir = INSTALLATION_PATH + pkg.name.lower()
|
||||
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
file_name = pkg.url_download.split('/')[-1]
|
||||
file_path = out_dir + '/' + file_name
|
||||
downloaded = self.file_downloader.download(pkg.url_download, watcher=watcher, output_path=file_path, cwd=HOME_PATH)
|
||||
|
||||
if downloaded:
|
||||
handler = ProcessHandler(watcher)
|
||||
watcher.change_substatus(self.i18n['appimage.install.permission'].format(bold(file_name)))
|
||||
return handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', file_path])))
|
||||
|
||||
return False
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
return self.enabled
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
self.enabled = enabled
|
||||
|
||||
def can_work(self) -> bool:
|
||||
# TODO check wget and sqlite
|
||||
return True
|
||||
|
||||
def requires_root(self, action: str, pkg: AppImage):
|
||||
return False
|
||||
|
||||
def prepare(self):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def list_warnings(self) -> List[str]:
|
||||
pass
|
||||
|
||||
def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def is_default_enabled(self) -> bool:
|
||||
return True
|
||||
|
||||
def launch(self, pkg: AppImage):
|
||||
# TODO
|
||||
pass
|
||||
60
bauh/gems/appimage/model.py
Normal file
60
bauh/gems/appimage/model.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from typing import List
|
||||
|
||||
from bauh.api.abstract.model import SoftwarePackage
|
||||
from bauh.commons import resource
|
||||
from bauh.gems.appimage import ROOT_DIR
|
||||
|
||||
|
||||
class AppImage(SoftwarePackage):
|
||||
|
||||
def __init__(self, name: str = None, description: str = None, github: str = None, source: str = None, version: str = None,
|
||||
url_download: str = None, url_icon: str = None, license: str = None, pictures: List[str] = None):
|
||||
super(AppImage, self).__init__(name=name, version=version, latest_version=version,
|
||||
icon_url=url_icon, license=license, description=description)
|
||||
self.source = source
|
||||
self.github = github
|
||||
self.pictures = pictures
|
||||
self.url_download = url_download
|
||||
|
||||
def can_be_installed(self):
|
||||
return not self.installed and self.url_download
|
||||
|
||||
def has_history(self):
|
||||
# TODO
|
||||
return False
|
||||
|
||||
def has_info(self):
|
||||
# TODO
|
||||
return False
|
||||
|
||||
def can_be_downgraded(self):
|
||||
# TODO
|
||||
return False
|
||||
|
||||
def get_type(self):
|
||||
return 'AppImage'
|
||||
|
||||
def get_default_icon_path(self):
|
||||
return self.get_type_icon_path()
|
||||
|
||||
def get_type_icon_path(self):
|
||||
return resource.get_path('img/appimage.png', ROOT_DIR)
|
||||
|
||||
def is_application(self):
|
||||
return True
|
||||
|
||||
def get_data_to_cache(self) -> dict:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def fill_cached_data(self, data: dict):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def can_be_run(self) -> str:
|
||||
# TODO
|
||||
return False
|
||||
|
||||
def get_publisher(self) -> str:
|
||||
# TODO
|
||||
pass
|
||||
5
bauh/gems/appimage/query.py
Normal file
5
bauh/gems/appimage/query.py
Normal file
@@ -0,0 +1,5 @@
|
||||
ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'license')
|
||||
|
||||
_SELECT_BASE = "SELECT {} FROM apps".format(','.join(ATTRS))
|
||||
|
||||
SEARCH_BY_NAME_OR_DESCRIPTION = _SELECT_BASE + " WHERE lower(name) LIKE '%{}%' or lower(description) LIKE '%{}%'"
|
||||
BIN
bauh/gems/appimage/resources/img/appimage.png
Executable file
BIN
bauh/gems/appimage/resources/img/appimage.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
2
bauh/gems/appimage/resources/locale/en
Normal file
2
bauh/gems/appimage/resources/locale/en
Normal file
@@ -0,0 +1,2 @@
|
||||
gem.appimage.label=AppImage
|
||||
appimage.install.permission=Giving execution permission to {}
|
||||
2
bauh/gems/appimage/resources/locale/es
Normal file
2
bauh/gems/appimage/resources/locale/es
Normal file
@@ -0,0 +1,2 @@
|
||||
gem.appimage.label=AppImage
|
||||
appimage.install.permission=Concediendo permiso de ejecución a {}
|
||||
2
bauh/gems/appimage/resources/locale/pt
Normal file
2
bauh/gems/appimage/resources/locale/pt
Normal file
@@ -0,0 +1,2 @@
|
||||
gem.appimage.label=AppImage
|
||||
appimage.install.permission=Concedendo permissão de execução para {}
|
||||
@@ -405,10 +405,6 @@ class ArchManager(SoftwareManager):
|
||||
else:
|
||||
args.update({'file_url': fdata[0], 'output_path': None})
|
||||
|
||||
file_size = self.context.http_client.get_content_length(args['file_url'])
|
||||
file_size = int(file_size) / (1024 ** 2) if file_size else None
|
||||
|
||||
watcher.change_substatus(bold('[{}] ').format(downloader) + self.i18n['downloading'] + ' ' + bold(args['file_url'].split('/')[-1]) + ' ( {0:.2f} Mb )'.format(file_size) if file_size else '')
|
||||
if not self.context.file_downloader.download(**args):
|
||||
watcher.print('Could not download source file {}'.format(args['file_url']))
|
||||
return False
|
||||
|
||||
@@ -5,14 +5,18 @@ import traceback
|
||||
|
||||
from bauh.api.abstract.download import FileDownloader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.http import HttpClient
|
||||
from bauh.commons.html import bold
|
||||
from bauh.commons.system import run_cmd, new_subprocess, ProcessHandler, SystemProcess
|
||||
|
||||
|
||||
class AdaptableFileDownloader(FileDownloader):
|
||||
|
||||
def __init__(self, logger: logging.Logger, multithread_enabled: bool):
|
||||
def __init__(self, logger: logging.Logger, multithread_enabled: bool, i18n: dict, http_client: HttpClient):
|
||||
self.logger = logger
|
||||
self.multithread_enabled = multithread_enabled
|
||||
self.i18n = i18n
|
||||
self.http_client = http_client
|
||||
|
||||
def is_aria2c_available(self) -> bool:
|
||||
return bool(run_cmd('which aria2c'))
|
||||
@@ -48,7 +52,7 @@ class AdaptableFileDownloader(FileDownloader):
|
||||
cmd = ['wget', url]
|
||||
|
||||
if output_path:
|
||||
cmd.append('-O')
|
||||
cmd.append('-o')
|
||||
cmd.append(output_path)
|
||||
|
||||
return SystemProcess(new_subprocess(cmd, cwd=cwd))
|
||||
@@ -73,10 +77,16 @@ class AdaptableFileDownloader(FileDownloader):
|
||||
if self.is_multithreaded():
|
||||
ti = time.time()
|
||||
process = self._get_aria2c_process(file_url, output_path, final_cwd)
|
||||
downloader = 'aria2c'
|
||||
else:
|
||||
ti = time.time()
|
||||
process = self._get_wget_process(file_url, output_path, final_cwd)
|
||||
downloader = 'wget'
|
||||
|
||||
file_size = self.http_client.get_content_length(file_url)
|
||||
file_size = int(file_size) / (1024 ** 2) if file_size else None
|
||||
msg = bold('[{}] ').format(downloader) + self.i18n['downloading'] + ' ' + bold(file_url.split('/')[-1]) + (' ( {0:.2f} Mb )'.format(file_size) if file_size else '')
|
||||
watcher.change_substatus(msg)
|
||||
success = handler.handle(process)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
@@ -91,6 +101,8 @@ class AdaptableFileDownloader(FileDownloader):
|
||||
|
||||
return success
|
||||
|
||||
|
||||
|
||||
def is_multithreaded(self) -> bool:
|
||||
return self.multithread_enabled and self.is_aria2c_available()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import List
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QPixmap, QIcon, QCursor
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QToolButton, QWidget, \
|
||||
QHeaderView, QLabel, QHBoxLayout, QPushButton, QToolBar
|
||||
|
||||
@@ -175,7 +175,8 @@ class AppsTable(QTableWidget):
|
||||
|
||||
self.window.install(pkgv)
|
||||
|
||||
def _load_icon_and_cache(self, http_response):
|
||||
def _load_icon_and_cache(self, http_response: QNetworkReply):
|
||||
|
||||
icon_url = http_response.url().toString()
|
||||
|
||||
icon_data = self.icon_cache.get(icon_url)
|
||||
|
||||
Reference in New Issue
Block a user