Skip to content

Commit

Permalink
0.10.1
Browse files Browse the repository at this point in the history
  • Loading branch information
vinifmor authored Mar 31, 2022
2 parents 79f7462 + c7fc6be commit fae6785
Show file tree
Hide file tree
Showing 58 changed files with 615 additions and 432 deletions.
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,62 @@ 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.10.1] 2022-03-31

### Features
- Flatpak
- new custom action "Full update": fully updates all installed Flatpak apps and components (useful if you are having issues with runtime updates)

<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.10.1/flatpak_full_update.png">
</p>


### Improvements
- General
- code refactoring
- backup:
- single mode: now supports two remove methods [#244](https://github.com/vinifmor/bauh/issues/244)
- self: it removes only self generated backups/snapshots (default)
- all: it removes all existing backup/snapshots on the disc

<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.10.1/bkp_remove.png">
</p>

- AppImage
- Limiting the UI components width of the file installation and upgrade windows

- Arch
- text length of some popups reduced

- Snap
- allowing the actions output locale to be decided by the Snap client

- UI
- only displaying the "Installed" filter when installed packages are available on the table
- settings: margin between components reduced [#241](https://github.com/vinifmor/bauh/issues/241)
- "close" button added to the screenshots window (some distributions hide the default "x" on the dialog frame) [#246](https://github.com/vinifmor/bauh/issues/246)

### Fixes
- Arch
- regression: not displaying ignored updates
- dependency size: display a '?' instead of '0' ('?' should only be displayed when the size is unknown)

- Debian
- packages descriptions are not displayed on the system's default language (when available)

- Flatpak:
- executed commands are not displayed on the system default language and encoding (requires Flatpak >= 1.12) [#242](https://github.com/vinifmor/bauh/issues/242)
- applications and runtimes descriptions are not displayed on the system default language (when available) [#242](https://github.com/vinifmor/bauh/issues/242)

- Web
- using the wrong locale format for the Accept-Language header

- UI:
- rare crash when updating table items
- some action errors not being displayed on the details component when they are concatenated with sudo output

## [0.10.0] 2022-03-14

### Features
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ prefer_repository_provider: true # when there is just one repository provider f
```
installation_level: null # defines a default installation level: "user" or "system". (null will display a popup asking the level)
```
- Custom actions supported:
- **Full update**: it completely updates the Flatpak apps and components. Useful if you are having issues with runtime updates.


#### <a name="type_snap">Snap</a>
Expand Down Expand Up @@ -436,12 +438,13 @@ disk:
after_upgrade: false # it trims the disk after a successful packages upgrade (`fstrim -a -v`). 'true' will automatically perform the trim and 'null' will display a confirmation dialog
backup:
enabled: true # generate timeshift snapshots before an action (if timeshift is installed on the system)
mode: 'incremental' # incremental=generates a new snapshot based on another pre-exising one. 'only_one'=deletes all pre-existing snapshots and generates a fresh one.
mode: 'incremental' # incremental=generates a new snapshot based on another pre-exising one. 'only_one'=deletes all pre-existing self created snapshots and generates a fresh one.
install: null # defines if the backup should be performed before installing a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation
uninstall: null # defines if the backup should be performed before uninstalling a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation
upgrade: null # defines if the backup should be performed before upgrading a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation
downgrade: null # defines if the backup should be performed before downgrading a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation
type: rsync # defines the Timeshift backup mode -> 'rsync' (default) or 'btrfs'
remove_method: self # define which backups should be removed in the 'only_one' mode. 'self': only self generated copies. 'all': all existing backups on the disc.
boot:
load_apps: true # if the installed applications or suggestions should be loaded on the management panel after the initialization process. Default: true.
```
Expand Down
2 changes: 1 addition & 1 deletion bauh/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.10.0'
__version__ = '0.10.1'
__app_name__ = 'bauh'

import os
Expand Down
4 changes: 3 additions & 1 deletion bauh/api/abstract/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def request_confirmation(self, title: str, body: Optional[str], components: List
confirmation_label: str = None, deny_label: str = None, deny_button: bool = True,
window_cancel: bool = False, confirmation_button: bool = True,
min_width: Optional[int] = None,
min_height: Optional[int] = None) -> bool:
min_height: Optional[int] = None,
max_width: Optional[int] = None) -> bool:
"""
request a user confirmation. In the current GUI implementation, it shows a popup to the user.
:param title: popup title
Expand All @@ -34,6 +35,7 @@ def request_confirmation(self, title: str, body: Optional[str], components: List
:param confirmation_button: if the confirmation button should be displayed
:param min_width: minimum width for the confirmation dialog
:param min_height: minimum height for the confirmation dialog
:param max_width: maximum width for the confirmation dialog
:return: if the request was confirmed by the user
"""
pass
Expand Down
42 changes: 25 additions & 17 deletions bauh/commons/system.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import re
import subprocess
import sys
import time
from io import StringIO
from subprocess import PIPE
from typing import List, Tuple, Set, Dict, Optional, Iterable
from typing import List, Tuple, Set, Dict, Optional, Iterable, Union, IO, Any

# default environment variables for subprocesses.
from bauh.api.abstract.handler import ProcessWatcher
Expand All @@ -13,7 +14,7 @@
GLOBAL_PY_LIBS = '/usr/lib/python{}'.format(PY_VERSION)

PATH = os.getenv('PATH')
DEFAULT_LANG = 'en'
DEFAULT_LANG = ''

GLOBAL_INTERPRETER_PATH = ':'.join(PATH.split(':')[1:])

Expand All @@ -22,8 +23,10 @@

USE_GLOBAL_INTERPRETER = bool(os.getenv('VIRTUAL_ENV'))

RE_SUDO_OUTPUT = re.compile(r'[sudo]\s*[\w\s]+:\s*')

def gen_env(global_interpreter: bool, lang: str = DEFAULT_LANG, extra_paths: Optional[Set[str]] = None) -> dict:

def gen_env(global_interpreter: bool, lang: Optional[str] = DEFAULT_LANG, extra_paths: Optional[Set[str]] = None) -> dict:
custom_env = dict(os.environ)

if lang is not None:
Expand Down Expand Up @@ -63,7 +66,7 @@ def wait(self):
class SimpleProcess:

def __init__(self, cmd: Iterable[str], cwd: str = '.', expected_code: int = 0,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, root_password: Optional[str] = None,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: Optional[str] = DEFAULT_LANG, root_password: Optional[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, extra_env: Optional[Dict[str, str]] = None,
custom_user: Optional[str] = None):
Expand All @@ -79,16 +82,17 @@ def __init__(self, cmd: Iterable[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, extra_env=extra_env)
self.instance = self._new(final_cmd, cwd, global_interpreter, lang=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,
def _new(self, cmd: List[str], cwd: str, global_interpreter: bool, lang: Optional[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)
env = gen_env(global_interpreter=global_interpreter, lang=lang, extra_paths=extra_paths)

if extra_env:
for var, val in extra_env.items():
Expand Down Expand Up @@ -197,8 +201,8 @@ def handle_simple(self, proc: SimpleProcess, output_handler=None, notify_watcher
except UnicodeDecodeError:
continue

if line.startswith('[sudo] password'):
continue
if line.startswith('[sudo]'):
line = RE_SUDO_OUTPUT.split(line)[1]

output.write(line)

Expand Down Expand Up @@ -240,15 +244,15 @@ def handle_simple(self, proc: SimpleProcess, output_handler=None, notify_watcher

def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, print_error: bool = True,
cwd: str = '.', global_interpreter: bool = USE_GLOBAL_INTERPRETER, extra_paths: Set[str] = None,
custom_user: Optional[str] = None) -> Optional[str]:
custom_user: Optional[str] = None, lang: Optional[str] = DEFAULT_LANG) -> Optional[str]:
"""
runs a given command and returns its default output
:return:
"""
args = {
"shell": True,
"stdout": PIPE,
"env": gen_env(global_interpreter, extra_paths=extra_paths),
"env": gen_env(global_interpreter=global_interpreter, lang=lang, extra_paths=extra_paths),
'cwd': cwd
}

Expand All @@ -265,8 +269,8 @@ def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False,
pass


def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin = None,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG,
def new_subprocess(cmd: Iterable[str], cwd: str = '.', shell: bool = False, stdin: Optional[Union[None, int, IO[Any]]] = None,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: Optional[str] = DEFAULT_LANG,
extra_paths: Set[str] = None, custom_user: Optional[str] = None) -> subprocess.Popen:
args = {
"stdout": PIPE,
Expand All @@ -281,18 +285,22 @@ def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin =
return subprocess.Popen(final_cmd, **args)


def new_root_subprocess(cmd: List[str], root_password: Optional[str], cwd: str = '.',
def new_root_subprocess(cmd: Iterable[str], root_password: Optional[str], cwd: str = '.',
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG,
extra_paths: Set[str] = None) -> subprocess.Popen:
pwdin, final_cmd = None, []
extra_paths: Set[str] = None, shell: bool = False) -> subprocess.Popen:
pwdin, final_cmd = subprocess.DEVNULL, []

if isinstance(root_password, str):
final_cmd.extend(['sudo', '-S'])
pwdin = new_subprocess(['echo', root_password], global_interpreter=global_interpreter, lang=lang).stdout

final_cmd.extend(cmd)

return subprocess.Popen(final_cmd, stdin=pwdin, stdout=PIPE, stderr=PIPE, cwd=cwd, env=gen_env(global_interpreter, lang, extra_paths))
if shell:
final_cmd = ' '.join(final_cmd)

return subprocess.Popen(final_cmd, stdin=pwdin, stdout=PIPE, stderr=PIPE, cwd=cwd,
env=gen_env(global_interpreter, lang, extra_paths), shell=shell)


def notify_user(msg: str, app_name: str, icon_path: str):
Expand Down
5 changes: 2 additions & 3 deletions bauh/commons/view_utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from typing import List, Tuple, Optional
from typing import Tuple, Optional, Iterable

from bauh.api.abstract.view import SelectViewType, InputOption, SingleSelectComponent


SIZE_UNITS = ((1, 'B'), (1024, 'Kb'), (1048576, 'Mb'), (1073741824, 'Gb'),
(1099511627776, 'Tb'), (1125899906842624, 'Pb'))


def new_select(label: str, tip: str, id_: str, opts: List[Tuple[Optional[str], object, Optional[str]]], value: object, max_width: int,
def new_select(label: str, tip: Optional[str], id_: str, opts: Iterable[Tuple[Optional[str], object, Optional[str]]], value: object, max_width: int,
type_: SelectViewType = SelectViewType.RADIO, capitalize_label: bool = True):
inp_opts = [InputOption(label=o[0].capitalize(), value=o[1], tooltip=o[2]) for o in opts]
def_opt = [o for o in inp_opts if o.value == value]
Expand Down
27 changes: 17 additions & 10 deletions bauh/gems/appimage/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,31 @@ def __init__(self, context: ApplicationContext):
self._search_unfilled_attrs: Optional[Tuple[str, ...]] = None

def install_file(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
max_width = 350
file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(),
allowed_extensions={'AppImage', '*'},
search_path=get_default_manual_installation_file_dir())
input_name = TextInputComponent(label=self.i18n['name'].capitalize())
input_version = TextInputComponent(label=self.i18n['version'].capitalize())
search_path=get_default_manual_installation_file_dir(),
max_width=max_width)
input_name = TextInputComponent(label=self.i18n['name'].capitalize(), max_width=max_width)
input_version = TextInputComponent(label=self.i18n['version'].capitalize(), max_width=max_width)
file_chooser.observers.append(ManualInstallationFileObserver(input_name, input_version))

input_description = TextInputComponent(label=self.i18n['description'].capitalize())
input_description = TextInputComponent(label=self.i18n['description'].capitalize(), max_width=max_width)

cat_ops = [InputOption(label=self.i18n['category.none'].capitalize(), value=0)]
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])
default_option=cat_ops[0], max_width=max_width)

form = FormComponent(label='', components=[file_chooser, input_name, input_version, input_description, inp_cat], spaces=False)
form = FormComponent(label='', components=[file_chooser, input_name, input_version, input_description, inp_cat],
spaces=False)

while True:
if watcher.request_confirmation(title=self.i18n['appimage.custom_action.install_file.details'], body=None,
components=[form],
confirmation_label=self.i18n['proceed'].capitalize(),
deny_label=self.i18n['cancel'].capitalize()):
deny_label=self.i18n['cancel'].capitalize(),
min_height=100, max_width=max_width + 150):
if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path) or not file_chooser.file_path.lower().strip().endswith('.appimage'):
watcher.request_confirmation(title=self.i18n['error'].capitalize(),
body=self.i18n['appimage.custom_action.install_file.invalid_file'],
Expand Down Expand Up @@ -139,17 +143,20 @@ def install_file(self, root_password: Optional[str], watcher: ProcessWatcher) ->
return res

def update_file(self, pkg: AppImage, root_password: Optional[str], watcher: ProcessWatcher):
max_width = 350
file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(),
allowed_extensions={'AppImage', '*'},
search_path=get_default_manual_installation_file_dir())
input_version = TextInputComponent(label=self.i18n['version'].capitalize())
search_path=get_default_manual_installation_file_dir(),
max_width=max_width)
input_version = TextInputComponent(label=self.i18n['version'].capitalize(), max_width=max_width)
file_chooser.observers.append(ManualInstallationFileObserver(None, input_version))

while True:
if watcher.request_confirmation(title=self.i18n['appimage.custom_action.manual_update.details'], body=None,
components=[FormComponent(label='', components=[file_chooser, input_version], spaces=False)],
confirmation_label=self.i18n['proceed'].capitalize(),
deny_label=self.i18n['cancel'].capitalize()):
deny_label=self.i18n['cancel'].capitalize(),
min_height=100, max_width=max_width + 150):

if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path) or not file_chooser.file_path.lower().strip().endswith('.appimage'):
watcher.request_confirmation(title=self.i18n['error'].capitalize(),
Expand Down
Loading

0 comments on commit fae6785

Please sign in to comment.