class GenericSoftwareManager(SoftwareManager): def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict, settings_manager: GenericSettingsManager = None): super(GenericSoftwareManager, self).__init__(context=context) self.managers = managers self.map = {t: m for m in self.managers for t in m.get_managed_types()} self._available_cache = {} if config['system'][ 'single_dependency_checking'] else None self.thread_prepare = None self.i18n = context.i18n self.disk_loader_factory = context.disk_loader_factory self.logger = context.logger self._already_prepared = [] self.working_managers = [] self.config = config self.settings_manager = settings_manager def reset_cache(self): if self._available_cache is not None: self._available_cache = {} self.working_managers.clear() def _sort(self, apps: List[SoftwarePackage], word: str) -> List[SoftwarePackage]: exact_name_matches, contains_name_matches, others = [], [], [] for app in apps: lower_name = app.name.lower() if word == lower_name: exact_name_matches.append(app) elif word in lower_name: contains_name_matches.append(app) else: others.append(app) res = [] for app_list in (exact_name_matches, contains_name_matches, others): app_list.sort(key=lambda a: a.name.lower()) res.extend(app_list) return res def _can_work(self, man: SoftwareManager): if self._available_cache is not None: available = False for t in man.get_managed_types(): available = self._available_cache.get(t) if available is None: available = man.is_enabled() and man.can_work() self._available_cache[t] = available if available: available = True else: available = man.is_enabled() and man.can_work() if available: if man not in self.working_managers: self.working_managers.append(man) else: if man in self.working_managers: self.working_managers.remove(man) return available def _search(self, word: str, is_url: bool, man: SoftwareManager, disk_loader, res: SearchResult): if self._can_work(man): mti = time.time() apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(apps_found.installed) res.new.extend(apps_found.new) def search(self, word: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult([], [], 0) if internet.is_available(self.context.http_client, self.context.logger): norm_word = word.strip().lower() url_words = RE_IS_URL.match(norm_word) disk_loader = self.disk_loader_factory.new() disk_loader.start() threads = [] for man in self.managers: t = Thread(target=self._search, args=(norm_word, url_words, man, disk_loader, res)) t.start() threads.append(t) for t in threads: t.join() if disk_loader: disk_loader.stop_working() disk_loader.join() res.installed = self._sort(res.installed, norm_word) res.new = self._sort(res.new, norm_word) res.total = len(res.installed) + len(res.new) else: raise NoInternetException() tf = time.time() self.logger.info('Took {0:.2f} seconds'.format(tf - ti)) return res def _wait_to_be_ready(self): if self.thread_prepare: self.thread_prepare.join() self.thread_prepare = None def set_enabled(self, enabled: bool): pass def can_work(self) -> bool: return True def _is_internet_available(self, res: dict): res['available'] = internet.is_available(self.context.http_client, self.context.logger) def _get_internet_check(self, res: dict) -> Thread: t = Thread(target=self._is_internet_available, args=(res, )) t.start() return t def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, net_check: bool = None) -> SearchResult: ti = time.time() self._wait_to_be_ready() net_check = {} thread_internet_check = self._get_internet_check(net_check) res = SearchResult([], None, 0) disk_loader = None internet_available = None if not pkg_types: # any type for man in self.managers: if self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() if internet_available is None: thread_internet_check.join() internet_available = net_check.get('available', True) mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=internet_available) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(man_res.installed) res.total += man_res.total else: man_already_used = [] for t in pkg_types: man = self.map.get(t) if man and (man not in man_already_used) and self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() if internet_available is None: thread_internet_check.join() internet_available = net_check.get('available', True) mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=internet_available) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(man_res.installed) res.total += man_res.total if disk_loader: disk_loader.stop_working() disk_loader.join() tf = time.time() self.logger.info('Took {0:.2f} seconds'.format(tf - ti)) return res def downgrade(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man and app.can_be_downgraded(): mti = time.time() res = man.downgrade(app, root_password, handler) mtf = time.time() self.logger.info('Took {0:.2f} seconds'.format(mtf - mti)) return res else: raise Exception("downgrade is not possible for {}".format( app.__class__.__name__)) def clean_cache_for(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.clean_cache_for(app) def update(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man: return man.update(app, root_password, handler) def uninstall(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man: return man.uninstall(app, root_password, handler) def install(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man: ti = time.time() try: self.logger.info('Installing {}'.format(app)) return man.install(app, root_password, handler) except: traceback.print_exc() return False finally: tf = time.time() self.logger.info('Installation of {}'.format(app) + 'took {0:.2f} minutes'.format((tf - ti) / 60)) def get_info(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.get_info(app) def get_history(self, app: SoftwarePackage) -> PackageHistory: man = self._get_manager_for(app) if man: mti = time.time() history = man.get_history(app) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) return history def get_managed_types(self) -> Set[Type[SoftwarePackage]]: pass def is_enabled(self): return True def _get_manager_for(self, app: SoftwarePackage) -> SoftwareManager: man = self.map[app.__class__] return man if man and self._can_work(man) else None def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): if self.context.disk_cache and pkg.supports_disk_cache(): man = self._get_manager_for(pkg) if man: return man.cache_to_disk(pkg, icon_bytes=icon_bytes, only_icon=only_icon) def requires_root(self, action: str, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.requires_root(action, app) def _prepare(self): if self.managers: for man in self.managers: if man not in self._already_prepared and self._can_work(man): man.prepare() self._already_prepared.append(man) def prepare(self): self.thread_prepare = Thread(target=self._prepare) self.thread_prepare.start() def list_updates(self, net_check: bool = None) -> List[PackageUpdate]: self._wait_to_be_ready() updates = [] if self.managers: net_check = {} thread_internet_check = self._get_internet_check(net_check) for man in self.managers: if self._can_work(man): if thread_internet_check.is_alive(): thread_internet_check.join() man_updates = man.list_updates( internet_available=net_check['available']) if man_updates: updates.extend(man_updates) return updates def list_warnings(self, internet_available: bool = None) -> List[str]: warnings = [] if self.managers: int_available = internet.is_available(self.context.http_client, self.context.logger) for man in self.managers: if man.is_enabled(): man_warnings = man.list_warnings( internet_available=int_available) if man_warnings: if warnings is None: warnings = [] warnings.extend(man_warnings) return warnings def _fill_suggestions(self, suggestions: list, man: SoftwareManager, limit: int, filter_installed: bool): if self._can_work(man): mti = time.time() man_sugs = man.list_suggestions(limit=limit, filter_installed=filter_installed) mtf = time.time() self.logger.info(man.__class__.__name__ + ' took {0:.2f} seconds'.format(mtf - mti)) if man_sugs: if 0 < limit < len(man_sugs): man_sugs = man_sugs[0:limit] suggestions.extend(man_sugs) def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]: if bool(self.config['suggestions']['enabled']): if self.managers and internet.is_available( self.context.http_client, self.context.logger): suggestions, threads = [], [] for man in self.managers: t = Thread( target=self._fill_suggestions, args=(suggestions, man, int(self.config['suggestions']['by_type']), filter_installed)) t.start() threads.append(t) for t in threads: t.join() if suggestions: suggestions.sort(key=lambda s: s.priority.value, reverse=True) return suggestions return [] def execute_custom_action(self, action: PackageAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher): man = self._get_manager_for(pkg) if man: return exec( 'man.{}(pkg=pkg, root_password=root_password, watcher=watcher)' .format(action.manager_method)) def is_default_enabled(self) -> bool: return True def launch(self, pkg: SoftwarePackage): self._wait_to_be_ready() man = self._get_manager_for(pkg) if man: self.logger.info('Launching {}'.format(pkg)) man.launch(pkg) def get_screenshots(self, pkg: SoftwarePackage): man = self._get_manager_for(pkg) if man: return man.get_screenshots(pkg) def get_working_managers(self): return [m for m in self.managers if self._can_work(m)] def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: if self.settings_manager is None: self.settings_manager = GenericSettingsManager( managers=self.managers, working_managers=self.working_managers, logger=self.logger, i18n=self.i18n) else: self.settings_manager.managers = self.managers self.settings_manager.working_managers = self.working_managers return self.settings_manager.get_settings(screen_width=screen_width, screen_height=screen_height) def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]: return self.settings_manager.save_settings(component) def sort_update_order( self, pkgs: List[SoftwarePackage]) -> List[SoftwarePackage]: by_manager = {} for pkg in pkgs: man = self._get_manager_for(pkg) if man: man_pkgs = by_manager.get(man) if man_pkgs is None: man_pkgs = [] by_manager[man] = man_pkgs man_pkgs.append(pkg) sorted_list = [] if by_manager: for man, pkgs in by_manager.items(): if len(pkgs) > 1: ti = time.time() sorted_list.extend(man.sort_update_order(pkgs)) tf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(tf - ti)) else: self.logger.info( "Only one package to sort for {}. Ignoring sorting.". format(man.__class__.__name__)) sorted_list.extend(pkgs) return sorted_list def get_update_requirements( self, pkgs: List[SoftwarePackage], watcher: ProcessWatcher) -> List[SoftwarePackage]: by_manager = {} for pkg in pkgs: man = self._get_manager_for(pkg) if man: man_pkgs = by_manager.get(man) if man_pkgs is None: man_pkgs = [] by_manager[man] = man_pkgs man_pkgs.append(pkg) required = [] if by_manager: for man, pkgs in by_manager.items(): ti = time.time() required.extend(man.get_update_requirements(pkgs, watcher)) tf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(tf - ti)) return required
class GenericSoftwareManager(SoftwareManager): def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict, settings_manager: GenericSettingsManager = None): super(GenericSoftwareManager, self).__init__(context=context) self.managers = managers self.map = {t: m for m in self.managers for t in m.get_managed_types()} self._available_cache = {} if config['system'][ 'single_dependency_checking'] else None self.thread_prepare = None self.i18n = context.i18n self.disk_loader_factory = context.disk_loader_factory self.logger = context.logger self._already_prepared = [] self.working_managers = [] self.config = config self.settings_manager = settings_manager self.http_client = context.http_client self.extra_actions = [ CustomSoftwareAction(i18_label_key='action.reset', i18n_status_key='action.reset.status', manager_method='reset', manager=self, icon_path=resource.get_path('img/logo.svg'), requires_root=False, refresh=False) ] def reset_cache(self): if self._available_cache is not None: self._available_cache = {} self.working_managers.clear() def _sort(self, apps: List[SoftwarePackage], word: str) -> List[SoftwarePackage]: exact_name_matches, contains_name_matches, others = [], [], [] for app in apps: lower_name = app.name.lower() if word == lower_name: exact_name_matches.append(app) elif word in lower_name: contains_name_matches.append(app) else: others.append(app) res = [] for app_list in (exact_name_matches, contains_name_matches, others): app_list.sort(key=lambda a: a.name.lower()) res.extend(app_list) return res def _can_work(self, man: SoftwareManager): if self._available_cache is not None: available = False for t in man.get_managed_types(): available = self._available_cache.get(t) if available is None: available = man.is_enabled() and man.can_work() self._available_cache[t] = available if available: available = True else: available = man.is_enabled() and man.can_work() if available: if man not in self.working_managers: self.working_managers.append(man) else: if man in self.working_managers: self.working_managers.remove(man) return available def _search(self, word: str, is_url: bool, man: SoftwareManager, disk_loader, res: SearchResult): if self._can_work(man): mti = time.time() apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(apps_found.installed) res.new.extend(apps_found.new) def search(self, word: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult([], [], 0) if internet.is_available(): norm_word = word.strip().lower() url_words = RE_IS_URL.match(norm_word) disk_loader = self.disk_loader_factory.new() disk_loader.start() threads = [] for man in self.managers: t = Thread(target=self._search, args=(norm_word, url_words, man, disk_loader, res)) t.start() threads.append(t) for t in threads: t.join() if disk_loader: disk_loader.stop_working() disk_loader.join() res.installed = self._sort(res.installed, norm_word) res.new = self._sort(res.new, norm_word) res.total = len(res.installed) + len(res.new) else: raise NoInternetException() tf = time.time() self.logger.info('Took {0:.2f} seconds'.format(tf - ti)) return res def _wait_to_be_ready(self): if self.thread_prepare: self.thread_prepare.join() self.thread_prepare = None def set_enabled(self, enabled: bool): pass def can_work(self) -> bool: return True def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult([], None, 0) disk_loader = None net_available = internet.is_available() if not pkg_types: # any type for man in self.managers: if self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=net_available) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(man_res.installed) res.total += man_res.total else: man_already_used = [] for t in pkg_types: man = self.map.get(t) if man and (man not in man_already_used) and self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=net_available) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) res.installed.extend(man_res.installed) res.total += man_res.total if disk_loader: disk_loader.stop_working() disk_loader.join() if res.installed: for p in res.installed: if p.is_update_ignored(): if p.categories is None: p.categories = ['updates_ignored'] elif 'updates_ignored' not in p.categories: p.categories.append('updates_ignored') tf = time.time() self.logger.info('Took {0:.2f} seconds'.format(tf - ti)) return res def downgrade(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man and app.can_be_downgraded(): mti = time.time() res = man.downgrade(app, root_password, handler) mtf = time.time() self.logger.info('Took {0:.2f} seconds'.format(mtf - mti)) return res else: raise Exception("downgrade is not possible for {}".format( app.__class__.__name__)) def clean_cache_for(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.clean_cache_for(app) def upgrade(self, requirements: GenericUpgradeRequirements, root_password: str, handler: ProcessWatcher) -> bool: for man, man_reqs in requirements.sub_requirements.items(): res = man.upgrade(man_reqs, root_password, handler) if not res: return False return True def uninstall(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man: return man.uninstall(app, root_password, handler) def install(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man: ti = time.time() try: self.logger.info('Installing {}'.format(app)) return man.install(app, root_password, handler) except: traceback.print_exc() return False finally: tf = time.time() self.logger.info('Installation of {}'.format(app) + 'took {0:.2f} minutes'.format((tf - ti) / 60)) def get_info(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.get_info(app) def get_history(self, app: SoftwarePackage) -> PackageHistory: man = self._get_manager_for(app) if man: mti = time.time() history = man.get_history(app) mtf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) return history def get_managed_types(self) -> Set[Type[SoftwarePackage]]: pass def is_enabled(self): return True def _get_manager_for(self, app: SoftwarePackage) -> SoftwareManager: man = self.map[app.__class__] return man if man and self._can_work(man) else None def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): if pkg.supports_disk_cache(): man = self._get_manager_for(pkg) if man: return man.cache_to_disk(pkg, icon_bytes=icon_bytes, only_icon=only_icon) def requires_root(self, action: str, app: SoftwarePackage) -> bool: if app is None: if self.managers: for man in self.managers: if self._can_work(man): if man.requires_root(action, app): return True return False else: man = self._get_manager_for(app) if man: return man.requires_root(action, app) def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): if self.managers: internet_on = internet.is_available() for man in self.managers: if man not in self._already_prepared and self._can_work(man): if task_manager: man.prepare(task_manager, root_password, internet_on) self._already_prepared.append(man) def list_updates(self, internet_available: bool = None) -> List[PackageUpdate]: self._wait_to_be_ready() updates = [] if self.managers: net_available = internet.is_available() for man in self.managers: if self._can_work(man): man_updates = man.list_updates( internet_available=net_available) if man_updates: updates.extend(man_updates) return updates def list_warnings(self, internet_available: bool = None) -> List[str]: warnings = [] int_available = internet.is_available() if int_available: updates_msg = check_for_update(self.logger, self.http_client, self.i18n) if updates_msg: warnings.append(updates_msg) if self.managers: for man in self.managers: if man.is_enabled(): man_warnings = man.list_warnings( internet_available=int_available) if man_warnings: if warnings is None: warnings = [] warnings.extend(man_warnings) return warnings def _fill_suggestions(self, suggestions: list, man: SoftwareManager, limit: int, filter_installed: bool): if self._can_work(man): mti = time.time() man_sugs = man.list_suggestions(limit=limit, filter_installed=filter_installed) mtf = time.time() self.logger.info(man.__class__.__name__ + ' took {0:.2f} seconds'.format(mtf - mti)) if man_sugs: if 0 < limit < len(man_sugs): man_sugs = man_sugs[0:limit] suggestions.extend(man_sugs) def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]: if bool(self.config['suggestions']['enabled']): if self.managers and internet.is_available(): suggestions, threads = [], [] for man in self.managers: t = Thread( target=self._fill_suggestions, args=(suggestions, man, int(self.config['suggestions']['by_type']), filter_installed)) t.start() threads.append(t) for t in threads: t.join() if suggestions: suggestions.sort(key=lambda s: s.priority.value, reverse=True) return suggestions return [] def execute_custom_action(self, action: CustomSoftwareAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher): man = action.manager if action.manager else self._get_manager_for(pkg) if man: return eval( 'man.{}({}root_password=root_password, watcher=watcher)'. format(action.manager_method, 'pkg=pkg, ' if pkg else '')) def is_default_enabled(self) -> bool: return True def launch(self, pkg: SoftwarePackage): self._wait_to_be_ready() man = self._get_manager_for(pkg) if man: self.logger.info('Launching {}'.format(pkg)) man.launch(pkg) def get_screenshots(self, pkg: SoftwarePackage): man = self._get_manager_for(pkg) if man: return man.get_screenshots(pkg) def get_working_managers(self): return [m for m in self.managers if self._can_work(m)] def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: if self.settings_manager is None: self.settings_manager = GenericSettingsManager( managers=self.managers, working_managers=self.working_managers, logger=self.logger, i18n=self.i18n, file_downloader=self.context.file_downloader) else: self.settings_manager.managers = self.managers self.settings_manager.working_managers = self.working_managers return self.settings_manager.get_settings(screen_width=screen_width, screen_height=screen_height) def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]: return self.settings_manager.save_settings(component) def _map_pkgs_by_manager(self, pkgs: List[SoftwarePackage], pkg_filters: list = None ) -> Dict[SoftwareManager, List[SoftwarePackage]]: by_manager = {} for pkg in pkgs: if pkg_filters and not all((1 for f in pkg_filters if f(pkg))): continue man = self._get_manager_for(pkg) if man: man_pkgs = by_manager.get(man) if man_pkgs is None: man_pkgs = [] by_manager[man] = man_pkgs man_pkgs.append(pkg) return by_manager def get_upgrade_requirements( self, pkgs: List[SoftwarePackage], root_password: str, watcher: ProcessWatcher) -> UpgradeRequirements: by_manager = self._map_pkgs_by_manager(pkgs) res = GenericUpgradeRequirements([], [], [], [], {}) if by_manager: for man, pkgs in by_manager.items(): ti = time.time() man_reqs = man.get_upgrade_requirements( pkgs, root_password, watcher) tf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(tf - ti)) if not man_reqs: return # it means the process should be stopped if man_reqs: res.sub_requirements[man] = man_reqs if man_reqs.to_install: res.to_install.extend(man_reqs.to_install) if man_reqs.to_remove: res.to_remove.extend(man_reqs.to_remove) if man_reqs.to_upgrade: res.to_upgrade.extend(man_reqs.to_upgrade) if man_reqs.cannot_upgrade: res.cannot_upgrade.extend(man_reqs.cannot_upgrade) return res def reset(self, root_password: str, watcher: ProcessWatcher) -> bool: body = '<p>{}</p><p>{}</p>'.format( self.i18n['action.reset.body_1'].format(bold( self.context.app_name)), self.i18n['action.reset.body_2']) if watcher.request_confirmation( title=self.i18n['action.reset'], body=body, confirmation_label=self.i18n['proceed'].capitalize(), deny_label=self.i18n['cancel'].capitalize()): try: clean_app_files(managers=self.managers, logs=False) restart_app() except: return False return True def get_custom_actions(self) -> List[CustomSoftwareAction]: actions = [] if self.managers: working_managers = [] for man in self.managers: if self._can_work(man): working_managers.append(man) if working_managers: working_managers.sort(key=lambda m: m.__class__.__name__) for man in working_managers: man_actions = man.get_custom_actions() if man_actions: actions.extend(man_actions) actions.extend(self.extra_actions) return actions def _fill_sizes(self, man: SoftwareManager, pkgs: List[SoftwarePackage]): ti = time.time() man.fill_sizes(pkgs) tf = time.time() self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(tf - ti)) def fill_sizes(self, pkgs: List[SoftwarePackage]): by_manager = self._map_pkgs_by_manager( pkgs, pkg_filters=[lambda p: p.size is None]) if by_manager: threads = [] for man, man_pkgs in by_manager.items(): if man_pkgs: t = Thread(target=self._fill_sizes, args=(man, man_pkgs), daemon=True) t.start() threads.append(t) for t in threads: t.join() def ignore_update(self, pkg: SoftwarePackage): manager = self._get_manager_for(pkg) if manager: manager.ignore_update(pkg) if pkg.is_update_ignored(): if pkg.categories is None: pkg.categories = ['updates_ignored'] elif 'updates_ignored' not in pkg.categories: pkg.categories.append('updates_ignored') def revert_ignored_update(self, pkg: SoftwarePackage): manager = self._get_manager_for(pkg) if manager: manager.revert_ignored_update(pkg) if not pkg.is_update_ignored( ) and pkg.categories and 'updates_ignored' in pkg.categories: pkg.categories.remove('updates_ignored')
class GenericSoftwareManager(SoftwareManager, SettingsController): def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict, force_suggestions: bool = False): super(GenericSoftwareManager, self).__init__(context=context) self.managers = managers self.map = {t: m for m in self.managers for t in m.get_managed_types()} self._available_cache = {} if config['system'][ 'single_dependency_checking'] else None self.thread_prepare = None self.i18n = context.i18n self.disk_loader_factory = context.disk_loader_factory self.logger = context.logger self._already_prepared = [] self.working_managers = [] self.config = config self.settings_manager: Optional[GenericSettingsManager] = None self.http_client = context.http_client self.configman = CoreConfigManager() self._action_reset: Optional[CustomSoftwareAction] = None self._dynamic_extra_actions: Optional[Dict[CustomSoftwareAction, Callable[[dict], bool]]] = None self.force_suggestions = force_suggestions @property def dynamic_extra_actions( self) -> Dict[CustomSoftwareAction, Callable[[dict], bool]]: if self._dynamic_extra_actions is None: self._dynamic_extra_actions = { CustomSoftwareAction(i18n_label_key='action.backups', i18n_status_key='action.backups.status', i18n_description_key='action.backups.desc', manager_method='launch_timeshift', manager=self, icon_path='timeshift', requires_root=False, refresh=False): self.is_backups_action_available } return self._dynamic_extra_actions @property def action_reset(self) -> CustomSoftwareAction: if self._action_reset is None: self._action_reset = CustomSoftwareAction( i18n_label_key='action.reset', i18n_status_key='action.reset.status', i18n_description_key='action.reset.desc', manager_method='reset', icon_path=resource.get_path('img/logo.svg'), requires_root=False, manager=self, refresh=False) return self._action_reset def _is_timeshift_launcher_available(self) -> bool: return bool(shutil.which('timeshift-launcher')) def is_backups_action_available(self, app_config: dict) -> bool: return bool(app_config['backup'] ['enabled']) and self._is_timeshift_launcher_available() def reset_cache(self): if self._available_cache is not None: self._available_cache = {} self.working_managers.clear() def launch_timeshift(self, root_password: Optional[str], watcher: ProcessWatcher): if self._is_timeshift_launcher_available(): try: Popen(['timeshift-launcher'], stderr=STDOUT) return True except: traceback.print_exc() watcher.show_message( title=self.i18n["error"].capitalize(), body=self.i18n['action.backups.tool_error'].format( bold('Timeshift')), type_=MessageType.ERROR) return False else: watcher.show_message( title=self.i18n["error"].capitalize(), body=self.i18n['action.backups.tool_error'].format( bold('Timeshift')), type_=MessageType.ERROR) return False def _can_work(self, man: SoftwareManager): if self._available_cache is not None: available = False for t in man.get_managed_types(): available = self._available_cache.get(t) if available is None: available = man.is_enabled() and man.can_work()[0] self._available_cache[t] = available if available: available = True else: available = man.is_enabled() and man.can_work()[0] if available: if man not in self.working_managers: self.working_managers.append(man) else: if man in self.working_managers: self.working_managers.remove(man) return available def _search(self, word: str, is_url: bool, man: SoftwareManager, disk_loader, res: SearchResult): if self._can_work(man): mti = time.time() apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url, limit=-1) mtf = time.time() self.logger.info( f'{man.__class__.__name__} took {mtf - mti:.8f} seconds') res.installed.extend(apps_found.installed) res.new.extend(apps_found.new) def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult.empty() if self.context.is_internet_available(): norm_query = sanitize_command_input(words).lower() self.logger.info(f"Search query: {norm_query}") if norm_query: is_url = bool(RE_IS_URL.match(norm_query)) disk_loader = self.disk_loader_factory.new() disk_loader.start() threads = [] for man in self.managers: t = Thread(target=self._search, args=(norm_query, is_url, man, disk_loader, res)) t.start() threads.append(t) for t in threads: t.join() if disk_loader: disk_loader.stop_working() disk_loader.join() # res.installed = self._sort(res.installed, norm_word) # res.new = self._sort(res.new, norm_word) else: raise NoInternetException() res.update_total() tf = time.time() self.logger.info(f'Took {tf - ti:.8f} seconds') return res def _wait_to_be_ready(self): if self.thread_prepare: self.thread_prepare.join() self.thread_prepare = None def set_enabled(self, enabled: bool): pass def can_work(self) -> Tuple[bool, Optional[str]]: return True, None def _get_package_lower_name(self, pkg: SoftwarePackage): return pkg.name.lower() def read_installed(self, disk_loader: DiskCacheLoader = None, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult([], None, 0) disk_loader = None net_available = self.context.is_internet_available() if not pkg_types: # any type for man in self.managers: if self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=net_available) mtf = time.time() self.logger.info( f'{man.__class__.__name__} took {mtf - mti:.2f} seconds' ) res.installed.extend(man_res.installed) res.total += man_res.total else: man_already_used = [] for t in pkg_types: man = self.map.get(t) if man and (man not in man_already_used) and self._can_work(man): if not disk_loader: disk_loader = self.disk_loader_factory.new() disk_loader.start() mti = time.time() man_res = man.read_installed( disk_loader=disk_loader, pkg_types=None, internet_available=net_available) mtf = time.time() self.logger.info( f'{man.__class__.__name__} took {mtf - mti:.2f} seconds' ) res.installed.extend(man_res.installed) res.total += man_res.total if disk_loader: disk_loader.stop_working() disk_loader.join() if res.installed: for p in res.installed: if p.is_update_ignored(): if p.categories is None: p.categories = ['updates_ignored'] elif 'updates_ignored' not in p.categories: self._add_category(p, 'updates_ignored') res.installed.sort(key=self._get_package_lower_name) tf = time.time() self.logger.info(f'Took {tf - ti:.2f} seconds') return res def _add_category(self, pkg: SoftwarePackage, category: str): if isinstance(pkg.categories, tuple): pkg.categories = tuple((*pkg.categories, category)) elif isinstance(pkg.categories, list): pkg.categories.append(category) elif isinstance(pkg.categories, set): pkg.categories.add(category) def downgrade(self, app: SoftwarePackage, root_password: Optional[str], handler: ProcessWatcher) -> bool: man = self._get_manager_for(app) if man and app.can_be_downgraded(): mti = time.time() res = man.downgrade(app, root_password, handler) mtf = time.time() self.logger.info(f'Took {mtf - mti:.2f} seconds') return res else: raise Exception( f"Downgrading is not possible for {app.__class__.__name__}") def clean_cache_for(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.clean_cache_for(app) def upgrade(self, requirements: GenericUpgradeRequirements, root_password: Optional[str], handler: ProcessWatcher) -> bool: for man, man_reqs in requirements.sub_requirements.items(): res = man.upgrade(man_reqs, root_password, handler) if not res: return False return True def _fill_post_transaction_status(self, pkg: SoftwarePackage, installed: bool): pkg.installed = installed pkg.update = False if pkg.latest_version: pkg.version = pkg.latest_version def _update_post_transaction_status(self, res: TransactionResult): if res.success: if res.installed: for p in res.installed: self._fill_post_transaction_status(p, True) if res.removed: for p in res.removed: self._fill_post_transaction_status(p, False) def uninstall(self, pkg: SoftwarePackage, root_password: Optional[str], handler: ProcessWatcher, disk_loader: DiskCacheLoader = None) -> TransactionResult: man = self._get_manager_for(pkg) if man: ti = time.time() disk_loader = self.disk_loader_factory.new() disk_loader.start() self.logger.info(f"Uninstalling {pkg.name}") try: res = man.uninstall(pkg, root_password, handler, disk_loader) disk_loader.stop_working() disk_loader.join() self._update_post_transaction_status(res) return res except: traceback.print_exc() return TransactionResult(success=False, installed=[], removed=[]) finally: tf = time.time() self.logger.info( f'Uninstallation of {pkg} took {(tf - ti) / 60:.2f} minutes' ) def install(self, app: SoftwarePackage, root_password: Optional[str], disk_loader: DiskCacheLoader, handler: ProcessWatcher) -> TransactionResult: man = self._get_manager_for(app) if man: ti = time.time() disk_loader = self.disk_loader_factory.new() disk_loader.start() try: self.logger.info(f'Installing {app}') res = man.install(app, root_password, disk_loader, handler) disk_loader.stop_working() disk_loader.join() self._update_post_transaction_status(res) return res except: traceback.print_exc() return TransactionResult(success=False, installed=[], removed=[]) finally: tf = time.time() self.logger.info( f'Installation of {app} took {(tf - ti) / 60:.2f} minutes') def get_info(self, app: SoftwarePackage): man = self._get_manager_for(app) if man: return man.get_info(app) def get_history(self, app: SoftwarePackage) -> PackageHistory: man = self._get_manager_for(app) if man: mti = time.time() history = man.get_history(app) mtf = time.time() self.logger.info( f'{man.__class__.__name__} took {mtf - mti:.2f} seconds') return history def get_managed_types(self) -> Set[Type[SoftwarePackage]]: available_types = set() for man in self.get_working_managers(): available_types.update(man.get_managed_types()) return available_types def is_enabled(self): return True def _get_manager_for(self, app: SoftwarePackage) -> SoftwareManager: man = self.map[app.__class__] return man if man and self._can_work(man) else None def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): if pkg.supports_disk_cache(): man = self._get_manager_for(pkg) if man: return man.cache_to_disk(pkg, icon_bytes=icon_bytes, only_icon=only_icon) def requires_root(self, action: SoftwareAction, app: SoftwarePackage) -> bool: if app is None: if self.managers: for man in self.managers: if self._can_work(man): if man.requires_root(action, app): return True return False else: man = self._get_manager_for(app) if man: return man.requires_root(action, app) def prepare(self, task_manager: TaskManager, root_password: Optional[str], internet_available: bool): ti = time.time() self.logger.info("Initializing") taskman = task_manager if task_manager else TaskManager( ) # empty task manager to prevent null pointers create_config = CreateConfigFile( taskman=taskman, configman=self.configman, i18n=self.i18n, task_icon_path=get_path('img/logo.svg'), logger=self.logger) create_config.start() if self.managers: internet_on = self.context.is_internet_available() prepare_tasks = [] for man in self.managers: if man not in self._already_prepared and self._can_work(man): t = Thread(target=man.prepare, args=(taskman, root_password, internet_on), daemon=True) t.start() prepare_tasks.append(t) self._already_prepared.append(man) for t in prepare_tasks: t.join() create_config.join() tf = time.time() self.logger.info(f'Finished ({tf - ti:.2f} seconds)') def cache_available_managers(self): if self.managers: for man in self.managers: self._can_work(man) def list_updates(self, internet_available: bool = None) -> List[PackageUpdate]: self._wait_to_be_ready() updates = [] if self.managers: net_available = self.context.is_internet_available() for man in self.managers: if self._can_work(man): man_updates = man.list_updates( internet_available=net_available) if man_updates: updates.extend(man_updates) return updates def list_warnings(self, internet_available: bool = None) -> List[str]: warnings = [] int_available = self.context.is_internet_available() if int_available: updates_msg = check_for_update(self.logger, self.http_client, self.i18n) if updates_msg: warnings.append(updates_msg) if self.managers: for man in self.managers: if self._can_work(man): man_warnings = man.list_warnings( internet_available=int_available) if man_warnings: warnings.extend(man_warnings) return warnings def _fill_suggestions(self, suggestions: list, man: SoftwareManager, limit: int, filter_installed: bool): if self._can_work(man): mti = time.time() man_sugs = man.list_suggestions(limit=limit, filter_installed=filter_installed) mtf = time.time() self.logger.info( f'{man.__class__.__name__} took {mtf - mti:.5f} seconds') if man_sugs: if 0 < limit < len(man_sugs): man_sugs = man_sugs[0:limit] suggestions.extend(man_sugs) def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]: if self.force_suggestions or bool( self.config['suggestions']['enabled']): if self.managers and self.context.is_internet_available(): suggestions, threads = [], [] for man in self.managers: t = Thread( target=self._fill_suggestions, args=(suggestions, man, int(self.config['suggestions']['by_type']), filter_installed)) t.start() threads.append(t) for t in threads: t.join() if suggestions: suggestions.sort(key=lambda s: s.priority.value, reverse=True) return suggestions return [] def execute_custom_action(self, action: CustomSoftwareAction, pkg: SoftwarePackage, root_password: Optional[str], watcher: ProcessWatcher): if action.requires_internet and not self.context.is_internet_available( ): raise NoInternetException() man = action.manager if action.manager else self._get_manager_for(pkg) if man: return eval( f"man.{action.manager_method}({'pkg=pkg, ' if pkg else ''}root_password=root_password, watcher=watcher)" ) def is_default_enabled(self) -> bool: return True def launch(self, pkg: SoftwarePackage): self._wait_to_be_ready() man = self._get_manager_for(pkg) if man: self.logger.info(f'Launching {pkg}') man.launch(pkg) def get_screenshots(self, pkg: SoftwarePackage) -> Generator[str, None, None]: man = self._get_manager_for(pkg) if man: yield from man.get_screenshots(pkg) def get_working_managers(self): return [m for m in self.managers if self._can_work(m)] def get_settings(self) -> Optional[Generator[SettingsView, None, None]]: if self.settings_manager is None: self.settings_manager = GenericSettingsManager( managers=self.managers, working_managers=self.working_managers, configman=self.configman, context=self.context) else: self.settings_manager.managers = self.managers self.settings_manager.working_managers = self.working_managers yield SettingsView(self, self.settings_manager.get_settings()) def save_settings( self, component: TabGroupComponent) -> Tuple[bool, Optional[List[str]]]: return self.settings_manager.save_settings(component) def _map_pkgs_by_manager(self, pkgs: List[SoftwarePackage], pkg_filters: list = None ) -> Dict[SoftwareManager, List[SoftwarePackage]]: by_manager = {} for pkg in pkgs: if pkg_filters and not all((1 for f in pkg_filters if f(pkg))): continue man = self._get_manager_for(pkg) if man: man_pkgs = by_manager.get(man) if man_pkgs is None: man_pkgs = [] by_manager[man] = man_pkgs man_pkgs.append(pkg) return by_manager def get_upgrade_requirements( self, pkgs: List[SoftwarePackage], root_password: Optional[str], watcher: ProcessWatcher) -> UpgradeRequirements: by_manager = self._map_pkgs_by_manager(pkgs) res = GenericUpgradeRequirements([], [], [], [], {}) if by_manager: for man, pkgs in by_manager.items(): ti = time.time() man_reqs = man.get_upgrade_requirements( pkgs, root_password, watcher) tf = time.time() self.logger.info( f'{man.__class__.__name__} took {tf - ti:.2f} seconds') if not man_reqs: return # it means the process should be stopped if man_reqs: res.sub_requirements[man] = man_reqs if man_reqs.to_install: res.to_install.extend(man_reqs.to_install) if man_reqs.to_remove: res.to_remove.extend(man_reqs.to_remove) if man_reqs.to_upgrade: res.to_upgrade.extend(man_reqs.to_upgrade) if man_reqs.cannot_upgrade: res.cannot_upgrade.extend(man_reqs.cannot_upgrade) return res def reset(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool: body = f"<p>{self.i18n['action.reset.body_1'].format(bold(self.context.app_name))}</p>" \ f"<p>{self.i18n['action.reset.body_2']}</p>" if watcher.request_confirmation( title=self.i18n['action.reset'], body=body, confirmation_label=self.i18n['proceed'].capitalize(), deny_label=self.i18n['cancel'].capitalize()): try: clean_app_files(managers=self.managers, logs=False) restart_app() except: return False return True def gen_custom_actions( self) -> Generator[CustomSoftwareAction, None, None]: if self.managers: working_managers = [] for man in self.managers: if self._can_work(man): working_managers.append(man) if working_managers: working_managers.sort(key=lambda m: m.__class__.__name__) for man in working_managers: for action in man.gen_custom_actions(): action.manager = man yield action app_config = self.configman.get_config() for action, available in self.dynamic_extra_actions.items(): if available(app_config): yield action yield self.action_reset def _fill_sizes(self, man: SoftwareManager, pkgs: List[SoftwarePackage]): ti = time.time() man.fill_sizes(pkgs) tf = time.time() self.logger.info( f'{man.__class__.__name__} took {tf - ti:.2f} seconds') def fill_sizes(self, pkgs: List[SoftwarePackage]): by_manager = self._map_pkgs_by_manager( pkgs, pkg_filters=[lambda p: p.size is None]) if by_manager: threads = [] for man, man_pkgs in by_manager.items(): if man_pkgs: t = Thread(target=self._fill_sizes, args=(man, man_pkgs), daemon=True) t.start() threads.append(t) for t in threads: t.join() def ignore_update(self, pkg: SoftwarePackage): manager = self._get_manager_for(pkg) if manager: manager.ignore_update(pkg) if pkg.is_update_ignored(): if pkg.categories is None: pkg.categories = ['updates_ignored'] elif 'updates_ignored' not in pkg.categories: self._add_category(pkg, 'updates_ignored') def revert_ignored_update(self, pkg: SoftwarePackage): manager = self._get_manager_for(pkg) if manager: manager.revert_ignored_update(pkg) if not pkg.is_update_ignored( ) and pkg.categories and 'updates_ignored' in pkg.categories: if isinstance(pkg.categories, tuple): pkg.categories = tuple(c for c in pkg.categories if c != 'updates_ignored') else: pkg.categories.remove('updates_ignored')