def get_fields(app_id: str, branch: str, fields: List[str]) -> List[str]: cmd = ['flatpak', 'info', app_id] if branch: cmd.append(branch) info = new_subprocess(cmd).stdout res = [] for o in new_subprocess(['grep', '-E', '({}):.+'.format('|'.join(fields)), '-o'], stdin=info).stdout: if o: res.append(o.decode().split(':')[-1].strip()) return res
def list_and_map_installed( ) -> dict: # returns a dict with with package names as keys and versions as values installed = new_subprocess( ['pacman', '-Qq']).stdout # retrieving all installed package names allinfo = new_subprocess( ['pacman', '-Qi'], stdin=installed).stdout # retrieving all installed packages info ignored = {} thread_ignored = Thread(target=_fill_ignored, args=(ignored, )) thread_ignored.start() pkgs, current_pkg = {'mirrors': {}, 'not_signed': {}}, {} for out in new_subprocess( ['grep', '-E', '(Name|Description|Version|Validated By)'], stdin=allinfo ).stdout: # filtering only the Name and Validated By fields: if out: line = out.decode() if line.startswith('Name'): current_pkg['name'] = line.split(':')[1].strip() elif line.startswith('Version'): version = line.split(':') current_pkg['version'] = version[len(version) - 1].strip() elif line.startswith('Description'): current_pkg['description'] = line.split(':')[1].strip() elif line.startswith('Validated'): if line.split(':')[1].strip().lower() == 'none': pkgs['not_signed'][current_pkg['name']] = { 'version': current_pkg['version'], 'description': current_pkg['description'] } current_pkg = {} if pkgs and pkgs.get('not_signed'): thread_ignored.join() if ignored['pkgs']: to_del = set() for pkg in pkgs['not_signed'].keys(): if pkg in ignored['pkgs']: to_del.add(pkg) for pkg in to_del: del pkgs['not_signed'][pkg] return pkgs
def read_updates(version: str, installation: str) -> Dict[str, set]: res = {'partial': set(), 'full': set()} if version < '1.2': try: output = run_cmd('{} update --no-related --no-deps --{}'.format( 'flatpak', installation), ignore_return_code=True) if 'Updating in {}'.format(installation) in output: for line in output.split('Updating in {}:\n'.format( installation))[1].split('\n'): if not line.startswith('Is this ok'): res['full'].add('{}/{}'.format( installation, line.split('\t')[0].strip())) except: traceback.print_exc() else: updates = new_subprocess( ['flatpak', 'update', '--{}'.format(installation)]).stdout reg = r'[0-9]+\.\s+.+' try: for o in new_subprocess(['grep', '-E', reg, '-o', '--color=never'], stdin=updates).stdout: if o: line_split = o.decode().strip().split('\t') if version >= '1.5.0': update_id = '{}/{}/{}'.format(line_split[2], line_split[3], installation) else: update_id = '{}/{}/{}'.format(line_split[2], line_split[4], installation) if len(line_split) >= 6: if line_split[4] != 'i': if '(partial)' in line_split[-1]: res['partial'].add(update_id) else: res['full'].add(update_id) else: res['full'].add(update_id) except: traceback.print_exc() return res
def has_apps_field(name: str, ubuntu_distro: bool) -> bool: info_path = _get_app_info_path(ubuntu_distro) info_out = new_subprocess(['cat', info_path.format(name)]).stdout res = False for o in new_subprocess(['grep', '-E', 'apps', '--colour=never'], stdin=info_out).stdout: if o: line = o.decode() if line.startswith('apps:'): res = True return res
def get_mirrors(pkgs: Set[str]) -> dict: pkgre = '|'.join(pkgs) searchres = new_subprocess(['pacman', '-Ss', pkgre]).stdout mirrors = {} for line in new_subprocess(['grep', '-E', '.+/({}) '.format(pkgre)], stdin=searchres).stdout: if line: match = line.decode() for p in pkgs: if p in match: mirrors[p] = match.split('/')[0] return mirrors
def _get_aria2c_process(self, url: str, output_path: str, cwd: str) -> SystemProcess: cmd = ['aria2c', url, '--no-conf', '--max-connection-per-server=16', '--split=16', '--enable-color=false', '--stderr=true', '--summary-interval=0', '--disable-ipv6', '--min-split-size=1M', '--allow-overwrite=true', '--continue=true', '--timeout=5', '--max-file-not-found=3', '--remote-time=true'] if output_path: output_split = output_path.split('/') cmd.append('--dir=' + '/'.join(output_split[:-1])) cmd.append('--out=' + output_split[-1]) return SystemProcess(new_subprocess(cmd=cmd, cwd=cwd), skip_stdout=True, check_error_output=False, success_phrases=['download completed'], output_delay=0.001)
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 uninstall(app_ref: str): """ Removes the app by its reference :param app_ref: :return: """ return new_subprocess([BASE_CMD, 'uninstall', app_ref, '-y'])
def update(app_ref: str): """ Updates the app reference :param app_ref: :return: """ return new_subprocess([BASE_CMD, 'update', '-y', app_ref])
def _get_wget_process(self, url: str, output_path: str, cwd: str) -> SystemProcess: cmd = ['wget', url] if output_path: cmd.append('-O') cmd.append(output_path) return SystemProcess(new_subprocess(cmd, cwd=cwd))
def list_updates_as_str(version: str): if version < '1.2': return run_cmd('{} update'.format(BASE_CMD), ignore_return_code=True) else: updates = new_subprocess([BASE_CMD, 'update']).stdout out = StringIO() reg = r'[0-9]+\.\s+(\w+|\.)+\s+(\w|\.)+' if version >= '1.5.0' else r'[0-9]+\.\s+(\w+|\.)+\s+\w+\s+(\w|\.)+' for o in new_subprocess(['grep', '-E', reg, '-o', '--color=never'], stdin=updates).stdout: if o: out.write('/'.join(o.decode().strip().split('\t')[2:]) + '\n') out.seek(0) return out.read()
def uninstall(app_ref: str, installation: str): """ Removes the app by its reference :param app_ref: :return: """ return new_subprocess( ['flatpak', 'uninstall', app_ref, '-y', '--{}'.format(installation)])
def receive_key(key: str, server: Optional[str] = None) -> SystemProcess: cmd = ['gpg'] if server: cmd.extend(['--keyserver', server]) cmd.extend(['--recv-key', key]) return SystemProcess(new_subprocess(cmd), check_error_output=False)
def is_snapd_running() -> bool: services = new_subprocess(['systemctl', 'list-units']) service, service_running = False, False socket, socket_running = False, False for o in new_subprocess(['grep', '-Eo', 'snapd.+'], stdin=services.stdout).stdout: if o: line = o.decode().strip() if line: line_split = RE_SNAPD_STATUS.split(line) running = line_split[3] in SNAPD_RUNNING_STATUS if line_split[0] == 'snapd.service': service = True service_running = running elif line_split[0] == 'snapd.socket': socket = True socket_running = running return socket and socket_running and (not service or (service and service_running))
def downgrade(app_ref: str, commit: str, installation: str, root_password: str) -> subprocess.Popen: cmd = [ 'flatpak', 'update', '--no-related', '--no-deps', '--commit={}'.format(commit), app_ref, '-y', '--{}'.format(installation) ] if installation == 'system': return new_root_subprocess(cmd, root_password) else: return new_subprocess(cmd)
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 update(app_ref: str, installation: str, related: bool = False, deps: bool = False): """ Updates the app reference :param app_ref: :return: """ cmd = ['flatpak', 'update', '-y', app_ref, '--{}'.format(installation)] if not related: cmd.append('--no-related') if not deps: cmd.append('--no-deps') return new_subprocess(cmd)
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 is_downgrade_enabled(self) -> bool: try: new_subprocess(['git', '--version']) return True except FileNotFoundError: return False
def _is_wget_available(self): try: new_subprocess(['wget', '--version']) return True except FileNotFoundError: return False
def _install_from_aur(self, pkgname: str, maintainer: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool: app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time())) try: if not os.path.exists(app_build_dir): build_dir = handler.handle( SystemProcess( new_subprocess(['mkdir', '-p', app_build_dir]))) self._update_progress(handler.watcher, 10, change_progress) if build_dir: file_url = URL_PKG_DOWNLOAD.format(pkgname) file_name = file_url.split('/')[-1] handler.watcher.change_substatus('{} {}'.format( self.i18n['arch.downloading.package'], bold(file_name))) download = handler.handle( SystemProcess(new_subprocess(['wget', file_url], cwd=app_build_dir), check_error_output=False)) if download: self._update_progress(handler.watcher, 30, change_progress) handler.watcher.change_substatus('{} {}'.format( self.i18n['arch.uncompressing.package'], bold(file_name))) uncompress = handler.handle( SystemProcess( new_subprocess([ 'tar', 'xvzf', '{}.tar.gz'.format(pkgname) ], cwd=app_build_dir))) self._update_progress(handler.watcher, 40, change_progress) if uncompress: uncompress_dir = '{}/{}'.format( app_build_dir, pkgname) return self._make_pkg( pkgname=pkgname, maintainer=maintainer, root_password=root_password, handler=handler, build_dir=app_build_dir, project_dir=uncompress_dir, dependency=dependency, skip_optdeps=skip_optdeps, change_progress=change_progress) finally: if os.path.exists(app_build_dir): handler.handle( SystemProcess(new_subprocess(['rm', '-rf', app_build_dir]))) return False
def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: handler = ProcessHandler(watcher) app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time())) watcher.change_progress(5) try: if not os.path.exists(app_build_dir): build_dir = handler.handle( SystemProcess( new_subprocess(['mkdir', '-p', app_build_dir]))) if build_dir: watcher.change_progress(10) watcher.change_substatus(self.i18n['arch.clone'].format( bold(pkg.name))) clone = handler.handle( SystemProcess(subproc=new_subprocess( ['git', 'clone', URL_GIT.format(pkg.name)], cwd=app_build_dir), check_error_output=False)) watcher.change_progress(30) if clone: watcher.change_substatus( self.i18n['arch.downgrade.reading_commits']) clone_path = '{}/{}'.format(app_build_dir, pkg.name) pkgbuild_path = '{}/PKGBUILD'.format(clone_path) commits = run_cmd("git log", cwd=clone_path) watcher.change_progress(40) if commits: commit_list = re.findall(r'commit (.+)\n', commits) if commit_list: if len(commit_list) > 1: for idx in range(1, len(commit_list)): commit = commit_list[idx] with open(pkgbuild_path) as f: pkgdict = aur.map_pkgbuild( f.read()) if not handler.handle( SystemProcess( subproc=new_subprocess( [ 'git', 'reset', '--hard', commit ], cwd=clone_path), check_error_output=False)): watcher.print( 'Could not downgrade anymore. Aborting...' ) return False if '{}-{}'.format( pkgdict.get('pkgver'), pkgdict.get( 'pkgrel')) == pkg.version: # current version found watcher.change_substatus(self.i18n[ 'arch.downgrade.version_found'] ) break watcher.change_substatus( self. i18n['arch.downgrade.install_older']) return self._make_pkg(pkg.name, pkg.maintainer, root_password, handler, app_build_dir, clone_path, dependency=False, skip_optdeps=True) else: watcher.show_message( title=self. i18n['arch.downgrade.error'], body=self. i18n['arch.downgrade.impossible']. format(pkg.name), type_=MessageType.ERROR) return False watcher.show_message( title=self.i18n['error'], body=self.i18n['arch.downgrade.no_commits'], type_=MessageType.ERROR) return False finally: if os.path.exists(app_build_dir): handler.handle( SystemProcess( subproc=new_subprocess(['rm', '-rf', app_build_dir]))) return False
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()