def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: if is_url or (not snap.is_installed() and not snapd.is_running()): return SearchResult([], [], 0) snapd_client = SnapdClient(self.logger) apps_found = snapd_client.query(words) res = SearchResult([], [], 0) if apps_found: installed = self.read_installed(disk_loader).installed for app_json in apps_found: already_installed = None if installed: already_installed = [ i for i in installed if i.id == app_json.get('id') ] already_installed = already_installed[ 0] if already_installed else None if already_installed: res.installed.append(already_installed) else: res.new.append(self._map_to_app(app_json, installed=False)) res.total = len(res.installed) + len(res.new) return res
def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]: res = [] if snapd.is_running(): self.logger.info( 'Downloading suggestions file {}'.format(SUGGESTIONS_FILE)) file = self.http_client.get(SUGGESTIONS_FILE) if not file or not file.text: self.logger.warning( "No suggestion found in {}".format(SUGGESTIONS_FILE)) return res else: self.logger.info('Mapping suggestions') suggestions, threads = [], [] snapd_client = SnapdClient(self.logger) installed = { s['name'].lower() for s in snapd_client.list_all_snaps() } for l in file.text.split('\n'): if l: if limit <= 0 or len(suggestions) < limit: sug = l.strip().split('=') name = sug[1] if not installed or name not in installed: cached_sug = self.suggestions_cache.get(name) if cached_sug: res.append(cached_sug) else: t = Thread(target=self._fill_suggestion, args=(name, SuggestionPriority( int(sug[0])), snapd_client, res)) t.start() threads.append(t) time.sleep(0.001) # to avoid being blocked else: break for t in threads: t.join() res.sort(key=lambda s: s.priority.value, reverse=True) return res
def change_channel(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: if not internet.is_available(): raise NoInternetException() try: channel = self._request_channel_installation( pkg=pkg, snap_config=None, snapd_client=SnapdClient(self.logger), watcher=watcher, exclude_current=True) if not channel: watcher.show_message( title=self.i18n['snap.action.channel.label'], body=self.i18n['snap.action.channel.error.no_channel']) return False return ProcessHandler(watcher).handle_simple( snap.refresh_and_stream(app_name=pkg.name, root_password=root_password, channel=channel))[0] except: return False
def get_info(self, pkg: SnapApplication) -> dict: info = { 'description': pkg.description, 'developer': pkg.developer, 'license': pkg.license, 'contact': pkg.contact, 'snap-id': pkg.id, 'name': pkg.name, 'publisher': pkg.publisher, 'revision': pkg.rev, 'tracking': pkg.tracking, 'channel': pkg.channel, 'type': pkg.type } if pkg.installed: commands = [ *{ c['name'] for c in SnapdClient(self.logger).list_commands(pkg.name) } ] commands.sort() info['commands'] = commands if pkg.installed_size: info['installed_size']: get_human_size_str(pkg.installed_size) elif pkg.download_size: info['download_size'] = get_human_size_str(pkg.download_size) return info
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: if snap.is_installed() and snapd.is_running(): snapd_client = SnapdClient(self.logger) app_names = {a['snap'] for a in snapd_client.list_only_apps()} installed = [ self._map_to_app(app_json=appjson, installed=True, disk_loader=disk_loader, is_application=app_names and appjson['name'] in app_names) for appjson in snapd_client.list_all_snaps() ] return SearchResult(installed, None, len(installed)) else: return SearchResult([], None, 0)
def launch(self, pkg: SnapApplication): commands = SnapdClient(self.logger).list_commands(pkg.name) if commands: if len(commands) == 1: cmd = commands[0]['name'] else: desktop_cmd = [c for c in commands if 'desktop-file' in c] if desktop_cmd: cmd = desktop_cmd[0]['name'] else: cmd = commands[0]['name'] self.logger.info("Running '{}': {}".format(pkg.name, cmd)) snap.run(cmd)
def _fill_suggestion(self, name: str, priority: SuggestionPriority, snapd_client: SnapdClient, out: List[PackageSuggestion]): res = snapd_client.find_by_name(name) if res: if len(res) == 1: app_json = res[0] else: jsons_found = [p for p in res if p['name'] == name] app_json = jsons_found[0] if jsons_found else None if app_json: sug = PackageSuggestion(self._map_to_app(app_json, False), priority) self.suggestions_cache.add(name, sug) out.append(sug) return self.logger.warning("Could not retrieve suggestion '{}'".format(name))
def _request_channel_installation( self, pkg: SnapApplication, snap_config: Optional[dict], snapd_client: SnapdClient, watcher: ProcessWatcher, exclude_current: bool = False) -> Optional[str]: if snap_config is None or snap_config['install_channel']: try: data = [ r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name ] except: self.logger.warning( "snapd client could not retrieve channels for '{}'".format( pkg.name)) return if not data: self.logger.warning( "snapd client could find a match for name '{}' when retrieving its channels" .format(pkg.name)) else: if not data[0].get('channels'): self.logger.info( "No channel available for '{}'. Skipping selection.". format(pkg.name)) else: if pkg.channel: current_channel = pkg.channel if '/' in pkg.channel else 'latest/{}'.format( pkg.channel) else: current_channel = 'latest/{}'.format(data[0].get( 'channel', 'stable')) opts = [] def_opt = None for channel in sorted(data[0]['channels'].keys()): if exclude_current: if channel != current_channel: opts.append( InputOption(label=channel, value=channel)) else: op = InputOption(label=channel, value=channel) opts.append(op) if not def_opt and channel == current_channel: def_opt = op if not opts: self.logger.info( "No different channel available for '{}'. Skipping selection." .format(pkg.name)) return select = SingleSelectComponent( label='', options=opts, default_option=def_opt if def_opt else opts[0], type_=SelectViewType.RADIO) if not watcher.request_confirmation( title=self. i18n['snap.install.available_channels.title'], body=self.i18n['snap.install.channel.body'] + ':', components=[select], confirmation_label=self.i18n['proceed'].capitalize( ), deny_label=self.i18n['cancel'].capitalize()): raise Exception('aborted') else: return select.get_selected()
def install(self, pkg: SnapApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: # retrieving all installed so it will be possible to know the additional installed runtimes after the operation succeeds if not snap.is_installed(): watcher.print("'snap' seems not to be installed") return TransactionResult.fail() if not snapd.is_running(): watcher.print("'snapd' seems not to be running") return TransactionResult.fail() installed_names = { s['name'] for s in SnapdClient(self.logger).list_all_snaps() } client = SnapdClient(self.logger) snap_config = read_config() try: channel = self._request_channel_installation( pkg=pkg, snap_config=snap_config, snapd_client=client, watcher=watcher) pkg.channel = channel except: watcher.print('Aborted by user') return TransactionResult.fail() res, output = ProcessHandler(watcher).handle_simple( snap.install_and_stream(app_name=pkg.name, confinement=pkg.confinement, root_password=root_password, channel=channel)) if 'error:' in output: res = False if 'not available on stable' in output: channels = RE_AVAILABLE_CHANNELS.findall(output) if channels: opts = [ InputOption(label=c[0], value=c[1]) for c in channels ] channel_select = SingleSelectComponent( type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0]) body = '<p>{}.</p>'.format( self.i18n['snap.install.available_channels.message']. format(bold(self.i18n['stable']), bold(pkg.name))) body += '<p>{}:</p>'.format( self.i18n['snap.install.available_channels.help']) if watcher.request_confirmation( title=self. i18n['snap.install.available_channels.title'], body=body, components=[channel_select], confirmation_label=self.i18n['continue'], deny_label=self.i18n['cancel']): self.logger.info( "Installing '{}' with the custom command '{}'". format(pkg.name, channel_select.value)) res = ProcessHandler(watcher).handle( SystemProcess( new_root_subprocess( channel_select.value.value.split(' '), root_password=root_password))) return self._gen_installation_response( success=res, pkg=pkg, installed=installed_names, disk_loader=disk_loader) else: self.logger.error( "Could not find available channels in the installation output: {}" .format(output)) return self._gen_installation_response(success=res, pkg=pkg, installed=installed_names, disk_loader=disk_loader)
def list_suggestions( self, limit: int, filter_installed: bool) -> Optional[List[PackageSuggestion]]: if limit == 0 or not snapd.is_running(): return if self.is_local_suggestions_file_mapped(): suggestions_str = self._read_local_suggestions_file() else: suggestions_str = self._download_remote_suggestions_file() if suggestions_str is None: return if not suggestions_str: self.logger.warning( f"No Snap suggestion found in {self.suggestions_url}") return ids_prios = suggestions.parse(suggestions_str, self.logger, 'Snap') if not ids_prios: self.logger.warning( f"No Snap suggestion could be parsed from {self.suggestions_url}" ) return suggestion_by_priority = suggestions.sort_by_priority(ids_prios) snapd_client = SnapdClient(self.logger) if filter_installed: installed = { s['name'].lower() for s in snapd_client.list_all_snaps() } if installed: suggestion_by_priority = tuple(n for n in suggestion_by_priority if n not in installed) if suggestion_by_priority and 0 < limit < len(suggestion_by_priority): suggestion_by_priority = suggestion_by_priority[0:limit] self.logger.info( f'Available Snap suggestions: {len(suggestion_by_priority)}') if not suggestion_by_priority: return self.logger.info("Mapping Snap suggestions") instances, threads = [], [] res, cached_count = [], 0 for name in suggestion_by_priority: cached_sug = self.suggestions_cache.get(name) if cached_sug: res.append(cached_sug) cached_count += 1 else: t = Thread(target=self._fill_suggestion, args=(name, ids_prios[name], snapd_client, res)) t.start() threads.append(t) time.sleep(0.001) # to avoid being blocked for t in threads: t.join() if cached_count > 0: self.logger.info( f"Returning {cached_count} cached Snap suggestions") return res