def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: handler = ProcessHandler(watcher) pkg.commit = flatpak.get_commit(pkg.id, pkg.branch, pkg.installation) watcher.change_progress(10) watcher.change_substatus(self.i18n['flatpak.downgrade.commits']) commits = flatpak.get_app_commits(pkg.ref, pkg.origin, pkg.installation, handler) if commits is None: return False commit_idx = commits.index(pkg.commit) # downgrade is not possible if the app current commit in the first one: if commit_idx == len(commits) - 1: watcher.show_message( self.i18n['flatpak.downgrade.impossible.title'], self.i18n['flatpak.downgrade.impossible.body'], MessageType.WARNING) return False commit = commits[commit_idx + 1] watcher.change_substatus(self.i18n['flatpak.downgrade.reverting']) watcher.change_progress(50) success = handler.handle( SystemProcess( subproc=flatpak.downgrade(pkg.ref, commit, pkg.installation, root_password), success_phrases=['Changes complete.', 'Updates complete.'], wrong_error_phrase='Warning')) watcher.change_progress(100) return success
def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: if not self._make_exports_dir(watcher): return False watcher.change_progress(10) watcher.change_substatus(self.i18n['flatpak.downgrade.commits']) history = self.get_history(pkg, full_commit_str=True) # downgrade is not possible if the app current commit in the first one: if history.pkg_status_idx == len(history.history) - 1: watcher.show_message( self.i18n['flatpak.downgrade.impossible.title'], self.i18n['flatpak.downgrade.impossible.body'].format( bold(pkg.name)), MessageType.ERROR) return False commit = history.history[history.pkg_status_idx + 1]['commit'] watcher.change_substatus(self.i18n['flatpak.downgrade.reverting']) watcher.change_progress(50) success, _ = ProcessHandler(watcher).handle_simple( flatpak.downgrade(pkg.ref, commit, pkg.installation, root_password)) watcher.change_progress(100) return success
def install(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher, skip_optdeps: bool = False) -> bool: clean_config = False if not self.local_config: self.local_config = read_config() clean_config = True if self.local_config['optimize'] and not os.path.exists( CUSTOM_MAKEPKG_FILE): watcher.change_substatus(self.i18n['arch.makepkg.optimizing']) ArchCompilationOptimizer(self.context.logger).optimize() res = self._install_from_aur(pkg.name, pkg.maintainer, root_password, ProcessHandler(watcher), dependency=False, skip_optdeps=skip_optdeps) if res: if os.path.exists(pkg.get_disk_data_path()): with open(pkg.get_disk_data_path()) as f: data = f.read() if data: data = json.loads(data) pkg.fill_cached_data(data) if clean_config: self.local_config = None return res
def _display_file_size(self, file_url: str, base_substatus, watcher: ProcessWatcher): try: size = self.http_client.get_content_length(file_url) if size: watcher.change_substatus(base_substatus + ' ( {} )'.format(size)) except: pass
def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessWatcher) -> bool: if self.context.file_downloader.is_multithreaded(): srcinfo = self.aur_client.get_src_info(pkgname) pre_download_files = [] for attr in SOURCE_FIELDS: if srcinfo.get(attr): if attr == 'source_x86_x64' and not self.context.is_system_x86_64( ): continue else: for f in srcinfo[attr]: if RE_PRE_DOWNLOADABLE_FILES.findall(f): pre_download_files.append(f) if pre_download_files: downloader = self.context.file_downloader.get_default_client_name( ) for f in pre_download_files: fdata = f.split('::') args = {'watcher': watcher, 'cwd': project_dir} if len(fdata) > 1: args.update({ 'file_url': fdata[1], 'output_path': fdata[0] }) else: args.update({ 'file_url': fdata[0], 'output_path': None }) file_size = self.context.http_client.get_content_length( args['file_url']) file_size = int(file_size) / (1024** 2) if file_size else None watcher.change_substatus( bold('[{}] ').format(downloader) + self.i18n['downloading'] + ' ' + bold(args['file_url'].split('/')[-1]) + ' ( {0:.2f} Mb )'.format(file_size) if file_size else '') if not self.context.file_downloader.download(**args): watcher.print( 'Could not download source file {}'.format( args['file_url'])) return False return True
def download(self, file_url: str, watcher: ProcessWatcher, output_path: str, cwd: str) -> bool: self.logger.info('Downloading {}'.format(file_url)) handler = ProcessHandler(watcher) file_name = file_url.split('/')[-1] final_cwd = cwd if cwd else '.' success = False ti = time.time() try: if output_path and os.path.exists(output_path): self.logger.info( 'Removing old file found before downloading: {}'.format( output_path)) os.remove(output_path) self.logger.info("Old file {} removed".format(output_path)) if self.is_multithreaded(): ti = time.time() process = self._get_aria2c_process(file_url, output_path, final_cwd) downloader = 'aria2c' else: ti = time.time() process = self._get_wget_process(file_url, output_path, final_cwd) downloader = 'wget' file_size = self.http_client.get_content_length(file_url) msg = bold('[{}] ').format( downloader) + self.i18n['downloading'] + ' ' + bold( file_url.split('/')[-1]) + (' ( {} )'.format(file_size) if file_size else '') watcher.change_substatus(msg) if isinstance(process, SimpleProcess): success = handler.handle_simple(process) else: success = handler.handle(process) except: traceback.print_exc() self._rm_bad_file(file_name, output_path, final_cwd) tf = time.time() self.logger.info(file_name + ' download took {0:.2f} minutes'.format((tf - ti) / 60)) if not success: self.logger.error("Could not download '{}'".format(file_name)) self._rm_bad_file(file_name, output_path, final_cwd) return success
def _concat_file_size(self, file_url: str, base_substatus: StringIO, watcher: ProcessWatcher): watcher.change_substatus(f'{base_substatus.getvalue()} ( ? Mb )') try: size = self.http_client.get_content_length(file_url) if size: base_substatus.write(f' ( {size} )') watcher.change_substatus(base_substatus.getvalue()) except: pass
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool: flatpak_version = flatpak.get_version() for req in requirements.to_upgrade: watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) related, deps = False, False ref = req.pkg.ref if req.pkg.partial and flatpak_version < '1.5': related, deps = True, True ref = req.pkg.base_ref try: res = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.update(app_ref=ref, installation=req.pkg.installation, related=related, deps=deps))) watcher.change_substatus('') if not res: self.logger.warning("Could not upgrade '{}'".format(req.pkg.id)) return False except: watcher.change_substatus('') self.logger.error("An error occurred while upgrading '{}'".format(req.pkg.id)) traceback.print_exc() return False watcher.change_substatus('') return True
def _refresh_apps_index(self, watcher: ProcessWatcher): watcher.change_substatus(self._i18n['debian.app_index.checking']) self._log.info("Reading the cached Debian applications") indexed_apps = self.app_indexer.read_index() self._log.info("Mapping the Debian applications") current_apps = self.app_mapper.map_executable_applications() if current_apps != indexed_apps: watcher.print(self._i18n['debian.app_index.updating'] + '...') watcher.change_substatus(self._i18n['debian.app_index.updating']) try: self.app_indexer.update_index(current_apps) self._update_apps_index(current_apps) if indexed_apps is not None: new_apps = current_apps.difference(indexed_apps) if new_apps: self._log.info(f"Debian applications added to the index: " f"{','.join((a.name for a in new_apps))}") except ApplicationIndexError: pass
def upgrade(self, requirements: UpgradeRequirements, root_password: Optional[str], watcher: ProcessWatcher) -> bool: not_upgraded = [] for req in requirements.to_upgrade: watcher.change_status(f"{self.i18n['manage_window.status.upgrading']} {req.pkg.name} ({req.pkg.version})...") download_data = None if not req.pkg.imported: download_data = self._download(req.pkg, watcher) if not download_data: not_upgraded.append(req.pkg) watcher.change_substatus('') continue if not self.uninstall(req.pkg, root_password, watcher).success: not_upgraded.append(req.pkg) watcher.change_substatus('') continue if not self._install(pkg=req.pkg, watcher=watcher, pre_downloaded_file=download_data).success: not_upgraded.append(req.pkg) watcher.change_substatus('') continue self.cache_to_disk(req.pkg, None, False) all_failed = len(not_upgraded) == len(requirements.to_upgrade) if not_upgraded: pkgs_str = ''.join((f'<li>{app.name}</li>' for app in not_upgraded)) watcher.show_message(title=self.i18n['error' if all_failed else 'warning'].capitalize(), body=self.i18n['appimage.upgrade.failed'].format(apps=f'<ul>{pkgs_str}</ul>'), type_=MessageType.ERROR if all_failed else MessageType.WARNING) watcher.change_substatus('') return not all_failed
def upgrade(self, requirements: UpgradeRequirements, root_password: Optional[str], watcher: ProcessWatcher) -> bool: flatpak_version = flatpak.get_version() if not self._make_exports_dir(watcher): return False for req in requirements.to_upgrade: watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) related, deps = False, False ref = req.pkg.ref if req.pkg.partial and flatpak_version < VERSION_1_5: related, deps = True, True ref = req.pkg.base_ref try: if req.pkg.update_component: self.logger.info(f"Installing {req.pkg}") res, _ = ProcessHandler(watcher).handle_simple(flatpak.install(app_id=ref, installation=req.pkg.installation, origin=req.pkg.origin, version=flatpak_version)) else: self.logger.info(f"Updating {req.pkg}") res, _ = ProcessHandler(watcher).handle_simple(flatpak.update(app_ref=ref, installation=req.pkg.installation, related=related, deps=deps, version=flatpak_version)) watcher.change_substatus('') if not res: self.logger.warning("Could not upgrade '{}'".format(req.pkg.id)) return False except: watcher.change_substatus('') self.logger.error("An error occurred while upgrading '{}'".format(req.pkg.id)) traceback.print_exc() return False watcher.change_substatus('') return True
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool: for req in requirements.to_upgrade: watcher.change_status("{} {} ({})...".format( self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) if not self.uninstall(req.pkg, root_password, watcher): watcher.show_message( title=self.i18n['error'], body=self.i18n['appimage.error.uninstall_current_version'], type_=MessageType.ERROR) watcher.change_substatus('') return False if not self.install(req.pkg, root_password, watcher): watcher.change_substatus('') return False self.cache_to_disk(req.pkg, None, False) watcher.change_substatus('') return True
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 download(self, file_url: str, watcher: ProcessWatcher, output_path: str = None, cwd: str = None, root_password: Optional[str] = None, substatus_prefix: str = None, display_file_size: bool = True, max_threads: int = None, known_size: int = None) -> bool: self.logger.info(f'Downloading {file_url}') handler = ProcessHandler(watcher) file_name = file_url.split('/')[-1] final_cwd = cwd if cwd else '.' success = False ti = time.time() try: if output_path: if os.path.exists(output_path): self.logger.info( f'Removing old file found before downloading: {output_path}' ) os.remove(output_path) self.logger.info(f'Old file {output_path} removed') else: output_dir = os.path.dirname(output_path) try: Path(output_dir).mkdir(exist_ok=True, parents=True) except OSError: self.logger.error( f"Could not make download directory '{output_dir}'" ) watcher.print( self.i18n['error.mkdir'].format(dir=output_dir)) return False client = self.get_available_multithreaded_tool() if client: threads = self._get_appropriate_threads_number( max_threads, known_size) if client == 'aria2': ti = time.time() process = self._get_aria2c_process(file_url, output_path, final_cwd, root_password, threads) downloader = 'aria2' else: ti = time.time() process = self._get_axel_process(file_url, output_path, final_cwd, root_password, threads) downloader = 'axel' else: ti = time.time() process = self._get_wget_process(file_url, output_path, final_cwd, root_password) downloader = 'wget' name = file_url.split('/')[-1] if output_path and not RE_HAS_EXTENSION.match( name) and RE_HAS_EXTENSION.match(output_path): name = output_path.split('/')[-1] if watcher: msg = StringIO() msg.write(f'{substatus_prefix} ' if substatus_prefix else '') msg.write( f"{bold('[{}]'.format(downloader))} {self.i18n['downloading']} {bold(name)}" ) if display_file_size: if known_size: msg.write(f' ( {get_human_size_str(known_size)} )') watcher.change_substatus(msg.getvalue()) else: Thread(target=self._concat_file_size, args=(file_url, msg, watcher)).start() else: msg.write(' ( ? Mb )') watcher.change_substatus(msg.getvalue()) success, _ = handler.handle_simple(process) except: traceback.print_exc() self._rm_bad_file(file_name, output_path, final_cwd, handler, root_password) tf = time.time() self.logger.info( f'{file_name} download took {(tf - ti) / 60:.2f} minutes') if not success: self.logger.error(f"Could not download '{file_name}'") self._rm_bad_file(file_name, output_path, final_cwd, handler, root_password) return success
def install(self, pkg: SoftwarePackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher) -> TransactionResult: watcher.change_substatus(self._i18n['debian.simulate_operation']) transaction = self.aptitude.simulate_installation((pkg.name, )) if transaction is None or not transaction.to_install: return TransactionResult.fail() if transaction.to_remove or (transaction.to_install and len(transaction.to_install) > 1): watcher.change_substatus(self._i18n['debian.transaction.get_data']) deps = tuple(p for p in transaction.to_install or () if p.name != pkg.name) removal = tuple(p for p in transaction.to_remove or ()) all_pkgs = [*deps, *removal] pkgs_data = self.aptitude.show(pkgs=(f'{d.name}={d.version}' for d in all_pkgs), attrs=self.install_show_attrs) if pkgs_data: for p in all_pkgs: fill_show_data(p, pkgs_data.get(p.name)) if not self.view.confirm_transaction(to_install=deps, removal=removal, watcher=watcher): return TransactionResult.fail() watcher.change_substatus(self._i18n['debian.installing_pkgs']) handler = ProcessHandler(watcher) targets = (p.name for p in transaction.to_install) with self.output_handler.start(watcher=watcher, targets=targets, action=AptitudeAction.INSTALL) as handle: installed, _ = handler.handle_simple(self.aptitude.install(packages=(pkg.name,), root_password=root_password), output_handler=handle) if installed: self._refresh_apps_index(watcher) watcher.change_substatus(self._i18n['debian.install.validating']) currently_installed = set(self.aptitude.read_installed_names()) installed_instances = [] if currently_installed: for p in transaction.to_install: instance = p if p != pkg else pkg if instance.name in currently_installed: instance.installed = True instance.bind_app(self.apps_index.get(instance.name)) installed_instances.append(instance) removed = None if transaction.to_remove: removed = [p for p in transaction.to_remove if p.name not in currently_installed] not_removed = set(transaction.to_remove).difference(removed) if not_removed: not_removed_str = ' '.join(p.name for p in not_removed) self._log.warning(f"The following packages were not removed: {not_removed_str}") return TransactionResult(installed=installed_instances, removed=removed, success=bool(installed_instances and pkg in installed_instances)) else: watcher.change_substatus('') return TransactionResult.fail()
def _ask_install_options( self, app: WebApplication, watcher: ProcessWatcher) -> Tuple[bool, List[str]]: watcher.change_substatus(self.i18n['web.install.substatus.options']) inp_url = TextInputComponent(label=self.i18n['address'], value=app.url, read_only=True) inp_name = TextInputComponent(label=self.i18n['name'], value=app.name) inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description) cat_ops = [ InputOption(label=self.i18n['web.install.option.category.none']. capitalize(), value=0) ] cat_ops.extend([ InputOption(label=self.i18n[c.lower()].capitalize(), value=c) for c in self.context.default_categories ]) def_cat = cat_ops[0] if app.categories: for opt in cat_ops: if opt.value == app.categories[0]: def_cat = opt break inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops, default_option=def_cat) tray_op_off = InputOption( id_='tray_off', label=self.i18n['web.install.option.tray.off.label'], value=0, tooltip=self.i18n['web.install.option.tray.off.tip']) tray_op_default = InputOption( id_='tray_def', label=self.i18n['web.install.option.tray.default.label'], value='--tray', tooltip=self.i18n['web.install.option.tray.default.tip']) tray_op_min = InputOption( id_='tray_min', label=self.i18n['web.install.option.tray.min.label'], value='--tray=start-in-tray', tooltip=self.i18n['web.install.option.tray.min.tip']) tray_opts = [tray_op_off, tray_op_default, tray_op_min] def_tray_opt = None if app.preset_options: for opt in tray_opts: if opt.id in app.preset_options: def_tray_opt = opt break inp_tray = SingleSelectComponent( type_=SelectViewType.COMBO, options=tray_opts, default_option=def_tray_opt, label=self.i18n['web.install.option.tray.label']) icon_op_ded = InputOption( id_='icon_ded', label=self.i18n['web.install.option.wicon.deducted.label'], value=0, tooltip=self.i18n['web.install.option.wicon.deducted.tip'].format( 'Nativefier')) icon_op_disp = InputOption( id_='icon_disp', label=self.i18n['web.install.option.wicon.displayed.label'], value=1, tooltip=self.i18n['web.install.option.wicon.displayed.tip']) inp_icon = SingleSelectComponent( type_=SelectViewType.COMBO, options=[icon_op_disp, icon_op_ded], default_option=icon_op_disp if app.icon_url and app.save_icon else icon_op_ded, label=self.i18n['web.install.option.wicon.label']) icon_chooser = FileChooserComponent( allowed_extensions={'png', 'svg', 'ico', 'jpg', 'jpeg'}, label=self.i18n['web.install.option.icon.label']) form_1 = FormComponent( components=[ inp_url, inp_name, inp_desc, inp_cat, inp_icon, icon_chooser, inp_tray ], label=self.i18n['web.install.options.basic'].capitalize()) op_single = InputOption( id_='single', label=self.i18n['web.install.option.single.label'], value="--single-instance", tooltip=self.i18n['web.install.option.single.tip']) op_max = InputOption(id_='max', label=self.i18n['web.install.option.max.label'], value="--maximize", tooltip=self.i18n['web.install.option.max.tip']) op_fs = InputOption( id_='fullscreen', label=self.i18n['web.install.option.fullscreen.label'], value="--full-screen", tooltip=self.i18n['web.install.option.fullscreen.tip']) op_nframe = InputOption( id_='no_frame', label=self.i18n['web.install.option.noframe.label'], value="--hide-window-frame", tooltip=self.i18n['web.install.option.noframe.tip']) op_allow_urls = InputOption( id_='allow_urls', label=self.i18n['web.install.option.allow_urls.label'], value='--internal-urls=.*', tooltip=self.i18n['web.install.option.allow_urls.tip']) op_ncache = InputOption( id_='no_cache', label=self.i18n['web.install.option.nocache.label'], value="--clear-cache", tooltip=self.i18n['web.install.option.nocache.tip']) op_insecure = InputOption( id_='insecure', label=self.i18n['web.install.option.insecure.label'], value="--insecure", tooltip=self.i18n['web.install.option.insecure.tip']) op_igcert = InputOption( id_='ignore_certs', label=self.i18n['web.install.option.ignore_certificate.label'], value="--ignore-certificate", tooltip=self.i18n['web.install.option.ignore_certificate.tip']) adv_opts = [ op_single, op_allow_urls, op_max, op_fs, op_nframe, op_ncache, op_insecure, op_igcert ] def_adv_opts = {op_single, op_allow_urls} if app.preset_options: for opt in adv_opts: if opt.id in app.preset_options: def_adv_opts.add(opt) check_options = MultipleSelectComponent( options=adv_opts, default_options=def_adv_opts, label=self.i18n['web.install.options.advanced'].capitalize()) res = watcher.request_confirmation( title=self.i18n['web.install.options_dialog.title'], body=None, components=[form_1, check_options], confirmation_label=self.i18n['continue'].capitalize(), deny_label=self.i18n['cancel'].capitalize()) if res: selected = [] if check_options.values: selected.extend(check_options.get_selected_values()) tray_mode = inp_tray.get_selected() if tray_mode is not None and tray_mode != 0: selected.append(tray_mode) custom_name = inp_name.get_value() if custom_name: app.name = custom_name custom_desc = inp_desc.get_value() if custom_desc: app.description = inp_desc.get_value() cat = inp_cat.get_selected() if cat != 0: app.categories = [cat] if icon_chooser.file_path: app.set_custom_icon(icon_chooser.file_path) selected.append('--icon={}'.format(icon_chooser.file_path)) app.save_icon = inp_icon.value == icon_op_disp return res, selected return False, []
def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = None, cwd: str = None, root_password: str = None, substatus_prefix: str = None, display_file_size: bool = True, max_threads: int = None, known_size: int = None) -> bool: self.logger.info('Downloading {}'.format(file_url)) handler = ProcessHandler(watcher) file_name = file_url.split('/')[-1] final_cwd = cwd if cwd else '.' success = False ti = time.time() try: if output_path and os.path.exists(output_path): self.logger.info( 'Removing old file found before downloading: {}'.format( output_path)) os.remove(output_path) self.logger.info("Old file {} removed".format(output_path)) client = self.get_available_multithreaded_tool() if client: threads = self._get_appropriate_threads_number( max_threads, known_size) if client == 'aria2': ti = time.time() process = self._get_aria2c_process(file_url, output_path, final_cwd, root_password, threads) downloader = 'aria2' else: ti = time.time() process = self._get_axel_process(file_url, output_path, final_cwd, root_password, threads) downloader = 'axel' else: ti = time.time() process = self._get_wget_process(file_url, output_path, final_cwd, root_password) downloader = 'wget' name = file_url.split('/')[-1] if output_path and not RE_HAS_EXTENSION.match( name) and RE_HAS_EXTENSION.match(output_path): name = output_path.split('/')[-1] if substatus_prefix: msg = substatus_prefix + ' ' else: msg = '' msg += bold('[{}] ').format( downloader) + self.i18n['downloading'] + ' ' + bold(name) if watcher: watcher.change_substatus(msg) if display_file_size: if known_size: watcher.change_substatus( msg + ' ( {} )'.format(get_human_size_str(known_size))) else: Thread(target=self._display_file_size, args=(file_url, msg, watcher)).start() success, _ = handler.handle_simple(process) except: traceback.print_exc() self._rm_bad_file(file_name, output_path, final_cwd, handler, root_password) tf = time.time() self.logger.info(file_name + ' download took {0:.2f} minutes'.format((tf - ti) / 60)) if not success: self.logger.error("Could not download '{}'".format(file_name)) self._rm_bad_file(file_name, output_path, final_cwd, handler, root_password) return success
def install(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool: continue_install, install_options = self._ask_install_options( pkg, watcher) if not continue_install: watcher.print("Installation aborted by the user") return False watcher.change_substatus(self.i18n['web.env.checking']) handler = ProcessHandler(watcher) env_settings = self.env_updater.read_settings() local_config = read_config() if local_config['environment'][ 'system'] and not nativefier.is_available(): watcher.show_message( title=self.i18n['error'].capitalize(), body=self.i18n['web.install.global_nativefier.unavailable']. format(n=bold('Nativefier'), app=bold(pkg.name)) + '.', type_=MessageType.ERROR) return False env_components = self.env_updater.check_environment( app=pkg, local_config=local_config, env=env_settings, is_x86_x64_arch=self.context.is_system_x86_64()) comps_to_update = [c for c in env_components if c.update] if comps_to_update and not self._ask_update_permission( comps_to_update, watcher): return False if not self.env_updater.update(components=comps_to_update, handler=handler): watcher.show_message(title=self.i18n['error'], body=self.i18n['web.env.error'].format( bold(pkg.name)), type_=MessageType.ERROR) return False Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True) app_id, treated_name = self._gen_app_id(pkg.name) pkg.id = app_id app_dir = '{}/{}'.format(INSTALLED_PATH, app_id) watcher.change_substatus( self.i18n['web.install.substatus.checking_fixes']) fix = self._get_fix_for( url_no_protocol=self._strip_url_protocol(pkg.url)) fix_path = '{}/fix.js'.format(app_dir) if fix: # just adding the fix as an installation option. The file will be written later self.logger.info('Fix found for {}'.format(pkg.url)) watcher.print('Fix found for {}'.format(pkg.url)) install_options.append('--inject={}'.format(fix_path)) # if a custom icon is defined for an app suggestion: icon_path, icon_bytes = None, None if pkg.icon_url and pkg.save_icon and not { o for o in install_options if o.startswith('--icon') }: download = self._download_suggestion_icon(pkg, app_dir) if download and download[1]: icon_path, icon_bytes = download[0], download[1] pkg.custom_icon = icon_path # writting the icon in a temporary folder to be used by the nativefier process temp_icon_path = '{}/{}'.format(TEMP_PATH, pkg.icon_url.split('/')[-1]) install_options.append('--icon={}'.format(temp_icon_path)) self.logger.info("Writing a temp suggestion icon at {}".format( temp_icon_path)) with open(temp_icon_path, 'wb+') as f: f.write(icon_bytes) watcher.change_substatus( self.i18n['web.install.substatus.call_nativefier'].format( bold('nativefier'))) electron_version = str( next((c for c in env_components if c.id == 'electron')).version) installed = handler.handle_simple( nativefier.install(url=pkg.url, name=app_id, output_dir=app_dir, electron_version=electron_version, system=bool( local_config['environment']['system']), cwd=INSTALLED_PATH, extra_options=install_options)) if not installed: msg = '{}.{}.'.format( self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.unknown'].format( bold(self.i18n['details'].capitalize()))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) return False inner_dir = os.listdir(app_dir) if not inner_dir: msg = '{}.{}.'.format( self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.inner_dir'].format( bold(app_dir))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) return False # bringing the inner app folder to the 'installed' folder level: inner_dir = '{}/{}'.format(app_dir, inner_dir[0]) temp_dir = '{}/tmp_{}'.format(INSTALLED_PATH, treated_name) os.rename(inner_dir, temp_dir) shutil.rmtree(app_dir) os.rename(temp_dir, app_dir) # injecting a fix if fix: self.logger.info('Writting JS fix at {}'.format(fix_path)) with open(fix_path, 'w+') as f: f.write(fix) # persisting the custom suggestion icon in the defitive directory if icon_bytes: self.logger.info( "Writting the final custom suggestion icon at {}".format( icon_path)) with open(icon_path, 'wb+') as f: f.write(icon_bytes) pkg.installation_dir = app_dir version_path = '{}/version'.format(app_dir) if os.path.exists(version_path): with open(version_path, 'r') as f: pkg.version = f.read().strip() pkg.latest_version = pkg.version watcher.change_substatus(self.i18n['web.install.substatus.shortcut']) desktop_entry_path = self._gen_desktop_entry_path(app_id) entry_content = self._gen_desktop_entry_content(pkg) Path(DESKTOP_ENTRIES_DIR).mkdir(parents=True, exist_ok=True) with open(desktop_entry_path, 'w+') as f: f.write(entry_content) pkg.desktop_entry = desktop_entry_path if '--tray=start-in-tray' in install_options: autostart_dir = '{}/.config/autostart'.format(Path.home()) Path(autostart_dir).mkdir(parents=True, exist_ok=True) with open(pkg.get_autostart_path(), 'w+') as f: f.write(entry_content) if install_options: pkg.options_set = install_options return True
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()
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 uninstall(self, pkg: DebianPackage, root_password: str, watcher: ProcessWatcher, disk_loader: Optional[DiskCacheLoader], purge: bool = False) -> TransactionResult: config_ = self.configman.get_config() purge_ = purge or config_.get('remove.purge', False) watcher.change_substatus(self._i18n['debian.simulate_operation']) transaction = self.aptitude.simulate_removal((pkg.name,), purge=purge_) if not transaction or not transaction.to_remove: return TransactionResult.fail() if pkg not in transaction.to_remove: watcher.show_message(title=self._i18n['popup.title.error'], body=self._i18n['debian.remove.impossible'].format(pkg=bold(pkg.name)), type_=MessageType.ERROR) return TransactionResult.fail() watcher.change_substatus('') deps = tuple(p for p in transaction.to_remove if p.name != pkg.name) if deps: # updates are required to be filled in case the dependencies are currently displayed on the view updates = dict() fill_updates = Thread(target=self._fill_updates, args=(updates,)) fill_updates.start() deps_data = self.aptitude.show((p.name for p in deps), attrs=('description', 'maintainer', 'section')) if deps_data: for p in deps: fill_show_data(p, deps_data.get(p.name)) if not self.view.confirm_removal(source_pkg=pkg.name, dependencies=deps, watcher=watcher): return TransactionResult.fail() fill_updates.join() if updates: for p in deps: latest_version = updates.get(p.name) if latest_version is not None and p.version != latest_version: p.latest_version = latest_version p.update = True watcher.change_substatus(self._i18n['debian.uninstall.removing']) handler = ProcessHandler(watcher) to_remove = tuple(p.name for p in transaction.to_remove) with self.output_handler.start(watcher=watcher, targets=to_remove, action=AptitudeAction.REMOVE) as handle: removed, _ = handler.handle_simple(self.aptitude.remove(packages=to_remove, root_password=root_password, purge=purge_), output_handler=handle) if not removed: return TransactionResult.fail() watcher.change_substatus(self._i18n['debian.uninstall.validating']) current_installed_names = set(self.aptitude.read_installed_names()) watcher.change_substatus('') all_removed, apps_removed, not_removed_names = [], set(), set() for p in transaction.to_remove: if p.name not in current_installed_names: instance = p if p != pkg else pkg all_removed.append(instance) if instance.app: apps_removed.add(instance.app) instance.installed = False instance.version = instance.latest_version instance.update = False instance.bind_app(None) else: not_removed_names.add(p.name) if apps_removed: # updating apps index watcher.print(self._i18n['debian.app_index.updating'] + ' ...') watcher.change_substatus(self._i18n['debian.app_index.updating']) indexed_apps = set(self.app_indexer.read_index()) if indexed_apps: new_index = indexed_apps.difference(apps_removed) try: self.app_indexer.update_index(new_index, update_timestamp=False) self._update_apps_index(new_index) self._log.info(f"Debian applications removed from the index: " f"{', '.join((a.name for a in apps_removed))}") except ApplicationIndexError: pass watcher.change_substatus('') success = True if not_removed_names: success = pkg.name not in not_removed_names not_removed_str = ', '.join((bold(p) for p in sorted(not_removed_names))) watcher.show_message(title=self._i18n[f"popup.title.{'warning' if success else 'error'}"], body=self._i18n['debian.uninstall.failed_to_remove'].format(no=len(not_removed_names), pkgs=not_removed_str), type_=MessageType.WARNING if success else MessageType.ERROR) return TransactionResult(success=success, installed=None, removed=all_removed)