diff --git a/CHANGELOG.md b/CHANGELOG.md index 089423002..73989fa32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,60 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.9.22] 2021-11-30 +### Improvements +- General + - directory paths changed for a root user using bauh: + - caching: `/var/cache/bauh` + - configuration: `/etc/bauh` + - temp dir: `/tmp/bauh@root` (`/tmp/bauh@$USER` for non-root users) + - autostart: `/etc/xdg/autostart` (only used by the Web gem at the moment) + - desktop entries: `/usr/share/applications` + - custom themes: `/usr/share/bauh/themes` + - symlinks/binaries : `/usr/local/bin` + - shared files: `/usr/local/share/bauh` + - adding the `XDG_RUNTIME_DIR` environment variable if not available (following the pattern `/run/user/$UID`) + - refactorings related to String formatting + - refactorings related to shared information + - useless code removed + +- UI + - settings panel: + - always displaying all supported packaging technologies + - displaying a tooltip with the missing dependencies for a supported packaging technology +
+ +
+ +- AppImage + - faster reading of installed applications (subprocess call replaced by Python call) + +- Flatpak + - settings: not displaying the installation target option when bauh is launched by the **root** user + - always considering **system** as the installation level for the **root** user + +- Web + - the Electron builds cache directory has been moved to the environment directory `~/.local/share/bauh/web/env/electron` + - letting the Electron client to download the Electron build file instead of bauh (to avoid wrong caching paths) + +### Fixes +- General + - single thread downloader (**wget**) does not create the directory where the file will be stored + +- AppImage + - trying to download a file without a URL associated with [#210](https://github.com/vinifmor/bauh/issues/210) + - regressions: + - not able to import AppImage files (introduced in **0.9.21**) + - not able to upgrade imported AppImage files (introduced in **0.9.21**) + +- Arch + - **wget** as a hard requirement for Arch package management + +- UI + - settings panel: + - not re-enabling the action buttons when a validation error is displayed + + ## [0.9.21] 2021-11-20 ### Fixes - General diff --git a/README.md b/README.md index 98e42ed90..503bb38ab 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Key features - [Snap](#type_snap) - [Native Web applications](#type_web) 7. [General settings](#settings) -8. [Cache and logs](#cache_logs) +8. [Directory structure, caching and logs](#dirs) 9. [Custom themes](#custom_themes) 10. [Tray icons](#tray_icons) 11. [CLI (Command Line Interface)](#cli) @@ -154,7 +154,7 @@ rm -rf bauh_env` (just remove the directory) To create a shortcut for bauh on your desktop menu: -- Copy the files from [bauh/desktop](https://raw.githubusercontent.com/vinifmor/bauh/master/bauh/desktop/bauh.desktop) to `~/.local/share/applications` +- Copy the files from [bauh/desktop](https://raw.githubusercontent.com/vinifmor/bauh/master/bauh/desktop/bauh.desktop) to `~/.local/share/applications` (or `/usr/share/applications` for **root**) - Replace the `Exec` field on theses files by the bauh binary path. e.g: `Exec=/usr/bin/bauh` (or `bauh_env/bin/bauh`) - Copy [logo.svg](https://raw.githubusercontent.com/vinifmor/bauh/master/bauh/view/resources/img/logo.svg) to `/usr/share/icons/hicolor/scalable/apps` as `bauh.svg` @@ -191,13 +191,14 @@ bauh is officially distributed through [PyPi](https://pypi.org/project/bauh) and - `Upgrade file`: allows to upgrade a manually installed AppImage file - `Update database`: manually synchronize the AppImage database -- Installed applications are store at `~/.local/share/bauh/appimage/installed` -- Desktop entries (menu shortcuts) of the installed applications are stored at **~/.local/share/applications** (name pattern: `bauh_appimage_appname.desktop`) -- Symlinks are created at **~/.local/bin**. They have the same name of the application (if the name already exists, it will be created as 'app_name-appimage'. e.g: `rpcs3-appimage`) -- Downloaded database files are stored at **~/.cache/bauh/appimage** as **apps.db** and **releases.db** +- Installed applications are store at `~/.local/share/bauh/appimage/installed` (or `/usr/local/share/bauh/installed` for **root**) +- Desktop entries (menu shortcuts) of the installed applications are stored at `~/.local/share/applications` (or `/usr/share/applications` for **root**). Name pattern: `bauh_appimage_appname.desktop` +- Symlinks are created at `~/.local/bin` (or `/usr/local/bin` for **root**). They have the same name of the application (if the name already exists, it will be created as 'app_name-appimage'. e.g: `rpcs3-appimage`) +- Downloaded database files are stored at `~/.cache/bauh/appimage` (or `/var/cache/bauh/appimage` for **root**) as **apps.db** and **releases.db** - Databases are updated during the initialization process if they are considered outdated -- Applications with ignored updates are defined at **~/.config/bauh/appimage/updates_ignored.txt** -- The configuration file is located at **~/.config/bauh/appimage.yml** and it allows the following customizations: +- The configuration file is located at `~/.config/bauh/appimage.yml` (or `/etc/bauh/appimage.yml` for **root**) and it allows the following customizations: +- Applications with ignored updates are defined at `~/.config/bauh/appimage/updates_ignored.txt` (or `/etc/bauh/appimage/updates_ignored.txt` for **root**) + ``` database: expiration: 60 # defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it. Default: 60. @@ -315,15 +316,15 @@ defined at [suggestions.yml](https://raw.githubusercontent.com/vinifmor/bauh-fil - It relies on [NodeJS](https://nodejs.org/en/), [Electron](https://electronjs.org/) and [nativefier](https://github.com/jiahaog/nativefier) to do all the magic, but you do not need them installed on your system. An isolated installation environment -will be generated at **~/.local/share/bauh/web/env**. +will be generated at `~/.local/share/bauh/web/env` (or `/usr/local/share/bauh/web/env` for **root**). - It supports DRM protected content through a custom Electron implementation provided by [castLabs](https://github.com/castlabs/electron-releases). nativefier handles the switch between the official Electron and the custom. - The isolated environment is created based on the settings defined in [environment.yml](https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v1/environment.yml) (downloaded during runtime). - Some applications require Javascript fixes to properly work. If there is a known fix, bauh will download the file from [fix](https://github.com/vinifmor/bauh-files/tree/master/web/fix) and attach it to the generated app. -- The installed applications are located at `~/.local/share/bauh/installed`. -- A desktop entry / menu shortcut will be generated for the installed applications at `~/.local/share/application` -- If the Tray Mode **Start Minimized** is defined during the installation setup, a desktop entry will be also generated at `~/.config/autostart` +- The installed applications are located at `~/.local/share/bauh/installed` (or `/usr/local/share/bauh/web/installed` for **root**). +- A desktop entry / menu shortcut will be generated for the installed applications at `~/.local/share/applications` (or `/usr/share/applications` for **root**) +- If the Tray Mode **Start Minimized** is defined during the installation setup, a desktop entry will be also generated at `~/.config/autostart` (or `/etc/xdg/autostart` for **root**) allowing the application to launch automatically after the system's boot attached to the tray.@@ -334,7 +335,7 @@ allowing the application to launch automatically after the system's boot attache - Extra actions - `Clean installation environment`: removes all the installation environment folders (it does not remove installed apps) -- The configuration file is located at `~/.config/bauh/web.yml` and it allows the following customizations: +- The configuration file is located at `~/.config/bauh/web.yml` (or `/etc/bauh/web.yml` for **root**) and it allows the following customizations: ``` environment: @@ -409,13 +410,14 @@ boot: load_apps: true # if the installed applications or suggestions should be loaded on the management panel after the initialization process. Default: true. ``` -#### Cache and Logs -- Installation logs and temporary files are saved at `/tmp/bauh` (or `/tmp/bauh_root` if you launch it as root) -- Some data about your installed applications are stored in `~/.cache/bauh` to load them faster +#### Directory structure, caching and logs +- `~/.config/bauh` (or `/etc/bauh` for **root**): stores configuration files +- `~/.cache/bauh` (or `/var/cache/bauh` for **root**): stores data about your installed applications, databases, indexes, etc. Files are stored here to provide a faster initialization and data recovery. +- `/tmp/bauh@$USER` (e.g: `/tmp/bauh@root`): stores logging and temporary files (e.g: build dependencies) #### Custom themes -- Custom themes can be provided by adding their files at `~/.local/share/bauh/themes` (sub-folders are allowed). +- Custom themes can be provided by adding their files at `~/.local/share/bauh/themes` (or `/usr/share/bauh/themes` for **root**). Sub-folders are allowed. - Themes are composed by 2 required and 1 optional files sharing the same name: - `my_theme.qss`: file with the qss rules. Full example: [light.qss](https://github.com/vinifmor/bauh/blob/master/bauh/view/resources/style/light/light.qss) - `my_theme.meta`: file defining the theme's data. Full example: [light.meta](https://github.com/vinifmor/bauh/blob/master/bauh/view/resources/style/light/light.meta) diff --git a/bauh/__init__.py b/bauh/__init__.py index e15eed953..74d7da2c9 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,6 +1,5 @@ -__version__ = '0.9.21' +__version__ = '0.9.22' __app_name__ = 'bauh' import os ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -LOGS_PATH = '/tmp/{}/logs'.format(__app_name__) diff --git a/bauh/api/abstract/context.py b/bauh/api/abstract/context.py index fdb91ed47..d327eed20 100644 --- a/bauh/api/abstract/context.py +++ b/bauh/api/abstract/context.py @@ -14,7 +14,7 @@ class ApplicationContext: def __init__(self, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: I18n, cache_factory: MemoryCacheFactory, disk_loader_factory: DiskCacheLoaderFactory, logger: logging.Logger, file_downloader: FileDownloader, distro: str, app_name: str, - internet_checker: InternetChecker): + internet_checker: InternetChecker, root_user: bool): """ :param download_icons: if packages icons should be downloaded :param http_client: a shared instance of http client @@ -41,6 +41,7 @@ def __init__(self, download_icons: bool, http_client: HttpClient, app_root_dir: self.default_categories = ('AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game', 'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System', 'Utility') self.app_name = app_name + self.root_user = root_user self.root_password = None self.internet_checker = internet_checker diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 7c0095390..d270340c6 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -243,9 +243,9 @@ def set_enabled(self, enabled: bool): pass @abstractmethod - def can_work(self) -> bool: + def can_work(self) -> Tuple[bool, Optional[str]]: """ - :return: if the instance can work based on what is installed in the user's machine. + :return: if the instance can work based on what is installed in the user's machine. If not, an optional string as a reason. """ def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool): @@ -368,7 +368,7 @@ def clear_data(self, logs: bool = True): """ pass - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: """ :param screen_width :param screen_height diff --git a/bauh/api/abstract/download.py b/bauh/api/abstract/download.py index 5b54e5896..8c4eb970c 100644 --- a/bauh/api/abstract/download.py +++ b/bauh/api/abstract/download.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Iterable, List, Optional +from typing import Iterable, List, Optional, Tuple from bauh.api.abstract.handler import ProcessWatcher @@ -41,3 +41,7 @@ def is_multithreaded_client_available(self, name: str) -> bool: @abstractmethod def list_available_multithreaded_clients(self) -> List[str]: pass + + @abstractmethod + def get_supported_clients(self) -> tuple: + pass diff --git a/bauh/api/abstract/model.py b/bauh/api/abstract/model.py index e569ee6e1..e674cf327 100644 --- a/bauh/api/abstract/model.py +++ b/bauh/api/abstract/model.py @@ -2,7 +2,7 @@ from enum import Enum from typing import List, Optional -from bauh.api.constants import CACHE_PATH +from bauh.api.paths import CACHE_DIR class CustomSoftwareAction: @@ -146,7 +146,7 @@ def get_disk_cache_path(self): """ :return: base cache path for the specific app type """ - return CACHE_PATH + '/' + self.get_type() + return f'{CACHE_DIR}/{self.get_type()}' def can_be_updated(self) -> bool: """ diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py index 5492776cd..351026016 100644 --- a/bauh/api/abstract/view.py +++ b/bauh/api/abstract/view.py @@ -1,6 +1,6 @@ from abc import ABC from enum import Enum -from typing import List, Set, Optional +from typing import List, Set, Optional, Dict class MessageType(Enum): @@ -46,13 +46,14 @@ class InputOption: def __init__(self, label: str, value: object, tooltip: Optional[str] = None, icon_path: Optional[str] = None, read_only: bool = False, id_: Optional[str] = None, - invalid: bool = False): + invalid: bool = False, extra_properties: Optional[Dict[str, str]] = None): """ :param label: the string that will be shown to the user :param value: the option value (not shown) :param tooltip: an optional tooltip :param icon_path: an optional icon path :param invalid: if this option is considered invalid + :param extra_properties: extra properties """ if not label: raise Exception("'label' must be a not blank string") @@ -64,6 +65,7 @@ def __init__(self, label: str, value: object, tooltip: Optional[str] = None, self.icon_path = icon_path self.read_only = read_only self.invalid = invalid + self.extra_properties = extra_properties def __hash__(self): return hash(self.label) + hash(self.value) diff --git a/bauh/api/constants.py b/bauh/api/constants.py deleted file mode 100644 index be89411d8..000000000 --- a/bauh/api/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -from pathlib import Path - -CACHE_PATH = '{}/.cache/bauh'.format(str(Path.home())) -CONFIG_PATH = '{}/.config/bauh'.format(str(Path.home())) -USER_THEMES_PATH = '{}/.local/share/bauh/themes'.format(str(Path.home())) -DESKTOP_ENTRIES_DIR = '{}/.local/share/applications'.format(str(Path.home())) -TEMP_DIR = '/tmp/bauh{}'.format('_root' if os.getuid() == 0 else '') diff --git a/bauh/api/paths.py b/bauh/api/paths.py new file mode 100644 index 000000000..fd7b5deb4 --- /dev/null +++ b/bauh/api/paths.py @@ -0,0 +1,15 @@ +from getpass import getuser +from pathlib import Path + +from bauh import __app_name__ +from bauh.api import user + +CACHE_DIR = f'/var/cache/{__app_name__}' if user.is_root() else f'{Path.home()}/.cache/{__app_name__}' +CONFIG_DIR = f'/etc/{__app_name__}' if user.is_root() else f'{Path.home()}/.config/{__app_name__}' +USER_THEMES_DIR = f'/usr/share/{__app_name__}/themes' if user.is_root() else f'{Path.home()}/.local/share/{__app_name__}/themes' +DESKTOP_ENTRIES_DIR = '/usr/share/applications' if user.is_root() else f'{Path.home()}/.local/share/applications' +TEMP_DIR = f'/tmp/{__app_name__}@{getuser()}' +LOGS_DIR = f'{TEMP_DIR}/logs' +AUTOSTART_DIR = f'/etc/xdg/autostart' if user.is_root() else f'{Path.home()}/.config/autostart' +BINARIES_DIR = f'/usr/local/bin' if user.is_root() else f'{Path.home()}/.local/bin' +SHARED_FILES_DIR = f'/usr/local/share/{__app_name__}' if user.is_root() else f'{Path.home()}/.local/share/{__app_name__}' diff --git a/bauh/api/user.py b/bauh/api/user.py new file mode 100644 index 000000000..cd407ac26 --- /dev/null +++ b/bauh/api/user.py @@ -0,0 +1,6 @@ +import os +from typing import Optional + + +def is_root(user_id: Optional[int] = None): + return user_id == 0 if user_id is not None else os.getuid() == 0 diff --git a/bauh/app.py b/bauh/app.py index 172b0e5a1..8f21bdb7f 100755 --- a/bauh/app.py +++ b/bauh/app.py @@ -15,6 +15,9 @@ def main(tray: bool = False): if not os.getenv('PYTHONUNBUFFERED'): os.environ['PYTHONUNBUFFERED'] = '1' + if not os.getenv('XDG_RUNTIME_DIR'): + os.environ['XDG_RUNTIME_DIR'] = f'/run/user/{os.getuid()}' + faulthandler.enable() urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) diff --git a/bauh/cli/app.py b/bauh/cli/app.py index 232e809a3..d5ffd7fdb 100644 --- a/bauh/cli/app.py +++ b/bauh/cli/app.py @@ -3,6 +3,7 @@ import urllib3 from bauh import ROOT_DIR +from bauh.api import user from bauh.api.abstract.context import ApplicationContext from bauh.api.http import HttpClient from bauh.cli import __app_name__, cli_args @@ -45,7 +46,8 @@ def main(): file_downloader=AdaptableFileDownloader(logger, bool(app_config['download']['multithreaded']), i18n, http_client, app_config['download']['multithreaded_client']), app_name=__app_name__, - internet_checker=InternetChecker(offline=False)) + internet_checker=InternetChecker(offline=False), + root_user=user.is_root()) managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config, default_locale=DEFAULT_I18N_KEY) diff --git a/bauh/commons/config.py b/bauh/commons/config.py index dcb426382..0ecc556ac 100644 --- a/bauh/commons/config.py +++ b/bauh/commons/config.py @@ -7,13 +7,13 @@ import yaml -from bauh.api.constants import CONFIG_PATH +from bauh.api.paths import CONFIG_DIR from bauh.commons import util def read_config(file_path: str, template: dict, update_file: bool = False, update_async: bool = False) -> dict: if not os.path.exists(file_path): - Path(CONFIG_PATH).mkdir(parents=True, exist_ok=True) + Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) save_config(template, file_path) else: with open(file_path) as f: diff --git a/bauh/commons/system.py b/bauh/commons/system.py index 3cbddbd89..eff0068b8 100644 --- a/bauh/commons/system.py +++ b/bauh/commons/system.py @@ -68,7 +68,7 @@ def __init__(self, cmd: List[str], cwd: str = '.', expected_code: int = 0, global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, root_password: str = None, extra_paths: Set[str] = None, error_phrases: Set[str] = None, wrong_error_phrases: Set[str] = None, shell: bool = False, - success_phrases: Set[str] = None): + success_phrases: Set[str] = None, extra_env: Optional[Dict[str, str]] = None): pwdin, final_cmd = None, [] self.shell = shell @@ -78,13 +78,21 @@ def __init__(self, cmd: List[str], cwd: str = '.', expected_code: int = 0, final_cmd.extend(cmd) - self.instance = self._new(final_cmd, cwd, global_interpreter, lang, stdin=pwdin, extra_paths=extra_paths) + self.instance = self._new(final_cmd, cwd, global_interpreter, lang, stdin=pwdin, extra_paths=extra_paths, extra_env=extra_env) self.expected_code = expected_code self.error_phrases = error_phrases self.wrong_error_phrases = wrong_error_phrases self.success_phrases = success_phrases - def _new(self, cmd: List[str], cwd: str, global_interpreter: bool, lang: str, stdin = None, extra_paths: Set[str] = None) -> subprocess.Popen: + def _new(self, cmd: List[str], cwd: str, global_interpreter: bool, lang: str, stdin = None, + extra_paths: Set[str] = None, extra_env: Optional[Dict[str, str]] = None) -> subprocess.Popen: + + env = gen_env(global_interpreter, lang, extra_paths=extra_paths) + + if extra_env: + for var, val in extra_env.items(): + if var not in env: + env[var] = val args = { "stdout": subprocess.PIPE, @@ -92,7 +100,7 @@ def _new(self, cmd: List[str], cwd: str, global_interpreter: bool, lang: str, st "stdin": stdin if stdin else subprocess.DEVNULL, "bufsize": -1, "cwd": cwd, - "env": gen_env(global_interpreter, lang, extra_paths=extra_paths), + "env": env, "shell": self.shell } diff --git a/bauh/commons/user.py b/bauh/commons/user.py deleted file mode 100644 index 6b5a0f63b..000000000 --- a/bauh/commons/user.py +++ /dev/null @@ -1,5 +0,0 @@ -import os - - -def is_root(): - return os.getuid() == 0 diff --git a/bauh/gems/appimage/__init__.py b/bauh/gems/appimage/__init__.py index 3ecbc8c98..24133d96b 100644 --- a/bauh/gems/appimage/__init__.py +++ b/bauh/gems/appimage/__init__.py @@ -2,25 +2,25 @@ from pathlib import Path from typing import Optional -from bauh.api.constants import CONFIG_PATH, CACHE_PATH, TEMP_DIR +from bauh import __app_name__ +from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR, BINARIES_DIR, SHARED_FILES_DIR from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -LOCAL_PATH = '{}/.local/share/bauh/appimage'.format(str(Path.home())) -INSTALLATION_PATH = LOCAL_PATH + '/installed/' -SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/suggestions.txt' -CONFIG_FILE = '{}/appimage.yml'.format(CONFIG_PATH) -CONFIG_DIR = '{}/appimage'.format(CONFIG_PATH) -UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) -SYMLINKS_DIR = '{}/.local/bin'.format(str(Path.home())) -URL_COMPRESSED_DATABASES = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/dbs.tar.gz' -APPIMAGE_CACHE_PATH = '{}/appimage'.format(CACHE_PATH) -DATABASE_APPS_FILE = '{}/apps.db'.format(APPIMAGE_CACHE_PATH) -DATABASE_RELEASES_FILE = '{}/releases.db'.format(APPIMAGE_CACHE_PATH) -DATABASES_TS_FILE = '{}/dbs.ts'.format(APPIMAGE_CACHE_PATH) -DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(str(Path.home())) -SUGGESTIONS_CACHED_FILE = '{}/suggestions.txt'.format(APPIMAGE_CACHE_PATH) -SUGGESTIONS_CACHED_TS_FILE = '{}/suggestions.ts'.format(APPIMAGE_CACHE_PATH) +APPIMAGE_SHARED_DIR = f'{SHARED_FILES_DIR}/appimage' +INSTALLATION_DIR = f'{APPIMAGE_SHARED_DIR}/installed' +SUGGESTIONS_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/appimage/suggestions.txt' +CONFIG_FILE = f'{CONFIG_DIR}/appimage.yml' +APPIMAGE_CONFIG_DIR = f'{CONFIG_DIR}/appimage' +UPDATES_IGNORED_FILE = f'{APPIMAGE_CONFIG_DIR}/updates_ignored.txt' +SYMLINKS_DIR = BINARIES_DIR +URL_COMPRESSED_DATABASES = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/appimage/dbs.tar.gz' +APPIMAGE_CACHE_DIR = f'{CACHE_DIR}/appimage' +DATABASE_APPS_FILE = f'{APPIMAGE_CACHE_DIR}/apps.db' +DATABASE_RELEASES_FILE = f'{APPIMAGE_CACHE_DIR}/releases.db' +DATABASES_TS_FILE = f'{APPIMAGE_CACHE_DIR}/dbs.ts' +SUGGESTIONS_CACHED_FILE = f'{APPIMAGE_CACHE_DIR}/suggestions.txt' +SUGGESTIONS_CACHED_TS_FILE = f'{APPIMAGE_CACHE_DIR}/suggestions.ts' DOWNLOAD_DIR = f'{TEMP_DIR}/appimage/download' @@ -29,5 +29,5 @@ def get_icon_path() -> str: def get_default_manual_installation_file_dir() -> Optional[str]: - default_path = '{}/Downloads'.format(str(Path.home())) + default_path = f'{Path.home()}/Downloads' return default_path if os.path.isdir(default_path) else None diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 5cc6d44fc..2769ba305 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -23,13 +23,14 @@ SuggestionPriority, CustomSoftwareAction from bauh.api.abstract.view import MessageType, ViewComponent, FormComponent, InputOption, SingleSelectComponent, \ SelectViewType, TextInputComponent, PanelComponent, FileChooserComponent, ViewObserver +from bauh.api.paths import DESKTOP_ENTRIES_DIR from bauh.commons import resource from bauh.commons.boot import CreateConfigFile 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, ROOT_DIR, \ - CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir, DATABASE_APPS_FILE, \ - DATABASE_RELEASES_FILE, DESKTOP_ENTRIES_PATH, APPIMAGE_CACHE_PATH, get_icon_path, DOWNLOAD_DIR +from bauh.gems.appimage import query, INSTALLATION_DIR, APPIMAGE_SHARED_DIR, ROOT_DIR, \ + APPIMAGE_CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir, DATABASE_APPS_FILE, \ + DATABASE_RELEASES_FILE, APPIMAGE_CACHE_DIR, get_icon_path, DOWNLOAD_DIR from bauh.gems.appimage.config import AppImageConfigManager from bauh.gems.appimage.model import AppImage from bauh.gems.appimage.util import replace_desktop_entry_exec_command @@ -107,7 +108,7 @@ def install_file(self, root_password: str, watcher: ProcessWatcher) -> bool: 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.get('category.{}'.format(c.lower()), c.lower()).capitalize(), value=c) for c in self.context.default_categories]) + cat_ops.extend([InputOption(label=self.i18n.get(f'category.{c.lower()}', 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]) @@ -183,13 +184,13 @@ def _get_db_connection(self, db_path: str) -> sqlite3.Connection: try: return sqlite3.connect(db_path) except: - self.logger.error("Could not connect to database file '{}'".format(db_path)) + self.logger.error(f"Could not connect to database file '{db_path}'") traceback.print_exc() else: - self.logger.warning("Could not get a connection for database '{}'".format(db_path)) + self.logger.warning(f"Could not get a connection for database '{db_path}'") def _gen_app_key(self, app: AppImage): - return '{}{}'.format(app.name.lower(), app.github.lower() if app.github else '') + return f"{app.name.lower()}{app.github.lower() if app.github else ''}" def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: if is_url: @@ -242,7 +243,7 @@ def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_u try: apps_conn.close() except: - self.logger.error("An exception happened when trying to close the connection to database file '{}'".format(DATABASE_APPS_FILE)) + self.logger.error(f"An exception happened when trying to close the connection to database file '{DATABASE_APPS_FILE}'") traceback.print_exc() return SearchResult(new=not_installed, installed=installed_found, total=len(not_installed) + len(installed_found)) @@ -252,19 +253,19 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1 installed_apps = [] res = SearchResult(installed_apps, [], 0) - if os.path.exists(INSTALLATION_PATH): - installed = run_cmd('ls {}*/data.json'.format(INSTALLATION_PATH), print_error=False) + if os.path.exists(INSTALLATION_DIR): + installed = glob.glob(f'{INSTALLATION_DIR}/*/data.json') if installed: names = set() - for path in installed.split('\n'): + for path in installed: if path: with open(path) as f: app = AppImage(installed=True, i18n=self.i18n, custom_actions=self.custom_app_actions, **json.loads(f.read())) app.icon_url = app.icon_path installed_apps.append(app) - names.add("'{}'".format(app.name.lower())) + names.add(f"'{app.name.lower()}'") if installed_apps: apps_con = self._get_db_connection(DATABASE_APPS_FILE) if not connection else connection @@ -298,7 +299,7 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1 break except: - self.logger.error("An exception happened while querying the database file {}".format(apps_con)) + self.logger.error(f"An exception happened while querying the database file '{DATABASE_APPS_FILE}'") traceback.print_exc() finally: if not connection: # the connection can only be closed if it was opened within this method @@ -362,14 +363,17 @@ def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher not_upgraded = [] for req in requirements.to_upgrade: - watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) + watcher.change_status(f"{self.i18n['manage_window.status.upgrading']} {req.pkg.name} ({req.pkg.version})...") - download_data = self._download(req.pkg, watcher) + download_data = None - if not download_data: - not_upgraded.append(req.pkg) - watcher.change_substatus('') - continue + if not req.pkg.imported: + download_data = self._download(req.pkg, watcher) + + if not download_data: + not_upgraded.append(req.pkg) + watcher.change_substatus('') + continue if not self.uninstall(req.pkg, root_password, watcher).success: not_upgraded.append(req.pkg) @@ -409,17 +413,17 @@ def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher, self.revert_ignored_update(pkg) if pkg.symlink and os.path.islink(pkg.symlink): - self.logger.info("Removing symlink '{}'".format(pkg.symlink)) + self.logger.info(f"Removing symlink '{pkg.symlink}'") try: os.remove(pkg.symlink) - self.logger.info("symlink '{}' successfully removed".format(pkg.symlink)) + self.logger.info(f"symlink '{pkg.symlink}' successfully removed") except: - msg = "could not remove symlink '{}'".format(pkg.symlink) + msg = f"could not remove symlink '{pkg.symlink}'" self.logger.error(msg) if watcher: - watcher.print("[error] {}".format(msg)) + watcher.print(f"[error] {msg}") return TransactionResult(success=True, installed=None, removed=[pkg]) @@ -440,7 +444,7 @@ def get_info(self, pkg: AppImage) -> dict: categories = data.get('categories') if categories: - data['categories'] = [self.i18n.get('category.{}'.format(c.lower()), self.i18n.get(c, c)).capitalize() for c in data['categories']] + data['categories'] = [self.i18n.get(f'category.{c.lower()}', self.i18n.get(c, c)).capitalize() for c in data['categories']] if data.get('symlink') and not os.path.islink(data['symlink']): del data['symlink'] @@ -463,10 +467,10 @@ def get_history(self, pkg: AppImage) -> PackageHistory: app_tuple = cursor.fetchone() if not app_tuple: - self.logger.warning("Could not retrieve {} from the database {}".format(pkg, DATABASE_APPS_FILE)) + self.logger.warning(f"Could not retrieve {pkg} from the database '{DATABASE_APPS_FILE}'") return res except: - self.logger.error("An exception happened while querying the database file '{}'".format(DATABASE_APPS_FILE)) + self.logger.error(f"An exception happened while querying the database file '{DATABASE_APPS_FILE}'") traceback.print_exc() app_con.close() return res @@ -494,7 +498,7 @@ def get_history(self, pkg: AppImage) -> PackageHistory: return res except: - self.logger.error("An exception happened while querying the database file '{}'".format(DATABASE_RELEASES_FILE)) + self.logger.error(f"An exception happened while querying the database file '{DATABASE_RELEASES_FILE}'") traceback.print_exc() finally: releases_con.close() @@ -512,6 +516,13 @@ def _find_icon_file(self, folder: str) -> str: def _download(self, pkg: AppImage, watcher: ProcessWatcher) -> Optional[Tuple[str, str]]: appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download + + if not appimage_url: + watcher.show_message(title=self.i18n['error'], + body=self.i18n['appimage.download.no_url'].format(app=bold(pkg.name)), + type_=MessageType.ERROR) + return + file_name = appimage_url.split('/')[-1] pkg.version = pkg.latest_version pkg.url_download = appimage_url @@ -542,12 +553,12 @@ def install(self, pkg: AppImage, root_password: str, disk_loader: Optional[DiskC def _install(self, pkg: AppImage, watcher: ProcessWatcher, pre_downloaded_file: Optional[Tuple[str, str]] = None): handler = ProcessHandler(watcher) - out_dir = INSTALLATION_PATH + pkg.get_clean_name() + out_dir = f'{INSTALLATION_DIR}/{pkg.get_clean_name()}' 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) + self.logger.info(f"Installation dir '{out_dir}' already exists. Generating a different one") + out_dir += f'-{counter}' counter += 1 else: break @@ -559,18 +570,19 @@ def _install(self, pkg: AppImage, watcher: ProcessWatcher, pre_downloaded_file: downloaded, file_name = True, pkg.local_file_path.split('/')[-1] - file_path = out_dir + '/' + file_name + install_file_path = out_dir + '/' + file_name try: - moved, output = handler.handle_simple(SimpleProcess(['mv', pkg.local_file_path, file_path])) + moved, output = handler.handle_simple(SimpleProcess(['mv', pkg.local_file_path, install_file_path])) except: - self.logger.error("Could not rename file '' as '{}'".format(pkg.local_file_path, file_path)) + output = '' + self.logger.error(f"Could not rename file '{pkg.local_file_path}' as '{install_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)), + body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]), + bold(output)), type_=MessageType.ERROR) return TransactionResult.fail() @@ -593,83 +605,82 @@ def _install(self, pkg: AppImage, watcher: ProcessWatcher, pre_downloaded_file: dest=bold(install_file_path))) return TransactionResult.fail() - watcher.change_substatus(self.i18n['appimage.install.permission'].format(bold(file_name))) - permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', install_file_path]))) + watcher.change_substatus(self.i18n['appimage.install.permission'].format(bold(file_name))) + permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', install_file_path]))) - if permission_given: + if permission_given: - watcher.change_substatus(self.i18n['appimage.install.extract'].format(bold(file_name))) + watcher.change_substatus(self.i18n['appimage.install.extract'].format(bold(file_name))) - try: - res, output = handler.handle_simple( - SimpleProcess([install_file_path, '--appimage-extract'], cwd=out_dir)) - - if 'Error: Failed to register AppImage in AppImageLauncherFS' in output: - watcher.show_message(title=self.i18n['error'], - body=self.i18n['appimage.install.appimagelauncher.error'].format( - appimgl=bold('AppImageLauncher'), app=bold(pkg.name)), - type_=MessageType.ERROR) - handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return TransactionResult.fail() - except: + try: + res, output = handler.handle_simple( + SimpleProcess([install_file_path, '--appimage-extract'], cwd=out_dir)) + + if 'Error: Failed to register AppImage in AppImageLauncherFS' in output: watcher.show_message(title=self.i18n['error'], - body=traceback.format_exc(), + body=self.i18n['appimage.install.appimagelauncher.error'].format( + appimgl=bold('AppImageLauncher'), app=bold(pkg.name)), type_=MessageType.ERROR) - traceback.print_exc() handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) return TransactionResult.fail() + except: + watcher.show_message(title=self.i18n['error'], + body=traceback.format_exc(), + type_=MessageType.ERROR) + traceback.print_exc() + handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) + return TransactionResult.fail() - watcher.change_substatus(self.i18n['appimage.install.desktop_entry']) - extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') + watcher.change_substatus(self.i18n['appimage.install.desktop_entry']) + extracted_folder = f'{out_dir}/squashfs-root' - if os.path.exists(extracted_folder): - desktop_entry = self._find_desktop_file(extracted_folder) + if os.path.exists(extracted_folder): + desktop_entry = self._find_desktop_file(extracted_folder) - with open('{}/{}'.format(extracted_folder, desktop_entry)) as f: - de_content = f.read() + with open(f'{extracted_folder}/{desktop_entry}') as f: + de_content = f.read() - if de_content: - de_content = replace_desktop_entry_exec_command(desktop_entry=de_content, - appname=pkg.name, - file_path=install_file_path) - extracted_icon = self._find_icon_file(extracted_folder) + if de_content: + de_content = replace_desktop_entry_exec_command(desktop_entry=de_content, + appname=pkg.name, + file_path=install_file_path) + extracted_icon = self._find_icon_file(extracted_folder) - if extracted_icon: - icon_path = out_dir + '/logo.' + extracted_icon.split('/')[-1].split('.')[-1] - shutil.copy(extracted_icon, icon_path) + if extracted_icon: + icon_path = out_dir + '/logo.' + extracted_icon.split('/')[-1].split('.')[-1] + shutil.copy(extracted_icon, icon_path) - if de_content: - de_content = RE_DESKTOP_ICON.sub('Icon={}\n'.format(icon_path), de_content) + if de_content: + de_content = RE_DESKTOP_ICON.sub(f'Icon={icon_path}\n', de_content) - pkg.icon_path = icon_path + pkg.icon_path = icon_path - if not de_content: - de_content = pkg.to_desktop_entry() + if not de_content: + de_content = pkg.to_desktop_entry() - Path(DESKTOP_ENTRIES_PATH).mkdir(parents=True, exist_ok=True) + Path(DESKTOP_ENTRIES_DIR).mkdir(parents=True, exist_ok=True) - with open(self._gen_desktop_entry_path(pkg), 'w+') as f: - f.write(de_content) + with open(self._gen_desktop_entry_path(pkg), 'w+') as f: + f.write(de_content) - try: - shutil.rmtree(extracted_folder) - except: - traceback.print_exc() + try: + shutil.rmtree(extracted_folder) + except: + traceback.print_exc() - SymlinksVerifier.create_symlink(app=pkg, file_path=install_file_path, logger=self.logger, - watcher=watcher) - return TransactionResult(success=True, installed=[pkg], removed=[]) - else: - watcher.show_message(title=self.i18n['error'], - body='Could extract content from {}'.format(bold(file_name)), - type_=MessageType.ERROR) + SymlinksVerifier.create_symlink(app=pkg, file_path=install_file_path, logger=self.logger, + watcher=watcher) + return TransactionResult(success=True, installed=[pkg], removed=[]) + else: + watcher.show_message(title=self.i18n['error'], + body=f'Could extract content from {bold(file_name)}', + type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) return TransactionResult.fail() - def _gen_desktop_entry_path(self, app: AppImage) -> str: - return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.get_clean_name()) + return f'{DESKTOP_ENTRIES_DIR}/bauh_appimage_{app.get_clean_name()}.desktop' def is_enabled(self) -> bool: return self.enabled @@ -680,8 +691,15 @@ def set_enabled(self, enabled: bool): def _is_sqlite3_available(self) -> bool: return bool(shutil.which('sqlite3')) - def can_work(self) -> bool: - return self._is_sqlite3_available() and self.file_downloader.can_work() + def can_work(self) -> Tuple[bool, Optional[str]]: + if not self._is_sqlite3_available(): + return False, self.i18n['missing_dep'].format(dep=bold('sqlite3')) + + if not self.file_downloader.can_work(): + download_clients = ', '.join(self.file_downloader.get_supported_clients()) + return False, self.i18n['appimage.missing_downloader'].format(clients=download_clients) + + return True, None def requires_root(self, action: SoftwareAction, pkg: AppImage) -> bool: return False @@ -715,7 +733,7 @@ def list_updates(self, internet_available: bool) -> List[PackageUpdate]: return updates def list_warnings(self, internet_available: bool) -> List[str]: - dbfiles = glob.glob('{}/*.db'.format(APPIMAGE_CACHE_PATH)) + dbfiles = glob.glob(f'{APPIMAGE_CACHE_DIR}/*.db') if not dbfiles or len({f for f in (DATABASE_APPS_FILE, DATABASE_RELEASES_FILE) if f in dbfiles}) != 2: return [self.i18n['appimage.warning.missing_db_files'].format(appimage=bold('AppImage'))] @@ -755,12 +773,12 @@ def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSu break cursor = connection.cursor() - cursor.execute(query.FIND_APPS_BY_NAME_FULL.format(','.join(["'{}'".format(s) for s in sugs_map.keys()]))) + cursor.execute(query.FIND_APPS_BY_NAME_FULL.format(','.join([f"'{s}'" for s in sugs_map.keys()]))) for t in cursor.fetchall(): app = AppImage(*t, i18n=self.i18n, custom_actions=self.custom_app_actions) res.append(PackageSuggestion(app, sugs_map[app.name.lower()])) - self.logger.info("Mapped {} suggestions".format(len(res))) + self.logger.info(f"Mapped {len(res)} suggestions") except: traceback.print_exc() finally: @@ -780,7 +798,7 @@ def launch(self, pkg: AppImage): subprocess.Popen(args=[appimag_path], shell=True, env={**os.environ}, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) else: - self.logger.error("Could not find the AppImage file of '{}' in '{}'".format(pkg.name, installation_dir)) + self.logger.error(f"Could not find the AppImage file of '{pkg.name}' in '{installation_dir}'") def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool): self.serialize_to_disk(pkg, icon_bytes, only_icon) @@ -792,20 +810,20 @@ def get_screenshots(self, pkg: AppImage) -> List[str]: return [] def clear_data(self, logs: bool = True): - for f in glob.glob('{}/*.db'.format(LOCAL_PATH)): + for f in glob.glob(f'{APPIMAGE_SHARED_DIR}/*.db'): try: if logs: - print('[bauh][appimage] Deleting {}'.format(f)) + print(f'[bauh][appimage] Deleting {f}') os.remove(f) if logs: - print('{}[bauh][appimage] {} deleted{}'.format(Fore.YELLOW, f, Fore.RESET)) + print(f'{Fore.YELLOW}[bauh][appimage] {f} deleted{Fore.RESET}') except: if logs: - print('{}[bauh][appimage] An exception has happened when deleting {}{}'.format(Fore.RED, f, Fore.RESET)) + print(f'{Fore.RED}[bauh][appimage] An exception has happened when deleting {f}{Fore.RESET}') traceback.print_exc() - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: appimage_config = self.configman.get_config() max_width = floor(screen_width * 0.15) @@ -885,14 +903,14 @@ def ignore_update(self, pkg: AppImage): pkg.updates_ignored = True def _write_ignored_updates(self, names: Set[str]): - Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) + Path(APPIMAGE_CONFIG_DIR).mkdir(parents=True, exist_ok=True) ignored_list = [*names] ignored_list.sort() with open(UPDATES_IGNORED_FILE, 'w+') as f: if ignored_list: for ignored in ignored_list: - f.write('{}\n'.format(ignored)) + f.write(f'{ignored}\n') else: f.write('') diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py index 880268c21..9da6041ae 100644 --- a/bauh/gems/appimage/model.py +++ b/bauh/gems/appimage/model.py @@ -4,7 +4,7 @@ from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.commons import resource -from bauh.gems.appimage import ROOT_DIR, INSTALLATION_PATH +from bauh.gems.appimage import ROOT_DIR, INSTALLATION_DIR from bauh.view.util.translation import I18n RE_MANY_SPACES = re.compile(r'\s+') @@ -94,7 +94,7 @@ def get_disk_cache_path(self) -> str: if self.install_dir: return self.install_dir elif self.name: - return INSTALLATION_PATH + self.name.lower() + return f'{INSTALLATION_DIR}/{self.name.lower()}' def get_disk_icon_path(self): return self.icon_path diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca index 6e001ac4a..3007e66db 100644 --- a/bauh/gems/appimage/resources/locale/ca +++ b/bauh/gems/appimage/resources/locale/ca @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=No s’ha pogut revertir la versió appimage.downgrade.install_version=No s’ha pogut instal·lar la versió {} ({}) appimage.downgrade.unknown_version.body=No s’ha pogut identificar la versió actual de {} a l’historial de versions appimage.download.error=No s’ha pogut baixar el fitxer {}. El servidor del fitxer pot estar inactiu. +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {} appimage.history.0_version=versió appimage.history.1_published_at=data @@ -33,6 +34,7 @@ appimage.install.desktop_entry=S’està creant una drecera del menú appimage.install.extract=S’està extraient el contingut de {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=S’està concedint el permís d’execució a {} +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de index 809b4e579..37780d0ce 100644 --- a/bauh/gems/appimage/resources/locale/de +++ b/bauh/gems/appimage/resources/locale/de @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Downgrade nicht möglich appimage.downgrade.install_version=Die Installation der Version {} ({}) war nicht möglich appimage.downgrade.unknown_version.body=Die Identifikation der aktuellen Version von {} im Versionsverlauf war nicht möglich appimage.download.error=Das Herunterladen der Datei {} ist fehlgeschlagen. Eventuell ist der Server nicht verfügbar +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=Deinstallation der aktuellen Version von {} war nicht möglich appimage.history.0_version=Version appimage.history.1_published_at=Datum @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Menü-Shortcut erstellen appimage.install.extract=Entpacke Inhalt von {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Ausführberechtigungen für {} +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index d30d5989d..6e24001a8 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Impossible to downgrade appimage.downgrade.install_version=It was not possible to install the version {} ({}) appimage.downgrade.unknown_version.body={} current version was not found in its release history appimage.download.error=It was not possible to download the file {}. The file server can be down. +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {} appimage.history.0_version=version appimage.history.1_published_at=date @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Generating a menu shortcut appimage.install.extract=Extracting the content from {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Giving execution permission to {} +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es index 344d3caeb..f35d6d298 100644 --- a/bauh/gems/appimage/resources/locale/es +++ b/bauh/gems/appimage/resources/locale/es @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Imposible revertir la versión appimage.downgrade.install_version=No fue posible instalar la versión {} ({}) appimage.downgrade.unknown_version.body=No fue posible identificar la versión actual de {} en su historial de versiones appimage.download.error=No fue posible descargar el archivo {}. El servidor del archivo puede estar inactivo. +appimage.download.no_url=No hay URL de descarga asociada a la aplicación {app} appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {} appimage.history.0_version=versión appimage.history.1_published_at=fecha @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Creando un atajo en el menú appimage.install.extract=Extrayendo el contenido de {} appimage.install.imported.rename_error=No fue posible mover el archivo {} a {} appimage.install.permission=Concediendo permiso de ejecución a {} +appimage.missing_downloader=No hay ningún cliente de descarga instalado ({clients}) appimage.update_database.deleting_old=Eliminando archivos antiguos appimage.update_database.downloading=Descargando archivos de la base de datos appimage.update_database.uncompressing=Descomprindo archivos diff --git a/bauh/gems/appimage/resources/locale/fr b/bauh/gems/appimage/resources/locale/fr index 6c6699cfe..bb71fab32 100644 --- a/bauh/gems/appimage/resources/locale/fr +++ b/bauh/gems/appimage/resources/locale/fr @@ -16,6 +16,8 @@ appimage.downgrade.impossible.body={} a une seule version publiée. appimage.downgrade.impossible.title=Impossible à downgrader appimage.downgrade.install_version=Installer la version {} ({}) était impossible appimage.downgrade.unknown_version.body=Impossible d'identifier la version actuelle de {} dans son historique de versions +appimage.download.error=It was not possible to download the file {}. The file server can be down. +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=Impossible de désinstaller la version actuelle de {} appimage.history.0_version=version appimage.history.1_published_at=date @@ -32,6 +34,7 @@ appimage.install.download.error=Échec du téléchargement du fichier {}. Le ser appimage.install.extract=Extractiion du contenu de {} appimage.install.imported.rename_error=Impossible de déplacer {} vers {} appimage.install.permission={} est maintenant exécutable +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it index 9b9330cea..c7f3214a6 100644 --- a/bauh/gems/appimage/resources/locale/it +++ b/bauh/gems/appimage/resources/locale/it @@ -17,6 +17,8 @@ appimage.downgrade.impossible.body={} ha solo una versione pubblicata. appimage.downgrade.impossible.title=Impossibile eseguire il downgrade appimage.downgrade.install_version=Non è stato possibile installare la versione {} ({}) appimage.downgrade.unknown_version.body=Non è stato possibile identificare la {} versione corrente nella sua cronologia delle versioni +appimage.download.error=It was not possible to download the file {}. The file server can be down. +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=Non è stato possibile disinstallare la versione corrente di {} appimage.history.0_version=versione appimage.history.1_published_at=data @@ -33,6 +35,7 @@ appimage.install.download.error=Non è stato possibile scaricare il file {}. Il appimage.install.extract=Estrarre il contenuto da {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Gdare il permesso di esecuzione a {} +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt index 51e1dd980..34cdfd5c4 100644 --- a/bauh/gems/appimage/resources/locale/pt +++ b/bauh/gems/appimage/resources/locale/pt @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Impossível reverter a versão appimage.downgrade.install_version=Não foi possivel instalar a versão {} ({}) appimage.downgrade.unknown_version.body=Não foi possível identificar a versão atual de {} no seu histórico de versões appimage.download.error=Não foi possível baixar o arquivo {}. O servidor do arquivo pode estar fora do ar. +appimage.download.no_url=Nenhuma URL de download associada ao aplicativo {app} appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {} appimage.history.0_version=versão appimage.history.1_published_at=data @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Criando um atalho no menu appimage.install.extract=Extraindo o conteúdo de {} appimage.install.imported.rename_error=Não foi possível mover o arquivo {} para {} appimage.install.permission=Concedendo permissão de execução para {} +appimage.missing_downloader=Nenhum cliente de para download está instalado ({clients}) appimage.update_database.deleting_old=Removendo arquicos antigos appimage.update_database.downloading=Baixando arquivos do banco de dados appimage.update_database.uncompressing=Descomprimindo arquivos diff --git a/bauh/gems/appimage/resources/locale/ru b/bauh/gems/appimage/resources/locale/ru index f5d2ff1a0..8843dd9ea 100644 --- a/bauh/gems/appimage/resources/locale/ru +++ b/bauh/gems/appimage/resources/locale/ru @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Не удалось понизить вер appimage.downgrade.install_version=Не удалось установить версию {} ({}) appimage.downgrade.unknown_version.body=Не удалось определить текущую версию {} в истории версий appimage.download.error=Не удалось загрузить файл {}. Файловый сервер не отвечает +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version=Не удалось удалить текущую версию {} appimage.history.0_version=версия appimage.history.1_published_at=дата @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Создать ярлык в меню appimage.install.extract=Извлечь содержимое из {} appimage.install.imported.rename_error=Не удалось переместить файл {} в {} appimage.install.permission=Установить права на выполнение для {} +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/resources/locale/tr b/bauh/gems/appimage/resources/locale/tr index 2c0f79e65..7da746d46 100644 --- a/bauh/gems/appimage/resources/locale/tr +++ b/bauh/gems/appimage/resources/locale/tr @@ -18,6 +18,7 @@ appimage.downgrade.impossible.title=Sürüm düşürmek imkansız appimage.downgrade.install_version={} ({}) sürümünü kurmak mümkün değildi appimage.downgrade.unknown_version.body=Sürüm geçmişinde {} mevcut sürümü tanımlamak mümkün değildi appimage.download.error={} dosyası indirilemedi. Dosya sunucusu kapalı olabilir. +appimage.download.no_url=No download URL associated with the app {app} appimage.error.uninstall_current_version={} mevcut sürümünü kaldırmak mümkün değildi appimage.history.0_version=sürüm appimage.history.1_published_at=tarih @@ -33,6 +34,7 @@ appimage.install.desktop_entry=Bir menü kısayolu oluşturuluyor appimage.install.extract={} 'den içerik ayıklanıyor appimage.install.imported.rename_error={} dosyasını {} klasörüne taşımak mümkün değildi appimage.install.permission={} için yürütme izni veriliyor +appimage.missing_downloader=No download client installed ({clients}) appimage.update_database.deleting_old=Removing old files appimage.update_database.downloading=Downloading database files appimage.update_database.uncompressing=Uncompressing files diff --git a/bauh/gems/appimage/worker.py b/bauh/gems/appimage/worker.py index caef42bd4..e416f5f9c 100644 --- a/bauh/gems/appimage/worker.py +++ b/bauh/gems/appimage/worker.py @@ -16,15 +16,15 @@ from bauh.api.http import HttpClient from bauh.commons.boot import CreateConfigFile from bauh.commons.html import bold -from bauh.gems.appimage import get_icon_path, INSTALLATION_PATH, SYMLINKS_DIR, util, DATABASES_TS_FILE, \ - APPIMAGE_CACHE_PATH, DATABASE_APPS_FILE, DATABASE_RELEASES_FILE, URL_COMPRESSED_DATABASES, SUGGESTIONS_FILE, \ +from bauh.gems.appimage import get_icon_path, INSTALLATION_DIR, SYMLINKS_DIR, util, DATABASES_TS_FILE, \ + APPIMAGE_CACHE_DIR, DATABASE_APPS_FILE, DATABASE_RELEASES_FILE, URL_COMPRESSED_DATABASES, SUGGESTIONS_FILE, \ SUGGESTIONS_CACHED_TS_FILE, SUGGESTIONS_CACHED_FILE from bauh.gems.appimage.model import AppImage from bauh.view.util.translation import I18n class DatabaseUpdater(Thread): - COMPRESS_FILE_PATH = '{}/db.tar.gz'.format(APPIMAGE_CACHE_PATH) + COMPRESS_FILE_PATH = f'{APPIMAGE_CACHE_DIR}/db.tar.gz' def __init__(self, i18n: I18n, http_client: HttpClient, logger: logging.Logger, taskman: TaskManager, watcher: Optional[ProcessWatcher] = None, appimage_config: Optional[dict] = None, create_config: Optional[CreateConfigFile] = None): @@ -52,10 +52,10 @@ def should_update(self, appimage_config: dict) -> bool: self.logger.info("No expiration time configured for the AppImage database") return True - files = {*glob.glob('{}/*'.format(APPIMAGE_CACHE_PATH))} + files = {*glob.glob(f'{APPIMAGE_CACHE_DIR}/*')} if not files: - self.logger.warning('No database files on {}'.format(APPIMAGE_CACHE_PATH)) + self.logger.warning(f'No database files on {APPIMAGE_CACHE_DIR}') return True if DATABASES_TS_FILE not in files: @@ -105,7 +105,7 @@ def download_databases(self) -> bool: self.logger.warning('Could not download the database file {}'.format(URL_COMPRESSED_DATABASES)) return False - Path(APPIMAGE_CACHE_PATH).mkdir(parents=True, exist_ok=True) + Path(APPIMAGE_CACHE_DIR).mkdir(parents=True, exist_ok=True) with open(self.COMPRESS_FILE_PATH, 'wb+') as f: f.write(res.content) @@ -113,7 +113,7 @@ def download_databases(self) -> bool: self.logger.info("Database file saved at {}".format(self.COMPRESS_FILE_PATH)) self._update_task_progress(50, self.i18n['appimage.update_database.deleting_old']) - old_db_files = glob.glob(APPIMAGE_CACHE_PATH + '/*.db') + old_db_files = glob.glob(f'{APPIMAGE_CACHE_DIR}/*.db') if old_db_files: self.logger.info('Deleting old database files') @@ -127,7 +127,7 @@ def download_databases(self) -> bool: try: tf = tarfile.open(self.COMPRESS_FILE_PATH) - tf.extractall(APPIMAGE_CACHE_PATH) + tf.extractall(APPIMAGE_CACHE_DIR) self.logger.info('Successfully uncompressed file {}'.format(self.COMPRESS_FILE_PATH)) except: self.logger.error('Could not extract file {}'.format(self.COMPRESS_FILE_PATH)) @@ -233,8 +233,8 @@ def create_symlink(app: AppImage, file_path: str, logger: logging.Logger, watche watcher.print('[error] {}'.format(msg)) def run(self): - if os.path.exists(INSTALLATION_PATH): - installed_files = glob.glob('{}/*/*.json'.format(INSTALLATION_PATH)) + if os.path.exists(INSTALLATION_DIR): + installed_files = glob.glob(f'{INSTALLATION_DIR}/*/*.json') if installed_files: self.logger.info("Checking installed AppImage files with no symlinks created") diff --git a/bauh/gems/arch/__init__.py b/bauh/gems/arch/__init__.py index 22fcb91f3..05169a09b 100644 --- a/bauh/gems/arch/__init__.py +++ b/bauh/gems/arch/__init__.py @@ -1,24 +1,24 @@ import os -from pathlib import Path -from bauh.api.constants import CACHE_PATH, CONFIG_PATH, TEMP_DIR +from bauh import __app_name__ +from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -BUILD_DIR = '{}/arch'.format(TEMP_DIR) -ARCH_CACHE_PATH = CACHE_PATH + '/arch' -CATEGORIES_FILE_PATH = ARCH_CACHE_PATH + '/categories.txt' -URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/arch/categories.txt' -URL_GPG_SERVERS = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/arch/gpgservers.txt' -CONFIG_DIR = '{}/.config/bauh/arch'.format(str(Path.home())) -CUSTOM_MAKEPKG_FILE = '{}/makepkg.conf'.format(CONFIG_DIR) -AUR_INDEX_FILE = '{}/aur/index.txt'.format(ARCH_CACHE_PATH) -AUR_INDEX_TS_FILE = '{}/aur/index.ts'.format(ARCH_CACHE_PATH) -CONFIG_FILE = '{}/arch.yml'.format(CONFIG_PATH) -SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/arch/aur_suggestions.txt' -UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) -EDITABLE_PKGBUILDS_FILE = '{}/aur/editable_pkgbuilds.txt'.format(CONFIG_DIR) -IGNORED_REBUILD_CHECK_FILE = '{}/aur/ignored_rebuild_check.txt'.format(CONFIG_DIR) +BUILD_DIR = f'{TEMP_DIR}/arch' +ARCH_CACHE_DIR = f'{CACHE_DIR}/arch' +CATEGORIES_FILE_PATH = f'{ARCH_CACHE_DIR}/categories.txt' +URL_CATEGORIES_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/categories.txt' +URL_GPG_SERVERS = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/gpgservers.txt' +ARCH_CONFIG_DIR = f'{CONFIG_DIR}/arch' +CUSTOM_MAKEPKG_FILE = f'{ARCH_CONFIG_DIR}/makepkg.conf' +AUR_INDEX_FILE = f'{ARCH_CACHE_DIR}/aur/index.txt' +AUR_INDEX_TS_FILE = f'{ARCH_CACHE_DIR}/aur/index.ts' +CONFIG_FILE = f'{CONFIG_DIR}/arch.yml' +SUGGESTIONS_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/aur_suggestions.txt' +UPDATES_IGNORED_FILE = f'{ARCH_CONFIG_DIR}/updates_ignored.txt' +EDITABLE_PKGBUILDS_FILE = f'{ARCH_CONFIG_DIR}/aur/editable_pkgbuilds.txt' +IGNORED_REBUILD_CHECK_FILE = f'{ARCH_CONFIG_DIR}/aur/ignored_rebuild_check.txt' def get_icon_path() -> str: diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 3839b1e8f..1e8329ed6 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -25,9 +25,10 @@ from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \ FileChooserComponent, TextComponent -from bauh.api.constants import TEMP_DIR +from bauh.api.paths import TEMP_DIR from bauh.api.exception import NoInternetException -from bauh.commons import user, system +from bauh.commons import system +from bauh.api import user from bauh.commons.boot import CreateConfigFile from bauh.commons.category import CategoriesDownloader from bauh.commons.html import bold @@ -37,7 +38,7 @@ from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \ get_icon_path, database, mirrors, sorting, cpu_manager, UPDATES_IGNORED_FILE, \ - CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR, rebuild_detector + ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR, rebuild_detector from bauh.gems.arch.aur import AURClient from bauh.gems.arch.config import get_build_dir, ArchConfigManager from bauh.gems.arch.dependencies import DependenciesAnalyser @@ -1655,7 +1656,7 @@ def _get_history_repo_pkg(self, pkg: ArchPackage) -> PackageHistory: version_files[ver] = file_path versions.sort(reverse=True) - extract_path = '{}/arch/history'.format(TEMP_DIR) + extract_path = f'{TEMP_DIR}/arch/history' try: Path(extract_path).mkdir(parents=True, exist_ok=True) @@ -2643,20 +2644,20 @@ def _install_from_repository(self, context: TransactionContext) -> bool: return res - def _is_wget_available(self) -> bool: - return bool(shutil.which('wget')) - def is_enabled(self) -> bool: return self.enabled def set_enabled(self, enabled: bool): self.enabled = enabled - def can_work(self) -> bool: - try: - return self.arch_distro and pacman.is_available() and self._is_wget_available() - except FileNotFoundError: - return False + def can_work(self) -> Tuple[bool, Optional[str]]: + if not self.arch_distro: + return False, self.i18n['arch.can_work.not_arch_distro'] + + if not pacman.is_available(): + return False, self.i18n['missing_dep'].format(dep=bold('pacman')) + + return True, None def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool): pass @@ -2802,7 +2803,7 @@ def _gen_bool_selector(self, id_: str, label_key: str, tooltip_key: str, value: id_=id_, capitalize_label=capitalize_label) - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: arch_config = self.configman.get_config() max_width = floor(screen_width * 0.25) @@ -3192,7 +3193,7 @@ def _list_ignored_updates(self) -> Set[str]: return ignored def _write_ignored(self, names: Set[str]): - Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) + Path(ARCH_CONFIG_DIR).mkdir(parents=True, exist_ok=True) ignored_list = [*names] ignored_list.sort() diff --git a/bauh/gems/arch/cpu_manager.py b/bauh/gems/arch/cpu_manager.py index 47731176f..e10dd249e 100644 --- a/bauh/gems/arch/cpu_manager.py +++ b/bauh/gems/arch/cpu_manager.py @@ -4,6 +4,7 @@ from logging import Logger from typing import Optional, Set, Tuple, Dict +from bauh.api.paths import TEMP_DIR from bauh.commons.system import new_root_subprocess @@ -24,7 +25,7 @@ def current_governors() -> Dict[str, Set[int]]: def set_governor(governor: str, root_password: str, cpu_idxs: Optional[Set[int]] = None): - new_gov_file = '/tmp/bauh_scaling_governor' + new_gov_file = f'{TEMP_DIR}/bauh_scaling_governor' with open(new_gov_file, 'w+') as f: f.write(governor) diff --git a/bauh/gems/arch/database.py b/bauh/gems/arch/database.py index b07b8a611..63a17f807 100644 --- a/bauh/gems/arch/database.py +++ b/bauh/gems/arch/database.py @@ -7,10 +7,10 @@ from pathlib import Path from typing import Optional -from bauh.api.constants import CACHE_PATH +from bauh.api.paths import CACHE_DIR from bauh.commons.system import ProcessHandler -SYNC_FILE = '{}/arch/db_sync'.format(CACHE_PATH) +SYNC_FILE = f'{CACHE_DIR}/arch/db_sync' def should_sync(arch_config: dict, aur_supported: bool, handler: Optional[ProcessHandler], logger: logging.Logger): diff --git a/bauh/gems/arch/mirrors.py b/bauh/gems/arch/mirrors.py index 98cc0a196..81dacebda 100644 --- a/bauh/gems/arch/mirrors.py +++ b/bauh/gems/arch/mirrors.py @@ -6,9 +6,9 @@ from logging import Logger from pathlib import Path -from bauh.api.constants import CACHE_PATH +from bauh.api.paths import CACHE_DIR -SYNC_FILE = '{}/arch/mirrors_sync'.format(CACHE_PATH) +SYNC_FILE = f'{CACHE_DIR}/arch/mirrors_sync' def should_sync(logger: logging.Logger): diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py index f63c26d4a..6c628e493 100644 --- a/bauh/gems/arch/model.py +++ b/bauh/gems/arch/model.py @@ -2,7 +2,7 @@ from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.commons import resource -from bauh.gems.arch import ROOT_DIR, ARCH_CACHE_PATH +from bauh.gems.arch import ROOT_DIR, ARCH_CACHE_DIR from bauh.view.util.translation import I18n CACHED_ATTRS = {'command', 'icon_path', 'repository', 'maintainer', 'desktop_entry', 'categories', 'last_modified', 'commit'} @@ -85,7 +85,7 @@ def __init__(self, name: str = None, version: str = None, latest_version: str = @staticmethod def disk_cache_path(pkgname: str): - return ARCH_CACHE_PATH + '/installed/' + pkgname + return f'{ARCH_CACHE_DIR}/installed/{pkgname}' def get_pkg_build_url(self): if self.package_base: diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index 3075d0bb0..4b4f93372 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=S’està compilant el paquet {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=S’està comprovant si hi ha conflictes amb {} arch.checking.deps=S’estan comprovant les dependències de {} arch.checking.missing_deps=Verificació de les dependències que falten de {} diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 169854ede..4f92a835b 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=Paket {} erstellen +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=Konflikte mit {} überprüfen arch.checking.deps={} Abhängigkeiten überprüfen arch.checking.missing_deps=Überprüfen der fehlenden Abhängigkeiten von {} diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 42d40bbd8..269354501 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=Building package {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=Checking any conflicts with {} arch.checking.deps=Checking {} dependencies arch.checking.missing_deps=Verifying missing dependencies of {} diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index d46548bbe..674343ab9 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=El archivo de definición (PKGBUILD) de { arch.aur.sync.several_names.popup.bt_only_chosen=Compilar sólo {} arch.aur.sync.several_names.popup.bt_selected=Compilar seleccionados también arch.building.package=Construyendo el paquete {} +arch.can_work.not_arch_distro=Solo disponible para distribuciones basadas en ArchLinux arch.checking.conflicts=Verificando se hay conflictos con {} arch.checking.deps=Verificando las dependencias de {} arch.checking.missing_deps=Verificando las dependencias faltantes de {} diff --git a/bauh/gems/arch/resources/locale/fr b/bauh/gems/arch/resources/locale/fr index a9c761783..23db1beec 100644 --- a/bauh/gems/arch/resources/locale/fr +++ b/bauh/gems/arch/resources/locale/fr @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=Le fichier de définition (PKGBUILD) de { arch.aur.sync.several_names.popup.bt_only_chosen=Compiler seulement {} arch.aur.sync.several_names.popup.bt_selected=Compilation sélectionnée aussi arch.building.package=Compilation de {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=Vérification de conflits aved {} arch.checking.deps=Vérification des dépendances de {} arch.checking.missing_deps=Vérification des dépendances manquantes de {} diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 0a86345f6..8af43cc56 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=Pacchetto costruito {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=Verifica di eventuali conflitti con {} arch.checking.deps=Verifica di {} dipendenze arch.checking.missing_deps=Verifica delle dipendenze mancanti di {} diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index 7f1c27838..4037df265 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=O arquivo de definição (PKGBUILD) de {} arch.aur.sync.several_names.popup.bt_only_chosen=Construir somente {} arch.aur.sync.several_names.popup.bt_selected=Construir selecionados também arch.building.package=Construindo o pacote {} +arch.can_work.not_arch_distro=Somente disponível para distribuições baseadas em ArchLinux arch.checking.conflicts=Verificando se há conflitos com {} arch.checking.deps=Verificando as dependências de {} arch.checking.missing_deps=Verificando dependências ausentes de {} diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index f91ce42e2..36b9f4d2f 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=Сборка пакета {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts=Проверка конфликтов с {} arch.checking.deps=Проверка зависимостей {} arch.checking.missing_deps=Проверка отсутствующих зависимостей {} diff --git a/bauh/gems/arch/resources/locale/tr b/bauh/gems/arch/resources/locale/tr index 720dc739b..b90b42de4 100644 --- a/bauh/gems/arch/resources/locale/tr +++ b/bauh/gems/arch/resources/locale/tr @@ -37,6 +37,7 @@ arch.aur.sync.several_names.popup.body=The definition file (PKGBUILD) of {} conf arch.aur.sync.several_names.popup.bt_only_chosen=Build only {} arch.aur.sync.several_names.popup.bt_selected=Build selected too arch.building.package=Paket inşa ediliyor {} +arch.can_work.not_arch_distro=Only available for ArchLinux based distributions arch.checking.conflicts={} ile çakışmalar kontrol ediliyor arch.checking.deps={} bağımlılıkları kontrol ediliyor arch.checking.missing_deps={} eksik bağımlılıkları kontrol ediliyor diff --git a/bauh/gems/arch/worker.py b/bauh/gems/arch/worker.py index 841c71e2b..c33cd28ba 100644 --- a/bauh/gems/arch/worker.py +++ b/bauh/gems/arch/worker.py @@ -17,8 +17,8 @@ from bauh.commons.boot import CreateConfigFile from bauh.commons.html import bold from bauh.commons.system import new_root_subprocess, ProcessHandler -from bauh.gems.arch import pacman, disk, CUSTOM_MAKEPKG_FILE, CONFIG_DIR, AUR_INDEX_FILE, get_icon_path, database, \ - mirrors, ARCH_CACHE_PATH, AUR_INDEX_TS_FILE, aur +from bauh.gems.arch import pacman, disk, CUSTOM_MAKEPKG_FILE, ARCH_CONFIG_DIR, AUR_INDEX_FILE, get_icon_path, database, \ + mirrors, ARCH_CACHE_DIR, AUR_INDEX_TS_FILE, aur from bauh.gems.arch.aur import URL_INDEX from bauh.view.util.translation import I18n @@ -156,8 +156,8 @@ def __init__(self, taskman: TaskManager, i18n: I18n, logger: logging.Logger, self.progress = 0 # progress is defined by the number of packages prepared and indexed self.controller = controller self.internet_available = internet_available - self.installed_hash_path = '{}/installed.sha1'.format(ARCH_CACHE_PATH) - self.installed_cache_dir = '{}/installed'.format(ARCH_CACHE_PATH) + self.installed_hash_path = f'{ARCH_CACHE_DIR}/installed.sha1' + self.installed_cache_dir = f'{ARCH_CACHE_DIR}/installed' self.aur_indexer = aur_indexer self.create_config = create_config self.taskman.register_task(self.task_id, self.i18n['arch.task.disk_cache'], get_icon_path()) @@ -275,7 +275,7 @@ def optimize(self): with open(GLOBAL_MAKEPKG) as f: global_makepkg = f.read() - Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) + Path(ARCH_CONFIG_DIR).mkdir(parents=True, exist_ok=True) custom_makepkg, optimizations = None, [] diff --git a/bauh/gems/flatpak/__init__.py b/bauh/gems/flatpak/__init__.py index 4c1839dd5..0f6ec4c3a 100644 --- a/bauh/gems/flatpak/__init__.py +++ b/bauh/gems/flatpak/__init__.py @@ -3,15 +3,17 @@ from packaging.version import parse as parse_version -from bauh.api.constants import CONFIG_PATH +from bauh import __app_name__ +from bauh.api import user +from bauh.api.paths import CONFIG_DIR from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/flatpak/suggestions.txt' -CONFIG_FILE = '{}/flatpak.yml'.format(CONFIG_PATH) -CONFIG_DIR = '{}/flatpak'.format(CONFIG_PATH) -UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) -EXPORTS_PATH = '{}/.local/share/flatpak/exports/share'.format(str(Path.home())) +SUGGESTIONS_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/flatpak/suggestions.txt' +CONFIG_FILE = f'{CONFIG_DIR}/flatpak.yml' +FLATPAK_CONFIG_DIR = f'{CONFIG_DIR}/flatpak' +UPDATES_IGNORED_FILE = f'{FLATPAK_CONFIG_DIR}/updates_ignored.txt' +EXPORTS_PATH = '/usr/share/flatpak/exports/share' if user.is_root() else f'{Path.home()}/.local/share/flatpak/exports/share' VERSION_1_2 = parse_version('1.2') VERSION_1_3 = parse_version('1.3') VERSION_1_4 = parse_version('1.4') diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 71b12a6f0..d0bff64c7 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -17,11 +17,11 @@ SuggestionPriority, PackageStatus from bauh.api.abstract.view import MessageType, FormComponent, SingleSelectComponent, InputOption, SelectViewType, \ ViewComponent, PanelComponent -from bauh.commons import user +from bauh.api import user from bauh.commons.boot import CreateConfigFile from bauh.commons.html import strip_html, bold from bauh.commons.system import ProcessHandler -from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE, CONFIG_FILE, UPDATES_IGNORED_FILE, CONFIG_DIR, EXPORTS_PATH, \ +from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE, CONFIG_FILE, UPDATES_IGNORED_FILE, FLATPAK_CONFIG_DIR, EXPORTS_PATH, \ get_icon_path, VERSION_1_5, VERSION_1_2 from bauh.gems.flatpak.config import FlatpakConfigManager from bauh.gems.flatpak.constants import FLATHUB_API_URL @@ -375,9 +375,11 @@ def _make_exports_dir(self, watcher: ProcessWatcher) -> bool: return True def install(self, pkg: FlatpakApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: - flatpak_config = self.configman.get_config() - - install_level = flatpak_config['installation_level'] + if not self.context.root_user: + flatpak_config = self.configman.get_config() + install_level = flatpak_config['installation_level'] + else: + install_level = 'system' if install_level is not None: self.logger.info("Default Flaptak installation level defined: {}".format(install_level)) @@ -484,8 +486,8 @@ def is_enabled(self): def set_enabled(self, enabled: bool): self.enabled = enabled - def can_work(self) -> bool: - return flatpak.is_installed() + def can_work(self) -> Tuple[bool, Optional[str]]: + return (True, None) if flatpak.is_installed() else (False, self.i18n['missing_dep'].format(dep=bold('flatpak'))) def requires_root(self, action: SoftwareAction, pkg: FlatpakApplication) -> bool: return action == SoftwareAction.DOWNGRADE and pkg.installation == 'system' @@ -591,28 +593,29 @@ def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: return urls - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: - fields = [] - - flatpak_config = self.configman.get_config() - - install_opts = [InputOption(label=self.i18n['flatpak.config.install_level.system'].capitalize(), - value='system', - tooltip=self.i18n['flatpak.config.install_level.system.tip']), - InputOption(label=self.i18n['flatpak.config.install_level.user'].capitalize(), - value='user', - tooltip=self.i18n['flatpak.config.install_level.user.tip']), - InputOption(label=self.i18n['ask'].capitalize(), - value=None, - tooltip=self.i18n['flatpak.config.install_level.ask.tip'].format(app=self.context.app_name))] - fields.append(SingleSelectComponent(label=self.i18n['flatpak.config.install_level'], - options=install_opts, - default_option=[o for o in install_opts if o.value == flatpak_config['installation_level']][0], - max_per_line=len(install_opts), - max_width=floor(screen_width * 0.22), - type_=SelectViewType.RADIO)) - - return PanelComponent([FormComponent(fields, self.i18n['installation'].capitalize())]) + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: + if not self.context.root_user: + fields = [] + + flatpak_config = self.configman.get_config() + + install_opts = [InputOption(label=self.i18n['flatpak.config.install_level.system'].capitalize(), + value='system', + tooltip=self.i18n['flatpak.config.install_level.system.tip']), + InputOption(label=self.i18n['flatpak.config.install_level.user'].capitalize(), + value='user', + tooltip=self.i18n['flatpak.config.install_level.user.tip']), + InputOption(label=self.i18n['ask'].capitalize(), + value=None, + tooltip=self.i18n['flatpak.config.install_level.ask.tip'].format(app=self.context.app_name))] + fields.append(SingleSelectComponent(label=self.i18n['flatpak.config.install_level'], + options=install_opts, + default_option=[o for o in install_opts if o.value == flatpak_config['installation_level']][0], + max_per_line=len(install_opts), + max_width=floor(screen_width * 0.22), + type_=SelectViewType.RADIO)) + + return PanelComponent([FormComponent(fields, self.i18n['installation'].capitalize())]) def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: flatpak_config = self.configman.get_config() @@ -689,7 +692,7 @@ def _read_ignored_updates(self) -> Set[str]: return ignored def _write_ignored_updates(self, keys: Set[str]): - Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) + Path(FLATPAK_CONFIG_DIR).mkdir(parents=True, exist_ok=True) ignored_list = [*keys] ignored_list.sort() diff --git a/bauh/gems/snap/__init__.py b/bauh/gems/snap/__init__.py index 0a3521c22..582ce071b 100644 --- a/bauh/gems/snap/__init__.py +++ b/bauh/gems/snap/__init__.py @@ -1,12 +1,12 @@ import os -from bauh.api.constants import CACHE_PATH, CONFIG_PATH +from bauh.api.paths import CONFIG_DIR, CACHE_DIR from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -SNAP_CACHE_PATH = CACHE_PATH + '/snap' -CONFIG_FILE = '{}/snap.yml'.format(CONFIG_PATH) -CATEGORIES_FILE_PATH = SNAP_CACHE_PATH + '/categories.txt' +SNAP_CACHE_DIR = f'{CACHE_DIR}/snap' +CONFIG_FILE = f'{CONFIG_DIR}/snap.yml' +CATEGORIES_FILE_PATH = f'{SNAP_CACHE_DIR}/categories.txt' URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/categories.txt' SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/suggestions.txt' diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index fc8c7b675..d2aca52b3 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -253,8 +253,8 @@ def is_enabled(self) -> bool: def set_enabled(self, enabled: bool): self.enabled = enabled - def can_work(self) -> bool: - return snap.is_installed() + def can_work(self) -> Tuple[bool, Optional[str]]: + return (True, None) if snap.is_installed() else (False, self.i18n['missing_dep'].format(dep=bold('snap'))) def requires_root(self, action: SoftwareAction, pkg: SnapApplication) -> bool: return action not in (SoftwareAction.PREPARE, SoftwareAction.SEARCH) @@ -443,7 +443,7 @@ def launch(self, pkg: SnapApplication): def get_screenshots(self, pkg: SnapApplication) -> List[str]: return pkg.screenshots if pkg.has_screenshots() else [] - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: snap_config = self.configman.get_config() max_width = 200 diff --git a/bauh/gems/web/__init__.py b/bauh/gems/web/__init__.py index 331f2e23d..9041a0bdd 100644 --- a/bauh/gems/web/__init__.py +++ b/bauh/gems/web/__init__.py @@ -1,39 +1,39 @@ import os -from pathlib import Path -from bauh.api.constants import DESKTOP_ENTRIES_DIR, CONFIG_PATH, TEMP_DIR, CACHE_PATH +from bauh import __app_name__ +from bauh.api.paths import DESKTOP_ENTRIES_DIR, CONFIG_DIR, TEMP_DIR, CACHE_DIR, SHARED_FILES_DIR from bauh.commons import resource from bauh.commons.util import map_timestamp_file ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -WEB_PATH = '{}/.local/share/bauh/web'.format(Path.home()) -WEB_CACHE_PATH = '{}/web'.format(CACHE_PATH) -INSTALLED_PATH = '{}/installed'.format(WEB_PATH) -ENV_PATH = '{}/env'.format(WEB_PATH) -FIXES_PATH = '{}/fixes'.format(WEB_PATH) -NODE_DIR_PATH = '{}/node'.format(ENV_PATH) -NODE_PATHS = {NODE_DIR_PATH + '/bin'} -NODE_BIN_PATH = '{}/bin/node'.format(NODE_DIR_PATH) -NPM_BIN_PATH = '{}/bin/npm'.format(NODE_DIR_PATH) -NODE_MODULES_PATH = '{}/node_modules'.format(ENV_PATH) -NATIVEFIER_BIN_PATH = '{}/.bin/nativefier'.format(NODE_MODULES_PATH) -ELECTRON_PATH = '{}/.cache/electron'.format(str(Path.home())) +WEB_SHARED_DIR = f'{SHARED_FILES_DIR}/web' +WEB_CACHE_DIR = f'{CACHE_DIR}/web' +INSTALLED_PATH = f'{WEB_SHARED_DIR}/installed' +ENV_PATH = f'{WEB_SHARED_DIR}/env' +FIXES_PATH = f'{WEB_SHARED_DIR}/fixes' +NODE_DIR_PATH = f'{ENV_PATH}/node' +NODE_PATHS = {f'{NODE_DIR_PATH}/bin'} +NODE_BIN_PATH = f'{NODE_DIR_PATH}/bin/node' +NPM_BIN_PATH = f'{NODE_DIR_PATH}/bin/npm' +NODE_MODULES_PATH = f'{ENV_PATH}/node_modules' +NATIVEFIER_BIN_PATH = f'{NODE_MODULES_PATH}/.bin/nativefier' +ELECTRON_CACHE_DIR = f'{ENV_PATH}/electron' ELECTRON_DOWNLOAD_URL = 'https://github.com/electron/electron/releases/download/v{version}/electron-v{version}-linux-{arch}.zip' ELECTRON_SHA256_URL = 'https://github.com/electron/electron/releases/download/v{version}/SHASUMS256.txt' ELECTRON_WIDEVINE_URL = 'https://github.com/castlabs/electron-releases/releases/download/v{version}-wvvmp/electron-v{version}-wvvmp-linux-{arch}.zip' ELECTRON_WIDEVINE_SHA256_URL = 'https://github.com/castlabs/electron-releases/releases/download/v{version}-wvvmp/SHASUMS256.txt' -URL_ENVIRONMENT_SETTINGS = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v1/environment.yml' -DESKTOP_ENTRY_PATH_PATTERN = DESKTOP_ENTRIES_DIR + '/bauh.web.{name}.desktop' +URL_ENVIRONMENT_SETTINGS = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/web/env/v1/environment.yml' +DESKTOP_ENTRY_PATH_PATTERN = f'{DESKTOP_ENTRIES_DIR}/{__app_name__}.web.' + '{name}.desktop' URL_FIX_PATTERN = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/fix/{url}.js" URL_SUGGESTIONS = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v1/suggestions.yml" UA_CHROME = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36' -TEMP_PATH = '{}/web'.format(TEMP_DIR) -SEARCH_INDEX_FILE = '{}/index.yml'.format(WEB_CACHE_PATH) -SUGGESTIONS_CACHE_FILE = '{}/suggestions.yml'.format(WEB_CACHE_PATH) +TEMP_PATH = f'{TEMP_DIR}/web' +SEARCH_INDEX_FILE = f'{WEB_CACHE_DIR}/index.yml' +SUGGESTIONS_CACHE_FILE = f'{WEB_CACHE_DIR}/suggestions.yml' SUGGESTIONS_CACHE_TS_FILE = map_timestamp_file(SUGGESTIONS_CACHE_FILE) -CONFIG_FILE = '{}/web.yml'.format(CONFIG_PATH) -ENVIRONMENT_SETTINGS_CACHED_FILE = '{}/environment.yml'.format(WEB_CACHE_PATH) -ENVIRONMENT_SETTINGS_TS_FILE = '{}/environment.ts'.format(WEB_CACHE_PATH) +CONFIG_FILE = f'{CONFIG_DIR}/web.yml' +ENVIRONMENT_SETTINGS_CACHED_FILE = f'{WEB_CACHE_DIR}/environment.yml' +ENVIRONMENT_SETTINGS_TS_FILE = f'{WEB_CACHE_DIR}/environment.ts' NATIVEFIER_BASE_URL = 'https://github.com/nativefier/nativefier/archive/v{version}.tar.gz' diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 8d67ec725..76972514c 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -26,13 +26,13 @@ SuggestionPriority, PackageStatus from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, SingleSelectComponent, \ SelectViewType, TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, PanelComponent -from bauh.api.constants import DESKTOP_ENTRIES_DIR +from bauh.api.paths import DESKTOP_ENTRIES_DIR from bauh.commons import resource from bauh.commons.boot import CreateConfigFile from bauh.commons.html import bold from bauh.commons.system import ProcessHandler, get_dir_size, get_human_size_str, SimpleProcess from bauh.gems.web import INSTALLED_PATH, nativefier, DESKTOP_ENTRY_PATH_PATTERN, URL_FIX_PATTERN, ENV_PATH, UA_CHROME, \ - SUGGESTIONS_CACHE_FILE, ROOT_DIR, TEMP_PATH, FIXES_PATH, ELECTRON_PATH, \ + SUGGESTIONS_CACHE_FILE, ROOT_DIR, TEMP_PATH, FIXES_PATH, ELECTRON_CACHE_DIR, \ get_icon_path from bauh.gems.web.config import WebConfigManager from bauh.gems.web.environment import EnvironmentUpdater, EnvironmentComponent @@ -93,7 +93,7 @@ def clean_environment(self, root_password: str, watcher: ProcessWatcher) -> bool handler = ProcessHandler(watcher) success = True - for path in (ENV_PATH, ELECTRON_PATH): + for path in (ENV_PATH, ELECTRON_CACHE_DIR): self.logger.info("Checking path '{}'".format(path)) if os.path.exists(path): try: @@ -762,13 +762,16 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach pkg.desktop_entry = desktop_entry_path - if '--tray=start-in-tray' in install_options: - autostart_dir = '{}/.config/autostart'.format(Path.home()) - Path(autostart_dir).mkdir(parents=True, exist_ok=True) + autostart_file = pkg.get_autostart_path() - with open(pkg.get_autostart_path(), 'w+') as f: + if autostart_file and '--tray=start-in-tray' in install_options: + Path(os.path.dirname(autostart_file)).mkdir(parents=True, exist_ok=True) + + with open(autostart_file, 'w+') as f: f.write(entry_content) + self.logger.info(f"Autostart file created '{autostart_file}'") + if install_options: pkg.options_set = install_options @@ -795,17 +798,23 @@ def is_enabled(self) -> bool: def set_enabled(self, enabled: bool): self.enabled = enabled - def can_work(self) -> bool: - if BS4_AVAILABLE and LXML_AVAILABLE: - config = self.configman.get_config() - use_system_env = config['environment']['system'] + def can_work(self) -> Tuple[bool, Optional[str]]: + if not BS4_AVAILABLE: + return False, self.i18n['missing_dep'].format(dep=bold('python3-beautifulsoup4')) - if not use_system_env: - return True + if not LXML_AVAILABLE: + return False, self.i18n['missing_dep'].format(dep=bold('python3-lxml')) - return nativefier.is_available() + config = self.configman.get_config() + use_system_env = config['environment']['system'] - return False + if not use_system_env: + return True, None + + if not nativefier.is_available(): + return False, self.i18n['missing_dep'].format(dep=bold('nativefier')) + + return True, None def requires_root(self, action: SoftwareAction, pkg: SoftwarePackage) -> bool: return False @@ -993,7 +1002,7 @@ def clear_data(self, logs: bool = True): print('{}[bauh][web] An exception has happened when deleting {}{}'.format(Fore.RED, ENV_PATH, Fore.RESET)) traceback.print_exc() - def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewComponent]: web_config = self.configman.get_config() max_width = floor(screen_width * 0.15) diff --git a/bauh/gems/web/environment.py b/bauh/gems/web/environment.py index 7946ae510..0d105f8fb 100644 --- a/bauh/gems/web/environment.py +++ b/bauh/gems/web/environment.py @@ -20,7 +20,7 @@ from bauh.commons.html import bold from bauh.commons.system import SimpleProcess, ProcessHandler from bauh.gems.web import ENV_PATH, NODE_DIR_PATH, NODE_BIN_PATH, NODE_MODULES_PATH, NATIVEFIER_BIN_PATH, \ - ELECTRON_PATH, ELECTRON_DOWNLOAD_URL, ELECTRON_SHA256_URL, URL_ENVIRONMENT_SETTINGS, NPM_BIN_PATH, NODE_PATHS, \ + ELECTRON_CACHE_DIR, ELECTRON_DOWNLOAD_URL, ELECTRON_SHA256_URL, URL_ENVIRONMENT_SETTINGS, NPM_BIN_PATH, NODE_PATHS, \ nativefier, ELECTRON_WIDEVINE_URL, ELECTRON_WIDEVINE_SHA256_URL, \ ENVIRONMENT_SETTINGS_CACHED_FILE, ENVIRONMENT_SETTINGS_TS_FILE, get_icon_path, NATIVEFIER_BASE_URL from bauh.gems.web.model import WebApplication @@ -50,48 +50,48 @@ def __init__(self, logger: logging.Logger, http_client: HttpClient, file_downloa self.taskman = taskman def _download_and_install(self, version: str, version_url: str, watcher: ProcessWatcher) -> bool: - self.logger.info("Downloading NodeJS {}: {}".format(version, version_url)) + self.logger.info(f"Downloading NodeJS {version}: {version_url}") - tarf_path = '{}/{}'.format(ENV_PATH, version_url.split('/')[-1]) + tarf_path = f"{ENV_PATH}/{version_url.split('/')[-1]}" downloaded = self.file_downloader.download(version_url, watcher=watcher, output_path=tarf_path, cwd=ENV_PATH) if not downloaded: - self.logger.error("Could not download '{}'. Aborting...".format(version_url)) + self.logger.error(f"Could not download '{version_url}'. Aborting...") return False else: try: tf = tarfile.open(tarf_path) tf.extractall(path=ENV_PATH) - extracted_file = '{}/{}'.format(ENV_PATH, tf.getnames()[0]) + extracted_file = f'{ENV_PATH}/{tf.getnames()[0]}' if os.path.exists(NODE_DIR_PATH): - self.logger.info("Removing old NodeJS version installation dir -> {}".format(NODE_DIR_PATH)) + self.logger.info(f"Removing old NodeJS version installation dir -> {NODE_DIR_PATH}") try: shutil.rmtree(NODE_DIR_PATH) except: - self.logger.error("Could not delete old NodeJS version dir -> {}".format(NODE_DIR_PATH)) + self.logger.error(f"Could not delete old NodeJS version dir -> {NODE_DIR_PATH}") traceback.print_exc() return False try: os.rename(extracted_file, NODE_DIR_PATH) except: - self.logger.error("Could not rename the NodeJS version file {} as {}".format(extracted_file, NODE_DIR_PATH)) + self.logger.error(f"Could not rename the NodeJS version file {extracted_file} as {NODE_DIR_PATH}") traceback.print_exc() return False if os.path.exists(NODE_MODULES_PATH): - self.logger.info('Deleting {}'.format(NODE_MODULES_PATH)) + self.logger.info(f'Deleting {NODE_MODULES_PATH}') try: shutil.rmtree(NODE_MODULES_PATH) except: - self.logger.error("Could not delete the directory {}".format(NODE_MODULES_PATH)) + self.logger.error(f"Could not delete the directory {NODE_MODULES_PATH}") return False return True except: - self.logger.error('Could not extract {}'.format(tarf_path)) + self.logger.error(f'Could not extract {tarf_path}') traceback.print_exc() return False finally: @@ -99,13 +99,13 @@ def _download_and_install(self, version: str, version_url: str, watcher: Process try: os.remove(tarf_path) except: - self.logger.error('Could not delete file {}'.format(tarf_path)) + self.logger.error(f'Could not delete file {tarf_path}') def check_node_installed(self, version: str) -> bool: if not os.path.exists(NODE_DIR_PATH): return False else: - installed_version = system.run_cmd('{} --version'.format(NODE_BIN_PATH), print_error=False) + installed_version = system.run_cmd(f'{NODE_BIN_PATH} --version', print_error=False) if installed_version: installed_version = installed_version.strip() @@ -113,7 +113,7 @@ def check_node_installed(self, version: str) -> bool: if installed_version.startswith('v'): installed_version = installed_version[1:] - self.logger.info('Node versions: installed ({}), cloud ({})'.format(installed_version, version)) + self.logger.info(f'Node versions: installed ({installed_version}), cloud ({version})') if version != installed_version: self.logger.info("The NodeJs installed version is different from the Cloud.") @@ -139,7 +139,7 @@ def update_node(self, version: str, version_url: str, watcher: ProcessWatcher = if installed_version.startswith('v'): installed_version = installed_version[1:] - self.logger.info('Node versions: installed ({}), cloud ({})'.format(installed_version, version)) + self.logger.info(f'Node versions: installed ({installed_version}), cloud ({version})') if version != installed_version: self.logger.info("The NodeJs installed version is different from the Cloud.") @@ -149,17 +149,17 @@ def update_node(self, version: str, version_url: str, watcher: ProcessWatcher = return True else: self.logger.warning("Could not determine the current NodeJS installed version") - self.logger.info("Removing {}".format(NODE_DIR_PATH)) + self.logger.info(f"Removing {NODE_DIR_PATH}") try: shutil.rmtree(NODE_DIR_PATH) return self._download_and_install(version=version, version_url=version_url, watcher=watcher) except: - self.logger.error('Could not delete the dir {}'.format(NODE_DIR_PATH)) + self.logger.error(f'Could not delete the dir {NODE_DIR_PATH}') return False def _install_node_lib(self, name: str, version: str, handler: ProcessHandler): - lib_repr = '{}{}'.format(name, '@{}'.format(version) if version else '') - self.logger.info("Installing {}".format(lib_repr)) + lib_repr = f"{name}{'@{}'.format(version) if version else ''}" + self.logger.info(f"Installing {lib_repr}") if handler and handler.watcher: handler.watcher.change_substatus(self.i18n['web.environment.install'].format(bold(lib_repr))) @@ -169,15 +169,15 @@ def _install_node_lib(self, name: str, version: str, handler: ProcessHandler): installed = handler.handle_simple(proc)[0] if installed: - self.logger.info("{} successfully installed".format(lib_repr)) + self.logger.info(f"{lib_repr} successfully installed") return installed def _install_nativefier(self, version: str, url: str, handler: ProcessHandler) -> bool: - self.logger.info("Checking if nativefier@{} exists".format(version)) + self.logger.info(f"Checking if nativefier@{version} exists") if not url or not self.http_client.exists(url): - self.logger.warning("The file {} seems not to exist".format(url)) + self.logger.warning(f"The file {url} seems not to exist") handler.watcher.show_message(title=self.i18n['message.file.not_exist'], body=self.i18n['message.file.not_exist.body'].format(bold(url)), type_=MessageType.ERROR) @@ -191,35 +191,6 @@ def _install_nativefier(self, version: str, url: str, handler: ProcessHandler) - def _is_nativefier_installed(self) -> bool: return os.path.exists(NATIVEFIER_BIN_PATH) - def download_electron(self, version: str, url: str, widevine: bool, watcher: ProcessWatcher) -> bool: - Path(ELECTRON_PATH).mkdir(parents=True, exist_ok=True) - self.logger.info("Downloading Electron {}".format(version)) - - electron_path = self._get_electron_file_path(url=url, relative=False) - - if not self.http_client.exists(url): - self.logger.warning("The file {} seems not to exist".format(url)) - watcher.show_message(title=self.i18n['message.file.not_exist'], - body=self.i18n['message.file.not_exist.body'].format(bold(url)), - type_=MessageType.ERROR) - return False - - return self.file_downloader.download(file_url=url, watcher=watcher, output_path=electron_path, cwd=ELECTRON_PATH) - - def download_electron_sha256(self, version: str, url: str, widevine: bool, watcher: ProcessWatcher) -> bool: - self.logger.info("Downloading Electron {} sha526".format(version)) - - sha256_path = self._get_electron_file_path(url=url, relative=False) - - if not self.http_client.exists(url): - self.logger.warning("The file {} seems not to exist".format(url)) - watcher.show_message(title=self.i18n['message.file.not_exist'], - body=self.i18n['message.file.not_exist.body'].format(bold(url)), - type_=MessageType.ERROR) - return False - - return self.file_downloader.download(file_url=url, watcher=watcher, output_path=sha256_path, cwd=ELECTRON_PATH) - def _get_electron_url(self, version: str, is_x86_x64_arch: bool, widevine: bool) -> str: arch = 'x64' if is_x86_x64_arch else 'ia32' if widevine: @@ -233,37 +204,25 @@ def _get_electron_sha256_url(self, version: str, widevine: bool) -> str: else: return ELECTRON_SHA256_URL.format(version=version) - def _get_electron_file_path(self, url: str, relative: bool) -> str: - file_path = url.replace(':', '').replace('/', '') + '/' + url.split('/')[-1] - return '{}/{}'.format(ELECTRON_PATH, file_path) if not relative else file_path - def check_electron_installed(self, version: str, is_x86_x64_arch: bool, widevine: bool) -> Dict[str, bool]: - self.logger.info("Checking if Electron {} (widevine={}) is installed".format(version, widevine)) + self.logger.info(f"Checking if Electron {version} (widevine={widevine}) is installed") res = {'electron': False, 'sha256': False} - if not os.path.exists(ELECTRON_PATH): - self.logger.info("The Electron folder {} was not found".format(ELECTRON_PATH)) + if not os.path.exists(ELECTRON_CACHE_DIR): + self.logger.info(f"Electron cache directory {ELECTRON_CACHE_DIR} not found") else: - files = {f.split(ELECTRON_PATH + '/')[1] for f in glob.glob(ELECTRON_PATH + '/**', recursive=True) if os.path.isfile(f)} + files = {os.path.basename(f) for f in glob.glob(f'{ELECTRON_CACHE_DIR}/**', recursive=True) if os.path.isfile(f)} if files: electron_url = self._get_electron_url(version, is_x86_x64_arch, widevine) - file_path = self._get_electron_file_path(url=electron_url, relative=True) - - res['electron'] = file_path in files - - if not res['electron']: - res['sha256'] = True - else: - sha_url = self._get_electron_sha256_url(version=version, widevine=widevine) - sha_path = self._get_electron_file_path(url=sha_url, relative=True) - res['sha256'] = sha_path in files + res['electron'] = os.path.basename(electron_url) in files + res['sha256'] = res['electron'] else: - self.logger.info('No Electron file found in {}'.format(ELECTRON_PATH)) + self.logger.info(f"No Electron file found in '{ELECTRON_CACHE_DIR}'") for att in ('electron', 'sha256'): if res[att]: - self.logger.info('{} ({}) already downloaded'.format(att, version)) + self.logger.info(f'{att} ({version}) already downloaded') return res @@ -276,7 +235,7 @@ def should_download_settings(self, web_config: dict) -> bool: try: settings_exp = int(web_config['environment']['cache_exp']) except ValueError: - self.logger.error("Could not parse settings property 'environment.cache_exp': {}".format(web_config['environment']['cache_exp'])) + self.logger.error(f"Could not parse settings property 'environment.cache_exp': {web_config['environment']['cache_exp']}") return True if settings_exp <= 0: @@ -299,7 +258,7 @@ def should_download_settings(self, web_config: dict) -> bool: try: env_timestamp = datetime.fromtimestamp(float(env_ts_str)) except: - self.logger.error("Could not parse environment settings file timestamp: {}".format(env_ts_str)) + self.logger.error(f"Could not parse environment settings file timestamp: {env_ts_str}") return True expired = env_timestamp + timedelta(hours=settings_exp) <= datetime.utcnow() @@ -319,7 +278,7 @@ def read_cached_settings(self, web_config: dict) -> Optional[dict]: try: return yaml.safe_load(cached_settings_str) except yaml.YAMLError: - self.logger.error('Could not parse the cache environment settings file: {}'.format(cached_settings_str)) + self.logger.error(f'Could not parse the cache environment settings file: {cached_settings_str}') def read_settings(self, web_config: dict, cache: bool = True) -> Optional[dict]: if self.taskman: @@ -346,7 +305,7 @@ def read_settings(self, web_config: dict, cache: bool = True) -> Optional[dict]: try: settings = yaml.safe_load(res.content) except yaml.YAMLError: - self.logger.error('Could not parse environment settings: {}'.format(res.text)) + self.logger.error(f'Could not parse environment settings: {res.text}') self._finish_task_download_settings() return @@ -356,7 +315,7 @@ def read_settings(self, web_config: dict, cache: bool = True) -> Optional[dict]: try: Path(cache_dir).mkdir(parents=True, exist_ok=True) except OSError: - self.logger.error("Could not create Web cache directory: {}".format(cache_dir)) + self.logger.error(f"Could not create Web cache directory: {cache_dir}") self.logger.info('Finished') self._finish_task_download_settings() return @@ -380,11 +339,11 @@ def _check_and_fill_electron(self, pkg: WebApplication, env: dict, local_config: electron_version = env['electron-wvvmp' if widevine else 'electron']['version'] if not widevine and pkg.version and pkg.version != electron_version: # this feature does not support custom widevine electron at the moment - self.logger.info('A preset Electron version is defined for {}: {}'.format(pkg.url, pkg.version)) + self.logger.info(f'A preset Electron version is defined for {pkg.url}: {pkg.version}') electron_version = pkg.version if not widevine and local_config['environment']['electron']['version']: - self.logger.warning("A custom Electron version will be used {} to install {}".format(electron_version, pkg.url)) + self.logger.warning(f"A custom Electron version will be used {electron_version} to install {pkg.url}") electron_version = local_config['environment']['electron']['version'] electron_status = self.check_electron_installed(version=electron_version, is_x86_x64_arch=x86_x64, widevine=widevine) @@ -427,7 +386,7 @@ def _check_and_fill_node(self, env: dict, output: List[EnvironmentComponent]): def _check_nativefier_installed(self, nativefier_settings: dict) -> bool: if not os.path.exists(NODE_MODULES_PATH): - self.logger.info('Node modules path {} not found'.format(NODE_MODULES_PATH)) + self.logger.info(f'Node modules path {NODE_MODULES_PATH} not found') return False else: if not self._is_nativefier_installed(): @@ -438,7 +397,7 @@ def _check_nativefier_installed(self, nativefier_settings: dict) -> bool: if installed_version: installed_version = installed_version.strip() - self.logger.info("Nativefier versions: installed ({}), cloud ({})".format(installed_version, nativefier_settings['version'])) + self.logger.info(f"Nativefier versions: installed ({installed_version}), cloud ({nativefier_settings['version']})") if nativefier_settings['version'] != installed_version: self.logger.info("Installed nativefier version is different from cloud's. Changing version.") @@ -450,11 +409,11 @@ def _check_nativefier_installed(self, nativefier_settings: dict) -> bool: def _map_nativefier_file(self, nativefier_settings: dict) -> EnvironmentComponent: base_url = nativefier_settings.get('url') if not base_url: - self.logger.warning("'url' not found in nativefier environment settings. Using hardcoded URL '{}'".format(NATIVEFIER_BASE_URL)) + self.logger.warning(f"'url' not found in nativefier environment settings. Using hardcoded URL '{NATIVEFIER_BASE_URL}'") base_url = NATIVEFIER_BASE_URL url = base_url.format(version=nativefier_settings['version']) - return EnvironmentComponent(name='nativefier@{}'.format(nativefier_settings['version']), + return EnvironmentComponent(name=f"nativefier@{nativefier_settings['version']}", url=url, size=self.http_client.get_content_length(url), version=nativefier_settings['version'], @@ -462,17 +421,13 @@ def _map_nativefier_file(self, nativefier_settings: dict) -> EnvironmentComponen def check_environment(self, env: dict, local_config: dict, app: WebApplication, is_x86_x64_arch: bool, widevine: bool) -> List[EnvironmentComponent]: - """ - :param app: - :param is_x86_x64_arch: - :return: the environment settings - """ + components, check_threads = [], [] system_env = local_config['environment'].get('system', False) if system_env: - self.logger.warning("Using system's nativefier to install {}".format(app.url)) + self.logger.warning(f"Using system's nativefier to install {app.url}") else: node_check = Thread(target=self._check_and_fill_node, args=(env, components)) node_check.start() @@ -506,17 +461,5 @@ def update(self, components: List[EnvironmentComponent], handler: ProcessHandler if nativefier_data and not self._install_nativefier(version=nativefier_data.version, url=nativefier_data.url, handler=handler): return False - electron_data = comp_map.get('electron') - - if electron_data: - if not self.download_electron(version=electron_data.version, url=electron_data.url, watcher=handler.watcher, widevine=electron_data.properties['widevine']): - return False - - sha256_data = comp_map.get('electron_sha256') - - if sha256_data: - if not self.download_electron_sha256(version=sha256_data.version, url=sha256_data.url, watcher=handler.watcher, widevine=sha256_data.properties['widevine']): - return False - self.logger.info('Environment successfully updated') return True diff --git a/bauh/gems/web/model.py b/bauh/gems/web/model.py index 866538f33..d04f2d474 100644 --- a/bauh/gems/web/model.py +++ b/bauh/gems/web/model.py @@ -3,8 +3,11 @@ from pathlib import Path from typing import List +from bauh import __app_name__ +from bauh.api import user from bauh.api.abstract.model import SoftwarePackage -from bauh.commons import resource, user +from bauh.api.paths import AUTOSTART_DIR +from bauh.commons import resource from bauh.gems.web import ROOT_DIR @@ -54,11 +57,11 @@ def can_be_downgraded(self): def get_exec_path(self) -> str: if self.installation_dir: - return '{}/{}'.format(self.installation_dir, self.id) + return f'{self.installation_dir}/{self.id}' def get_command(self) -> str: if self.installation_dir: - return '{}{}'.format(self.get_exec_path(), ' --no-sandbox' if user.is_root() else '') + return f"{self.get_exec_path()}{' --no-sandbox' if user.is_root() else ''}" def get_type(self): return 'web' @@ -70,14 +73,14 @@ def get_default_icon_path(self) -> str: return resource.get_path('img/web.svg', ROOT_DIR) def get_disk_data_path(self) -> str: - return '{}/data.yml'.format(self.get_disk_cache_path()) + return f'{self.get_disk_cache_path()}/data.yml' def get_disk_icon_path(self) -> str: if self.custom_icon: return self.custom_icon if self.installation_dir: - return '{}/resources/app/icon.png'.format(self.installation_dir) + return f'{self.installation_dir}/resources/app/icon.png' def is_application(self): return True @@ -116,14 +119,14 @@ def is_trustable(self) -> bool: return False def get_publisher(self) -> str: - return 'bauh' + return __app_name__ def has_screenshots(self) -> bool: return False def get_autostart_path(self) -> str: if self.desktop_entry: - return '{}/.config/autostart/{}'.format(Path.home(), self.desktop_entry.split('/')[-1]) + return f"{AUTOSTART_DIR}/{self.desktop_entry.split('/')[-1]}" def set_custom_icon(self, custom_icon: str): self.custom_icon = custom_icon @@ -133,16 +136,16 @@ def set_custom_icon(self, custom_icon: str): def get_config_dir(self) -> str: if self.package_name: - config_dir = '{}/.config/{}'.format(str(Path.home()), self.package_name) + config_dir = f'{Path.home()}/.config/{self.package_name}' if os.path.exists(config_dir): return config_dir else: if self.installation_dir: - config_path = '{}/.config'.format(str(Path.home())) + config_path = f'{Path.home()}/.config' if os.path.exists(config_path): - config_dirs = glob.glob('{}/{}-nativefier-*'.format(config_path, self.installation_dir.split('/')[-1])) + config_dirs = glob.glob(f"{config_path}/{self.installation_dir.split('/')[-1]}-nativefier-*") if config_dirs: return config_dirs[0] diff --git a/bauh/gems/web/nativefier.py b/bauh/gems/web/nativefier.py index 3a807202e..a1590a943 100644 --- a/bauh/gems/web/nativefier.py +++ b/bauh/gems/web/nativefier.py @@ -1,8 +1,9 @@ +import os import shutil from typing import List, Optional from bauh.commons.system import SimpleProcess, run_cmd -from bauh.gems.web import NATIVEFIER_BIN_PATH, NODE_PATHS +from bauh.gems.web import NATIVEFIER_BIN_PATH, NODE_PATHS, ELECTRON_CACHE_DIR def install(url: str, name: str, output_dir: str, electron_version: Optional[str], cwd: str, system: bool, extra_options: List[str] = None) -> SimpleProcess: @@ -15,7 +16,8 @@ def install(url: str, name: str, output_dir: str, electron_version: Optional[str if extra_options: cmd.extend(extra_options) - return SimpleProcess(cmd, cwd=cwd, extra_paths=NODE_PATHS if not system else None) + return SimpleProcess(cmd, cwd=cwd, extra_paths=NODE_PATHS if not system else None, + extra_env={'XDG_CACHE_HOME': os.path.dirname(ELECTRON_CACHE_DIR)}) def is_available() -> bool: diff --git a/bauh/manage.py b/bauh/manage.py index 2e7f3370d..14458a175 100644 --- a/bauh/manage.py +++ b/bauh/manage.py @@ -5,6 +5,7 @@ from PyQt5.QtWidgets import QApplication, QWidget from bauh import ROOT_DIR, __app_name__ +from bauh.api import user from bauh.api.abstract.context import ApplicationContext from bauh.api.http import HttpClient from bauh.commons.internet import InternetChecker @@ -41,7 +42,8 @@ def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logg file_downloader=AdaptableFileDownloader(logger, bool(app_config['download']['multithreaded']), i18n, http_client, app_config['download']['multithreaded_client']), app_name=__app_name__, - internet_checker=InternetChecker(offline=app_args.offline)) + internet_checker=InternetChecker(offline=app_args.offline), + root_user=user.is_root()) managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config, default_locale=DEFAULT_I18N_KEY) diff --git a/bauh/stylesheet.py b/bauh/stylesheet.py index 4942754ec..37584c6e5 100644 --- a/bauh/stylesheet.py +++ b/bauh/stylesheet.py @@ -3,7 +3,7 @@ import re from typing import Optional, Dict, Tuple, Set -from bauh.api.constants import USER_THEMES_PATH +from bauh.api.paths import USER_THEMES_DIR from bauh.view.util import resource from bauh.view.util.translation import I18n @@ -114,7 +114,7 @@ def read_default_themes() -> Dict[str, str]: def read_user_themes() -> Dict[str, str]: - return {f: f for f in glob.glob('{}/**/*.qss'.format(USER_THEMES_PATH), recursive=True)} + return {f: f for f in glob.glob('{}/**/*.qss'.format(USER_THEMES_DIR), recursive=True)} def read_all_themes_metadata() -> Set[ThemeMetadata]: diff --git a/bauh/view/core/config.py b/bauh/view/core/config.py index 850eca3ad..c931eddc9 100644 --- a/bauh/view/core/config.py +++ b/bauh/view/core/config.py @@ -1,9 +1,7 @@ -from pathlib import Path - -from bauh import __app_name__ +from bauh.api.paths import CONFIG_DIR from bauh.commons.config import YAMLConfigManager -FILE_PATH = '{}/.config/{}/config.yml'.format(str(Path.home()), __app_name__) +FILE_PATH = f'{CONFIG_DIR}/config.yml' class CoreConfigManager(YAMLConfigManager): diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 70a94a871..ac889b105 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -4,7 +4,7 @@ import traceback from subprocess import Popen, STDOUT from threading import Thread -from typing import List, Set, Type, Tuple, Dict +from typing import List, Set, Type, Tuple, Dict, Optional from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ UpgradeRequirement, TransactionResult, SoftwareAction @@ -119,20 +119,19 @@ def _sort(self, apps: List[SoftwarePackage], word: str) -> List[SoftwarePackage] return res def _can_work(self, man: SoftwareManager): - if self._available_cache is not None: available = False for t in man.get_managed_types(): available = self._available_cache.get(t) if available is None: - available = man.is_enabled() and man.can_work() + available = man.is_enabled() and man.can_work()[0] self._available_cache[t] = available if available: available = True else: - available = man.is_enabled() and man.can_work() + available = man.is_enabled() and man.can_work()[0] if available: if man not in self.working_managers: @@ -198,8 +197,8 @@ def _wait_to_be_ready(self): def set_enabled(self, enabled: bool): pass - def can_work(self) -> bool: - return True + def can_work(self) -> Tuple[bool, Optional[str]]: + return True, None def _get_package_lower_name(self, pkg: SoftwarePackage): return pkg.name.lower() diff --git a/bauh/view/core/downloader.py b/bauh/view/core/downloader.py index a53f7718f..5c17d92c6 100644 --- a/bauh/view/core/downloader.py +++ b/bauh/view/core/downloader.py @@ -6,6 +6,7 @@ import traceback from io import StringIO from math import floor +from pathlib import Path from threading import Thread from typing import Iterable, List @@ -130,10 +131,20 @@ def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = No success = False ti = time.time() try: - if output_path and os.path.exists(output_path): - self.logger.info(f'Removing old file found before downloading: {output_path}') - os.remove(output_path) - self.logger.info(f'Old file {output_path} removed') + if output_path: + if os.path.exists(output_path): + self.logger.info(f'Removing old file found before downloading: {output_path}') + os.remove(output_path) + self.logger.info(f'Old file {output_path} removed') + else: + output_dir = os.path.dirname(output_path) + + try: + Path(output_dir).mkdir(exist_ok=True, parents=True) + except OSError: + self.logger.error(f"Could not make download directory '{output_dir}'") + watcher.print(self.i18n['error.mkdir'].format(dir=output_dir)) + return False client = self.get_available_multithreaded_tool() if client: @@ -223,3 +234,6 @@ def is_multithreaded_client_available(self, name: str) -> bool: def list_available_multithreaded_clients(self) -> List[str]: return [c for c in self.supported_multithread_clients if self.is_multithreaded_client_available(c)] + + def get_supported_clients(self) -> tuple: + return 'wget', 'aria2', 'axel' diff --git a/bauh/view/core/settings.py b/bauh/view/core/settings.py index b75e23e50..4244070b6 100644 --- a/bauh/view/core/settings.py +++ b/bauh/view/core/settings.py @@ -39,23 +39,26 @@ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: gem_opts, def_gem_opts, gem_tabs = [], set(), [] for man in self.managers: - if man.can_work(): - man_comp = man.get_settings(screen_width, screen_height) - modname = man.__module__.split('.')[-2] - icon_path = "{r}/gems/{n}/resources/img/{n}.svg".format(r=ROOT_DIR, n=modname) - - if man_comp: - tab_name = self.i18n.get('gem.{}.label'.format(modname), modname.capitalize()) - gem_tabs.append(TabComponent(label=tab_name, content=man_comp, icon_path=icon_path, id_=modname)) - - opt = InputOption(label=self.i18n.get('gem.{}.label'.format(modname), modname.capitalize()), - tooltip=self.i18n.get('gem.{}.info'.format(modname)), - value=modname, - icon_path='{r}/gems/{n}/resources/img/{n}.svg'.format(r=ROOT_DIR, n=modname)) - gem_opts.append(opt) - - if man.is_enabled() and man in self.working_managers: - def_gem_opts.add(opt) + can_work, reason_not_work = man.can_work() + modname = man.__module__.split('.')[-2] + icon_path = "{r}/gems/{n}/resources/img/{n}.svg".format(r=ROOT_DIR, n=modname) + + man_comp = man.get_settings(screen_width, screen_height) if can_work else None + if man_comp: + tab_name = self.i18n.get('gem.{}.label'.format(modname), modname.capitalize()) + gem_tabs.append(TabComponent(label=tab_name, content=man_comp, icon_path=icon_path, id_=modname)) + + help_tip = reason_not_work if not can_work and reason_not_work else self.i18n.get(f'gem.{modname}.info') + opt = InputOption(label=self.i18n.get('gem.{}.label'.format(modname), modname.capitalize()), + tooltip=help_tip, + value=modname, + icon_path='{r}/gems/{n}/resources/img/{n}.svg'.format(r=ROOT_DIR, n=modname), + read_only=not can_work, + extra_properties={'warning': 'true'} if not can_work else None) + gem_opts.append(opt) + + if man.is_enabled() and man in self.working_managers: + def_gem_opts.add(opt) core_config = self.configman.get_config() diff --git a/bauh/view/core/tray_client.py b/bauh/view/core/tray_client.py index 340174b0d..1bdad0f98 100644 --- a/bauh/view/core/tray_client.py +++ b/bauh/view/core/tray_client.py @@ -1,12 +1,12 @@ from pathlib import Path -from bauh.api.constants import CACHE_PATH +from bauh.api.paths import CACHE_DIR -TRAY_CHECK_FILE = '{}/notify_tray'.format(CACHE_PATH) # it is a file that signals to the tray icon it should recheck for updates +TRAY_CHECK_FILE = f'{CACHE_DIR}/notify_tray' # it is a file that signals to the tray icon it should recheck for updates def notify_tray(): - Path(CACHE_PATH).mkdir(exist_ok=True, parents=True) + Path(CACHE_DIR).mkdir(exist_ok=True, parents=True) with open(TRAY_CHECK_FILE, 'w+') as f: f.write('') diff --git a/bauh/view/core/update.py b/bauh/view/core/update.py index 7b69bcd79..168fdb209 100644 --- a/bauh/view/core/update.py +++ b/bauh/view/core/update.py @@ -5,8 +5,8 @@ from packaging.version import parse as parse_version from bauh import __app_name__, __version__ -from bauh.api.constants import CACHE_PATH from bauh.api.http import HttpClient +from bauh.api.paths import CACHE_DIR from bauh.commons.html import bold, link from bauh.view.util.translation import I18n @@ -33,7 +33,7 @@ def check_for_update(logger: logging.Logger, http_client: HttpClient, i18n: I18n break if latest and latest.get('tag_name'): - notifications_dir = '{}/updates'.format(CACHE_PATH) + notifications_dir = f'{CACHE_DIR}/updates' release_file = '{}/{}{}'.format(notifications_dir, '' if not tray else 'tray_', latest['tag_name']) if os.path.exists(release_file): logger.info("Release {} already notified".format(latest['tag_name'])) diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index ac5b80727..6c0cec62b 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -275,6 +275,10 @@ def __init__(self, model: InputOption, model_parent: SingleSelectComponent): self.setAttribute(Qt.WA_TransparentForMouseEvents) self.setFocusPolicy(Qt.NoFocus) + if model.extra_properties: + for name, val in model.extra_properties.items(): + self.setProperty(name, val) + def _set_checked(self, checked: bool): if checked: self.model_parent.value = self.model @@ -303,6 +307,10 @@ def __init__(self, model: InputOption, model_parent: MultipleSelectComponent, ca else: self.setCursor(QCursor(Qt.PointingHandCursor)) + if model.extra_properties: + for name, val in model.extra_properties.items(): + self.setProperty(name, val) + def _set_checked(self, state): checked = state == 2 @@ -541,7 +549,12 @@ def __init__(self, model: MultipleSelectComponent, callback): if op.tooltip: help_icon = QLabel() - help_icon.setProperty('help_icon', 'true') + + if op.extra_properties and op.extra_properties.get('warning') == 'true': + help_icon.setProperty('warning_icon', 'true') + else: + help_icon.setProperty('help_icon', 'true') + help_icon.setCursor(QCursor(Qt.WhatsThisCursor)) help_icon.setToolTip(op.tooltip) widget.layout().addWidget(help_icon) @@ -596,7 +609,12 @@ def __init__(self, model: MultipleSelectComponent, parent: QWidget = None): if op.tooltip: help_icon = QLabel() - help_icon.setProperty('help_icon', 'true') + + if op.extra_properties and op.extra_properties.get('warning') == 'true': + help_icon.setProperty('warning_icon', 'true') + else: + help_icon.setProperty('help_icon', 'true') + help_icon.setToolTip(op.tooltip) help_icon.setCursor(QCursor(Qt.WhatsThisCursor)) widget.layout().addWidget(help_icon) diff --git a/bauh/view/qt/prepare.py b/bauh/view/qt/prepare.py index e31c78a59..1439dd1f9 100644 --- a/bauh/view/qt/prepare.py +++ b/bauh/view/qt/prepare.py @@ -13,7 +13,7 @@ from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.controller import SoftwareManager, SoftwareAction from bauh.api.abstract.handler import TaskManager -from bauh.commons import user +from bauh.api import user from bauh.view.qt.components import new_spacer, QCustomToolbar from bauh.view.qt.qt_utils import centralize from bauh.view.qt.root import RootDialog diff --git a/bauh/view/qt/settings.py b/bauh/view/qt/settings.py index 11b987370..150431c24 100644 --- a/bauh/view/qt/settings.py +++ b/bauh/view/qt/settings.py @@ -38,7 +38,7 @@ class SettingsWindow(QWidget): def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, window: QWidget, parent: Optional[QWidget] = None): super(SettingsWindow, self).__init__(parent=parent, flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint) - self.setWindowTitle('{} ({})'.format(i18n['settings'].capitalize(), __app_name__)) + self.setWindowTitle(f"{i18n['settings'].capitalize()} ({__app_name__})") self.setLayout(QVBoxLayout()) self.manager = manager self.i18n = i18n @@ -115,8 +115,8 @@ def _save_settings(self): type_=MessageType.INFO) QCoreApplication.exit() elif ConfirmationDialog(title=self.i18n['warning'].capitalize(), - body="
{}
{}
".format(self.i18n['settings.changed.success.warning'], - self.i18n['settings.changed.success.reboot']), + body=f"{self.i18n['settings.changed.success.warning']}
" + f"{self.i18n['settings.changed.success.reboot']}
", i18n=self.i18n).ask(): self.close() util.restart_app() @@ -125,13 +125,16 @@ def _save_settings(self): QApplication.setOverrideCursor(Qt.WaitCursor) else: msg = StringIO() - msg.write("{}
".format(self.i18n['settings.error'])) + msg.write(f"{self.i18n['settings.error']}
") for w in warnings: - msg.write('* ' + w + '
* {w}