mirror of
https://github.com/lutris/lutris.git
synced 2026-06-23 05:10:33 -04:00
Fix downloader (almost, still slow)
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user