mirror of
https://github.com/lutris/lutris
synced 2024-10-02 22:14:23 +00:00
Installer rewriting almost complete + Gio mounts handled in Downloader
This commit is contained in:
parent
14a6881627
commit
b3a85220cb
43
bin/lutris
43
bin/lutris
|
@ -46,8 +46,9 @@ except ImportError:
|
|||
|
||||
from lutris.constants import CONFIG_EXTENSION, GAME_CONFIG_PATH
|
||||
from lutris.util.log import logger
|
||||
from lutris.installer import Installer
|
||||
from lutris.installer import InstallerDialog
|
||||
from lutris.config import check_config
|
||||
from lutris.game import LutrisGame
|
||||
from lutris.pga import get_games
|
||||
from lutris.gui.lutriswindow import LutrisWindow
|
||||
|
||||
|
@ -62,11 +63,7 @@ parser.add_option("-i", "--install", dest="installer_file",
|
|||
help="Install a game from a yml file")
|
||||
parser.add_option("-l", "--list-games", action="store_true",
|
||||
help="List all games in database")
|
||||
parser.add_option("-f", "--force-install", action="store_true",
|
||||
help="Force installation even if game is installed")
|
||||
parser.add_option("-n", "--no-download",
|
||||
action="store_true", dest="no_download",
|
||||
help="Don't download anything when installing")
|
||||
parser.add_option("--reinstall", action="store_true", help="Reinstall game")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# Set the logging level to show debug messages.
|
||||
|
@ -91,38 +88,24 @@ check_config(force_wipe=False)
|
|||
|
||||
installer = False
|
||||
game = None
|
||||
if options.installer_file:
|
||||
if not os.path.exists(options.installer_file):
|
||||
logger.error("Unable to find installer at : %s",
|
||||
options.installer_file)
|
||||
exit()
|
||||
else:
|
||||
game, _ext = os.path.splitext(options.installer_file)
|
||||
installer = os.path.abspath(options.installer_file)
|
||||
logger.info("Installing game from %s" % installer)
|
||||
|
||||
|
||||
GObject.threads_init()
|
||||
# Run the application.
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
GObject.threads_init()
|
||||
|
||||
game_slug = []
|
||||
for arg in args:
|
||||
if arg.startswith('lutris:'):
|
||||
game = arg[7:]
|
||||
game_slug = arg[7:]
|
||||
break
|
||||
|
||||
if game:
|
||||
file_path = os.path.join(GAME_CONFIG_PATH, game + CONFIG_EXTENSION)
|
||||
if os.path.exists(file_path) and not options.force_install:
|
||||
import lutris.game
|
||||
logger.info('Launching ' + game)
|
||||
lutris_game = lutris.game.LutrisGame(game)
|
||||
if game_slug or options.installer_file:
|
||||
file_path = os.path.join(GAME_CONFIG_PATH, game_slug + CONFIG_EXTENSION)
|
||||
if os.path.exists(file_path) and not options.reinstall:
|
||||
lutris_game = LutrisGame(game_slug)
|
||||
lutris_game.play()
|
||||
else:
|
||||
logger.debug('Installing %s' % game)
|
||||
installer = Installer(game, installer)
|
||||
InstallerDialog(options.installer_file or game_slug)
|
||||
Gtk.main()
|
||||
else:
|
||||
lutris_window = LutrisWindow()
|
||||
GObject.threads_init()
|
||||
LutrisWindow()
|
||||
Gtk.main()
|
||||
logger.debug("Application exit")
|
||||
|
|
|
@ -6,9 +6,9 @@ Writing installers
|
|||
Displaying an 'Insert disc' dialog
|
||||
----------------------------------
|
||||
|
||||
The ``insert-disc`` command will display a message box to the user requesting
|
||||
The ``insert-disc`` command will display a message box to the user requesting
|
||||
him to insert the game's disc into the optical drive. A link to CDEmu homepage's
|
||||
and PPA will also be displayed if the program isn't detected on the machine,
|
||||
and PPA will also be displayed if the program isn't detected on the machine,
|
||||
otherwise it will be replaced with a button to open gCDEmu.
|
||||
|
||||
An optional parameter ``message`` will override the default text if given.
|
||||
|
@ -17,16 +17,24 @@ An optional parameter ``message`` will override the default text if given.
|
|||
Moving files
|
||||
------------
|
||||
|
||||
Move files by using the ``move`` command. ``move`` requires two parameters:
|
||||
Move files by using the ``move`` command. ``move`` requires two parameters:
|
||||
``src`` and ``dst``.
|
||||
|
||||
The ``src`` parameter can either be a ``file id`` or a relative location. If the
|
||||
parameter value is not found in the list of ``file ids``, then it must be
|
||||
parameter value is not found in the list of ``file ids``, then it must be
|
||||
prefixed by either ``$CACHE`` or ``$GAMEDIR`` to move a file or directory from
|
||||
the download cache or the game installation dir, respectively.
|
||||
|
||||
The ``dst`` parameter should be prefixed by either ``$GAMEDIR`` or ``$HOME``
|
||||
to move files to path relative to the game dir or the current user's home
|
||||
The ``dst`` parameter should be prefixed by either ``$GAMEDIR`` or ``$HOME``
|
||||
to move files to path relative to the game dir or the current user's home
|
||||
directory.
|
||||
|
||||
The ``move`` command cannot overwrite files.
|
||||
|
||||
|
||||
|
||||
Calling the installer
|
||||
=====================
|
||||
|
||||
The installer can be called with the ``lutris:<game-slug>`` url scheme or by
|
||||
specifying the path to an installer script.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
""" Non-blocking Gio Downloader """
|
||||
import time
|
||||
from gi.repository import Gio, GLib, GObject
|
||||
from gi.repository import Gio, GLib, Gtk
|
||||
|
||||
|
||||
class Downloader():
|
||||
|
@ -14,7 +14,6 @@ class Downloader():
|
|||
def __init__(self, url, dest):
|
||||
self.remote = Gio.File.new_for_uri(url)
|
||||
self.local = Gio.File.new_for_path(dest)
|
||||
self.job_cancellable = Gio.Cancellable()
|
||||
self.cancellable = Gio.Cancellable()
|
||||
self.progress = 0
|
||||
self.start_time = None
|
||||
|
@ -29,22 +28,44 @@ class Downloader():
|
|||
|
||||
def cancel(self):
|
||||
self.cancellable.cancel()
|
||||
self.job_cancellable.cancel()
|
||||
self.cancelled = True
|
||||
|
||||
def download(self, job, cancellable, user_data):
|
||||
def download(self, job, cancellable, _data):
|
||||
flags = Gio.FileCopyFlags.OVERWRITE
|
||||
try:
|
||||
self.remote.copy(self.local, flags, self.cancellable,
|
||||
self.progress_callback, None)
|
||||
except GLib.GError:
|
||||
print "Download canceled"
|
||||
self.cancelled = True
|
||||
self.progress_callback, None)
|
||||
except GLib.GError as ex:
|
||||
print "transfer error:", ex.message
|
||||
if ex.code == Gio.IOErrorEnum.TIMED_OUT:
|
||||
# For unknown reasons, FTP transfers times out at 25 seconds
|
||||
# Hint: 25 seconds is the default timeout of GDusProxy
|
||||
# https://developer.gnome.org/gio/2.26/GDBusProxy.html#GDBusProxy--g-default-timeout
|
||||
print "FTP tranfers not supported yet"
|
||||
|
||||
def schedule_download(self, data):
|
||||
def mount_cb(self, fileobj, result, _data):
|
||||
try:
|
||||
mount_success = fileobj.mount_enclosing_volume_finish(result)
|
||||
if mount_success:
|
||||
GLib.idle_add(self.schedule_download)
|
||||
except GLib.GError as ex:
|
||||
if(ex.code != Gio.IOErrorEnum.ALREADY_MOUNTED and
|
||||
ex.code != Gio.IOErrorEnum.NOT_SUPPORTED):
|
||||
print ex.message
|
||||
|
||||
def schedule_download(self):
|
||||
Gio.io_scheduler_push_job(self.download, None,
|
||||
GLib.PRIORITY_HIGH, self.job_cancellable)
|
||||
GLib.PRIORITY_DEFAULT_IDLE,
|
||||
Gio.Cancellable())
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
GLib.idle_add(self.schedule_download, None)
|
||||
if not self.remote.query_exists(Gio.Cancellable()):
|
||||
self.remote.mount_enclosing_volume(Gio.MountMountFlags.NONE,
|
||||
Gtk.MountOperation(),
|
||||
Gio.Cancellable(),
|
||||
self.mount_cb,
|
||||
None)
|
||||
|
||||
else:
|
||||
GLib.idle_add(self.schedule_download)
|
||||
|
|
|
@ -97,10 +97,10 @@ class DownloadDialog(Gtk.Dialog):
|
|||
def __init__(self, url, dest):
|
||||
super(DownloadDialog, self).__init__("Downloading file")
|
||||
self.set_size_request(560, 100)
|
||||
#self.connect('destroy', self.destroy_cb)
|
||||
params = {'url': url, 'dest': dest}
|
||||
self.download_progress_box = DownloadProgressBox(params)
|
||||
self.download_progress_box.connect('complete', self.download_complete)
|
||||
self.download_progress_box.connect('cancelrequested', self.download_cancelled)
|
||||
label = Gtk.Label(label='Downloading %s' % url)
|
||||
label.set_selectable(True)
|
||||
label.set_padding(0, 0)
|
||||
|
@ -110,17 +110,11 @@ class DownloadDialog(Gtk.Dialog):
|
|||
self.show_all()
|
||||
self.download_progress_box.start()
|
||||
|
||||
def destroy_cb(self, widget, data=None):
|
||||
"""Action triggered when window is closed"""
|
||||
self.destroy()
|
||||
|
||||
def download_complete(self, _widget, _data):
|
||||
self.destroy()
|
||||
|
||||
def download_cancel(self, _widget, _data=None):
|
||||
"""Action triggered when download is cancelled"""
|
||||
#self.download_progress_box.cancel()
|
||||
pass
|
||||
def download_cancelled(self, _widget, data):
|
||||
self.destroy()
|
||||
|
||||
|
||||
class PgaSourceDialog(GtkBuilderDialog):
|
||||
|
|
|
@ -420,6 +420,7 @@ class DownloadProgressBox(Gtk.HBox):
|
|||
if self.downloader.cancelled:
|
||||
self.progressbar.set_fraction(0)
|
||||
self.progress_label.set_text("Download canceled")
|
||||
self.emit('cancelrequested', {})
|
||||
return False
|
||||
self.progressbar.set_fraction(progress)
|
||||
megabytes = 1024 * 1024
|
||||
|
|
|
@ -1,25 +1,8 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding:Utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2010 Mathieu Comandon <strider@strycore.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# pylint: disable=E1101, E0611
|
||||
"""Installer module"""
|
||||
import os
|
||||
import yaml
|
||||
import shutil
|
||||
import urllib
|
||||
import urllib2
|
||||
import platform
|
||||
import subprocess
|
||||
|
@ -30,56 +13,205 @@ from os.path import join, exists
|
|||
|
||||
from lutris import pga
|
||||
from lutris.util.log import logger
|
||||
from lutris.util import http
|
||||
from lutris.util.strings import slugify
|
||||
from lutris.util.files import calculate_md5
|
||||
from lutris.util import extract
|
||||
from lutris.game import LutrisGame
|
||||
#from lutris.config import LutrisConfig
|
||||
from lutris.gui.dialogs import ErrorDialog, FileDialog
|
||||
from lutris.gui.dialogs import FileDialog
|
||||
from lutris.gui.widgets import DownloadProgressBox, FileChooserEntry
|
||||
from lutris.shortcuts import create_launcher
|
||||
from lutris import settings
|
||||
from lutris.runners import import_task
|
||||
|
||||
|
||||
def run_installer(filename):
|
||||
"""Run an installer of .sh or .run type"""
|
||||
subprocess.call(["chmod", "+x", filename])
|
||||
subprocess.call([filename])
|
||||
|
||||
|
||||
def reporthook(piece, received_bytes, total_size):
|
||||
"""Follows the progress of a download"""
|
||||
print("%d %%" % ((piece * received_bytes) * 100 / total_size))
|
||||
|
||||
|
||||
class ScriptingError(Exception):
|
||||
""" Custom exception for scripting errors, can be caught by modifying
|
||||
excepthook """
|
||||
def __init__(self, message, faulty_data=None):
|
||||
self.message = message
|
||||
self.faulty_data = faulty_data
|
||||
logger.error(self.message + repr(self.faulty_data))
|
||||
super(ScriptingError, self).__init__()
|
||||
|
||||
def __str__(self):
|
||||
return self.message + "\n" + repr(self.faulty_data)
|
||||
|
||||
|
||||
class ScriptInterpreter(object):
|
||||
""" Class that converts raw script data to actions """
|
||||
game_name = None
|
||||
game_slug = None
|
||||
game_files = {}
|
||||
target_path = None
|
||||
errors = []
|
||||
|
||||
def __init__(self, script):
|
||||
self.script = yaml.safe_load(script)
|
||||
def __init__(self, game_ref, parent):
|
||||
self.parent = parent
|
||||
self.script = self._fetch_script(game_ref)
|
||||
if not self.is_valid():
|
||||
raise ScriptingError("Invalid script", self.script)
|
||||
self.game_name = self.script.get('name')
|
||||
self.game_slug = slugify(self.game_name)
|
||||
self.target_path = self.default_target
|
||||
|
||||
@property
|
||||
def default_target(self):
|
||||
#games_dir = self.lutris_config.get_path()
|
||||
return join(os.path.expanduser('~'), self.game_slug)
|
||||
|
||||
def _fetch_script(self, game_ref):
|
||||
if os.path.exists(game_ref):
|
||||
script_contents = open(game_ref, 'r').read()
|
||||
else:
|
||||
full_url = settings.INSTALLER_URL + game_ref + '.yml'
|
||||
request = urllib2.Request(url=full_url)
|
||||
try:
|
||||
request = urllib2.urlopen(request)
|
||||
script_contents = request.read()
|
||||
except IOError:
|
||||
raise ScriptingError("Server unreachable", full_url)
|
||||
return yaml.safe_load(script_contents)
|
||||
|
||||
def is_valid(self):
|
||||
""" Return True if script is usable """
|
||||
required_fields = ('runner', 'name', 'installer')
|
||||
for field in required_fields:
|
||||
if not self.script.get(field):
|
||||
self.errors.append("Missing field '%s'" % field)
|
||||
return not bool(self.errors)
|
||||
|
||||
def run(self):
|
||||
""" Launch the install process """
|
||||
if not os.path.exists(self.target_path):
|
||||
os.makedirs(self.target_path)
|
||||
else:
|
||||
raise ScriptingError("Target path already exists",
|
||||
self.target_path)
|
||||
self.iter_game_files()
|
||||
|
||||
def iter_game_files(self):
|
||||
dest_dir = join(settings.CACHE_DIR, "installer/%s" % self.game_slug)
|
||||
if not exists(dest_dir):
|
||||
logger.debug('Creating destination directory %s' % dest_dir)
|
||||
os.mkdir(dest_dir)
|
||||
|
||||
files = self.script.get('files', [])
|
||||
if len(self.game_files) < len(files):
|
||||
logger.info(
|
||||
"Downloading file %d of %d",
|
||||
len(self.game_files) + 1, len(self.script["files"])
|
||||
)
|
||||
self._download_file(self.script["files"][len(self.game_files)])
|
||||
else:
|
||||
self._iter_commands()
|
||||
|
||||
def _download_file(self, game_file):
|
||||
"""Download a file referenced in the installer script
|
||||
|
||||
Game files can be either a string, containing the location of the
|
||||
file to fetch or a dict with the following keys:
|
||||
- url : location of file, if not present, filename will be used
|
||||
this should be the case for local files
|
||||
- filename : force destination filename when url is present or path
|
||||
of local file
|
||||
"""
|
||||
|
||||
# Setup file_id, file_uri and local filename
|
||||
file_id = game_file.keys()[0]
|
||||
if isinstance(game_file[file_id], dict):
|
||||
filename = game_file[file_id]['filename']
|
||||
file_uri = game_file[file_id]['url']
|
||||
if file_uri.startswith("/"):
|
||||
file_uri = "file://" + file_uri
|
||||
else:
|
||||
file_uri = game_file[file_id]
|
||||
filename = os.path.basename(file_uri)
|
||||
logger.debug("Fetching [%s]: %s" % (file_id, file_uri))
|
||||
|
||||
# Check for file availability in PGA
|
||||
pga_uri = pga.check_for_file(self.game_slug, file_id)
|
||||
if pga_uri:
|
||||
file_uri = pga_uri
|
||||
|
||||
# Setup destination path
|
||||
dest_dir = join(settings.CACHE_DIR, "installer/%s" % self.game_slug)
|
||||
dest_file = os.path.join(dest_dir, filename)
|
||||
if os.path.exists(dest_file):
|
||||
logger.debug("Destination file exists")
|
||||
if settings.KEEP_CACHED_ASSETS:
|
||||
# Fast !
|
||||
self.game_files[file_id] = dest_file
|
||||
self.iter_game_files()
|
||||
return
|
||||
else:
|
||||
os.remove(dest_file)
|
||||
|
||||
if file_uri == "N/A":
|
||||
#Ask the user where is located the file
|
||||
file_uri = self.parent.ask_user_for_file()
|
||||
|
||||
# Change parent's status
|
||||
self.parent.set_status('Fetching %s' % file_uri)
|
||||
self.game_files[file_id] = dest_file
|
||||
self.parent.start_download(file_uri, dest_file)
|
||||
|
||||
def _iter_commands(self):
|
||||
os.chdir(self.target_path)
|
||||
for command in self.script['installer']:
|
||||
method, params = self._map_command(command)
|
||||
method(self, params)
|
||||
self.parent.set_status("Writing configuration")
|
||||
self._write_config()
|
||||
self._cleanup()
|
||||
self.parent.set_status("Installation finished !")
|
||||
self.parent.on_install_finished()
|
||||
|
||||
def _cleanup(self):
|
||||
print "To delete:"
|
||||
for game_file in self.game_files:
|
||||
print game_file
|
||||
|
||||
def _write_config(self):
|
||||
"""Write the game configuration as a Lutris launcher."""
|
||||
config_filename = join(settings.CONFIG_DIR,
|
||||
"games/%s.yml" % self.game_slug)
|
||||
config_data = {
|
||||
'game': {},
|
||||
'realname': self.script['name'],
|
||||
'runner': self.script['runner']
|
||||
}
|
||||
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']:
|
||||
if launcher in self.script:
|
||||
if launcher == "exe64":
|
||||
key = "exe"
|
||||
else:
|
||||
key = launcher
|
||||
game_resource = self.script[launcher]
|
||||
if type(game_resource) == list:
|
||||
resource_paths = []
|
||||
for res in game_resource:
|
||||
if res in self.game_files:
|
||||
resource_paths.append(self.game_files[res])
|
||||
else:
|
||||
resource_paths.append(res)
|
||||
config_data['game'][key] = resource_paths
|
||||
else:
|
||||
if game_resource in self.game_files:
|
||||
game_resource = self.game_files[game_resource]
|
||||
else:
|
||||
game_resource = join(self.target_path, game_resource)
|
||||
config_data['game'][key] = game_resource
|
||||
|
||||
yaml_config = yaml.safe_dump(config_data, default_flow_style=False)
|
||||
with open(config_filename, "w") as config_file:
|
||||
config_file.write(yaml_config)
|
||||
|
||||
@classmethod
|
||||
def _map_command(cls, command_data):
|
||||
""" Converts a line from the installer directive an internal method """
|
||||
if isinstance(command_data, dict):
|
||||
command_name = command_data.keys()[0]
|
||||
command_params = command_data[command_name]
|
||||
|
@ -94,19 +226,18 @@ class ScriptInterpreter(object):
|
|||
return getattr(cls, command_name), command_params
|
||||
|
||||
def _substitute(self, path_ref, path_type):
|
||||
""" Replace path aliases with real paths """
|
||||
if not path_ref.startswith("$%s" % path_type):
|
||||
return
|
||||
if path_type == "GAMEDIR":
|
||||
if not self.gamedir:
|
||||
raise ValueError("No gamedir set")
|
||||
else:
|
||||
return path_ref.replace("$GAMEDIR", self.gamedir)
|
||||
return path_ref.replace("$GAMEDIR", self.target_path)
|
||||
if path_type == "CACHE":
|
||||
return path_ref.replace("$CACHE", settings.DATA_DIR)
|
||||
if path_type == "HOME":
|
||||
return path_ref.replace("$HOME", os.path.expanduser("~"))
|
||||
|
||||
def _get_move_paths(self, params):
|
||||
""" Validate and converts raw data passed to 'move' """
|
||||
for required_param in ('dst', 'src'):
|
||||
if required_param not in params:
|
||||
raise ScriptingError(
|
||||
|
@ -114,7 +245,7 @@ class ScriptInterpreter(object):
|
|||
% required_param, params
|
||||
)
|
||||
src_ref = params['src']
|
||||
src = (self.files.get(src_ref)
|
||||
src = (self.game_files.get(src_ref)
|
||||
or self._substitute(src_ref, 'CACHE')
|
||||
or self._substitute(src_ref, 'GAMEDIR'))
|
||||
if not src:
|
||||
|
@ -126,7 +257,14 @@ class ScriptInterpreter(object):
|
|||
raise ScriptingError("Wrong value for 'dst' param", dst_ref)
|
||||
return (src, dst)
|
||||
|
||||
def check_md5(self, data):
|
||||
filename = self.game_files.get(data['file'])
|
||||
_hash = calculate_md5(filename)
|
||||
if _hash != data['value']:
|
||||
raise ScriptingError("MD5 checksum mismatch", data)
|
||||
|
||||
def move(self, params):
|
||||
""" Move a file or directory """
|
||||
src, dst = self._get_move_paths(params)
|
||||
if not os.path.exists(src):
|
||||
self.errors.append("I can't move %s, it does not exist" % src)
|
||||
|
@ -143,7 +281,7 @@ class ScriptInterpreter(object):
|
|||
|
||||
def extract(self, data):
|
||||
""" Extracts a file, guessing the compression method """
|
||||
filename = self.files.get(data.get('file'))
|
||||
filename = self.game_files.get(data['file'])
|
||||
if not filename:
|
||||
logger.error("No file for '%s' in game files" % data)
|
||||
return False
|
||||
|
@ -152,10 +290,10 @@ class ScriptInterpreter(object):
|
|||
return False
|
||||
msg = "Extracting %s" % filename
|
||||
logger.debug(msg)
|
||||
self.set_status(msg)
|
||||
self.parent.set_status(msg)
|
||||
_, extension = os.path.splitext(filename)
|
||||
if extension == ".zip":
|
||||
extract.unzip(filename, self.game_dir)
|
||||
extract.unzip(filename, self.target_path)
|
||||
elif filename.endswith('.tgz') or filename.endswith('.tar.gz'):
|
||||
extract.untar(filename, None)
|
||||
elif filename.endswith('.tar.bz2'):
|
||||
|
@ -164,85 +302,60 @@ class ScriptInterpreter(object):
|
|||
logger.error("unrecognised file extension %s" % extension)
|
||||
return False
|
||||
|
||||
def _delete(self, data):
|
||||
print "Script has requested to delete %s" % data
|
||||
|
||||
|
||||
# pylint: disable=R0904
|
||||
class Installer(Gtk.Dialog):
|
||||
class InstallerDialog(Gtk.Dialog):
|
||||
""" Gtk Dialog used during the install process """
|
||||
game_dir = None
|
||||
download_progress = None
|
||||
|
||||
"""Installer class"""
|
||||
def __init__(self, game, installer=False):
|
||||
super(Installer, self).__init__()
|
||||
self.set_size_request(500, 400)
|
||||
self.lutris_config = None # Internal game config
|
||||
if not game:
|
||||
msg = "No game specified in this installer"
|
||||
logger.error(msg)
|
||||
ErrorDialog(msg)
|
||||
return
|
||||
self.game = game
|
||||
self.game_slug = slugify(self.game)
|
||||
self.description = False
|
||||
self.download_index = 0
|
||||
self.rules = {} # Content of yaml file
|
||||
self.actions = []
|
||||
self.errors = []
|
||||
# Essential game information to create Lutris launcher
|
||||
self.game_info = {}
|
||||
# Dictionary of the files needed to install the game
|
||||
self.gamefiles = {}
|
||||
# # Fetch assets
|
||||
# banner_url = settings.INSTALLER_URL + '%s.jpg' % self.game_slug
|
||||
# banner_dest = join(settings.DATA_DIR, "banners/%s.jpg" % self.game_slug)
|
||||
# http.download_asset(banner_url, banner_dest, True)
|
||||
# icon_url = settings.INSTALLER_URL + 'icon/%s.jpg' % self.game_slug
|
||||
# icon_dest = join(settings.DATA_DIR, "icons/%s.png" % self.game_slug)
|
||||
# http.download_asset(icon_url, icon_dest, True)
|
||||
|
||||
if installer is False:
|
||||
self.installer_path = join(settings.CACHE_DIR, self.game + ".yml")
|
||||
else:
|
||||
self.installer_path = installer
|
||||
def __init__(self, game_ref):
|
||||
Gtk.Dialog.__init__(self)
|
||||
|
||||
# Install location
|
||||
# Dialog properties
|
||||
self.set_size_request(600, 480)
|
||||
self.set_default_size(600, 480)
|
||||
self.set_resizable(False)
|
||||
|
||||
# Default signals
|
||||
self.connect('destroy', lambda q: Gtk.main_quit())
|
||||
|
||||
# Interpreter
|
||||
self.interpreter = ScriptInterpreter(game_ref, self)
|
||||
self.interpreter.is_valid()
|
||||
|
||||
## GUI Setup
|
||||
|
||||
# Top label
|
||||
self.status_label = Gtk.Label()
|
||||
self.status_label.set_markup('<b>Select installation directory:</b>')
|
||||
self.status_label.set_alignment(0, 0)
|
||||
self.status_label.set_padding(20, 0)
|
||||
self.vbox.pack_start(self.status_label, True, True, 2)
|
||||
self.vbox.add(self.status_label)
|
||||
|
||||
success = self.pre_install()
|
||||
self.location_entry = FileChooserEntry(default=self.game_dir)
|
||||
self.location_entry.entry.connect('changed', self.set_game_dir)
|
||||
self.vbox.add(self.location_entry)
|
||||
if not success:
|
||||
logger.error("Unable to install game")
|
||||
else:
|
||||
logger.info("Ready! Launching installer.")
|
||||
|
||||
self.download_progress = None
|
||||
self.set_default_size(600, 480)
|
||||
self.set_resizable(False)
|
||||
self.connect('destroy', lambda q: Gtk.main_quit())
|
||||
|
||||
banner_path = join(settings.CACHE_DIR,
|
||||
"banners/%s.jpg" % self.game_slug)
|
||||
if os.path.exists(banner_path):
|
||||
banner = Gtk.Image()
|
||||
banner.set_from_file(banner_path)
|
||||
self.vbox.pack_start(banner, False, False)
|
||||
|
||||
if self.description:
|
||||
description = Gtk.Label()
|
||||
description.set_markup(self.description)
|
||||
description.set_padding(20, 20)
|
||||
self.vbox.pack_start(description, True, True)
|
||||
# Target chooser
|
||||
default_path = self.interpreter.default_target
|
||||
location_entry = FileChooserEntry(default=default_path)
|
||||
location_entry.entry.connect('changed', self.on_target_changed)
|
||||
self.vbox.add(location_entry)
|
||||
|
||||
self.widget_box = Gtk.HBox()
|
||||
self.vbox.pack_start(self.widget_box, True, True, 0)
|
||||
|
||||
separator = Gtk.HSeparator()
|
||||
self.vbox.pack_start(separator, True, True, 10)
|
||||
# Separator
|
||||
self.vbox.pack_start(Gtk.HSeparator(), True, True, 10)
|
||||
|
||||
# Install button
|
||||
self.install_button = Gtk.Button('Install')
|
||||
self.install_button.connect('clicked', self.download_game_files)
|
||||
#self.install_button.set_sensitive(False)
|
||||
self.install_button.connect('clicked', self.on_install_clicked)
|
||||
|
||||
self.action_buttons = Gtk.Alignment.new(0.95, 0.1, 0.15, 0)
|
||||
self.action_buttons.add(self.install_button)
|
||||
|
@ -250,205 +363,38 @@ class Installer(Gtk.Dialog):
|
|||
self.vbox.pack_start(self.action_buttons, False, False, 0)
|
||||
self.show_all()
|
||||
|
||||
def display_errors(self):
|
||||
full_message = "\n\n".join(self.errors)
|
||||
ErrorDialog(full_message)
|
||||
def on_target_changed(self, text_entry):
|
||||
""" Sets the installation target for the game """
|
||||
self.interpreter.target_path = text_entry.get_text()
|
||||
|
||||
def download_installer(self):
|
||||
""" Save the downloaded installer to disk. """
|
||||
def on_install_clicked(self, button):
|
||||
self.interpreter.run()
|
||||
|
||||
full_url = settings.INSTALLER_URL + self.game_slug + '.yml'
|
||||
request = urllib2.Request(url=full_url)
|
||||
try:
|
||||
urllib2.urlopen(request)
|
||||
except urllib2.URLError:
|
||||
error_msg = "Server is unreachable at %s", full_url
|
||||
logger.error(error_msg)
|
||||
self.errors.append(error_msg)
|
||||
success = False
|
||||
else:
|
||||
logger.debug("Downloading installer: %s" % full_url)
|
||||
urllib.urlretrieve(full_url, self.installer_path)
|
||||
success = True
|
||||
return success
|
||||
def ask_user_for_file(self):
|
||||
dlg = FileDialog()
|
||||
return dlg.get_uri()
|
||||
|
||||
def pre_install(self):
|
||||
"""
|
||||
Reads the installer and checks everything is OK before beginning
|
||||
the install process.
|
||||
"""
|
||||
# Fetch assets
|
||||
banner_url = settings.INSTALLER_URL + '%s.jpg' % self.game_slug
|
||||
banner_dest = join(settings.DATA_DIR, "banners/%s.jpg" % self.game_slug)
|
||||
http.download_asset(banner_url, banner_dest, True)
|
||||
icon_url = settings.INSTALLER_URL + 'icon/%s.jpg' % self.game_slug
|
||||
icon_dest = join(settings.DATA_DIR, "icons/%s.png" % self.game_slug)
|
||||
http.download_asset(icon_url, icon_dest, True)
|
||||
def set_status(self, text):
|
||||
self.status_label.set_text(text)
|
||||
|
||||
# Download installer if not already there.
|
||||
success = self.download_installer()
|
||||
if not success:
|
||||
self.display_errors()
|
||||
return False
|
||||
|
||||
# Parse installer file
|
||||
script_data = file(self.installer_path, 'r').read()
|
||||
self.interpreter = ScriptInterpreter(script_data)
|
||||
if not self.interpreter.is_valid():
|
||||
raise ScriptingError("Installation script contains errors",
|
||||
self.interpreter.errors)
|
||||
success = self.parse_config()
|
||||
games_dir = self.lutris_config.get_path()
|
||||
|
||||
if not games_dir:
|
||||
logger.debug("No default path for %s games" % self.rules['runner'])
|
||||
default_path = join(os.path.expanduser('~'), self.game_slug)
|
||||
logger.debug("default path set to %s " % default_path)
|
||||
self.game_dir = default_path
|
||||
return True
|
||||
|
||||
self.game_dir = join(games_dir, self.game_slug)
|
||||
logger.debug("Setting default path to : %s", self.game_dir)
|
||||
if not os.path.exists(self.game_dir):
|
||||
os.mkdir(self.game_dir)
|
||||
return success
|
||||
|
||||
def set_game_dir(self, widget):
|
||||
self.game_dir = widget.get_text()
|
||||
|
||||
def download_game_files(self, _widget=None, _data=None):
|
||||
""" Runs the actions to complete the install. """
|
||||
|
||||
dest_dir = join(settings.CACHE_DIR, "installer/%s" % self.game_slug)
|
||||
if not exists(dest_dir):
|
||||
logger.debug('Creating destination directory %s' % dest_dir)
|
||||
os.mkdir(dest_dir)
|
||||
self.location_entry.destroy()
|
||||
self.install_button.set_sensitive(False)
|
||||
self.process_downloads()
|
||||
|
||||
def process_downloads(self):
|
||||
"""Download each file needed for the game"""
|
||||
files = self.rules.get('files', [])
|
||||
if self.download_index < len(files):
|
||||
logger.info(
|
||||
"Downloading file %d of %d",
|
||||
self.download_index + 1, len(self.rules["files"])
|
||||
)
|
||||
self.download_game_file(self.rules["files"][self.download_index])
|
||||
else:
|
||||
logger.info("All files downloaded")
|
||||
self.install()
|
||||
def start_download(self, file_uri, dest_file):
|
||||
if self.download_progress:
|
||||
# Remove existing progress bar
|
||||
self.download_progress.destroy()
|
||||
self.download_progress = DownloadProgressBox(
|
||||
{'url': file_uri, 'dest': dest_file}, cancelable=True
|
||||
)
|
||||
self.download_progress.connect('complete', self.download_complete)
|
||||
self.widget_box.pack_start(self.download_progress, True, True, 10)
|
||||
self.download_progress.show()
|
||||
self.download_progress.start()
|
||||
|
||||
def download_complete(self, widget=None, data=None):
|
||||
"""Action called on a completed download"""
|
||||
self.download_index += 1
|
||||
self.process_downloads()
|
||||
self.interpreter.iter_game_files()
|
||||
|
||||
def download_game_file(self, game_file):
|
||||
"""Download a file referenced in the installer script
|
||||
|
||||
Game files can be either a string, containing the location of the
|
||||
file to fetch or a dict with the possible options :
|
||||
- url : location of file, if not present, filename will be used
|
||||
this should be the case for local files
|
||||
- filename : force destination filename when url is present or path
|
||||
of local file
|
||||
|
||||
return the path of local file
|
||||
"""
|
||||
file_id = game_file.keys()[0]
|
||||
if isinstance(game_file[file_id], dict):
|
||||
filename = game_file[file_id].get('filename')
|
||||
url = game_file[file_id].get('url', filename)
|
||||
else:
|
||||
url = game_file[file_id]
|
||||
filename = None
|
||||
|
||||
logger.debug("Fetching [%s]: %s" % (file_id, url))
|
||||
pga_url = pga.check_for_file(self.game_slug, file_id)
|
||||
if pga_url:
|
||||
url = pga_url
|
||||
|
||||
self.status_label.set_text('Fetching %s' % url)
|
||||
dest_dir = join(settings.CACHE_DIR, "installer/%s" % self.game_slug)
|
||||
if not filename:
|
||||
filename = os.path.basename(url)
|
||||
dest_file = os.path.join(dest_dir, filename)
|
||||
if os.path.exists(dest_file):
|
||||
logger.debug("Destination file exists")
|
||||
os.remove(dest_file)
|
||||
if url == "N/A":
|
||||
if not filename:
|
||||
#Ask the user where is located the file
|
||||
dlg = FileDialog()
|
||||
url = dlg.filename
|
||||
if not filename:
|
||||
self.errors.append("Installation cancelled")
|
||||
return False
|
||||
self.gamefiles[file_id] = dest_file
|
||||
if url.startswith('/'):
|
||||
shutil.copy(url, dest_dir)
|
||||
dest_file = os.path.join(dest_dir, os.path.basename(url))
|
||||
self.download_complete(data=os.path.join(
|
||||
dest_dir, os.path.basename(filename)
|
||||
))
|
||||
elif url.startswith("http"):
|
||||
if self.download_progress:
|
||||
# Remove existing progress bar
|
||||
self.download_progress.destroy()
|
||||
self.download_progress = DownloadProgressBox(
|
||||
{'url': url, 'dest': dest_file}, cancelable=True
|
||||
)
|
||||
self.download_progress.connect('complete', self.download_complete)
|
||||
self.widget_box.pack_start(self.download_progress, True, True, 10)
|
||||
self.download_progress.show()
|
||||
self.download_progress.start()
|
||||
|
||||
def install(self):
|
||||
def on_install_finished(self):
|
||||
"""Actual game installation"""
|
||||
logger.debug("Running installation")
|
||||
|
||||
if not os.path.exists(self.game_dir):
|
||||
os.makedirs(self.game_dir)
|
||||
os.chdir(self.game_dir)
|
||||
|
||||
for action in self.actions:
|
||||
print action
|
||||
if isinstance(action, dict):
|
||||
action_name = action.keys()[0]
|
||||
action_data = action[action_name]
|
||||
else:
|
||||
action_name = action
|
||||
action_data = None
|
||||
mappings = {
|
||||
'insert-disc': self._insert_disc,
|
||||
'extract': self._extract,
|
||||
'move': self._move,
|
||||
'run': self._run,
|
||||
'runner': self._runner_task,
|
||||
'check_md5': self._check_md5,
|
||||
'delete': self._delete,
|
||||
}
|
||||
if not hasattr(ScriptInterpreter, action_name):
|
||||
raise ScriptingError("The command %s does not exists"
|
||||
% action_name)
|
||||
if action_name not in mappings.keys():
|
||||
logger.error("Action " + action_name + " not supported !")
|
||||
continue
|
||||
mappings[action_name](action_data)
|
||||
if self.errors:
|
||||
self.status_label.set_text("Installation error")
|
||||
error_label = Gtk.Label()
|
||||
error_label.set_line_wrap(True)
|
||||
error_label.set_selectable(True)
|
||||
error_label.set_markup("\n".join(self.errors))
|
||||
error_label.show()
|
||||
self.widget_box.pack_start(error_label, True, True, 20)
|
||||
return False
|
||||
self.status_label.set_text("Writing configuration")
|
||||
self.write_config()
|
||||
|
||||
self.status_label.set_text("Installation finished !")
|
||||
|
||||
desktop_btn = Gtk.Button('Create a desktop shortcut')
|
||||
|
@ -474,86 +420,22 @@ class Installer(Gtk.Dialog):
|
|||
play_button.connect('clicked', self.launch_game)
|
||||
self.action_buttons.add(play_button)
|
||||
|
||||
def write_config(self):
|
||||
"""Write the game configuration as a Lutris launcher."""
|
||||
config_filename = join(settings.CONFIG_DIR,
|
||||
"games/%s.yml" % self.game_slug)
|
||||
config_data = {
|
||||
'game': {},
|
||||
'realname': self.game_info['name'],
|
||||
'runner': self.game_info['runner']
|
||||
}
|
||||
is_64bit = platform.machine() == "x86_64"
|
||||
exe = 'exe64' if 'exe64' in self.game_info and is_64bit else 'exe'
|
||||
for launcher in [exe, 'iso', 'rom', 'disk']:
|
||||
if launcher in self.game_info:
|
||||
if launcher == "exe64":
|
||||
key = "exe"
|
||||
else:
|
||||
key = launcher
|
||||
game_resource = self.game_info[launcher]
|
||||
if type(game_resource) == list:
|
||||
resource_paths = []
|
||||
for res in game_resource:
|
||||
if res in self.gamefiles:
|
||||
resource_paths.append(self.gamefiles[res])
|
||||
else:
|
||||
resource_paths.append(res)
|
||||
config_data['game'][key] = resource_paths
|
||||
else:
|
||||
if game_resource in self.gamefiles:
|
||||
game_resource = self.gamefiles[game_resource]
|
||||
else:
|
||||
game_resource = join(self.game_dir, game_resource)
|
||||
config_data['game'][key] = game_resource
|
||||
|
||||
yaml_config = yaml.safe_dump(config_data, default_flow_style=False)
|
||||
with open(config_filename, "w") as config_file:
|
||||
config_file.write(yaml_config)
|
||||
|
||||
def _get_path(self, data):
|
||||
"""Return a filesystem path based on data"""
|
||||
if data == 'parent':
|
||||
path = os.path.dirname(self.game_dir)
|
||||
else:
|
||||
path = self.game_dir
|
||||
return path
|
||||
|
||||
def _check_md5(self, data):
|
||||
return True
|
||||
print "MD5"
|
||||
calculate_md5(self.gamefiles.get(data))
|
||||
print self.gamefiles
|
||||
print data
|
||||
|
||||
def _run(self, executable):
|
||||
"""Run an executable script"""
|
||||
exec_path = os.path.join(settings.CACHE_DIR, self.game_slug,
|
||||
self.gamefiles[executable])
|
||||
if not os.path.exists(exec_path):
|
||||
print("unable to find %s" % exec_path)
|
||||
exit()
|
||||
else:
|
||||
os.popen('chmod +x %s' % exec_path)
|
||||
subprocess.call([exec_path])
|
||||
|
||||
def _runner_task(self, data):
|
||||
""" This action triggers a task within a runner.
|
||||
Mandatory parameters in data are 'task' and 'args'
|
||||
"""
|
||||
|
||||
logger.info("Called runner task")
|
||||
logger.debug(data)
|
||||
logger.debug("runner is %s", self.rules['runner'])
|
||||
runner_name = self.rules["runner"]
|
||||
logger.debug("runner is %s", self.script['runner'])
|
||||
runner_name = self.script["runner"]
|
||||
task = import_task(runner_name, data['task'])
|
||||
args = data['args']
|
||||
for key in args:
|
||||
if args[key] in ("$GAME_DIR", "$GAMEDIR"):
|
||||
args[key] = self.game_dir
|
||||
if key == 'filename':
|
||||
if args[key] in self.gamefiles.keys():
|
||||
args[key] = self.gamefiles[args[key]]
|
||||
if args[key] in self.game_files.keys():
|
||||
args[key] = self.game_files[args[key]]
|
||||
logger.debug("args are %s", repr(args))
|
||||
# FIXME pass args as kwargs and not args
|
||||
task(**args)
|
||||
|
|
|
@ -18,26 +18,15 @@
|
|||
"""Personnal Game Archive module. Handle local database of user's games."""
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from lutris.util.strings import slugify
|
||||
from lutris.util.log import logger
|
||||
from lutris.util import sql
|
||||
from lutris import settings
|
||||
|
||||
PGA_DB = settings.PGA_DB
|
||||
|
||||
|
||||
class db_cursor():
|
||||
def __enter__(self):
|
||||
self.db_conn = sqlite3.connect(PGA_DB)
|
||||
cursor = self.db_conn.cursor()
|
||||
return cursor
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.db_conn.commit()
|
||||
self.db_conn.close()
|
||||
|
||||
|
||||
def create_games(cursor):
|
||||
create_game_table_query = """CREATE TABLE games (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
@ -62,33 +51,14 @@ def create_sources(cursor):
|
|||
def create():
|
||||
"""Create the local PGA database."""
|
||||
logger.debug("Running CREATE statement...")
|
||||
with db_cursor() as cursor:
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
create_games(cursor)
|
||||
create_sources(cursor)
|
||||
|
||||
|
||||
def db_insert(table, fields):
|
||||
field_names = ", ".join(fields.keys())
|
||||
placeholders = ("?, " * len(fields))[:-2]
|
||||
field_values = tuple(fields.values())
|
||||
with db_cursor() as cursor:
|
||||
cursor.execute(
|
||||
"insert into {0}({1}) values ({2})".format(table,
|
||||
field_names,
|
||||
placeholders),
|
||||
field_values
|
||||
)
|
||||
|
||||
|
||||
def db_delete(table, field, value):
|
||||
with db_cursor() as cursor:
|
||||
cursor.execute("delete from {0} where {1}=?".format(table, field),
|
||||
(value,))
|
||||
|
||||
|
||||
def get_games(name_filter=None):
|
||||
"""Get the list of every game in database."""
|
||||
with db_cursor() as cursor:
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
if name_filter is not None:
|
||||
query = "select * from games where name LIKE ?"
|
||||
rows = cursor.execute(query, (name_filter, ))
|
||||
|
@ -110,24 +80,24 @@ def add_game(name, runner, slug=None):
|
|||
"""Adds a game to the PGA database."""
|
||||
if not slug:
|
||||
slug = slugify(name)
|
||||
db_insert("games", {'name': name, 'slug': slug, 'runner': runner})
|
||||
sql.db_insert("games", {'name': name, 'slug': slug, 'runner': runner})
|
||||
|
||||
|
||||
def delete_game(name):
|
||||
"""Deletes a game from the PGA"""
|
||||
db_delete("games", 'name', name)
|
||||
sql.db_delete(PGA_DB, "games", 'name', name)
|
||||
|
||||
|
||||
def add_source(uri):
|
||||
db_insert("sources", {"uri": uri})
|
||||
sql.db_insert(PGA_DB, "sources", {"uri": uri})
|
||||
|
||||
|
||||
def delete_source(uri):
|
||||
db_delete("sources", 'uri', uri)
|
||||
sql.db_delete(PGA_DB, "sources", 'uri', uri)
|
||||
|
||||
|
||||
def read_sources():
|
||||
with db_cursor() as cursor:
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
rows = cursor.execute("select uri from sources")
|
||||
results = rows.fetchall()
|
||||
return [row[0] for row in results]
|
||||
|
@ -137,10 +107,10 @@ def write_sources(sources):
|
|||
db_sources = read_sources()
|
||||
for uri in db_sources:
|
||||
if uri not in sources:
|
||||
db_delete("sources", 'uri', uri)
|
||||
sql.db_delete(PGA_DB, "sources", 'uri', uri)
|
||||
for uri in sources:
|
||||
if uri not in db_sources:
|
||||
db_insert("sources", {'uri': uri})
|
||||
sql.db_insert(PGA_DB, "sources", {'uri': uri})
|
||||
|
||||
|
||||
def check_for_file(game, file_id):
|
||||
|
@ -158,11 +128,9 @@ def check_for_file(game, file_id):
|
|||
continue
|
||||
game_dir = os.path.join(source, game)
|
||||
if not os.path.exists(game_dir):
|
||||
print "dir", game_dir
|
||||
continue
|
||||
game_files = os.listdir(game_dir)
|
||||
for game_file in game_files:
|
||||
print "file", game_file
|
||||
game_base, _ext = os.path.splitext(game_file)
|
||||
if game_base == file_id:
|
||||
return os.path.join(game_dir, game_file)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -87,7 +87,7 @@ setup(
|
|||
packages=['lutris', 'lutris.gui', 'lutris.util', 'lutris.runners'],
|
||||
scripts=['bin/lutris'],
|
||||
data_files=data_files,
|
||||
install_requires=['PyYAML'],
|
||||
install_requires=['PyYAML', 'pyxdg', 'PyGObject'],
|
||||
url='http://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
|
||||
|
|
|
@ -8,13 +8,20 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
|||
from lutris.util import http
|
||||
from lutris.gui.dialogs import DownloadDialog
|
||||
|
||||
TEST_URL = "http://strycore.com/documents/serious.sam.tfe_1.05beta3-english-2.run"
|
||||
#TEST_URL = "file:///usr/bin/gvim"
|
||||
#TEST_URL = "http://strycore.com/documents/serious.sam.tfe_1.05beta3-english-2.run"
|
||||
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 = "http://strycore.com/documents/normality-en.7z"
|
||||
TEST_URL = "smb://newport/games/linux/aquaria/aquaria-lnx-humble-bundle.mojo.run"
|
||||
GObject.threads_init()
|
||||
|
||||
|
||||
def timed(function):
|
||||
def _wrapped(*args, **kwargs):
|
||||
print ">",
|
||||
start_time = time.time()
|
||||
retval = function(*args, **kwargs)
|
||||
total = time.time() - start_time
|
||||
|
@ -35,6 +42,7 @@ def test_download_asset():
|
|||
|
||||
class DownloadDialogBenchmark(DownloadDialog):
|
||||
def download_complete(self, _widget, _data):
|
||||
print "Complete"
|
||||
self.destroy()
|
||||
Gtk.main_quit()
|
||||
|
||||
|
@ -45,8 +53,6 @@ def test_download_dialog():
|
|||
Gtk.main()
|
||||
|
||||
|
||||
test_urlretrieve()
|
||||
time.sleep(5)
|
||||
test_download_asset()
|
||||
time.sleep(5)
|
||||
test_download_dialog()
|
||||
#test_urlretrieve()
|
||||
#test_download_asset()
|
||||
|
|
Loading…
Reference in a new issue