From d43548016531da46f9e706d008282782426f14f3 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Tue, 8 Dec 2020 18:55:57 -0300 Subject: [PATCH] [appimage] fix: missing **Exec** parameters on generated desktop entries --- CHANGELOG.md | 2 + bauh/gems/appimage/controller.py | 8 +- bauh/gems/appimage/util.py | 28 ++++ tests/gems/appimage/__init__.py | 0 tests/gems/appimage/test_util.py | 239 +++++++++++++++++++++++++++++++ 5 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 tests/gems/appimage/__init__.py create mode 100644 tests/gems/appimage/test_util.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c34a9e89..38e1bb96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ``` ### Fixes +- AppImage + - missing **Exec** parameters on generated desktop entries [#152](https://github.com/vinifmor/bauh/issues/152) - Core - bauh release notification not working properly - Web: diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index a433cb28..5aea10b7 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -32,6 +32,7 @@ from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir from bauh.gems.appimage.config import read_config from bauh.gems.appimage.model import AppImage +from bauh.gems.appimage.util import replace_desktop_entry_exec_command from bauh.gems.appimage.worker import DatabaseUpdater, SymlinksVerifier DB_APPS_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/apps.db') @@ -39,7 +40,6 @@ DB_RELEASES_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/ DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(str(Path.home())) -RE_DESKTOP_EXEC = re.compile(r'Exec\s*=\s*.+\n') RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n') RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$') RE_APPIMAGE_NAME = re.compile(r'(.+)\.appimage', flags=re.IGNORECASE) @@ -530,7 +530,9 @@ class AppImageManager(SoftwareManager): with open('{}/{}'.format(extracted_folder, desktop_entry)) as f: de_content = f.read() - de_content = RE_DESKTOP_EXEC.sub('Exec="{}"\n'.format(file_path), de_content) + de_content = replace_desktop_entry_exec_command(desktop_entry=de_content, + appname=pkg.name, + file_path=file_path) extracted_icon = self._find_icon_file(extracted_folder) @@ -675,7 +677,7 @@ class AppImageManager(SoftwareManager): else: self.logger.error("Could not find the AppImage file of '{}' in '{}'".format(pkg.name, installation_dir)) - def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): + def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool): self.serialize_to_disk(pkg, icon_bytes, only_icon) def get_screenshots(self, pkg: AppImage) -> List[str]: diff --git a/bauh/gems/appimage/util.py b/bauh/gems/appimage/util.py index c717f5b4..d1a39274 100644 --- a/bauh/gems/appimage/util.py +++ b/bauh/gems/appimage/util.py @@ -1,4 +1,8 @@ import os +import re + +RE_DESKTOP_EXEC = re.compile(r'(Exec\s*=(.+)(\n?))') +RE_MANY_SPACES = re.compile(r'\s+') def find_appimage_file(folder: str) -> str: @@ -6,3 +10,27 @@ def find_appimage_file(folder: str) -> str: for f in files: if f.lower().endswith('.appimage'): return '{}/{}'.format(folder, f) + + +def replace_desktop_entry_exec_command(desktop_entry: str, appname: str, file_path: str) -> str: + execs = RE_DESKTOP_EXEC.findall(desktop_entry) + + if not execs: + return desktop_entry + + final_entry = desktop_entry + treated_name = appname.strip().lower() + + for exec_groups in execs: + full_match = exec_groups[0] + cmd = RE_MANY_SPACES.sub(' ', exec_groups[1].strip()) + if cmd: + words = cmd.split(' ') + + for idx in range(len(words)): + if words[idx].lower() == treated_name: + words[idx] = '"{}"'.format(file_path) + + final_entry = final_entry.replace(full_match, full_match.replace(exec_groups[1], ' '.join(words))) + + return final_entry diff --git a/tests/gems/appimage/__init__.py b/tests/gems/appimage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/gems/appimage/test_util.py b/tests/gems/appimage/test_util.py new file mode 100644 index 00000000..77cbf44b --- /dev/null +++ b/tests/gems/appimage/test_util.py @@ -0,0 +1,239 @@ +from unittest import TestCase + +from bauh.gems.appimage.util import replace_desktop_entry_exec_command + + +class TestUtil(TestCase): + + def test_replace_desktop_entry_exec_command__only_one_exec_field_no_spaces_and_no_params(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=myapp + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec="/path/to/myapp.appimage" + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__only_one_exec_field_command_with_different_cases(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=MyApP + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec="/path/to/myapp.appimage" + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__only_one_exec_field_no_spaces_and_params(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=myapp %f + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec="/path/to/myapp.appimage" %f + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__only_one_exec_field_no_line_jump_in_the_end(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=myapp %f""" + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec="/path/to/myapp.appimage" %f""" + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__only_one_exec_field_with_spaces_and_params(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec = myapp %f --a + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec ="/path/to/myapp.appimage" %f --a + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__param_with_the_same_app_name(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=myapp %f --myapp + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec="/path/to/myapp.appimage" %f --myapp + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__evvar_with_same_app_name(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + Exec=MYAPP=123 myapp + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + Exec=MYAPP=123 "/path/to/myapp.appimage" + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__only_one_tryexec_field_with_spaces_and_params(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + TryExec = myapp %f --a + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + TryExec ="/path/to/myapp.appimage" %f --a + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__exec_and_tryexec_fields(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + TryExec = myapp %f + Exec=myapp --a + Terminal=false + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + TryExec ="/path/to/myapp.appimage" %f + Exec="/path/to/myapp.appimage" --a + Terminal=false + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__exec_and_tryexec_fields_with_envvars_and_params(self): + desktop_entry = """ + Name=MyApp + Icon=MyApp + TryExec=__MY_VAR=1 myapp %f + Exec=NEW_VAR=abc myapp --a + Terminal=false + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='myapp', + file_path='/path/to/myapp.appimage') + + expected = """ + Name=MyApp + Icon=MyApp + TryExec=__MY_VAR=1 "/path/to/myapp.appimage" %f + Exec=NEW_VAR=abc "/path/to/myapp.appimage" --a + Terminal=false + """ + + self.assertEqual(expected, res) + + def test_replace_desktop_entry_exec_command__rpcs3(self): + desktop_entry = """ + [Desktop Entry] +Type=Application +Name=RPCS3 +GenericName=PlayStation 3 Emulator +Comment=An open-source PlayStation 3 emulator/debugger written in C++. +Icon=rpcs3 +TryExec=rpcs3 +Exec=rpcs3 %f +Terminal=false +Categories=Game;Emulator; +Keywords=PS3;Playstation; + + """ + + res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry, + appname='rpcs3', + file_path='/path/to/rpcs3.appimage') + + expected = """ + [Desktop Entry] +Type=Application +Name=RPCS3 +GenericName=PlayStation 3 Emulator +Comment=An open-source PlayStation 3 emulator/debugger written in C++. +Icon=rpcs3 +TryExec="/path/to/rpcs3.appimage" +Exec="/path/to/rpcs3.appimage" %f +Terminal=false +Categories=Game;Emulator; +Keywords=PS3;Playstation; + + """ + + self.assertEqual(expected, res)