Unify games import
This commit is contained in:
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,18 @@
|
||||
<key name="high-quality-images" type="b">
|
||||
<default>false</default>
|
||||
</key>
|
||||
<key name="steam" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="steam-location" type="s">
|
||||
<default>"~/.steam/"</default>
|
||||
</key>
|
||||
<key name="steam-extra-dirs" type="as">
|
||||
<default>[]</default>
|
||||
</key>
|
||||
<key name="heroic" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="heroic-location" type="s">
|
||||
<default>"~/.var/app/com.heroicgameslauncher.hgl/config/heroic/"</default>
|
||||
</key>
|
||||
@@ -28,6 +34,9 @@
|
||||
<key name="heroic-import-sideload" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="bottles" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="bottles-location" type="s">
|
||||
<default>"~/.var/app/com.usebottles.bottles/data/bottles/"</default>
|
||||
</key>
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
48
src/main.py
48
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, ["<primary>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, ["<primary>n"])
|
||||
self.create_action("import", self.on_import_action, ["<primary>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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
72
src/utils/importer.py
Normal file
72
src/utils/importer.py
Normal file
@@ -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."),
|
||||
)
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user