[aur] initial implementation to determine the best update order

This commit is contained in:
Vinícius Moreira
2020-02-05 18:46:55 -03:00
parent a11f418759
commit a10851a16f
11 changed files with 260 additions and 11 deletions

View File

@@ -12,6 +12,7 @@ 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, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction
from bauh.api.abstract.view import FormComponent, ViewComponent from bauh.api.abstract.view import FormComponent, ViewComponent
from build.lib.bauh.api.abstract.controller import SoftwareManager
class SearchResult: class SearchResult:
@@ -82,6 +83,14 @@ class SoftwareManager(ABC):
if pkg.supports_disk_cache() and os.path.exists(pkg.get_disk_cache_path()): if pkg.supports_disk_cache() and os.path.exists(pkg.get_disk_cache_path()):
shutil.rmtree(pkg.get_disk_cache_path()) shutil.rmtree(pkg.get_disk_cache_path())
def sort_update_order(self, pkgs: List[SoftwarePackage]) -> List[SoftwarePackage]:
"""
sorts the best order to perform the update of some packages
:param pkgs:
:return:
"""
return pkgs
@abstractmethod @abstractmethod
def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
""" """

View File

@@ -993,3 +993,70 @@ class ArchManager(SoftwareManager):
return True, None return True, None
except: except:
return False, [traceback.format_exc()] return False, [traceback.format_exc()]
def sort_update_order(self, pkgs: List[ArchPackage]) -> List[ArchPackage]:
# TODO pegar o 'provides' dos pacotes ?
pkg_deps = {}
def _add_info(pkg: ArchPackage):
try:
pkg_deps[pkg] = self.aur_client.get_all_dependencies(pkg.name)
except:
pkg_deps[pkg] = None
self.logger.warning("Could not retrieve dependencies for '{}'".format(pkg.name))
traceback.print_exc()
threads = []
for pkg in pkgs:
t = Thread(target=_add_info, args=(pkg, ))
t.start()
threads.append(t)
for t in threads:
t.join()
return self._sort_deps(pkg_deps)
@classmethod
def _sort_deps(cls, pkg_deps: Dict[ArchPackage, Set[str]]) -> List[ArchPackage]:
sorted_names, not_sorted = {}, {}
pkg_map = {}
# first adding all with no deps:
for pkg, deps in pkg_deps.items():
if not deps:
sorted_names[pkg.name] = len(sorted_names)
else:
not_sorted[pkg.name] = pkg
pkg_map[pkg.name] = pkg
# now adding all that depends on another:
for name, pkg in not_sorted.items():
cls._add_to_sort(pkg, pkg_deps, sorted_names, not_sorted)
position_map = {'{}-{}'.format(i, n): pkg_map[n] for n, i in sorted_names.items()}
return [position_map[idx] for idx in sorted(position_map)]
@classmethod
def _add_to_sort(cls, pkg: ArchPackage, pkg_deps: Dict[ArchPackage, Set[str]], sorted_names: Dict[str, int], not_sorted: Dict[str, ArchPackage]) -> int:
idx = sorted_names.get(pkg.name)
if idx is not None:
return idx
else:
idx = len(sorted_names)
sorted_names[pkg.name] = idx
for dep in pkg_deps[pkg]:
dep_idx = sorted_names.get(dep)
if dep_idx is not None:
idx = dep_idx + 1
elif dep in not_sorted: # it means the dep is one of the packages to sort, but it not sorted yet
dep_idx = cls._add_to_sort(not_sorted[dep], pkg_deps, sorted_names, not_sorted)
idx = dep_idx + 1
sorted_names[pkg.name] = idx
return sorted_names[pkg.name]

View File

@@ -419,3 +419,28 @@ class GenericSoftwareManager(SoftwareManager):
self.settings_manager = None self.settings_manager = None
return res return res
def sort_update_order(self, pkgs: List[SoftwarePackage]) -> List[SoftwarePackage]:
by_manager = {}
for pkg in pkgs:
man = self._get_manager_for(pkg)
if man:
man_pkgs = by_manager.get(man)
if man_pkgs is None:
man_pkgs = []
by_manager[man] = man_pkgs
man_pkgs.append(pkg)
sorted_list = []
if by_manager:
for man, pkgs in by_manager.items():
ti = time.time()
sorted_list.extend(man.sort_update_order(pkgs))
tf = time.time()
self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(tf - ti))
return sorted_list

View File

@@ -103,19 +103,23 @@ class UpdateSelectedApps(AsyncAction):
if self.apps_to_update: if self.apps_to_update:
updated, updated_types = 0, set() updated, updated_types = 0, set()
for app in self.apps_to_update:
name = app.model.name if not RE_VERSION_IN_NAME.findall(app.model.name) else app.model.name.split('version')[0].strip() self.change_substatus(self.i18n['action.update.status.sorting'])
sorted_pkgs = self.manager.sort_update_order([view.model for view in self.apps_to_update])
self.change_status('{} {} {}...'.format(self.i18n['manage_window.status.upgrading'], name, app.model.version)) for pkg in sorted_pkgs:
success = bool(self.manager.update(app.model, self.root_password, self))
name = pkg.name if not RE_VERSION_IN_NAME.findall(pkg.name) else pkg.name.split('version')[0].strip()
self.change_status('{} {} {}...'.format(self.i18n['manage_window.status.upgrading'], name, pkg.version))
success = bool(self.manager.update(pkg, self.root_password, self))
self.change_substatus('') self.change_substatus('')
if not success: if not success:
break break
else: else:
updated += 1 updated += 1
updated_types.add(app.model.__class__) updated_types.add(pkg.__class__)
self.signal_output.emit('\n') self.signal_output.emit('\n')
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types}) self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})

View File

@@ -266,3 +266,4 @@ interval=interval
installation=instal·lació installation=instal·lació
download=download download=download
clean=netejar clean=netejar
action.update.status.sorting=Determinant el millor ordre dactualització

View File

@@ -221,3 +221,4 @@ interval=intervall
installation=Installation installation=Installation
download=download download=download
clean=reinigen clean=reinigen
action.update.status.sorting=Determining the best update order

View File

@@ -228,3 +228,4 @@ interval=interval
installation=installation installation=installation
download=download download=download
clean=clean clean=clean
action.update.status.sorting=Determining the best update order

View File

@@ -269,3 +269,4 @@ interval=intervalo
installation=instalación installation=instalación
download=descarga download=descarga
clean=limpiar clean=limpiar
action.update.status.sorting=Determinando el mejor orden de actualización

View File

@@ -221,3 +221,4 @@ interval=intervallo
installation=installazione installation=installazione
download=download download=download
clean=pulire clean=pulire
action.update.status.sorting=Sto determinando il miglior ordine di aggiornamento

View File

@@ -272,3 +272,4 @@ interval=intervalo
installation=instalação installation=instalação
download=download download=download
clean=limpar clean=limpar
action.update.status.sorting=Determinando a melhor ordem de atualização

View File

@@ -0,0 +1,138 @@
from unittest import TestCase
from bauh.gems.arch.controller import ArchManager
from bauh.gems.arch.model import ArchPackage
class ArchManagerSortUpdateOrderTest(TestCase):
def test__sort_deps__not_related_packages(self):
deps = {
ArchPackage(name='google-chrome', package_base='google-chrome'): {'alsa-lib', 'gtk3', 'libcups'},
ArchPackage(name='git-cola', package_base='git-cola'): {'git', 'python-pyqt5', 'icu qt5-svg'},
ArchPackage(name='kazam', package_base='kazam'): {'python', 'python-cairo'}
}
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
def test__sort_deps__all_packages_no_deps(self):
deps = {
ArchPackage(name='xpto', package_base='xpto'): set(),
ArchPackage(name='abc', package_base='abc'): None
}
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
def test__sort_deps__one_of_three_related(self):
deps = {
ArchPackage(name='abc', package_base='abc'): {'ghi', 'xpto'},
ArchPackage(name='def', package_base='def'): {'jkl'},
ArchPackage(name='ghi', package_base='ghi'): {}
}
for _ in range(5): # testing n times to see if the same result is produced
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
ghi = [p for p in sorted_list if p.name == 'ghi']
self.assertEqual(1, len(ghi))
ghi_idx = sorted_list.index(ghi[0])
abc = [p for p in sorted_list if p.name == 'abc']
self.assertEqual(1, len(abc))
abc_idx = sorted_list.index(abc[0])
self.assertGreater(abc_idx, ghi_idx)
def test__sort_deps__two_of_three_related(self):
"""
dep order = abc -> ghi -> def
expected: def, ghi, abc
"""
deps = {
ArchPackage(name='abc', package_base='abc'): {'ghi', 'xpto'},
ArchPackage(name='def', package_base='def'): {'jkl'},
ArchPackage(name='ghi', package_base='ghi'): {'def'}
}
for _ in range(5): # testing n times to see if the same result is produced
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
self.assertEqual(sorted_list[0].name, 'def')
self.assertEqual(sorted_list[1].name, 'ghi')
self.assertEqual(sorted_list[2].name, 'abc')
def test__sort_deps__two_relying_on_the_same_package(self):
"""
dep order:
abc -> ghi
jkl -> ghi
ghi -> def
def -> mno
expected: def, ghi, (abc | jkl )
"""
deps = {
ArchPackage(name='abc', package_base='abc'): {'ghi', 'xpto'},
ArchPackage(name='def', package_base='def'): {'mno'},
ArchPackage(name='ghi', package_base='ghi'): {'def'},
ArchPackage(name='jkl', package_base='jkl'): {'ghi'}
}
for _ in range(5): # testing n times to see if the same result is produced
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
self.assertEqual(sorted_list[0].name, 'def')
self.assertEqual(sorted_list[1].name, 'ghi')
self.assertNotEqual(sorted_list[2].name, sorted_list[3].name)
self.assertIn(sorted_list[2].name, {'abc', 'jkl'})
self.assertIn(sorted_list[3].name, {'abc', 'jkl'})
def test__sort_deps__with_cycle(self):
"""
dep order:
abc -> def -> ghi -> jkl -> abc
expected:
"""
deps = {
ArchPackage(name='abc', package_base='abc'): {'def'},
ArchPackage(name='def', package_base='def'): {'ghi'},
ArchPackage(name='ghi', package_base='ghi'): {'jkl'},
ArchPackage(name='jkl', package_base='jkl'): {'abc'}
}
sorted_list = ArchManager._sort_deps(deps)
self.assertIsInstance(sorted_list, list)
self.assertEqual(len(deps), len(sorted_list))
for pkg in sorted_list:
self.assertIn(pkg, deps)
def test__sort_deps__a_declared_dep_provided_as_a_different_name(self):
pass