From b9ed2cf4c1e401365e4f63dc5c576a84908f46ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Moreira?= Date: Mon, 9 Dec 2019 19:30:27 -0300 Subject: [PATCH] sketch --- bauh/__init__.py | 2 +- bauh/api/abstract/controller.py | 3 +- bauh/gems/appimage/controller.py | 5 +- bauh/gems/arch/controller.py | 5 +- bauh/gems/flatpak/controller.py | 4 +- bauh/gems/snap/controller.py | 5 +- bauh/gems/snap/snap.py | 2 +- bauh/gems/web/__init__.py | 3 + bauh/gems/web/controller.py | 117 ++++++++++++++++++++++++++++ bauh/gems/web/model.py | 59 ++++++++++++++ bauh/gems/web/npm.py | 6 ++ bauh/gems/web/resources/img/web.png | Bin 0 -> 1454 bytes bauh/gems/web/resources/locale/en | 1 + bauh/gems/web/resources/locale/pt | 1 + bauh/view/core/controller.py | 13 +++- bauh/view/qt/window.py | 1 - 16 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 bauh/gems/web/__init__.py create mode 100644 bauh/gems/web/controller.py create mode 100644 bauh/gems/web/model.py create mode 100644 bauh/gems/web/npm.py create mode 100644 bauh/gems/web/resources/img/web.png create mode 100644 bauh/gems/web/resources/locale/en create mode 100644 bauh/gems/web/resources/locale/pt diff --git a/bauh/__init__.py b/bauh/__init__.py index d3a4efa3..7289277f 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.7.4' +__version__ = '0.8.0' __app_name__ = 'bauh' import os diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 41d9abd1..1138bd02 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -37,11 +37,12 @@ class SoftwareManager(ABC): self.context = context @abstractmethod - def search(self, words: str, disk_loader: DiskCacheLoader, limit: int) -> SearchResult: + def search(self, words: str, disk_loader: DiskCacheLoader, limit: int, is_url: bool) -> SearchResult: """ :param words: the words typed by the user :param disk_loader: a running disk loader thread that loads package data from the disk asynchronously :param limit: the max number of packages to be retrieved. <= 1 should retrieve everything + :param is_url: if "words" is a URL :return: """ pass diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 77aef8f1..242a6a6a 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -61,7 +61,10 @@ class AppImageManager(SoftwareManager): def _gen_app_key(self, app: AppImage): return '{}{}'.format(app.name.lower(), app.github.lower() if app.github else '') - def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult: + def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: + if is_url: + return SearchResult([], [], 0) + res = SearchResult([], [], 0) connection = self._get_db_connection(DB_APPS_PATH) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 87ce01a8..7dfe6d1a 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -79,7 +79,10 @@ class ArchManager(SoftwareManager): 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, is_url: bool = False) -> SearchResult: + if is_url: + return SearchResult([], [], 0) + self.comp_optimizer.join() downgrade_enabled = git.is_enabled() diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 2227ae7a..5b9d7b39 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -56,7 +56,9 @@ class FlatpakManager(SoftwareManager): return app - def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult: + def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: + if is_url: + return SearchResult([], [], 0) res = SearchResult([], [], 0) apps_found = flatpak.search(flatpak.get_version(), words) diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 37489422..608740ff 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -84,7 +84,10 @@ class SnapManager(SoftwareManager): return app - def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult: + def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: + if is_url: + return SearchResult([], [], 0) + if snap.is_snapd_running(): installed = self.read_installed(disk_loader).installed diff --git a/bauh/gems/snap/snap.py b/bauh/gems/snap/snap.py index ad73dcc1..e0fb2689 100644 --- a/bauh/gems/snap/snap.py +++ b/bauh/gems/snap/snap.py @@ -13,7 +13,7 @@ SNAPD_RUNNING_STATUS = {'listening', 'running'} def is_installed(): - res = run_cmd('which snap') + res = run_cmd('which snap', print_error=False) return res and not res.strip().startswith('which ') diff --git a/bauh/gems/web/__init__.py b/bauh/gems/web/__init__.py new file mode 100644 index 00000000..81c0edfd --- /dev/null +++ b/bauh/gems/web/__init__.py @@ -0,0 +1,3 @@ +import os + +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) \ No newline at end of file diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py new file mode 100644 index 00000000..97f7ab0a --- /dev/null +++ b/bauh/gems/web/controller.py @@ -0,0 +1,117 @@ +from typing import List, Type, Set + +from bauh.api.abstract.context import ApplicationContext +from bauh.api.abstract.controller import SoftwareManager, SearchResult +from bauh.api.abstract.disk import DiskCacheLoader +from bauh.api.abstract.handler import ProcessWatcher +from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory +from bauh.gems.web import npm +from bauh.gems.web.model import WebApplication + +try: + from bs4 import BeautifulSoup, SoupStrainer + BS4_AVAILABLE = True +except: + BS4_AVAILABLE = False + + +try: + import lxml + LXML_AVAILABLE = True +except: + LXML_AVAILABLE = False + + +class WebApplicationManager(SoftwareManager): + + def __init__(self, context: ApplicationContext): + super(WebApplicationManager, self).__init__(context=context) + self.http_client = context.http_client + self.enabled = True + + def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: + res = SearchResult([], [], 0) + + if is_url: + url_res = self.http_client.get(words) + + if url_res: + soup = BeautifulSoup(url_res.text, 'lxml', parse_only=SoupStrainer('head')) + + name_tag = soup.head.find('meta', attrs={'name': 'application-name'}) + name = name_tag.get('content') if name_tag else words.split('.')[0] + + desc_tag = soup.head.find('meta', attrs={'name': 'description'}) + desc = desc_tag.get('content') if desc_tag else words + + icon_tag = soup.head.find('link', attrs={"rel": "icon"}) + icon_url = icon_tag.get('href') if icon_tag else None + + res.new = [WebApplication(url=words, name=name, description=desc, icon_url=icon_url)] + res.total = 1 + else: + # TODO + pass + + return res + + def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = True) -> SearchResult: + # TODO + return SearchResult([], [], 0) + + def downgrade(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: + pass + + def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + pass + + def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + pass + + def get_managed_types(self) -> Set[Type[SoftwarePackage]]: + return {WebApplication} + + def get_info(self, pkg: SoftwarePackage) -> dict: + pass + + def get_history(self, pkg: SoftwarePackage) -> PackageHistory: + pass + + def install(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + pass + + def is_enabled(self) -> bool: + return self.enabled + + def set_enabled(self, enabled: bool): + self.enabled = enabled + + def can_work(self) -> bool: + return BS4_AVAILABLE and LXML_AVAILABLE and npm.is_available() + + def requires_root(self, action: str, pkg: SoftwarePackage): + return False + + def prepare(self): + pass + + def list_updates(self, internet_available: bool) -> List[PackageUpdate]: + pass + + def list_warnings(self, internet_available: bool) -> List[str]: + pass + + def list_suggestions(self, limit: int) -> List[PackageSuggestion]: + pass + + def execute_custom_action(self, action: PackageAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + pass + + def is_default_enabled(self) -> bool: + return True + + def launch(self, pkg: SoftwarePackage): + pass + + def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: + pass diff --git a/bauh/gems/web/model.py b/bauh/gems/web/model.py new file mode 100644 index 00000000..43f0c7b4 --- /dev/null +++ b/bauh/gems/web/model.py @@ -0,0 +1,59 @@ +from bauh.api.abstract.model import SoftwarePackage +from bauh.api.constants import CACHE_PATH +from bauh.commons import resource +from bauh.gems.web import ROOT_DIR + + +class WebApplication(SoftwarePackage): + + def __init__(self, url: str, name: str, description: str, icon_url: str): + super(WebApplication, self).__init__(id=url, name=name, description=description, icon_url=icon_url) + self.url = url + + def has_history(self): + return False + + def has_info(self): + return False + + def can_be_downgraded(self): + return False + + def get_type(self): + return 'web' + + def get_type_icon_path(self) -> str: + return self.get_default_icon_path() + + def get_default_icon_path(self) -> str: + return resource.get_path('img/web.png', ROOT_DIR) + + def is_application(self): + return True + + def supports_disk_cache(self): + return self.installed and self.is_application() + + def get_disk_cache_path(self): + return CACHE_PATH + '/' + self.get_type() + + def get_data_to_cache(self) -> dict: + """ + :return: the application data that should be cached in disk / memory for quick access + """ + pass + + def fill_cached_data(self, data: dict): + pass + + def can_be_run(self) -> bool: + return self.installed + + def is_trustable(self) -> bool: + return False + + def get_publisher(self) -> str: + pass + + def has_screenshots(self) -> bool: + return False diff --git a/bauh/gems/web/npm.py b/bauh/gems/web/npm.py new file mode 100644 index 00000000..9721b78b --- /dev/null +++ b/bauh/gems/web/npm.py @@ -0,0 +1,6 @@ +from bauh.commons.system import run_cmd + + +def is_available() -> bool: + res = run_cmd('which npm', print_error=False) + return res and not res.strip().startswith('which ') diff --git a/bauh/gems/web/resources/img/web.png b/bauh/gems/web/resources/img/web.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6fb51c3560f6de8c7a3428cf71caf2e0086521 GIT binary patch literal 1454 zcmV;f1yTBmP)e>1Z?yDhZ4rIu1@D=jUh$V-U|CNxn& z1%+TmAc_WKh#F&jh8PkJLIgxi@Ij1xP@^RHzyKlv4D!^r(k`{o(yrxIp!Bi3?e4bw znBAGVegKx;KJV zo!4mFziSYu8HO!nnxC&d;rejMfOW@QuSAgE*3Du10gNmdhqtkw{muiwNZfDew;P7ccQDZ><2fQ~;7<7=ox2th`{II^eTL&hD&00`5>#W#^;nZApybk!Wk zIx+)$b`CG~I%aBzS#cCz5VlDvQhB`v+m$_qp<@Mf6k{B7+ zrpZCWSYjrfLq)Y=YFdhS-=*_3ZQDY)tAp|vUZrr}B64TWrl+|vp>dX$6YgkFe49c@ zeR_O}1=;dYvYBIi8J@pS;+!}cfc7H?>AQ4};>xE<%gMdf=Mfp0@fgA|kQ9YjFo2rr zNGZ&aEpuc^k;f*3nZ>1OJwE!|FVlYXAX=}FvgckREhm@m+A7X`wh@2p_4{Pc&LP_4 z1)!&~9>@5ylmJ;#@?}X@Zc9NWiK2x|xUh3Oe)n057A-?k6aYMR)kJ+BysnxXs~Cw9 zjQ|k!c^EZy1_KguWFf?$1D!T{43Z=v^f-E`FM$^=T1L){nH0`jG;j(fOIILUEGd|2 zni1KE$2|kZ{Z}rLo;Q|)hZo}h_DlTkCIHg&#xicfQ>c!tK>?wwZFE*2!-&Uj1?cf^ zS=XX11I4~G^<+<-funRH6PB;U`*$rZUvI^C+JznoCv=9Ufw*q|&yKH%db{bWI+oz$ z(Wu)J?)C4rXF49g2?o!*@tt;I&%VAjsw0b%r7MVZcj9fRqpRjb;x`cs^b+pwU_@R% zHfI*T=0-}NdNvUd@%QYpbhztwIrH;3$%^6t@SHkH&V94-o_3KtvjS7s6Z=vyH&F+A zBuvXUUoiRQw+6Kfz%&ehoBP~udH;qDo!V=q0s#E(CaxaZH^{(u z<`h{s?#fUj*m3of-#4uG$p9@s?%foNMp^*Ua`PDfm@6aWZ{v7YMZ8+NxN< zkEgbZ(f8gzqP zIKMJcL`rLP-RnMb;DLSX*EFUC04!Xyv7&g^?C&h9t#HW7f&uhUKla;3BU`P5cs-_F zzWCRl%lEvu>cmY3f_71?6g5dhx&l6q_AuMrPt>Ck+wq@g1z$@!gWc zpKfYMCWd_U0kv}7$B*Vsnf8V~!!b*eCEHLUVG1o8^jBZ5cWv3b_MHQOHc&HMKq9F8 z{s;4>RxX}vcRGtLsy!D#k43w}!QS(YJAXdZTy?kuhz)1(FPPXmS SearchResult: + def search(self, word: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: ti = time.time() self._wait_to_be_ready() @@ -97,13 +100,15 @@ class GenericSoftwareManager(SoftwareManager): if internet.is_available(self.context.http_client, self.context.logger): norm_word = word.strip().lower() + + url_words = RE_IS_URL.match(norm_word) disk_loader = self.disk_loader_factory.new() disk_loader.start() threads = [] for man in self.managers: - t = Thread(target=self._search, args=(norm_word, man, disk_loader, res)) + t = Thread(target=self._search, args=(norm_word, url_words, man, disk_loader, res)) t.start() threads.append(t) diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index c7a449d2..3117d887 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -106,7 +106,6 @@ class ManageWindow(QWidget): self.toolbar_search.addWidget(label_pre_search) self.input_search = QLineEdit() - self.input_search.setMaxLength(20) self.input_search.setFrame(False) self.input_search.setPlaceholderText(self.i18n['window_manage.input_search.placeholder'] + "...") self.input_search.setToolTip(self.i18n['window_manage.input_search.tooltip'])