mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 00:04:15 +02:00
[aur] initial implementation to determine the best update order
This commit is contained in:
@@ -12,6 +12,7 @@ from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction
|
||||
from bauh.api.abstract.view import FormComponent, ViewComponent
|
||||
from build.lib.bauh.api.abstract.controller import SoftwareManager
|
||||
|
||||
|
||||
class SearchResult:
|
||||
@@ -82,6 +83,14 @@ class SoftwareManager(ABC):
|
||||
if pkg.supports_disk_cache() and os.path.exists(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
|
||||
def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
|
||||
@@ -993,3 +993,70 @@ class ArchManager(SoftwareManager):
|
||||
return True, None
|
||||
except:
|
||||
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]
|
||||
|
||||
@@ -419,3 +419,28 @@ class GenericSoftwareManager(SoftwareManager):
|
||||
self.settings_manager = None
|
||||
|
||||
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
|
||||
|
||||
@@ -103,19 +103,23 @@ class UpdateSelectedApps(AsyncAction):
|
||||
|
||||
if self.apps_to_update:
|
||||
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))
|
||||
success = bool(self.manager.update(app.model, self.root_password, self))
|
||||
for pkg in sorted_pkgs:
|
||||
|
||||
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('')
|
||||
|
||||
if not success:
|
||||
break
|
||||
else:
|
||||
updated += 1
|
||||
updated_types.add(app.model.__class__)
|
||||
updated_types.add(pkg.__class__)
|
||||
self.signal_output.emit('\n')
|
||||
|
||||
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})
|
||||
|
||||
@@ -265,4 +265,5 @@ locale.it=italià
|
||||
interval=interval
|
||||
installation=instal·lació
|
||||
download=download
|
||||
clean=netejar
|
||||
clean=netejar
|
||||
action.update.status.sorting=Determinant el millor ordre d’actualització
|
||||
@@ -220,4 +220,5 @@ locale.it=italienisch
|
||||
interval=intervall
|
||||
installation=Installation
|
||||
download=download
|
||||
clean=reinigen
|
||||
clean=reinigen
|
||||
action.update.status.sorting=Determining the best update order
|
||||
@@ -227,4 +227,5 @@ locale.it=italian
|
||||
interval=interval
|
||||
installation=installation
|
||||
download=download
|
||||
clean=clean
|
||||
clean=clean
|
||||
action.update.status.sorting=Determining the best update order
|
||||
@@ -268,4 +268,5 @@ locale.it=italiano
|
||||
interval=intervalo
|
||||
installation=instalación
|
||||
download=descarga
|
||||
clean=limpiar
|
||||
clean=limpiar
|
||||
action.update.status.sorting=Determinando el mejor orden de actualización
|
||||
@@ -220,4 +220,5 @@ locale.it=italiano
|
||||
interval=intervallo
|
||||
installation=installazione
|
||||
download=download
|
||||
clean=pulire
|
||||
clean=pulire
|
||||
action.update.status.sorting=Sto determinando il miglior ordine di aggiornamento
|
||||
@@ -271,4 +271,5 @@ locale.it=italiano
|
||||
interval=intervalo
|
||||
installation=instalação
|
||||
download=download
|
||||
clean=limpar
|
||||
clean=limpar
|
||||
action.update.status.sorting=Determinando a melhor ordem de atualização
|
||||
138
tests/gems/arch/controller.py
Normal file
138
tests/gems/arch/controller.py
Normal 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
|
||||
Reference in New Issue
Block a user