Better steam data handling + client login

This commit is contained in:
Mathieu Comandon 2013-06-16 01:28:23 +02:00
parent 397e7deafb
commit c8accf0293
9 changed files with 162 additions and 49 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkDialog" id="dialog1">
<object class="GtkDialog" id="lutris-login">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="type_hint">dialog</property>
@ -15,11 +15,12 @@
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button1">
<property name="label" translatable="yes">button</property>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
@ -28,11 +29,12 @@
</packing>
</child>
<child>
<object class="GtkButton" id="button2">
<property name="label" translatable="yes">button</property>
<object class="GtkButton" id="connect_button">
<property name="label">gtk-connect</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
@ -98,11 +100,14 @@
</packing>
</child>
<child>
<object class="GtkEntry" id="entry1">
<object class="GtkEntry" id="username_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="hexpand">True</property>
<property name="invisible_char">•</property>
<property name="width_chars">32</property>
<property name="shadow_type">none</property>
</object>
<packing>
<property name="left_attach">1</property>
@ -112,10 +117,12 @@
</packing>
</child>
<child>
<object class="GtkEntry" id="entry2">
<object class="GtkEntry" id="password_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="max_length">26</property>
<property name="visibility">False</property>
<property name="invisible_char">•</property>
<property name="invisible_char_set">True</property>
</object>
@ -148,8 +155,8 @@
</object>
</child>
<action-widgets>
<action-widget response="0">button1</action-widget>
<action-widget response="0">button2</action-widget>
<action-widget response="0">cancel_button</action-widget>
<action-widget response="4">connect_button</action-widget>
</action-widgets>
</object>
</interface>

20
lutris/api.py Normal file
View file

@ -0,0 +1,20 @@
import os
import json
import urllib
import urllib2
from lutris import settings
def connect(username, password):
credentials = urllib.urlencode({'username': username,
'password': password})
login_url = settings.SITE_URL + "user/auth/"
request = urllib2.urlopen(login_url, credentials, 3)
response = json.loads(request.read())
if 'token' in response:
token = response['token']
token_file_path = os.path.join(settings.CACHE_DIR, 'auth-token')
with open(token_file_path, "w") as token_file:
token_file.write(token)
return response['token']
return False

View file

@ -141,7 +141,7 @@ class LutrisGame(object):
return False
logger.debug("get ready for %s " % self.get_real_name())
gameplay_info = self.runner.play()
logger.debug("gameplay_info: %s" % gameplay_info)
if isinstance(gameplay_info, dict):
if 'error' in gameplay_info:
show_error_message(gameplay_info)

View file

@ -7,11 +7,13 @@ from lutris.gui.widgets import DownloadProgressBox
from lutris import settings
from lutris import pga
from lutris import api
class GtkBuilderDialog(object):
class GtkBuilderDialog(GObject.Object):
def __init__(self):
super(GtkBuilderDialog, self).__init__()
ui_filename = os.path.join(settings.get_data_path(), 'ui',
self.glade_file)
if not os.path.exists(ui_filename):
@ -36,6 +38,7 @@ class NoticeDialog(Gtk.MessageDialog):
""" Displays a message to the user. """
def __init__(self, message):
super(NoticeDialog, self).__init__(buttons=Gtk.ButtonsType.OK)
from lutris import api
self.set_markup(message)
self.run()
self.destroy()
@ -130,7 +133,9 @@ class PgaSourceDialog(GtkBuilderDialog):
# GtkBuilder Objects
self.sources_selection = self.builder.get_object("sources_selection")
self.sources_treeview = self.builder.get_object("sources_treeview")
self.remove_source_button = self.builder.get_object("remove_source_button")
self.remove_source_button = self.builder.get_object(
"remove_source_button"
)
# Treeview setup
self.sources_liststore = Gtk.ListStore(str)
@ -180,3 +185,32 @@ class PgaSourceDialog(GtkBuilderDialog):
""" Set sentivity of remove source button """
(model, treeiter) = self.sources_selection.get_selected()
self.remove_source_button.set_sensitive(treeiter is not None)
class ClientLoginDialog(GtkBuilderDialog):
glade_file = 'dialog-lutris-login.ui'
dialog_object = 'lutris-login'
__gsignals__ = {
"connected": (GObject.SIGNAL_RUN_FIRST, None, (str, )),
}
def __init__(self):
super(ClientLoginDialog, self).__init__()
cancel_button = self.builder.get_object('cancel_button')
cancel_button.connect('clicked', self.on_cancel)
connect_button = self.builder.get_object('connect_button')
connect_button.connect('clicked', self.on_connect)
def on_cancel(self, widget):
self.dialog.destroy()
def on_connect(self, widget):
username = self.builder.get_object('username_entry').get_text()
password = self.builder.get_object('password_entry').get_text()
token = api.connect(username, password)
if not token:
NoticeDialog("Login failed")
else:
self.emit('connected', token)
self.dialog.destroy()

View file

@ -152,7 +152,11 @@ class LutrisWindow(object):
# Callbacks
def on_connect(self, *args):
"""Callback when a user connects to his account"""
dialogs.NoticeDialog("This functionnality is not yet implemented.")
login_dialog = dialogs.ClientLoginDialog()
login_dialog.connect('connected', self.on_connect_success)
def on_connect_success(self, dialog, token):
self.status_label.set_text("Connected")
def on_destroy(self, *args):
"""Signal for window close"""

View file

@ -123,9 +123,15 @@ class ScriptInterpreter(object):
else:
self._iter_commands()
def install_steam_game(self, appid):
def get_steam_game_path(self, appid, file_id, steam_rel_path):
steam_runner = steam()
steam_runner.install_game(appid)
data_path = steam_runner.get_game_data_path(appid)
if not data_path:
steam_runner.install_game(appid)
data_path = steam_runner.get_game_data_path(appid)
logger.debug("got data path: %s" % data_path)
self.game_files[file_id] = os.path.join(data_path, steam_rel_path)
self.iter_game_files()
def _download_file(self, game_file):
"""Download a file referenced in the installer script
@ -137,14 +143,6 @@ class ScriptInterpreter(object):
- filename : force destination filename when url is present or path
of local file
"""
def on_steam_installed(appid):
game_path = steam_runner.get_game_data_path(appid)
if not game_path:
steam_runner.install_game(appid)
game_path = steam_runner.get_game_data_path(appid)
self.game_files[file_id] = os.path.join(game_path, parts[2])
self.iter_game_files()
# Setup file_id, file_uri and local filename
file_id = game_file.keys()[0]
if isinstance(game_file[file_id], dict):
@ -158,6 +156,7 @@ class ScriptInterpreter(object):
elif file_uri.startswith("$WINESTEAM"):
parts = file_uri.split(":", 2)
appid = parts[1]
steam_rel_path = parts[2]
steam_runner = steam()
if not steam_runner.is_installed():
steam_installer_path = os.path.join(
@ -170,7 +169,8 @@ class ScriptInterpreter(object):
appid
)
else:
self.install_steam_game(appid)
logger.debug("Steam already installed, installing game")
self.get_steam_game_path(appid, file_id, steam_rel_path)
return
logger.debug("Fetching [%s]: %s" % (file_id, file_uri))
@ -225,13 +225,21 @@ class ScriptInterpreter(object):
"""Write the game configuration as a Lutris launcher."""
config_filename = join(settings.CONFIG_DIR,
"games/%s.yml" % self.game_slug)
runner_name = self.script['runner']
config_data = {
'game': {},
'realname': self.script['name'],
'runner': self.script['runner']
'runner': runner_name
}
if 'system' in self.script:
config_data['system'] = self.script['system']
if runner_name in self.script:
config_data[runner_name] = self.script[runner_name]
if 'game' in self.script:
for key in self.script['game']:
value = self._substitute(self.script['game'][key])
config_data['game'][key] = value
is_64bit = platform.machine() == "x86_64"
exe = 'exe64' if 'exe64' in self.script and is_64bit else 'exe'
for launcher in [exe, 'iso', 'rom', 'disk', 'main_file']:
@ -257,6 +265,7 @@ class ScriptInterpreter(object):
config_data['game'][key] = game_resource
yaml_config = yaml.safe_dump(config_data, default_flow_style=False)
logger.debug(yaml_config)
with open(config_filename, "w") as config_file:
config_file.write(yaml_config)
@ -330,8 +339,20 @@ class ScriptInterpreter(object):
raise ScriptingError("MD5 checksum mismatch", data)
def mergecopy(self, params):
logger.debug("Merging %s" % str(params))
src, dst = self._get_move_paths(params)
raise ScriptingError("%s, %s" % (str(src), str(dst)))
if not os.path.exists(dst):
raise ValueError(dst)
for (dirpath, dirnames, filenames) in os.walk(src):
src_relpath = dirpath[len(src) + 1:]
dst_abspath = os.path.join(dst, src_relpath)
for dirname in dirnames:
new_dir = os.path.join(dst_abspath, dirname)
logger.debug("creating dir: %s" % new_dir)
os.mkdir(new_dir)
for filename in filenames:
shutil.copy(os.path.join(dirpath, filename),
os.path.join(dst_abspath, filename))
def move(self, params):
""" Move a file or directory """
@ -353,9 +374,11 @@ class ScriptInterpreter(object):
def extract(self, data):
""" Extracts a file, guessing the compression method """
logger.debug("extracting file %s" % str(data))
filename = self.game_files.get(data['file'])
if not filename:
raise ScriptingError("No file for '%s' in game files %s " % (data, self.game_files))
raise ScriptingError("No file for '%s' in game files %s "
% (data, self.game_files))
return False
if not os.path.exists(filename):
logger.error("%s does not exists" % filename)
@ -373,6 +396,8 @@ class ScriptInterpreter(object):
else:
logger.error("unrecognised file extension %s" % extension)
return False
import time
time.sleep(1)
def runner_task(self, data):
""" This action triggers a task within a runner.
@ -461,6 +486,7 @@ class InstallerDialog(Gtk.Dialog):
self.interpreter.target_path = text_entry.get_text()
def on_install_clicked(self, button):
button.set_sensitive(False)
self.interpreter._start_install()
def ask_user_for_file(self):
@ -491,6 +517,7 @@ class InstallerDialog(Gtk.Dialog):
self.interpreter.iter_game_files()
def on_steam_downloaded(self, widget, data, steam_info):
logger.debug("Steam downloaded")
steam_runner = steam()
steam_runner.install(steam_info[0])
steam_runner.install_game(steam_info[1])

View file

@ -21,6 +21,8 @@
""" Runner for MS Dos games """
import os
from lutris.util.log import logger
from lutris.runners.runner import Runner
@ -34,18 +36,34 @@ class dosbox(Runner):
self.executable = "dosbox"
self.platform = "MS DOS"
self.description = "DOS Emulator"
self.game_options = [{
"option": "main_file",
"type": "file_chooser",
"label": "EXE File"
}]
self.game_options = [
{
"option": "main_file",
"type": "file_chooser",
"label": "EXE File"
},
{
"option": "config_file",
"type": "file_chooser",
"label": "Configuration file"
}
]
self.runner_options = []
if settings:
self.exe = settings["game"]["main_file"]
self.settings = settings
def play(self):
""" Run the game """
if not self.is_installed():
return {'error': 'RUNNER_NOT_INSTALLED'}
command = [self.executable, "\"%s\"" % self.exe]
return command
logger.debug(self.settings)
self.exe = self.settings["game"]["main_file"]
if not os.path.exists(self.exe):
return {'error': "FILE_NOT_FOUND", 'file': self.exe}
if self.exe.endswith(".conf"):
exe = ["-conf", self.exe]
else:
exe = [self.exe]
if "config_file" in self.settings["game"]:
params = ["-conf", self.settings["game"]["config_file"]]
else:
params = []
return [self.executable] + params + exe

View file

@ -24,8 +24,8 @@
import os
import stat
import os.path
import logging
from lutris.util.log import logger
from lutris.runners.runner import Runner
@ -61,7 +61,7 @@ class linux(Runner):
#Check if script is executable and make it executable if not
if not os.access(installer_path, os.X_OK):
logging.debug("%s is not executable, setting it executable")
logger.debug("%s is not executable, setting it executable")
os.chmod(installer_path,
stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
@ -73,6 +73,7 @@ class linux(Runner):
def play(self):
"""Run native game."""
logger.debug("Launching Linux game")
game_config = self.config.get('game')
if not game_config:
return {'error': 'INVALID_CONFIG'}
@ -89,4 +90,5 @@ class linux(Runner):
command.append("./%s" % os.path.basename(executable))
for arg in args.split():
command.append(arg)
logger.debug("Linux runner args: %s" % command)
return {'command': command}

View file

@ -23,12 +23,12 @@
import os
import subprocess
from lutris.gui.dialogs import QuestionDialog, DirectoryDialog
from lutris.gui.dialogs import DirectoryDialog
from lutris.runners.wine import wine
from lutris.util.log import logger
from lutris.config import LutrisConfig
def get_name(steam_file):
"""Get game name from some weird steam file"""
data = steam_file.read(1000)
@ -74,7 +74,8 @@ def vdf_parse(steam_config_file, config):
# pylint: disable=C0103
class steam(wine):
installer_url = "http://cdn.steampowered.com/download/SteamInstall.msi"
#installer_url = "http://cdn.steampowered.com/download/SteamInstall.msi"
installer_url = "http://lutris.net/files/runners/SteamInstall.msi"
"""Runs Steam games with Wine"""
def __init__(self, settings=None):
@ -134,10 +135,10 @@ class steam(wine):
def install_game(self, appid):
#print "Q2", apps["2320"]
#print "Shadow", apps["238070"]
subprocess.Popen(
["wine", '%s' % os.path.join(self.game_path, self.executable),
"-no-drwite", "steam://install/%s" % appid]
)
args = ["wine", '%s' % os.path.join(self.game_path, self.executable),
"-no-dwrite", "steam://install/%s" % appid]
logger.debug(args)
subprocess.Popen(args)
def play(self):
appid = self.settings['game']['appid']