Fix downloader (almost, still slow)

This commit is contained in:
Mathieu Comandon
2013-05-25 03:20:31 +02:00
parent f7e148cb22
commit ccde83698f
4 changed files with 88 additions and 109 deletions

View File

@@ -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")

View File

@@ -1,67 +1,40 @@
#!/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/>.
#
""" 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)

View File

@@ -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):

View File

@@ -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)