def __init__(self, context: ApplicationContext): super(AppImageManager, self).__init__(context=context) self.i18n = context.i18n self.api_cache = context.cache_factory.new() context.disk_loader_factory.map(AppImageManager, self.api_cache) self.enabled = True self.http_client = context.http_client self.logger = context.logger self.file_downloader = context.file_downloader self.dbs_updater = DatabaseUpdater(http_client=context.http_client, logger=context.logger)
def update_database(self, root_password: str, watcher: ProcessWatcher) -> bool: db_updater = DatabaseUpdater(i18n=self.i18n, http_client=self.context.http_client, logger=self.context.logger, watcher=watcher, taskman=TaskManager()) res = db_updater.download_databases() return res
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): create_config = CreateConfigFile(taskman=task_manager, configman=self.configman, i18n=self.i18n, task_icon_path=get_icon_path(), logger=self.logger) create_config.start() symlink_check = SymlinksVerifier(taskman=task_manager, i18n=self.i18n, logger=self.logger) symlink_check.start() if internet_available: DatabaseUpdater(taskman=task_manager, i18n=self.context.i18n, create_config=create_config, http_client=self.context.http_client, logger=self.context.logger).start() AppImageSuggestionsDownloader(taskman=task_manager, i18n=self.context.i18n, http_client=self.context.http_client, logger=self.context.logger, create_config=create_config).start()
def _start_updater(self): local_config = read_config(update_file=True) interval = local_config['db_updater']['interval'] or 20 * 60 updater = DatabaseUpdater(http_client=self.context.http_client, logger=self.context.logger, db_locks=self.db_locks, interval=interval) if local_config['db_updater']['enabled']: updater.start() else: updater.download_databases() # only once
def prepare(self, task_manager: TaskManager, root_password: Optional[str], internet_available: bool): create_config = CreateConfigFile(taskman=task_manager, configman=self.configman, i18n=self.i18n, task_icon_path=get_icon_path(), logger=self.logger) create_config.start() symlink_check = SymlinksVerifier(taskman=task_manager, i18n=self.i18n, logger=self.logger) symlink_check.start() if internet_available: DatabaseUpdater(taskman=task_manager, i18n=self.context.i18n, create_config=create_config, http_client=self.context.http_client, logger=self.context.logger).start() if not self.suggestions_downloader.is_custom_local_file_mapped(): self.suggestions_downloader.taskman = task_manager self.suggestions_downloader.create_config = create_config self.suggestions_downloader.register_task() self.suggestions_downloader.start()
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): local_config = read_config(update_file=True) interval = local_config['db_updater']['interval'] or 20 * 60 updater = DatabaseUpdater(task_man=task_manager, i18n=self.context.i18n, http_client=self.context.http_client, logger=self.context.logger, db_locks=self.db_locks, interval=interval) if local_config['db_updater']['enabled']: updater.start() elif internet_available: updater.download_databases() # only once
class AppImageManager(SoftwareManager): def __init__(self, context: ApplicationContext): super(AppImageManager, self).__init__(context=context) self.i18n = context.i18n self.api_cache = context.cache_factory.new() context.disk_loader_factory.map(AppImageManager, self.api_cache) self.enabled = True self.http_client = context.http_client self.logger = context.logger self.file_downloader = context.file_downloader self.dbs_updater = DatabaseUpdater(http_client=context.http_client, logger=context.logger) def _get_db_connection(self, db_path: str) -> sqlite3.Connection: if os.path.exists(db_path): db.acquire_lock(db_path) return sqlite3.connect(db_path) else: self.logger.warning("Could not get a database connection. File '{}' not found".format(db_path)) def _close_connection(self, db_path: str, con: sqlite3.Connection): con.close() db.release_lock(db_path) def _gen_app_key(self, app: AppImage): return '{}{}'.format(app.name.lower(), app.github.lower() if app.github else '') 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 read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = 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 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: self._close_connection(DB_APPS_PATH, con) res.total = len(res.installed) return res 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 update(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: if not self.uninstall(pkg, root_password, watcher): watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.error.uninstall_current_version'], type_=MessageType.ERROR) return False if self.install(pkg, root_password, watcher): self.cache_to_disk(pkg, None, False) return True return False 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 get_managed_types(self) -> Set[Type[SoftwarePackage]]: return {AppImage} def clean_cache_for(self, pkg: AppImage): pass 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 get_history(self, pkg: AppImage) -> PackageHistory: history = [] res = PackageHistory(pkg, history, -1) connection = self._get_db_connection(DB_APPS_PATH) if connection: try: cursor = connection.cursor() cursor.execute(query.FIND_APP_ID_BY_NAME_AND_GITHUB.format(pkg.name.lower(), pkg.github.lower() if pkg.github else '')) app_tuple = cursor.fetchone() if not app_tuple: raise Exception("Could not retrieve {} from the database {}".format(pkg, DB_APPS_PATH)) finally: self._close_connection(DB_APPS_PATH, connection) connection = self._get_db_connection(DB_RELEASES_PATH) if connection: try: cursor = connection.cursor() releases = cursor.execute(query.FIND_RELEASES_BY_APP_ID.format(app_tuple[0])) if releases: for idx, tup in enumerate(releases): history.append({'0_version': tup[0], '1_published_at': datetime.strptime(tup[2], '%Y-%m-%dT%H:%M:%SZ') if tup[2] else '', '2_url_download': tup[1]}) if res.pkg_status_idx == -1 and pkg.version == tup[0]: res.pkg_status_idx = idx finally: self._close_connection(DB_RELEASES_PATH, connection) return res def _find_desktop_file(self, folder: str) -> str: for r, d, files in os.walk(folder): for f in files: if f.endswith('.desktop'): return f def _find_appimage_file(self, folder: str) -> str: for r, d, files in os.walk(folder): for f in files: if f.lower().endswith('.appimage'): return '{}/{}'.format(folder, f) def _find_icon_file(self, folder: str) -> str: for r, d, files in os.walk(folder): for f in files: if RE_ICON_ENDS_WITH.match(f): return f def install(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: handler = ProcessHandler(watcher) out_dir = INSTALLATION_PATH + pkg.name.lower() Path(out_dir).mkdir(parents=True, exist_ok=True) 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=HOME_PATH) 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 _gen_desktop_entry_path(self, app: AppImage) -> str: return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower()) def is_enabled(self) -> bool: return self.enabled def set_enabled(self, enabled: bool): self.enabled = enabled def _is_sqlite3_available(self): res = run_cmd('which sqlite3') return res and not res.strip().startswith('which ') def _is_wget_available(self): res = run_cmd('which wget') return res and not res.strip().startswith('which ') def _is_aria2_available(self): res = run_cmd('which aria2c') return res and not res.strip().startswith('which ') def can_work(self) -> bool: return self._is_sqlite3_available() and (self._is_wget_available() or self._is_aria2_available()) def requires_root(self, action: str, pkg: AppImage): return False def prepare(self): for path in (DB_APPS_PATH, DB_RELEASES_PATH): db.release_lock(path) self.dbs_updater.start() def list_updates(self, internet_available: bool) -> List[PackageUpdate]: res = self.read_installed(disk_loader=None, internet_available=internet_available) updates = [] if res.installed: for app in res.installed: if app.update: updates.append(PackageUpdate(pkg_id=app.name, pkg_type='appimage', version=app.latest_version)) return updates def list_warnings(self, internet_available: bool) -> List[str]: pass 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 is_default_enabled(self) -> bool: return True 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 cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): self.serialize_to_disk(pkg, icon_bytes, only_icon) def get_screenshots(self, pkg: AppImage) -> List[str]: if pkg.has_screenshots(): return [pkg.url_screenshot] return []