[arch] improvement -> AUR: upgrade checking now considers modification dates as well

This commit is contained in:
Vinicius Moreira
2020-12-17 18:35:34 -03:00
parent 4540fc938e
commit cf17a91b83
32 changed files with 432 additions and 149 deletions

View File

@@ -5,14 +5,19 @@ 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.9.11] ## [0.9.11]
### New system requirements
- **python-dateutil**: better Python library for date handling (install the equivalent package for your Linux distribution before upgrading bauh)
### Improvements ### Improvements
- Arch - Arch
- AUR: upgrade checking now considers modification dates as well (needed because not all AUR packages follow versioning standards)
- the task responsible for generating a local AUR index is displayed on the initialization dialog - the task responsible for generating a local AUR index is displayed on the initialization dialog
### Fixes ### Fixes
- AppImage - AppImage
- some installed applications cannot be launched by their desktop entries (regression from **0.9.10**) [#155](https://github.com/vinifmor/bauh/issues/155). If any of your installed AppImages are affected by this issue, just reinstall them. - some installed applications cannot be launched by their desktop entries (regression from **0.9.10**) [#155](https://github.com/vinifmor/bauh/issues/155). If any of your installed AppImages are affected by this issue, just reinstall them.
## [0.9.10] 2020-12-11 ## [0.9.10] 2020-12-11
### Features ### Features
- Web - Web

View File

@@ -31,9 +31,10 @@ To contribute have a look at [CONTRIBUTING.md](https://github.com/vinifmor/bauh/
#### Debian-based distros #### Debian-based distros
- **python3.5** or above - **python3.5** or above
- **pip3** - **pip3**
- **python3-requests**
- **python-yaml** - **python-yaml**
- **python3-dateutil**
- **python3-pyqt5.qtsvg** - **python3-pyqt5.qtsvg**
- **python3-requests**
- **libqt5svg5** - **libqt5svg5**
- **qt5dxcb-plugin** - **qt5dxcb-plugin**
- **libappindicator3** (for the **tray mode** in GTK3 desktop environments) - **libappindicator3** (for the **tray mode** in GTK3 desktop environments)
@@ -42,6 +43,7 @@ To contribute have a look at [CONTRIBUTING.md](https://github.com/vinifmor/bauh/
#### Arch-based distros #### Arch-based distros
- **python** - **python**
- **python-dateutil**
- **python-requests** - **python-requests**
- **python-pip** - **python-pip**
- **python-pyqt5** - **python-pyqt5**

View File

@@ -19,11 +19,12 @@ class DiskCacheLoader:
""" """
pass pass
def fill(self, pkg: SoftwarePackage): def fill(self, pkg: SoftwarePackage, sync: bool = False):
""" """
fill cached data from the disk of a given package instance fill cached data from the disk of a given package instance
If a cache mapping was previously done, then data retrieved will be cached to memory as well. If a cache mapping was previously done, then data retrieved will be cached to memory as well.
:param pkg: :param pkg:
:param sync: if the package data must be filled synchronously
:return: :return:
""" """
pass pass

View File

@@ -4,7 +4,7 @@ import sys
import time import time
from io import StringIO from io import StringIO
from subprocess import PIPE from subprocess import PIPE
from typing import List, Tuple, Set, Dict from typing import List, Tuple, Set, Dict, Optional
# default environment variables for subprocesses. # default environment variables for subprocesses.
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
@@ -322,6 +322,11 @@ def check_enabled_services(*names: str) -> Dict[str, bool]:
return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s} return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s}
def execute(cmd: str, shell: bool = False) -> Tuple[int, str]: def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None) -> Tuple[int, str]:
p = subprocess.run(args=cmd.split(' ') if not shell else [cmd], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell) p = subprocess.run(args=cmd.split(' ') if not shell else [cmd],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=shell,
cwd=cwd)
return p.returncode, p.stdout.decode() return p.returncode, p.stdout.decode()

View File

@@ -1,4 +1,4 @@
import collections from datetime import datetime
def deep_update(source: dict, overrides: dict): def deep_update(source: dict, overrides: dict):
@@ -26,3 +26,7 @@ def size_to_byte(size: float, unit: str) -> int:
final_size = size * 1000000000000000 final_size = size * 1000000000000000
return int(final_size) return int(final_size)
def datetime_as_milis(date: datetime) -> int:
return int(round(date.timestamp() * 1000))

View File

@@ -14,6 +14,7 @@ from threading import Thread
from typing import List, Set, Type, Tuple, Dict, Iterable, Optional from typing import List, Set, Type, Tuple, Dict, Iterable, Optional
import requests import requests
from dateutil.parser import parse as parse_date
from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \
TransactionResult, SoftwareAction TransactionResult, SoftwareAction
@@ -30,6 +31,7 @@ from bauh.commons.category import CategoriesDownloader
from bauh.commons.config import save_config from bauh.commons.config import save_config
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess
from bauh.commons.util import datetime_as_milis
from bauh.commons.view_utils import new_select from bauh.commons.view_utils import new_select
from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \ from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \
gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \
@@ -40,7 +42,7 @@ from bauh.gems.arch.config import read_config, get_build_dir
from bauh.gems.arch.dependencies import DependenciesAnalyser from bauh.gems.arch.dependencies import DependenciesAnalyser
from bauh.gems.arch.download import MultithreadedDownloadService, ArchDownloadException from bauh.gems.arch.download import MultithreadedDownloadService, ArchDownloadException
from bauh.gems.arch.exceptions import PackageNotFoundException, PackageInHoldException from bauh.gems.arch.exceptions import PackageNotFoundException, PackageInHoldException
from bauh.gems.arch.mapper import ArchDataMapper from bauh.gems.arch.mapper import AURDataMapper
from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.model import ArchPackage
from bauh.gems.arch.output import TransactionStatusHandler from bauh.gems.arch.output import TransactionStatusHandler
from bauh.gems.arch.pacman import RE_DEP_OPERATORS from bauh.gems.arch.pacman import RE_DEP_OPERATORS
@@ -73,7 +75,7 @@ class TransactionContext:
missing_deps: List[Tuple[str, str]] = None, installed: Set[str] = None, removed: Dict[str, SoftwarePackage] = None, missing_deps: List[Tuple[str, str]] = None, installed: Set[str] = None, removed: Dict[str, SoftwarePackage] = None,
disk_loader: DiskCacheLoader = None, disk_cache_updater: Thread = None, disk_loader: DiskCacheLoader = None, disk_cache_updater: Thread = None,
new_pkg: bool = False, custom_pkgbuild_path: str = None, new_pkg: bool = False, custom_pkgbuild_path: str = None,
pkgs_to_build: Set[str] = None): pkgs_to_build: Set[str] = None, last_modified: Optional[int] = None):
self.name = name self.name = name
self.base = base self.base = base
self.maintainer = maintainer self.maintainer = maintainer
@@ -103,13 +105,14 @@ class TransactionContext:
self.custom_pkgbuild_path = custom_pkgbuild_path self.custom_pkgbuild_path = custom_pkgbuild_path
self.pkgs_to_build = pkgs_to_build self.pkgs_to_build = pkgs_to_build
self.previous_change_progress = change_progress self.previous_change_progress = change_progress
self.last_modified = last_modified
@classmethod @classmethod
def gen_context_from(cls, pkg: ArchPackage, arch_config: dict, root_password: str, handler: ProcessHandler) -> "TransactionContext": def gen_context_from(cls, pkg: ArchPackage, arch_config: dict, root_password: str, handler: ProcessHandler) -> "TransactionContext":
return cls(name=pkg.name, base=pkg.get_base_name(), maintainer=pkg.maintainer, repository=pkg.repository, return cls(name=pkg.name, base=pkg.get_base_name(), maintainer=pkg.maintainer, repository=pkg.repository,
arch_config=arch_config, watcher=handler.watcher, handler=handler, skip_opt_deps=True, arch_config=arch_config, watcher=handler.watcher, handler=handler, skip_opt_deps=True,
change_progress=True, root_password=root_password, dependency=False, change_progress=True, root_password=root_password, dependency=False,
installed=set(), removed={}, new_pkg=not pkg.installed) installed=set(), removed={}, new_pkg=not pkg.installed, last_modified=pkg.last_modified)
def get_base_name(self): def get_base_name(self):
return self.base if self.base else self.name return self.base if self.base else self.name
@@ -189,7 +192,7 @@ class ArchManager(SoftwareManager):
self.aur_cache = context.cache_factory.new() self.aur_cache = context.cache_factory.new()
# context.disk_loader_factory.map(ArchPackage, self.aur_cache) TODO # context.disk_loader_factory.map(ArchPackage, self.aur_cache) TODO
self.mapper = ArchDataMapper(http_client=context.http_client, i18n=context.i18n) self.aur_mapper = AURDataMapper(http_client=context.http_client, i18n=context.i18n, logger=context.logger)
self.i18n = context.i18n self.i18n = context.i18n
self.aur_client = AURClient(http_client=context.http_client, logger=context.logger, x86_64=context.is_system_x86_64()) self.aur_client = AURClient(http_client=context.http_client, logger=context.logger, x86_64=context.is_system_x86_64())
self.dcache_updater = None self.dcache_updater = None
@@ -334,7 +337,7 @@ class ArchManager(SoftwareManager):
pkg = installed_pkgs.get(apidata['Name']) pkg = installed_pkgs.get(apidata['Name'])
if not pkg: if not pkg:
pkg = self.mapper.map_api_data(apidata, None, self.categories) pkg = self.aur_mapper.map_api_data(apidata, None, self.categories)
pkg.downgrade_enabled = downgrade_enabled pkg.downgrade_enabled = downgrade_enabled
if pkg.installed: if pkg.installed:
@@ -342,7 +345,7 @@ class ArchManager(SoftwareManager):
else: else:
res.new.append(pkg) res.new.append(pkg)
Thread(target=self.mapper.fill_package_build, args=(pkg,), daemon=True).start() Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True).start()
def _search_in_repos_and_fill(self, words: str, disk_loader: DiskCacheLoader, read_installed: Thread, installed: List[ArchPackage], res: SearchResult): def _search_in_repos_and_fill(self, words: str, disk_loader: DiskCacheLoader, read_installed: Thread, installed: List[ArchPackage], res: SearchResult):
repo_search = pacman.search(words) repo_search = pacman.search(words)
@@ -383,7 +386,7 @@ class ArchManager(SoftwareManager):
read_installed.join() read_installed.join()
aur_installed = {p.name: p for p in installed if p.repository == 'aur'} aur_installed = {p.name: p for p in installed if p.repository == 'aur'}
downgrade_enabled = git.is_enabled() downgrade_enabled = git.is_installed()
for pkgdata in api_res['results']: for pkgdata in api_res['results']:
self._upgrade_search_result(pkgdata, aur_installed, downgrade_enabled, res, disk_loader) self._upgrade_search_result(pkgdata, aur_installed, downgrade_enabled, res, disk_loader)
@@ -407,7 +410,7 @@ class ArchManager(SoftwareManager):
if pkgsinfo: if pkgsinfo:
read_installed.join() read_installed.join()
aur_installed = {p.name: p for p in installed if p.repository == 'aur'} aur_installed = {p.name: p for p in installed if p.repository == 'aur'}
downgrade_enabled = git.is_enabled() downgrade_enabled = git.is_installed()
for pkgdata in pkgsinfo: for pkgdata in pkgsinfo:
self._upgrade_search_result(pkgdata, aur_installed, downgrade_enabled, res, disk_loader) self._upgrade_search_result(pkgdata, aur_installed, downgrade_enabled, res, disk_loader)
@@ -452,7 +455,7 @@ class ArchManager(SoftwareManager):
def _fill_aur_pkgs(self, aur_pkgs: dict, output: List[ArchPackage], disk_loader: DiskCacheLoader, internet_available: bool, def _fill_aur_pkgs(self, aur_pkgs: dict, output: List[ArchPackage], disk_loader: DiskCacheLoader, internet_available: bool,
arch_config: dict): arch_config: dict):
downgrade_enabled = git.is_enabled() downgrade_enabled = git.is_installed()
if internet_available: if internet_available:
try: try:
@@ -461,14 +464,18 @@ class ArchManager(SoftwareManager):
if pkgsinfo: if pkgsinfo:
editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None
for pkgdata in pkgsinfo: for pkgdata in pkgsinfo:
pkg = self.mapper.map_api_data(pkgdata, aur_pkgs, self.categories) pkg = self.aur_mapper.map_api_data(pkgdata, aur_pkgs, self.categories)
pkg.downgrade_enabled = downgrade_enabled pkg.downgrade_enabled = downgrade_enabled
pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None
if pkg.installed:
if disk_loader: if disk_loader:
disk_loader.fill(pkg) disk_loader.fill(pkg, sync=True)
pkg.status = PackageStatus.READY
pkg.update = self._check_aur_package_update(pkg=pkg,
installed_data=aur_pkgs.get(pkg.name, {}),
api_data=pkgdata)
pkg.status = PackageStatus.READY
output.append(pkg) output.append(pkg)
return return
@@ -489,10 +496,24 @@ class ArchManager(SoftwareManager):
if disk_loader: if disk_loader:
disk_loader.fill(pkg) disk_loader.fill(pkg)
pkg.status = PackageStatus.READY
pkg.status = PackageStatus.READY
output.append(pkg) output.append(pkg)
def _check_aur_package_update(self, pkg: ArchPackage, installed_data: dict, api_data: dict) -> bool:
if pkg.last_modified is None: # if last_modified is not available, then the install_date will be used instead
install_date = installed_data.get('install_date')
if install_date:
try:
pkg.install_date = datetime_as_milis(parse_date(install_date))
except ValueError:
self.logger.error("Could not parse 'install_date' ({}) from AUR package '{}'".format(install_date, pkg.name))
else:
self.logger.error("AUR package '{}' install_date was not retrieved".format(pkg.name))
return self.aur_mapper.check_update(pkg=pkg, last_modified=api_data['LastModified'])
def _fill_repo_updates(self, updates: dict): def _fill_repo_updates(self, updates: dict):
updates.update(pacman.list_repository_updates()) updates.update(pacman.list_repository_updates())
@@ -620,18 +641,21 @@ class ArchManager(SoftwareManager):
context.project_dir = clone_path context.project_dir = clone_path
srcinfo_path = '{}/.SRCINFO'.format(clone_path) srcinfo_path = '{}/.SRCINFO'.format(clone_path)
commits = run_cmd("git log", cwd=clone_path) logs = git.log_shas_and_timestamps(clone_path)
context.watcher.change_progress(40) context.watcher.change_progress(40)
if commits: if not logs:
commit_list = re.findall(r'commit (.+)\n', commits) context.watcher.show_message(title=self.i18n['arch.downgrade.error'],
if commit_list: body=self.i18n['arch.downgrade.impossible'].format(
if len(commit_list) > 1: context.name),
srcfields = {'pkgver', 'pkgrel'} type_=MessageType.ERROR)
return False
commit_found = None srcfields = {'pkgver', 'pkgrel', 'epoch'}
for idx in range(1, len(commit_list)):
commit = commit_list[idx] commit_found, commit_date = None, None
for idx in range(1, len(logs)):
commit, date = logs[idx][0], logs[idx][1]
with open(srcinfo_path) as f: with open(srcinfo_path) as f:
pkgsrc = aur.map_srcinfo(string=f.read(), pkgname=context.name, fields=srcfields) pkgsrc = aur.map_srcinfo(string=f.read(), pkgname=context.name, fields=srcfields)
@@ -640,9 +664,16 @@ class ArchManager(SoftwareManager):
context.handler.watcher.print('Could not downgrade anymore. Aborting...') context.handler.watcher.print('Could not downgrade anymore. Aborting...')
return False return False
if '{}-{}'.format(pkgsrc.get('pkgver'), pkgsrc.get('pkgrel')) == context.get_version(): epoch, version, release = pkgsrc.get('epoch'), pkgsrc.get('pkgver'), pkgsrc.get('pkgrel')
if epoch:
current_version = '{}:{}-{}'.format(epoch, version, release)
else:
current_version = '{}-{}'.format(version, release)
if current_version == context.get_version():
# current version found # current version found
commit_found = commit commit_found, commit_date = commit, date
elif commit_found: elif commit_found:
context.watcher.change_substatus(self.i18n['arch.downgrade.version_found']) context.watcher.change_substatus(self.i18n['arch.downgrade.version_found'])
checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_path) checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_path)
@@ -658,18 +689,8 @@ class ArchManager(SoftwareManager):
break break
context.watcher.change_substatus(self.i18n['arch.downgrade.install_older']) context.watcher.change_substatus(self.i18n['arch.downgrade.install_older'])
context.last_modified = commit_date
return self._build(context) return self._build(context)
else:
context.watcher.show_message(title=self.i18n['arch.downgrade.error'],
body=self.i18n['arch.downgrade.impossible'].format(context.name),
type_=MessageType.ERROR)
return False
context.watcher.show_message(title=self.i18n['error'],
body=self.i18n['arch.downgrade.no_commits'],
type_=MessageType.ERROR)
return False
finally: finally:
if os.path.exists(context.build_dir) and context.config['aur_remove_build_dir']: if os.path.exists(context.build_dir) and context.config['aur_remove_build_dir']:
context.handler.handle(SystemProcess(subproc=new_subprocess(['rm', '-rf', context.build_dir]))) context.handler.handle(SystemProcess(subproc=new_subprocess(['rm', '-rf', context.build_dir])))
@@ -1071,8 +1092,24 @@ class ArchManager(SoftwareManager):
if aur_pkgs: if aur_pkgs:
watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs'])) watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs']))
self.logger.info("Retrieving the 'last_modified' field for each package to upgrade")
pkgs_api_data = self.aur_client.get_info({p.name for p in aur_pkgs})
if not pkgs_api_data:
self.logger.warning("Could not retrieve the 'last_modified' fields from the AUR API during the upgrade process")
for pkg in aur_pkgs: for pkg in aur_pkgs:
watcher.change_substatus("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], pkg.name, pkg.version)) watcher.change_substatus("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], pkg.name, pkg.version))
if pkgs_api_data:
apidata = [p for p in pkgs_api_data if p.get('Name') == pkg.name]
if not apidata:
self.logger.warning("AUR API data from package '{}' could not be found".format(pkg.name))
else:
self.aur_mapper.fill_last_modified(pkg=pkg, api_data=apidata[0])
context = TransactionContext.gen_context_from(pkg=pkg, arch_config=arch_config, context = TransactionContext.gen_context_from(pkg=pkg, arch_config=arch_config,
root_password=root_password, handler=handler) root_password=root_password, handler=handler)
context.change_progress = False context.change_progress = False
@@ -1329,11 +1366,15 @@ class ArchManager(SoftwareManager):
def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict: def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict:
if pkg.installed: if pkg.installed:
t = Thread(target=self.mapper.fill_package_build, args=(pkg,), daemon=True) t = Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True)
t.start() t.start()
info = pacman.get_info_dict(pkg.name) info = pacman.get_info_dict(pkg.name)
if pkg.last_modified:
info['last_modified'] = self._parse_timestamp(ts=pkg.last_modified,
error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified))
t.join() t.join()
if pkg.pkgbuild: if pkg.pkgbuild:
@@ -1352,11 +1393,17 @@ class ArchManager(SoftwareManager):
'05_votes': pkg.votes, '05_votes': pkg.votes,
'06_package_base': pkg.package_base, '06_package_base': pkg.package_base,
'07_maintainer': pkg.maintainer, '07_maintainer': pkg.maintainer,
'08_first_submitted': pkg.first_submitted,
'09_last_modified': pkg.last_modified,
'10_url': pkg.url_download '10_url': pkg.url_download
} }
if pkg.first_submitted:
info['08_first_submitted'] = self._parse_timestamp(ts=pkg.first_submitted,
error_msg="Could not parse AUR package '{}' 'first_submitted' field".format(pkg.name, pkg.first_submitted))
if pkg.last_modified:
info['09_last_modified'] = self._parse_timestamp(ts=pkg.last_modified,
error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified))
srcinfo = self.aur_client.get_src_info(pkg.name) srcinfo = self.aur_client.get_src_info(pkg.name)
if srcinfo: if srcinfo:
@@ -1383,6 +1430,14 @@ class ArchManager(SoftwareManager):
return info return info
def _parse_timestamp(self, ts: int, error_msg: str) -> datetime:
if ts:
try:
return datetime.fromtimestamp(ts)
except ValueError:
if error_msg:
self.logger.warning(error_msg)
def _get_info_repo_pkg(self, pkg: ArchPackage) -> dict: def _get_info_repo_pkg(self, pkg: ArchPackage) -> dict:
info = pacman.get_info_dict(pkg.name, remote=not pkg.installed) info = pacman.get_info_dict(pkg.name, remote=not pkg.installed)
if pkg.installed: if pkg.installed:
@@ -2205,6 +2260,7 @@ class ArchManager(SoftwareManager):
cache_map = {context.name: ArchPackage(name=context.name, cache_map = {context.name: ArchPackage(name=context.name,
repository=context.repository, repository=context.repository,
maintainer=pkg_maintainer, maintainer=pkg_maintainer,
last_modified=context.last_modified,
categories=self.categories.get(context.name))} categories=self.categories.get(context.name))}
if context.missing_deps: if context.missing_deps:
aur_deps = {dep[0] for dep in context.missing_deps if dep[1] == 'aur'} aur_deps = {dep[0] for dep in context.missing_deps if dep[1] == 'aur'}
@@ -2344,7 +2400,7 @@ class ArchManager(SoftwareManager):
watcher.change_substatus(self.i18n['arch.makepkg.optimizing']) watcher.change_substatus(self.i18n['arch.makepkg.optimizing'])
ArchCompilationOptimizer(arch_config=arch_config, i18n=self.i18n, logger=self.context.logger).optimize() ArchCompilationOptimizer(arch_config=arch_config, i18n=self.i18n, logger=self.context.logger).optimize()
def install(self, pkg: ArchPackage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult: def install(self, pkg: ArchPackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult:
self.aur_client.clean_caches() self.aur_client.clean_caches()
if not self._check_action_allowed(pkg, watcher): if not self._check_action_allowed(pkg, watcher):
@@ -2550,7 +2606,7 @@ class ArchManager(SoftwareManager):
if not self._is_wget_available(): if not self._is_wget_available():
warnings.append(self.i18n['arch.warning.disabled'].format(bold('wget'))) warnings.append(self.i18n['arch.warning.disabled'].format(bold('wget')))
if not git.is_enabled(): if not git.is_installed():
warnings.append(self.i18n['arch.warning.git'].format(bold('git'))) warnings.append(self.i18n['arch.warning.git'].format(bold('git')))
return warnings return warnings
@@ -2580,7 +2636,7 @@ class ArchManager(SoftwareManager):
res = [] res = []
for pkg in api_res: for pkg in api_res:
if pkg.get('Name') in suggestions: if pkg.get('Name') in suggestions:
res.append(PackageSuggestion(self.mapper.map_api_data(pkg, {}, self.categories), suggestions[pkg['Name']])) res.append(PackageSuggestion(self.aur_mapper.map_api_data(pkg, {}, self.categories), suggestions[pkg['Name']]))
self.logger.info("Mapped {} suggestions".format(len(suggestions))) self.logger.info("Mapped {} suggestions".format(len(suggestions)))
return res return res

View File

@@ -1,10 +1,11 @@
from datetime import datetime from datetime import datetime
from typing import List from typing import List, Tuple, Optional
from bauh.commons import system
from bauh.commons.system import new_subprocess from bauh.commons.system import new_subprocess
def is_enabled() -> bool: def is_installed() -> bool:
try: try:
new_subprocess(['git', '--version']) new_subprocess(['git', '--version'])
return True return True
@@ -27,3 +28,18 @@ def list_commits(proj_dir:str) -> List[dict]:
commit = {} commit = {}
return commits return commits
def log_shas_and_timestamps(repo_path: str) -> Optional[List[Tuple[str, int]]]:
code, output = system.execute(cmd='git log --format="%H %at"', shell=True, cwd=repo_path)
if code == 0:
logs = []
for line in output.strip().split('\n'):
line_strip = line.strip()
if line_strip:
line_split = line_strip.split(' ')
logs.append((line_split[0].strip(), int(line_split[1].strip())))
return logs

View File

@@ -1,6 +1,7 @@
import logging
import os import os
import traceback import traceback
from datetime import datetime from typing import Optional
from colorama import Fore from colorama import Fore
from pkg_resources import parse_version from pkg_resources import parse_version
@@ -13,41 +14,56 @@ from bauh.view.util.translation import I18n
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}'
class ArchDataMapper: class AURDataMapper:
def __init__(self, http_client: HttpClient, i18n: I18n): def __init__(self, http_client: HttpClient, i18n: I18n, logger: logging.Logger):
self.http_client = http_client self.http_client = http_client
self.i18n = i18n self.i18n = i18n
self.logger = logger
def fill_api_data(self, pkg: ArchPackage, package: dict, fill_version: bool = True): def fill_last_modified(self, pkg: ArchPackage, api_data: dict):
last_modified = api_data.get('LastModified')
if last_modified is not None and isinstance(last_modified, int):
pkg.last_modified = last_modified
self.logger.info("'last_modified' field ({}) set to package '{}'".format(last_modified, pkg.name))
else:
self.logger.warning("Could not set the 'last_modified' field ({}) to package '{}'".format(last_modified, pkg.name))
version = package.get('Version') def fill_api_data(self, pkg: ArchPackage, api_data: dict, fill_version: bool = True):
pkg.id = api_data.get('ID')
if not pkg.name:
pkg.name = api_data.get('Name')
if not pkg.description:
pkg.description = api_data.get('Description')
pkg.package_base = api_data.get('PackageBase')
pkg.popularity = api_data.get('Popularity')
pkg.votes = api_data.get('NumVotes')
pkg.maintainer = api_data.get('Maintainer')
pkg.url_download = URL_PKG_DOWNLOAD.format(api_data['URLPath']) if api_data.get('URLPath') else None
if api_data['FirstSubmitted'] and isinstance(api_data['FirstSubmitted'], int):
pkg.first_submitted = api_data['FirstSubmitted']
if not pkg.installed:
self.fill_last_modified(pkg=pkg, api_data=api_data)
version = api_data.get('Version')
if version: if version:
version = version.split(':') version = version.split(':')
version = version[0] if len(version) == 1 else version[1] version = version[0] if len(version) == 1 else version[1]
pkg.id = package.get('ID')
pkg.name = package.get('Name')
if fill_version: if fill_version:
pkg.version = version pkg.version = version
pkg.latest_version = version pkg.latest_version = version
pkg.description = package.get('Description')
pkg.package_base = package.get('PackageBase')
pkg.popularity = package.get('Popularity')
pkg.votes = package.get('NumVotes')
pkg.maintainer = package.get('Maintainer')
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.last_modified = datetime.fromtimestamp(package['LastModified']) if package.get('LastModified') else None
pkg.update = self.check_update(pkg.version, pkg.latest_version)
@staticmethod @staticmethod
def check_update(version: str, latest_version: str) -> bool: def check_version_update(version: str, latest_version: str) -> bool:
if version and latest_version: if version and latest_version and version != latest_version:
try: try:
ver_epoch, latest_epoch = version.split(':'), latest_version.split(':') ver_epoch, latest_epoch = version.split(':'), latest_version.split(':')
@@ -82,8 +98,8 @@ 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, categories: dict) -> ArchPackage: def map_api_data(self, apidata: dict, pkgs_installed: Optional[dict], categories: dict) -> ArchPackage:
data = installed.get(apidata.get('Name')) if installed else None data = pkgs_installed.get(apidata.get('Name')) if pkgs_installed else None
app = ArchPackage(name=apidata.get('Name'), installed=bool(data), repository='aur', i18n=self.i18n) app = ArchPackage(name=apidata.get('Name'), installed=bool(data), repository='aur', i18n=self.i18n)
app.status = PackageStatus.LOADING_DATA app.status = PackageStatus.LOADING_DATA
@@ -95,5 +111,26 @@ class ArchDataMapper:
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
def check_update(self, pkg: ArchPackage, last_modified: Optional[int]) -> bool:
valid_last_modified = last_modified is not None and isinstance(last_modified, int)
if not valid_last_modified:
self.logger.warning("'last_modified' timestamp informed for package '{}' is invalid: {}".format(pkg.name, valid_last_modified))
pkg_last_modified_ts = pkg.last_modified if pkg.last_modified is not None else pkg.install_date
if pkg.last_modified is None:
self.logger.warning("AUR package '{}' has no 'last_modified' field set.".format(pkg.name))
if pkg.install_date is None:
self.logger.warning("AUR package '{}' has no 'install_date' field set".format(pkg.name))
self.logger.warning("Update checking for AUR package '{}' will only consider version strings".format(pkg.name))
else:
self.logger.warning("AUR package {} 'install_date' field will be used for update checking".format(pkg.name))
if pkg_last_modified_ts is not None and valid_last_modified and pkg_last_modified_ts < last_modified:
return True
else:
return self.check_version_update(pkg.version, pkg.latest_version)

View File

@@ -1,12 +1,11 @@
import datetime from typing import List, Set, Optional
from typing import List, Set
from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction
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, ARCH_CACHE_PATH
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
CACHED_ATTRS = {'command', 'icon_path', 'repository', 'maintainer', 'desktop_entry', 'categories'} CACHED_ATTRS = {'command', 'icon_path', 'repository', 'maintainer', 'desktop_entry', 'categories', 'last_modified'}
ACTIONS_AUR_ENABLE_PKGBUILD_EDITION = [CustomSoftwareAction(i18n_label_key='arch.action.enable_pkgbuild_edition', ACTIONS_AUR_ENABLE_PKGBUILD_EDITION = [CustomSoftwareAction(i18n_label_key='arch.action.enable_pkgbuild_edition',
i18n_status_key='arch.action.enable_pkgbuild_edition.status', i18n_status_key='arch.action.enable_pkgbuild_edition.status',
@@ -27,11 +26,11 @@ class ArchPackage(SoftwarePackage):
def __init__(self, name: str = None, version: str = None, latest_version: str = None, description: str = None, def __init__(self, name: str = None, version: str = None, latest_version: str = None, description: str = None,
package_base: str = None, votes: int = None, popularity: float = None, package_base: str = None, votes: int = None, popularity: float = None,
first_submitted: datetime.datetime = None, last_modified: datetime.datetime = None, first_submitted: Optional[int] = None, last_modified: Optional[int] = None,
maintainer: str = None, url_download: str = None, pkgbuild: str = None, repository: str = None, maintainer: str = None, url_download: str = None, pkgbuild: str = None, repository: str = None,
desktop_entry: str = None, installed: bool = False, srcinfo: dict = None, dependencies: Set[str] = None, desktop_entry: str = None, installed: bool = False, srcinfo: dict = None, dependencies: Set[str] = None,
categories: List[str] = None, i18n: I18n = None, update_ignored: bool = False, arch: str = None, categories: List[str] = None, i18n: I18n = None, update_ignored: bool = False, arch: str = None,
pkgbuild_editable: bool = None): pkgbuild_editable: bool = None, install_date: Optional[int] = None):
super(ArchPackage, self).__init__(name=name, version=version, latest_version=latest_version, description=description, super(ArchPackage, self).__init__(name=name, version=version, latest_version=latest_version, description=description,
installed=installed, categories=categories) installed=installed, categories=categories)
@@ -55,6 +54,7 @@ class ArchPackage(SoftwarePackage):
self.update_ignored = update_ignored self.update_ignored = update_ignored
self.view_name = name # name displayed on the view self.view_name = name # name displayed on the view
self.pkgbuild_editable = pkgbuild_editable # if the PKGBUILD can be edited by the user (only for AUR) self.pkgbuild_editable = pkgbuild_editable # if the PKGBUILD can be edited by the user (only for AUR)
self.install_date = install_date
@staticmethod @staticmethod
def disk_cache_path(pkgname: str): def disk_cache_path(pkgname: str):

View File

@@ -15,7 +15,7 @@ RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-\.]+\s+\[\w+\]')
RE_OPTDEPS = re.compile(r'[\w\._\-]+\s*:') RE_OPTDEPS = re.compile(r'[\w\._\-]+\s*:')
RE_DEP_NOTFOUND = re.compile(r'error:.+\'(.+)\'') RE_DEP_NOTFOUND = re.compile(r'error:.+\'(.+)\'')
RE_DEP_OPERATORS = re.compile(r'[<>=]') RE_DEP_OPERATORS = re.compile(r'[<>=]')
RE_INSTALLED_FIELDS = re.compile(r'(Name|Description|Version|Validated By)\s*:\s*(.+)') RE_INSTALLED_FIELDS = re.compile(r'(Name|Description|Version|Install Date|Validated By)\s*:\s*(.+)')
RE_INSTALLED_SIZE = re.compile(r'Installed Size\s*:\s*([0-9,\.]+)\s(\w+)\n?', re.IGNORECASE) RE_INSTALLED_SIZE = re.compile(r'Installed Size\s*:\s*([0-9,\.]+)\s(\w+)\n?', re.IGNORECASE)
RE_DOWNLOAD_SIZE = re.compile(r'Download Size\s*:\s*([0-9,\.]+)\s(\w+)\n?', re.IGNORECASE) RE_DOWNLOAD_SIZE = re.compile(r'Download Size\s*:\s*([0-9,\.]+)\s(\w+)\n?', re.IGNORECASE)
RE_UPDATE_REQUIRED_FIELDS = re.compile(r'(\bProvides\b|\bInstalled Size\b|\bConflicts With\b)\s*:\s(.+)\n') RE_UPDATE_REQUIRED_FIELDS = re.compile(r'(\bProvides\b|\bInstalled Size\b|\bConflicts With\b)\s*:\s(.+)\n')
@@ -114,9 +114,11 @@ def map_installed(names: Iterable[str] = None) -> dict: # returns a dict with w
if field_tuple[0].startswith('N'): if field_tuple[0].startswith('N'):
current_pkg['name'] = field_tuple[1].strip() current_pkg['name'] = field_tuple[1].strip()
elif field_tuple[0].startswith('Ve'): elif field_tuple[0].startswith('Ve'):
current_pkg['version'] = field_tuple[1].split(':')[-1].strip() current_pkg['version'] = field_tuple[1].strip()
elif field_tuple[0].startswith('D'): elif field_tuple[0].startswith('D'):
current_pkg['description'] = field_tuple[1].strip() current_pkg['description'] = field_tuple[1].strip()
elif field_tuple[0].startswith('I'):
current_pkg['install_date'] = field_tuple[1].strip()
elif field_tuple[0].startswith('Va'): elif field_tuple[0].startswith('Va'):
if field_tuple[1].strip().lower() == 'none': if field_tuple[1].strip().lower() == 'none':
pkgs['not_signed'][current_pkg['name']] = current_pkg pkgs['not_signed'][current_pkg['name']] = current_pkg
@@ -449,8 +451,7 @@ def search(words: str) -> Dict[str, dict]:
data_split = repo_split[1].split(' ') data_split = repo_split[1].split(' ')
current['name'] = data_split[0] current['name'] = data_split[0]
version = data_split[1].split(':') current['version'] = data_split[1]
current['version'] = version[0] if len(version) == 1 else version[1]
return found return found

View File

@@ -144,6 +144,7 @@ arch.info.install script=script d'instal·lació
arch.info.install script.no=no arch.info.install script.no=no
arch.info.installed files=arxius instal·lats arch.info.installed files=arxius instal·lats
arch.info.installed size=mida instal·lat arch.info.installed size=mida instal·lat
arch.info.last_modified=última modificació
arch.info.license=llicència arch.info.license=llicència
arch.info.licenses=llicències arch.info.licenses=llicències
arch.info.licenses.custom=costum arch.info.licenses.custom=costum

View File

@@ -144,6 +144,7 @@ arch.info.install script=Installationsskript
arch.info.install script.no=Nein arch.info.install script.no=Nein
arch.info.installed files=installierten Dateien arch.info.installed files=installierten Dateien
arch.info.installed size=installierte Größe arch.info.installed size=installierte Größe
arch.info.last_modified=zuletzt bearbeitet
arch.info.license=Lizenz arch.info.license=Lizenz
arch.info.licenses=Lizenzen arch.info.licenses=Lizenzen
arch.info.licenses.custom=Benutzerdefiniert arch.info.licenses.custom=Benutzerdefiniert

View File

@@ -144,6 +144,7 @@ arch.info.install script=install script
arch.info.install script.no=no arch.info.install script.no=no
arch.info.installed files=installed files arch.info.installed files=installed files
arch.info.installed size=installed size arch.info.installed size=installed size
arch.info.last_modified=last modified
arch.info.license=license arch.info.license=license
arch.info.licenses=licenses arch.info.licenses=licenses
arch.info.licenses.custom=custom arch.info.licenses.custom=custom

View File

@@ -144,6 +144,7 @@ arch.info.install script=script de instalación
arch.info.install script.no=ninguno arch.info.install script.no=ninguno
arch.info.installed files=archivos instalados arch.info.installed files=archivos instalados
arch.info.installed size=tamaño da instalación arch.info.installed size=tamaño da instalación
arch.info.last_modified=última modificación
arch.info.license=licencia arch.info.license=licencia
arch.info.licenses=licencias arch.info.licenses=licencias
arch.info.licenses.custom=personalizada arch.info.licenses.custom=personalizada

View File

@@ -144,6 +144,7 @@ arch.info.install script=script d'installation
arch.info.install script.no=non arch.info.install script.no=non
arch.info.installed files=fichiers installés arch.info.installed files=fichiers installés
arch.info.installed size=taille installée arch.info.installed size=taille installée
arch.info.last_modified=dernière modification
arch.info.license=licence arch.info.license=licence
arch.info.licenses=licences arch.info.licenses=licences
arch.info.licenses.custom=personnalisé arch.info.licenses.custom=personnalisé

View File

@@ -144,6 +144,7 @@ arch.info.install script=script di installazione
arch.info.install script.no=nessuno arch.info.install script.no=nessuno
arch.info.installed files=file installati arch.info.installed files=file installati
arch.info.installed size=taglia installata arch.info.installed size=taglia installata
arch.info.last_modified=Ultima modifica
arch.info.license=licenza arch.info.license=licenza
arch.info.licenses=licenze arch.info.licenses=licenze
arch.info.licenses.custom=costume arch.info.licenses.custom=costume

View File

@@ -143,6 +143,7 @@ arch.info.install script=script de instalação
arch.info.install script.no=nenhum arch.info.install script.no=nenhum
arch.info.installed files=arquivos instalados arch.info.installed files=arquivos instalados
arch.info.installed size=tamanho da instalação arch.info.installed size=tamanho da instalação
arch.info.last_modified=última modificação
arch.info.license=licença arch.info.license=licença
arch.info.licenses=licenças arch.info.licenses=licenças
arch.info.licenses.custom=personalizada arch.info.licenses.custom=personalizada

View File

@@ -144,6 +144,7 @@ arch.info.install script=Скрипт установки
arch.info.install script.no=Нет arch.info.install script.no=Нет
arch.info.installed files=установленные файлы arch.info.installed files=установленные файлы
arch.info.installed size=Размер установки arch.info.installed size=Размер установки
arch.info.last_modified=Последнее изменение
arch.info.license=Лицензия arch.info.license=Лицензия
arch.info.licenses=Лицензии arch.info.licenses=Лицензии
arch.info.licenses.custom=таможенная arch.info.licenses.custom=таможенная

View File

@@ -144,6 +144,7 @@ arch.info.install script=kurulum betiği
arch.info.install script.no=hayır arch.info.install script.no=hayır
arch.info.installed files=yüklü dosyalar arch.info.installed files=yüklü dosyalar
arch.info.installed size=kurulum boyutu arch.info.installed size=kurulum boyutu
arch.info.last_modified=son değişiklik
arch.info.license=lisans arch.info.license=lisans
arch.info.licenses=lisanslar arch.info.licenses=lisanslar
arch.info.licenses.custom=özel arch.info.licenses.custom=özel

View File

@@ -390,6 +390,7 @@ style=estil
success=success success=success
summary=resum summary=resum
task.download_categories=Download de categories [ {} ] task.download_categories=Download de categories [ {} ]
task.waiting_task=Waiting for {}
type=tipus type=tipus
uninstall=desinstal·la uninstall=desinstal·la
uninstalled=desinstal·lada uninstalled=desinstal·lada

View File

@@ -390,6 +390,7 @@ style=Stil
success=success success=success
summary=Zusammenfassung summary=Zusammenfassung
task.download_categories=Downloading categories [ {} ] task.download_categories=Downloading categories [ {} ]
task.waiting_task=Waiting for {}
type=Typ type=Typ
uninstall=Deinstallation uninstall=Deinstallation
uninstalled=deinstalliert uninstalled=deinstalliert

View File

@@ -391,6 +391,7 @@ style=style
success=success success=success
summary=summary summary=summary
task.download_categories=Downloading categories [ {} ] task.download_categories=Downloading categories [ {} ]
task.waiting_task=Waiting for {}
type=type type=type
uninstall=uninstall uninstall=uninstall
uninstalled=uninstalled uninstalled=uninstalled

View File

@@ -393,6 +393,7 @@ style=estilo
success=éxito success=éxito
summary=resumen summary=resumen
task.download_categories=Descargando categorías [ {} ] task.download_categories=Descargando categorías [ {} ]
task.waiting_task=Esperando por {}
type=tipo type=tipo
uninstall=desinstalar uninstall=desinstalar
uninstalled=desinstalada uninstalled=desinstalada

View File

@@ -387,6 +387,7 @@ style=style
success=succès success=succès
summary=résumé summary=résumé
task.download_categories=Téléchargement des catégories [ {} ] task.download_categories=Téléchargement des catégories [ {} ]
task.waiting_task=Waiting for {}
type=type type=type
uninstall=désinstaller uninstall=désinstaller
uninstalled=désinstallé uninstalled=désinstallé

View File

@@ -393,6 +393,7 @@ style=stile
success=success success=success
summary=riepilogo summary=riepilogo
task.download_categories=Download delle categorie [ {} ] task.download_categories=Download delle categorie [ {} ]
task.waiting_task=Waiting for {}
type=tipe type=tipe
uninstall=Disinstalla uninstall=Disinstalla
uninstalled=disinstallato uninstalled=disinstallato

View File

@@ -391,6 +391,7 @@ style=estilo
success=sucesso success=sucesso
summary=resumo summary=resumo
task.download_categories=Baixando categorias [ {} ] task.download_categories=Baixando categorias [ {} ]
task.waiting_task=Aguardando {}
type=tipo type=tipo
uninstall=desinstalar uninstall=desinstalar
uninstalled=desinstalado uninstalled=desinstalado

View File

@@ -390,6 +390,7 @@ style=Стиль
success=success success=success
summary=Суммарно summary=Суммарно
task.download_categories=Downloading categories [ {} ] task.download_categories=Downloading categories [ {} ]
task.waiting_task=Waiting for {}
type=Тип type=Тип
uninstall=Деинсталляция uninstall=Деинсталляция
uninstalled=Деинсталлировано uninstalled=Деинсталлировано

View File

@@ -390,6 +390,7 @@ style=Görünüm biçimi
success=başarılı success=başarılı
summary=özet summary=özet
task.download_categories=Kategoriler indiriliyor [ {} ] task.download_categories=Kategoriler indiriliyor [ {} ]
task.waiting_task=Waiting for {}
type=tür type=tür
uninstall=kaldır uninstall=kaldır
uninstalled=kaldırıldı uninstalled=kaldırıldı

View File

@@ -23,13 +23,17 @@ class AsyncDiskCacheLoader(Thread, DiskCacheLoader):
self.logger = logger self.logger = logger
self.processed = 0 self.processed = 0
def fill(self, pkg: SoftwarePackage): def fill(self, pkg: SoftwarePackage, sync: bool = False):
""" """
Adds a package which data must be read from the disk to a queue. Adds a package which data must be read from the disk to a queue (if not sync)
:param pkg: :param pkg:
:param sync:
:return: :return:
""" """
if pkg and pkg.supports_disk_cache(): if pkg and pkg.supports_disk_cache():
if sync:
self._fill_cached_data(pkg)
else:
self.pkgs.append(pkg) self.pkgs.append(pkg)
def stop_working(self): def stop_working(self):

View File

@@ -2,3 +2,4 @@ pyqt5>=5.12
requests>=2.18 requests>=2.18
colorama>=0.3.8 colorama>=0.3.8
pyyaml>=3.13 pyyaml>=3.13
python-dateutil>=2.7

View File

@@ -1,45 +0,0 @@
from unittest import TestCase
from bauh.gems.arch.mapper import ArchDataMapper
class ArchDataMapperTest(TestCase):
def test_check_update(self):
self.assertTrue(ArchDataMapper.check_update('1.0.0-1', '1.0.0-2'))
self.assertFalse(ArchDataMapper.check_update('1.0.0-2', '1.0.0-1'))
self.assertTrue(ArchDataMapper.check_update('1.0.0-5', '1.0.1-1'))
self.assertFalse(ArchDataMapper.check_update('1.0.1-1', '1.0.0-1'))
self.assertTrue(ArchDataMapper.check_update('1.0.5-5', '1.1.0-2'))
self.assertFalse(ArchDataMapper.check_update('1.1.0-2', '1.0.5-5'))
self.assertTrue(ArchDataMapper.check_update('1.5.0-2', '1.5.1-1'))
self.assertFalse(ArchDataMapper.check_update('1.5.1-1', '1.5.0-2'))
self.assertTrue(ArchDataMapper.check_update('1.5.1-1', '1.5.1-2'))
self.assertTrue(ArchDataMapper.check_update('1.5.1-1', '2.0.0-1'))
self.assertFalse(ArchDataMapper.check_update('2.0.0-1', '1.5.1-1'))
self.assertTrue(ArchDataMapper.check_update('77.0.3865.90-1', '77.0.3865.120-1'))
self.assertTrue(ArchDataMapper.check_update('77.0.3865.90-1', '77.0.3865.90-2'))
self.assertFalse(ArchDataMapper.check_update('77.0.3865.900-1', '77.0.3865.120-1'))
self.assertTrue(ArchDataMapper.check_update('77.0.3865.120-1', '77.0.3865.900-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.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.assertTrue(ArchDataMapper.check_update('0.9.7.RC-9', '0.9.7.RC-10'))
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__versions_with_epics(self):
self.assertTrue(ArchDataMapper.check_update('1.2-1', '1:1.1-1'))
self.assertFalse(ArchDataMapper.check_update('1:1.1-1', '1.2-1'))
self.assertTrue(ArchDataMapper.check_update('1:1.2-1', '2:0.1-1'))
self.assertFalse(ArchDataMapper.check_update('2:0.1-1', '1:1.2-1'))
self.assertTrue(ArchDataMapper.check_update('10:1.1-1', '10:1.2-1'))
self.assertFalse(ArchDataMapper.check_update('10:1.2-1', '10:1.2-1'))
self.assertTrue(ArchDataMapper.check_update('9:1.2-1', '10:0.1-1'))
self.assertTrue(ArchDataMapper.check_update('9:1.1.1.1-2', '10:0.0'))
self.assertFalse(ArchDataMapper.check_update('10:0.0', '9:1.1.1.1-2'))

View File

@@ -0,0 +1,178 @@
from unittest import TestCase
from unittest.mock import Mock
from bauh.gems.arch.mapper import AURDataMapper
from bauh.gems.arch.model import ArchPackage
class ArchDataMapperTest(TestCase):
def test_check_version_update(self):
self.assertTrue(AURDataMapper.check_version_update('1.0.0-1', '1.0.0-2'))
self.assertFalse(AURDataMapper.check_version_update('1.0.0-2', '1.0.0-1'))
self.assertTrue(AURDataMapper.check_version_update('1.0.0-5', '1.0.1-1'))
self.assertFalse(AURDataMapper.check_version_update('1.0.1-1', '1.0.0-1'))
self.assertTrue(AURDataMapper.check_version_update('1.0.5-5', '1.1.0-2'))
self.assertFalse(AURDataMapper.check_version_update('1.1.0-2', '1.0.5-5'))
self.assertTrue(AURDataMapper.check_version_update('1.5.0-2', '1.5.1-1'))
self.assertFalse(AURDataMapper.check_version_update('1.5.1-1', '1.5.0-2'))
self.assertTrue(AURDataMapper.check_version_update('1.5.1-1', '1.5.1-2'))
self.assertTrue(AURDataMapper.check_version_update('1.5.1-1', '2.0.0-1'))
self.assertFalse(AURDataMapper.check_version_update('2.0.0-1', '1.5.1-1'))
self.assertTrue(AURDataMapper.check_version_update('77.0.3865.90-1', '77.0.3865.120-1'))
self.assertTrue(AURDataMapper.check_version_update('77.0.3865.90-1', '77.0.3865.90-2'))
self.assertFalse(AURDataMapper.check_version_update('77.0.3865.900-1', '77.0.3865.120-1'))
self.assertTrue(AURDataMapper.check_version_update('77.0.3865.120-1', '77.0.3865.900-1'))
self.assertFalse(AURDataMapper.check_version_update('77.0.3865.120-1', '77.0.3865.90-1'))
self.assertTrue(AURDataMapper.check_version_update('77.0.3865.a-1', '77.0.3865.b-1'))
self.assertFalse(AURDataMapper.check_version_update('77.0.b.0-1', '77.0.a.1-1'))
self.assertFalse(AURDataMapper.check_version_update('r25.e22697c-1', 'r8.19fe011-1'))
self.assertTrue(AURDataMapper.check_version_update('0.9.7.RC-9', '0.9.7.RC-10'))
self.assertFalse(AURDataMapper.check_version_update('1.1.0.r11.caacf30-1', 'r65.4c7144a-1'))
self.assertFalse(AURDataMapper.check_version_update('1.2.16.r688.8b2c199-1', 'r2105.e91f0e9-3'))
def test_check_version_update__versions_with_epics(self):
self.assertTrue(AURDataMapper.check_version_update('1.2-1', '1:1.1-1'))
self.assertFalse(AURDataMapper.check_version_update('1:1.1-1', '1.2-1'))
self.assertTrue(AURDataMapper.check_version_update('1:1.2-1', '2:0.1-1'))
self.assertFalse(AURDataMapper.check_version_update('2:0.1-1', '1:1.2-1'))
self.assertTrue(AURDataMapper.check_version_update('10:1.1-1', '10:1.2-1'))
self.assertFalse(AURDataMapper.check_version_update('10:1.2-1', '10:1.2-1'))
self.assertTrue(AURDataMapper.check_version_update('9:1.2-1', '10:0.1-1'))
self.assertTrue(AURDataMapper.check_version_update('9:1.1.1.1-2', '10:0.0'))
self.assertFalse(AURDataMapper.check_version_update('10:0.0', '9:1.1.1.1-2'))
def test_check_update__pkg_no_last_modified_and_same_versions(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.version = '1.0.0'
pkg.latest_version = pkg.version
self.assertFalse(mapper.check_update(pkg=pkg, last_modified=1608143812))
def test_check_update__pkg_no_last_modified_and_latest_version_higher_than_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=1608143812))
def test_check_update__pkg_no_last_modified_and_no_install_date_and_version_higher_than_latest_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.install_date = None
pkg.version = '1.1.0'
pkg.latest_version = '1.0.0'
self.assertFalse(mapper.check_update(pkg=pkg, last_modified=1608143812))
def test_check_update__none_last_modified_and_latest_version_higher_than_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=None))
def test_check_update__none_last_modified_and_version_equal_latest_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = pkg.version
self.assertFalse(mapper.check_update(pkg=pkg, last_modified=None))
def test_check_update__string_last_modified_and_latest_version_higher_than_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
self.assertTrue(mapper.check_update(pkg=pkg, last_modified='abc'))
def test_check_update__pkg_last_modified_equal_last_modified_and_version_equal_latest_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = pkg.version
self.assertFalse(mapper.check_update(pkg=pkg, last_modified=pkg.last_modified))
def test_check_update__pkg_last_modified_higher_than_last_modified_and_latest_version_higher_than_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=pkg.last_modified - 100))
def test_check_update__pkg_last_modified_less_than_last_modified_and_version_higher_than_latest_version(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = 1608143812
pkg.version = '2.0.0'
pkg.latest_version = '1.0.0'
# in this case, last modified is more relevant than the string version
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=pkg.last_modified + 100))
def test_check_update__pkg_no_last_modified_and_install_date_less_than_last_modified_and_version_higher_than_latest(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.install_date = 1608143812
pkg.version = '3.0.0'
pkg.latest_version = '2.0.0'
# in this case, install_date will be considered instead of package's last_modified.
# even that 'version' is higher than 'latest_version', 'last_modified' is greater than 'install_date'
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=pkg.install_date + 100))
def test_check_update__pkg_no_last_modified_and_install_date_higher_than_last_modified_and_version_equal_latest(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.install_date = 1608143812
pkg.version = '2.0.0'
pkg.latest_version = pkg.version
# in this case, install_date will be considered instead of package's last_modified.
# as 'install_date' is higher, only the string versions will be compared
self.assertFalse(mapper.check_update(pkg=pkg, last_modified=pkg.install_date - 100))
def test_check_update__pkg_no_last_modified_and_install_date_higher_than_last_modified_and_latest_higher(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.install_date = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
# in this case, install_date will be considered instead of package's last_modified.
# as 'install_date' is higher, only the string versions will be compared
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=pkg.install_date - 100))
def test_check_update__pkg_no_last_modified_and_install_date_and_no_last_modified_and_latest_higher(self):
mapper = AURDataMapper(i18n=Mock(), logger=Mock(), http_client=Mock())
pkg = ArchPackage(name='test')
pkg.last_modified = None
pkg.install_date = 1608143812
pkg.version = '1.0.0'
pkg.latest_version = '1.1.0'
# in this case, install_date will be considered instead of package's last_modified.
# as 'install_date' is higher, only the string versions will be compared
self.assertTrue(mapper.check_update(pkg=pkg, last_modified=None))