0.7.1
45
CHANGELOG.md
@@ -4,6 +4,49 @@ 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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [0.7.1] 2019-10-25
|
||||||
|
### Features
|
||||||
|
- Snap:
|
||||||
|
- if the **stable** channel is not available while an application is being installed, a popup is displayed allowing the user to choose a different one ( e.g: dbeaver-ce )
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- 3 password attempts for root authentication
|
||||||
|
- not changing the table applied filters after a uninstall
|
||||||
|
- cleaning the progress bar substatus after each upgrade
|
||||||
|
- sorted categories
|
||||||
|
- AppImage:
|
||||||
|
- showing an error popup when **AppImageLauncher** messes up with an application installation
|
||||||
|
- Flatpak:
|
||||||
|
- Runtimes now are categorized as "runtime"
|
||||||
|
- Formatting the API categories to the same format provided by the other packaging technologies
|
||||||
|
- AUR:
|
||||||
|
- showing a "user-friendly" popup when there are integrity issues with the source-files of a building package
|
||||||
|
- not waiting for the categories file to be retrieved from the cloud during application boot ( reduces boot time )
|
||||||
|
- caching cloud categories to the disk so they can be used in scenarios when it is not possible to retrieve them ( e.g: internet is off )
|
||||||
|
- mapping known search key words to the specific package name ( e.g:"google chrome" will become "google-chrome" )
|
||||||
|
- Snap:
|
||||||
|
- not waiting for the categories file to be retrieved from the cloud during application boot ( reduces boot time )
|
||||||
|
- caching cloud categories to the disk so they can be used in scenarios when it is not possible to retrieve them ( e.g: internet is off )
|
||||||
|
- showing a warning popup when the Snap API is out
|
||||||
|
- Snaps not treated as applications with be categorized as "runtime" at least
|
||||||
|
- minor thread improvements
|
||||||
|
|
||||||
|
### UI
|
||||||
|
- Screenshots panel:
|
||||||
|
- "downloading" label replaced by a progress bar
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- application not initializing when there is no internet connection
|
||||||
|
- not loading application icons after some filters are applied to the table results
|
||||||
|
- not reloading the available categories after asynchronous data is fetched
|
||||||
|
- not keeping the update toggle-button state after a filter is applied
|
||||||
|
- AUR:
|
||||||
|
- update-checking for some scenarios
|
||||||
|
- not respecting **ignorepkg** settings in **pacman.conf**
|
||||||
|
- not able to handle **missing dependencies with symbols** ( e.g: libpng++ )
|
||||||
|
- not able to work with **.xpm** icons
|
||||||
|
- not mapping categories to the search results
|
||||||
|
|
||||||
## [0.7.0] 2019-10-18
|
## [0.7.0] 2019-10-18
|
||||||
### Features
|
### Features
|
||||||
- AppImage support ( see below )
|
- AppImage support ( see below )
|
||||||
@@ -34,7 +77,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
### AppImage support
|
### AppImage support
|
||||||
- Search, install, uninstall, downgrade, launch and retrieve the applications history
|
- Search, install, uninstall, downgrade, launch and retrieve the applications history
|
||||||
- Supported sources: [AppImageHub](https://appimage.github.io) ( **applications with no releases published to GitHub are currently not available** )
|
- Supported sources: [AppImageHub](https://appimage.github.io) ( **applications with no releases published to GitHub are currently not available** )
|
||||||
- Adds desktop entries ( menu shortcuts ) for the installed applications ( **~/.local/share/applications **)
|
- Adds desktop entries ( menu shortcuts ) for the installed applications ( **~/.local/share/applications**)
|
||||||
|
|
||||||
## [0.6.4] 2019-10-13
|
## [0.6.4] 2019-10-13
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ include LICENSE CHANGELOG.md README.md
|
|||||||
include requirements.txt
|
include requirements.txt
|
||||||
recursive-include bauh/view/resources *
|
recursive-include bauh/view/resources *
|
||||||
recursive-include bauh/gems/*/resources *
|
recursive-include bauh/gems/*/resources *
|
||||||
|
recursive-include tests *
|
||||||
|
|||||||
2
aur/bauh-tray
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
/usr/bin/bauh --tray=1
|
||||||
9
aur/bauh.desktop
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=bauh
|
||||||
|
Categories=System;
|
||||||
|
Comment=Manage your Flatpak / Snap / AppImage / AUR applications
|
||||||
|
Comment[pt]=Gerencie seus aplicativos Flatpak / Snap / AppImage / AUR
|
||||||
|
Comment[es]=Administre sus aplicaciones Flatpak / Snap / AppImage / AUR
|
||||||
|
Exec=/usr/bin/bauh
|
||||||
|
Icon=bauh
|
||||||
11
aur/bauh_tray.desktop
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=bauh ( tray )
|
||||||
|
Name[pt]=bauh ( bandeja )
|
||||||
|
Name[es]=bauh ( bandeja )
|
||||||
|
Categories=System;
|
||||||
|
Comment=Manage your Flatpak / Snap / AppImage / AUR applications
|
||||||
|
Comment[pt]=Gerencie seus aplicativos Flatpak / Snap / AppImage / AUR
|
||||||
|
Comment[es]=Administre sus aplicaciones Flatpak / Snap / AppImage / AUR
|
||||||
|
Exec=/usr/bin/bauh --tray=1
|
||||||
|
Icon=bauh
|
||||||
14
aur/copy_icons.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import glob
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
output_base = sys.argv[2]
|
||||||
|
|
||||||
|
for f in glob.glob(sys.argv[1] + '/*.png'):
|
||||||
|
res = f.split('/')[-1].split('.')[0]
|
||||||
|
dest_dir = output_base + '/' + res + '/apps'
|
||||||
|
|
||||||
|
Path(dest_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
shutil.copy(f, dest_dir + '/bauh.png')
|
||||||
BIN
aur/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
aur/icons/12x12.png
Normal file
|
After Width: | Height: | Size: 889 B |
BIN
aur/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 1011 B |
BIN
aur/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
aur/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
aur/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
aur/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
aur/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
aur/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
aur/icons/8x8.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
aur/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
@@ -1,24 +0,0 @@
|
|||||||
# Generates a .desktop file based on the current python version. Used for AUR installation
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
desktop_file = """
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=bauh
|
|
||||||
Categories=System;
|
|
||||||
Comment=Manage your Flatpak / Snap / AppImage / AUR applications
|
|
||||||
Comment[pt]=Gerencie seus aplicativos Flatpak / Snap / AppImage / AUR
|
|
||||||
Comment[es]=Administre sus aplicaciones Flatpak / Snap / AppImage / AUR
|
|
||||||
Exec = {path}
|
|
||||||
Icon = {lib_path}/python{version}/site-packages/bauh/view/resources/img/logo.svg
|
|
||||||
"""
|
|
||||||
|
|
||||||
py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
|
|
||||||
|
|
||||||
app_cmd = os.getenv('BAUH_PATH', '/usr/bin/bauh')
|
|
||||||
|
|
||||||
with open('bauh.desktop', 'w+') as f:
|
|
||||||
f.write(desktop_file.format(lib_path=os.getenv('BAUH_LIB_PATH', '/usr/lib'),
|
|
||||||
version=py_version,
|
|
||||||
path=app_cmd))
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generates a .desktop file based on the current python version. Used for AUR installation
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
desktop_file = """
|
|
||||||
[Desktop Entry]
|
|
||||||
Type = Application
|
|
||||||
Name = bauh ( tray )
|
|
||||||
Name[pt] = bauh ( bandeja )
|
|
||||||
Name[es] = bauh ( bandeja )
|
|
||||||
Categories = System;
|
|
||||||
Comment=Manage your Flatpak / Snap / AppImage / AUR applications
|
|
||||||
Comment[pt]=Gerencie seus aplicativos Flatpak / Snap / AppImage / AUR
|
|
||||||
Comment[es]=Administre sus aplicaciones Flatpak / Snap / AppImage / AUR
|
|
||||||
Exec = {path}
|
|
||||||
Icon = {lib_path}/python{version}/site-packages/bauh/view/resources/img/logo.svg
|
|
||||||
"""
|
|
||||||
|
|
||||||
py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
|
|
||||||
|
|
||||||
app_cmd = os.getenv('BAUH_PATH', '/usr/bin/bauh') + ' --tray=1'
|
|
||||||
|
|
||||||
with open('bauh_tray.desktop', 'w+') as f:
|
|
||||||
f.write(desktop_file.format(lib_path=os.getenv('BAUH_LIB_PATH', '/usr/lib'),
|
|
||||||
version=py_version,
|
|
||||||
path=app_cmd))
|
|
||||||
|
|
||||||
|
|
||||||
with open('bauh-tray', 'w') as f:
|
|
||||||
f.write(app_cmd)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '0.7.0'
|
__version__ = '0.7.1'
|
||||||
__app_name__ = 'bauh'
|
__app_name__ = 'bauh'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -213,8 +213,9 @@ class SoftwareManager(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
:param internet_available
|
||||||
:return: a list of warnings to be shown to the user
|
:return: a list of warnings to be shown to the user
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -59,9 +59,6 @@ class SingleSelectComponent(InputViewComponent):
|
|||||||
|
|
||||||
def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None, max_per_line: int = 1, id_: str = None):
|
def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None, max_per_line: int = 1, id_: str = None):
|
||||||
super(SingleSelectComponent, self).__init__(id_=id_)
|
super(SingleSelectComponent, self).__init__(id_=id_)
|
||||||
if options is None or len(options) < 2:
|
|
||||||
raise Exception("'options' must be a list with at least 2 elements")
|
|
||||||
|
|
||||||
self.type = type_
|
self.type = type_
|
||||||
self.label = label
|
self.label = label
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|||||||
108
bauh/commons/category.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
from pathlib import Path
|
||||||
|
from threading import Thread
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from bauh.api.abstract.controller import SoftwareManager
|
||||||
|
from bauh.api.http import HttpClient
|
||||||
|
|
||||||
|
|
||||||
|
class CategoriesDownloader(Thread):
|
||||||
|
|
||||||
|
def __init__(self, id_: str, http_client: HttpClient, logger: logging.Logger, manager: SoftwareManager,
|
||||||
|
disk_cache: bool, url_categories_file: str, disk_cache_dir: str, categories_path: str):
|
||||||
|
super(CategoriesDownloader, self).__init__(daemon=True)
|
||||||
|
self.id_ = id_
|
||||||
|
self.http_client = http_client
|
||||||
|
self.logger = logger
|
||||||
|
self.manager = manager
|
||||||
|
self.disk_cache = disk_cache
|
||||||
|
self.url_categories_file = url_categories_file
|
||||||
|
self.disk_cache_dir = disk_cache_dir
|
||||||
|
self.categories_path = categories_path
|
||||||
|
|
||||||
|
def _msg(self, msg: str):
|
||||||
|
return '{}({}): {}'.format(self.__class__.__name__, self.id_, msg)
|
||||||
|
|
||||||
|
def _read_categories_from_disk(self) -> Dict[str, List[str]]:
|
||||||
|
if self.disk_cache and os.path.exists(self.categories_path):
|
||||||
|
self.logger.info(self._msg("Reading cached categories from the disk"))
|
||||||
|
|
||||||
|
with open(self.categories_path) as f:
|
||||||
|
categories = f.read()
|
||||||
|
|
||||||
|
return self._map_categories(categories)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _map_categories(self, categories: str) -> Dict[str, List[str]]:
|
||||||
|
categories_map = {}
|
||||||
|
for l in categories.split('\n'):
|
||||||
|
if l:
|
||||||
|
data = l.split('=')
|
||||||
|
categories_map[data[0]] = [c.strip() for c in data[1].split(',') if c]
|
||||||
|
|
||||||
|
return categories_map
|
||||||
|
|
||||||
|
def _cache_categories_to_disk(self, categories: str):
|
||||||
|
self.logger.info(self._msg('Caching categories to the disk'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
Path(self.disk_cache_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(self.categories_path, 'w+') as f:
|
||||||
|
f.write(categories)
|
||||||
|
|
||||||
|
self.logger.info(self._msg("Categories cached to the disk as '{}'".format(self.categories_path)))
|
||||||
|
except:
|
||||||
|
self.logger.error(self._msg("Could not cache categories to the disk as '{}'".format(self.categories_path)))
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def download_categories(self) -> Dict[str, List[str]]:
|
||||||
|
self.logger.info(self._msg('Downloading category definitions from {}'.format(self.url_categories_file)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.http_client.get(self.url_categories_file)
|
||||||
|
|
||||||
|
if res:
|
||||||
|
try:
|
||||||
|
categories = self._map_categories(res.text)
|
||||||
|
self.logger.info(self._msg('Loaded categories for {} applications'.format(len(categories))))
|
||||||
|
|
||||||
|
if self.disk_cache and categories:
|
||||||
|
Thread(target=self._cache_categories_to_disk, args=(res.text,), daemon=True).start()
|
||||||
|
|
||||||
|
return categories
|
||||||
|
except:
|
||||||
|
self.logger.error(self._msg("Could not parse categories definitions"))
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
self.logger.info(self._msg('Could not download {}'.format(self.url_categories_file)))
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
self.logger.warning(self._msg('The internet connection seems to be off.'))
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _set_categories(self, categories: dict):
|
||||||
|
if categories:
|
||||||
|
self.logger.info(self._msg("Settings {} categories to {}".format(len(categories), self.manager.__class__.__name__)))
|
||||||
|
self.manager.categories = categories
|
||||||
|
|
||||||
|
def _download_and_set(self):
|
||||||
|
self._set_categories(self.download_categories())
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
cached = self._read_categories_from_disk()
|
||||||
|
|
||||||
|
if cached:
|
||||||
|
self._set_categories(cached)
|
||||||
|
Thread(target=self._download_and_set, daemon=True).start()
|
||||||
|
else:
|
||||||
|
self._download_and_set()
|
||||||
|
|
||||||
|
self.logger.info(self._msg('Finished'))
|
||||||
@@ -100,7 +100,7 @@ class ProcessHandler:
|
|||||||
if self.watcher:
|
if self.watcher:
|
||||||
self.watcher.print(msg)
|
self.watcher.print(msg)
|
||||||
|
|
||||||
def handle(self, process: SystemProcess) -> bool:
|
def handle(self, process: SystemProcess, error_output: StringIO = None) -> bool:
|
||||||
self._notify_watcher(' '.join(process.subproc.args) + '\n')
|
self._notify_watcher(' '.join(process.subproc.args) + '\n')
|
||||||
|
|
||||||
already_succeeded = False
|
already_succeeded = False
|
||||||
@@ -126,6 +126,9 @@ class ProcessHandler:
|
|||||||
if line:
|
if line:
|
||||||
self._notify_watcher(line)
|
self._notify_watcher(line)
|
||||||
|
|
||||||
|
if error_output is not None:
|
||||||
|
error_output.write(line)
|
||||||
|
|
||||||
if process.check_error_output:
|
if process.check_error_output:
|
||||||
if process.wrong_error_phrase and process.wrong_error_phrase in line:
|
if process.wrong_error_phrase and process.wrong_error_phrase in line:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpda
|
|||||||
from bauh.api.abstract.view import MessageType
|
from bauh.api.abstract.view import MessageType
|
||||||
from bauh.api.constants import HOME_PATH
|
from bauh.api.constants import HOME_PATH
|
||||||
from bauh.commons.html import bold
|
from bauh.commons.html import bold
|
||||||
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd
|
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
|
||||||
from bauh.gems.appimage import query, INSTALLATION_PATH, suggestions, db
|
from bauh.gems.appimage import query, INSTALLATION_PATH, suggestions, db
|
||||||
from bauh.gems.appimage.model import AppImage
|
from bauh.gems.appimage.model import AppImage
|
||||||
from bauh.gems.appimage.worker import DatabaseUpdater
|
from bauh.gems.appimage.worker import DatabaseUpdater
|
||||||
@@ -298,12 +298,20 @@ class AppImageManager(SoftwareManager):
|
|||||||
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:
|
try:
|
||||||
handler.handle(SystemProcess(new_subprocess([file_path, '--appimage-extract'], cwd=out_dir)))
|
res, output = handler.handle_simple(SimpleProcess([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 False
|
||||||
except:
|
except:
|
||||||
watcher.show_message(title=self.i18n['error'],
|
watcher.show_message(title=self.i18n['error'],
|
||||||
body=traceback.format_exc(),
|
body=traceback.format_exc(),
|
||||||
type_=MessageType.ERROR)
|
type_=MessageType.ERROR)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
watcher.change_substatus(self.i18n['appimage.install.desktop_entry'])
|
watcher.change_substatus(self.i18n['appimage.install.desktop_entry'])
|
||||||
@@ -388,7 +396,7 @@ class AppImageManager(SoftwareManager):
|
|||||||
|
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
|
def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ appimage.downgrade.first_version={} is in its first published version
|
|||||||
appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {}
|
appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {}
|
||||||
appimage.downgrade.install_version=It was not possible to install the version {} ({})
|
appimage.downgrade.install_version=It was not possible to install the version {} ({})
|
||||||
appimage.install.download.error=It was not possible to download the file {}. The file server can be down.
|
appimage.install.download.error=It was not possible to download the file {}. The file server can be down.
|
||||||
|
appimage.install.appimagelauncher.error={appimgl} is not allowing {app} to be installed. Uninstall {appimgl}, reboot your system and try to install {app} again.
|
||||||
@@ -16,3 +16,4 @@ appimage.downgrade.first_version={} está en su primera versión publicada
|
|||||||
appimage.downgrade.uninstall_current_version=No fue posible desinstalar la versión actual de {}
|
appimage.downgrade.uninstall_current_version=No fue posible desinstalar la versión actual de {}
|
||||||
appimage.downgrade.install_version=No fue posible instalar la versión {} ({})
|
appimage.downgrade.install_version=No fue posible instalar la versión {} ({})
|
||||||
appimage.install.download.error=No fue posible descargar el archivo {}. El servidor del archivo puede estar inactivo.
|
appimage.install.download.error=No fue posible descargar el archivo {}. El servidor del archivo puede estar inactivo.
|
||||||
|
appimage.install.appimagelauncher.error = {appimgl} no permite la instalación de {app}. Desinstale {appimgl}, reinicie su sistema e intente instalar {app} nuevamente.
|
||||||
@@ -16,3 +16,4 @@ appimage.downgrade.first_version={} se encontra em sua primeira versão publicad
|
|||||||
appimage.downgrade.uninstall_current_version=Não foi possível desinstalar a versão atual de {}
|
appimage.downgrade.uninstall_current_version=Não foi possível desinstalar a versão atual de {}
|
||||||
appimage.downgrade.install_version=Não foi possivel instalar a versão {} ({})
|
appimage.downgrade.install_version=Não foi possivel instalar a versão {} ({})
|
||||||
appimage.install.download.error=Não foi possível baixar o arquivo {}. O servidor do arquivo pode estar fora do ar.
|
appimage.install.download.error=Não foi possível baixar o arquivo {}. O servidor do arquivo pode estar fora do ar.
|
||||||
|
appimage.install.appimagelauncher.error={appimgl} não está permitindo que o {app} seja instalado. Desinstale o {appimgl}, reinicie o sistema e tente instalar o {app} novamente.
|
||||||
@@ -8,6 +8,8 @@ from multiprocessing import Process
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from bauh.api.http import HttpClient
|
from bauh.api.http import HttpClient
|
||||||
from bauh.gems.appimage import LOCAL_PATH, db
|
from bauh.gems.appimage import LOCAL_PATH, db
|
||||||
|
|
||||||
@@ -68,7 +70,11 @@ class DatabaseUpdater(Thread if bool(int(os.getenv('BAUH_DEBUG', 0))) else Proce
|
|||||||
def run(self):
|
def run(self):
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
while True:
|
while True:
|
||||||
self._download_databases()
|
try:
|
||||||
|
self._download_databases()
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
self.logger.warning('The internet connection seems to be off.')
|
||||||
|
|
||||||
self.logger.info('Sleeping')
|
self.logger.info('Sleeping')
|
||||||
time.sleep(self.sleep)
|
time.sleep(self.sleep)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
from bauh.api.constants import CACHE_PATH
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
BUILD_DIR = '/tmp/bauh/aur'
|
BUILD_DIR = '/tmp/bauh/aur'
|
||||||
|
ARCH_CACHE_PATH = CACHE_PATH + '/arch'
|
||||||
|
CATEGORIES_CACHE_DIR = ARCH_CACHE_PATH + '/categories'
|
||||||
|
CATEGORIES_FILE_PATH = CATEGORIES_CACHE_DIR + '/aur.txt'
|
||||||
|
URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/aur/categories.txt'
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Set, List
|
from typing import Set, List
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from bauh.api.http import HttpClient
|
from bauh.api.http import HttpClient
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
URL_INFO = 'https://aur.archlinux.org/rpc/?v=5&type=info&'
|
URL_INFO = 'https://aur.archlinux.org/rpc/?v=5&type=info&'
|
||||||
URL_SRC_INFO = 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h='
|
URL_SRC_INFO = 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h='
|
||||||
@@ -32,7 +31,7 @@ class AURClient:
|
|||||||
return res['results'] if res and res.get('results') else []
|
return res['results'] if res and res.get('results') else []
|
||||||
|
|
||||||
def get_src_info(self, name: str) -> dict:
|
def get_src_info(self, name: str) -> dict:
|
||||||
res = self.http_client.get(URL_SRC_INFO + name)
|
res = self.http_client.get(URL_SRC_INFO + urllib.parse.quote(name))
|
||||||
|
|
||||||
if res and res.text:
|
if res and res.text:
|
||||||
info = {}
|
info = {}
|
||||||
@@ -48,4 +47,4 @@ class AURClient:
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
def _map_names_as_queries(self, names) -> str:
|
def _map_names_as_queries(self, names) -> str:
|
||||||
return '&'.join(['arg[{}]={}'.format(i, n) for i, n in enumerate(names)])
|
return '&'.join(['arg[{}]={}'.format(i, urllib.parse.quote(n)) for i, n in enumerate(names)])
|
||||||
|
|||||||
@@ -15,14 +15,16 @@ from bauh.api.abstract.disk import DiskCacheLoader
|
|||||||
from bauh.api.abstract.handler import ProcessWatcher
|
from bauh.api.abstract.handler import ProcessWatcher
|
||||||
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus
|
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus
|
||||||
from bauh.api.abstract.view import MessageType
|
from bauh.api.abstract.view import MessageType
|
||||||
|
from bauh.commons.category import CategoriesDownloader
|
||||||
from bauh.commons.html import bold
|
from bauh.commons.html import bold
|
||||||
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \
|
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \
|
||||||
SimpleProcess
|
SimpleProcess
|
||||||
from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, suggestions, gpg
|
from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, suggestions, \
|
||||||
|
gpg, URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH
|
||||||
from bauh.gems.arch.aur import AURClient
|
from bauh.gems.arch.aur import AURClient
|
||||||
from bauh.gems.arch.mapper import ArchDataMapper
|
from bauh.gems.arch.mapper import ArchDataMapper
|
||||||
from bauh.gems.arch.model import ArchPackage
|
from bauh.gems.arch.model import ArchPackage
|
||||||
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, CategoriesDownloader
|
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer
|
||||||
|
|
||||||
URL_GIT = 'https://aur.archlinux.org/{}.git'
|
URL_GIT = 'https://aur.archlinux.org/{}.git'
|
||||||
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/cgit/aur.git/snapshot/{}.tar.gz'
|
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/cgit/aur.git/snapshot/{}.tar.gz'
|
||||||
@@ -33,6 +35,12 @@ RE_SPLIT_VERSION = re.compile(r'(=|>|<)')
|
|||||||
SOURCE_FIELDS = ('source', 'source_x86_64')
|
SOURCE_FIELDS = ('source', 'source_x86_64')
|
||||||
RE_PRE_DOWNLOADABLE_FILES = re.compile(r'(https?|ftp)://.+\.\w+[^gpg|git]$')
|
RE_PRE_DOWNLOADABLE_FILES = re.compile(r'(https?|ftp)://.+\.\w+[^gpg|git]$')
|
||||||
|
|
||||||
|
SEARCH_OPTIMIZED_MAP = {
|
||||||
|
'google chrome': 'google-chrome',
|
||||||
|
'chrome google': 'google-chrome',
|
||||||
|
'googlechrome': 'google-chrome'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ArchManager(SoftwareManager):
|
class ArchManager(SoftwareManager):
|
||||||
|
|
||||||
@@ -51,11 +59,12 @@ class ArchManager(SoftwareManager):
|
|||||||
self.logger = context.logger
|
self.logger = context.logger
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.arch_distro = context.distro == 'arch'
|
self.arch_distro = context.distro == 'arch'
|
||||||
self.categories_downloader = CategoriesDownloader(context.http_client, context.logger)
|
self.categories_mapper = CategoriesDownloader('AUR', context.http_client, context.logger, self, self.context.disk_cache,
|
||||||
self.categories_map = {}
|
URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH)
|
||||||
|
self.categories = {}
|
||||||
|
|
||||||
def _upgrade_search_result(self, apidata: dict, installed_pkgs: dict, downgrade_enabled: bool, res: SearchResult, disk_loader: DiskCacheLoader):
|
def _upgrade_search_result(self, apidata: dict, installed_pkgs: dict, downgrade_enabled: bool, res: SearchResult, disk_loader: DiskCacheLoader):
|
||||||
app = self.mapper.map_api_data(apidata, installed_pkgs['not_signed'])
|
app = self.mapper.map_api_data(apidata, installed_pkgs['not_signed'], self.categories)
|
||||||
app.downgrade_enabled = downgrade_enabled
|
app.downgrade_enabled = downgrade_enabled
|
||||||
|
|
||||||
if app.installed:
|
if app.installed:
|
||||||
@@ -66,7 +75,7 @@ class ArchManager(SoftwareManager):
|
|||||||
else:
|
else:
|
||||||
res.new.append(app)
|
res.new.append(app)
|
||||||
|
|
||||||
Thread(target=self.mapper.fill_package_build, args=(app,)).start()
|
Thread(target=self.mapper.fill_package_build, args=(app,), daemon=True).start()
|
||||||
|
|
||||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult:
|
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult:
|
||||||
self.comp_optimizer.join()
|
self.comp_optimizer.join()
|
||||||
@@ -75,10 +84,12 @@ class ArchManager(SoftwareManager):
|
|||||||
res = SearchResult([], [], 0)
|
res = SearchResult([], [], 0)
|
||||||
|
|
||||||
installed = {}
|
installed = {}
|
||||||
read_installed = Thread(target=lambda: installed.update(pacman.list_and_map_installed()))
|
read_installed = Thread(target=lambda: installed.update(pacman.list_and_map_installed()), daemon=True)
|
||||||
read_installed.start()
|
read_installed.start()
|
||||||
|
|
||||||
api_res = self.aur_client.search(words)
|
mapped_words = SEARCH_OPTIMIZED_MAP.get(words)
|
||||||
|
|
||||||
|
api_res = self.aur_client.search(mapped_words if mapped_words else words)
|
||||||
|
|
||||||
if api_res and api_res.get('results'):
|
if api_res and api_res.get('results'):
|
||||||
read_installed.join()
|
read_installed.join()
|
||||||
@@ -117,9 +128,8 @@ class ArchManager(SoftwareManager):
|
|||||||
|
|
||||||
if pkgsinfo:
|
if pkgsinfo:
|
||||||
for pkgdata in pkgsinfo:
|
for pkgdata in pkgsinfo:
|
||||||
pkg = self.mapper.map_api_data(pkgdata, not_signed)
|
pkg = self.mapper.map_api_data(pkgdata, not_signed, self.categories)
|
||||||
pkg.downgrade_enabled = downgrade_enabled
|
pkg.downgrade_enabled = downgrade_enabled
|
||||||
pkg.categories = self.categories_map.get(pkg.name)
|
|
||||||
|
|
||||||
if disk_loader:
|
if disk_loader:
|
||||||
disk_loader.fill(pkg)
|
disk_loader.fill(pkg)
|
||||||
@@ -137,7 +147,7 @@ class ArchManager(SoftwareManager):
|
|||||||
latest_version=data.get('version'), description=data.get('description'),
|
latest_version=data.get('version'), description=data.get('description'),
|
||||||
installed=True, mirror='aur')
|
installed=True, mirror='aur')
|
||||||
|
|
||||||
pkg.categories = self.categories_map.get(pkg.name)
|
pkg.categories = self.categories.get(pkg.name)
|
||||||
pkg.downgrade_enabled = downgrade_enabled
|
pkg.downgrade_enabled = downgrade_enabled
|
||||||
|
|
||||||
if disk_loader:
|
if disk_loader:
|
||||||
@@ -161,6 +171,7 @@ class ArchManager(SoftwareManager):
|
|||||||
apps = []
|
apps = []
|
||||||
if installed and installed['not_signed']:
|
if installed and installed['not_signed']:
|
||||||
self.dcache_updater.join()
|
self.dcache_updater.join()
|
||||||
|
self.categories_mapper.join()
|
||||||
|
|
||||||
self._fill_aur_pkgs(installed['not_signed'], apps, disk_loader, internet_available)
|
self._fill_aur_pkgs(installed['not_signed'], apps, disk_loader, internet_available)
|
||||||
|
|
||||||
@@ -490,6 +501,12 @@ class ArchManager(SoftwareManager):
|
|||||||
handler.watcher.print(self.i18n['action.cancelled'])
|
handler.watcher.print(self.i18n['action.cancelled'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if check_res.get('validity_check'):
|
||||||
|
handler.watcher.show_message(title=self.i18n['arch.aur.install.validity_check.title'],
|
||||||
|
body=self.i18n['arch.aur.install.validity_check.body'].format(bold(pkgname)),
|
||||||
|
type_=MessageType.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _install_optdeps(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str, change_progress: bool = True) -> bool:
|
def _install_optdeps(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str, change_progress: bool = True) -> bool:
|
||||||
@@ -559,7 +576,7 @@ class ArchManager(SoftwareManager):
|
|||||||
if installed and self.context.disk_cache:
|
if installed and self.context.disk_cache:
|
||||||
handler.watcher.change_substatus(self.i18n['status.caching_data'].format(bold(pkgname)))
|
handler.watcher.change_substatus(self.i18n['status.caching_data'].format(bold(pkgname)))
|
||||||
if self.context.disk_cache:
|
if self.context.disk_cache:
|
||||||
disk.save_several({pkgname}, mirror=mirror, maintainer=maintainer, overwrite=True)
|
disk.save_several({pkgname}, mirror=mirror, maintainer=maintainer, overwrite=True, categories=self.categories)
|
||||||
|
|
||||||
self._update_progress(handler.watcher, 100, change_progress)
|
self._update_progress(handler.watcher, 100, change_progress)
|
||||||
|
|
||||||
@@ -678,7 +695,7 @@ class ArchManager(SoftwareManager):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool):
|
def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def requires_root(self, action: str, pkg: ArchPackage):
|
def requires_root(self, action: str, pkg: ArchPackage):
|
||||||
return action != 'search'
|
return action != 'search'
|
||||||
@@ -687,16 +704,13 @@ class ArchManager(SoftwareManager):
|
|||||||
self.dcache_updater.start()
|
self.dcache_updater.start()
|
||||||
self.comp_optimizer.start()
|
self.comp_optimizer.start()
|
||||||
self.aur_index_updater.start()
|
self.aur_index_updater.start()
|
||||||
categories = self.categories_downloader.get_categories()
|
self.categories_mapper.start()
|
||||||
|
|
||||||
if categories:
|
|
||||||
self.categories_map = categories
|
|
||||||
|
|
||||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||||
installed = self.read_installed(disk_loader=None, internet_available=internet_available).installed
|
installed = self.read_installed(disk_loader=None, internet_available=internet_available).installed
|
||||||
return [PackageUpdate(p.id, p.latest_version, 'aur') for p in installed if p.update]
|
return [PackageUpdate(p.id, p.latest_version, 'aur') for p in installed if p.update]
|
||||||
|
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
if self.arch_distro:
|
if self.arch_distro:
|
||||||
@@ -725,9 +739,10 @@ class ArchManager(SoftwareManager):
|
|||||||
api_res = self.aur_client.get_info(sug_names)
|
api_res = self.aur_client.get_info(sug_names)
|
||||||
|
|
||||||
if api_res:
|
if api_res:
|
||||||
|
self.categories_mapper.join()
|
||||||
for pkg in api_res:
|
for pkg in api_res:
|
||||||
if pkg.get('Name') in sug_names:
|
if pkg.get('Name') in sug_names:
|
||||||
res.append(PackageSuggestion(self.mapper.map_api_data(pkg, {}), suggestions.ALL.get(pkg['Name'])))
|
res.append(PackageSuggestion(self.mapper.map_api_data(pkg, {}, self.categories), suggestions.ALL.get(pkg['Name'])))
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def write(app: ArchPackage):
|
|||||||
|
|
||||||
|
|
||||||
def fill_icon_path(app: ArchPackage, icon_paths: List[str], only_exact_match: bool):
|
def fill_icon_path(app: ArchPackage, icon_paths: List[str], only_exact_match: bool):
|
||||||
ends_with = re.compile(r'.+/{}\.(png|svg)$'.format(app.icon_path if app.icon_path else app.name), re.IGNORECASE)
|
ends_with = re.compile(r'.+/{}\.(png|svg|xpm)$'.format(app.icon_path if app.icon_path else app.name), re.IGNORECASE)
|
||||||
|
|
||||||
for path in icon_paths:
|
for path in icon_paths:
|
||||||
if ends_with.match(path):
|
if ends_with.match(path):
|
||||||
@@ -47,7 +47,7 @@ def set_icon_path(app: ArchPackage, icon_name: str = None):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def save_several(pkgnames: Set[str], mirror: str, overwrite: bool = True, maintainer: str = None) -> int:
|
def save_several(pkgnames: Set[str], mirror: str, overwrite: bool = True, maintainer: str = None, categories: dict = None) -> int:
|
||||||
to_cache = {n for n in pkgnames if overwrite or not os.path.exists(ArchPackage.disk_cache_path(n, mirror))}
|
to_cache = {n for n in pkgnames if overwrite or not os.path.exists(ArchPackage.disk_cache_path(n, mirror))}
|
||||||
desktop_files = pacman.list_desktop_entries(to_cache)
|
desktop_files = pacman.list_desktop_entries(to_cache)
|
||||||
|
|
||||||
@@ -138,8 +138,10 @@ def save_several(pkgnames: Set[str], mirror: str, overwrite: bool = True, mainta
|
|||||||
|
|
||||||
if to_write:
|
if to_write:
|
||||||
for p in to_write:
|
for p in to_write:
|
||||||
|
if categories:
|
||||||
|
p.categories = categories.get(p.name)
|
||||||
|
|
||||||
p.maintainer = maintainer
|
p.maintainer = maintainer
|
||||||
write(p)
|
write(p)
|
||||||
return len(to_write)
|
return len(to_write)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ def check(pkgdir: str, handler: ProcessHandler) -> dict:
|
|||||||
if gpg_keys:
|
if gpg_keys:
|
||||||
res['gpg_key'] = gpg_keys[0]
|
res['gpg_key'] = gpg_keys[0]
|
||||||
|
|
||||||
|
if 'One or more files did not pass the validity check' in output:
|
||||||
|
res['validity_check'] = True
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ from bauh.gems.arch.model import ArchPackage
|
|||||||
|
|
||||||
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}'
|
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}'
|
||||||
RE_LETTERS = re.compile(r'\.([a-zA-Z]+)-\d+$')
|
RE_LETTERS = re.compile(r'\.([a-zA-Z]+)-\d+$')
|
||||||
RE_ANY_LETTER = re.compile(r'[a-zA-Z]')
|
RE_VERSION_SPLIT = re.compile(r'[a-zA-Z]+|\d+|[\.\-_@#]+')
|
||||||
RE_VERSION_DELS = re.compile(r'[.:#@\-_]')
|
|
||||||
|
|
||||||
|
BAUH_PACKAGES = {'bauh', 'bauh-staging'}
|
||||||
RE_SFX = ('r', 're', 'release')
|
RE_SFX = ('r', 're', 'release')
|
||||||
GA_SFX = ('ga', 'ge')
|
GA_SFX = ('ga', 'ge')
|
||||||
RC_SFX = ('rc',)
|
RC_SFX = ('rc',)
|
||||||
@@ -49,46 +49,47 @@ class ArchDataMapper:
|
|||||||
pkg.url_download = URL_PKG_DOWNLOAD.format(package['URLPath']) if package.get('URLPath') else None
|
pkg.url_download = URL_PKG_DOWNLOAD.format(package['URLPath']) if package.get('URLPath') else None
|
||||||
pkg.first_submitted = datetime.fromtimestamp(package['FirstSubmitted']) if package.get('FirstSubmitted') else None
|
pkg.first_submitted = datetime.fromtimestamp(package['FirstSubmitted']) if package.get('FirstSubmitted') else None
|
||||||
pkg.last_modified = datetime.fromtimestamp(package['LastModified']) if package.get('LastModified') else None
|
pkg.last_modified = datetime.fromtimestamp(package['LastModified']) if package.get('LastModified') else None
|
||||||
pkg.update = self.check_update(pkg.version, pkg.latest_version)
|
pkg.update = self.check_update(pkg.version, pkg.latest_version, check_suffix=pkg.name in BAUH_PACKAGES)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_update(version: str, latest_version: str) -> bool:
|
def check_update(version: str, latest_version: str, check_suffix: bool = False) -> bool:
|
||||||
if version and latest_version:
|
if version and latest_version:
|
||||||
current_sfx = RE_LETTERS.findall(version)
|
|
||||||
latest_sf = RE_LETTERS.findall(latest_version)
|
|
||||||
|
|
||||||
if latest_sf and current_sfx:
|
if check_suffix:
|
||||||
current_sfx = current_sfx[0]
|
current_sfx = RE_LETTERS.findall(version)
|
||||||
latest_sf = latest_sf[0]
|
latest_sf = RE_LETTERS.findall(latest_version)
|
||||||
|
|
||||||
current_sfx_data = V_SUFFIX_MAP.get(current_sfx.lower())
|
if latest_sf and current_sfx:
|
||||||
latest_sfx_data = V_SUFFIX_MAP.get(latest_sf.lower())
|
current_sfx = current_sfx[0]
|
||||||
|
latest_sf = latest_sf[0]
|
||||||
|
|
||||||
if current_sfx_data and latest_sfx_data:
|
current_sfx_data = V_SUFFIX_MAP.get(current_sfx.lower())
|
||||||
nversion = version.split(current_sfx)[0]
|
latest_sfx_data = V_SUFFIX_MAP.get(latest_sf.lower())
|
||||||
nlatest = latest_version.split(latest_sf)[0]
|
|
||||||
|
|
||||||
if nversion == nlatest:
|
if current_sfx_data and latest_sfx_data:
|
||||||
if current_sfx_data['c'] != latest_sfx_data['c']:
|
nversion = version.split(current_sfx)[0]
|
||||||
return latest_sfx_data['p'] < current_sfx_data['p']
|
nlatest = latest_version.split(latest_sf)[0]
|
||||||
else:
|
|
||||||
return ''.join(latest_version.split(latest_sf)) > ''.join(version.split(current_sfx))
|
|
||||||
|
|
||||||
return nlatest > nversion
|
if nversion == nlatest:
|
||||||
|
if current_sfx_data['c'] != latest_sfx_data['c']:
|
||||||
|
return latest_sfx_data['p'] < current_sfx_data['p']
|
||||||
|
else:
|
||||||
|
return ''.join(latest_version.split(latest_sf)) > ''.join(version.split(current_sfx))
|
||||||
|
|
||||||
latest_split = RE_VERSION_DELS.split(latest_version)
|
return nlatest > nversion
|
||||||
version_split = RE_VERSION_DELS.split(version)
|
|
||||||
|
latest_split = RE_VERSION_SPLIT.findall(latest_version)
|
||||||
|
current_split = RE_VERSION_SPLIT.findall(version)
|
||||||
|
|
||||||
for idx in range(len(latest_split)):
|
for idx in range(len(latest_split)):
|
||||||
if idx < len(version_split):
|
if idx < len(current_split):
|
||||||
latest_part = latest_split[idx]
|
latest_part = latest_split[idx]
|
||||||
version_part = version_split[idx]
|
current_part = current_split[idx]
|
||||||
|
|
||||||
if latest_part != version_part:
|
if latest_part != current_part:
|
||||||
if RE_ANY_LETTER.findall(latest_part) or RE_ANY_LETTER.findall(version_part):
|
|
||||||
return latest_part > version_part
|
try:
|
||||||
else:
|
dif = int(latest_part) - int(current_part)
|
||||||
dif = int(latest_part) - int(version_part)
|
|
||||||
|
|
||||||
if dif > 0:
|
if dif > 0:
|
||||||
return True
|
return True
|
||||||
@@ -96,6 +97,14 @@ class ArchDataMapper:
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
if latest_part.isdigit():
|
||||||
|
return True
|
||||||
|
elif current_part.isdigit():
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return latest_part > current_part
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def fill_package_build(self, pkg: ArchPackage):
|
def fill_package_build(self, pkg: ArchPackage):
|
||||||
@@ -104,14 +113,18 @@ class ArchDataMapper:
|
|||||||
if res and res.status_code == 200 and res.text:
|
if res and res.status_code == 200 and res.text:
|
||||||
pkg.pkgbuild = res.text
|
pkg.pkgbuild = res.text
|
||||||
|
|
||||||
def map_api_data(self, apidata: dict, installed: dict) -> ArchPackage:
|
def map_api_data(self, apidata: dict, installed: dict, categories: dict) -> ArchPackage:
|
||||||
data = installed.get(apidata.get('Name'))
|
data = installed.get(apidata.get('Name'))
|
||||||
app = ArchPackage(name=apidata.get('Name'), installed=bool(data), mirror='aur')
|
app = ArchPackage(name=apidata.get('Name'), installed=bool(data), mirror='aur')
|
||||||
app.status = PackageStatus.LOADING_DATA
|
app.status = PackageStatus.LOADING_DATA
|
||||||
|
|
||||||
|
if categories:
|
||||||
|
app.categories = categories.get(app.name)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
app.version = data.get('version')
|
app.version = data.get('version')
|
||||||
app.description = data.get('description')
|
app.description = data.get('description')
|
||||||
|
|
||||||
self.fill_api_data(app, apidata, fill_version=not data)
|
self.fill_api_data(app, apidata, fill_version=not data)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import datetime
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from bauh.api.abstract.model import SoftwarePackage
|
from bauh.api.abstract.model import SoftwarePackage
|
||||||
from bauh.api.constants import CACHE_PATH
|
|
||||||
from bauh.commons import resource
|
from bauh.commons import resource
|
||||||
|
from bauh.gems.arch import ROOT_DIR, ARCH_CACHE_PATH
|
||||||
|
|
||||||
from bauh.gems.arch import ROOT_DIR
|
CACHED_ATTRS = {'command', 'icon_path', 'mirror', 'maintainer', 'desktop_entry', 'categories'}
|
||||||
|
|
||||||
CACHED_ATTRS = {'command', 'icon_path', 'mirror', 'maintainer', 'desktop_entry'}
|
|
||||||
|
|
||||||
|
|
||||||
class ArchPackage(SoftwarePackage):
|
class ArchPackage(SoftwarePackage):
|
||||||
@@ -35,7 +33,7 @@ class ArchPackage(SoftwarePackage):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def disk_cache_path(pkgname: str, mirror: str):
|
def disk_cache_path(pkgname: str, mirror: str):
|
||||||
return CACHE_PATH + '/arch/installed/' + ('aur' if mirror == 'aur' else 'mirror') + '/' + pkgname
|
return ARCH_CACHE_PATH + '/installed/' + ('aur' if mirror == 'aur' else 'mirror') + '/' + pkgname
|
||||||
|
|
||||||
def get_pkg_build_url(self):
|
def get_pkg_build_url(self):
|
||||||
if self.package_base:
|
if self.package_base:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
from threading import Thread
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess
|
from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess
|
||||||
@@ -13,7 +14,7 @@ def is_enabled() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def get_mirrors(pkgs: Set[str]) -> dict:
|
def get_mirrors(pkgs: Set[str]) -> dict:
|
||||||
pkgre = '|'.join(pkgs)
|
pkgre = '|'.join(pkgs).replace('+', r'\+').replace('.', r'\.')
|
||||||
|
|
||||||
searchres = new_subprocess(['pacman', '-Ss', pkgre]).stdout
|
searchres = new_subprocess(['pacman', '-Ss', pkgre]).stdout
|
||||||
mirrors = {}
|
mirrors = {}
|
||||||
@@ -71,10 +72,18 @@ def check_installed(pkg: str) -> bool:
|
|||||||
return bool(res)
|
return bool(res)
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_ignored(res: dict):
|
||||||
|
res['pkgs'] = list_ignored_packages()
|
||||||
|
|
||||||
|
|
||||||
def list_and_map_installed() -> dict: # returns a dict with with package names as keys and versions as values
|
def list_and_map_installed() -> dict: # returns a dict with with package names as keys and versions as values
|
||||||
installed = new_subprocess(['pacman', '-Qq']).stdout # retrieving all installed package names
|
installed = new_subprocess(['pacman', '-Qq']).stdout # retrieving all installed package names
|
||||||
allinfo = new_subprocess(['pacman', '-Qi'], stdin=installed).stdout # retrieving all installed packages info
|
allinfo = new_subprocess(['pacman', '-Qi'], stdin=installed).stdout # retrieving all installed packages info
|
||||||
|
|
||||||
|
ignored = {}
|
||||||
|
thread_ignored = Thread(target=_fill_ignored, args=(ignored,))
|
||||||
|
thread_ignored.start()
|
||||||
|
|
||||||
pkgs, current_pkg = {'mirrors': {}, 'not_signed': {}}, {}
|
pkgs, current_pkg = {'mirrors': {}, 'not_signed': {}}, {}
|
||||||
for out in new_subprocess(['grep', '-E', '(Name|Description|Version|Validated By)'],
|
for out in new_subprocess(['grep', '-E', '(Name|Description|Version|Validated By)'],
|
||||||
stdin=allinfo).stdout: # filtering only the Name and Validated By fields:
|
stdin=allinfo).stdout: # filtering only the Name and Validated By fields:
|
||||||
@@ -96,6 +105,18 @@ def list_and_map_installed() -> dict: # returns a dict with with package names
|
|||||||
|
|
||||||
current_pkg = {}
|
current_pkg = {}
|
||||||
|
|
||||||
|
if pkgs and pkgs.get('not_signed'):
|
||||||
|
thread_ignored.join()
|
||||||
|
|
||||||
|
if ignored['pkgs']:
|
||||||
|
to_del = set()
|
||||||
|
for pkg in pkgs['not_signed'].keys():
|
||||||
|
if pkg in ignored['pkgs']:
|
||||||
|
to_del.add(pkg)
|
||||||
|
|
||||||
|
for pkg in to_del:
|
||||||
|
del pkgs['not_signed'][pkg]
|
||||||
|
|
||||||
return pkgs
|
return pkgs
|
||||||
|
|
||||||
|
|
||||||
@@ -124,7 +145,7 @@ def list_icon_paths(pkgnames: Set[str]) -> List[str]:
|
|||||||
installed_files = new_subprocess(['pacman', '-Qlq', *pkgnames])
|
installed_files = new_subprocess(['pacman', '-Qlq', *pkgnames])
|
||||||
|
|
||||||
icon_files = []
|
icon_files = []
|
||||||
for out in new_subprocess(['grep', '-E', '.(png|svg)$'], stdin=installed_files.stdout).stdout:
|
for out in new_subprocess(['grep', '-E', '.(png|svg|xpm)$'], stdin=installed_files.stdout).stdout:
|
||||||
if out:
|
if out:
|
||||||
line = out.decode().strip()
|
line = out.decode().strip()
|
||||||
if line:
|
if line:
|
||||||
@@ -179,3 +200,19 @@ def receive_key(key: str, root_password: str) -> SystemProcess:
|
|||||||
def sign_key(key: str, root_password: str) -> SystemProcess:
|
def sign_key(key: str, root_password: str) -> SystemProcess:
|
||||||
return SystemProcess(new_root_subprocess(['pacman-key', '--lsign-key', key], root_password=root_password), check_error_output=False)
|
return SystemProcess(new_root_subprocess(['pacman-key', '--lsign-key', key], root_password=root_password), check_error_output=False)
|
||||||
|
|
||||||
|
|
||||||
|
def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]:
|
||||||
|
pacman_conf = new_subprocess(['cat', config_path])
|
||||||
|
|
||||||
|
ignored = set()
|
||||||
|
grep = new_subprocess(['grep', '-Eo', r'\s*#*\s*ignorepkg\s*=\s*.+'], stdin=pacman_conf.stdout)
|
||||||
|
for o in grep.stdout:
|
||||||
|
if o:
|
||||||
|
line = o.decode().strip()
|
||||||
|
|
||||||
|
if not line.startswith('#'):
|
||||||
|
ignored.add(line.split('=')[1].strip())
|
||||||
|
|
||||||
|
pacman_conf.terminate()
|
||||||
|
grep.terminate()
|
||||||
|
return ignored
|
||||||
|
|||||||
@@ -61,3 +61,5 @@ arch.aur.install.unknown_key.title=Public key required
|
|||||||
arch.install.aur.unknown_key.body=To continue {} installation is necessary to trust the following public key {}
|
arch.install.aur.unknown_key.body=To continue {} installation is necessary to trust the following public key {}
|
||||||
arch.aur.install.unknown_key.status=Receiving public key {}
|
arch.aur.install.unknown_key.status=Receiving public key {}
|
||||||
arch.aur.install.unknown_key.receive_error=Could not receive public key {}
|
arch.aur.install.unknown_key.receive_error=Could not receive public key {}
|
||||||
|
arch.aur.install.validity_check.title=Integrity issues
|
||||||
|
arch.aur.install.validity_check.body=Some of the source-files needed for {} installation are not healthy. The installation will be cancelled to prevent damage to your system.
|
||||||
@@ -93,3 +93,5 @@ arch.aur.install.unknown_key.title=Clave pública necesaria
|
|||||||
arch.aur.install.unknown_key.status=Recibiendo la clave pública {}
|
arch.aur.install.unknown_key.status=Recibiendo la clave pública {}
|
||||||
arch.aur.install.unknown_key.receive_error=No fue posible recibir la clave pública {}
|
arch.aur.install.unknown_key.receive_error=No fue posible recibir la clave pública {}
|
||||||
arch.install.aur.unknown_key.body=Para continuar la instalación de {} es necesario confiar en la siguiente clave pública {}
|
arch.install.aur.unknown_key.body=Para continuar la instalación de {} es necesario confiar en la siguiente clave pública {}
|
||||||
|
arch.aur.install.validity_check.title=Problemas de integridad
|
||||||
|
arch.aur.install.validity_check.body=Algunos de los archivos fuente necesarios para la instalación de {} no están en buen estado. La instalación se cancelará para evitar daños a su sistema.
|
||||||
@@ -93,3 +93,5 @@ arch.aur.install.unknown_key.title=Chave pública necessária
|
|||||||
arch.install.aur.unknown_key.body=Para continuar a instalação de {} é ncessário confiar na seguinte chave pública {}
|
arch.install.aur.unknown_key.body=Para continuar a instalação de {} é ncessário confiar na seguinte chave pública {}
|
||||||
arch.aur.install.unknown_key.status=Recebendo a chave pública {}
|
arch.aur.install.unknown_key.status=Recebendo a chave pública {}
|
||||||
arch.aur.install.unknown_key.receive_error=Não fui possível receber a chave pública {}
|
arch.aur.install.unknown_key.receive_error=Não fui possível receber a chave pública {}
|
||||||
|
arch.aur.install.validity_check.title=Problemas de integridade
|
||||||
|
arch.aur.install.validity_check.body=Alguns dos arquivos-fonte necessários para instalação de {} não estão íntegros. A instalação será cancelada para evitar danos no seu sistema.
|
||||||
@@ -2,18 +2,15 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from bauh.api.abstract.context import ApplicationContext
|
from bauh.api.abstract.context import ApplicationContext
|
||||||
from bauh.api.abstract.controller import SoftwareManager
|
from bauh.api.abstract.controller import SoftwareManager
|
||||||
from bauh.api.constants import HOME_PATH
|
from bauh.api.constants import HOME_PATH
|
||||||
from bauh.api.http import HttpClient
|
|
||||||
from bauh.gems.arch import pacman, disk
|
from bauh.gems.arch import pacman, disk
|
||||||
|
|
||||||
URL_INDEX = 'https://aur.archlinux.org/packages.gz'
|
URL_INDEX = 'https://aur.archlinux.org/packages.gz'
|
||||||
@@ -47,7 +44,7 @@ class AURIndexUpdater(Thread):
|
|||||||
self.logger.info('Pre-indexed {} AUR package names in memory'.format(len(self.man.names_index)))
|
self.logger.info('Pre-indexed {} AUR package names in memory'.format(len(self.man.names_index)))
|
||||||
else:
|
else:
|
||||||
self.logger.warning('No data returned from: {}'.format(URL_INDEX))
|
self.logger.warning('No data returned from: {}'.format(URL_INDEX))
|
||||||
except ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.logger.warning('No internet connection: could not pre-index packages')
|
self.logger.warning('No internet connection: could not pre-index packages')
|
||||||
|
|
||||||
time.sleep(60 * 20) # updates every 20 minutes
|
time.sleep(60 * 20) # updates every 20 minutes
|
||||||
@@ -137,34 +134,3 @@ class ArchCompilationOptimizer(Thread if bool(os.getenv('BAUH_DEBUG', 0)) else P
|
|||||||
self.logger.info("A custom optimized 'makepkg.conf' was generated at '{}'".format(HOME_PATH))
|
self.logger.info("A custom optimized 'makepkg.conf' was generated at '{}'".format(HOME_PATH))
|
||||||
else:
|
else:
|
||||||
self.logger.warning("A custom 'makepkg.conf' is already defined at '{}'".format(HOME_PATH))
|
self.logger.warning("A custom 'makepkg.conf' is already defined at '{}'".format(HOME_PATH))
|
||||||
|
|
||||||
|
|
||||||
class CategoriesDownloader:
|
|
||||||
|
|
||||||
URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/aur/categories.txt'
|
|
||||||
|
|
||||||
def __init__(self, http_client: HttpClient, logger: logging.Logger):
|
|
||||||
self.http_client = http_client
|
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def get_categories(self) -> Dict[str, List[str]]:
|
|
||||||
self.logger.info('Downloading AUR category definitions from {}'.format(self.URL_CATEGORIES_FILE))
|
|
||||||
|
|
||||||
res = self.http_client.get(self.URL_CATEGORIES_FILE)
|
|
||||||
|
|
||||||
if res:
|
|
||||||
try:
|
|
||||||
categories_map = {}
|
|
||||||
for l in res.text.split('\n'):
|
|
||||||
if l:
|
|
||||||
data = l.split('=')
|
|
||||||
categories_map[data[0]] = [c.strip() for c in data[1].split(',') if c]
|
|
||||||
|
|
||||||
self.logger.info('Loaded categories for {} AUR packages'.format(len(categories_map)))
|
|
||||||
return categories_map
|
|
||||||
except:
|
|
||||||
self.logger.error("Could not parse AUR categories definitions")
|
|
||||||
traceback.print_exc()
|
|
||||||
return {}
|
|
||||||
else:
|
|
||||||
self.logger.info('Could not download {}'.format(self.URL_CATEGORIES_FILE))
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@@ -24,6 +25,7 @@ class FlatpakManager(SoftwareManager):
|
|||||||
super(FlatpakManager, self).__init__(context=context)
|
super(FlatpakManager, self).__init__(context=context)
|
||||||
self.i18n = context.i18n
|
self.i18n = context.i18n
|
||||||
self.api_cache = context.cache_factory.new()
|
self.api_cache = context.cache_factory.new()
|
||||||
|
self.category_cache = context.cache_factory.new()
|
||||||
context.disk_loader_factory.map(FlatpakApplication, self.api_cache)
|
context.disk_loader_factory.map(FlatpakApplication, self.api_cache)
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.http_client = context.http_client
|
self.http_client = context.http_client
|
||||||
@@ -45,7 +47,8 @@ class FlatpakManager(SoftwareManager):
|
|||||||
disk_loader.fill(app) # preloading cached disk data
|
disk_loader.fill(app) # preloading cached disk data
|
||||||
|
|
||||||
if internet:
|
if internet:
|
||||||
FlatpakAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self, context=self.context).start()
|
FlatpakAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self,
|
||||||
|
context=self.context, category_cache=self.category_cache).start()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
app.fill_cached_data(api_data)
|
app.fill_cached_data(api_data)
|
||||||
@@ -247,7 +250,7 @@ class FlatpakManager(SoftwareManager):
|
|||||||
|
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
if flatpak.is_installed():
|
if flatpak.is_installed():
|
||||||
if not flatpak.has_remotes_set():
|
if not flatpak.has_remotes_set():
|
||||||
return [self.i18n['flatpak.notification.no_remotes']]
|
return [self.i18n['flatpak.notification.no_remotes']]
|
||||||
@@ -279,11 +282,20 @@ class FlatpakManager(SoftwareManager):
|
|||||||
flatpak.run(str(pkg.id))
|
flatpak.run(str(pkg.id))
|
||||||
|
|
||||||
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
|
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
|
||||||
res = self.http_client.get_json('{}/apps/{}'.format(FLATHUB_API_URL, pkg.id))
|
screenshots_url = '{}/apps/{}'.format(FLATHUB_API_URL, pkg.id)
|
||||||
urls = []
|
urls = []
|
||||||
if res and res.get('screenshots'):
|
try:
|
||||||
for s in res['screenshots']:
|
res = self.http_client.get_json(screenshots_url)
|
||||||
if s.get('imgDesktopUrl'):
|
|
||||||
urls.append(s['imgDesktopUrl'])
|
if res and res.get('screenshots'):
|
||||||
|
for s in res['screenshots']:
|
||||||
|
if s.get('imgDesktopUrl'):
|
||||||
|
urls.append(s['imgDesktopUrl'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if e.__class__.__name__ == 'JSONDecodeError':
|
||||||
|
self.context.logger.error("Could not decode json from '{}'".format(screenshots_url))
|
||||||
|
else:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
return urls
|
return urls
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class FlatpakApplication(SoftwarePackage):
|
|||||||
self.runtime = runtime
|
self.runtime = runtime
|
||||||
self.commit = commit
|
self.commit = commit
|
||||||
|
|
||||||
|
if runtime:
|
||||||
|
self.categories = ['runtime']
|
||||||
|
|
||||||
def is_incomplete(self):
|
def is_incomplete(self):
|
||||||
return self.description is None and self.icon_url
|
return self.description is None and self.icon_url
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import traceback
|
import traceback
|
||||||
|
from io import StringIO
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from bauh.api.abstract.cache import MemoryCache
|
from bauh.api.abstract.cache import MemoryCache
|
||||||
@@ -12,7 +13,7 @@ from bauh.gems.flatpak.model import FlatpakApplication
|
|||||||
|
|
||||||
class FlatpakAsyncDataLoader(Thread):
|
class FlatpakAsyncDataLoader(Thread):
|
||||||
|
|
||||||
def __init__(self, app: FlatpakApplication, manager: SoftwareManager, context: ApplicationContext, api_cache: MemoryCache):
|
def __init__(self, app: FlatpakApplication, manager: SoftwareManager, context: ApplicationContext, api_cache: MemoryCache, category_cache: MemoryCache):
|
||||||
super(FlatpakAsyncDataLoader, self).__init__(daemon=True)
|
super(FlatpakAsyncDataLoader, self).__init__(daemon=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
@@ -20,6 +21,21 @@ class FlatpakAsyncDataLoader(Thread):
|
|||||||
self.api_cache = api_cache
|
self.api_cache = api_cache
|
||||||
self.persist = False
|
self.persist = False
|
||||||
self.logger = context.logger
|
self.logger = context.logger
|
||||||
|
self.category_cache = category_cache
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_category(category: str) -> str:
|
||||||
|
word = StringIO()
|
||||||
|
last_l = None
|
||||||
|
for idx, l in enumerate(category):
|
||||||
|
if idx != 0 and last_l != ' ' and l.isupper() and idx + 1 < len(category) and category[idx + 1].islower():
|
||||||
|
word.write(' ')
|
||||||
|
|
||||||
|
last_l = l.lower()
|
||||||
|
word.write(last_l)
|
||||||
|
|
||||||
|
word.seek(0)
|
||||||
|
return word.read()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.app:
|
if self.app:
|
||||||
@@ -51,7 +67,17 @@ class FlatpakAsyncDataLoader(Thread):
|
|||||||
self.app.icon_url = FLATHUB_URL + self.app.icon_url
|
self.app.icon_url = FLATHUB_URL + self.app.icon_url
|
||||||
|
|
||||||
if data.get('categories'):
|
if data.get('categories'):
|
||||||
self.app.categories = [c['name'] for c in data['categories']]
|
cats = []
|
||||||
|
for c in data['categories']:
|
||||||
|
cached = self.category_cache.get(c['name'])
|
||||||
|
|
||||||
|
if not cached:
|
||||||
|
cached = self.format_category(c['name'])
|
||||||
|
self.category_cache.add_non_existing(c['name'], cached)
|
||||||
|
|
||||||
|
cats.append(cached)
|
||||||
|
|
||||||
|
self.app.categories = cats
|
||||||
|
|
||||||
loaded_data = self.app.get_data_to_cache()
|
loaded_data = self.app.get_data_to_cache()
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
from bauh.api.constants import CACHE_PATH
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
SNAP_CACHE_PATH = CACHE_PATH + '/snap'
|
||||||
|
CATEGORIES_FILE_PATH = SNAP_CACHE_PATH + '/categories.txt'
|
||||||
|
URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/categories.txt'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@@ -8,12 +9,16 @@ from bauh.api.abstract.disk import DiskCacheLoader
|
|||||||
from bauh.api.abstract.handler import ProcessWatcher
|
from bauh.api.abstract.handler import ProcessWatcher
|
||||||
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
|
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
|
||||||
SuggestionPriority
|
SuggestionPriority
|
||||||
|
from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption
|
||||||
|
from bauh.commons.category import CategoriesDownloader
|
||||||
from bauh.commons.html import bold
|
from bauh.commons.html import bold
|
||||||
from bauh.commons.system import SystemProcess, ProcessHandler
|
from bauh.commons.system import SystemProcess, ProcessHandler, new_root_subprocess
|
||||||
from bauh.gems.snap import snap, suggestions
|
from bauh.gems.snap import snap, suggestions, URL_CATEGORIES_FILE, SNAP_CACHE_PATH, CATEGORIES_FILE_PATH
|
||||||
from bauh.gems.snap.constants import SNAP_API_URL
|
from bauh.gems.snap.constants import SNAP_API_URL
|
||||||
from bauh.gems.snap.model import SnapApplication
|
from bauh.gems.snap.model import SnapApplication
|
||||||
from bauh.gems.snap.worker import SnapAsyncDataLoader, CategoriesDownloader
|
from bauh.gems.snap.worker import SnapAsyncDataLoader
|
||||||
|
|
||||||
|
RE_AVAILABLE_CHANNELS = re.compile(re.compile(r'(\w+)\s+(snap install.+)'))
|
||||||
|
|
||||||
|
|
||||||
class SnapManager(SoftwareManager):
|
class SnapManager(SoftwareManager):
|
||||||
@@ -28,7 +33,8 @@ class SnapManager(SoftwareManager):
|
|||||||
self.logger = context.logger
|
self.logger = context.logger
|
||||||
self.ubuntu_distro = context.distro == 'ubuntu'
|
self.ubuntu_distro = context.distro == 'ubuntu'
|
||||||
self.categories = {}
|
self.categories = {}
|
||||||
self.categories_downloader = CategoriesDownloader(self.http_client, self.logger)
|
self.categories_downloader = CategoriesDownloader('snap', self.http_client, self.logger, self, context.disk_cache,
|
||||||
|
URL_CATEGORIES_FILE, SNAP_CACHE_PATH, CATEGORIES_FILE_PATH)
|
||||||
|
|
||||||
def map_json(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader, internet: bool = True) -> SnapApplication:
|
def map_json(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader, internet: bool = True) -> SnapApplication:
|
||||||
app = SnapApplication(publisher=app_json.get('publisher'),
|
app = SnapApplication(publisher=app_json.get('publisher'),
|
||||||
@@ -41,13 +47,27 @@ class SnapManager(SoftwareManager):
|
|||||||
latest_version=app_json.get('version'),
|
latest_version=app_json.get('version'),
|
||||||
description=app_json.get('description', app_json.get('summary')))
|
description=app_json.get('description', app_json.get('summary')))
|
||||||
|
|
||||||
if app.publisher:
|
if app.publisher and app.publisher.endswith('*'):
|
||||||
|
app.verified_publisher = True
|
||||||
app.publisher = app.publisher.replace('*', '')
|
app.publisher = app.publisher.replace('*', '')
|
||||||
|
|
||||||
app.categories = self.categories.get(app.name.lower())
|
categories = self.categories.get(app.name.lower())
|
||||||
|
|
||||||
|
if categories:
|
||||||
|
app.categories = categories
|
||||||
|
|
||||||
app.installed = installed
|
app.installed = installed
|
||||||
|
|
||||||
|
if not app.is_application():
|
||||||
|
categories = app.categories
|
||||||
|
|
||||||
|
if categories is None:
|
||||||
|
categories = []
|
||||||
|
app.categories = categories
|
||||||
|
|
||||||
|
if 'runtime' not in categories:
|
||||||
|
categories.append('runtime')
|
||||||
|
|
||||||
api_data = self.api_cache.get(app_json['name'])
|
api_data = self.api_cache.get(app_json['name'])
|
||||||
expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()
|
expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()
|
||||||
|
|
||||||
@@ -88,6 +108,7 @@ class SnapManager(SoftwareManager):
|
|||||||
|
|
||||||
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult:
|
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult:
|
||||||
if snap.is_snapd_running():
|
if snap.is_snapd_running():
|
||||||
|
self.categories_downloader.join()
|
||||||
installed = [self.map_json(app_json, installed=True, disk_loader=disk_loader, internet=internet_available) for app_json in snap.read_installed(self.ubuntu_distro)]
|
installed = [self.map_json(app_json, installed=True, disk_loader=disk_loader, internet=internet_available) for app_json in snap.read_installed(self.ubuntu_distro)]
|
||||||
return SearchResult(installed, None, len(installed))
|
return SearchResult(installed, None, len(installed))
|
||||||
else:
|
else:
|
||||||
@@ -125,9 +146,31 @@ class SnapManager(SoftwareManager):
|
|||||||
raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__))
|
raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__))
|
||||||
|
|
||||||
def install(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
def install(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||||
res = ProcessHandler(watcher).handle(SystemProcess(subproc=snap.install_and_stream(pkg.name, pkg.confinement, root_password)))
|
res, output = ProcessHandler(watcher).handle_simple(snap.install_and_stream(pkg.name, pkg.confinement, root_password))
|
||||||
|
|
||||||
if res:
|
if 'error:' in output:
|
||||||
|
res = False
|
||||||
|
if 'not available on stable' in output:
|
||||||
|
channels = RE_AVAILABLE_CHANNELS.findall(output)
|
||||||
|
|
||||||
|
if channels:
|
||||||
|
opts = [InputOption(label=c[0], value=c[1]) for c in channels]
|
||||||
|
channel_select = SingleSelectComponent(type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0])
|
||||||
|
if watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'],
|
||||||
|
body=self.i18n['snap.install.available_channels.body'].format(bold(self.i18n['stable']), bold(pkg.name)) + ':',
|
||||||
|
components=[channel_select],
|
||||||
|
confirmation_label=self.i18n['continue'],
|
||||||
|
deny_label=self.i18n['cancel']):
|
||||||
|
self.logger.info("Installing '{}' with the custom command '{}'".format(pkg.name, channel_select.value))
|
||||||
|
res = ProcessHandler(watcher).handle(SystemProcess(new_root_subprocess(channel_select.value.value.split(' '), root_password=root_password)))
|
||||||
|
|
||||||
|
if res:
|
||||||
|
pkg.has_apps_field = snap.has_apps_field(pkg.name, self.ubuntu_distro)
|
||||||
|
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
self.logger.error("Could not find available channels in the installation output: {}".format(output))
|
||||||
|
else:
|
||||||
pkg.has_apps_field = snap.has_apps_field(pkg.name, self.ubuntu_distro)
|
pkg.has_apps_field = snap.has_apps_field(pkg.name, self.ubuntu_distro)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -148,20 +191,24 @@ class SnapManager(SoftwareManager):
|
|||||||
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.refresh_and_stream(pkg.name, root_password)))
|
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.refresh_and_stream(pkg.name, root_password)))
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
categories = self.categories_downloader.get_categories()
|
self.categories_downloader.start()
|
||||||
|
|
||||||
if categories:
|
|
||||||
self.categories = categories
|
|
||||||
|
|
||||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
if snap.is_installed() and not snap.is_snapd_running():
|
if snap.is_installed() and not snap.is_snapd_running():
|
||||||
snap_bold = bold('Snap')
|
snap_bold = bold('Snap')
|
||||||
return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold),
|
return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold),
|
||||||
self.i18n['snap.notification.snap.disable'].format(snap_bold, bold(self.i18n['manage_window.settings.gems']))]
|
self.i18n['snap.notification.snap.disable'].format(snap_bold, bold(self.i18n['manage_window.settings.gems']))]
|
||||||
|
|
||||||
|
if internet_available:
|
||||||
|
available, output = snap.is_api_available()
|
||||||
|
|
||||||
|
if not available:
|
||||||
|
self.logger.warning('It seems Snap API is not available. Search output: {}'.format(output))
|
||||||
|
return [self.i18n['snap.notifications.api.unavailable'].format(bold('Snaps'), bold('Snap'))]
|
||||||
|
|
||||||
def _fill_suggestion(self, pkg_name: str, priority: SuggestionPriority, out: List[PackageSuggestion]):
|
def _fill_suggestion(self, pkg_name: str, priority: SuggestionPriority, out: List[PackageSuggestion]):
|
||||||
res = self.http_client.get_json(SNAP_API_URL + '/search?q=package_name:{}'.format(pkg_name))
|
res = self.http_client.get_json(SNAP_API_URL + '/search?q=package_name:{}'.format(pkg_name))
|
||||||
|
|
||||||
@@ -182,6 +229,8 @@ class SnapManager(SoftwareManager):
|
|||||||
sugs.sort(key=lambda t: t[1].value, reverse=True)
|
sugs.sort(key=lambda t: t[1].value, reverse=True)
|
||||||
|
|
||||||
threads = []
|
threads = []
|
||||||
|
self.categories_downloader.join()
|
||||||
|
|
||||||
for sug in sugs:
|
for sug in sugs:
|
||||||
|
|
||||||
if limit <= 0 or len(res) < limit:
|
if limit <= 0 or len(res) < limit:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class SnapApplication(SoftwarePackage):
|
|||||||
|
|
||||||
def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None,
|
def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None,
|
||||||
description: str = None, publisher: str = None, rev: str = None, notes: str = None,
|
description: str = None, publisher: str = None, rev: str = None, notes: str = None,
|
||||||
confinement: str = None, has_apps_field: bool = None):
|
confinement: str = None, has_apps_field: bool = None, verified_publisher: bool = False):
|
||||||
super(SnapApplication, self).__init__(id=id, name=name, version=version,
|
super(SnapApplication, self).__init__(id=id, name=name, version=version,
|
||||||
latest_version=latest_version, description=description)
|
latest_version=latest_version, description=description)
|
||||||
self.publisher = publisher
|
self.publisher = publisher
|
||||||
@@ -27,6 +27,7 @@ class SnapApplication(SoftwarePackage):
|
|||||||
self.notes = notes
|
self.notes = notes
|
||||||
self.confinement = confinement
|
self.confinement = confinement
|
||||||
self.has_apps_field = has_apps_field
|
self.has_apps_field = has_apps_field
|
||||||
|
self.verified_publisher = verified_publisher
|
||||||
|
|
||||||
def supports_disk_cache(self):
|
def supports_disk_cache(self):
|
||||||
return self.installed
|
return self.installed
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
gem.snap.info=Applications published at https://snapcraft.io/store
|
gem.snap.info=Applications published at https://snapcraft.io/store
|
||||||
snap.notification.snapd_unavailable={} seems not to be installed or enabled. {} packages will not be available.
|
snap.notification.snapd_unavailable={} seems not to be installed or enabled. {} packages will not be available.
|
||||||
snap.notification.snap.disable=If you do not want to use Snap applications, uncheck {} in {}
|
snap.notification.snap.disable=If you do not want to use Snap applications, uncheck {} in {}
|
||||||
|
snap.notifications.api.unavailable=It seems the {} API is unavailable at the moment. It will not be possible to search for new {} applications.
|
||||||
snap.action.refresh.status=Refreshing
|
snap.action.refresh.status=Refreshing
|
||||||
snap.action.refresh.label=Refresh
|
snap.action.refresh.label=Refresh
|
||||||
|
snap.install.available_channels.title=Available channels
|
||||||
|
snap.install.available_channels.body=There is no {} channel available for {}. But there are these below ( select one if you want to continue )
|
||||||
@@ -12,5 +12,8 @@ snap.info.version=versión
|
|||||||
snap.info.size=tamaño
|
snap.info.size=tamaño
|
||||||
snap.notification.snapd_unavailable={} no parece estar instalado o habilitado. Los paquetes {} estarán indisponibles.
|
snap.notification.snapd_unavailable={} no parece estar instalado o habilitado. Los paquetes {} estarán indisponibles.
|
||||||
snap.notification.snap.disable=Si no desea usar aplicativos Snap, desmarque {} en {}
|
snap.notification.snap.disable=Si no desea usar aplicativos Snap, desmarque {} en {}
|
||||||
|
snap.notifications.api.unavailable=Parece que la API de {} no está disponible en este momento. No será posible buscar nuevos aplicativos {}.
|
||||||
snap.action.refresh.status=Actualizando
|
snap.action.refresh.status=Actualizando
|
||||||
snap.action.refresh.label=Actualizar
|
snap.action.refresh.label=Actualizar
|
||||||
|
snap.install.available_channels.title=Canales disponibles
|
||||||
|
snap.install.available_channels.body=No hay un canal {} ( stable ) disponible para {}. Pero hay estos otros abajo ( seleccione uno si desea continuar )
|
||||||
@@ -12,5 +12,8 @@ snap.info.version=versão
|
|||||||
snap.info.size=tamanho
|
snap.info.size=tamanho
|
||||||
snap.notification.snapd_unavailable={} não parece estar instalado ou habilitado. Os pacotes {} estarão indisponíveis.
|
snap.notification.snapd_unavailable={} não parece estar instalado ou habilitado. Os pacotes {} estarão indisponíveis.
|
||||||
snap.notification.snap.disable=Se não deseja usar aplicativos Snap, desmarque {} em {}
|
snap.notification.snap.disable=Se não deseja usar aplicativos Snap, desmarque {} em {}
|
||||||
|
snap.notifications.api.unavailable=Parece que a API de {} está indisponível no momento. Não será possível buscar novos aplicativos {}.
|
||||||
snap.action.refresh.status=Atualizando
|
snap.action.refresh.status=Atualizando
|
||||||
snap.action.refresh.label=Atualizar
|
snap.action.refresh.label=Atualizar
|
||||||
|
snap.install.available_channels.title=Canais disponíveis
|
||||||
|
snap.install.available_channels.body=Não há um canal {} ( stable ) disponível para {}. Porém existem estes outros abaixo ( selecione algum se quiser continuar )
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List
|
from io import StringIO
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
from bauh.commons.system import new_root_subprocess, run_cmd, new_subprocess
|
from bauh.commons.system import new_root_subprocess, run_cmd, new_subprocess, SimpleProcess
|
||||||
from bauh.gems.snap.model import SnapApplication
|
from bauh.gems.snap.model import SnapApplication
|
||||||
|
|
||||||
BASE_CMD = 'snap'
|
BASE_CMD = 'snap'
|
||||||
@@ -176,14 +177,15 @@ def uninstall_and_stream(app_name: str, root_password: str):
|
|||||||
return new_root_subprocess([BASE_CMD, 'remove', app_name], root_password)
|
return new_root_subprocess([BASE_CMD, 'remove', app_name], root_password)
|
||||||
|
|
||||||
|
|
||||||
def install_and_stream(app_name: str, confinement: str, root_password: str) -> subprocess.Popen:
|
def install_and_stream(app_name: str, confinement: str, root_password: str) -> SimpleProcess:
|
||||||
|
|
||||||
install_cmd = [BASE_CMD, 'install', app_name] # default
|
install_cmd = [BASE_CMD, 'install', app_name] # default
|
||||||
|
|
||||||
if confinement == 'classic':
|
if confinement == 'classic':
|
||||||
install_cmd.append('--classic')
|
install_cmd.append('--classic')
|
||||||
|
|
||||||
return new_root_subprocess(install_cmd, root_password)
|
# return new_root_subprocess(install_cmd, root_password)
|
||||||
|
return SimpleProcess(install_cmd, root_password=root_password)
|
||||||
|
|
||||||
|
|
||||||
def downgrade_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
def downgrade_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
||||||
@@ -226,3 +228,14 @@ def run(app: SnapApplication, logger: logging.Logger):
|
|||||||
logger.error("No valid command found for '{}'".format(app_name))
|
logger.error("No valid command found for '{}'".format(app_name))
|
||||||
else:
|
else:
|
||||||
logger.error("No command found for '{}'".format(app_name))
|
logger.error("No command found for '{}'".format(app_name))
|
||||||
|
|
||||||
|
|
||||||
|
def is_api_available() -> Tuple[bool, str]:
|
||||||
|
output = StringIO()
|
||||||
|
for o in SimpleProcess(['snap', 'search']).instance.stdout:
|
||||||
|
if o:
|
||||||
|
output.write(o.decode())
|
||||||
|
|
||||||
|
output.seek(0)
|
||||||
|
output = output.read()
|
||||||
|
return 'error:' not in output, output
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
import traceback
|
import traceback
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from bauh.api.abstract.cache import MemoryCache
|
from bauh.api.abstract.cache import MemoryCache
|
||||||
from bauh.api.abstract.context import ApplicationContext
|
from bauh.api.abstract.context import ApplicationContext
|
||||||
from bauh.api.abstract.controller import SoftwareManager
|
from bauh.api.abstract.controller import SoftwareManager
|
||||||
from bauh.api.abstract.model import PackageStatus
|
from bauh.api.abstract.model import PackageStatus
|
||||||
from bauh.api.http import HttpClient
|
|
||||||
from bauh.gems.snap import snap
|
from bauh.gems.snap import snap
|
||||||
from bauh.gems.snap.constants import SNAP_API_URL
|
from bauh.gems.snap.constants import SNAP_API_URL
|
||||||
from bauh.gems.snap.model import SnapApplication
|
from bauh.gems.snap.model import SnapApplication
|
||||||
@@ -72,34 +68,3 @@ class SnapAsyncDataLoader(Thread):
|
|||||||
|
|
||||||
if self.persist:
|
if self.persist:
|
||||||
self.manager.cache_to_disk(pkg=self.app, icon_bytes=None, only_icon=False)
|
self.manager.cache_to_disk(pkg=self.app, icon_bytes=None, only_icon=False)
|
||||||
|
|
||||||
|
|
||||||
class CategoriesDownloader:
|
|
||||||
|
|
||||||
URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/categories.txt'
|
|
||||||
|
|
||||||
def __init__(self, http_client: HttpClient, logger: logging.Logger):
|
|
||||||
self.http_client = http_client
|
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def get_categories(self) -> Dict[str, List[str]]:
|
|
||||||
self.logger.info('Downloading Snap category definitions from {}'.format(self.URL_CATEGORIES_FILE))
|
|
||||||
|
|
||||||
res = self.http_client.get(self.URL_CATEGORIES_FILE)
|
|
||||||
|
|
||||||
if res:
|
|
||||||
try:
|
|
||||||
categories_map = {}
|
|
||||||
for l in res.text.split('\n'):
|
|
||||||
if l:
|
|
||||||
data = l.split('=')
|
|
||||||
categories_map[data[0]] = [c.strip() for c in data[1].split(',') if c]
|
|
||||||
|
|
||||||
self.logger.info('Loaded categories for {} Snap applications'.format(len(categories_map)))
|
|
||||||
return categories_map
|
|
||||||
except:
|
|
||||||
self.logger.error("Could not parse categories definitions")
|
|
||||||
traceback.print_exc()
|
|
||||||
return {}
|
|
||||||
else:
|
|
||||||
self.logger.info('Could not download {}'.format(self.URL_CATEGORIES_FILE))
|
|
||||||
|
|||||||
@@ -307,13 +307,15 @@ class GenericSoftwareManager(SoftwareManager):
|
|||||||
|
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
def list_warnings(self) -> List[str]:
|
def list_warnings(self, internet_available: bool = None) -> List[str]:
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
if self.managers:
|
if self.managers:
|
||||||
|
int_available = internet.is_available(self.context.http_client, self.context.logger)
|
||||||
|
|
||||||
for man in self.managers:
|
for man in self.managers:
|
||||||
if man.is_enabled():
|
if man.is_enabled():
|
||||||
man_warnings = man.list_warnings()
|
man_warnings = man.list_warnings(internet_available=int_available)
|
||||||
|
|
||||||
if man_warnings:
|
if man_warnings:
|
||||||
if warnings is None:
|
if warnings is None:
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ class AboutDialog(QDialog):
|
|||||||
layout.addWidget(gems_widget)
|
layout.addWidget(gems_widget)
|
||||||
layout.addWidget(QLabel(''))
|
layout.addWidget(QLabel(''))
|
||||||
|
|
||||||
label_version = QLabel(locale_keys['version'].lower() + ': ' + __version__)
|
label_version = QLabel(locale_keys['version'].lower() + ' ' + __version__)
|
||||||
label_version.setStyleSheet('QLabel { font-size: 10px; font-weight: bold }')
|
label_version.setStyleSheet('QLabel { font-size: 11px; font-weight: bold }')
|
||||||
label_version.setAlignment(Qt.AlignCenter)
|
label_version.setAlignment(Qt.AlignCenter)
|
||||||
layout.addWidget(label_version)
|
layout.addWidget(label_version)
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class AppsTable(QTableWidget):
|
|||||||
col_update = None
|
col_update = None
|
||||||
|
|
||||||
if update_check_enabled and pkg.model.update:
|
if update_check_enabled and pkg.model.update:
|
||||||
col_update = UpdateToggleButton(pkg, self.window, self.i18n, pkg.model.update)
|
col_update = UpdateToggleButton(pkg, self.window, self.i18n, pkg.update_checked)
|
||||||
|
|
||||||
self.setCellWidget(pkg.table_index, 7, col_update)
|
self.setCellWidget(pkg.table_index, 7, col_update)
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ def update_info(pkgv: PackageView, pkgs_info: dict):
|
|||||||
pkgs_info['updates'] += 1
|
pkgs_info['updates'] += 1
|
||||||
|
|
||||||
if pkgv.model.categories:
|
if pkgv.model.categories:
|
||||||
pkgs_info['categories'].update([s.lower() for s in pkgv.model.categories])
|
for c in pkgv.model.categories:
|
||||||
|
pkgs_info['categories'].add(c.lower())
|
||||||
|
|
||||||
pkgs_info['pkgs'].append(pkgv)
|
pkgs_info['pkgs'].append(pkgv)
|
||||||
pkgs_info['not_installed'] += 1 if not pkgv.model.installed else 0
|
pkgs_info['not_installed'] += 1 if not pkgv.model.installed else 0
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ComboBoxQt(QComboBox):
|
|||||||
class RadioSelectQt(QGroupBox):
|
class RadioSelectQt(QGroupBox):
|
||||||
|
|
||||||
def __init__(self, model: SingleSelectComponent):
|
def __init__(self, model: SingleSelectComponent):
|
||||||
super(RadioSelectQt, self).__init__(model.label + ' :')
|
super(RadioSelectQt, self).__init__(model.label + ' :' if model.label else None)
|
||||||
self.model = model
|
self.model = model
|
||||||
self.setStyleSheet("QGroupBox { font-weight: bold }")
|
self.setStyleSheet("QGroupBox { font-weight: bold }")
|
||||||
|
|
||||||
|
|||||||
@@ -13,28 +13,41 @@ def is_root():
|
|||||||
return os.getuid() == 0
|
return os.getuid() == 0
|
||||||
|
|
||||||
|
|
||||||
def ask_root_password(locale_keys: dict):
|
def ask_root_password(i18n: dict):
|
||||||
|
|
||||||
diag = QInputDialog()
|
diag = QInputDialog()
|
||||||
diag.setStyleSheet("""QLineEdit { border-radius: 5px; font-size: 16px }""")
|
diag.setStyleSheet("""QLineEdit { border-radius: 5px; font-size: 16px }""")
|
||||||
diag.setInputMode(QInputDialog.TextInput)
|
diag.setInputMode(QInputDialog.TextInput)
|
||||||
diag.setTextEchoMode(QLineEdit.Password)
|
diag.setTextEchoMode(QLineEdit.Password)
|
||||||
diag.setWindowIcon(QIcon(resource.get_path('img/lock.png')))
|
diag.setWindowIcon(QIcon(resource.get_path('img/lock.png')))
|
||||||
diag.setWindowTitle(locale_keys['popup.root.title'])
|
diag.setWindowTitle(i18n['popup.root.title'])
|
||||||
diag.setLabelText('')
|
diag.setLabelText('')
|
||||||
diag.setCancelButtonText(locale_keys['popup.button.cancel'])
|
diag.setCancelButtonText(i18n['popup.button.cancel'])
|
||||||
diag.resize(400, 200)
|
diag.resize(400, 200)
|
||||||
|
|
||||||
ok = diag.exec_()
|
for attempt in range(3):
|
||||||
|
|
||||||
if ok:
|
ok = diag.exec_()
|
||||||
if not validate_password(diag.textValue()):
|
|
||||||
show_message(title=locale_keys['popup.root.bad_password.title'],
|
|
||||||
body=locale_keys['popup.root.bad_password.body'],
|
|
||||||
type_=MessageType.ERROR)
|
|
||||||
ok = False
|
|
||||||
|
|
||||||
return diag.textValue(), ok
|
if ok:
|
||||||
|
if not validate_password(diag.textValue()):
|
||||||
|
|
||||||
|
body = i18n['popup.root.bad_password.body']
|
||||||
|
|
||||||
|
if attempt == 2:
|
||||||
|
body += '. ' + i18n['popup.root.bad_password.last_try']
|
||||||
|
|
||||||
|
show_message(title=i18n['popup.root.bad_password.title'],
|
||||||
|
body=body,
|
||||||
|
type_=MessageType.ERROR)
|
||||||
|
ok = False
|
||||||
|
diag.setTextValue('')
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
return diag.textValue(), ok
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return '', False
|
||||||
|
|
||||||
|
|
||||||
def validate_password(password: str) -> bool:
|
def validate_password(password: str) -> bool:
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ from typing import List
|
|||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QIcon, QPixmap
|
from PyQt5.QtGui import QIcon, QPixmap
|
||||||
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QToolBar, QVBoxLayout
|
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QToolBar, QVBoxLayout, QProgressBar, QApplication
|
||||||
|
|
||||||
from bauh.api.abstract.cache import MemoryCache
|
from bauh.api.abstract.cache import MemoryCache
|
||||||
from bauh.api.http import HttpClient
|
from bauh.api.http import HttpClient
|
||||||
from bauh.view.qt import qt_utils
|
from bauh.view.qt import qt_utils
|
||||||
from bauh.view.qt.components import new_spacer
|
from bauh.view.qt.components import new_spacer
|
||||||
|
from bauh.view.qt.thread import AnimateProgress
|
||||||
from bauh.view.qt.view_model import PackageView
|
from bauh.view.qt.view_model import PackageView
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +29,12 @@ class ScreenshotsDialog(QDialog):
|
|||||||
self.resize(1280, 720)
|
self.resize(1280, 720)
|
||||||
self.i18n = i18n
|
self.i18n = i18n
|
||||||
self.http_client = http_client
|
self.http_client = http_client
|
||||||
|
self.progress_bar = QProgressBar()
|
||||||
|
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 6)
|
||||||
|
self.progress_bar.setTextVisible(False)
|
||||||
|
self.thread_progress = AnimateProgress()
|
||||||
|
self.thread_progress.signal_change.connect(self._update_progress)
|
||||||
|
self.thread_progress.start()
|
||||||
|
|
||||||
# THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED
|
# THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED
|
||||||
#
|
#
|
||||||
@@ -52,7 +59,9 @@ class ScreenshotsDialog(QDialog):
|
|||||||
|
|
||||||
self.img_label = QLabel()
|
self.img_label = QLabel()
|
||||||
self.img_label.setStyleSheet('QLabel { font-weight: bold; text-align: center }')
|
self.img_label.setStyleSheet('QLabel { font-weight: bold; text-align: center }')
|
||||||
self.bottom_bar.addWidget(self.img_label)
|
self.ref_img_label = self.bottom_bar.addWidget(self.img_label)
|
||||||
|
self.ref_img_label.setVisible(False)
|
||||||
|
self.ref_progress_bar = self.bottom_bar.addWidget(self.progress_bar)
|
||||||
self.bottom_bar.addWidget(new_spacer(50))
|
self.bottom_bar.addWidget(new_spacer(50))
|
||||||
|
|
||||||
self.bt_next = QPushButton(self.i18n['screenshots.bt_next.label'].capitalize())
|
self.bt_next = QPushButton(self.i18n['screenshots.bt_next.label'].capitalize())
|
||||||
@@ -64,11 +73,14 @@ class ScreenshotsDialog(QDialog):
|
|||||||
self.img_idx = 0
|
self.img_idx = 0
|
||||||
|
|
||||||
for idx, s in enumerate(self.screenshots):
|
for idx, s in enumerate(self.screenshots):
|
||||||
t = Thread(target=self._download_img, args=(idx, s))
|
t = Thread(target=self._download_img, args=(idx, s), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
self._load_img()
|
self._load_img()
|
||||||
|
|
||||||
|
def _update_progress(self, val: int):
|
||||||
|
self.progress_bar.setValue(val)
|
||||||
|
|
||||||
def _load_img(self):
|
def _load_img(self):
|
||||||
if len(self.loaded_imgs) > self.img_idx:
|
if len(self.loaded_imgs) > self.img_idx:
|
||||||
img = self.loaded_imgs[self.img_idx]
|
img = self.loaded_imgs[self.img_idx]
|
||||||
@@ -79,9 +91,15 @@ class ScreenshotsDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
self.img_label.setText(img)
|
self.img_label.setText(img)
|
||||||
self.img.setPixmap(QPixmap())
|
self.img.setPixmap(QPixmap())
|
||||||
|
|
||||||
|
self.thread_progress.stop = True
|
||||||
|
self.ref_progress_bar.setVisible(False)
|
||||||
|
self.ref_img_label.setVisible(True)
|
||||||
else:
|
else:
|
||||||
self.img.setPixmap(QPixmap())
|
self.img.setPixmap(QPixmap())
|
||||||
self.img_label.setText('...{}...'.format(self.i18n['screenshots,download.running']))
|
self.ref_img_label.setVisible(False)
|
||||||
|
self.ref_progress_bar.setVisible(True)
|
||||||
|
self.thread_progress.start()
|
||||||
|
|
||||||
if len(self.screenshots) == 1:
|
if len(self.screenshots) == 1:
|
||||||
self.ref_bt_back.setVisible(False)
|
self.ref_bt_back.setVisible(False)
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ class AsyncAction(QThread, ProcessWatcher):
|
|||||||
self.signal_status.emit(status)
|
self.signal_status.emit(status)
|
||||||
|
|
||||||
def change_substatus(self, substatus: str):
|
def change_substatus(self, substatus: str):
|
||||||
if substatus:
|
self.signal_substatus.emit(substatus)
|
||||||
self.signal_substatus.emit(substatus)
|
|
||||||
|
|
||||||
def change_progress(self, val: int):
|
def change_progress(self, val: int):
|
||||||
if val is not None:
|
if val is not None:
|
||||||
@@ -96,6 +95,7 @@ class UpdateSelectedApps(AsyncAction):
|
|||||||
|
|
||||||
self.change_status('{} {} {}...'.format(self.locale_keys['manage_window.status.upgrading'], name, app.model.version))
|
self.change_status('{} {} {}...'.format(self.locale_keys['manage_window.status.upgrading'], name, app.model.version))
|
||||||
success = bool(self.manager.update(app.model, self.root_password, self))
|
success = bool(self.manager.update(app.model, self.root_password, self))
|
||||||
|
self.change_substatus('')
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
break
|
break
|
||||||
@@ -352,6 +352,7 @@ class VerifyModels(QThread):
|
|||||||
def __init__(self, apps: List[PackageView] = None):
|
def __init__(self, apps: List[PackageView] = None):
|
||||||
super(VerifyModels, self).__init__()
|
super(VerifyModels, self).__init__()
|
||||||
self.apps = apps
|
self.apps = apps
|
||||||
|
self.work = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
@@ -361,6 +362,10 @@ class VerifyModels(QThread):
|
|||||||
last_ready = 0
|
last_ready = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
|
if not self.work:
|
||||||
|
break
|
||||||
|
|
||||||
current_ready = 0
|
current_ready = 0
|
||||||
|
|
||||||
for app in self.apps:
|
for app in self.apps:
|
||||||
@@ -379,6 +384,7 @@ class VerifyModels(QThread):
|
|||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.work = True
|
||||||
self.apps = None
|
self.apps = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from bauh.api.http import HttpClient
|
|||||||
from bauh.commons.html import bold
|
from bauh.commons.html import bold
|
||||||
from bauh.view.core.config import Configuration
|
from bauh.view.core.config import Configuration
|
||||||
from bauh.view.core.controller import GenericSoftwareManager
|
from bauh.view.core.controller import GenericSoftwareManager
|
||||||
from bauh.view.qt.screenshots import ScreenshotsDialog
|
|
||||||
from bauh.view.util import util, resource
|
|
||||||
from bauh.view.qt import dialog, commons, qt_utils
|
from bauh.view.qt import dialog, commons, qt_utils
|
||||||
from bauh.view.qt.about import AboutDialog
|
from bauh.view.qt.about import AboutDialog
|
||||||
from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton
|
from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton
|
||||||
@@ -30,12 +28,14 @@ from bauh.view.qt.gem_selector import GemSelectorPanel
|
|||||||
from bauh.view.qt.history import HistoryDialog
|
from bauh.view.qt.history import HistoryDialog
|
||||||
from bauh.view.qt.info import InfoDialog
|
from bauh.view.qt.info import InfoDialog
|
||||||
from bauh.view.qt.root import is_root, ask_root_password
|
from bauh.view.qt.root import is_root, ask_root_password
|
||||||
|
from bauh.view.qt.screenshots import ScreenshotsDialog
|
||||||
from bauh.view.qt.styles import StylesComboBox
|
from bauh.view.qt.styles import StylesComboBox
|
||||||
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
||||||
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \
|
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \
|
||||||
AsyncAction, LaunchApp, ApplyFilters, CustomAction, GetScreenshots
|
AsyncAction, LaunchApp, ApplyFilters, CustomAction, GetScreenshots
|
||||||
from bauh.view.qt.view_model import PackageView
|
from bauh.view.qt.view_model import PackageView
|
||||||
from bauh.view.qt.view_utils import load_icon
|
from bauh.view.qt.view_utils import load_icon
|
||||||
|
from bauh.view.util import util, resource
|
||||||
|
|
||||||
DARK_ORANGE = '#FF4500'
|
DARK_ORANGE = '#FF4500'
|
||||||
|
|
||||||
@@ -283,6 +283,7 @@ class ManageWindow(QWidget):
|
|||||||
self._maximized = False
|
self._maximized = False
|
||||||
self.progress_controll_enabled = True
|
self.progress_controll_enabled = True
|
||||||
self.recent_installation = False
|
self.recent_installation = False
|
||||||
|
self.recent_uninstall = False
|
||||||
|
|
||||||
self.dialog_about = None
|
self.dialog_about = None
|
||||||
self.first_refresh = suggestions
|
self.first_refresh = suggestions
|
||||||
@@ -314,6 +315,13 @@ class ManageWindow(QWidget):
|
|||||||
self._update_table(pkgs_info=pkgs_info, signal=True)
|
self._update_table(pkgs_info=pkgs_info, signal=True)
|
||||||
self.update_bt_upgrade(pkgs_info)
|
self.update_bt_upgrade(pkgs_info)
|
||||||
|
|
||||||
|
if self.pkgs_available:
|
||||||
|
self._notify_model_data_change()
|
||||||
|
self.thread_verify_models.work = False
|
||||||
|
self.thread_verify_models.wait(50)
|
||||||
|
self.thread_verify_models.apps = self.pkgs_available
|
||||||
|
self.thread_verify_models.start()
|
||||||
|
|
||||||
def _finish_apply_filters_async(self, success: bool):
|
def _finish_apply_filters_async(self, success: bool):
|
||||||
self.label_status.setText('')
|
self.label_status.setText('')
|
||||||
self.ref_toolbar_search.setVisible(True)
|
self.ref_toolbar_search.setVisible(True)
|
||||||
@@ -400,6 +408,21 @@ class ManageWindow(QWidget):
|
|||||||
def _notify_model_data_change(self):
|
def _notify_model_data_change(self):
|
||||||
self.table_apps.fill_async_data()
|
self.table_apps.fill_async_data()
|
||||||
|
|
||||||
|
if not self.recent_installation:
|
||||||
|
self._reload_categories()
|
||||||
|
|
||||||
|
def _reload_categories(self):
|
||||||
|
|
||||||
|
categories = set()
|
||||||
|
|
||||||
|
for p in self.pkgs_available:
|
||||||
|
if p.model.categories:
|
||||||
|
for c in p.model.categories:
|
||||||
|
categories.add(c.lower())
|
||||||
|
|
||||||
|
if categories:
|
||||||
|
self._update_categories(categories, keep_selected=True)
|
||||||
|
|
||||||
def changeEvent(self, e: QEvent):
|
def changeEvent(self, e: QEvent):
|
||||||
if isinstance(e, QWindowStateChangeEvent):
|
if isinstance(e, QWindowStateChangeEvent):
|
||||||
self._maximized = self.isMaximized()
|
self._maximized = self.isMaximized()
|
||||||
@@ -431,7 +454,6 @@ class ManageWindow(QWidget):
|
|||||||
|
|
||||||
def refresh_apps(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None):
|
def refresh_apps(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None):
|
||||||
self.recent_installation = False
|
self.recent_installation = False
|
||||||
self.type_filter = None
|
|
||||||
self.input_search.clear()
|
self.input_search.clear()
|
||||||
|
|
||||||
if not keep_console:
|
if not keep_console:
|
||||||
@@ -439,7 +461,7 @@ class ManageWindow(QWidget):
|
|||||||
|
|
||||||
self.ref_checkbox_updates.setVisible(False)
|
self.ref_checkbox_updates.setVisible(False)
|
||||||
self.ref_checkbox_only_apps.setVisible(False)
|
self.ref_checkbox_only_apps.setVisible(False)
|
||||||
self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False, clear_filters=True)
|
self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False, clear_filters=not self.recent_uninstall)
|
||||||
|
|
||||||
self.thread_refresh.app = top_app # the app will be on top when refresh happens
|
self.thread_refresh.app = top_app # the app will be on top when refresh happens
|
||||||
self.thread_refresh.pkg_types = pkg_types
|
self.thread_refresh.pkg_types = pkg_types
|
||||||
@@ -449,8 +471,9 @@ class ManageWindow(QWidget):
|
|||||||
self.finish_action()
|
self.finish_action()
|
||||||
self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
|
self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
|
||||||
self.ref_bt_upgrade.setVisible(True)
|
self.ref_bt_upgrade.setVisible(True)
|
||||||
self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types'])
|
self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types'], keep_filters=self.recent_uninstall and res['types'])
|
||||||
self.first_refresh = False
|
self.first_refresh = False
|
||||||
|
self.recent_uninstall = False
|
||||||
self._hide_fields_after_recent_installation()
|
self._hide_fields_after_recent_installation()
|
||||||
|
|
||||||
def uninstall_app(self, app: PackageView):
|
def uninstall_app(self, app: PackageView):
|
||||||
@@ -464,7 +487,7 @@ class ManageWindow(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._handle_console_option(True)
|
self._handle_console_option(True)
|
||||||
self._begin_action('{} {}'.format(self.i18n['manage_window.status.uninstalling'], app.model.name))
|
self._begin_action('{} {}'.format(self.i18n['manage_window.status.uninstalling'], app.model.name), clear_filters=False)
|
||||||
|
|
||||||
self.thread_uninstall.app = app
|
self.thread_uninstall.app = app
|
||||||
self.thread_uninstall.root_password = pwd
|
self.thread_uninstall.root_password = pwd
|
||||||
@@ -483,6 +506,7 @@ class ManageWindow(QWidget):
|
|||||||
util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled']))
|
util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled']))
|
||||||
|
|
||||||
only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2
|
only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2
|
||||||
|
self.recent_uninstall = True
|
||||||
self.refresh_apps(pkg_types={pkgv.model.__class__} if only_pkg_type else None)
|
self.refresh_apps(pkg_types={pkgv.model.__class__} if only_pkg_type else None)
|
||||||
else:
|
else:
|
||||||
if self._can_notify_user():
|
if self._can_notify_user():
|
||||||
@@ -547,7 +571,7 @@ class ManageWindow(QWidget):
|
|||||||
|
|
||||||
self.ref_bt_upgrade.setVisible(show_bt_upgrade)
|
self.ref_bt_upgrade.setVisible(show_bt_upgrade)
|
||||||
|
|
||||||
def change_update_state(self, pkgs_info: dict, trigger_filters: bool = True):
|
def change_update_state(self, pkgs_info: dict, trigger_filters: bool = True, keep_selected: bool = False):
|
||||||
self.update_bt_upgrade(pkgs_info)
|
self.update_bt_upgrade(pkgs_info)
|
||||||
|
|
||||||
if pkgs_info['updates'] > 0:
|
if pkgs_info['updates'] > 0:
|
||||||
@@ -556,13 +580,14 @@ class ManageWindow(QWidget):
|
|||||||
if not self.ref_checkbox_updates.isVisible():
|
if not self.ref_checkbox_updates.isVisible():
|
||||||
self.ref_checkbox_updates.setVisible(True)
|
self.ref_checkbox_updates.setVisible(True)
|
||||||
|
|
||||||
if not self.filter_updates:
|
if not self.filter_updates and not keep_selected:
|
||||||
self._change_checkbox(self.checkbox_updates, True, 'filter_updates', trigger_filters)
|
self._change_checkbox(self.checkbox_updates, True, 'filter_updates', trigger_filters)
|
||||||
|
|
||||||
if pkgs_info['napp_updates'] > 0 and self.filter_only_apps:
|
if pkgs_info['napp_updates'] > 0 and self.filter_only_apps and not keep_selected:
|
||||||
self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger_filters)
|
self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger_filters)
|
||||||
else:
|
else:
|
||||||
self._change_checkbox(self.checkbox_updates, False, 'filter_updates', trigger_filters)
|
if not keep_selected:
|
||||||
|
self._change_checkbox(self.checkbox_updates, False, 'filter_updates', trigger_filters)
|
||||||
|
|
||||||
self.ref_checkbox_updates.setVisible(False)
|
self.ref_checkbox_updates.setVisible(False)
|
||||||
|
|
||||||
@@ -586,7 +611,7 @@ class ManageWindow(QWidget):
|
|||||||
'display_limit': self.display_limit if updates <= 0 else None
|
'display_limit': self.display_limit if updates <= 0 else None
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False):
|
def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False, keep_filters: bool = False):
|
||||||
self.input_name_filter.setText('')
|
self.input_name_filter.setText('')
|
||||||
pkgs_info = commons.new_pkgs_info()
|
pkgs_info = commons.new_pkgs_info()
|
||||||
filters = self._gen_filters(ignore_updates)
|
filters = self._gen_filters(ignore_updates)
|
||||||
@@ -620,15 +645,19 @@ class ManageWindow(QWidget):
|
|||||||
self.thread_suggestions.start()
|
self.thread_suggestions.start()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger=False)
|
if not keep_filters:
|
||||||
self.checkbox_only_apps.setCheckable(False)
|
self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger=False)
|
||||||
|
self.checkbox_only_apps.setCheckable(False)
|
||||||
else:
|
else:
|
||||||
self.checkbox_only_apps.setCheckable(True)
|
if not keep_filters:
|
||||||
self._change_checkbox(self.checkbox_only_apps, True, 'filter_only_apps', trigger=False)
|
self.checkbox_only_apps.setCheckable(True)
|
||||||
|
self._change_checkbox(self.checkbox_only_apps, True, 'filter_only_apps', trigger=False)
|
||||||
|
|
||||||
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)
|
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False, keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
|
||||||
|
self._update_categories(pkgs_info['categories'], keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
|
||||||
|
self._update_type_filters(pkgs_info['available_types'], keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
|
||||||
self._apply_filters(pkgs_info, ignore_updates=ignore_updates)
|
self._apply_filters(pkgs_info, ignore_updates=ignore_updates)
|
||||||
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)
|
self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False, keep_selected=keep_filters and bool(pkgs_info['pkgs_displayed']))
|
||||||
|
|
||||||
self.pkgs_available = pkgs_info['pkgs']
|
self.pkgs_available = pkgs_info['pkgs']
|
||||||
|
|
||||||
@@ -640,9 +669,6 @@ class ManageWindow(QWidget):
|
|||||||
if self.pkgs:
|
if self.pkgs:
|
||||||
self.ref_input_name_filter.setVisible(True)
|
self.ref_input_name_filter.setVisible(True)
|
||||||
|
|
||||||
self._update_type_filters(pkgs_info['available_types'])
|
|
||||||
self._update_categories(pkgs_info['categories'])
|
|
||||||
|
|
||||||
self._update_table(pkgs_info=pkgs_info)
|
self._update_table(pkgs_info=pkgs_info)
|
||||||
|
|
||||||
if new_pkgs:
|
if new_pkgs:
|
||||||
@@ -660,19 +686,25 @@ class ManageWindow(QWidget):
|
|||||||
for pkgv in pkgs_info['pkgs']:
|
for pkgv in pkgs_info['pkgs']:
|
||||||
commons.apply_filters(pkgv, filters, pkgs_info)
|
commons.apply_filters(pkgv, filters, pkgs_info)
|
||||||
|
|
||||||
def _update_type_filters(self, available_types: dict = None):
|
def _update_type_filters(self, available_types: dict = None, keep_selected: bool = False):
|
||||||
|
|
||||||
if available_types is None:
|
if available_types is None:
|
||||||
self.ref_combo_filter_type.setVisible(self.combo_filter_type.count() > 1)
|
self.ref_combo_filter_type.setVisible(self.combo_filter_type.count() > 1)
|
||||||
else:
|
else:
|
||||||
self.type_filter = self.any_type_filter
|
keeping_selected = keep_selected and available_types and self.type_filter in available_types
|
||||||
|
|
||||||
|
if not keeping_selected:
|
||||||
|
self.type_filter = self.any_type_filter
|
||||||
|
|
||||||
if available_types and len(available_types) > 1:
|
if available_types and len(available_types) > 1:
|
||||||
if self.combo_filter_type.count() > 1:
|
if self.combo_filter_type.count() > 1:
|
||||||
for _ in range(self.combo_filter_type.count() - 1):
|
for _ in range(self.combo_filter_type.count() - 1):
|
||||||
self.combo_filter_type.removeItem(1)
|
self.combo_filter_type.removeItem(1)
|
||||||
|
|
||||||
for app_type, icon_path in available_types.items():
|
sel_type = -1
|
||||||
|
for idx, item in enumerate(available_types.items()):
|
||||||
|
app_type, icon_path = item[0], item[1]
|
||||||
|
|
||||||
icon = self.cache_type_filter_icons.get(app_type)
|
icon = self.cache_type_filter_icons.get(app_type)
|
||||||
|
|
||||||
if not icon:
|
if not icon:
|
||||||
@@ -681,28 +713,48 @@ class ManageWindow(QWidget):
|
|||||||
|
|
||||||
self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type)
|
self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type)
|
||||||
|
|
||||||
|
if keeping_selected and app_type == self.type_filter:
|
||||||
|
sel_type = idx + 1
|
||||||
|
|
||||||
|
self.combo_filter_type.blockSignals(True)
|
||||||
|
self.combo_filter_type.setCurrentIndex(sel_type if sel_type > -1 else 0)
|
||||||
|
self.combo_filter_type.blockSignals(False)
|
||||||
self.ref_combo_filter_type.setVisible(True)
|
self.ref_combo_filter_type.setVisible(True)
|
||||||
else:
|
else:
|
||||||
self.ref_combo_filter_type.setVisible(False)
|
self.ref_combo_filter_type.setVisible(False)
|
||||||
|
|
||||||
def _update_categories(self, categories: Set[str] = None):
|
def _update_categories(self, categories: Set[str] = None, keep_selected: bool = False):
|
||||||
|
|
||||||
if categories is None:
|
if categories is None:
|
||||||
self.ref_combo_categories.setVisible(self.combo_categories.count() > 1)
|
self.ref_combo_categories.setVisible(self.combo_categories.count() > 1)
|
||||||
else:
|
else:
|
||||||
self.category_filter = self.any_category_filter
|
keeping_selected = keep_selected and categories and self.category_filter in categories
|
||||||
|
|
||||||
|
if not keeping_selected:
|
||||||
|
self.category_filter = self.any_category_filter
|
||||||
|
|
||||||
if categories:
|
if categories:
|
||||||
if self.combo_categories.count() > 1:
|
if self.combo_categories.count() > 1:
|
||||||
for _ in range(self.combo_categories.count() - 1):
|
for _ in range(self.combo_categories.count() - 1):
|
||||||
self.combo_categories.removeItem(1)
|
self.combo_categories.removeItem(1)
|
||||||
|
|
||||||
for c in categories:
|
selected_cat = -1
|
||||||
|
cat_list = list(categories)
|
||||||
|
cat_list.sort()
|
||||||
|
|
||||||
|
for idx, c in enumerate(cat_list):
|
||||||
i18n_cat = self.i18n.get(c)
|
i18n_cat = self.i18n.get(c)
|
||||||
cat_label = i18n_cat if i18n_cat else c
|
cat_label = i18n_cat if i18n_cat else c
|
||||||
self.combo_categories.addItem(cat_label.capitalize(), c)
|
self.combo_categories.addItem(cat_label.capitalize(), c)
|
||||||
|
|
||||||
|
if keeping_selected and c == self.category_filter:
|
||||||
|
selected_cat = idx + 1
|
||||||
|
|
||||||
|
self.combo_categories.blockSignals(True)
|
||||||
|
self.combo_categories.setCurrentIndex(selected_cat if selected_cat > -1 else 0)
|
||||||
|
self.combo_categories.blockSignals(False)
|
||||||
self.ref_combo_categories.setVisible(True)
|
self.ref_combo_categories.setVisible(True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.ref_combo_categories.setVisible(False)
|
self.ref_combo_categories.setVisible(False)
|
||||||
|
|
||||||
@@ -809,7 +861,7 @@ class ManageWindow(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.combo_filter_type.setEnabled(False)
|
self.combo_filter_type.setEnabled(False)
|
||||||
|
|
||||||
def finish_action(self):
|
def finish_action(self, keep_filters: bool = False):
|
||||||
self.ref_combo_styles.setVisible(True)
|
self.ref_combo_styles.setVisible(True)
|
||||||
self.thread_animate_progress.stop = True
|
self.thread_animate_progress.stop = True
|
||||||
self.thread_animate_progress.wait(msecs=1000)
|
self.thread_animate_progress.wait(msecs=1000)
|
||||||
@@ -835,8 +887,8 @@ class ManageWindow(QWidget):
|
|||||||
if self.pkgs:
|
if self.pkgs:
|
||||||
self.ref_input_name_filter.setVisible(True)
|
self.ref_input_name_filter.setVisible(True)
|
||||||
self.update_bt_upgrade()
|
self.update_bt_upgrade()
|
||||||
self._update_type_filters()
|
self._update_type_filters(keep_selected=keep_filters)
|
||||||
self._update_categories()
|
self._update_categories(keep_selected=keep_filters)
|
||||||
|
|
||||||
if self.ref_bt_installed.isVisible():
|
if self.ref_bt_installed.isVisible():
|
||||||
self.ref_bt_installed.setEnabled(True)
|
self.ref_bt_installed.setEnabled(True)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ manage_window.checkbox.show_details=Show details
|
|||||||
popup.root.title=Requires your password to continue
|
popup.root.title=Requires your password to continue
|
||||||
popup.root.bad_password.title=Error
|
popup.root.bad_password.title=Error
|
||||||
popup.root.bad_password.body=Wrong password
|
popup.root.bad_password.body=Wrong password
|
||||||
|
popup.root.bad_password.last_try=Attempts finished. Action cancelled.
|
||||||
popup.history.title=History
|
popup.history.title=History
|
||||||
popup.history.selected.tooltip=Current version
|
popup.history.selected.tooltip=Current version
|
||||||
popup.button.yes=Yes
|
popup.button.yes=Yes
|
||||||
@@ -140,6 +141,9 @@ filetransfer=file transfer
|
|||||||
packagemanager=package manager
|
packagemanager=package manager
|
||||||
sportsgame=sports game
|
sportsgame=sports game
|
||||||
arcadegame=arcade game
|
arcadegame=arcade game
|
||||||
|
player=player
|
||||||
screenshots,download.running=downloading image
|
screenshots,download.running=downloading image
|
||||||
screenshots.download.no_content=No content to display
|
screenshots.download.no_content=No content to display
|
||||||
screenshots.download.no_response=Image not found
|
screenshots.download.no_response=Image not found
|
||||||
|
continue=continue
|
||||||
|
stable=stable
|
||||||
@@ -39,6 +39,7 @@ manage_window.checkbox.show_details=Mostrar detalles
|
|||||||
popup.root.title=Requiere tu contraseña para continuar
|
popup.root.title=Requiere tu contraseña para continuar
|
||||||
popup.root.bad_password.title=Error
|
popup.root.bad_password.title=Error
|
||||||
popup.root.bad_password.body=Contraseña incorrecta
|
popup.root.bad_password.body=Contraseña incorrecta
|
||||||
|
popup.root.bad_password.last_try=Intentos finalizados. Acción cancelada.
|
||||||
popup.history.title=Historia
|
popup.history.title=Historia
|
||||||
popup.history.selected.tooltip=Versión actual
|
popup.history.selected.tooltip=Versión actual
|
||||||
popup.button.yes=Sí
|
popup.button.yes=Sí
|
||||||
@@ -181,8 +182,12 @@ filetransfer=transferencia de archivos
|
|||||||
packagemanager=administrador de paquetes
|
packagemanager=administrador de paquetes
|
||||||
sportsgame=juego de deportes
|
sportsgame=juego de deportes
|
||||||
arcadegame=juego arcade
|
arcadegame=juego arcade
|
||||||
|
player=jugador
|
||||||
|
fonts=fuentes
|
||||||
screenshots.bt_next.label=próxima
|
screenshots.bt_next.label=próxima
|
||||||
screenshots.bt_back.label=anterior
|
screenshots.bt_back.label=anterior
|
||||||
screenshots,download.running=descargando imagen
|
screenshots,download.running=descargando imagen
|
||||||
screenshots.download.no_content=No hay contenido para mostrar
|
screenshots.download.no_content=No hay contenido para mostrar
|
||||||
screenshots.download.no_response=Imagen no encontrada
|
screenshots.download.no_response=Imagen no encontrada
|
||||||
|
continue=continuar
|
||||||
|
stable=estable
|
||||||
@@ -39,6 +39,7 @@ manage_window.checkbox.show_details=Mostrar detalhes
|
|||||||
popup.root.title=Requer sua senha para prosseguir
|
popup.root.title=Requer sua senha para prosseguir
|
||||||
popup.root.bad_password.title=Erro
|
popup.root.bad_password.title=Erro
|
||||||
popup.root.bad_password.body=Senha incorreta
|
popup.root.bad_password.body=Senha incorreta
|
||||||
|
popup.root.bad_password.last_try=Tentativas finalizadas. Ação cancelada.
|
||||||
popup.history.title=Histórico
|
popup.history.title=Histórico
|
||||||
popup.history.selected.tooltip=Versão atual
|
popup.history.selected.tooltip=Versão atual
|
||||||
popup.button.yes=Sim
|
popup.button.yes=Sim
|
||||||
@@ -184,8 +185,12 @@ viewer=visualizador
|
|||||||
packagemanager=gerenciador de pacotes
|
packagemanager=gerenciador de pacotes
|
||||||
sportsgame=jogo de esportes
|
sportsgame=jogo de esportes
|
||||||
arcadegame=jogo arcade
|
arcadegame=jogo arcade
|
||||||
|
player=jogador
|
||||||
|
fonts=fontes
|
||||||
screenshots.bt_next.label=próxima
|
screenshots.bt_next.label=próxima
|
||||||
screenshots.bt_back.label=anterior
|
screenshots.bt_back.label=anterior
|
||||||
screenshots,download.running=baixando imagem
|
screenshots,download.running=baixando imagem
|
||||||
screenshots.download.no_content=Sem conteúdo para exibir
|
screenshots.download.no_content=Sem conteúdo para exibir
|
||||||
screenshots.download.no_response=Imagem não encontrada
|
screenshots.download.no_response=Imagem não encontrada
|
||||||
|
continue=continuar
|
||||||
|
stable=estável
|
||||||
2
setup.py
@@ -3,7 +3,7 @@ import os
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
DESCRIPTION = (
|
DESCRIPTION = (
|
||||||
"Graphical user interface to manage Flatpak / Snap applications."
|
"Graphical interface to manage Flatpak, Snap, AppImage and AUR packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTHOR = "Vinicius Moreira"
|
AUTHOR = "Vinicius Moreira"
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class ArchDataMapperTest(TestCase):
|
|||||||
self.assertFalse(ArchDataMapper.check_update('77.0.3865.120-1', '77.0.3865.90-1'))
|
self.assertFalse(ArchDataMapper.check_update('77.0.3865.120-1', '77.0.3865.90-1'))
|
||||||
self.assertTrue(ArchDataMapper.check_update('77.0.3865.a-1', '77.0.3865.b-1'))
|
self.assertTrue(ArchDataMapper.check_update('77.0.3865.a-1', '77.0.3865.b-1'))
|
||||||
self.assertFalse(ArchDataMapper.check_update('77.0.b.0-1', '77.0.a.1-1'))
|
self.assertFalse(ArchDataMapper.check_update('77.0.b.0-1', '77.0.a.1-1'))
|
||||||
|
self.assertFalse(ArchDataMapper.check_update('r25.e22697c-1', 'r8.19fe011-1'))
|
||||||
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.r11.caacf30-1', 'r65.4c7144a-1'))
|
||||||
|
self.assertFalse(ArchDataMapper.check_update('1.2.16.r688.8b2c199-1', 'r2105.e91f0e9-3'))
|
||||||
|
|
||||||
def test_check_update_no_suffix_3_x_2_digits(self):
|
def test_check_update_no_suffix_3_x_2_digits(self):
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0-1', '1.1-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0-1', '1.1-1'))
|
||||||
@@ -35,97 +38,97 @@ class ArchDataMapperTest(TestCase):
|
|||||||
|
|
||||||
def test_check_update_release(self):
|
def test_check_update_release(self):
|
||||||
# RE
|
# RE
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.R-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.R-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-2', '1.0.0.R-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-2', '1.0.0.R-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.R-5'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.R-5', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.1.R-1', '1.0.1.R-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.1.R-1', '1.0.1.R-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.2.R-1', '1.0.1.R-7'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.2.R-1', '1.0.1.R-7', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.R-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.R-2', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.RELEASE-2', '1.0.0.R-5'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.RELEASE-2', '1.0.0.R-5', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.RELEASE-5'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.RELEASE-5', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.RE-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.1.R-1', '1.0.0.RE-1', check_suffix=True))
|
||||||
|
|
||||||
# GA
|
# GA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.Ge-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.Ge-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.Ge-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.Ge-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.GE-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.GE-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GE-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GE-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.GE-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.GE-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.GA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.GA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.GA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.GA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.GA-1', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.GA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.GA-1', check_suffix=True))
|
||||||
|
|
||||||
# RCS
|
# RCS
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.RC-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.RC-1', check_suffix=True))
|
||||||
|
|
||||||
# BETA
|
# BETA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.B-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.B-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.B-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.B-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.B-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.B-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.BETA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.BETA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.BETA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.BETA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.BETA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.BETA-2', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.BETA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.BETA-1', check_suffix=True))
|
||||||
|
|
||||||
# ALPHA
|
# ALPHA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.Alpha-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.Alpha-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALPHA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALPHA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.ALPHA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALPHA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALPHA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.ALFA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.ALFA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALFA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.ALFA-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALFA-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.ALFA-2', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.Alfa-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.Alfa-1', check_suffix=True))
|
||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.DeV-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.DeV-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.DEV-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.DEV-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.DEVEL-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.DEVEL-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.DEV-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.R-1', '1.0.0.DEV-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.DEV-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RE-1', '1.0.0.DEV-2', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.DEV-2'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.RELEASE-1', '1.0.0.DEV-2', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.DEV-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.R-5', '1.0.1.DEV-1', check_suffix=True))
|
||||||
|
|
||||||
def test_check_update_ga(self):
|
def test_check_update_ga(self):
|
||||||
# GA
|
# GA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.GE-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.GE-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.GA-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.GA-2', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GA-2', '1.0.1.GA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GA-2', '1.0.1.GA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.1.0.GE-3', '1.0.6.GE-10'))
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.GE-3', '1.0.6.GE-10', check_suffix=True))
|
||||||
|
|
||||||
# RC
|
# RC
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.RC-1', check_suffix=True))
|
||||||
|
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.RC-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.RC-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.10.GA-10', '1.1.0.RC-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.10.GA-10', '1.1.0.RC-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.1.0.GE-1', '1.0.1.RC-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.GE-1', '1.0.1.RC-1', check_suffix=True))
|
||||||
|
|
||||||
# BETA
|
# BETA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.BETA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.BETA-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.BETA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.BETA-1', check_suffix=True))
|
||||||
|
|
||||||
# ALPHA
|
# ALPHA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.ALPHA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.0.ALPHA-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.1.ALPHA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.1.ALPHA-1', check_suffix=True))
|
||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.DEV-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.GA-1', '1.0.0.DEV-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.DEV-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.GE-1', '1.0.1.DEV-1', check_suffix=True))
|
||||||
|
|
||||||
def test_check_update_rc(self):
|
def test_check_update_rc(self):
|
||||||
# RC
|
# RC
|
||||||
@@ -148,38 +151,38 @@ class ArchDataMapperTest(TestCase):
|
|||||||
|
|
||||||
def test_check_update_beta(self):
|
def test_check_update_beta(self):
|
||||||
# BETA
|
# BETA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.BETA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.BETA-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.BETA-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.BETA-2', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.BETA-2', '1.0.1.B-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.BETA-2', '1.0.1.B-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.1.0.B-3', '1.0.6.BETA-10'))
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.B-3', '1.0.6.BETA-10', check_suffix=True))
|
||||||
|
|
||||||
# ALPHA
|
# ALPHA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.ALPHA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.ALPHA-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.B-1', '1.0.1.ALPHA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.B-1', '1.0.1.ALPHA-1', check_suffix=True))
|
||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.DEV-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.BETA-1', '1.0.0.DEV-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.B-1', '1.0.1.DEV-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.B-1', '1.0.1.DEV-1', check_suffix=True))
|
||||||
|
|
||||||
def test_check_update_alpha(self):
|
def test_check_update_alpha(self):
|
||||||
# ALPHA
|
# ALPHA
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.0.ALPHA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.0.ALPHA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.0.ALFA-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.0.ALFA-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALFA-1', '1.0.0.ALPHA-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALFA-1', '1.0.0.ALPHA-2', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALPHA-2', '1.0.1.ALFA-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALPHA-2', '1.0.1.ALFA-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.1.0.ALFA-3', '1.0.6.ALFA-10'))
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.ALFA-3', '1.0.6.ALFA-10', check_suffix=True))
|
||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALFA-1', '1.0.0.DEV-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.ALFA-1', '1.0.0.DEV-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.1.DEV-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.ALPHA-1', '1.0.1.DEV-1', check_suffix=True))
|
||||||
|
|
||||||
def test_check_update_dev(self):
|
def test_check_update_dev(self):
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEV-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEV-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEVEL-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEVEL-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEVEL-1', '1.0.0.DEVELOPMENT-1'))
|
self.assertFalse(ArchDataMapper.check_update('1.0.0.DEVEL-1', '1.0.0.DEVELOPMENT-1', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEVEL-2'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.DEV-1', '1.0.0.DEVEL-2', check_suffix=True))
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.DEVEL-2', '1.0.1.DEV-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.DEVEL-2', '1.0.1.DEV-1', check_suffix=True))
|
||||||
self.assertFalse(ArchDataMapper.check_update('1.1.0.DEV-3', '1.0.6.DEVELOPMENT-10'))
|
self.assertFalse(ArchDataMapper.check_update('1.1.0.DEV-3', '1.0.6.DEVELOPMENT-10', check_suffix=True))
|
||||||
|
|
||||||
def test_check_update_unknown_suffix(self):
|
def test_check_update_unknown_suffix(self):
|
||||||
self.assertTrue(ArchDataMapper.check_update('1.0.0.BALL-1', '1.0.0.TAR-1'))
|
self.assertTrue(ArchDataMapper.check_update('1.0.0.BALL-1', '1.0.0.TAR-1'))
|
||||||
|
|||||||
23
tests/gems/arch/pacman_test.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from bauh.gems.arch import pacman
|
||||||
|
|
||||||
|
FILE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class PacmanTest(TestCase):
|
||||||
|
|
||||||
|
def test_list_ignored_packages(self):
|
||||||
|
ignored = pacman.list_ignored_packages(FILE_DIR + '/resources/pacman_ign_pkgs.conf')
|
||||||
|
|
||||||
|
self.assertIsNotNone(ignored)
|
||||||
|
self.assertEqual(2, len(ignored))
|
||||||
|
self.assertIn('google-chrome', ignored)
|
||||||
|
self.assertIn('firefox', ignored)
|
||||||
|
|
||||||
|
def test_list_ignored_packages__no_ignored_packages(self):
|
||||||
|
ignored = pacman.list_ignored_packages(FILE_DIR + '/resources/pacman.conf')
|
||||||
|
|
||||||
|
self.assertIsNotNone(ignored)
|
||||||
|
self.assertEqual(0, len(ignored))
|
||||||
98
tests/gems/arch/resources/pacman.conf
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#
|
||||||
|
# /etc/pacman.conf
|
||||||
|
#
|
||||||
|
# See the pacman.conf(5) manpage for option and repository directives
|
||||||
|
|
||||||
|
#
|
||||||
|
# GENERAL OPTIONS
|
||||||
|
#
|
||||||
|
[options]
|
||||||
|
# The following paths are commented out with their default values listed.
|
||||||
|
# If you wish to use different paths, uncomment and update the paths.
|
||||||
|
#RootDir = /
|
||||||
|
#DBPath = /var/lib/pacman/
|
||||||
|
CacheDir = /var/cache/pacman/pkg/
|
||||||
|
#LogFile = /var/log/pacman.log
|
||||||
|
#GPGDir = /etc/pacman.d/gnupg/
|
||||||
|
#HookDir = /etc/pacman.d/hooks/
|
||||||
|
HoldPkg = pacman glibc manjaro-system
|
||||||
|
# If upgrades are available for these packages they will be asked for first
|
||||||
|
SyncFirst = manjaro-system archlinux-keyring manjaro-keyring
|
||||||
|
#XferCommand = /usr/bin/curl -C - -f %u > %o
|
||||||
|
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
|
||||||
|
#CleanMethod = KeepInstalled
|
||||||
|
#UseDelta = 0.7
|
||||||
|
Architecture = auto
|
||||||
|
|
||||||
|
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
|
||||||
|
#IgnorePkg =
|
||||||
|
#IgnoreGroup =
|
||||||
|
|
||||||
|
#NoUpgrade =
|
||||||
|
#NoExtract =
|
||||||
|
|
||||||
|
# Misc options
|
||||||
|
#UseSyslog
|
||||||
|
#Color
|
||||||
|
#TotalDownload
|
||||||
|
# We cannot check disk space from within a chroot environment
|
||||||
|
CheckSpace
|
||||||
|
#VerbosePkgLists
|
||||||
|
|
||||||
|
# By default, pacman accepts packages signed by keys that its local keyring
|
||||||
|
# trusts (see pacman-key and its man page), as well as unsigned packages.
|
||||||
|
SigLevel = Required DatabaseOptional
|
||||||
|
LocalFileSigLevel = Optional
|
||||||
|
#RemoteFileSigLevel = Required
|
||||||
|
|
||||||
|
# NOTE: You must run `pacman-key --init` before first using pacman; the local
|
||||||
|
# keyring can then be populated with the keys of all official Manjaro Linux
|
||||||
|
# packagers with `pacman-key --populate archlinux manjaro`.
|
||||||
|
|
||||||
|
#
|
||||||
|
# REPOSITORIES
|
||||||
|
# - can be defined here or included from another file
|
||||||
|
# - pacman will search repositories in the order defined here
|
||||||
|
# - local/custom mirrors can be added here or in separate files
|
||||||
|
# - repositories listed first will take precedence when packages
|
||||||
|
# have identical names, regardless of version number
|
||||||
|
# - URLs will have $repo replaced by the name of the current repo
|
||||||
|
# - URLs will have $arch replaced by the name of the architecture
|
||||||
|
#
|
||||||
|
# Repository entries are of the format:
|
||||||
|
# [repo-name]
|
||||||
|
# Server = ServerName
|
||||||
|
# Include = IncludePath
|
||||||
|
#
|
||||||
|
# The header [repo-name] is crucial - it must be present and
|
||||||
|
# uncommented to enable the repo.
|
||||||
|
#
|
||||||
|
|
||||||
|
# The testing repositories are disabled by default. To enable, uncomment the
|
||||||
|
# repo name header and Include lines. You can add preferred servers immediately
|
||||||
|
# after the header, and they will be used before the default mirrors.
|
||||||
|
|
||||||
|
[core]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[community]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
# If you want to run 32 bit applications on your x86_64 system,
|
||||||
|
# enable the multilib repositories as required here.
|
||||||
|
|
||||||
|
[multilib]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
# An example of a custom package repository. See the pacman manpage for
|
||||||
|
# tips on creating your own repositories.
|
||||||
|
#[custom]
|
||||||
|
#SigLevel = Optional TrustAll
|
||||||
|
#Server = file:///home/custompkgs
|
||||||
101
tests/gems/arch/resources/pacman_ign_pkgs.conf
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#
|
||||||
|
# /etc/pacman.conf
|
||||||
|
#
|
||||||
|
# See the pacman.conf(5) manpage for option and repository directives
|
||||||
|
|
||||||
|
#
|
||||||
|
# GENERAL OPTIONS
|
||||||
|
#
|
||||||
|
[options]
|
||||||
|
# The following paths are commented out with their default values listed.
|
||||||
|
# If you wish to use different paths, uncomment and update the paths.
|
||||||
|
#RootDir = /
|
||||||
|
#DBPath = /var/lib/pacman/
|
||||||
|
CacheDir = /var/cache/pacman/pkg/
|
||||||
|
#LogFile = /var/log/pacman.log
|
||||||
|
#GPGDir = /etc/pacman.d/gnupg/
|
||||||
|
#HookDir = /etc/pacman.d/hooks/
|
||||||
|
HoldPkg = pacman glibc manjaro-system
|
||||||
|
# If upgrades are available for these packages they will be asked for first
|
||||||
|
SyncFirst = manjaro-system archlinux-keyring manjaro-keyring
|
||||||
|
#XferCommand = /usr/bin/curl -C - -f %u > %o
|
||||||
|
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
|
||||||
|
#CleanMethod = KeepInstalled
|
||||||
|
#UseDelta = 0.7
|
||||||
|
Architecture = auto
|
||||||
|
|
||||||
|
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
|
||||||
|
#IgnorePkg =
|
||||||
|
#IgnoreGroup =
|
||||||
|
|
||||||
|
#NoUpgrade =
|
||||||
|
#NoExtract =
|
||||||
|
|
||||||
|
# Misc options
|
||||||
|
#UseSyslog
|
||||||
|
#Color
|
||||||
|
#TotalDownload
|
||||||
|
# We cannot check disk space from within a chroot environment
|
||||||
|
CheckSpace
|
||||||
|
#VerbosePkgLists
|
||||||
|
|
||||||
|
# By default, pacman accepts packages signed by keys that its local keyring
|
||||||
|
# trusts (see pacman-key and its man page), as well as unsigned packages.
|
||||||
|
SigLevel = Required DatabaseOptional
|
||||||
|
LocalFileSigLevel = Optional
|
||||||
|
#RemoteFileSigLevel = Required
|
||||||
|
|
||||||
|
# NOTE: You must run `pacman-key --init` before first using pacman; the local
|
||||||
|
# keyring can then be populated with the keys of all official Manjaro Linux
|
||||||
|
# packagers with `pacman-key --populate archlinux manjaro`.
|
||||||
|
|
||||||
|
#
|
||||||
|
# REPOSITORIES
|
||||||
|
# - can be defined here or included from another file
|
||||||
|
# - pacman will search repositories in the order defined here
|
||||||
|
# - local/custom mirrors can be added here or in separate files
|
||||||
|
# - repositories listed first will take precedence when packages
|
||||||
|
# have identical names, regardless of version number
|
||||||
|
# - URLs will have $repo replaced by the name of the current repo
|
||||||
|
# - URLs will have $arch replaced by the name of the architecture
|
||||||
|
#
|
||||||
|
# Repository entries are of the format:
|
||||||
|
# [repo-name]
|
||||||
|
# Server = ServerName
|
||||||
|
# Include = IncludePath
|
||||||
|
#
|
||||||
|
# The header [repo-name] is crucial - it must be present and
|
||||||
|
# uncommented to enable the repo.
|
||||||
|
#
|
||||||
|
|
||||||
|
# The testing repositories are disabled by default. To enable, uncomment the
|
||||||
|
# repo name header and Include lines. You can add preferred servers immediately
|
||||||
|
# after the header, and they will be used before the default mirrors.
|
||||||
|
|
||||||
|
[core]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[community]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
# If you want to run 32 bit applications on your x86_64 system,
|
||||||
|
# enable the multilib repositories as required here.
|
||||||
|
|
||||||
|
[multilib]
|
||||||
|
SigLevel = PackageRequired
|
||||||
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
# An example of a custom package repository. See the pacman manpage for
|
||||||
|
# tips on creating your own repositories.
|
||||||
|
#[custom]
|
||||||
|
#SigLevel = Optional TrustAll
|
||||||
|
#Server = file:///home/custompkgs
|
||||||
|
ignorepkg=google-chrome
|
||||||
|
# ignorepkg=abc
|
||||||
|
ignorepkg = firefox
|
||||||
0
tests/gems/flatpak/__init__.py
Normal file
20
tests/gems/flatpak/worker_test.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from bauh.gems.flatpak.worker import FlatpakAsyncDataLoader
|
||||||
|
|
||||||
|
|
||||||
|
class FlatpakAsyncDataLoaderTest(TestCase):
|
||||||
|
|
||||||
|
def test_format_category(self):
|
||||||
|
self.assertEqual('irc client', FlatpakAsyncDataLoader.format_category('IRCClient'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('TextEditor'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('Text Editor'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('text editor'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('Text editor'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('text Editor'))
|
||||||
|
self.assertEqual('text editor', FlatpakAsyncDataLoader.format_category('textEditor'))
|
||||||
|
self.assertEqual('ide', FlatpakAsyncDataLoader.format_category('IDE'))
|
||||||
|
self.assertEqual('faster irc client', FlatpakAsyncDataLoader.format_category('Faster IRCClient'))
|
||||||
|
self.assertEqual('3d graphics', FlatpakAsyncDataLoader.format_category('3DGraphics'))
|
||||||
|
self.assertEqual('32d graphics', FlatpakAsyncDataLoader.format_category('32DGraphics'))
|
||||||
|
self.assertEqual('d32 graphics', FlatpakAsyncDataLoader.format_category('D32Graphics'))
|
||||||