diff --git a/bin/lutris b/bin/lutris index 5b4ad13f4..d52d99021 100755 --- a/bin/lutris +++ b/bin/lutris @@ -22,7 +22,7 @@ import optparse import signal # pylint: disable=E0611 -from gi.repository import Gtk +from gi.repository import Gtk, GObject from os.path import realpath, dirname, normpath @@ -122,6 +122,6 @@ if game: Gtk.main() else: lutris_window = LutrisWindow() - + GObject.threads_init() Gtk.main() logger.debug("Application exit") diff --git a/lutris/downloader.py b/lutris/downloader.py index 382cfd04a..29a11e19d 100644 --- a/lutris/downloader.py +++ b/lutris/downloader.py @@ -1,67 +1,40 @@ -#!/usr/bin/python -# -*- coding:Utf-8 -*- -# -# Copyright (C) 2010 Mathieu Comandon -# -# 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 . -# -""" Downloader module """ - -import urllib -import datetime -import threading - -from lutris.util.log import logger +""" Non-blocking Gio Downloader """ +import time +from gi.repository import Gio, GLib, GObject -class DownloadStoppedException(Exception): - """ Dummy exception for download canceled. """ - def __init__(self): - super(DownloadStoppedException, self).__init__() - - -class Downloader(threading.Thread): - """Downloader class that doesn't block the program""" - - start_time = 0 - elapsed_time = datetime.timedelta(seconds=1) - total_downloaded = 0 - total_size = 0 +class Downloader(): + downloaded_bytes = 0 + total_bytes = 0 + time_elapsed = 0 + time_remaining = 0 + speed = 0 def __init__(self, url, dest): - """Set up the downloader.""" - threading.Thread.__init__(self) - self.setDaemon(False) - self.url = url - self.dest = dest + self.remote = Gio.File.new_for_uri(url) + self.local = Gio.File.new_for_path(dest) + self.cancellable = Gio.Cancellable() self.progress = 0 - self.kill = None + self.start_time = None - def run(self): - """Start the download.""" - logger.debug("Download of %s starting" % self.url) - self.start_time = datetime.datetime.now() - urllib.urlretrieve(self.url, self.dest, self._report_progress) - return True + def progress_callback(self, downloaded_bytes, total_bytes, _user_data): + self.downloaded_bytes = downloaded_bytes + self.total_bytes = total_bytes + self.time_elapsed = time.time() - self.start_time + self.speed = self.downloaded_bytes / self.time_elapsed or 1 + self.time_remaining = (total_bytes - downloaded_bytes) / self.speed + self.progress = float(downloaded_bytes) / float(total_bytes) - def _report_progress(self, piece, received_bytes, total_size): - """ Update download's progress. """ - self.elapsed_time = datetime.datetime.now() - self.start_time - self.total_downloaded += received_bytes - self.total_size = total_size - self.progress = ((piece * received_bytes)) / (total_size * 1.0) - try: - if self.kill is True: - raise DownloadStoppedException - except DownloadStoppedException: - logger.debug("stopping download") + def cancel(self): + self.cancellable.cancel() + + def download(self, job, cancellable, user_data): + flags = Gio.FileCopyFlags.OVERWRITE + self.remote.copy(self.local, flags, self.cancellable, + self.progress_callback, None) + + def start(self): + GObject.threads_init() + self.start_time = time.time() + Gio.io_scheduler_push_job(self.download, None, + GLib.PRIORITY_DEFAULT, self.cancellable) diff --git a/lutris/gui/widgets.py b/lutris/gui/widgets.py index e9018bd90..906aed5b0 100644 --- a/lutris/gui/widgets.py +++ b/lutris/gui/widgets.py @@ -253,7 +253,6 @@ class GameTreeView(Gtk.TreeView, GameView): else: self.emit("game-selected") -import datetime class GameIconView(Gtk.IconView, GameView): __gsignals__ = GameView.__gsignals__ @@ -377,24 +376,32 @@ class GameCover(Gtk.Image): class DownloadProgressBox(Gtk.HBox): """Progress bar used to monitor a file download.""" - __gsignals__ = {'complete': (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT,)), - 'cancelrequested': (GObject.SignalFlags.RUN_LAST, - None, (GObject.TYPE_PYOBJECT,))} + __gsignals__ = { + 'complete': (GObject.SignalFlags.RUN_LAST, None, + (GObject.TYPE_PYOBJECT,)), + 'cancelrequested': (GObject.SignalFlags.RUN_LAST, None, + (GObject.TYPE_PYOBJECT,)) + } def __init__(self, params, cancelable=True): super(DownloadProgressBox, self).__init__() self.downloader = None + + self.progress_box = Gtk.VBox() + self.progressbar = Gtk.ProgressBar() - self.progressbar.show() - self.pack_start(self.progressbar, True, True, 10) + self.progress_box.pack_start(self.progressbar, True, True, 10) + self.progress_label = Gtk.Label() + self.progress_box.pack_start(self.progress_label, True, True, 10) + self.pack_start(self.progress_box, True, True, 10) + self.progress_box.show_all() + self.cancel_button = Gtk.Button(stock=Gtk.STOCK_CANCEL) if cancelable: self.cancel_button.show() - self.cancel_button.set_sensitive(False) - self.cancel_button.connect('clicked', self._stop_download) - self.pack_end(self.cancel_button, False, False, 10) + self.cancel_button.set_sensitive(False) + self.cancel_button.connect('clicked', self.cancel) + self.pack_end(self.cancel_button, False, False, 10) self.url = params['url'] self.dest = params['dest'] @@ -411,19 +418,16 @@ class DownloadProgressBox(Gtk.HBox): """Show download progress.""" progress = min(self.downloader.progress, 1) self.progressbar.set_fraction(progress) - total_downloaded = self.downloader.total_downloaded - elapsed_seconds = self.downloader.elapsed_time.seconds or 1 - total_size = self.downloader.total_size - speed = total_downloaded / elapsed_seconds or 1 - time_left = (total_size - total_downloaded) / speed megabytes = 1024 * 1024 - progress_label = ("%0.2fMb out of %0.2fMb (%0.2fMb/s), " - "%d seconds remaining" - % (float(total_downloaded) / megabytes, - float(total_size) / megabytes, - float(speed) / megabytes, - time_left)) - self.progressbar.set_text(progress_label) + progress_text = ( + "%0.2fMb out of %0.2fMb (%0.2fMb/s), %d seconds remaining" % ( + float(self.downloader.downloaded_bytes) / megabytes, + float(self.downloader.total_bytes) / megabytes, + float(self.downloader.speed) / megabytes, + self.downloader.time_remaining + ) + ) + self.progress_label.set_text(progress_text) self.progressbar.set_fraction(progress) if progress >= 1.0: self.cancel_button.set_sensitive(False) @@ -431,15 +435,11 @@ class DownloadProgressBox(Gtk.HBox): return False return True - def _stop_download(self): - """Stop the current download.""" - self.downloader.kill = True - self.cancel_button.set_sensitive(False) - - def cancel(self): + def cancel(self, _widget): """Cancel the current download.""" if self.downloader: - self.downloader.kill = True + self.downloader.cancel() + self.cancel_button.set_sensitive(False) class FileChooserEntry(Gtk.Box): diff --git a/lutris/installer.py b/lutris/installer.py index 73d3dc0de..cb610b70d 100644 --- a/lutris/installer.py +++ b/lutris/installer.py @@ -249,16 +249,13 @@ class Installer(Gtk.Dialog): "Downloading file %d of %d", self.download_index + 1, len(self.rules["files"]) ) - logger.debug(self.rules["files"][self.download_index]) self.download_game_file(self.rules["files"][self.download_index]) else: - logger.debug("All files downloaded") + logger.info("All files downloaded") self.install() def download_complete(self, widget=None, data=None): """Action called on a completed download""" - logger.debug("widget: %s", widget) - logger.debug("data: %s", data) self.download_index += 1 self.process_downloads() @@ -315,9 +312,9 @@ class Installer(Gtk.Dialog): dest_dir, os.path.basename(filename) )) elif url.startswith("http"): - self.download_progress = DownloadProgressBox({'url': url, - 'dest': dest_file}, - cancelable=False) + 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() @@ -334,9 +331,15 @@ class Installer(Gtk.Dialog): os.chdir(self.game_dir) for action in self.actions: - action_name = action.keys()[0] - action_data = action[action_name] + 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, 'request_media': self._request_media, @@ -460,14 +463,17 @@ class Installer(Gtk.Dialog): print self.gamefiles print data + def _insert_disc(self, _data): + print "Insert disc" + def _extract(self, data): """ Extracts a file, guessing the compression method """ filename = self.gamefiles.get(data.get('file')) if not filename: - log.logger.error("No file for '%s' in game files" % data) + logger.error("No file for '%s' in game files" % data) return False if not os.path.exists(filename): - log.logger.error("%s does not exists" % filename) + logger.error("%s does not exists" % filename) return False msg = "Extracting %s" % filename log.logger.debug(msg) @@ -533,10 +539,10 @@ class Installer(Gtk.Dialog): else: return False - def _run(self, data): + def _run(self, executable): """Run an executable script""" exec_path = os.path.join(settings.CACHE_DIR, self.game_slug, - self.gamefiles[data['file']]) + self.gamefiles[executable]) if not os.path.exists(exec_path): print("unable to find %s" % exec_path) exit() @@ -549,9 +555,9 @@ class Installer(Gtk.Dialog): Mandatory parameters in data are 'task' and 'args' """ - log.logger.info("Called runner task") - log.logger.debug(data) - log.logger.debug("runner is %s", self.rules['runner']) + logger.info("Called runner task") + logger.debug(data) + logger.debug("runner is %s", self.rules['runner']) runner_name = self.rules["runner"] task = import_task(runner_name, data['task']) args = data['args'] @@ -561,7 +567,7 @@ class Installer(Gtk.Dialog): if key == 'filename': if args[key] in self.gamefiles.keys(): args[key] = self.gamefiles[args[key]] - log.logger.debug("args are %s", repr(args)) + logger.debug("args are %s", repr(args)) # FIXME pass args as kwargs and not args task(**args)