Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
    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))
Ejemplo n.º 8
0
    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()
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
    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