Merge branch 'next'

This commit is contained in:
Mathieu Comandon 2016-08-10 18:56:46 -07:00
commit da24e09f84
46 changed files with 201 additions and 214 deletions

View file

@ -7,7 +7,7 @@ cover:
test:
rm tests/fixtures/pga.db -f
nosetests
nosetests3
deb-source: clean
gbp buildpackage -S

View file

@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# This program is free software: you can redistribute it and/or modify it
@ -49,7 +49,7 @@ from lutris.game import Game
from lutris.gui.installgamedialog import InstallerDialog
from lutris.settings import VERSION
from lutris.util import service
from lutris.util.steam import get_steamapps_paths
from lutris.util.steam import get_steamapps_paths, AppManifest, get_appmanifests
# Support for command line options.
parser = optparse.OptionParser(version="%prog " + VERSION)
@ -63,8 +63,8 @@ parser.add_option("-l", "--list-games", action="store_true", dest="list_games",
help="List games in database")
parser.add_option("-o", "--installed", action="store_true", dest="list_installed",
help="Only list installed games")
parser.add_option("--list-steam", action="store_true",
help="List Steam (Windows) games")
parser.add_option("--list-steam-games", action="store_true",
help="List available Steam games")
parser.add_option("--list-steam-folders", action="store_true",
help="List all known Steam library folders")
parser.add_option("-j", "--json", action="store_true", dest="json",
@ -92,31 +92,37 @@ if options.list_games:
'runner': game['runner'],
'directory': game['directory'] or '-'
})
print json.dumps(games, indent=2).encode('utf-8')
print(json.dumps(games, indent=2).encode('utf-8'))
else:
for game in game_list:
print u"{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format(
print("{:4} | {:<40} | {:<40} | {:<15} | {:<64}".format(
game['id'],
game['name'][:40],
game['slug'][:40],
game['runner'],
game['directory'] or '-'
).encode('utf-8')
).encode('utf-8'))
exit()
if options.list_steam:
# FIXME: this returns a list of appids
# FIXME: this only works for Wine Steam games
from lutris.runners import winesteam
steam_runner = winesteam.winesteam()
for appid in steam_runner.get_appid_list():
print appid
if options.list_steam_games:
steamapps_paths = get_steamapps_paths()
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
appmanifest_files = get_appmanifests(path)
for appmanifest_file in appmanifest_files:
appmanifest = AppManifest(os.path.join(path, appmanifest_file))
print(" {:8} | {:<60} | {:10} | {}".format(
appmanifest.steamid,
appmanifest.name or '-',
platform,
", ".join(appmanifest.states)
))
exit()
if options.list_steam_folders:
steamapps_paths = get_steamapps_paths()
for path in steamapps_paths['linux']:
print path
for path in steamapps_paths['windows']:
print path
for platform in ('linux', 'windows'):
for path in steamapps_paths[platform]:
print(path)
exit()
@ -138,11 +144,6 @@ if type(lutris) is dbus.Interface:
lutris.is_running()
except dbus.exceptions.DBusException as e:
logger.error(e)
# FIXME This whole thing below is utterly broken and I've never seen
# the expected behavior happen. Now, if only I knew how to reproduce
# this state, maybe I might be able to write a fix but I still have no
# idea what's causing this non-responsive DBus thing.
try:
# Get existing process' PID
dbus_proxy = bus.get_object('org.freedesktop.DBus',
@ -155,7 +156,7 @@ if type(lutris) is dbus.Interface:
logger.error("Lutris was non responsive, we tried, we failed and failed "
"some more. Please now try to restart Lutris")
logger.error(ex)
exit() # Give up :(
exit()
else:
time.sleep(1) # Wait for bus name to be available again
lutris = service.LutrisService(bus, '/', service.DBUS_INTERFACE)

15
debian/control vendored
View file

@ -2,7 +2,7 @@ Source: lutris
Section: games
Priority: optional
Build-Depends: debhelper,
python,
python3,
dh-python,
gir1.2-gtk-3.0,
gir1.2-glib-2.0,
@ -12,19 +12,20 @@ Maintainer: Mathieu Comandon <strider@strycore.com>
Standards-Version: 3.9.5
Vcs-Git: https://github.com/lutris/lutris
Homepage: https://lutris.net
X-Python-Version: >= 2.7
X-Python-Version: >= 3.4
Package: lutris
Architecture: any
Depends: ${misc:Depends},
${python:Depends},
python-yaml,
python-dbus,
python-gi,
python-pyinotify,
python3-yaml,
python3-dbus,
python3-gi,
python3-pyinotify,
python3-xdg,
python3-evdev
gir1.2-gtk-3.0,
xdg-user-dirs,
python-xdg,
psmisc,
libc6-i386 [amd64],
lib32gcc1 [amd64]

View file

@ -1,7 +1,8 @@
import os
import json
import urllib
import urllib2
import urllib.request
import urllib.parse
import urllib.error
import socket
from lutris import settings
@ -25,12 +26,12 @@ def read_api_key():
def connect(username, password):
credentials = urllib.urlencode({'username': username,
'password': password})
credentials = urllib.parse.urlencode({'username': username,
'password': password})
login_url = settings.SITE_URL + "api/accounts/token"
try:
request = urllib2.urlopen(login_url, credentials, 10)
except (socket.timeout, urllib2.URLError) as ex:
request = urllib.request.urlopen(login_url, credentials, 10)
except (socket.timeout, urllib.error.URLError) as ex:
logger.error("Unable to connect to server (%s): %s", login_url, ex)
return False
response = json.loads(request.read())

View file

@ -69,7 +69,7 @@ def read_yaml_from_file(filename):
if not filename or not os.path.exists(filename):
return {}
try:
content = file(filename, 'r').read()
content = open(filename, 'r').read()
yaml_content = yaml.load(content) or {}
except (yaml.scanner.ScannerError, yaml.parser.ParserError):
logger.error("error parsing file %s", filename)
@ -263,7 +263,7 @@ class LutrisConfig(object):
"""Return a dict of options' default value."""
options_dict = self.options_as_dict(options_type)
defaults = {}
for option, params in options_dict.iteritems():
for option, params in options_dict.items():
if 'default' in params:
defaults[option] = params['default']
return defaults

View file

@ -1,6 +1,6 @@
import os
import time
import Queue
import queue
from lutris.util import http, jobs
from lutris.util.log import logger
@ -17,7 +17,7 @@ class Downloader():
DOWNLOADING,
CANCELLED,
ERROR,
COMPLETED) = range(5)
COMPLETED) = list(range(5))
def __init__(self, url, dest, overwrite=False):
self.url = url
@ -42,7 +42,7 @@ class Downloader():
self.time_left_check_time = 0
self.file_pointer = None
self.queue = Queue.Queue()
self.queue = queue.Queue()
def start(self):
"""Start download job."""
@ -108,7 +108,7 @@ class Downloader():
def write_queue(self):
"""Append download queue to destination file."""
buffered_chunk = ''
buffered_chunk = b''
while self.queue.qsize():
chunk, received_bytes, total_bytes = self.queue.get()
buffered_chunk += chunk

View file

@ -343,7 +343,7 @@ class Game(object):
"""Restore some settings and cleanup after game quit."""
self.heartbeat = None
quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
logger.debug("%s stopped at %s", self.name, quit_time.decode("utf-8"))
logger.debug("%s stopped at %s", self.name, quit_time)
self.state = self.STATE_STOPPED
os.chdir(os.path.expanduser('~'))

View file

@ -477,7 +477,9 @@ class ConfigBox(VBox):
def set_style_property(self, property_, value, wrapper):
"""Add custom style."""
style_provider = Gtk.CssProvider()
style_provider.load_from_data("GtkHBox {%s: %s;}" % (property_, value))
style_provider.load_from_data(
"GtkHBox {{{}: {};}}".format(property_, value).encode()
)
style_context = wrapper.get_style_context()
style_context.add_provider(style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

View file

@ -8,7 +8,7 @@ from lutris.gui.widgets import DownloadProgressBox
from lutris.util import datapath
class GtkBuilderDialog(GObject.Object):
class GtkBuilderDialog():
def __init__(self, parent=None, **kwargs):
super(GtkBuilderDialog, self).__init__()

View file

@ -28,7 +28,7 @@ ICON_SIZE = (32, 32)
COL_YEAR,
COL_RUNNER,
COL_INSTALLED,
) = range(7)
) = list(range(7))
def sort_func(store, a_iter, b_iter, _user_data):
@ -113,7 +113,7 @@ class GameStore(GObject.Object):
def fill_store(self, games):
"""Fill the model asynchronously and in steps."""
loader = self._fill_store_generator(games)
GLib.idle_add(loader.next)
GLib.idle_add(loader.__next__)
def _fill_store_generator(self, games, step=100):
"""Generator to fill the model in steps."""

View file

@ -154,7 +154,7 @@ class InstallerDialog(Gtk.Window):
# Build list
for index, script in enumerate(self.scripts):
for item in ['description', 'notes']:
script[item] = (script.get(item) or '').encode('utf-8')
script[item] = script.get(item) or ''
runner = script['runner']
version = script['version']
label = "{} ({})".format(version, runner)
@ -211,9 +211,6 @@ class InstallerDialog(Gtk.Window):
def prepare_install(self, script_index):
script = self.scripts[script_index]
# FIXME: auto-installers should provide game_slug
if not script.get('game_slug'):
script['game_slug'] = self.game_ref
self.interpreter = interpreter.ScriptInterpreter(script, self)
game_name = self.interpreter.game_name.replace('&', '&amp;')
self.title_label.set_markup(u"<b>Installing {}</b>".format(game_name))

View file

@ -327,7 +327,7 @@ class LutrisWindow(Gtk.Application):
"""Synchronize games with local stuff and server."""
def update_gui(result, error):
if result:
added, updated = result # , installed, uninstalled = result
added, updated = result
self.switch_splash_screen()
self.game_store.fill_store(added)
@ -339,7 +339,6 @@ class LutrisWindow(Gtk.Application):
AsyncCall(Sync().sync_all, update_gui)
def update_existing_games(self, added, updated, first_run=False):
# , installed, uninstalled, first_run=False):
for game_id in updated.difference(added):
self.view.update_row(pga.get_game_by_field(game_id, 'id'))

View file

@ -160,7 +160,8 @@ class RunnerInstallDialog(Dialog):
extract_archive(src, dst)
return src, row
def on_extracted(self, (src, row), error):
def on_extracted(self, xxx_todo_changeme, error):
(src, row) = xxx_todo_changeme
os.remove(src)
row[self.COL_PROGRESS] = 0
row[self.COL_INSTALLED] = True

View file

@ -141,6 +141,9 @@ class RunnersDialog(Gtk.Window):
def on_install_clicked(self, widget, runner, runner_label):
"""Install a runner."""
if runner.depends_on is not None:
dependency = runner.depends_on()
dependency.install()
runner.install()
if runner.is_installed():
self.emit('runner-installed')

View file

@ -4,7 +4,7 @@ import os
import time
import yaml
import shutil
import urllib2
import urllib.request, urllib.error, urllib.parse
import platform
from gi.repository import GLib
@ -28,9 +28,10 @@ from lutris.runners import (
def fetch_script(game_ref):
"""Download install script(s) for matching game_ref."""
request = urllib2.Request(url=settings.INSTALLER_URL % game_ref)
# TODO use http.Request
request = urllib.request.Request(url=settings.INSTALLER_URL % game_ref)
try:
request = urllib2.urlopen(request)
request = urllib.request.urlopen(request)
script_contents = request.read()
except IOError:
return
@ -183,7 +184,7 @@ class ScriptInterpreter(CommandsMixin):
of local file.
"""
# Setup file_id, file_uri and local filename
file_id = game_file.keys()[0]
file_id = list(game_file.keys())[0]
if isinstance(game_file[file_id], dict):
filename = game_file[file_id]['filename']
file_uri = game_file[file_id]['url']
@ -390,7 +391,7 @@ class ScriptInterpreter(CommandsMixin):
def _get_command_name_and_params(self, command_data):
if isinstance(command_data, dict):
command_name = command_data.keys()[0]
command_name = list(command_data.keys())[0]
command_params = command_data[command_name]
else:
command_name = command_data
@ -515,14 +516,14 @@ class ScriptInterpreter(CommandsMixin):
"""Substitute values such as $GAMEDIR in a config dict."""
config = {}
for key in script_config:
if not isinstance(key, basestring):
if not isinstance(key, str):
raise ScriptingError("Game config key must be a string", key)
value = script_config[key]
if isinstance(value, list):
config[key] = [self._substitute(i) for i in value]
elif isinstance(value, dict):
config[key] = dict(
[(k, self._substitute(v)) for (k, v) in value.iteritems()]
[(k, self._substitute(v)) for (k, v) in value.items()]
)
elif isinstance(value, bool):
config[key] = value

View file

@ -1,3 +1,4 @@
import importlib
from lutris import settings
from lutris.util.log import logger
@ -20,8 +21,7 @@ MIGRATIONS.append([
def get_migration_module(migration_name):
return __import__('lutris.migrations.%s' % migration_name,
globals(), locals(), [migration_name], -1)
return importlib.import_module('lutris.migrations.%s' % migration_name)
def migrate():

View file

@ -39,7 +39,7 @@ def get_runner_module(runner_name):
if runner_name not in __all__:
raise InvalidRunner("Invalid runner name '%s'", runner_name)
return __import__('lutris.runners.%s' % runner_name,
globals(), locals(), [runner_name], -1)
globals(), locals(), [runner_name], 0)
def import_runner(runner_name):

View file

@ -2,7 +2,7 @@
import re
import os
import shutil
from ConfigParser import ConfigParser
from configparser import ConfigParser
from collections import Counter
from lutris import settings
from lutris.runners.runner import Runner
@ -115,8 +115,8 @@ class reicast(Runner):
for section in config:
if not parser.has_section(section):
parser.add_section(section)
for (key, value) in config[section].iteritems():
parser.set(section, key, value)
for (key, value) in config[section].items():
parser.set(section, key, str(value))
with open(config_path, 'w') as config_file:
parser.write(config_file)

View file

@ -51,6 +51,9 @@ class Runner(object):
self.config.game_config_id, 'configpath'
)
def __lt__(self, other):
return self.name < other.name
@property
def description(self):
"""Return the class' docstring as the description."""

View file

@ -1,6 +1,5 @@
import os
import shlex
import shutil
import subprocess
from textwrap import dedent
@ -263,7 +262,7 @@ def get_default_version():
def get_system_wine_version(wine_path="wine"):
"""Return the version of Wine installed on the system."""
try:
version = subprocess.check_output([wine_path, "--version"]).strip()
version = subprocess.check_output([wine_path, "--version"]).decode().strip()
except OSError:
return
else:
@ -629,7 +628,7 @@ class wine(Runner):
"""Reset regedit keys according to config."""
prefix = self.prefix_path
enable_wine_desktop = False
for key, path in self.reg_keys.iteritems():
for key, path in self.reg_keys.items():
value = self.runner_config.get(key) or 'auto'
if not value or value == 'auto' and key != 'ShowCrashDialog':
delete_registry_key(path, wine_path=self.get_executable(),
@ -652,7 +651,7 @@ class wine(Runner):
self.set_wine_desktop(enable_wine_desktop)
overrides = self.runner_config.get('overrides') or {}
overrides_path = "%s\DllOverrides" % self.reg_prefix
for dll, value in overrides.iteritems():
for dll, value in overrides.items():
set_regedit(overrides_path, dll, value,
wine_path=self.get_executable(),
prefix=prefix, arch=self.wine_arch)

View file

@ -234,10 +234,6 @@ class winesteam(wine.wine):
if callback:
callback()
if not self.is_wine_installed():
# FIXME find another way to do that (already fixed from the install game
# dialog)
wine.wine().install(version=version, downloader=downloader)
if downloader:
downloader(STEAM_INSTALLER_URL, installer_path, on_steam_downloaded)
else:

View file

@ -15,7 +15,9 @@ STATUS_UPDATER = None
def is_updating(include_pending_updates=True):
if include_pending_updates and CURRENT_UPDATES is None:
return True
return CURRENT_UPDATES > 0
if CURRENT_UPDATES:
return CURRENT_UPDATES > 0
return False
def get_created_at(name):

View file

@ -15,9 +15,9 @@ from lutris.settings import CACHE_DIR
def get_xdg_basename(game_slug, game_id, legacy=False):
if legacy:
filename = "%s.desktop" % game_slug
filename = "{}.desktop".format(game_slug)
else:
filename = "%s-%s.desktop" % (game_slug, game_id)
filename = "{}-{}.desktop".format(game_slug, game_id)
return filename
@ -34,7 +34,8 @@ def create_launcher(game_slug, game_id, game_name, desktop=False, menu=False):
Icon=%s
Exec=lutris lutris:%s
Categories=Game
""" % (game_name, 'lutris_' + game_slug, game_id))
""".format(game_name, 'lutris_{}'.format(game_slug), game_id)
)
launcher_filename = get_xdg_basename(game_slug, game_id, legacy=False)
tmp_launcher_path = os.path.join(CACHE_DIR, launcher_filename)
@ -65,7 +66,7 @@ def get_launcher_path(game_slug, game_id):
return
desktop_dir = subprocess.Popen([xdg_executable, 'DESKTOP'],
stdout=subprocess.PIPE).communicate()[0]
desktop_dir = desktop_dir.strip()
desktop_dir = str(desktop_dir).strip()
legacy_launcher_path = os.path.join(
desktop_dir, get_xdg_basename(game_slug, game_id, legacy=True)

View file

@ -95,7 +95,7 @@ class Sync(object):
sync = True
# Sync new DB fields
else:
for key, value in local_game.iteritems():
for key, value in local_game.items():
if value or key not in game:
continue
if game[key]:

View file

@ -17,14 +17,14 @@ oss_list = [
def get_resolution_choices():
resolutions = display.get_resolutions()
resolution_choices = zip(resolutions, resolutions)
resolution_choices = list(zip(resolutions, resolutions))
resolution_choices.insert(0, ("Keep current", 'off'))
return resolution_choices
def get_output_choices():
outputs = display.get_output_names()
output_choices = zip(outputs, outputs)
output_choices = list(zip(outputs, outputs))
output_choices.insert(0, ("Off", 'off'))
return output_choices
@ -210,5 +210,5 @@ def with_runner_overrides(runner_slug):
opts_dict[key].update(option)
else:
opts_dict[key] = option
options = [opt for opt in opts_dict.values()]
options = [opt for opt in list(opts_dict.values())]
return options

View file

@ -54,7 +54,7 @@ class LutrisThread(threading.Thread):
self.cwd = os.path.expanduser(self.cwd)
self.env_string = ''
for (k, v) in self.env.iteritems():
for (k, v) in self.env.items():
self.env_string += '%s="%s" ' % (k, v)
self.command_string = ' '.join(
@ -82,6 +82,9 @@ class LutrisThread(threading.Thread):
if not self.game_process:
return
for line in iter(self.game_process.stdout.readline, ''):
line = line.decode()
if not line:
continue
self.stdout += line
if self.debug_output:
sys.stdout.write(line)
@ -104,7 +107,7 @@ class LutrisThread(threading.Thread):
exec sh # Keep term open
""" % (self.cwd, self.env_string, self.command_string)
))
os.chmod(file_path, 0744)
os.chmod(file_path, 0o744)
self.game_process = self.execute_process([self.terminal, '-e', file_path])

View file

@ -77,11 +77,11 @@ class TOSEC:
)''')
def __enter__(self):
print 'enter'
print('enter')
return self
def __exit__(self, type, value, traceback):
print 'exit'
print('exit')
self.db.close()
def __del__(self):
@ -98,7 +98,7 @@ class TOSEC:
# If the info don't have a version, it is not valid and the file
# shouldn't be added
if not 'version' in info:
if 'version' not in info:
return False
new_version = info["version"]

View file

@ -21,7 +21,7 @@ def get_vidmodes():
xrandr_output = subprocess.Popen("xrandr",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
return list([line for line in xrandr_output.split("\n")])
return list([line for line in str(xrandr_output).split("\n")])
def get_outputs():

View file

@ -1,5 +1,5 @@
from collections import OrderedDict
from ConfigParser import RawConfigParser
from configparser import RawConfigParser
class EvilConfigParser(RawConfigParser):
@ -7,7 +7,7 @@ class EvilConfigParser(RawConfigParser):
def write(self, fp):
for section in self._sections:
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
for (key, value) in list(self._sections[section].items()):
if key == "__name__":
continue
if (value is not None) or (self._optcre == self.OPTCRE):

View file

@ -1,7 +1,9 @@
import os
import json
import socket
import urllib2
import urllib.request
import urllib.error
import urllib.parse
from lutris.util.log import logger
@ -53,19 +55,19 @@ class Request(object):
self.headers = headers
def get(self, data=None):
req = urllib2.Request(url=self.url, data=data, headers=self.headers)
req = urllib.request.Request(url=self.url, data=data, headers=self.headers)
try:
request = urllib2.urlopen(req, timeout=self.timeout)
except urllib2.HTTPError as e:
request = urllib.request.urlopen(req, timeout=self.timeout)
except urllib.error.HTTPError as e:
if self.error_logging:
logger.error("Unavailable url (%s): %s", self.url, e)
except (socket.timeout, urllib2.URLError) as e:
except (socket.timeout, urllib.error.URLError) as e:
if self.error_logging:
logger.error("Unable to connect to server (%s): %s",
self.url, e)
else:
try:
total_size = request.info().getheader('Content-Length').strip()
total_size = request.info().get('Content-Length').strip()
total_size = int(total_size)
except AttributeError:
total_size = 0
@ -86,7 +88,7 @@ class Request(object):
if not chunk:
break
request.close()
self.content = ''.join(chunks)
self.content = b''.join(chunks).decode('utf-8')
return self
def post(self, data):

View file

@ -1,3 +1,5 @@
import sys
import traceback
import threading
from gi.repository import GLib
@ -27,10 +29,12 @@ class AsyncCall(threading.Thread):
try:
result = self.function(*args, **kwargs)
except Exception, err:
except Exception as err:
logger.error("Error while completing task %s: %s",
self.function, err)
error = err
# raise # Uncomment this to inspect errors
ex_type, ex_value, tb = sys.exc_info()
print(ex_type, ex_value)
traceback.print_tb(tb)
GLib.idle_add(lambda: self.on_done(result, error))

View file

@ -18,11 +18,11 @@ def read_button(device):
"""
for event in device.read_loop():
if event.type == evdev.ecodes.EV_KEY and event.value == 0:
print "button %s (%s): %s" % (event.code, hex(event.code), event.value)
print("button %s (%s): %s" % (event.code, hex(event.code), event.value))
if event.type == evdev.ecodes.EV_ABS:
sticks = (0, 1, 3, 4)
if event.code not in sticks or abs(event.value) > 5000:
print "axis %s (%s): %s" % (event.code, hex(event.code), event.value)
print("axis %s (%s): %s" % (event.code, hex(event.code), event.value))
# Unreacheable return statement, to return the even, place a 'break' in the
# for loop

View file

@ -1,21 +1,21 @@
import os
import ConfigParser
import configparser
class SettingsIO(object):
"""ConfigParser abstraction."""
def __init__(self, config_file):
self.config_file = config_file
self.config = ConfigParser.ConfigParser()
self.config = configparser.ConfigParser()
if os.path.exists(self.config_file):
self.config.read([self.config_file])
def read_setting(self, key, section='lutris'):
try:
value = self.config.get(section, key)
except ConfigParser.NoOptionError:
except configparser.NoOptionError:
value = None
except ConfigParser.NoSectionError:
except configparser.NoSectionError:
value = None
return value
@ -24,5 +24,5 @@ class SettingsIO(object):
self.config.add_section(section)
self.config.set(section, key, str(value))
with open(self.config_file, 'wb') as config_file:
with open(self.config_file, 'w') as config_file:
self.config.write(config_file)

View file

@ -16,9 +16,9 @@ class db_cursor(object):
def db_insert(db_path, table, fields):
columns = ", ".join(fields.keys())
columns = ", ".join(list(fields.keys()))
placeholders = ("?, " * len(fields))[:-2]
field_values = _decode_utf8_values(fields.values())
field_values = _decode_utf8_values(list(fields.values()))
with db_cursor(db_path) as cursor:
try:
cursor.execute(
@ -28,8 +28,8 @@ def db_insert(db_path, table, fields):
field_values
)
except sqlite3.IntegrityError:
print columns
print field_values
print(columns)
print(field_values)
raise
inserted_id = cursor.lastrowid
return inserted_id
@ -39,8 +39,8 @@ def db_update(db_path, table, updated_fields, row):
"""Update `table` with the values given in the dict `values` on the
condition given with the `row` tuple.
"""
columns = "=?, ".join(updated_fields.keys()) + "=?"
field_values = _decode_utf8_values(updated_fields.values())
columns = "=?, ".join(list(updated_fields.keys())) + "=?"
field_values = _decode_utf8_values(list(updated_fields.values()))
condition_field = "{0}=?".format(row[0])
condition_value = (row[1], )
with db_cursor(db_path) as cursor:
@ -96,11 +96,13 @@ def db_query(db_path, query, params=()):
def _decode_utf8_values(values_list):
"""Return a tuple of values with UTF-8 string values being decoded."""
"""Return a tuple of values with UTF-8 string values being decoded.
XXX Might be obsolete in Python3 (Removed the decoding part)
"""
i = 0
for v in values_list:
if type(v) is str:
values_list[i] = v.decode('UTF-8')
values_list[i] = v
i += 1
return tuple(values_list)

View file

@ -353,9 +353,16 @@ class AppManifest:
def app_state(self):
return self.appmanifest_data.get('AppState') or {}
@property
def user_config(self):
return self.app_state.get('UserConfig') or {}
@property
def name(self):
return self.app_state.get('name')
_name = self.app_state.get('name')
if not _name:
_name = self.user_config.get('name')
return _name
@property
def slug(self):

View file

@ -8,9 +8,10 @@ def slugify(value):
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
value = value.decode('UTF-8')
value = str(value)
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
value = value.decode('utf-8')
value = str(re.sub('[^\w\s-]', '', value)).strip().lower()
return re.sub('[-\s]+', '-', value)

View file

@ -18,7 +18,7 @@ def execute(command, env=None, cwd=None, log_errors=False):
existing_env = os.environ.copy()
if env:
existing_env.update(env)
logger.debug(' '.join('{}={}'.format(k, v) for k, v in env.iteritems()))
logger.debug(' '.join('{}={}'.format(k, v) for k, v in env.items()))
logger.debug("Executing %s", ' '.join(command))
# Piping stderr can cause slowness in the programs, use carefully
@ -43,7 +43,7 @@ def execute(command, env=None, cwd=None, log_errors=False):
stderr_handler.close()
if stderr and log_errors:
logger.error(stderr)
return stdout.strip()
return stdout.decode().strip()
def get_md5_hash(filename):
@ -54,7 +54,7 @@ def get_md5_hash(filename):
for chunk in iter(lambda: f.read(8192), b''):
md5.update(chunk)
except IOError:
print "Error reading %s" % filename
print("Error reading %s" % filename)
return False
return md5.hexdigest()
@ -103,7 +103,7 @@ def get_command_line(pid):
def python_identifier(string):
if not isinstance(string, basestring):
if not isinstance(string, str):
logger.error("python_identifier requires a string, got %s", string)
return
@ -115,9 +115,9 @@ def python_identifier(string):
def substitute(fileid, files):
fileid = python_identifier(str(fileid))
files = dict((k.replace('-', '_'), v) for k, v in files.items())
files = dict((k.replace('-', '_'), v) for k, v in list(files.items()))
template = string.Template(fileid)
if fileid in files.keys():
if fileid in list(files.keys()):
return files[fileid]
return template.safe_substitute(files)

View file

@ -95,12 +95,12 @@ if __name__ == "__main__":
registry = WineRegistry(os.path.expanduser("~/.wine/user.reg"))
for keyname in registry.keys:
key = registry.keys[keyname]
print key
print(key)
for name in key.values:
print key.show_key(name)
print
print((key.show_key(name)))
print()
steam_key = "Software/Valve/Steam"
print "Querying registry for {}".format(steam_key)
print(("Querying registry for {}".format(steam_key)))
q = registry.query(steam_key, "SteamExe")
print q
print(q)

View file

@ -15,7 +15,7 @@ def get_xdg_games():
if app.get_name().lower() in IGNORED_ENTRIES:
continue
categories = app.get_categories()
if not categories or not 'Game' in categories:
if not categories or 'Game' not in categories:
continue
exe_and_args = app.get_string('Exec').split(' ', 2)
@ -28,7 +28,3 @@ def get_xdg_games():
shell=True).communicate()[0].strip('\n')
xdg_games.append((app.get_display_name(), exe, args))
return xdg_games
if __name__ == '__main__':
print get_xdg_games()

View file

@ -1,53 +1,8 @@
#!/usr/bin/python2
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# 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 <http://www.gnu.org/licenses/>.
#
#!/usr/bin/python3
import os
import sys
from distutils.core import setup
from setuptools import setup
from lutris.settings import VERSION
def update_data_path(prefix, oldvalue=None):
try:
fin = file('lutris/lutrisconfig.py', 'r')
fout = file(fin.name + '.new', 'w')
for line in fin:
fields = line.split(' = ') # Separate variable from value
if fields[0] == '__lutris_data_directory__':
# update to prefix, store oldvalue
if not oldvalue:
oldvalue = fields[1]
line = "%s = '%s'\n" % (fields[0], prefix)
else: # restore oldvalue
line = "%s = %s" % (fields[0], oldvalue)
fout.write(line)
fout.flush()
fout.close()
fin.close()
os.rename(fout.name, fin.name)
except (OSError, IOError):
print ("ERROR: Can't find lutris/lutrisconfig.py")
sys.exit(1)
return oldvalue
data_files = []
for directory, _, filenames in os.walk(u'share'):
@ -70,8 +25,14 @@ setup(
'lutris.installer', 'lutris.migrations'],
scripts=['bin/lutris'],
data_files=data_files,
# FIXME: find a way to install dependencies
# install_requires=['PyYAML', 'PyGObject'],
install_requires=[
'PyYAML',
'PyGObject',
'dbus-python',
'pyxdg',
'pyinotify',
'evdev'
],
url='https://lutris.net',
description='Install and play any video game on Linux',
long_description="""Lutris is a gaming platform for GNU/Linux. It's goal is
@ -79,11 +40,11 @@ setup(
and setting up the game for the user. The only thing you have to do is play
the game. It aims to support every game that is playable on Linux.""",
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GPLv3',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Programming Language :: Python',
'Operating System :: Linux',
'Topic :: Games'
'Topic :: Games/Entertainment'
],
)

View file

@ -2,29 +2,28 @@ import time
import urllib
import sys
import os
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lutris.util import http
from lutris.gui.dialogs import DownloadDialog
TEST_URL = "https://lutris.net/releases/lutris_0.3.5.tar.gz"
TEST_FILE_SIZE = 11034817
#TEST_URL = "ftp://ftp.3drealms.com/share/3dsw12.zip"
#TEST_URL = "ftp://ftp.idsoftware.com/idstuff/wolf/linux/wolf-linux-1.41b.x86.run"
#TEST_URL = "ftp://download.nvidia.com/XFree86/Linux-x86/319.23/NVIDIA-Linux-x86-319.23.run"
#TEST_URL = "smb://newport/games/linux/aquaria/aquaria-lnx-humble-bundle.mojo.run"
TEST_URL = "https://lutris.net/releases/lutris_0.3.0.tar.gz"
TEST_FILE_SIZE = 4582508
Gdk.threads_init()
GObject.threads_init()
def timed(function):
def _wrapped(*args, **kwargs):
print ">",
start_time = time.time()
retval = function(*args, **kwargs)
total = time.time() - start_time
print function.__name__, (TEST_FILE_SIZE / total) / 1048576
print(function.__name__, (TEST_FILE_SIZE / total) / 1048576)
return retval
return _wrapped
@ -41,7 +40,7 @@ def test_download_asset():
class DownloadDialogBenchmark(DownloadDialog):
def download_complete(self, _widget, _data):
print "Complete"
print("Complete")
self.destroy()
Gtk.main_quit()
@ -53,5 +52,5 @@ def test_download_dialog():
test_download_dialog()
#test_urlretrieve()
#test_download_asset()
# test_urlretrieve()
# test_download_asset()

View file

@ -1,4 +1,8 @@
import os
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk
from lutris.game import Game
from lutris.config import check_config

View file

@ -37,7 +37,7 @@ class TestScriptInterpreter(TestCase):
def test_get_command_returns_a_method(self):
interpreter = MockInterpreter({}, None)
command, params = interpreter._map_command({'move': 'whatever'})
self.assertIn("bound method MockInterpreter.move", str(command))
self.assertIn("bound method CommandsMixin.move", str(command))
self.assertEqual(params, "whatever")
def test_get_command_doesnt_return_private_methods(self):

View file

@ -53,7 +53,7 @@ class TestPersonnalGameArchive(DatabaseTester):
pga.add_game(name="installed_game", runner="Linux", installed=1)
pga.add_game(name="bang", runner="Linux", installed=0)
game_list = pga.get_games(filter_installed=True)
print game_list
print(game_list)
self.assertEqual(len(game_list), 1)
self.assertEqual(game_list[0]['name'], 'installed_game')

View file

@ -1,5 +1,5 @@
import logging
from mock import patch
from unittest.mock import patch
from lutris.config import LutrisConfig
from lutris import runners

View file

@ -1,3 +1,4 @@
from collections import OrderedDict
from unittest import TestCase
from lutris.util import system
from lutris.util import steam
@ -34,16 +35,16 @@ class TestFileUtils(TestCase):
class TestSteamUtils(TestCase):
def test_dict_to_vdf(self):
dict_data = {
'AppState': {
'appID': '13240',
'StateFlags': '4',
'UserConfig': {
"name": "Unreal Tournament",
"gameid": "13240"
}
}
}
appstate = OrderedDict()
userconfig = OrderedDict()
userconfig['gameid'] = "13240"
userconfig['name'] = "Unreal Tournament"
appstate['UserConfig'] = userconfig
appstate['StateFlags'] = '4'
appstate['appID'] = '13240'
dict_data = OrderedDict()
dict_data['AppState'] = appstate
expected_vdf = """"AppState"
{
\t"UserConfig"