def get_info(self, pkg: WebApplication) -> dict: if pkg.installed: info = { '0{}_{}'.format(idx + 1, att): getattr(pkg, att) for idx, att in enumerate(('url', 'description', 'version', 'categories', 'installation_dir', 'desktop_entry')) } info['07_exec_file'] = pkg.get_exec_path() info['08_icon_path'] = pkg.get_disk_icon_path() if os.path.exists(pkg.installation_dir): info['09_size'] = get_human_size_str( get_dir_size(pkg.installation_dir)) config_dir = pkg.get_config_dir() if config_dir: info['10_config_dir'] = config_dir if info.get('04_categories'): info['04_categories'] = [ self.i18n[c.lower()].capitalize() for c in info['04_categories'] ] return info else: return { '0{}_{}'.format(idx + 1, att): getattr(pkg, att) for idx, att in enumerate(('url', 'description', 'version', 'categories')) }
def _map_suggestion(self, suggestion: dict) -> PackageSuggestion: app = WebApplication(name=suggestion.get('name'), url=suggestion.get('url'), icon_url=suggestion.get('icon_url'), categories=[suggestion['category']] if suggestion.get('category') else None, preset_options=suggestion.get('options'), save_icon=suggestion.get('save_icon', False)) app.set_version(suggestion.get('version')) description = suggestion.get('description') if isinstance(description, dict): app.description = description.get( self.i18n.current_key, description.get(self.i18n.default_key)) elif isinstance(description, str): app.description = description if not app.version and self.env_settings and self.env_settings.get( 'electron'): app.version = self.env_settings['electron']['version'] app.latest_version = app.version app.status = PackageStatus.LOADING_DATA Thread(target=self._fill_suggestion, args=(app, ), daemon=True).start() return PackageSuggestion(priority=SuggestionPriority( suggestion['priority']), package=app)
def _gen_desktop_entry_content(self, pkg: WebApplication) -> str: return """ [Desktop Entry] Type=Application Name={name} ( web ) Comment={desc} Icon={icon} Exec={exec_path} {categories} """.format(name=pkg.name, exec_path=pkg.get_command(), desc=pkg.description or pkg.url, icon=pkg.get_disk_icon_path(), categories='Categories={}'.format(';'.join(pkg.categories)) if pkg.categories else '')
def _fill_suggestion(self, app: WebApplication): soup = self._map_url(app.url) if soup: if not app.name: app.name = self._get_app_name(app.url, soup) if not app.description: app.description = self._get_app_description(app.url, soup) find_url = not app.icon_url or ( app.icon_url and not self.http_client.exists(app.icon_url)) if find_url: app.icon_url = self._get_app_icon_url(app.url, soup) app.status = PackageStatus.READY
def _fill_suggestion(self, app: WebApplication): soup_map = self._map_url(app.url) if soup_map: soup, res = soup_map[0], soup_map[1] app.url = res.url if app.url.endswith('/'): app.url = app.url[0:-1] if not app.name: app.name = self._get_app_name(app.url, soup) if not app.description: app.description = self._get_app_description(app.url, soup) find_url = not app.icon_url or ( app.icon_url and not self.http_client.exists(app.icon_url, session=False)) if find_url: app.icon_url = self._get_app_icon_url(app.url, soup) app.status = PackageStatus.READY
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: res = SearchResult([], [], 0) if os.path.exists(INSTALLED_PATH): for data_path in glob.glob( '{}/*/*data.yml'.format(INSTALLED_PATH)): with open(data_path, 'r') as f: res.installed.append( WebApplication(installed=True, **yaml.safe_load(f.read()))) res.total += 1 return res
def install(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool: continue_install, install_options = self._ask_install_options( pkg, watcher) if not continue_install: watcher.print("Installation aborted by the user") return False watcher.change_substatus(self.i18n['web.env.checking']) handler = ProcessHandler(watcher) env_settings = self.env_updater.read_settings() local_config = read_config() if local_config['environment'][ 'system'] and not nativefier.is_available(): watcher.show_message( title=self.i18n['error'].capitalize(), body=self.i18n['web.install.global_nativefier.unavailable']. format(n=bold('Nativefier'), app=bold(pkg.name)) + '.', type_=MessageType.ERROR) return False env_components = self.env_updater.check_environment( app=pkg, local_config=local_config, env=env_settings, is_x86_x64_arch=self.context.is_system_x86_64()) comps_to_update = [c for c in env_components if c.update] if comps_to_update and not self._ask_update_permission( comps_to_update, watcher): return False if not self.env_updater.update(components=comps_to_update, handler=handler): watcher.show_message(title=self.i18n['error'], body=self.i18n['web.env.error'].format( bold(pkg.name)), type_=MessageType.ERROR) return False Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True) app_id, treated_name = self._gen_app_id(pkg.name) pkg.id = app_id app_dir = '{}/{}'.format(INSTALLED_PATH, app_id) watcher.change_substatus( self.i18n['web.install.substatus.checking_fixes']) fix = self._get_fix_for( url_no_protocol=self._strip_url_protocol(pkg.url)) fix_path = '{}/fix.js'.format(app_dir) if fix: # just adding the fix as an installation option. The file will be written later self.logger.info('Fix found for {}'.format(pkg.url)) watcher.print('Fix found for {}'.format(pkg.url)) install_options.append('--inject={}'.format(fix_path)) # if a custom icon is defined for an app suggestion: icon_path, icon_bytes = None, None if pkg.icon_url and pkg.save_icon and not { o for o in install_options if o.startswith('--icon') }: download = self._download_suggestion_icon(pkg, app_dir) if download and download[1]: icon_path, icon_bytes = download[0], download[1] pkg.custom_icon = icon_path # writting the icon in a temporary folder to be used by the nativefier process temp_icon_path = '{}/{}'.format(TEMP_PATH, pkg.icon_url.split('/')[-1]) install_options.append('--icon={}'.format(temp_icon_path)) self.logger.info("Writing a temp suggestion icon at {}".format( temp_icon_path)) with open(temp_icon_path, 'wb+') as f: f.write(icon_bytes) watcher.change_substatus( self.i18n['web.install.substatus.call_nativefier'].format( bold('nativefier'))) electron_version = str( next((c for c in env_components if c.id == 'electron')).version) installed = handler.handle_simple( nativefier.install(url=pkg.url, name=app_id, output_dir=app_dir, electron_version=electron_version, system=bool( local_config['environment']['system']), cwd=INSTALLED_PATH, extra_options=install_options)) if not installed: msg = '{}.{}.'.format( self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.unknown'].format( bold(self.i18n['details'].capitalize()))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) return False inner_dir = os.listdir(app_dir) if not inner_dir: msg = '{}.{}.'.format( self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.inner_dir'].format( bold(app_dir))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) return False # bringing the inner app folder to the 'installed' folder level: inner_dir = '{}/{}'.format(app_dir, inner_dir[0]) temp_dir = '{}/tmp_{}'.format(INSTALLED_PATH, treated_name) os.rename(inner_dir, temp_dir) shutil.rmtree(app_dir) os.rename(temp_dir, app_dir) # injecting a fix if fix: self.logger.info('Writting JS fix at {}'.format(fix_path)) with open(fix_path, 'w+') as f: f.write(fix) # persisting the custom suggestion icon in the defitive directory if icon_bytes: self.logger.info( "Writting the final custom suggestion icon at {}".format( icon_path)) with open(icon_path, 'wb+') as f: f.write(icon_bytes) pkg.installation_dir = app_dir version_path = '{}/version'.format(app_dir) if os.path.exists(version_path): with open(version_path, 'r') as f: pkg.version = f.read().strip() pkg.latest_version = pkg.version watcher.change_substatus(self.i18n['web.install.substatus.shortcut']) desktop_entry_path = self._gen_desktop_entry_path(app_id) entry_content = self._gen_desktop_entry_content(pkg) Path(DESKTOP_ENTRIES_DIR).mkdir(parents=True, exist_ok=True) with open(desktop_entry_path, 'w+') as f: f.write(entry_content) pkg.desktop_entry = desktop_entry_path if '--tray=start-in-tray' in install_options: autostart_dir = '{}/.config/autostart'.format(Path.home()) Path(autostart_dir).mkdir(parents=True, exist_ok=True) with open(pkg.get_autostart_path(), 'w+') as f: f.write(entry_content) if install_options: pkg.options_set = install_options return True
def _ask_install_options( self, app: WebApplication, watcher: ProcessWatcher) -> Tuple[bool, List[str]]: watcher.change_substatus(self.i18n['web.install.substatus.options']) inp_url = TextInputComponent(label=self.i18n['address'], value=app.url, read_only=True) inp_name = TextInputComponent(label=self.i18n['name'], value=app.name) inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description) cat_ops = [ InputOption(label=self.i18n['web.install.option.category.none']. capitalize(), value=0) ] cat_ops.extend([ InputOption(label=self.i18n[c.lower()].capitalize(), value=c) for c in self.context.default_categories ]) def_cat = cat_ops[0] if app.categories: for opt in cat_ops: if opt.value == app.categories[0]: def_cat = opt break inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops, default_option=def_cat) tray_op_off = InputOption( id_='tray_off', label=self.i18n['web.install.option.tray.off.label'], value=0, tooltip=self.i18n['web.install.option.tray.off.tip']) tray_op_default = InputOption( id_='tray_def', label=self.i18n['web.install.option.tray.default.label'], value='--tray', tooltip=self.i18n['web.install.option.tray.default.tip']) tray_op_min = InputOption( id_='tray_min', label=self.i18n['web.install.option.tray.min.label'], value='--tray=start-in-tray', tooltip=self.i18n['web.install.option.tray.min.tip']) tray_opts = [tray_op_off, tray_op_default, tray_op_min] def_tray_opt = None if app.preset_options: for opt in tray_opts: if opt.id in app.preset_options: def_tray_opt = opt break inp_tray = SingleSelectComponent( type_=SelectViewType.COMBO, options=tray_opts, default_option=def_tray_opt, label=self.i18n['web.install.option.tray.label']) icon_op_ded = InputOption( id_='icon_ded', label=self.i18n['web.install.option.wicon.deducted.label'], value=0, tooltip=self.i18n['web.install.option.wicon.deducted.tip'].format( 'Nativefier')) icon_op_disp = InputOption( id_='icon_disp', label=self.i18n['web.install.option.wicon.displayed.label'], value=1, tooltip=self.i18n['web.install.option.wicon.displayed.tip']) inp_icon = SingleSelectComponent( type_=SelectViewType.COMBO, options=[icon_op_disp, icon_op_ded], default_option=icon_op_disp if app.icon_url and app.save_icon else icon_op_ded, label=self.i18n['web.install.option.wicon.label']) icon_chooser = FileChooserComponent( allowed_extensions={'png', 'svg', 'ico', 'jpg', 'jpeg'}, label=self.i18n['web.install.option.icon.label']) form_1 = FormComponent( components=[ inp_url, inp_name, inp_desc, inp_cat, inp_icon, icon_chooser, inp_tray ], label=self.i18n['web.install.options.basic'].capitalize()) op_single = InputOption( id_='single', label=self.i18n['web.install.option.single.label'], value="--single-instance", tooltip=self.i18n['web.install.option.single.tip']) op_max = InputOption(id_='max', label=self.i18n['web.install.option.max.label'], value="--maximize", tooltip=self.i18n['web.install.option.max.tip']) op_fs = InputOption( id_='fullscreen', label=self.i18n['web.install.option.fullscreen.label'], value="--full-screen", tooltip=self.i18n['web.install.option.fullscreen.tip']) op_nframe = InputOption( id_='no_frame', label=self.i18n['web.install.option.noframe.label'], value="--hide-window-frame", tooltip=self.i18n['web.install.option.noframe.tip']) op_allow_urls = InputOption( id_='allow_urls', label=self.i18n['web.install.option.allow_urls.label'], value='--internal-urls=.*', tooltip=self.i18n['web.install.option.allow_urls.tip']) op_ncache = InputOption( id_='no_cache', label=self.i18n['web.install.option.nocache.label'], value="--clear-cache", tooltip=self.i18n['web.install.option.nocache.tip']) op_insecure = InputOption( id_='insecure', label=self.i18n['web.install.option.insecure.label'], value="--insecure", tooltip=self.i18n['web.install.option.insecure.tip']) op_igcert = InputOption( id_='ignore_certs', label=self.i18n['web.install.option.ignore_certificate.label'], value="--ignore-certificate", tooltip=self.i18n['web.install.option.ignore_certificate.tip']) adv_opts = [ op_single, op_allow_urls, op_max, op_fs, op_nframe, op_ncache, op_insecure, op_igcert ] def_adv_opts = {op_single, op_allow_urls} if app.preset_options: for opt in adv_opts: if opt.id in app.preset_options: def_adv_opts.add(opt) check_options = MultipleSelectComponent( options=adv_opts, default_options=def_adv_opts, label=self.i18n['web.install.options.advanced'].capitalize()) res = watcher.request_confirmation( title=self.i18n['web.install.options_dialog.title'], body=None, components=[form_1, check_options], confirmation_label=self.i18n['continue'].capitalize(), deny_label=self.i18n['cancel'].capitalize()) if res: selected = [] if check_options.values: selected.extend(check_options.get_selected_values()) tray_mode = inp_tray.get_selected() if tray_mode is not None and tray_mode != 0: selected.append(tray_mode) custom_name = inp_name.get_value() if custom_name: app.name = custom_name custom_desc = inp_desc.get_value() if custom_desc: app.description = inp_desc.get_value() cat = inp_cat.get_selected() if cat != 0: app.categories = [cat] if icon_chooser.file_path: app.set_custom_icon(icon_chooser.file_path) selected.append('--icon={}'.format(icon_chooser.file_path)) app.save_icon = inp_icon.value == icon_op_disp return res, selected return False, []
def uninstall(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool: self.logger.info( "Checking if {} installation directory {} exists".format( pkg.name, pkg.installation_dir)) if not os.path.exists(pkg.installation_dir): watcher.show_message( title=self.i18n['error'], body=self.i18n['web.uninstall.error.install_dir.not_found']. format(bold(pkg.installation_dir)), type_=MessageType.ERROR) return False self.logger.info("Removing {} installation directory {}".format( pkg.name, pkg.installation_dir)) try: shutil.rmtree(pkg.installation_dir) except: watcher.show_message( title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format( bold(pkg.installation_dir)), type_=MessageType.ERROR) traceback.print_exc() return False self.logger.info("Checking if {} desktop entry file {} exists".format( pkg.name, pkg.desktop_entry)) if os.path.exists(pkg.desktop_entry): try: os.remove(pkg.desktop_entry) except: watcher.show_message( title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format( bold(pkg.desktop_entry)), type_=MessageType.ERROR) traceback.print_exc() autostart_path = pkg.get_autostart_path() if os.path.exists(autostart_path): try: os.remove(autostart_path) except: watcher.show_message( title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format( bold(autostart_path)), type_=MessageType.WARNING) traceback.print_exc() config_path = pkg.get_config_dir() if config_path and os.path.exists(config_path): try: shutil.rmtree(config_path) except: watcher.show_message( title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format( bold(config_path)), type_=MessageType.WARNING) traceback.print_exc() return True
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: local_config = {} thread_config = Thread(target=self._fill_config_async, args=(local_config, )) thread_config.start() res = SearchResult([], [], 0) installed = self.read_installed(disk_loader=disk_loader, limit=limit).installed if is_url: url = words[0:-1] if words.endswith('/') else words url_no_protocol = self._strip_url_protocol(url) installed_matches = [ app for app in installed if self._strip_url_protocol(app.url) == url_no_protocol ] if installed_matches: res.installed.extend(installed_matches) else: soup_map = self._map_url(url) if soup_map: soup, response = soup_map[0], soup_map[1] final_url = response.url if final_url.endswith('/'): final_url = final_url[0:-1] name = self._get_app_name(url_no_protocol, soup) desc = self._get_app_description(final_url, soup) icon_url = self._get_app_icon_url(final_url, soup) app = WebApplication(url=final_url, name=name, description=desc, icon_url=icon_url) if self.env_settings.get('electron') and self.env_settings[ 'electron'].get('version'): app.version = self.env_settings['electron']['version'] app.latest_version = app.version res.new = [app] else: lower_words = words.lower().strip() installed_matches = [ app for app in installed if lower_words in app.name.lower() ] index = self._read_search_index() if index: split_words = lower_words.split(' ') singleword = ''.join(lower_words) query_list = [*split_words, singleword] index_match_keys = set() for key in index: for query in query_list: if query in key: index_match_keys.update(index[key]) if not index_match_keys: self.logger.info( "Query '{}' was not found in the suggestion's index". format(words)) res.installed.extend(installed_matches) else: if not os.path.exists(SUGGESTIONS_CACHE_FILE): # if the suggestions cache was not found, it will not be possible to retrieve the matched apps # so only the installed matches will be returned self.logger.warning( "Suggestion cached file {} was not found".format( SUGGESTIONS_CACHE_FILE)) res.installed.extend(installed_matches) else: with open(SUGGESTIONS_CACHE_FILE) as f: cached_suggestions = yaml.safe_load(f.read()) if not cached_suggestions: # if no suggestion is found, it will not be possible to retrieve the matched apps # so only the installed matches will be returned self.logger.warning( "No suggestion found in {}".format( SUGGESTIONS_CACHE_FILE)) res.installed.extend(installed_matches) else: matched_suggestions = [ cached_suggestions[key] for key in index_match_keys if cached_suggestions.get(key) ] if not matched_suggestions: self.logger.warning( "No suggestion found for the search index keys: {}" .format(index_match_keys)) res.installed.extend(installed_matches) else: matched_suggestions.sort( key=lambda s: s.get('priority', 0), reverse=True) if installed_matches: # checking if any of the installed matches is one of the matched suggestions for sug in matched_suggestions: found = [ i for i in installed_matches if i.url == sug.get('url') ] if found: res.installed.extend(found) else: res.new.append( self._map_suggestion( sug).package) else: for sug in matched_suggestions: res.new.append( self._map_suggestion(sug).package) res.total += len(res.installed) res.total += len(res.new) if res.new: thread_config.join() if local_config['environment']['electron']['version']: for app in res.new: app.version = str( local_config['environment']['electron']['version']) app.latest_version = app.version return res
def launch(self, pkg: WebApplication): subprocess.Popen(pkg.get_command(), shell=user.is_root())
def launch(self, pkg: WebApplication): subprocess.Popen(pkg.get_exec_path())