Example #1
0
    def update_file(self, pkg: AppImage, root_password: Optional[str], watcher: ProcessWatcher):
        max_width = 350
        file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(),
                                            allowed_extensions={'AppImage', '*'},
                                            search_path=get_default_manual_installation_file_dir(),
                                            max_width=max_width)
        input_version = TextInputComponent(label=self.i18n['version'].capitalize(), max_width=max_width)
        file_chooser.observers.append(ManualInstallationFileObserver(None, input_version))

        while True:
            if watcher.request_confirmation(title=self.i18n['appimage.custom_action.manual_update.details'], body=None,
                                            components=[FormComponent(label='', components=[file_chooser, input_version], spaces=False)],
                                            confirmation_label=self.i18n['proceed'].capitalize(),
                                            deny_label=self.i18n['cancel'].capitalize(),
                                            min_height=100, max_width=max_width + 150):

                if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path) or not file_chooser.file_path.lower().strip().endswith('.appimage'):
                    watcher.request_confirmation(title=self.i18n['error'].capitalize(),
                                                 body=self.i18n['appimage.custom_action.install_file.invalid_file'],
                                                 deny_button=False)
                else:
                    break
            else:
                return False

        pkg.local_file_path = file_chooser.file_path
        pkg.version = input_version.get_value()

        reqs = UpgradeRequirements(to_install=None, to_remove=None, to_upgrade=[UpgradeRequirement(pkg=pkg)], cannot_upgrade=None)
        return self.upgrade(reqs, root_password=root_password, watcher=watcher)
Example #2
0
    def uninstall(self, pkg: AppImage, root_password: Optional[str], watcher: ProcessWatcher, disk_loader: DiskCacheLoader = None) -> TransactionResult:
        if os.path.exists(pkg.get_disk_cache_path()):
            handler = ProcessHandler(watcher)

            if not handler.handle(SystemProcess(new_subprocess(['rm', '-rf', pkg.get_disk_cache_path()]))):
                watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.uninstall.error.remove_folder'].format(bold(pkg.get_disk_cache_path())))
                return TransactionResult.fail()

            de_path = self._gen_desktop_entry_path(pkg)
            if os.path.exists(de_path):
                os.remove(de_path)

            self.revert_ignored_update(pkg)

        if pkg.symlink and os.path.islink(pkg.symlink):
            self.logger.info(f"Removing symlink '{pkg.symlink}'")

            try:
                os.remove(pkg.symlink)
                self.logger.info(f"symlink '{pkg.symlink}' successfully removed")
            except:
                msg = f"could not remove symlink '{pkg.symlink}'"
                self.logger.error(msg)

                if watcher:
                    watcher.print(f"[error] {msg}")

        self._add_self_latest_version(pkg)  # only for self installation
        return TransactionResult(success=True, installed=None, removed=[pkg])
Example #3
0
    def _download(self, pkg: AppImage, watcher: ProcessWatcher) -> Optional[Tuple[str, str]]:
        appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download

        if not appimage_url:
            watcher.show_message(title=self.i18n['error'],
                                 body=self.i18n['appimage.download.no_url'].format(app=bold(pkg.name)),
                                 type_=MessageType.ERROR)
            return

        file_name = appimage_url.split('/')[-1]
        pkg.version = pkg.latest_version
        pkg.url_download = appimage_url

        try:
            Path(DOWNLOAD_DIR).mkdir(exist_ok=True, parents=True)
        except OSError:
            watcher.show_message(title=self.i18n['error'],
                                 body=self.i18n['error.mkdir'].format(dir=bold(DOWNLOAD_DIR)),
                                 type_=MessageType.ERROR)
            return

        file_path = f'{DOWNLOAD_DIR}/{file_name}'
        downloaded = self.file_downloader.download(file_url=pkg.url_download, watcher=watcher,
                                                   output_path=file_path, cwd=str(Path.home()))

        if not downloaded:
            watcher.show_message(title=self.i18n['error'],
                                 body=self.i18n['appimage.download.error'].format(bold(pkg.url_download)),
                                 type_=MessageType.ERROR)
            return

        return file_name, file_path
Example #4
0
    def create_symlink(app: AppImage, file_path: str, logger: logging.Logger, watcher: ProcessWatcher = None):
        logger.info("Creating a symlink for '{}'".format(app.name))
        possible_names = (app.get_clean_name(), '{}-appimage'.format(app.get_clean_name()), app.name.lower(), '{}-appimage'.format(app.name.lower()))

        if os.path.exists(SYMLINKS_DIR) and not os.path.isdir(SYMLINKS_DIR):
            logger.warning("'{}' is not a directory. It will not be possible to create a symlink for '{}'".format(SYMLINKS_DIR, app.name))
            return

        available_system_dirs = (SYMLINKS_DIR, *(l for l in ('/usr/bin', '/usr/local/bin') if os.path.isdir(l)))

        # checking if the link already exists:

        available_name = None
        for name in possible_names:
            available_name = name
            for sysdir in available_system_dirs:
                if os.path.exists('{}/{}'.format(sysdir, name)):
                    available_name = None
                    break

            if available_name:
                break

        if not available_name:
            msg = "It was not possible to create a symlink for '{}' because the names {} are already available on the system".format(app.name,
                                                                                                                                     possible_names)
            logger.warning(msg)
            if watcher:
                watcher.print('[warning] {}'.format(msg))
        else:
            try:
                Path(SYMLINKS_DIR).mkdir(parents=True, exist_ok=True)
            except:
                logger.error("Could not create symlink directory '{}'".format(SYMLINKS_DIR))
                return

            symlink_path = '{}/{}'.format(SYMLINKS_DIR, available_name)

            try:
                os.symlink(src=file_path, dst=symlink_path)
                app.symlink = symlink_path

                msg = "symlink successfully created at {}".format(symlink_path)
                logger.info(msg)

                if watcher:
                    watcher.print(msg)
            except:
                msg = "Could not create the symlink '{}'".format(symlink_path)
                logger.error(msg)

                if watcher:
                    watcher.print('[error] {}'.format(msg))
Example #5
0
    def _add_self_latest_version(self, app: AppImage):
        if app.name == self.context.app_name and app.github == self.app_github and not app.url_download_latest_version:
            history = self.get_history(app)

            if not history or not history.history:
                self.logger.warning(f"Could not retrieve '{app.name}' versions. "
                                    f"It will not be possible to determine the current latest version")
            else:
                app.version = history.history[0]['0_version']
                app.latest_version = app.version
                app.url_download = history.history[0]['2_url_download']
                app.url_download_latest_version = app.url_download
Example #6
0
    def install_file(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
        max_width = 350
        file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(),
                                            allowed_extensions={'AppImage', '*'},
                                            search_path=get_default_manual_installation_file_dir(),
                                            max_width=max_width)
        input_name = TextInputComponent(label=self.i18n['name'].capitalize(), max_width=max_width)
        input_version = TextInputComponent(label=self.i18n['version'].capitalize(), max_width=max_width)
        file_chooser.observers.append(ManualInstallationFileObserver(input_name, input_version))

        input_description = TextInputComponent(label=self.i18n['description'].capitalize(), max_width=max_width)

        cat_ops = [InputOption(label=self.i18n['category.none'].capitalize(), value=0)]
        cat_ops.extend([InputOption(label=self.i18n.get(f'category.{c.lower()}', c.lower()).capitalize(), value=c) for c in self.context.default_categories])
        inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops,
                                        default_option=cat_ops[0], max_width=max_width)

        form = FormComponent(label='', components=[file_chooser, input_name, input_version, input_description, inp_cat],
                             spaces=False)

        while True:
            if watcher.request_confirmation(title=self.i18n['appimage.custom_action.install_file.details'], body=None,
                                            components=[form],
                                            confirmation_label=self.i18n['proceed'].capitalize(),
                                            deny_label=self.i18n['cancel'].capitalize(),
                                            min_height=100, max_width=max_width + 150):
                if not file_chooser.file_path or not os.path.isfile(file_chooser.file_path) or not file_chooser.file_path.lower().strip().endswith('.appimage'):
                    watcher.request_confirmation(title=self.i18n['error'].capitalize(),
                                                 body=self.i18n['appimage.custom_action.install_file.invalid_file'],
                                                 deny_button=False)
                elif not input_name.get_value() or not input_name.get_value().strip():
                    watcher.request_confirmation(title=self.i18n['error'].capitalize(),
                                                 body=self.i18n['appimage.custom_action.install_file.invalid_name'],
                                                 deny_button=False)
                else:
                    break
            else:
                return False

        appim = AppImage(i18n=self.i18n, imported=True)
        appim.name = input_name.get_value().strip()
        appim.local_file_path = file_chooser.file_path
        appim.version = input_version.get_value()
        appim.latest_version = input_version.get_value()
        appim.description = input_description.get_value()
        appim.categories = ['Imported']

        if inp_cat.get_selected() != cat_ops[0].value:
            appim.categories.append(inp_cat.get_selected())

        res = self.install(root_password=root_password, pkg=appim, disk_loader=None, watcher=watcher).success

        if res:
            appim.installed = True
            self.cache_to_disk(appim, None, False)

        return res
Example #7
0
    def self_install(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
        file_path = self._get_self_appimage_running()

        if not file_path:
            return False

        if self._is_self_installed():
            return False

        app = AppImage(name=self.context.app_name, version=self.context.app_version,
                       categories=['system'], author=self.context.app_name, github=self.app_github,
                       license='zlib/libpng')

        res = self._install(pkg=app, watcher=watcher,
                            pre_downloaded_file=(os.path.basename(file_path), file_path))
        if res.success:
            app.installed = True

            de_path = self._gen_desktop_entry_path(app)

            if de_path and os.path.exists(de_path):
                with open(de_path) as f:
                    bauh_entry = f.read()

                if bauh_entry:
                    comments = re.compile(r'Comment(\[\w+])?\s*=\s*(.+)').findall(bauh_entry)

                    if comments:
                        locale = f'{self.i18n.current_key}' if self.i18n.current_key != self.i18n.default_key else None

                        for key, desc in comments:
                            if desc:
                                if not key:
                                    app.description = desc  # default description

                                    if not locale:
                                        break

                                elif key == locale:
                                    app.description = desc  # localized description
                                    break
                    else:
                        self.context.logger.warning(f"Could not find the 'Comment' fields from {self.context.app_name}'s desktop entry")
                else:
                    self.context.logger.warning(f"{self.context.app_name} desktop entry is empty. Is is not possible to determine the 'description' field")

            else:
                self.context.logger.warning(f"{self.context.app_name} desktop file not found ({de_path}). It is not possible to determine the 'description' field")

            self.cache_to_disk(app, None, False)

        return res.success
Example #8
0
    def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
        if os.path.exists(pkg.get_disk_cache_path()):
            handler = ProcessHandler(watcher)

            if not handler.handle(SystemProcess(new_subprocess(['rm', '-rf', pkg.get_disk_cache_path()]))):
                watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.uninstall.error.remove_folder'].format(bold(pkg.get_disk_cache_path())))
                return False

            de_path = self._gen_desktop_entry_path(pkg)
            if os.path.exists(de_path):
                os.remove(de_path)

        return True
Example #9
0
    def downgrade(self, pkg: AppImage, root_password: str,
                  watcher: ProcessWatcher) -> bool:
        versions = self.get_history(pkg)

        if len(versions.history) == 1:
            watcher.show_message(
                title=self.i18n['appimage.downgrade.impossible.title'],
                body=self.i18n['appimage.downgrade.impossible.body'].format(
                    bold(pkg.name)),
                type_=MessageType.ERROR)
            return False
        elif versions.pkg_status_idx == -1:
            watcher.show_message(
                title=self.i18n['appimage.downgrade.impossible.title'],
                body=self.i18n['appimage.downgrade.unknown_version.body'].
                format(bold(pkg.name)),
                type_=MessageType.ERROR)
            return False
        elif versions.pkg_status_idx == len(versions.history) - 1:
            watcher.show_message(
                title=self.i18n['appimage.downgrade.impossible.title'],
                body=self.i18n['appimage.downgrade.first_version'].format(
                    bold(pkg.name)),
                type_=MessageType.ERROR)
            return False
        else:
            if self.uninstall(pkg, root_password, watcher):
                old_release = versions.history[versions.pkg_status_idx + 1]
                pkg.version = old_release['0_version']
                pkg.latest_version = pkg.version
                pkg.url_download = old_release['2_url_download']
                if self.install(pkg, root_password, watcher):
                    self.cache_to_disk(pkg, None, False)
                    return True
                else:
                    watcher.show_message(
                        title=self.i18n['error'],
                        body=self.i18n['appimage.downgrade.install_version'].
                        format(bold(pkg.version), bold(pkg.url_download)),
                        type_=MessageType.ERROR)
            else:
                watcher.show_message(
                    title=self.i18n['error'],
                    body=self.i18n['appimage.error.uninstall_current_version'].
                    format(bold(pkg.name)),
                    type_=MessageType.ERROR)

            return False
Example #10
0
    def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
        if is_url:
            return SearchResult.empty()

        apps_conn = self._get_db_connection(DATABASE_APPS_FILE)

        if not apps_conn:
            return SearchResult.empty()

        not_installed, found_map = [], {}

        try:
            cursor = apps_conn.cursor()
            cursor.execute(query.SEARCH_APPS_BY_NAME_OR_DESCRIPTION.format(words, words))

            idx = 0
            for r in cursor.fetchall():
                app = AppImage(*r, i18n=self.i18n)
                not_installed.append(app)
                found_map[self._gen_app_key(app)] = {'app': app, 'idx': idx}
                idx += 1
        except:
            self.logger.error("An exception happened while querying the 'apps' database")
            traceback.print_exc()

        try:
            installed = self.read_installed(connection=apps_conn, disk_loader=disk_loader, limit=limit, only_apps=False, pkg_types=None, internet_available=True).installed
        except:
            installed = None

        installed_found = []

        if installed:
            lower_words = words.lower()
            for appim in installed:
                found = False

                if not_installed and found_map:
                    key = self._gen_app_key(appim)
                    new_found = found_map.get(key)

                    if new_found:
                        if not appim.imported:
                            for attr in self.search_unfilled_attrs:
                                if getattr(appim, attr) is None:
                                    setattr(appim, attr, getattr(new_found['app'], attr))

                        del not_installed[new_found['idx']]
                        installed_found.append(appim)
                        found = True

                if not found and (lower_words in appim.name.lower() or (appim.description and lower_words in appim.description.lower())):
                    installed_found.append(appim)
        try:
            apps_conn.close()
        except:
            self.logger.error(f"An exception happened when trying to close the connection to database file '{DATABASE_APPS_FILE}'")
            traceback.print_exc()

        return SearchResult(new=not_installed, installed=installed_found, total=len(not_installed) + len(installed_found))
Example #11
0
    def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult:
        res = SearchResult([], [], 0)
        connection = self._get_db_connection(DB_APPS_PATH)

        if connection:
            try:
                cursor = connection.cursor()
                cursor.execute(query.SEARCH_APPS_BY_NAME_OR_DESCRIPTION.format(words, words))

                found_map = {}
                idx = 0
                for l in cursor.fetchall():
                    app = AppImage(*l)
                    res.new.append(app)
                    found_map[self._gen_app_key(app)] = {'app': app, 'idx': idx}
                    idx += 1

            finally:
                self._close_connection(DB_APPS_PATH, connection)

            if res.new:
                installed = self.read_installed(disk_loader, limit, only_apps=False, pkg_types=None, internet_available=True).installed

                if installed:
                    for iapp in installed:
                        key = self._gen_app_key(iapp)

                        new_found = found_map.get(key)

                        if new_found:
                            del res.new[new_found['idx']]
                            res.installed.append(iapp)

        res.total = len(res.installed) + len(res.new)
        return res
Example #12
0
    def list_suggestions(self, limit: int,
                         filter_installed: bool) -> List[PackageSuggestion]:
        res = []

        connection = self._get_db_connection(DB_APPS_PATH)

        if connection:
            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")
                try:
                    sugs = [l for l in file.text.split('\n') if l]

                    if filter_installed:
                        installed = {
                            i.name.lower()
                            for i in self.read_installed(
                                disk_loader=None,
                                connection=connection).installed
                        }
                    else:
                        installed = None

                    sugs_map = {}

                    for s in sugs:
                        lsplit = s.split('=')

                        name = lsplit[1].strip()

                        if limit <= 0 or len(sugs_map) < limit:
                            if not installed or not name.lower() in installed:
                                sugs_map[name] = SuggestionPriority(
                                    int(lsplit[0]))
                        else:
                            break

                    cursor = connection.cursor()
                    cursor.execute(
                        query.FIND_APPS_BY_NAME_FULL.format(','.join(
                            ["'{}'".format(s) for s in sugs_map.keys()])))

                    for t in cursor.fetchall():
                        app = AppImage(*t,
                                       i18n=self.i18n,
                                       custom_actions=self.custom_app_actions)
                        res.append(
                            PackageSuggestion(app, sugs_map[app.name.lower()]))
                    self.logger.info("Mapped {} suggestions".format(len(res)))
                except:
                    traceback.print_exc()
                finally:
                    self._close_connection(DB_APPS_PATH, connection)

        return res
Example #13
0
    def ignore_update(self, pkg: AppImage):
        current_ignored = self._read_ignored_updates()

        if pkg.name not in current_ignored:
            current_ignored.add(pkg.name)
            self._write_ignored_updates(current_ignored)

        pkg.updates_ignored = True
Example #14
0
    def get_info(self, pkg: AppImage) -> dict:
        data = pkg.get_data_to_cache()

        if data.get('url_download'):
            size = self.http_client.get_content_length(data['url_download'])
            if size:
                data['size'] = size

        return data
Example #15
0
    def launch(self, pkg: AppImage):
        installation_dir = pkg.get_disk_cache_path()
        if os.path.exists(installation_dir):
            appimag_path = self._find_appimage_file(installation_dir)

            if appimag_path:
                subprocess.Popen([appimag_path])
            else:
                self.logger.error("Could not find the AppImage file of '{}' in '{}'".format(pkg.name, installation_dir))
Example #16
0
    def revert_ignored_update(self, pkg: AppImage):
        current_ignored = self._read_ignored_updates()

        if current_ignored and pkg.name in current_ignored:
            current_ignored.remove(pkg.name)

            self._write_ignored_updates(current_ignored)

        pkg.updates_ignored = False
Example #17
0
    def launch(self, pkg: AppImage):
        installation_dir = pkg.get_disk_cache_path()
        if os.path.exists(installation_dir):
            appimag_path = util.find_appimage_file(installation_dir)

            if appimag_path:
                subprocess.Popen(args=[appimag_path], shell=True, env={**os.environ},
                                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
            else:
                self.logger.error(f"Could not find the AppImage file of '{pkg.name}' in '{installation_dir}'")
Example #18
0
    def update_file(self, pkg: AppImage, root_password: str,
                    watcher: ProcessWatcher):
        file_chooser = FileChooserComponent(
            label=self.i18n['file'].capitalize(),
            allowed_extensions={'AppImage'})
        input_version = TextInputComponent(
            label=self.i18n['version'].capitalize())

        while True:
            if watcher.request_confirmation(
                    title=self.
                    i18n['appimage.custom_action.manual_update.details'],
                    body=None,
                    components=[
                        FormComponent(label='',
                                      components=[file_chooser, input_version],
                                      spaces=False)
                    ],
                    confirmation_label=self.i18n['proceed'].capitalize(),
                    deny_label=self.i18n['cancel'].capitalize()):
                if not file_chooser.file_path or not os.path.isfile(
                        file_chooser.file_path):
                    watcher.request_confirmation(
                        title=self.i18n['error'].capitalize(),
                        body=self.i18n[
                            'appimage.custom_action.install_file.invalid_file'],
                        deny_button=False)
                else:
                    break
            else:
                return False

        pkg.local_file_path = file_chooser.file_path
        pkg.version = input_version.get_value()

        reqs = UpgradeRequirements(to_install=None,
                                   to_remove=None,
                                   to_upgrade=[UpgradeRequirement(pkg=pkg)],
                                   cannot_upgrade=None)
        return self.upgrade(reqs, root_password=root_password, watcher=watcher)
Example #19
0
    def run(self):
        self.taskman.register_task(self.task_id, self.i18n['appimage.task.symlink_check'], get_icon_path())

        if os.path.exists(INSTALLATION_PATH):
            installed_files = glob.glob('{}/*/*.json'.format(INSTALLATION_PATH))

            if installed_files:
                self.logger.info("Checking installed AppImage files with no symlinks created")

                progress_per_file = (1/len(installed_files)) * 100
                total_progress = 0
                for json_file in installed_files:
                    with open(json_file) as f:
                        try:
                            data = json.loads(f.read())
                        except:
                            self.logger.warning("Could not parse data from '{}'".format(json_file))
                            data = None

                    if data and not data.get('symlink'):
                        if not data.get('install_dir'):
                            data['install_dir'] = '/'.join(json_file.split('/')[0:-1])

                        app = AppImage(**data, i18n=self.i18n)

                        file_path = util.find_appimage_file(app.install_dir)

                        if file_path:
                            self.create_symlink(app, file_path, self.logger)
                            data['symlink'] = app.symlink

                            # caching
                            try:
                                with open(json_file, 'w+') as f:
                                    f.write(json.dumps(data))
                            except:
                                self.logger.warning("Could not update cached data on '{}'".format(json_file))
                                traceback.print_exc()

                        else:
                            self.logger.warning("No AppImage file found on installation dir '{}'".format(file_path))

                    total_progress += progress_per_file
                    self.taskman.update_progress(self.task_id, total_progress, '')

                self.taskman.update_progress(self.task_id, 100, '')
                self.taskman.finish_task(self.task_id)
                return

        self.logger.info("No AppImage applications found. Aborting")
        self.taskman.update_progress(self.task_id, 100, '')
        self.taskman.finish_task(self.task_id)
Example #20
0
    def launch(self, pkg: AppImage):
        installation_dir = pkg.get_disk_cache_path()
        if os.path.exists(installation_dir):
            appimag_path = util.find_appimage_file(installation_dir)

            if appimag_path:
                subprocess.Popen(args=[appimag_path],
                                 shell=True,
                                 env={**os.environ})
            else:
                self.logger.error(
                    "Could not find the AppImage file of '{}' in '{}'".format(
                        pkg.name, installation_dir))
Example #21
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,
                       connection: sqlite3.Connection = None) -> SearchResult:
        res = SearchResult([], [], 0)

        if os.path.exists(INSTALLATION_PATH):
            installed = run_cmd('ls {}*/data.json'.format(INSTALLATION_PATH),
                                print_error=False)

            if installed:
                names = set()
                for path in installed.split('\n'):
                    if path:
                        with open(path) as f:
                            app = AppImage(installed=True,
                                           **json.loads(f.read()))
                            app.icon_url = app.icon_path

                        res.installed.append(app)
                        names.add("'{}'".format(app.name.lower()))

                if res.installed:
                    con = self._get_db_connection(
                        DB_APPS_PATH) if not connection else connection

                    if con:
                        try:
                            cursor = con.cursor()
                            cursor.execute(
                                query.FIND_APPS_BY_NAME.format(
                                    ','.join(names)))

                            for tup in cursor.fetchall():
                                for app in res.installed:
                                    if app.name.lower() == tup[0].lower() and (
                                            not app.github
                                            or app.github.lower()
                                            == tup[1].lower()):
                                        app.update = tup[2] > app.version

                                        if app.update:
                                            app.latest_version = tup[2]
                                            app.url_download_latest_version = tup[
                                                3]

                                        break
                        except:
                            traceback.print_exc()
                        finally:
                            if not connection:
                                self._close_connection(DB_APPS_PATH, con)

        res.total = len(res.installed)
        return res
Example #22
0
    def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[PackageSuggestion]]:
        if limit == 0:
            return

        connection = self._get_db_connection(DATABASE_APPS_FILE)

        if connection:
            self.suggestions_downloader.taskman = TaskManager()
            suggestions = tuple(self.suggestions_downloader.read())

            if not suggestions:
                self.logger.warning("Could not read AppImage suggestions")
                return
            else:
                self.logger.info("Mapping AppImage suggestions")
                try:
                    if filter_installed:
                        installed = {i.name.lower() for i in self.read_installed(disk_loader=None,
                                                                                 connection=connection).installed}
                    else:
                        installed = None

                    sugs_map = {}

                    for s in suggestions:
                        lsplit = s.split('=')

                        name = lsplit[1].strip()

                        if limit < 0 or len(sugs_map) < limit:
                            if not installed or not name.lower() in installed:
                                sugs_map[name] = SuggestionPriority(int(lsplit[0]))
                        else:
                            break

                    cursor = connection.cursor()
                    cursor.execute(query.FIND_APPS_BY_NAME_FULL.format(','.join([f"'{s}'" for s in sugs_map.keys()])))

                    res = []
                    for t in cursor.fetchall():
                        app = AppImage(*t, i18n=self.i18n)
                        res.append(PackageSuggestion(app, sugs_map[app.name.lower()]))

                    self.logger.info(f"Mapped {len(res)} AppImage suggestions")
                    return res
                except:
                    traceback.print_exc()
                finally:
                    connection.close()
Example #23
0
    def get_info(self, pkg: AppImage) -> dict:
        data = pkg.get_data_to_cache()

        if data.get('url_download'):
            size = self.http_client.get_content_length(data['url_download'])
            if size:
                data['size'] = size

        categories = data.get('categories')

        if categories:
            data['categories'] = [
                self.i18n.get('category.{}'.format(c.lower()),
                              self.i18n.get(c, c)).capitalize()
                for c in data['categories']
            ]

        return data
Example #24
0
    def get_info(self, pkg: AppImage) -> dict:
        data = pkg.get_data_to_cache()

        if not pkg.installed:
            for key in ('install_dir', 'symlink', 'icon_path'):
                if key in data:
                    del data[key]

        if data.get('url_download'):
            size = self.http_client.get_content_length(data['url_download'])
            if size:
                data['size'] = size

        categories = data.get('categories')

        if categories:
            data['categories'] = [self.i18n.get(f'category.{c.lower()}', self.i18n.get(c, c)).capitalize() for c in data['categories']]

        if data.get('symlink') and not os.path.islink(data['symlink']):
            del data['symlink']

        return data
Example #25
0
    def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
        res = []

        connection = self._get_db_connection(DB_APPS_PATH)

        if connection:
            try:
                sugs = [(i, p) for i, p in suggestions.ALL.items()]
                sugs.sort(key=lambda t: t[1].value, reverse=True)

                if limit > 0:
                    sugs = sugs[0:limit]

                cursor = connection.cursor()
                cursor.execute(query.FIND_APPS_BY_NAME_FULL.format(','.join(["'{}'".format(s[0]) for s in sugs])))

                for t in cursor.fetchall():
                    app = AppImage(*t)
                    res.append(PackageSuggestion(app, suggestions.ALL.get(app.name.lower())))
            finally:
                self._close_connection(DB_APPS_PATH, connection)

        return res
Example #26
0
    def install(self, pkg: AppImage, root_password: str,
                watcher: ProcessWatcher) -> bool:
        handler = ProcessHandler(watcher)

        out_dir = INSTALLATION_PATH + pkg.name.lower()
        counter = 0
        while True:
            if os.path.exists(out_dir):
                self.logger.info(
                    "Installation dir '{}' already exists. Generating a different one"
                    .format(out_dir))
                out_dir += '-{}'.format(counter)
                counter += 1
            else:
                break

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        pkg.install_dir = out_dir

        if pkg.imported:

            downloaded, file_name = True, pkg.local_file_path.split('/')[-1]

            file_path = out_dir + '/' + file_name

            try:
                moved, output = handler.handle_simple(
                    SimpleProcess(['mv', pkg.local_file_path, file_path]))
            except:
                self.logger.error("Could not rename file '' as '{}'".format(
                    pkg.local_file_path, file_path))
                moved = False

            if not moved:
                watcher.show_message(
                    title=self.i18n['error'].capitalize(),
                    body=self.i18n['appimage.install.imported.rename_error'].
                    format(bold(pkg.local_file_path.split('/')[-1]),
                           bold(output)),
                    type_=MessageType.ERROR)
                return False

        else:
            appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download
            file_name = appimage_url.split('/')[-1]
            pkg.version = pkg.latest_version
            pkg.url_download = appimage_url

            file_path = out_dir + '/' + file_name
            downloaded = self.file_downloader.download(
                file_url=pkg.url_download,
                watcher=watcher,
                output_path=file_path,
                cwd=str(Path.home()))

        if downloaded:
            watcher.change_substatus(
                self.i18n['appimage.install.permission'].format(
                    bold(file_name)))
            permission_given = handler.handle(
                SystemProcess(new_subprocess(['chmod', 'a+x', file_path])))

            if permission_given:

                watcher.change_substatus(
                    self.i18n['appimage.install.extract'].format(
                        bold(file_name)))

                try:
                    res, output = handler.handle_simple(
                        SimpleProcess([file_path, '--appimage-extract'],
                                      cwd=out_dir))

                    if 'Error: Failed to register AppImage in AppImageLauncherFS' in output:
                        watcher.show_message(
                            title=self.i18n['error'],
                            body=self.
                            i18n['appimage.install.appimagelauncher.error'].
                            format(appimgl=bold('AppImageLauncher'),
                                   app=bold(pkg.name)),
                            type_=MessageType.ERROR)
                        handler.handle(
                            SystemProcess(
                                new_subprocess(['rm', '-rf', out_dir])))
                        return False
                except:
                    watcher.show_message(title=self.i18n['error'],
                                         body=traceback.format_exc(),
                                         type_=MessageType.ERROR)
                    traceback.print_exc()
                    handler.handle(
                        SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
                    return False

                watcher.change_substatus(
                    self.i18n['appimage.install.desktop_entry'])
                extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root')

                if os.path.exists(extracted_folder):
                    desktop_entry = self._find_desktop_file(extracted_folder)

                    with open('{}/{}'.format(extracted_folder,
                                             desktop_entry)) as f:
                        de_content = f.read()

                    de_content = RE_DESKTOP_EXEC.sub(
                        'Exec={}\n'.format(file_path), de_content)

                    extracted_icon = self._find_icon_file(extracted_folder)

                    if extracted_icon:
                        icon_path = out_dir + '/logo.' + extracted_icon.split(
                            '.')[-1]
                        shutil.copy(
                            '{}/{}'.format(extracted_folder, extracted_icon),
                            icon_path)
                        de_content = RE_DESKTOP_ICON.sub(
                            'Icon={}\n'.format(icon_path), de_content)
                        pkg.icon_path = icon_path

                    Path(DESKTOP_ENTRIES_PATH).mkdir(parents=True,
                                                     exist_ok=True)

                    with open(self._gen_desktop_entry_path(pkg), 'w+') as f:
                        f.write(de_content)

                    shutil.rmtree(extracted_folder)
                    return True
                else:
                    watcher.show_message(
                        title=self.i18n['error'],
                        body='Could extract content from {}'.format(
                            bold(file_name)),
                        type_=MessageType.ERROR)
        else:
            watcher.show_message(
                title=self.i18n['error'],
                body=self.i18n['appimage.install.download.error'].format(
                    bold(pkg.url_download)),
                type_=MessageType.ERROR)

        handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
        return False
Example #27
0
    def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1, only_apps: bool = False,
                       pkg_types: Optional[Set[Type[SoftwarePackage]]] = None, internet_available: bool = None, connection: sqlite3.Connection = None) -> SearchResult:
        installed_apps = []
        res = SearchResult(installed_apps, [], 0)

        if os.path.exists(INSTALLATION_DIR):
            installed = glob.glob(f'{INSTALLATION_DIR}/*/data.json')

            if installed:
                names = set()
                for path in installed:
                    if path:
                        with open(path) as f:
                            app = AppImage(installed=True, i18n=self.i18n, **json.loads(f.read()))
                            app.icon_url = app.icon_path

                        installed_apps.append(app)
                        names.add(f"'{app.name.lower()}'")

                if installed_apps:
                    apps_con = self._get_db_connection(DATABASE_APPS_FILE) if not connection else connection

                    if apps_con:
                        try:
                            cursor = apps_con.cursor()
                            cursor.execute(query.FIND_APPS_BY_NAME.format(','.join(names)))

                            for tup in cursor.fetchall():
                                for app in installed_apps:
                                    if app.name.lower() == tup[0].lower() and (not app.github or app.github.lower() == tup[1].lower()):
                                        continuous_version = app.version == 'continuous'
                                        continuous_update = tup[2] == 'continuous'

                                        if tup[3]:
                                            if continuous_version and not continuous_update:
                                                app.update = True
                                            elif continuous_update and not continuous_version:
                                                app.update = False
                                            else:
                                                try:
                                                    app.update = parse_version(tup[2]) > parse_version(app.version) if tup[2] else False
                                                except:
                                                    app.update = False
                                                    traceback.print_exc()

                                        if app.update:
                                            app.latest_version = tup[2]
                                            app.url_download_latest_version = tup[3]

                                        break
                        except:
                            self.logger.error(f"An exception happened while querying the database file '{DATABASE_APPS_FILE}'")
                            traceback.print_exc()
                        finally:
                            if not connection:  # the connection can only be closed if it was opened within this method
                                apps_con.close()

                    ignored_updates = self._read_ignored_updates()

                    if ignored_updates:
                        for app in installed_apps:
                            if app.supports_ignored_updates() and app.name in ignored_updates:
                                app.updates_ignored = True

        res.total = len(res.installed)
        return res
Example #28
0
    def install_file(self, root_password: str,
                     watcher: ProcessWatcher) -> bool:
        file_chooser = FileChooserComponent(
            label=self.i18n['file'].capitalize(),
            allowed_extensions={'AppImage'})
        input_name = TextInputComponent(label=self.i18n['name'].capitalize())
        input_version = TextInputComponent(
            label=self.i18n['version'].capitalize())
        input_description = TextInputComponent(
            label=self.i18n['description'].capitalize())

        cat_ops = [
            InputOption(label=self.i18n['category.none'].capitalize(), value=0)
        ]
        cat_ops.extend([
            InputOption(label=self.i18n[c.lower()].capitalize(), value=c)
            for c in self.context.default_categories
        ])
        inp_cat = SingleSelectComponent(label=self.i18n['category'],
                                        type_=SelectViewType.COMBO,
                                        options=cat_ops,
                                        default_option=cat_ops[0])

        form = FormComponent(label='',
                             components=[
                                 file_chooser, input_name, input_version,
                                 input_description, inp_cat
                             ],
                             spaces=False)

        while True:
            if watcher.request_confirmation(
                    title=self.
                    i18n['appimage.custom_action.install_file.details'],
                    body=None,
                    components=[form],
                    confirmation_label=self.i18n['proceed'].capitalize(),
                    deny_label=self.i18n['cancel'].capitalize()):
                if not file_chooser.file_path or not os.path.isfile(
                        file_chooser.file_path):
                    watcher.request_confirmation(
                        title=self.i18n['error'].capitalize(),
                        body=self.i18n[
                            'appimage.custom_action.install_file.invalid_file'],
                        deny_button=False)
                elif not input_name.get_value() or not input_name.get_value(
                ).strip():
                    watcher.request_confirmation(
                        title=self.i18n['error'].capitalize(),
                        body=self.i18n[
                            'appimage.custom_action.install_file.invalid_name'],
                        deny_button=False)
                else:
                    break
            else:
                return False

        appim = AppImage(i18n=self.i18n,
                         imported=True,
                         custom_actions=self.custom_app_actions)
        appim.name = input_name.get_value().strip()
        appim.local_file_path = file_chooser.file_path
        appim.version = input_version.get_value()
        appim.latest_version = input_version.get_value()
        appim.description = input_description.get_value()
        appim.categories = ['Imported']

        if inp_cat.get_selected() != cat_ops[0].value:
            appim.categories.append(inp_cat.get_selected())

        installed = self.install(root_password=root_password,
                                 pkg=appim,
                                 watcher=watcher)

        if installed:
            appim.installed = True
            self.cache_to_disk(appim, None, False)

        return installed
Example #29
0
    def get_screenshots(self, pkg: AppImage) -> List[str]:
        if pkg.has_screenshots():
            return [pkg.url_screenshot]

        return []
Example #30
0
    def _install(self, pkg: AppImage, watcher: ProcessWatcher, pre_downloaded_file: Optional[Tuple[str, str]] = None) \
            -> TransactionResult:

        handler = ProcessHandler(watcher)
        out_dir = f'{INSTALLATION_DIR}/{pkg.get_clean_name()}'
        counter = 0
        while True:
            if os.path.exists(out_dir):
                self.logger.info(f"Installation dir '{out_dir}' already exists. Generating a different one")
                out_dir += f'-{counter}'
                counter += 1
            else:
                break

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        pkg.install_dir = out_dir

        if pkg.imported:

            downloaded, file_name = True, pkg.local_file_path.split('/')[-1]

            install_file_path = out_dir + '/' + file_name

            try:
                moved, output = handler.handle_simple(SimpleProcess(['mv', pkg.local_file_path, install_file_path]))
            except:
                output = ''
                self.logger.error(f"Could not rename file '{pkg.local_file_path}' as '{install_file_path}'")
                moved = False

            if not moved:
                watcher.show_message(title=self.i18n['error'].capitalize(),
                                     body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]),
                                                                                                     bold(output)),
                                     type_=MessageType.ERROR)

                return TransactionResult.fail()

        else:
            download_data = pre_downloaded_file if pre_downloaded_file else self._download(pkg, watcher)

            if not download_data:
                return TransactionResult.fail()

            file_name, download_path = download_data[0], download_data[1]

            install_file_path = f'{out_dir}/{file_name}'

            try:
                shutil.move(download_path, install_file_path)
            except OSError:
                watcher.show_message(title=self.i18n['error'],
                                     body=self.i18n['error.mvfile'].formmat(src=bold(download_path),
                                                                            dest=bold(install_file_path)))
                return TransactionResult.fail()

        watcher.change_substatus(self.i18n['appimage.install.permission'].format(bold(file_name)))
        permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', install_file_path])))

        if permission_given:

            watcher.change_substatus(self.i18n['appimage.install.extract'].format(bold(file_name)))

            try:
                res, output = handler.handle_simple(
                    SimpleProcess([install_file_path, '--appimage-extract'], cwd=out_dir))

                if 'Error: Failed to register AppImage in AppImageLauncherFS' in output:
                    watcher.show_message(title=self.i18n['error'],
                                         body=self.i18n['appimage.install.appimagelauncher.error'].format(
                                             appimgl=bold('AppImageLauncher'), app=bold(pkg.name)),
                                         type_=MessageType.ERROR)
                    handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
                    return TransactionResult.fail()
            except:
                watcher.show_message(title=self.i18n['error'],
                                     body=traceback.format_exc(),
                                     type_=MessageType.ERROR)
                traceback.print_exc()
                handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
                return TransactionResult.fail()

            watcher.change_substatus(self.i18n['appimage.install.desktop_entry'])
            extracted_folder = f'{out_dir}/squashfs-root'

            if os.path.exists(extracted_folder):
                desktop_entry = self._find_desktop_file(extracted_folder)

                with open(f'{extracted_folder}/{desktop_entry}') as f:
                    de_content = f.read()

                if de_content:
                    de_content = replace_desktop_entry_exec_command(desktop_entry=de_content,
                                                                    appname=pkg.name,
                                                                    file_path=install_file_path)
                extracted_icon = self._find_icon_file(extracted_folder)

                if extracted_icon:
                    icon_path = out_dir + '/logo.' + extracted_icon.split('/')[-1].split('.')[-1]
                    shutil.copy(extracted_icon, icon_path)

                    if de_content:
                        de_content = RE_DESKTOP_ICON.sub(f'Icon={icon_path}\n', de_content)

                    pkg.icon_path = icon_path

                if not de_content:
                    de_content = pkg.to_desktop_entry()

                Path(DESKTOP_ENTRIES_DIR).mkdir(parents=True, exist_ok=True)

                with open(self._gen_desktop_entry_path(pkg), 'w+') as f:
                    f.write(de_content)

                try:
                    shutil.rmtree(extracted_folder)
                except:
                    traceback.print_exc()

                SymlinksVerifier.create_symlink(app=pkg, file_path=install_file_path, logger=self.logger,
                                                watcher=watcher)
                return TransactionResult(success=True, installed=[pkg], removed=[])
            else:
                watcher.show_message(title=self.i18n['error'],
                                     body=f'Could extract content from {bold(file_name)}',
                                     type_=MessageType.ERROR)

        handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
        return TransactionResult.fail()