Build a cache with game paths

This commit is contained in:
Mathieu Comandon 2023-01-30 19:25:16 -08:00
parent cbec00994f
commit 021a687fea
9 changed files with 96 additions and 25 deletions

View file

@ -12,7 +12,7 @@ from lutris.config import duplicate_game_config
from lutris.database.games import add_game, get_game_by_field, get_unusued_game_name
from lutris.game import Game
from lutris.gui import dialogs
from lutris.gui.config.add_game import AddGameDialog
from lutris.gui.config.add_game_dialog import AddGameDialog
from lutris.gui.config.edit_game import EditGameConfigDialog
from lutris.gui.dialogs import QuestionDialog
from lutris.gui.dialogs.log import LogWindow

View file

@ -5,7 +5,7 @@ from gi.repository import Gio, GLib, Gtk
from lutris import api
from lutris.exceptions import watch_errors
from lutris.gui.config.add_game import AddGameDialog
from lutris.gui.config.add_game_dialog import AddGameDialog
from lutris.gui.dialogs import ErrorDialog
from lutris.gui.dialogs.game_import import ImportGameDialog
from lutris.gui.widgets.common import FileChooserEntry

View file

@ -2,18 +2,19 @@ from collections import OrderedDict
from copy import deepcopy
from gettext import gettext as _
from gi.repository import Gtk, GLib
from gi.repository import GLib, Gtk
from lutris.config import write_game_config
from lutris.database.games import add_game, get_games
from lutris.database.games import add_game
from lutris.game import Game
from lutris.gui.dialogs import ModalDialog
from lutris.scanners.default_installers import DEFAULT_INSTALLERS
from lutris.scanners.lutris import add_to_path_cache, get_path_cache
from lutris.scanners.tosec import clean_rom_name, guess_platform, search_tosec_by_md5
from lutris.services.lutris import download_lutris_media
from lutris.util.jobs import AsyncCall
from lutris.util.log import logger
from lutris.util.strings import slugify, gtk_safe
from lutris.util.strings import gtk_safe, slugify
from lutris.util.system import get_md5_hash, get_md5_in_zip
@ -87,17 +88,7 @@ class ImportGameDialog(ModalDialog):
logger.debug('Game not launched')
def search_checksums(self):
all_games = get_games()
def find_game(filepath):
for db_game in all_games:
g = Game(db_game["id"])
if not g.config:
continue
for _key, value in g.config.game_config.items():
if value == filepath:
logger.debug("Found %s", g)
return g
game_path_cache = get_path_cache()
def show_progress(filepath, message):
# It's not safe to directly update labels from a worker thread, so
@ -108,11 +99,13 @@ class ImportGameDialog(ModalDialog):
for filename in self.files:
try:
show_progress(filename, _("Looking for installed game..."))
game = find_game(filename)
if game:
# Found a game to launch instead of installing, but we can't safely
# do this on this thread.
result = [{"name": game.name, "game": game, "roms": []}]
if filename in game_path_cache.values():
for game_id in game_path_cache:
if game_path_cache[game_id] == filename:
# Found a game to launch instead of installing, but we can't safely
# do this on this thread.
game = Game(game_id)
result = [{"name": game.name, "game": game, "roms": []}]
else:
show_progress(filename, _("Calculating checksum..."))
if filename.lower().endswith(".zip"):
@ -206,4 +199,5 @@ class ImportGameDialog(ModalDialog):
configpath=configpath
)
download_lutris_media(slug)
add_to_path_cache(Game(game_id))
return game_id

View file

@ -5,10 +5,11 @@ from gi.repository import Gtk, Pango
from lutris.database.games import get_games
from lutris.game import Game
from lutris.gui.dialogs import ModalDialog, QuestionDialog
from lutris.scanners.lutris import remove_from_path_cache
from lutris.util.jobs import AsyncCall
from lutris.util.log import logger
from lutris.util.strings import gtk_safe, human_size
from lutris.util.system import get_disk_size, is_removeable, reverse_expanduser, path_exists
from lutris.util.system import get_disk_size, is_removeable, path_exists, reverse_expanduser
class UninstallGameDialog(ModalDialog):
@ -99,6 +100,7 @@ class UninstallGameDialog(ModalDialog):
self.folder_label.set_markup(_("Uninstalling game and deleting files..."))
else:
self.folder_label.set_markup(_("Uninstalling game..."))
remove_from_path_cache(self.game)
self.game.remove(self.delete_files)
self.destroy()

View file

@ -27,6 +27,7 @@ from lutris.gui.widgets.game_bar import GameBar
from lutris.gui.widgets.gi_composites import GtkTemplate
from lutris.gui.widgets.sidebar import LutrisSidebar
from lutris.gui.widgets.utils import load_icon_theme, open_uri
from lutris.scanners.lutris import remove_from_path_cache
# pylint: disable=no-member
from lutris.services.base import BaseService
from lutris.services.lutris import LutrisService
@ -132,7 +133,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
GObject.add_emission_hook(BaseService, "service-games-loaded", self.on_service_games_updated)
GObject.add_emission_hook(Game, "game-updated", self.on_game_updated)
GObject.add_emission_hook(Game, "game-stopped", self.on_game_stopped)
GObject.add_emission_hook(Game, "game-removed", self.on_game_collection_changed)
GObject.add_emission_hook(Game, "game-removed", self.on_game_removed)
GObject.add_emission_hook(Game, "game-unhandled_error", self.on_game_unhandled_error)
def _init_actions(self):
@ -874,9 +875,11 @@ class LutrisWindow(Gtk.ApplicationWindow,
self.game_store.remove_game(game.id)
return True
def on_game_collection_changed(self, _sender):
def on_game_removed(self, game):
"""Simple method used to refresh the view"""
remove_from_path_cache(game)
self.emit("view-updated")
return True
@watch_errors()

View file

@ -1,13 +1,19 @@
import json
import os
import time
from lutris import settings
from lutris.api import get_api_games, get_game_installers
from lutris.database.games import get_games
from lutris.game import Game
from lutris.installer.errors import MissingGameDependency
from lutris.installer.interpreter import ScriptInterpreter
from lutris.services.lutris import download_lutris_media
from lutris.util.log import logger
from lutris.util.strings import slugify
GAME_PATH_CACHE_PATH = os.path.join(settings.CACHE_DIR, "game-paths.json")
def get_game_slugs_and_folders(dirname):
"""Scan a directory for games previously installed with lutris"""
@ -95,3 +101,67 @@ def scan_directory(dirname):
installed_map = {slug: folder for slug, folder in slugs_map.items() if slug in slugs_installed}
missing_map = {slug: folder for slug, folder in slugs_map.items() if slug not in slugs_installed}
return installed_map, missing_map
def get_path_from_config(game):
"""Return the path of the main entry point for a game"""
if not game.config:
logger.warning("Game %s has no configuration", game)
return ""
for key in ["exe", "main_file", "iso", "rom"]:
if key in game.config.game_config:
path = game.config.game_config[key]
if not path.startswith("/"):
path = os.path.join(game.directory, path)
return path
logger.warning("No path found in %s", game.config)
return ""
def get_game_paths():
game_paths = {}
all_games = get_games(filters={'installed': 1})
for db_game in all_games:
game = Game(db_game["id"])
path = get_path_from_config(game)
if not path:
logger.warning("Game %s has no path", game)
continue
game_paths[db_game["id"]] = path
return game_paths
def build_path_cache():
"""Generate a new cache path"""
start_time = time.time()
with open(GAME_PATH_CACHE_PATH, "w", encoding="utf-8") as cache_file:
game_paths = get_game_paths()
json.dump(game_paths, cache_file, indent=2)
end_time = time.time()
logger.debug("Game path cache built in %0.2f seconds", end_time - start_time)
def add_to_path_cache(game):
path = get_path_from_config(game)
if not path:
logger.warning("No path for %s", game)
return
current_cache = get_path_cache()
current_cache[game.id] = path
with open(GAME_PATH_CACHE_PATH, "w", encoding="utf-8") as cache_file:
json.dump(current_cache, cache_file, indent=2)
def remove_from_path_cache(game):
current_cache = get_path_cache()
if game.id not in current_cache:
return
del current_cache[game.id]
with open(GAME_PATH_CACHE_PATH, "w", encoding="utf-8") as cache_file:
json.dump(current_cache, cache_file, indent=2)
def get_path_cache():
"""Return the contents of the path cache file"""
with open(GAME_PATH_CACHE_PATH, encoding="utf-8") as cache_file:
return json.load(cache_file)

View file

@ -17,6 +17,7 @@ from lutris.game import Game
from lutris.gui.dialogs import DontShowAgainDialog
from lutris.runners.json import load_json_runners
from lutris.runtime import RuntimeUpdater
from lutris.scanners.lutris import build_path_cache
from lutris.services import DEFAULT_SERVICES
from lutris.services.lutris import sync_media
from lutris.util.graphics import drivers, vkquery
@ -202,6 +203,7 @@ def init_lutris():
if not settings.read_setting(service, section="services"):
settings.write_setting(service, True, section="services")
cleanup_games()
build_path_cache()
class StartupRuntimeUpdater(RuntimeUpdater):

View file

@ -2,7 +2,7 @@ from unittest import TestCase
from lutris import runners
from lutris.gui.application import Application
from lutris.gui.config.add_game import AddGameDialog
from lutris.gui.config.add_game_dialog import AddGameDialog
# from lutris import settings
from lutris.gui.config.common import GameDialogCommon
from lutris.gui.views.store import sort_func