From 89e1f93c7ede85151ddf0492cdaae3b425394f6d Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Wed, 10 Jun 2020 12:18:52 -0300 Subject: [PATCH] [improvement][ui] filters algorithm speed and sorting | [fix][web] handling web page fetch errors --- CHANGELOG.md | 3 ++- bauh/gems/web/controller.py | 3 ++- bauh/view/qt/commons.py | 25 +++++++++--------- bauh/view/qt/thread.py | 51 ++++++++++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b282789..33f82042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.9.6] 2020 ### Improvements - UI - - name filter now compares the typed word with the beginning of the package name + - filters algorithm speed and sorting ### Fixes - Web - handling unexpected connection errors + - handling web page fetch errors ## [0.9.5] 2020-06-07 ### Features diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index e6e726a7..ce062fc2 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -202,7 +202,8 @@ class WebApplicationManager(SoftwareManager): def _map_url(self, url: str) -> Tuple["BeautifulSoup", requests.Response]: url_res = self._request_url(url) - return BeautifulSoup(url_res.text, 'lxml', parse_only=SoupStrainer('head')), url_res + if url_res: + return BeautifulSoup(url_res.text, 'lxml', parse_only=SoupStrainer('head')), url_res def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: local_config = {} diff --git a/bauh/view/qt/commons.py b/bauh/view/qt/commons.py index 34708aab..eed8f509 100644 --- a/bauh/view/qt/commons.py +++ b/bauh/view/qt/commons.py @@ -38,20 +38,21 @@ def update_info(pkgv: PackageView, pkgs_info: dict): pkgs_info['not_installed'] += 1 if not pkgv.model.installed else 0 -def apply_filters(pkgv: PackageView, filters: dict, info: dict): - hidden = filters['only_apps'] and pkgv.model.installed and not pkgv.model.is_application() +def apply_filters(pkgv: PackageView, filters: dict, info: dict, limit: bool = True): + if not limit or not filters['display_limit'] or len(info['pkgs_displayed']) < filters['display_limit']: + hidden = filters['only_apps'] and pkgv.model.installed and not pkgv.model.is_application() - if not hidden and filters['updates']: - hidden = not pkgv.model.update or pkgv.model.is_update_ignored() + if not hidden and filters['updates']: + hidden = not pkgv.model.update or pkgv.model.is_update_ignored() - if not hidden and filters['type'] is not None and filters['type'] != 'any': - hidden = pkgv.model.get_type() != filters['type'] + if not hidden and filters['type'] is not None and filters['type'] != 'any': + hidden = pkgv.model.get_type() != filters['type'] - if not hidden and filters['category'] is not None and filters['category'] != 'any': - hidden = not pkgv.model.categories or not [c for c in pkgv.model.categories if c.lower() == filters['category']] + if not hidden and filters['category'] is not None and filters['category'] != 'any': + hidden = not pkgv.model.categories or not [c for c in pkgv.model.categories if c.lower() == filters['category']] - if not hidden and filters['name']: - hidden = not pkgv.model.name.lower().startswith(filters['name']) + if not hidden and filters['name']: + hidden = not filters['name'] in pkgv.model.name.lower() - if not hidden and (not filters['display_limit'] or len(info['pkgs_displayed']) < filters['display_limit']): - info['pkgs_displayed'].append(pkgv) + if not hidden: + info['pkgs_displayed'].append(pkgv) diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 09ab0824..a72ca182 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -843,13 +843,62 @@ class ApplyFilters(AsyncAction): def stop_waiting(self): self.wait_table_update = False + def _sort_by_word(self, word: str, pkgs: List[PackageView], limit: int) -> List[PackageView]: + norm_word = word.strip().lower() + exact_installed, exact_not_installed = [], [] + starts_installed, starts_not_installed = [], [] + contains_installed, contains_not_installed = [], [] + + for p in pkgs: + lower_name = p.model.name.lower() + + if norm_word == lower_name: + if p.model.installed: + exact_installed.append(p) + else: + exact_not_installed.append(p) + elif lower_name.startswith(norm_word): + if p.model.installed: + starts_installed.append(p) + else: + starts_not_installed.append(p) + else: + if p.model.installed: + contains_installed.append(p) + else: + contains_not_installed.append(p) + + res = [] + for matches in (exact_installed, exact_not_installed, + starts_installed, starts_not_installed, + contains_installed, contains_not_installed): + if matches: + matches.sort(key=lambda p: p.model.name.lower()) + + if limit: + res.extend(matches[0:limit - len(res)]) + + if len(res) == limit: + break + else: + res.extend(matches) + + return res + def run(self): if self.pkgs: pkgs_info = commons.new_pkgs_info() + name_filtering = bool(self.filters['name']) + for pkgv in self.pkgs: commons.update_info(pkgv, pkgs_info) - commons.apply_filters(pkgv, self.filters, pkgs_info) + commons.apply_filters(pkgv, self.filters, pkgs_info, limit=not name_filtering) + + if name_filtering and pkgs_info['pkgs_displayed']: + pkgs_info['pkgs_displayed'] = self._sort_by_word(word=self.filters['name'], + pkgs=pkgs_info['pkgs_displayed'], + limit=self.filters['display_limit']) self.wait_table_update = True self.signal_table.emit(pkgs_info)