From 643ca368a6023646c3c38f471c91e2221386a663 Mon Sep 17 00:00:00 2001 From: kramo <93832451+kra-mo@users.noreply.github.com> Date: Wed, 29 Mar 2023 00:23:23 +0200 Subject: [PATCH] Unify games import --- data/gtk/help-overlay.blp | 25 ++-- data/gtk/preferences.blp | 26 +++- data/gtk/window.blp | 20 +-- data/hu.kramo.Cartridges.gschema.xml | 9 ++ src/game.py | 4 +- src/main.py | 48 +++---- src/meson.build | 3 +- src/preferences.py | 21 +++ src/utils/bottles_parser.py | 78 ++--------- src/utils/create_details_window.py | 8 +- src/utils/heroic_parser.py | 95 ++++---------- src/utils/importer.py | 72 +++++++++++ src/utils/save_cover.py | 11 +- src/utils/{save_games.py => save_game.py} | 9 +- src/utils/steam_parser.py | 150 ++++------------------ src/window.py | 4 +- 16 files changed, 252 insertions(+), 331 deletions(-) create mode 100644 src/utils/importer.py rename src/utils/{save_games.py => save_game.py} (81%) diff --git a/data/gtk/help-overlay.blp b/data/gtk/help-overlay.blp index 0fa1d35..9982000 100644 --- a/data/gtk/help-overlay.blp +++ b/data/gtk/help-overlay.blp @@ -8,49 +8,54 @@ ShortcutsWindow help_overlay { max-height: 10; ShortcutsGroup { - title: C_("shortcut window", "General"); + title: C_("shortcuts window", "General"); ShortcutsShortcut { - title: C_("shortcut window", "Quit"); + title: C_("shortcuts window", "Quit"); action-name: "app.quit"; } ShortcutsShortcut { - title: C_("shortcut window", "Search"); + title: C_("shortcuts window", "Search"); action-name: "win.toggle_search"; } ShortcutsShortcut { - title: C_("shortcut window", "Show preferences"); + title: C_("shortcuts window", "Show preferences"); action-name: "app.preferences"; } ShortcutsShortcut { - title: C_("shortcut window", "Shortcuts"); + title: C_("shortcuts window", "Shortcuts"); action-name: "win.show-help-overlay"; } ShortcutsShortcut { - title: C_("shortcut window", "Undo"); + title: C_("shortcuts window", "Undo"); action-name: "win.undo_remove"; } ShortcutsShortcut { - title: C_("shortcut window", "Open menu"); + title: C_("shortcuts window", "Open menu"); action-name: "win.open_menu"; } } ShortcutsGroup { - title: C_("shortcut window", "Games"); + title: C_("shortcuts window", "Games"); ShortcutsShortcut { - title: C_("shortcut window", "Add new game"); + title: C_("shortcuts window", "Add new game"); action-name: "app.add_game"; } ShortcutsShortcut { - title: C_("shortcut window", "Show hidden games"); + title: C_("shortcuts window", "Import games"); + action-name: "app.import"; + } + + ShortcutsShortcut { + title: C_("shortcuts window", "Show hidden games"); action-name: "win.show_hidden"; } } diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp index f0bb691..f1ea811 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -3,7 +3,7 @@ using Adw 1; template PreferencesWindow : Adw.PreferencesWindow { search-enabled: false; - default-height: 550; + default-height: 500; Adw.PreferencesPage page { Adw.PreferencesGroup general_group { @@ -39,6 +39,14 @@ template PreferencesWindow : Adw.PreferencesWindow { Adw.PreferencesGroup steam_group { title: _("Steam"); + Adw.ActionRow { + title: _("Import From Steam"); + + Switch steam_switch { + valign: center; + } + } + Adw.ActionRow { title: _("Steam Install Location"); subtitle: _("Directory to use when importing games"); @@ -77,6 +85,14 @@ template PreferencesWindow : Adw.PreferencesWindow { Adw.PreferencesGroup heroic_group { title: _("Heroic"); + Adw.ActionRow { + title: _("Import From Heroic"); + + Switch heroic_switch { + valign: center; + } + } + Adw.ActionRow { title: _("Heroic Install Location"); subtitle: _("Directory to use when importing games"); @@ -115,6 +131,14 @@ template PreferencesWindow : Adw.PreferencesWindow { Adw.PreferencesGroup bottles_group { title: _("Bottles"); + Adw.ActionRow { + title: _("Import From Bottles"); + + Switch bottles_switch { + valign: center; + } + } + Adw.ActionRow { title: _("Bottles Install Location"); subtitle: _("Directory to use when importing games"); diff --git a/data/gtk/window.blp b/data/gtk/window.blp index 9a4767a..29b3493 100644 --- a/data/gtk/window.blp +++ b/data/gtk/window.blp @@ -384,23 +384,9 @@ menu add_games { } } section { - submenu { - label: _("Import from"); - item { - label: _("Steam"); - action: "app.steam_import"; - } - - item { - label: _("Heroic"); - action: "app.heroic_import"; - } - - item { - label: _("Bottles"); - action: "app.bottles_import"; - hidden-when: "action-disabled"; - } + item { + label: _("Import"); + action: "app.import"; } } } diff --git a/data/hu.kramo.Cartridges.gschema.xml b/data/hu.kramo.Cartridges.gschema.xml index dc2708e..b0304d0 100644 --- a/data/hu.kramo.Cartridges.gschema.xml +++ b/data/hu.kramo.Cartridges.gschema.xml @@ -10,12 +10,18 @@ false + + true + "~/.steam/" [] + + true + "~/.var/app/com.heroicgameslauncher.hgl/config/heroic/" @@ -28,6 +34,9 @@ true + + true + "~/.var/app/com.usebottles.bottles/data/bottles/" diff --git a/src/game.py b/src/game.py index d0e8fff..484c492 100644 --- a/src/game.py +++ b/src/game.py @@ -25,7 +25,7 @@ import sys from gi.repository import GdkPixbuf, Gio, Gtk -from .save_games import save_games +from .save_game import save_game @Gtk.Template(resource_path="/hu/kramo/Cartridges/gtk/game.ui") @@ -120,7 +120,7 @@ class game(Gtk.Box): # pylint: disable=invalid-name data["hidden"] = not data["hidden"] - save_games({self.game_id: data}) + save_game(data) def get_cover(self): diff --git a/src/main.py b/src/main.py index b346887..24a3281 100644 --- a/src/main.py +++ b/src/main.py @@ -33,8 +33,9 @@ from .bottles_parser import bottles_parser from .create_details_window import create_details_window from .get_games import get_games from .heroic_parser import heroic_parser +from .importer import Importer from .preferences import PreferencesWindow -from .save_games import save_games +from .save_game import save_game from .steam_parser import steam_parser from .window import CartridgesWindow @@ -49,18 +50,13 @@ class CartridgesApplication(Adw.Application): self.create_action( "preferences", self.on_preferences_action, ["comma"] ) - self.create_action("steam_import", self.on_steam_import_action) - self.create_action("heroic_import", self.on_heroic_import_action) - self.create_action("bottles_import", self.on_bottles_import_action) self.create_action("launch_game", self.on_launch_game_action) self.create_action("hide_game", self.on_hide_game_action) self.create_action("edit_details", self.on_edit_details_action) self.create_action("add_game", self.on_add_game_action, ["n"]) + self.create_action("import", self.on_import_action, ["i"]) self.create_action("remove_game", self.on_remove_game_action) - if os.name == "nt": - self.lookup_action("bottles_import").set_enabled(False) - self.win = None def do_activate(self): # pylint: disable=arguments-differ @@ -134,20 +130,6 @@ class CartridgesApplication(Adw.Application): def on_preferences_action(self, _widget, _callback=None): PreferencesWindow(self.win).present() - def on_steam_import_action(self, _widget, _callback=None): - # Handle the updating of games inside of the module because the function is async - steam_parser(self.win, self.on_steam_import_action) - - def on_heroic_import_action(self, _widget, _callback=None): - games = heroic_parser(self.win, self.on_heroic_import_action) - save_games(games) - self.win.update_games(games.keys()) - - def on_bottles_import_action(self, _widget, _callback=None): - games = bottles_parser(self.win, self.on_bottles_import_action) - save_games(games) - self.win.update_games(games.keys()) - def on_launch_game_action(self, _widget, _callback=None): # Launch the game and update the last played value @@ -155,7 +137,7 @@ class CartridgesApplication(Adw.Application): data = get_games([game_id])[game_id] data["last_played"] = int(time.time()) - save_games({game_id: data}) + save_game(data) self.win.games[game_id].launch() @@ -176,13 +158,33 @@ class CartridgesApplication(Adw.Application): def on_add_game_action(self, _widget, _callback=None): create_details_window(self.win) + def on_import_action(self, _widget, _callback=None): + self.win.importer = Importer(self.win) + + self.win.importer.blocker = True + + if self.win.schema.get_boolean("steam"): + steam_parser(self.win) + + if self.win.schema.get_boolean("heroic"): + heroic_parser(self.win) + + if self.win.schema.get_boolean("bottles"): + bottles_parser(self.win) + + self.win.importer.blocker = False + + if self.win.importer.import_dialog.is_visible and self.win.importer.queue == 0: + self.win.importer.queue = 1 + self.win.importer.save_game() + def on_remove_game_action(self, _widget, _callback=None): # Add "removed=True" to the game properties so it can be deleted on next init game_id = self.win.active_game_id data = get_games([game_id])[game_id] data["removed"] = True - save_games({game_id: data}) + save_game(data) self.win.update_games([game_id]) if self.win.stack.get_visible_child() == self.win.overview: diff --git a/src/meson.build b/src/meson.build index 32b0b14..f2cd13c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,11 +22,12 @@ cartridges_sources = [ 'window.py', 'preferences.py', 'game.py', + 'utils/importer.py', 'utils/steam_parser.py', 'utils/heroic_parser.py', 'utils/bottles_parser.py', 'utils/get_games.py', - 'utils/save_games.py', + 'utils/save_game.py', 'utils/save_cover.py', 'utils/create_dialog.py', 'utils/create_details_window.py' diff --git a/src/preferences.py b/src/preferences.py index b4a655d..400a3d2 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -32,17 +32,20 @@ class PreferencesWindow(Adw.PreferencesWindow): cover_launches_game_switch = Gtk.Template.Child() high_quality_images_switch = Gtk.Template.Child() + steam_switch = Gtk.Template.Child() steam_file_chooser_button = Gtk.Template.Child() steam_extra_file_chooser_button = Gtk.Template.Child() steam_clear_button_revealer = Gtk.Template.Child() steam_clear_button = Gtk.Template.Child() + heroic_switch = Gtk.Template.Child() heroic_file_chooser_button = Gtk.Template.Child() heroic_epic_switch = Gtk.Template.Child() heroic_gog_switch = Gtk.Template.Child() heroic_sideloaded_switch = Gtk.Template.Child() bottles_group = Gtk.Template.Child() + bottles_switch = Gtk.Template.Child() bottles_file_chooser_button = Gtk.Template.Child() def __init__(self, parent_widget, **kwargs): @@ -68,6 +71,18 @@ class PreferencesWindow(Adw.PreferencesWindow): "active", Gio.SettingsBindFlags.DEFAULT, ) + schema.bind( + "steam", + self.steam_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) + schema.bind( + "heroic", + self.heroic_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) schema.bind( "heroic-import-epic", self.heroic_epic_switch, @@ -86,6 +101,12 @@ class PreferencesWindow(Adw.PreferencesWindow): "active", Gio.SettingsBindFlags.DEFAULT, ) + schema.bind( + "bottles", + self.bottles_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ) filechooser = Gtk.FileDialog() diff --git a/src/utils/bottles_parser.py b/src/utils/bottles_parser.py index 315ca5b..9beee8d 100644 --- a/src/utils/bottles_parser.py +++ b/src/utils/bottles_parser.py @@ -21,24 +21,19 @@ import os import time import yaml -from gi.repository import GLib, Gtk - -from .create_dialog import create_dialog -from .save_cover import save_cover -def bottles_parser(parent_widget, action): +def bottles_parser(parent_widget): schema = parent_widget.schema bottles_dir = os.path.expanduser(schema.get_string("bottles-location")) - def bottles_not_found(): + if not os.path.isfile(os.path.join(bottles_dir, "library.yml")): if os.path.exists( os.path.expanduser("~/.var/app/com.usebottles.bottles/data/bottles/") ): schema.set_string( "bottles-location", "~/.var/app/com.usebottles.bottles/data/bottles/" ) - action(None, None) elif os.path.exists( os.path.join( os.getenv("XDG_DATA_HOME") @@ -54,42 +49,10 @@ def bottles_parser(parent_widget, action): "bottles", ), ) - action(None, None) else: - filechooser = Gtk.FileDialog.new() - - def set_bottles_dir(_source, result, _unused): - try: - schema.set_string( - "bottles-location", - filechooser.select_folder_finish(result).get_path(), - ) - action(None, None) - except GLib.GError: - return - - def choose_folder(_widget): - filechooser.select_folder(parent_widget, None, set_bottles_dir, None) - - def response(widget, response): - if response == "choose_folder": - choose_folder(widget) - - create_dialog( - parent_widget, - _("Couldn't Import Games"), - _("The Bottles directory cannot be found."), - "choose_folder", - _("Set Bottles Location"), - ).connect("response", response) - - if not os.path.isfile(os.path.join(bottles_dir, "library.yml")): - bottles_not_found() - return {} + return bottles_dir = os.path.expanduser(schema.get_string("bottles-location")) - - bottles_games = {} current_time = int(time.time()) with open(os.path.join(bottles_dir, "library.yml"), "r") as open_file: @@ -97,6 +60,10 @@ def bottles_parser(parent_widget, action): library = yaml.load(data, Loader=yaml.Loader) + importer = parent_widget.importer + importer.total_queue += len(library) + importer.queue += len(library) + for game in library: game = library[game] values = {} @@ -107,6 +74,7 @@ def bottles_parser(parent_widget, action): values["game_id"] in parent_widget.games and not parent_widget.games[values["game_id"]].removed ): + importer.save_game() continue values["name"] = game["name"] @@ -120,9 +88,8 @@ def bottles_parser(parent_widget, action): values["last_played"] = 0 if game["thumbnail"]: - save_cover( - values, - parent_widget, + importer.save_cover( + values["game_id"], os.path.join( bottles_dir, "bottles", @@ -131,27 +98,4 @@ def bottles_parser(parent_widget, action): game["thumbnail"].split(":")[1], ), ) - - bottles_games[values["game_id"]] = values - - if not bottles_games: - create_dialog( - parent_widget, - _("No Games Found"), - _("No new games were found in the Bottles library."), - ) - elif len(bottles_games) == 1: - create_dialog( - parent_widget, - _("Bottles Games Imported"), - _("Successfully imported 1 game."), - ) - elif len(bottles_games) > 1: - games_no = str(len(bottles_games)) - create_dialog( - parent_widget, - _("Bottles Games Imported"), - # The variable is the number of games - _(f"Successfully imported {games_no} games."), - ) - return bottles_games + importer.save_game(values) diff --git a/src/utils/create_details_window.py b/src/utils/create_details_window.py index 4163c9a..e37e6f5 100644 --- a/src/utils/create_details_window.py +++ b/src/utils/create_details_window.py @@ -26,7 +26,7 @@ from gi.repository import Adw, GdkPixbuf, Gio, GLib, GObject, Gtk from .create_dialog import create_dialog from .save_cover import save_cover -from .save_games import save_games +from .save_game import save_game def create_details_window(parent_widget, game_id=None): @@ -268,7 +268,7 @@ def create_details_window(parent_widget, game_id=None): return if pixbuf is not None: - save_cover(None, parent_widget, None, pixbuf, game_id) + save_cover(parent_widget, game_id, None, pixbuf) values["name"] = final_name values["developer"] = final_developer or None @@ -288,9 +288,9 @@ def create_details_window(parent_widget, game_id=None): with open(path, "r") as open_file: data = json.loads(open_file.read()) data.update(values) - save_games({game_id: data}) + save_game(data) else: - save_games({game_id: values}) + save_game(values) parent_widget.update_games([game_id]) if parent_widget.stack.get_visible_child() == parent_widget.overview: diff --git a/src/utils/heroic_parser.py b/src/utils/heroic_parser.py index 8597f7f..84c2a7f 100644 --- a/src/utils/heroic_parser.py +++ b/src/utils/heroic_parser.py @@ -22,17 +22,12 @@ import json import os import time -from gi.repository import GLib, Gtk -from .create_dialog import create_dialog -from .save_cover import save_cover - - -def heroic_parser(parent_widget, action): +def heroic_parser(parent_widget): schema = parent_widget.schema heroic_dir = os.path.expanduser(schema.get_string("heroic-location")) - def heroic_not_found(): + if not os.path.exists(os.path.join(heroic_dir, "config.json")): if os.path.exists( os.path.expanduser("~/.var/app/com.heroicgameslauncher.hgl/config/heroic/") ): @@ -40,7 +35,6 @@ def heroic_parser(parent_widget, action): "heroic-location", "~/.var/app/com.heroicgameslauncher.hgl/config/heroic/", ) - action(None, None) elif os.path.exists( os.path.join( os.getenv("XDG_CONFIG_HOME") @@ -56,49 +50,18 @@ def heroic_parser(parent_widget, action): "heroic", ), ) - action(None, None) elif os.path.exists(os.path.join(os.getenv("appdata"), "heroic")): schema.set_string( "heroic-location", os.path.join(os.getenv("appdata"), "heroic") ) - action(None, None) else: - filechooser = Gtk.FileDialog.new() - - def set_heroic_dir(_source, result, _unused): - try: - schema.set_string( - "heroic-location", - filechooser.select_folder_finish(result).get_path(), - ) - action(None, None) - except GLib.GError: - return - - def choose_folder(_widget): - filechooser.select_folder(parent_widget, None, set_heroic_dir, None) - - def response(widget, response): - if response == "choose_folder": - choose_folder(widget) - - create_dialog( - parent_widget, - _("Couldn't Import Games"), - _("The Heroic directory cannot be found."), - "choose_folder", - _("Set Heroic Location"), - ).connect("response", response) - - if not os.path.exists(os.path.join(heroic_dir, "config.json")): - heroic_not_found() - return {} + return heroic_dir = os.path.expanduser(schema.get_string("heroic-location")) - - heroic_games = {} current_time = int(time.time()) + importer = parent_widget.importer + # Import Epic games if not schema.get_boolean("heroic-import-epic"): pass @@ -114,6 +77,9 @@ def heroic_parser(parent_widget, action): if not game["is_installed"]: continue + importer.total_queue += 1 + importer.queue += 1 + values = {} app_name = game["app_name"] @@ -123,6 +89,7 @@ def heroic_parser(parent_widget, action): values["game_id"] in parent_widget.games and not parent_widget.games[values["game_id"]].removed ): + importer.save_game() continue values["name"] = game["title"] @@ -145,9 +112,9 @@ def heroic_parser(parent_widget, action): ).hexdigest(), ) if os.path.exists(image_path): - save_cover(values, parent_widget, image_path) + importer.save_cover(values["game_id"], image_path) - heroic_games[values["game_id"]] = values + importer.save_game(values) except KeyError: pass @@ -160,6 +127,10 @@ def heroic_parser(parent_widget, action): ) as open_file: data = open_file.read() installed = json.loads(data) + + importer.total_queue += len(installed["installed"]) + importer.queue += len(installed["installed"]) + for item in installed["installed"]: values = {} app_name = item["appName"] @@ -170,6 +141,7 @@ def heroic_parser(parent_widget, action): values["game_id"] in parent_widget.games and not parent_widget.games[values["game_id"]].removed ): + importer.save_game() continue # Get game title and developer from library.json as they are not present in installed.json @@ -188,7 +160,7 @@ def heroic_parser(parent_widget, action): hashlib.sha256(game["art_square"].encode()).hexdigest(), ) if os.path.exists(image_path): - save_cover(values, parent_widget, image_path) + importer.save_cover(values["game_id"], image_path) break values["executable"] = ( @@ -201,7 +173,7 @@ def heroic_parser(parent_widget, action): values["added"] = current_time values["last_played"] = 0 - heroic_games[values["game_id"]] = values + importer.save_game(values) # Import sideloaded games if not schema.get_boolean("heroic-import-sideload"): @@ -212,6 +184,10 @@ def heroic_parser(parent_widget, action): ) as open_file: data = open_file.read() library = json.loads(data) + + importer.total_queue += len(library["games"]) + importer.queue += len(library["games"]) + for item in library["games"]: values = {} app_name = item["app_name"] @@ -222,6 +198,7 @@ def heroic_parser(parent_widget, action): values["game_id"] in parent_widget.games and not parent_widget.games[values["game_id"]].removed ): + importer.save_game() continue values["name"] = item["title"] @@ -240,28 +217,6 @@ def heroic_parser(parent_widget, action): hashlib.sha256(item["art_square"].encode()).hexdigest(), ) if os.path.exists(image_path): - save_cover(values, parent_widget, image_path) + importer.save_cover(values["game_id"], image_path) - heroic_games[values["game_id"]] = values - - if not heroic_games: - create_dialog( - parent_widget, - _("No Games Found"), - _("No new games were found in the Heroic library."), - ) - elif len(heroic_games) == 1: - create_dialog( - parent_widget, - _("Heroic Games Imported"), - _("Successfully imported 1 game."), - ) - elif len(heroic_games) > 1: - games_no = str(len(heroic_games)) - create_dialog( - parent_widget, - _("Heroic Games Imported"), - # The variable is the number of games - _(f"Successfully imported {games_no} games."), - ) - return heroic_games + importer.save_game(values) diff --git a/src/utils/importer.py b/src/utils/importer.py new file mode 100644 index 0000000..8a57b72 --- /dev/null +++ b/src/utils/importer.py @@ -0,0 +1,72 @@ +from gi.repository import Adw, Gtk + +from .create_dialog import create_dialog +from .save_cover import save_cover +from .save_game import save_game + + +class Importer: + def __init__(self, parent_widget): + self.parent_widget = parent_widget + self.total_queue = 0 + self.queue = 0 + self.imported_no = 0 + self.blocker = False + + self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12) + import_statuspage = Adw.StatusPage( + title=_("Importing Games…"), + child=self.progressbar, + ) + self.import_dialog = Adw.Window( + content=import_statuspage, + modal=True, + default_width=350, + default_height=-1, + transient_for=parent_widget, + deletable=False, + ) + + self.import_dialog.present() + + def save_cover(self, game_id, cover_path): + save_cover(self.parent_widget, game_id, cover_path) + + def save_game(self, values=None): + if values: + self.imported_no += 1 + save_game(values) + self.parent_widget.update_games([values["game_id"]]) + + self.queue -= 1 + self.progressbar.set_fraction(1 - (self.queue / self.total_queue)) + + if self.queue == 0 and not self.blocker: + self.import_dialog.close() + + def response(_widget, response): + if response == "open_preferences": + self.parent_widget.get_application().on_preferences_action(None) + + if self.imported_no == 0: + create_dialog( + self.parent_widget, + _("No Games Found"), + _("No new games were found on your device."), + "open_preferences", + _("Preferences"), + ).connect("response", response) + + elif self.imported_no == 1: + create_dialog( + self.parent_widget, + _("Game Imported"), + _("Successfully imported 1 game."), + ) + elif self.imported_no > 1: + create_dialog( + self.parent_widget, + _("Games Imported"), + # The variable is the number of games + _(f"Successfully imported {self.imported_no} games."), + ) diff --git a/src/utils/save_cover.py b/src/utils/save_cover.py index 70599ea..9777aa5 100644 --- a/src/utils/save_cover.py +++ b/src/utils/save_cover.py @@ -22,7 +22,7 @@ import os from gi.repository import GdkPixbuf, Gio -def save_cover(game, parent_widget, file_path, pixbuf=None, game_id=None): +def save_cover(parent_widget, game_id, cover_path, pixbuf=None): covers_dir = os.path.join( os.getenv("XDG_DATA_HOME") or os.path.expanduser(os.path.join("~", ".local", "share")), @@ -30,18 +30,15 @@ def save_cover(game, parent_widget, file_path, pixbuf=None, game_id=None): "covers", ) - if game_id is None: - game_id = game["game_id"] + if not os.path.exists(covers_dir): + os.makedirs(covers_dir) if pixbuf is None: - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(file_path, 600, 900, False) + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(cover_path, 600, 900, False) def cover_callback(*_unused): pass - if not os.path.exists(covers_dir): - os.makedirs(covers_dir) - open_file = Gio.File.new_for_path(os.path.join(covers_dir, f"{game_id}.tiff")) parent_widget.pixbufs[game_id] = pixbuf pixbuf.save_to_streamv_async( diff --git a/src/utils/save_games.py b/src/utils/save_game.py similarity index 81% rename from src/utils/save_games.py rename to src/utils/save_game.py index 854c140..b93b28d 100644 --- a/src/utils/save_games.py +++ b/src/utils/save_game.py @@ -1,4 +1,4 @@ -# save_games.py +# save_game.py # # Copyright 2022-2023 kramo # @@ -21,7 +21,7 @@ import json import os -def save_games(games): +def save_game(game): games_dir = os.path.join( os.getenv("XDG_DATA_HOME") or os.path.expanduser(os.path.join("~", ".local", "share")), @@ -32,6 +32,5 @@ def save_games(games): if not os.path.exists(games_dir): os.makedirs(games_dir) - for game in games: - with open(os.path.join(games_dir, f"{game}.json"), "w") as open_file: - open_file.write(json.dumps(games[game], indent=4, sort_keys=True)) + with open(os.path.join(games_dir, f'{game["game_id"]}.json'), "w") as open_file: + open_file.write(json.dumps(game, indent=4, sort_keys=True)) diff --git a/src/utils/steam_parser.py b/src/utils/steam_parser.py index 02d6133..299d3ba 100644 --- a/src/utils/steam_parser.py +++ b/src/utils/steam_parser.py @@ -23,11 +23,7 @@ import re import time import urllib.request -from gi.repository import Adw, Gio, GLib, Gtk - -from .create_dialog import create_dialog -from .save_cover import save_cover -from .save_games import save_games +from gi.repository import Gio, GLib def update_values_from_data(content, values): @@ -44,7 +40,9 @@ def update_values_from_data(content, values): return values -def get_game(task, datatypes, current_time, parent_widget, appmanifest, steam_dir): +def get_game( + task, datatypes, current_time, parent_widget, appmanifest, steam_dir, importer +): values = {} with open(appmanifest, "r") as open_file: @@ -99,9 +97,8 @@ def get_game(task, datatypes, current_time, parent_widget, appmanifest, steam_di f'{values["appid"]}_library_600x900.jpg', ) ): - save_cover( - values, - parent_widget, + importer.save_cover( + values["game_id"], os.path.join( steam_dir, "appcache", @@ -114,93 +111,45 @@ def get_game(task, datatypes, current_time, parent_widget, appmanifest, steam_di return -def get_games_async(parent_widget, appmanifests, steam_dir, import_dialog, progressbar): +def get_games_async(parent_widget, appmanifests, steam_dir, importer): datatypes = ["appid", "name"] current_time = int(time.time()) - steam_games = {} - queue = 0 - # Wrap the function in another one as Gio.Task.run_in_thread does not allow for passing args def create_func(datatypes, current_time, parent_widget, appmanifest, steam_dir): def wrapper(task, *_unused): get_game( - task, datatypes, current_time, parent_widget, appmanifest, steam_dir + task, + datatypes, + current_time, + parent_widget, + appmanifest, + steam_dir, + importer, ) return wrapper - def update_games(_task, result, parent_widget): - nonlocal queue - nonlocal total_queue - nonlocal import_dialog - nonlocal progressbar - - queue -= 1 - progressbar.set_fraction(1 - (queue / total_queue)) - + def update_games(_task, result): try: final_values = result.propagate_value()[1] - steam_games[final_values["game_id"]] = final_values - except (TypeError, GLib.GError): - pass + # No need for an if statement as final_value would be None for games we don't want to save + importer.save_game(final_values) + except GLib.GError: # Handle the exception for the timeout + importer.save_game() - if queue == 0: - save_games(steam_games) - parent_widget.update_games(steam_games) - import_dialog.close() - games_no = len( - { - game_id: final_values - for game_id, final_values in steam_games.items() - if "blacklisted" not in final_values.keys() - } - ) - - def response(_widget, response): - if response == "open_preferences": - parent_widget.get_application().on_preferences_action(None) - - if games_no == 0: - create_dialog( - parent_widget, - _("No Games Found"), - _("No new games were found in the Steam library."), - "open_preferences", - _("Preferences"), - ).connect("response", response) - - elif games_no == 1: - create_dialog( - parent_widget, - _("Steam Games Imported"), - _("Successfully imported 1 game."), - ) - elif games_no > 1: - games_no = str(games_no) - create_dialog( - parent_widget, - _("Steam Games Imported"), - # The variable is the number of games - _(f"Successfully imported {games_no} games."), - ) - - total_queue = 0 for appmanifest in appmanifests: - queue += 1 - total_queue += 1 - cancellable = Gio.Cancellable.new() GLib.timeout_add_seconds(5, cancellable.cancel) - task = Gio.Task.new(None, cancellable, update_games, parent_widget) + task = Gio.Task.new(None, cancellable, update_games) task.set_return_on_cancel(True) task.run_in_thread( create_func(datatypes, current_time, parent_widget, appmanifest, steam_dir) ) -def steam_parser(parent_widget, action): +def steam_parser(parent_widget): schema = parent_widget.schema steam_dir = os.path.expanduser(schema.get_string("steam-location")) @@ -211,42 +160,14 @@ def steam_parser(parent_widget, action): schema.set_string( "steam-location", "~/.var/app/com.valvesoftware.Steam/data/Steam/" ) - action(None, None) elif os.path.exists(os.path.expanduser("~/.steam/steam/")): schema.set_string("steam-location", "~/.steam/steam/") - action(None, None) elif os.path.exists(os.path.join(os.getenv("programfiles(x86)"), "Steam")): schema.set_string( "steam-location", os.path.join(os.getenv("programfiles(x86)"), "Steam") ) - action(None, None) else: - filechooser = Gtk.FileDialog.new() - - def set_steam_dir(_source, result, _unused): - try: - schema.set_string( - "steam-location", - filechooser.select_folder_finish(result).get_path(), - ) - action(None, None) - except GLib.GError: - return - - def choose_folder(_widget): - filechooser.select_folder(parent_widget, None, set_steam_dir, None) - - def response(widget, response): - if response == "choose_folder": - choose_folder(widget) - - create_dialog( - parent_widget, - _("Couldn't Import Games"), - _("The Steam directory cannot be found."), - "choose_folder", - _("Set Steam Location"), - ).connect("response", response) + return if os.path.exists(os.path.join(steam_dir, "steamapps")): pass @@ -256,28 +177,9 @@ def steam_parser(parent_widget, action): schema.set_string("steam-location", os.path.join(steam_dir, "Steam")) else: steam_not_found() - return {} + return steam_dir = os.path.expanduser(schema.get_string("steam-location")) - - progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12) - import_statuspage = Adw.StatusPage( - title=_("Importing Games…"), - description=_("Talking to Steam"), - child=progressbar, - ) - - import_dialog = Adw.Window( - content=import_statuspage, - modal=True, - default_width=350, - default_height=-1, - transient_for=parent_widget, - deletable=False, - ) - - import_dialog.present() - appmanifests = [] steam_dirs = schema.get_strv("steam-extra-dirs") @@ -293,4 +195,8 @@ def steam_parser(parent_widget, action): if os.path.isfile(path) and "appmanifest" in open_file: appmanifests.append(path) - get_games_async(parent_widget, appmanifests, directory, import_dialog, progressbar) + importer = parent_widget.importer + importer.total_queue += len(appmanifests) + importer.queue += len(appmanifests) + + get_games_async(parent_widget, appmanifests, directory, importer) diff --git a/src/window.py b/src/window.py index 986f183..e69b168 100644 --- a/src/window.py +++ b/src/window.py @@ -24,7 +24,7 @@ from gi.repository import Adw, GdkPixbuf, Gio, GLib, Gtk from .game import game from .get_games import get_games -from .save_games import save_games +from .save_game import save_game @Gtk.Template(resource_path="/hu/kramo/Cartridges/gtk/window.ui") @@ -422,7 +422,7 @@ class CartridgesWindow(Adw.ApplicationWindow): return data = get_games([game_id])[game_id] data.pop("removed") - save_games({game_id: data}) + save_game(data) self.update_games([game_id]) self.toasts[game_id].dismiss() self.toasts.pop(game_id)