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)
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])
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
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))
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
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
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
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
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
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))
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
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
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
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
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))
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
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}'")
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)
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)
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))
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
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()
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
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
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
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
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
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
def get_screenshots(self, pkg: AppImage) -> List[str]: if pkg.has_screenshots(): return [pkg.url_screenshot] return []
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()