def add_games(self, games): """Add games to the store""" self.media_loaded = False if games: AsyncCall(self.get_missing_media, None, [game["slug"] for game in games]) for game in list(games): GLib.idle_add(self.add_game, game)
def sync_library(self): """Synchronize games with local stuff and server.""" def update_gui(result, error): self.sync_label.set_label("Synchronize library") self.sync_spinner.props.active = False self.sync_button.set_sensitive(True) if error: if isinstance(error, http.UnauthorizedAccess): GLib.idle_add(self.show_invalid_credential_warning) else: GLib.idle_add(self.show_library_sync_error) return if result: added_ids, updated_ids = result self.game_store.add_games_by_ids(added_ids) for game_id in updated_ids.difference(added_ids): self.game_store.update_game_by_id(game_id) else: logger.error("No results returned when syncing the library") self.sync_label.set_label("Synchronizing…") self.sync_spinner.props.active = True self.sync_button.set_sensitive(False) AsyncCall(sync_from_remote, update_gui)
def sync_library(self): """Synchronize games with local stuff and server.""" def update_gui(result, error): if error: logger.error("Failed to synchrone library: %s", error) return if result: added_ids, updated_ids = result # sqlite limits the number of query parameters to 999, to # bypass that limitation, divide the query in chunks size = 999 added_games = chain.from_iterable([ pga.get_games_where( id__in=list(added_ids)[page * size:page * size + size]) for page in range(math.ceil(len(added_ids) / size)) ]) self.game_list += added_games self.switch_splash_screen() self.view.populate_games(added_games) GLib.idle_add(self.update_existing_games, added_ids, updated_ids, True) else: logger.error("No results returned when syncing the library") self.set_status("Syncing library") AsyncCall(sync_from_remote, update_gui)
def _iter_commands(self, result=None, exception=None): if result == 'STOP' or self.cancelled: return self.parent.set_status("Installing game data") self.parent.add_spinner() self.parent.continue_button.hide() commands = self.script.get('installer', []) if exception: self.parent.on_install_error(repr(exception)) elif self.current_command < len(commands): try: command = commands[self.current_command] except KeyError: raise ScriptingError( 'Installer commands are not formatted correctly') self.current_command += 1 method, params = self._map_command(command) if isinstance(params, dict): status_text = params.pop("description", None) else: status_text = None if status_text: self.parent.set_status(status_text) logger.debug('Installer command: %s', command) AsyncCall(method, self._iter_commands, params) else: self._finish_install()
def on_refresh_clicked(self, button): """Reload the service games""" button.set_sensitive(False) if self.service.online and not self.service.is_connected(): self.service.logout() return AsyncCall(self.service.reload, self.service_load_cb)
def install_steam_game(self, runner_class=None, is_game_files=False): """Launch installation of a steam game. runner_class: class of the steam runner to use is_game_files: whether game data is added to game_files """ # Check if Steam is installed, save the method's arguments so it can # be called again once Steam is installed. self.steam_data['callback_args'] = (runner_class, is_game_files) steam_runner = self._get_steam_runner(runner_class) self.steam_data['is_game_files'] = is_game_files appid = self.steam_data['appid'] if not steam_runner.get_game_path_from_appid(appid): logger.debug("Installing steam game %s", appid) steam_runner.config = LutrisConfig(runner_slug=self.runner) if 'arch' in self.steam_data: steam_runner.config.game_config['arch'] = self.steam_data[ 'arch'] AsyncCall(steam_runner.install_game, None, appid, is_game_files) self.install_start_time = time.localtime() self.steam_poll = GLib.timeout_add( 2000, self._monitor_steam_game_install) self.abort_current_task = ( lambda: steam_runner.remove_game_data(appid=appid)) return 'STOP' elif is_game_files: self._append_steam_data_to_files(runner_class) else: self.target_path = self._get_steam_game_path()
def get_games_from_filters(self): if self.filters.get("service"): service_name = self.filters["service"] if service_name in services.get_services(): self.service = services.get_services()[service_name]() if self.service.online: self.service.connect("service-login", self.on_service_games_updated) self.service.connect("service-logout", self.on_service_logout) self.service.connect("service-games-loaded", self.on_service_games_updated) service_games = ServiceGameCollection.get_for_service( service_name) if service_games: return [ game for game in sorted( service_games, key=lambda game: game.get(self.view_sorting) or game["name"], reverse=not self.view_sorting_ascending, ) if self.game_matches(game) ] if not self.service.online or self.service.is_connected(): AsyncCall(self.service.load, None) spinner = Gtk.Spinner(visible=True) spinner.start() self.blank_overlay.add(spinner) else: self.blank_overlay.add( Gtk.Label( _("Connect your %s account to access your games") % self.service.name, visible=True, )) self.blank_overlay.props.visible = True return self.unset_service() dynamic_categories = { "running": self.get_running_games, "installed": self.get_installed_games, } if self.filters.get("dynamic_category") in dynamic_categories: return dynamic_categories[self.filters["dynamic_category"]]() self.unset_service() if self.filters.get("category"): game_ids = categories_db.get_game_ids_for_category( self.filters["category"]) return games_db.get_games_by_ids(game_ids) searches, filters, excludes = self.get_sql_filters() return games_db.get_games( searches=searches, filters=filters, excludes=excludes, sorts=self.sort_params, )
def on_connect_clicked(self, _button): if self.service.is_authenticated(): AsyncCall(self._connect_button_toggle, None) self.service.logout() else: self._connect_button_toggle() self.service.login() return False
def load_games(self, force_reload=False): """Load the list of games in a treeview""" if self.games_loaded and not force_reload: return if self.service.ONLINE and not self.service.is_connected(): return syncer = self.service.SYNCER() AsyncCall(syncer.load, self.on_games_loaded, force_reload)
def update_existing_games(self, added, updated, first_run=False): for game_id in updated.difference(added): self.view.update_row(pga.get_game_by_field(game_id, 'id')) if first_run: icons_sync = AsyncCall(self.sync_icons, callback=None) self.threads_stoppers.append(icons_sync.stop_request.set) self.set_status("")
def on_sync_button_clicked(self, _button, sync_with_lutris_method): # pylint: disable=unused-argument """Called when the sync button is clicked. Launches the import of selected games """ syncer = self.service.SYNCER() AsyncCall(syncer.sync, self.on_service_synced, self.get_imported_games())
def on_connect_clicked(self, _button): if self.service.is_authenticated(): logger.debug("Disconnecting from %s", self.identifier) AsyncCall(self._connect_button_toggle, None) self.service.logout() else: logger.debug("Connecting to %s", self.identifier) self._connect_button_toggle() self.service.login() return False
def _on_toggle_with_callback(self, widget, _gparam, option): """Action for the checkbox's toggled signal. With callback method""" option_name = option["option"] callback = option["callback"] callback_on = option.get("callback_on") if widget.get_active() == callback_on or callback_on is None: AsyncCall(callback, self._on_callback_finished, widget, option, self.config) else: self.option_changed(widget, option_name, widget.get_active())
def __init__(self, game_id, parent=None): super().__init__(parent=parent) self.set_size_request(640, 128) self.game = Game(game_id) self.delete_files = False container = Gtk.VBox(visible=True) self.get_content_area().add(container) title_label = Gtk.Label(visible=True) title_label.set_line_wrap(True) title_label.set_alignment(0, 0.5) title_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) title_label.set_markup( "<span font_desc='14'><b>Uninstall %s</b></span>" % gtk_safe(self.game.name)) container.pack_start(title_label, False, False, 4) self.folder_label = Gtk.Label(visible=True) self.folder_label.set_alignment(0, 0.5) self.delete_button = Gtk.Button(_("Uninstall"), visible=True) self.delete_button.connect("clicked", self.on_delete_clicked) if not self.game.directory: self.folder_label.set_markup("No file will be deleted") elif len(get_games(searches={"directory": self.game.directory})) > 1: self.folder_label.set_markup( "The folder %s is used by other games and will be kept." % self.game.directory) elif is_removeable(self.game.directory): self.delete_button.set_sensitive(False) self.folder_label.set_markup("<i>Calculating size…</i>") AsyncCall(get_disk_size, self.folder_size_cb, self.game.directory) else: self.folder_label.set_markup( "Content of %s are protected and will not be deleted." % reverse_expanduser(self.game.directory)) container.pack_start(self.folder_label, False, False, 4) self.confirm_delete_button = Gtk.CheckButton() self.confirm_delete_button.set_active(True) container.pack_start(self.confirm_delete_button, False, False, 4) button_box = Gtk.HBox(visible=True) button_box.set_margin_top(30) style_context = button_box.get_style_context() style_context.add_class("linked") cancel_button = Gtk.Button(_("Cancel"), visible=True) cancel_button.connect("clicked", self.on_close) button_box.add(cancel_button) button_box.add(self.delete_button) container.pack_end(button_box, False, False, 0) self.show()
def update_existing_games(self, added, updated, first_run=False): """???""" for game_id in updated.difference(added): game = pga.get_game_by_field(game_id, "id") self.view.update_row(game["id"], game["year"], game["playtime"]) if first_run: self.update_games(added) game_slugs = [game["slug"] for game in self.game_list] AsyncCall(resources.get_missing_media, self.on_media_returned, game_slugs)
def scan_folder(self): """Scan a folder of already installed games""" self.title_label.set_markup("<b>Import games from a folder</b>") self.listbox.destroy() script_dlg = DirectoryDialog(_("Select folder to scan")) if not script_dlg.folder: self.destroy() return spinner = Gtk.Spinner(visible=True) spinner.start() self.vbox.pack_start(spinner, False, False, 18) AsyncCall(scan_directory, self._on_folder_scanned, script_dlg.folder)
def service_load_cb(self, games, error): if games is None: logger.warning("No game returned from the service") if not error and not games and self.service.id == "steam": # This should not be handled here, the steam service should raise an error error = _( "Failed to load games. Check that your profile is set to public during the sync." ) if error: ErrorDialog(str(error)) AsyncCall(self.service.add_installed_games, None) GLib.timeout_add(5000, self.enable_refresh_button)
def __init__( self, icon_type, filter_installed, sort_key, sort_ascending, show_installed_first=False, ): super(GameStore, self).__init__() self.games = pga.get_games(show_installed_first=show_installed_first) self.games_to_refresh = set() self.icon_type = icon_type self.filter_installed = filter_installed self.show_installed_first = show_installed_first self.filter_text = None self.filter_runner = None self.filter_platform = None self.store = Gtk.ListStore( int, str, str, Pixbuf, str, str, str, str, int, str, bool, int, str, str, str, ) if show_installed_first: self.store.set_sort_column_id(COL_INSTALLED, Gtk.SortType.DESCENDING) else: self.store.set_sort_column_id(COL_NAME, Gtk.SortType.ASCENDING) self.prevent_sort_update = False # prevent recursion with signals self.modelfilter = self.store.filter_new() self.modelfilter.set_visible_func(self.filter_view) self.modelsort = Gtk.TreeModelSort.sort_new_with_model( self.modelfilter) self.modelsort.connect("sort-column-changed", self.on_sort_column_changed) self.sort_view(sort_key, sort_ascending) self.medias = {"banner": {}, "icon": {}} self.media_loaded = False self.connect('media-loaded', self.on_media_loaded) self.connect('icon-loaded', self.on_icon_loaded) AsyncCall(self.get_missing_media)
def load_games(self, force_reload=False): """Load the list of games in a treeview""" if self.games_loaded and not force_reload: return if self.service.ONLINE and not self.service.is_connected(): return syncer = self.service.SYNCER() if force_reload: self.service.SERVICE.wipe_game_cache() self.is_connecting = True self.swap_content(self.get_content_widget()) AsyncCall(syncer.load, self.on_games_loaded)
def on_zoom_changed(self, adjustment): """Handler for zoom modification""" media_index = round(adjustment.props.value) adjustment.props.value = media_index service = self.service if self.service else LutrisService media_services = list(service.medias.keys()) if len(media_services) <= media_index: media_index = media_services.index(service.default_format) icon_type = media_services[media_index] if icon_type != self.icon_type: self.save_icon_type(icon_type) self.reload_service_media() self.show_spinner() AsyncCall(self.game_store.load_icons, self.icons_loaded_cb)
def update_existing_games(self, added, updated, first_run=False): """Updates the games in the view from the callback of the method Still, working on this docstring. If the implementation is shit, the docstring is as well. """ for game_id in updated.difference(added): game = pga.get_game_by_field(game_id, "id") self.view.update_row(game["id"], game["year"], game["playtime"]) if first_run: self.update_games(added) game_slugs = [game["slug"] for game in self.game_list] AsyncCall(resources.get_missing_media, self.on_media_returned, game_slugs)
def __init__(self, init_lutris): super().__init__() self.set_size_request(320, 60) self.set_border_width(24) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 12) label = Gtk.Label("Lutris is starting...") vbox.add(label) self.progress = Gtk.ProgressBar(visible=True) self.progress.set_pulse_step(0.1) vbox.add(self.progress) self.get_content_area().add(vbox) GLib.timeout_add(125, self.show_progress) self.show_all() AsyncCall(self.initialize, self.init_cb, init_lutris)
def install_steam_game(self): """Launch installation of a steam game""" if self.runner.get_game_path_from_appid(appid=self.appid): logger.info("Steam game %s is already installed", self.appid) self.emit("game-installed", self.appid) else: logger.debug("Installing steam game %s", self.appid) self.runner.config = LutrisConfig(runner_slug=self.runner.name) # FIXME Find a way to bring back arch support # steam_runner.config.game_config["arch"] = self.steam_data["arch"] AsyncCall(self.runner.install_game, self.on_steam_game_installed, self.appid) self.install_start_time = time.localtime() self.steam_poll = GLib.timeout_add(2000, self._monitor_steam_game_install) self.stop_func = lambda: self.runner.remove_game_data(appid=self.appid)
def update_search_results(self): # Don't start a search while another is going; defer it instead. if self.search_spinner.get_visible(): self.search_timer_id = GLib.timeout_add(750, self.update_search_results) return self.search_timer_id = None if self.text_query: self.search_spinner.show() self.search_spinner.start() AsyncCall(api.search_games, self.update_search_results_cb, self.text_query)
def __init__(self, init_lutris): super().__init__() self.set_size_request(320, 60) self.set_border_width(24) self.set_decorated(False) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 12) label = Gtk.Label(_("Checking for runtime updates, please wait…")) vbox.add(label) self.progress = Gtk.ProgressBar(visible=True) self.progress.set_pulse_step(0.1) vbox.add(self.progress) self.get_content_area().add(vbox) GLib.timeout_add(125, self.show_progress) self.show_all() AsyncCall(self.initialize, self.init_cb, init_lutris)
def get_api_games(self): """Return games from the lutris API""" if not self.filters.get("text"): return [] api_games = api.search_games(self.filters["text"]) if "icon" in self.icon_type: api_field = "icon_url" _service_media = LutrisIcon else: api_field = "banner_url" _service_media = LutrisBanner AsyncCall(download_icons, self.icons_download_cb, {g["slug"]: g[api_field] for g in api_games}, _service_media()) return api_games
def update_existing_games(self, added, updated, first_run=False): """???""" for game_id in updated.difference(added): # XXX this might not work if the game has no 'item' set logger.debug("Updating row for ID %s", game_id) self.view.update_row(pga.get_game_by_field(game_id, 'id')) if first_run: logger.info("Setting up view for first run") for game_id in added: logger.debug("Adding %s", game_id) self.add_game_to_view(game_id) icons_sync = AsyncCall(self.sync_icons, callback=None) self.threads_stoppers.append(icons_sync.stop_request.set) self.set_status("")
def sync_library(self): """Synchronize games with local stuff and server.""" def update_gui(result, error): if result: added_ids, updated_ids = result added_games = pga.get_game_by_field(added_ids, 'id', all=True) self.game_list += added_games self.view.populate_games(added_games) self.switch_splash_screen() GLib.idle_add(self.update_existing_games, added_ids, updated_ids, True) else: logger.error("No results returned when syncing the library") self.set_status("Syncing library") AsyncCall(sync_from_remote, update_gui)
def sync_services(self): """Sync local lutris library with current Steam games and desktop games""" def full_sync(syncer_cls): syncer = syncer_cls() games = syncer.load() return syncer.sync(games, full=True) def on_sync_complete(response, errors): """Callback to update the view on sync complete""" if errors: logger.error("Sync failed: %s", errors) added_games, removed_games = response self.update_games(added_games) for game_id in removed_games: self.remove_game_from_view(game_id) for service in get_services_synced_at_startup(): AsyncCall(full_sync, on_sync_complete, service.SYNCER)
def on_delete_clicked(self, button): button.set_sensitive(False) if self.delete_files and not hasattr(self.game.runner, "no_game_remove_warning"): dlg = QuestionDialog({ "question": _("Please confirm.\nEverything under <b>%s</b>\n" "will be deleted.") % gtk_safe(self.game.directory), "title": _("Permanently delete files?"), }) if dlg.result != Gtk.ResponseType.YES: button.set_sensitive(True) return if self.delete_files: self.folder_label.set_markup( "Uninstalling game and deleting files...") else: self.folder_label.set_markup("Uninstalling game...") AsyncCall(self.game.remove, self.delete_cb, self.delete_files)