Beispiel #1
0
    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'))
            }
Beispiel #2
0
    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)
Beispiel #3
0
 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 '')
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
    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, []
Beispiel #9
0
    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
Beispiel #10
0
    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
Beispiel #11
0
 def launch(self, pkg: WebApplication):
     subprocess.Popen(pkg.get_command(), shell=user.is_root())
Beispiel #12
0
 def launch(self, pkg: WebApplication):
     subprocess.Popen(pkg.get_exec_path())