diff --git a/lutris/api.py b/lutris/api.py
index f813e17e6..c0b57b295 100644
--- a/lutris/api.py
+++ b/lutris/api.py
@@ -5,9 +5,7 @@ import urllib2
import socket
from lutris import settings
-from lutris import pga
from lutris.util import http
-from lutris.util import resources
from lutris.util.log import logger
@@ -73,114 +71,3 @@ def get_games(slugs):
game_set = ';'.join(slugs)
url = settings.SITE_URL + "api/v1/game/set/%s/" % game_set
return http.download_json(url, params="?format=json")['objects']
-
-
-def sync(caller=None):
- """Synchronize from remote to local library.
-
- :param caller: The LutrisWindow object
- :return: The synchronized games (slugs)
- :rtype: set of strings
- """
- logger.debug("Syncing game library")
- # Get local library
- local_library = pga.get_games()
- local_slugs = set([game['slug'] for game in local_library])
- logger.debug("%d games in local library", len(local_slugs))
- # Get remote library
- remote_library = get_library()
- remote_slugs = set([game['slug'] for game in remote_library])
- logger.debug("%d games in remote library (inc. unpublished)",
- len(remote_slugs))
-
- not_in_local = remote_slugs.difference(local_slugs)
-
- added = sync_missing_games(not_in_local, remote_library, caller)
- updated = sync_game_details(remote_library, caller)
- return added.update(updated)
-
-
-def sync_missing_games(not_in_local, remote_library, caller=None):
- """Get missing games in local library from remote library.
-
- :param caller: The LutrisWindow object
- :return: The slugs of the added games
- :rtype: set
- """
- if not not_in_local:
- return set()
-
- for game in remote_library:
- slug = game['slug']
- # Sync
- if slug in not_in_local:
- logger.debug("Adding to local library: %s", slug)
- pga.add_game(
- game['name'], slug=slug, year=game['year'],
- updated=game['updated'], steamid=game['steamid']
- )
- if caller:
- caller.add_game_to_view(slug)
- else:
- not_in_local.discard(slug)
- logger.debug("%d games added", len(not_in_local))
- return not_in_local
-
-
-def sync_game_details(remote_library, caller):
- """Update local game details,
-
- :param caller: The LutrisWindow object
- :return: The slugs of the updated games.
- :rtype: set
- """
- if not remote_library:
- return set()
-
- updated = set()
-
- # Get remote games (TODO: use this when switched API to DRF)
- # remote_games = get_games(sorted(local_slugs))
- # if not remote_games:
- # return set()
-
- for game in remote_library:
- slug = game['slug']
- sync = False
- sync_icons = True
- local_game = pga.get_game_by_slug(slug)
- if not local_game:
- continue
-
- # Sync updated
- if game['updated'] > local_game['updated']:
- sync = True
- # Sync new fields
- else:
- for key, value in local_game.iteritems():
- if value or not key in game:
- continue
- if game[key]:
- sync = True
- sync_icons = False
- if not sync:
- continue
-
- logger.debug("Syncing details for %s" % slug)
- pga.add_or_update(
- local_game['name'], local_game['runner'], slug,
- year=game['year'], updated=game['updated'],
- steamid=game['steamid']
- )
- caller.view.update_row(game)
-
- # Sync icons (TODO: Only update if icon actually updated)
- if sync_icons:
- resources.download_icon(slug, 'banner', overwrite=True,
- callback=caller.on_image_downloaded)
- resources.download_icon(slug, 'icon', overwrite=True,
- callback=caller.on_image_downloaded)
- updated.add(slug)
-
- logger.debug("%d games updated", len(updated))
- return updated
diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py
index 80934ef7d..449358c3d 100644
--- a/lutris/gui/lutriswindow.py
+++ b/lutris/gui/lutriswindow.py
@@ -155,7 +155,7 @@ class LutrisWindow(object):
self.toggle_connection(False)
sync = Sync()
async_call(
- sync.sync_steam,
+ sync.sync_steam_local,
lambda r, e: async_call(self.sync_icons, None),
caller=self
)
@@ -166,19 +166,6 @@ class LutrisWindow(object):
if self.view.__class__.__name__ == "GameGridView" \
else 'list'
- def switch_splash_screen(self):
- if self.view.n_games == 0:
- self.splash_box.show()
- self.games_scrollwindow.hide()
- else:
- self.splash_box.hide()
- self.games_scrollwindow.show()
-
- def sync_icons(self):
- game_list = pga.get_games()
- resources.fetch_icons([game_info['slug'] for game_info in game_list],
- callback=self.on_image_downloaded)
-
def connect_signals(self):
"""Connect signals from the view with the main window.
This must be called each time the view is rebuilt.
@@ -209,6 +196,59 @@ class LutrisWindow(object):
def get_size(self, widget, _):
self.window_size = widget.get_size()
+ def switch_splash_screen(self):
+ if self.view.n_games == 0:
+ self.splash_box.show()
+ self.games_scrollwindow.hide()
+ else:
+ self.splash_box.hide()
+ self.games_scrollwindow.show()
+
+ def switch_view(self, view_type):
+ """Switch between grid view and list view."""
+ logger.debug("Switching view")
+ self.icon_type = self.get_icon_type(view_type)
+ self.view.destroy()
+ self.view = load_view(
+ view_type,
+ get_game_list(filter_installed=self.filter_installed),
+ filter_text=self.search_entry.get_text(),
+ icon_type=self.icon_type
+ )
+ self.view.contextual_menu = self.menu
+ self.connect_signals()
+ self.games_scrollwindow.add(self.view)
+ self.view.show_all()
+ self.view.check_resize()
+ # Note: set_active(True *or* False) apparently makes ALL the menuitems
+ # in the group send the activate signal...
+ if self.icon_type == 'banner_small':
+ self.banner_small_menuitem.set_active(True)
+ if self.icon_type == 'icon':
+ self.icon_menuitem.set_active(True)
+ if self.icon_type == 'banner':
+ self.banner_menuitem.set_active(True)
+
+ def sync_library(self):
+ def set_library_synced(result, error):
+ self.set_status("Library synced")
+ self.switch_splash_screen()
+ self.set_status("Syncing library")
+ sync = Sync()
+ async_call(
+ sync.sync_all,
+ lambda r, e: async_call(self.sync_icons, set_library_synced),
+ caller=self
+ )
+
+ def sync_icons(self):
+ game_list = pga.get_games()
+ resources.fetch_icons([game_info['slug'] for game_info in game_list],
+ callback=self.on_image_downloaded)
+
+ def set_status(self, text):
+ self.status_label.set_text(text)
+
def refresh_status(self):
"""Refresh status bar."""
if self.running_game:
@@ -236,12 +276,28 @@ class LutrisWindow(object):
"""Open the about dialog."""
dialogs.AboutDialog()
+ def reset(self, *args):
+ """Reset the desktop to it's initial state."""
+ if self.running_game:
+ self.running_game.quit_game()
+ self.status_label.set_text("Stopped %s" % self.running_game.name)
+ self.running_game = None
+ self.stop_button.set_sensitive(False)
+
# Callbacks
def on_connect(self, *args):
"""Callback when a user connects to his account."""
login_dialog = dialogs.ClientLoginDialog()
login_dialog.connect('connected', self.on_connect_success)
+ def on_connect_success(self, dialog, credentials):
+ if isinstance(credentials, str):
+ username = credentials
+ else:
+ username = credentials["username"]
+ self.toggle_connection(True, username)
+ self.sync_library()
+
def on_disconnect(self, *args):
api.disconnect()
self.toggle_connection(False)
@@ -262,14 +318,6 @@ class LutrisWindow(object):
logger.info(connection_status)
connection_label.set_text(connection_status)
- def on_connect_success(self, dialog, credentials):
- if isinstance(credentials, str):
- username = credentials
- else:
- username = credentials["username"]
- self.toggle_connection(True, username)
- self.sync_library()
-
def on_destroy(self, *args):
"""Signal for window close."""
view_type = 'grid' if 'GridView' in str(type(self.view)) else 'list'
@@ -280,9 +328,6 @@ class LutrisWindow(object):
Gtk.main_quit(*args)
logger.debug("Quitting lutris")
- def on_game_installed(self, view, slug):
- view.set_installed(Game(slug))
-
def on_runners_activate(self, _widget, _data=None):
"""Callback when manage runners is activated."""
RunnersDialog()
@@ -302,10 +347,6 @@ class LutrisWindow(object):
def on_pga_menuitem_activate(self, _widget, _data=None):
dialogs.PgaSourceDialog()
- def on_image_downloaded(self, game_slug):
- is_installed = Game(game_slug).is_installed
- self.view.update_image(game_slug, is_installed)
-
def on_search_entry_changed(self, widget):
self.view.emit('filter-updated', widget.get_text())
@@ -325,29 +366,6 @@ class LutrisWindow(object):
else:
InstallerDialog(game_slug, self)
- def set_status(self, text):
- self.status_label.set_text(text)
-
- def sync_library(self):
- def set_library_synced(result, error):
- self.set_status("Library synced")
- self.switch_splash_screen()
- self.set_status("Syncing library")
- sync = Sync()
- async_call(
- sync.sync_all,
- lambda r, e: async_call(self.sync_icons, set_library_synced),
- caller=self
- )
-
- def reset(self, *args):
- """Reset the desktop to it's initial state."""
- if self.running_game:
- self.running_game.quit_game()
- self.status_label.set_text("Stopped %s" % self.running_game.name)
- self.running_game = None
- self.stop_button.set_sensitive(False)
-
def game_selection_changed(self, _widget):
# Emulate double click to workaround GTK bug #484640
# https://bugzilla.gnome.org/show_bug.cgi?id=484640
@@ -363,6 +381,27 @@ class LutrisWindow(object):
self.play_button.set_sensitive(sensitive)
self.delete_button.set_sensitive(sensitive)
+ def on_game_installed(self, view, slug):
+ view.set_installed(Game(slug))
+
+ def on_image_downloaded(self, game_slug):
+ is_installed = Game(game_slug).is_installed
+ self.view.update_image(game_slug, is_installed)
+
+ def add_manually(self, *args):
+ game = Game(self.view.selected_game)
+ add_game_dialog = AddGameDialog(self, game)
+ add_game_dialog.run()
+ if add_game_dialog.installed:
+ self.view.set_installed(game)
+
+ def add_game(self, _widget, _data=None):
+ """Add a new game."""
+ add_game_dialog = AddGameDialog(self)
+ add_game_dialog.run()
+ if add_game_dialog.runner_name and add_game_dialog.slug:
+ self.add_game_to_view(add_game_dialog.slug)
+
def add_game_to_view(self, slug):
if not slug:
raise ValueError("Missing game slug")
@@ -373,20 +412,6 @@ class LutrisWindow(object):
self.switch_splash_screen()
GLib.idle_add(do_add_game)
- def add_game(self, _widget, _data=None):
- """Add a new game."""
- add_game_dialog = AddGameDialog(self)
- add_game_dialog.run()
- if add_game_dialog.runner_name and add_game_dialog.slug:
- self.add_game_to_view(add_game_dialog.slug)
-
- def add_manually(self, *args):
- game = Game(self.view.selected_game)
- add_game_dialog = AddGameDialog(self, game)
- add_game_dialog.run()
- if add_game_dialog.installed:
- self.view.set_installed(game)
-
def on_remove_game(self, _widget, _data=None):
selected_game = self.view.selected_game
UninstallGameDialog(slug=selected_game,
@@ -439,31 +464,6 @@ class LutrisWindow(object):
self.grid_view_menuitem.set_active(view_type == 'grid')
self.list_view_menuitem.set_active(view_type == 'list')
- def switch_view(self, view_type):
- """Switch between grid view and list view."""
- logger.debug("Switching view")
- self.icon_type = self.get_icon_type(view_type)
- self.view.destroy()
- self.view = load_view(
- view_type,
- get_game_list(filter_installed=self.filter_installed),
- filter_text=self.search_entry.get_text(),
- icon_type=self.icon_type
- )
- self.view.contextual_menu = self.menu
- self.connect_signals()
- self.games_scrollwindow.add(self.view)
- self.view.show_all()
- self.view.check_resize()
- # Note: set_active(True *or* False) apparently makes ALL the menuitems
- # in the group send the activate signal...
- if self.icon_type == 'banner_small':
- self.banner_small_menuitem.set_active(True)
- if self.icon_type == 'icon':
- self.icon_menuitem.set_active(True)
- if self.icon_type == 'banner':
- self.banner_menuitem.set_active(True)
-
def on_icon_type_activate(self, menuitem):
icon_type = menuitem.get_name()
if icon_type == self.view.icon_type or not menuitem.get_active():
diff --git a/lutris/gui/widgets.py b/lutris/gui/widgets.py
index 5f8456e6e..9d91a0d3a 100644
--- a/lutris/gui/widgets.py
+++ b/lutris/gui/widgets.py
@@ -207,10 +207,10 @@ class GameView(object):
"""Update a game row to show as installed"""
row = self.get_row_by_slug(game.slug)
if not row:
- logger.error("Can't find row for %s", game.slug)
- return
- row[COL_RUNNER] = game.runner_name
- self.update_image(game.slug, is_installed=True)
+ self.add_game(game)
+ else:
+ row[COL_RUNNER] = game.runner_name
+ self.update_image(game.slug, is_installed=True)
def set_uninstalled(self, game_slug):
"""Update a game row to show as uninstalled"""
diff --git a/lutris/installer.py b/lutris/installer.py
index f02e2a1e1..d81ffdd20 100644
--- a/lutris/installer.py
+++ b/lutris/installer.py
@@ -159,10 +159,13 @@ class ScriptInterpreter(object):
"Downloading file %d of %d",
len(self.game_files) + 1, len(self.script["files"])
)
+ file_index = len(self.game_files)
try:
- self._download_file(self.script["files"][len(self.game_files)])
+ current_file = self.script["files"][file_index]
except KeyError:
- raise ScriptingError("Badly formatted script", self.script)
+ raise ScriptingError("Error getting file %d in %s",
+ file_index, self.script['files'])
+ self._download_file(current_file)
else:
self.current_command = 0
self._prepare_commands()
@@ -553,7 +556,8 @@ class ScriptInterpreter(object):
def _append_steam_data_to_files(self, runner_class):
steam_runner = runner_class()
- data_path = steam_runner.get_game_data_path(self.steam_data['appid'])
+ data_path = steam_runner.get_game_path_from_appid(
+ self.steam_data['appid'])
if not data_path or not os.path.exists(data_path):
raise ScriptingError("Unable to get Steam data for game")
logger.debug("got data path: %s" % data_path)
@@ -591,7 +595,7 @@ class ScriptInterpreter(object):
def install_steam_game(self, runner_class):
steam_runner = runner_class()
appid = self.steam_data['appid']
- if not steam_runner.get_game_data_path(appid):
+ if not steam_runner.get_game_path_from_appid(appid):
logger.debug("Installing steam game %s" % appid)
# Here the user must wait for the game to finish installing, a
# better way to handle this would be to poll StateFlags on the
diff --git a/lutris/runners/dosbox.py b/lutris/runners/dosbox.py
index 60477dd72..e10f4a8d7 100644
--- a/lutris/runners/dosbox.py
+++ b/lutris/runners/dosbox.py
@@ -84,6 +84,10 @@ class dosbox(Runner):
"x64": "dosbox-0.74-x86_64.tar.gz",
}
+ @property
+ def main_file(self):
+ return self.settings.get('game', {}).get('main_file') or ''
+
@property
def browse_dir(self):
"""Return the path to open with the Browse Files action."""
@@ -99,7 +103,7 @@ class dosbox(Runner):
return os.path.join(settings.RUNNER_DIR, "dosbox/bin/dosbox")
def play(self):
- main_file = self.settings["game"]["main_file"]
+ main_file = self.main_file
if not os.path.exists(main_file):
return {'error': "FILE_NOT_FOUND", 'file': main_file}
diff --git a/lutris/runners/steam.py b/lutris/runners/steam.py
index 3e9829e20..6ced8cce5 100644
--- a/lutris/runners/steam.py
+++ b/lutris/runners/steam.py
@@ -58,7 +58,7 @@ class steam(Runner):
@property
def game_path(self):
- appid = self.settings['game'].get('appid')
+ appid = self.config['game'].get('appid')
for apps_path in self.get_steamapps_dirs():
game_path = get_path_from_appmanifest(apps_path, appid)
if game_path:
@@ -84,6 +84,14 @@ class steam(Runner):
if os.path.exists(path):
return path
+ def get_game_path_from_appid(self, appid):
+ """Return the game directory"""
+ for apps_path in self.get_steamapps_dirs():
+ game_path = get_path_from_appmanifest(apps_path, appid)
+ if game_path:
+ return game_path
+ logger.warning("Data path for SteamApp %s not found.", appid)
+
def get_steamapps_dirs(self):
"""Return a list of the Steam library main + custom folders."""
dirs = []
diff --git a/lutris/runners/winesteam.py b/lutris/runners/winesteam.py
index 532a0ebfb..5003ca1dc 100644
--- a/lutris/runners/winesteam.py
+++ b/lutris/runners/winesteam.py
@@ -69,12 +69,18 @@ class winesteam(wine.wine):
{
'option': 'appid',
'type': 'string',
- 'label': 'appid'
+ 'label': 'Application ID',
+ 'help': ("The application ID can be retrieved from the game's "
+ "page at steampowered.com. Example: 235320 is the "
+ "app ID for Original War in: \n"
+ "http://store.steampowered.com/app/235320/")
},
{
'option': 'args',
'type': 'string',
- 'label': 'arguments'
+ 'label': 'Arguments',
+ 'help': ("Windows command line arguments used when launching "
+ "Steam")
},
{
'option': 'prefix',
@@ -173,6 +179,14 @@ class winesteam(wine.wine):
apps = config['apps']
return apps.keys()
+ def get_game_path_from_appid(self, appid):
+ """Return the game directory"""
+ for apps_path in self.get_steamapps_dirs():
+ game_path = get_path_from_appmanifest(apps_path, appid)
+ if game_path:
+ return game_path
+ logger.warning("Data path for SteamApp %s not found.", appid)
+
def get_steamapps_dirs(self):
"""Return a list of the Steam library main + custom folders."""
dirs = []
diff --git a/lutris/sync.py b/lutris/sync.py
index bf9211911..307b7c248 100644
--- a/lutris/sync.py
+++ b/lutris/sync.py
@@ -2,11 +2,12 @@
"""Synchronization of the game library with the server and other platforms."""
import os
-from lutris.util.log import logger
from lutris import api, pga
from lutris.game import Game
from lutris.runners.steam import steam
from lutris.runners.winesteam import winesteam
+from lutris.util import resources
+from lutris.util.log import logger
class Sync(object):
@@ -14,10 +15,120 @@ class Sync(object):
self.library = pga.get_games()
def sync_all(self, caller):
- api.sync(caller)
- self.sync_steam(caller)
+ self.sync_from_remote(caller)
+ self.sync_steam_local(caller)
- def sync_steam(self, caller):
+ def sync_from_remote(self, caller=None):
+ """Synchronize from remote to local library.
+
+ :param caller: The LutrisWindow object
+ :return: The synchronized games (slugs)
+ :rtype: set of strings
+ """
+ logger.debug("Syncing game library")
+ # Get local library
+ local_slugs = set([game['slug'] for game in self.library])
+ logger.debug("%d games in local library", len(local_slugs))
+ # Get remote library
+ remote_library = api.get_library()
+ remote_slugs = set([game['slug'] for game in remote_library])
+ logger.debug("%d games in remote library (inc. unpublished)",
+ len(remote_slugs))
+
+ not_in_local = remote_slugs.difference(local_slugs)
+
+ added = self.sync_missing_games(not_in_local, remote_library, caller)
+ updated = self.sync_game_details(remote_library, caller)
+ return added.update(updated)
+
+ @staticmethod
+ def sync_missing_games(not_in_local, remote_library, caller=None):
+ """Get missing games in local library from remote library.
+
+ :param caller: The LutrisWindow object
+ :return: The slugs of the added games
+ :rtype: set
+ """
+ if not not_in_local:
+ return set()
+
+ for game in remote_library:
+ slug = game['slug']
+ # Sync
+ if slug in not_in_local:
+ logger.debug("Adding to local library: %s", slug)
+ pga.add_game(
+ game['name'], slug=slug, year=game['year'],
+ updated=game['updated'], steamid=game['steamid']
+ )
+ if caller:
+ caller.add_game_to_view(slug)
+ else:
+ not_in_local.discard(slug)
+ logger.debug("%d games added", len(not_in_local))
+ return not_in_local
+
+ @staticmethod
+ def sync_game_details(remote_library, caller):
+ """Update local game details,
+
+ :param caller: The LutrisWindow object
+ :return: The slugs of the updated games.
+ :rtype: set
+ """
+ if not remote_library:
+ return set()
+ updated = set()
+
+ # Get remote games (TODO: use this when switched API to DRF)
+ # remote_games = get_games(sorted(local_slugs))
+ # if not remote_games:
+ # return set()
+
+ for game in remote_library:
+ slug = game['slug']
+ sync = False
+ sync_icons = True
+ local_game = pga.get_game_by_slug(slug)
+ if not local_game:
+ continue
+
+ # Sync updated
+ if game['updated'] > local_game['updated']:
+ sync = True
+ # Sync new DB fields
+ else:
+ for key, value in local_game.iteritems():
+ if value or not key in game:
+ continue
+ if game[key]:
+ sync = True
+ sync_icons = False
+ if not sync:
+ continue
+
+ logger.debug("Syncing details for %s" % slug)
+ pga.add_or_update(
+ local_game['name'], local_game['runner'], slug,
+ year=game['year'], updated=game['updated'],
+ steamid=game['steamid']
+ )
+ caller.view.update_row(game)
+
+ # Sync icons (TODO: Only update if icon actually updated)
+ if sync_icons:
+ resources.download_icon(slug, 'banner', overwrite=True,
+ callback=caller.on_image_downloaded)
+ resources.download_icon(slug, 'icon', overwrite=True,
+ callback=caller.on_image_downloaded)
+ updated.add(slug)
+
+ logger.debug("%d games updated", len(updated))
+ return updated
+
+ def sync_steam_local(self, caller):
+ """Sync Steam games in library with Steam and Wine Steam"""
+ logger.debug("Syncing local steam games")
steam_ = steam()
winesteam_ = winesteam()
@@ -33,19 +144,21 @@ class Sync(object):
# Set installed (steam linux only)
if installed_in_steam and not game_info['installed']:
+ logger.debug("Setting %s as installed" % game_info['name'])
pga.add_or_update(game_info['name'], 'steam',
game_info['slug'],
installed=1)
game.config.game_config.update({'game':
{'appid': str(steamid)}})
game.config.save()
- caller.view.set_installed(game)
+ caller.view.set_installed(Game(game_info['slug']))
continue
# Set uninstalled
if not (installed_in_steam or installed_in_winesteam) \
and game_info['installed'] \
and game_info['runner'] in ['steam', 'winesteam']:
+ logger.debug("Setting %s as uninstalled" % game_info['name'])
pga.add_or_update(game_info['name'], '',
game_info['slug'],
installed=0)
@@ -53,6 +166,7 @@ class Sync(object):
@staticmethod
def _get_installed_steamapps(runner):
+ """Return a list of appIDs of the installed Steam games."""
if not runner.is_installed():
return []
installed = []