From bc9192e83b7c2db32a394994c806b2c9b8049535 Mon Sep 17 00:00:00 2001 From: kramo <93832451+kra-mo@users.noreply.github.com> Date: Tue, 18 Apr 2023 19:07:49 +0200 Subject: [PATCH] Cleanups --- src/game.py | 119 +++++++++++++++++++++-------- src/game_cover.py | 7 +- src/main.py | 38 +++------ src/meson.build | 1 - src/preferences.py | 21 ++--- src/utils/create_details_window.py | 20 ++--- src/utils/get_games.py | 10 +-- src/utils/importer.py | 6 +- src/utils/save_cover.py | 8 +- src/utils/save_game.py | 28 ------- src/utils/steamgriddb.py | 6 +- src/window.py | 63 ++++----------- 12 files changed, 144 insertions(+), 183 deletions(-) delete mode 100644 src/utils/save_game.py diff --git a/src/game.py b/src/game.py index c6134a6..5bd547d 100644 --- a/src/game.py +++ b/src/game.py @@ -23,7 +23,6 @@ import os from gi.repository import Gio, GLib, Gtk from .game_cover import GameCover -from .save_game import save_game @Gtk.Template(resource_path="/hu/kramo/Cartridges/gtk/game.ui") @@ -45,36 +44,23 @@ class Game(Gtk.Box): loading = 0 filtered = False + added = None + executable = None + game_id = None + hidden = None + last_played = None + name = None + developer = None + removed = None + blacklisted = None + def __init__(self, win, data, **kwargs): super().__init__(**kwargs) self.win = win self.app = win.get_application() - self.added = data["added"] - self.executable = data["executable"] - self.game_id = data["game_id"] - self.hidden = data["hidden"] - self.last_played = data["last_played"] - self.name = data["name"] - self.developer = data["developer"] if "developer" in data else None - self.removed = "removed" in data - self.blacklisted = "blacklisted" in data - - if self.game_id in self.win.game_covers: - self.win.game_covers[self.game_id].add_picture(self.cover) - else: - game_cover = GameCover({self.cover}, self.get_cover_path()) - self.win.game_covers[self.game_id] = game_cover - - if self.hidden: - self.menu_button.set_menu_model(self.hidden_game_options) - else: - self.menu_button.set_menu_model(self.game_options) - - self.title.set_label(self.name) self.set_play_label() - self.overlay.set_measure_overlay(self.play_revealer, True) self.event_contoller_motion = Gtk.EventControllerMotion.new() @@ -85,12 +71,86 @@ class Game(Gtk.Box): self.cover_button.connect("clicked", self.cover_button_clicked) self.play_button.connect("clicked", self.play_button_clicked) + self.win.schema.connect("changed", self.schema_changed) + + self.update_values(data) + + def update(self): + if self.win.stack.get_visible_child() == self.win.details_view: + self.win.show_details_view(None, self.game_id) + + self.win.games[self.game_id] = self + + if self.get_parent(): + self.get_parent().get_parent().remove(self) + if self.get_parent(): + self.get_parent().set_child() + + if self.game_id in self.win.game_covers: + self.win.game_covers[self.game_id].add_picture(self.cover) + else: + game_cover = GameCover({self.cover}, self.get_cover_path()) + self.win.game_covers[self.game_id] = game_cover + + self.menu_button.set_menu_model( + self.hidden_game_options if self.hidden else self.game_options + ) + + self.title.set_label(self.name) + self.menu_button.get_popover().connect("notify::visible", self.hide_play) self.menu_button.get_popover().connect( "notify::visible", self.win.set_active_game, self.game_id ) - self.win.schema.connect("changed", self.schema_changed) + if not self.removed or self.blacklisted: + if self.hidden: + self.win.hidden_library.append(self) + else: + self.win.library.append(self) + self.get_parent().set_focusable(False) + + def update_values(self, data): + for key, value in data.items(): + setattr(self, key, value) + + self.save() + + def save(self): + self.win.games_dir.mkdir(parents=True, exist_ok=True) + + attrs = ( + "added", + "executable", + "game_id", + "hidden", + "last_played", + "name", + "developer", + "removed", + "blacklisted", + ) + + json.dump( + {attr: getattr(self, attr) for attr in attrs if attr}, + (self.win.games_dir / f"{self.game_id}.json").open("w"), + indent=4, + sort_keys=True, + ) + + self.update() + + self.win.library_bin.set_child( + self.win.scrolledwindow + if any(not game.hidden for game in self.win.games.values()) + else self.win.notice_empty + ) + + self.win.hidden_library_bin.set_child( + self.win.hidden_scrolledwindow + if any(game.hidden for game in self.win.games.values()) + else self.win.hidden_notice_empty + ) def launch(self): # Generate launch arguments, either list (no shell) or a string (for shell). @@ -105,11 +165,8 @@ class Game(Gtk.Box): self.app.quit() def toggle_hidden(self): - data = json.load((self.win.games_dir / f"{self.game_id}.json").open()) - - data["hidden"] = not data["hidden"] - - save_game(self.win, data) + self.hidden = not self.hidden + self.save() def get_cover_path(self): cover_path = self.win.covers_dir / f"{self.game_id}.gif" @@ -120,6 +177,8 @@ class Game(Gtk.Box): if cover_path.is_file(): return cover_path + return None + def show_play(self, _widget, *_unused): self.play_revealer.set_reveal_child(True) self.title_revealer.set_reveal_child(False) diff --git a/src/game_cover.py b/src/game_cover.py index afa650f..4921d44 100644 --- a/src/game_cover.py +++ b/src/game_cover.py @@ -32,7 +32,7 @@ class GameCover: def __init__(self, pictures, path=None): self.pictures = pictures - self.new_pixbuf(path) + self.new_cover(path) # Wrap the function in another one as Gio.Task.run_in_thread does not allow for passing args def create_func(self, path): @@ -44,7 +44,7 @@ class GameCover: return wrapper - def new_pixbuf(self, path=None): + def new_cover(self, path=None): self.animation = None self.pixbuf = None self.path = path @@ -62,9 +62,6 @@ class GameCover: def get_pixbuf(self): return self.animation.get_static_image() if self.animation else self.pixbuf - def get_animation(self): - return self.path if self.animation else None - def add_picture(self, picture): self.pictures.add(picture) if not self.animation: diff --git a/src/main.py b/src/main.py index d4df085..730dd56 100644 --- a/src/main.py +++ b/src/main.py @@ -30,13 +30,11 @@ from gi.repository import Adw, Gio, GLib, Gtk from .bottles_importer import bottles_importer from .create_details_window import create_details_window -from .get_games import get_games from .heroic_importer import heroic_importer from .importer import Importer from .itch_importer import itch_importer from .lutris_importer import lutris_importer from .preferences import PreferencesWindow -from .save_game import save_game from .steam_importer import steam_importer from .window import CartridgesWindow @@ -150,24 +148,13 @@ class CartridgesApplication(Adw.Application): def on_launch_game_action(self, _widget, _callback=None): # Launch the game and update the last played value + game = self.win.games[self.win.active_game_id] - game_id = self.win.active_game_id - last_played = int(time.time()) + game.last_played = int(time.time()) + game.save() + game.launch() - data = get_games(self.win, {game_id})[game_id] - data["last_played"] = last_played - save_game(self.win, data) - - self.win.games[game_id].launch() - - # Update state - self.win.games[game_id].last_played = last_played - self.win.library.invalidate_sort() - self.win.hidden_library.invalidate_sort() - if self.win.stack.get_visible_child() == self.win.details_view: - self.win.show_details_view(None, game_id) - - title = self.win.games[game_id].name + title = game.name # The variable is the title of the game toast = Adw.Toast.new(_("{} launched").format(title)) toast.set_priority(Adw.ToastPriority.HIGH) @@ -180,7 +167,6 @@ class CartridgesApplication(Adw.Application): if self.win.stack.get_visible_child() == self.win.details_view: self.win.on_go_back_action(None, None) self.win.games[game_id].toggle_hidden() - self.win.update_games({game_id}) if not toast: return @@ -235,23 +221,21 @@ class CartridgesApplication(Adw.Application): 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 + game = self.win.games[self.win.active_game_id] - data = get_games(self.win, {game_id})[game_id] - data["removed"] = True - save_game(self.win, data) + game.removed = True + game.save() - self.win.update_games({game_id}) if self.win.stack.get_visible_child() == self.win.details_view: self.win.on_go_back_action(None, None) - title = self.win.games[game_id].name + title = game.name # The variable is the title of the game toast = Adw.Toast.new(_("{} removed").format(title)) toast.set_button_label(_("Undo")) - toast.connect("button-clicked", self.win.on_undo_action, game_id, "remove") + toast.connect("button-clicked", self.win.on_undo_action, game.game_id, "remove") toast.set_priority(Adw.ToastPriority.HIGH) - self.win.toasts[(game_id, "remove")] = toast + self.win.toasts[(game.game_id, "remove")] = toast self.win.toast_overlay.add_toast(toast) def on_remove_game_details_view_action(self, _widget, _callback=None): diff --git a/src/meson.build b/src/meson.build index fda2582..8566f36 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,7 +31,6 @@ cartridges_sources = [ 'utils/importer.py', 'utils/steamgriddb.py', 'utils/get_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 e258ce8..2363225 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -24,11 +24,9 @@ from gi.repository import Adw, Gio, GLib, Gtk from .bottles_importer import bottles_installed from .create_dialog import create_dialog -from .get_games import get_games from .heroic_importer import heroic_installed from .itch_importer import itch_installed from .lutris_importer import lutris_cache_exists, lutris_installed -from .save_game import save_game from .steam_importer import steam_installed @@ -335,23 +333,20 @@ class PreferencesWindow(Adw.PreferencesWindow): def undo_remove_all(self, _widget, _unused): for game_id in self.removed_games: - data = get_games(self.win, {game_id})[game_id] - if "removed" in data: - data.pop("removed") - save_game(self.win, data) + self.win.games[game_id].removed = False + self.win.games[game_id].save() - self.win.update_games(self.removed_games) self.removed_games = set() self.toast.dismiss() def remove_all_games(self, _widget): - for game in get_games(self.win).values(): - if "removed" not in game: - self.removed_games.add(game["game_id"]) - game["removed"] = True - save_game(self.win, game) + for game in self.win.games: + if not game.removed: + self.removed_games.add(game.game_id) + + game.removed = True + game.save() - self.win.update_games(self.win.games) if self.win.stack.get_visible_child() == self.win.details_view: self.win.on_go_back_action(None, None) diff --git a/src/utils/create_details_window.py b/src/utils/create_details_window.py index 55e9c64..2e961ff 100644 --- a/src/utils/create_details_window.py +++ b/src/utils/create_details_window.py @@ -26,9 +26,9 @@ from gi.repository import Adw, Gio, GLib, GObject, Gtk from PIL import Image from .create_dialog import create_dialog +from .game import Game from .game_cover import GameCover from .save_cover import resize_cover, save_cover -from .save_game import save_game from .steamgriddb import SGDBSave @@ -62,7 +62,7 @@ def create_details_window(win, game_id=None): nonlocal game_cover nonlocal cover_changed - game_cover.new_pixbuf() + game_cover.new_cover() cover_button_delete_revealer.set_reveal_child(False) cover_changed = True @@ -89,7 +89,7 @@ def create_details_window(win, game_id=None): ) apply_button = Gtk.Button.new_with_label(_("Apply")) - game_cover.new_pixbuf(win.games[game_id].get_cover_path()) + game_cover.new_cover(win.games[game_id].get_cover_path()) if game_cover.get_pixbuf(): cover_button_delete_revealer.set_reveal_child(True) else: @@ -233,7 +233,7 @@ def create_details_window(win, game_id=None): cover_button_delete_revealer.set_reveal_child(True) cover_changed = True - game_cover.new_pixbuf(resize_cover(win, path)) + game_cover.new_cover(resize_cover(win, path)) def close_window(_widget, _callback=None): window.close() @@ -323,16 +323,10 @@ def create_details_window(win, game_id=None): game_cover.path, ) - path = win.games_dir / f"{game_id}.json" - - if path.exists(): - data = json.load(path.open()) - data.update(values) - save_game(win, data) + if game_id in win.games: + win.games[game_id].update_values(values) else: - save_game(win, values) - - win.update_games({game_id}) + Game(win, values).save() if not game_cover.get_pixbuf(): SGDBSave(win, {(game_id, values["name"])}) diff --git a/src/utils/get_games.py b/src/utils/get_games.py index 579b3a4..c20b35a 100644 --- a/src/utils/get_games.py +++ b/src/utils/get_games.py @@ -20,16 +20,10 @@ import json -def get_games(win, game_ids=None): +def get_games(win): games = {} - game_files = ( - {win.games_dir / f"{game_id}.json" for game_id in game_ids} - if game_ids - else win.games_dir.iterdir() - ) - - for open_file in game_files: + for open_file in win.games_dir.iterdir(): if open_file.exists(): data = json.load(open_file.open()) games[data["game_id"]] = data diff --git a/src/utils/importer.py b/src/utils/importer.py index b2b0d80..030d200 100644 --- a/src/utils/importer.py +++ b/src/utils/importer.py @@ -22,8 +22,8 @@ from pathlib import Path from gi.repository import Adw, Gtk from .create_dialog import create_dialog +from .game import Game from .save_cover import resize_cover, save_cover -from .save_game import save_game from .steamgriddb import SGDBSave @@ -56,7 +56,7 @@ class Importer: def save_game(self, values=None, cover_path=None): if values: - save_game(self.win, values) + Game(self.win, values).save() if cover_path: save_cover( @@ -66,7 +66,7 @@ class Importer: self.games.add((values["game_id"], values["name"])) self.games_no += 1 - if "blacklisted" in values: + if values.get("blacklisted"): self.games_no -= 1 self.queue -= 1 diff --git a/src/utils/save_cover.py b/src/utils/save_cover.py index e2636b1..f68e3a0 100644 --- a/src/utils/save_cover.py +++ b/src/utils/save_cover.py @@ -27,7 +27,7 @@ from PIL import Image, ImageSequence def resize_cover(win, cover_path=None, pixbuf=None): if not cover_path and not pixbuf: - return + return None if pixbuf: cover_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()) @@ -65,6 +65,9 @@ def resize_cover(win, cover_path=None, pixbuf=None): def save_cover(win, game_id, cover_path): + if not cover_path: + return + win.covers_dir.mkdir(parents=True, exist_ok=True) animated_path = win.covers_dir / f"{game_id}.gif" @@ -80,7 +83,6 @@ def save_cover(win, game_id, cover_path): ) if game_id in win.game_covers: - win.game_covers[game_id].new_pixbuf( + win.game_covers[game_id].new_cover( animated_path if cover_path.suffix == ".gif" else static_path ) - return diff --git a/src/utils/save_game.py b/src/utils/save_game.py deleted file mode 100644 index 3bb5be8..0000000 --- a/src/utils/save_game.py +++ /dev/null @@ -1,28 +0,0 @@ -# save_game.py -# -# Copyright 2022-2023 kramo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# SPDX-License-Identifier: GPL-3.0-or-later - -import json - - -def save_game(win, game): - win.games_dir.mkdir(parents=True, exist_ok=True) - - (win.games_dir / f'{game["game_id"]}.json').write_text( - json.dumps(game, indent=4, sort_keys=True), "utf-8" - ) diff --git a/src/utils/steamgriddb.py b/src/utils/steamgriddb.py index f232889..ac808be 100644 --- a/src/utils/steamgriddb.py +++ b/src/utils/steamgriddb.py @@ -111,8 +111,10 @@ class SGDBSave: _("Preferences"), ).connect("response", self.response) - game_id = result.propagate_value()[1] - self.win.update_games({game_id}) + if not self.importer: + game = self.win.games[result.propagate_value()[1]] + game.set_loading(-1) + game.update() def response(self, _widget, response): if response == "open_preferences": diff --git a/src/window.py b/src/window.py index 6197adc..a793138 100644 --- a/src/window.py +++ b/src/window.py @@ -19,14 +19,13 @@ import datetime import os -from struct import unpack_from from pathlib import Path +from struct import unpack_from from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk from .game import Game from .get_games import get_games -from .save_game import save_game @Gtk.Template(resource_path="/hu/kramo/Cartridges/gtk/window.ui") @@ -107,9 +106,8 @@ class CartridgesWindow(Adw.ApplicationWindow): ) self.image_size = (200 * scale_factor, 300 * scale_factor) - games = get_games(self) - for game_id in games: - if "removed" in games[game_id]: + for game_id, game in get_games(self).items(): + if game.get("removed"): (self.games_dir / f"{game_id}.json").unlink(missing_ok=True) (self.covers_dir / f"{game_id}.tiff").unlink(missing_ok=True) (self.covers_dir / f"{game_id}.gif").unlink(missing_ok=True) @@ -123,7 +121,8 @@ class CartridgesWindow(Adw.ApplicationWindow): self.library.set_sort_func(self.sort_func) self.hidden_library.set_sort_func(self.sort_func) - self.update_games(get_games(self)) + for game in get_games(self).values(): + Game(self, game).update() # Connect signals self.search_entry.connect("search-changed", self.search_changed, False) @@ -140,41 +139,6 @@ class CartridgesWindow(Adw.ApplicationWindow): "notify::high-contrast", self.set_details_view_opacity ) - def update_games(self, games): - for game_id in games: - if game_id in self.games and self.games[game_id].get_parent(): - self.games[game_id].get_parent().get_parent().remove( - self.games[game_id] - ) - - entry = Game(self, get_games(self, {game_id})[game_id]) - self.games[game_id] = entry - - if entry.removed or entry.blacklisted: - continue - - if entry.hidden: - self.hidden_library.append(entry) - else: - self.library.append(entry) - - entry.get_parent().set_focusable(False) - - self.library_bin.set_child( - self.scrolledwindow - if any(not game.hidden for game in self.games.values()) - else self.notice_empty - ) - - self.hidden_library_bin.set_child( - self.hidden_scrolledwindow - if any(game.hidden for game in self.games.values()) - else self.hidden_notice_empty - ) - - if self.stack.get_visible_child() == self.details_view: - self.show_details_view(None, self.active_game_id) - def search_changed(self, _widget, hidden): # Refresh search filter on keystroke in search box if hidden: @@ -190,14 +154,15 @@ class CartridgesWindow(Adw.ApplicationWindow): .lower() ) + game = child.get_child() + filtered = text != "" and not ( - text in child.get_first_child().name.lower() - or text in child.get_first_child().developer.lower() - if child.get_first_child().developer + text in game.name.lower() or text in game.developer.lower() + if game.developer else None ) - child.get_first_child().filtered = filtered + game.filtered = filtered (self.hidden_library_bin if hidden else self.library_bin).set_child( (self.hidden_scrolledwindow if hidden else self.scrolledwindow) @@ -327,7 +292,7 @@ class CartridgesWindow(Adw.ApplicationWindow): ) def sort_func(self, child1, child2): - games = (child1.get_first_child(), child2.get_first_child()) + games = (child1.get_child(), child2.get_child()) var, order = "name", True if self.sort_state in ("newest", "oldest"): @@ -437,10 +402,8 @@ class CartridgesWindow(Adw.ApplicationWindow): ) elif undo == "remove": - data = get_games(self, {game_id})[game_id] - data.pop("removed", None) - save_game(self, data) - self.update_games({game_id}) + self.games[game_id].removed = False + self.games[game_id].save() self.toasts[(game_id, undo)].dismiss() self.toasts.pop((game_id, undo))