Implement download retries

This commit is contained in:
Mathieu Comandon 2021-05-25 02:23:36 -07:00
parent b7301fd03a
commit 4dfeafa710
6 changed files with 76 additions and 27 deletions

View file

@ -2,7 +2,7 @@ from gettext import gettext as _
from gi.repository import Gtk
from lutris.gui.widgets.download_progress import DownloadProgressBox
from lutris.gui.widgets.download_progress_box import DownloadProgressBox
class DownloadDialog(Gtk.Dialog):
@ -13,15 +13,15 @@ class DownloadDialog(Gtk.Dialog):
self.set_size_request(485, 104)
self.set_border_width(12)
params = {"url": url, "dest": dest, "title": label or _("Downloading %s") % url}
self.download_box = DownloadProgressBox(params, downloader=downloader)
self.dialog_progress_box = DownloadProgressBox(params, downloader=downloader)
self.download_box.connect("complete", self.download_complete)
self.download_box.connect("cancel", self.download_cancelled)
self.dialog_progress_box.connect("complete", self.download_complete)
self.dialog_progress_box.connect("cancel", self.download_cancelled)
self.connect("response", self.on_response)
self.get_content_area().add(self.download_box)
self.get_content_area().add(self.dialog_progress_box)
self.show_all()
self.download_box.start()
self.dialog_progress_box.start()
def download_complete(self, _widget, _data):
self.response(Gtk.ResponseType.OK)
@ -33,7 +33,7 @@ class DownloadDialog(Gtk.Dialog):
def on_response(self, _dialog, response):
if response == Gtk.ResponseType.DELETE_EVENT:
self.download_box.downloader.cancel()
self.dialog_progress_box.downloader.cancel()
self.destroy()

View file

@ -8,9 +8,10 @@ from gi.repository import GObject, Gtk
from lutris.cache import save_to_cache
from lutris.gui.installer.widgets import InstallerLabel
from lutris.gui.widgets.common import FileChooserEntry
from lutris.gui.widgets.download_progress import DownloadProgressBox
from lutris.gui.widgets.download_progress_box import DownloadProgressBox
from lutris.installer.steam_installer import SteamInstaller
from lutris.util import system
from lutris.util.log import logger
from lutris.util.strings import add_url_tags, gtk_safe
@ -54,8 +55,9 @@ class InstallerFileBox(Gtk.VBox):
"url": self.installer_file.url,
"dest": self.installer_file.dest_file,
"referer": self.installer_file.referer
}, cancelable=True)
})
download_progress.connect("complete", self.on_download_complete)
download_progress.connect("cancel", self.on_download_cancelled)
download_progress.show()
if (
not self.installer_file.uses_pga_cache()
@ -70,7 +72,7 @@ class InstallerFileBox(Gtk.VBox):
if self.provider == "download":
download_progress = self.get_download_progress()
self.start_func = download_progress.start
self.stop_func = download_progress.cancel
self.stop_func = download_progress.on_cancel_clicked
box.pack_start(download_progress, False, False, 0)
return box
if self.provider == "pga":
@ -268,8 +270,10 @@ class InstallerFileBox(Gtk.VBox):
if self.cache_to_pga:
save_to_cache(self.installer_file.dest_file, self.installer_file.cache_path)
def on_download_cancelled(self):
def on_download_cancelled(self, downloader):
"""Handle cancellation of installers"""
logger.error("Download from %s cancelled", downloader)
downloader.set_retry_button()
def on_download_complete(self, widget, _data=None):
"""Action called on a completed download."""

View file

@ -1,8 +1,10 @@
from gettext import gettext as _
from urllib.parse import urlparse
from gi.repository import GLib, GObject, Gtk, Pango
from lutris.util.downloader import Downloader
from lutris.util.log import logger
from lutris.util.strings import gtk_safe
@ -12,7 +14,7 @@ class DownloadProgressBox(Gtk.Box):
__gsignals__ = {
"complete": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, )),
"cancel": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, )),
"cancel": (GObject.SignalFlags.RUN_LAST, None, ()),
"error": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, )),
}
@ -24,9 +26,8 @@ class DownloadProgressBox(Gtk.Box):
self.url = params.get("url")
self.dest = params.get("dest")
self.referer = params.get("referer")
title = params.get("title", _("Downloading {}").format(self.url))
self.main_label = Gtk.Label(title)
self.main_label = Gtk.Label(self.get_title())
self.main_label.set_alignment(0, 0)
self.main_label.set_property("wrap", True)
self.main_label.set_margin_bottom(10)
@ -44,7 +45,7 @@ class DownloadProgressBox(Gtk.Box):
progress_box.pack_start(self.progressbar, True, True, 0)
self.cancel_button = Gtk.Button.new_with_mnemonic(_("_Cancel"))
self.cancel_button.connect("clicked", self.cancel)
self.cancel_cb_id = self.cancel_button.connect("clicked", self.on_cancel_clicked)
if not cancelable:
self.cancel_button.set_sensitive(False)
progress_box.pack_end(self.cancel_button, False, False, 0)
@ -58,6 +59,11 @@ class DownloadProgressBox(Gtk.Box):
self.show_all()
self.cancel_button.hide()
def get_title(self):
"""Return the main label text for the widget"""
parsed = urlparse(self.url)
return "%s%s" % (parsed.netloc, parsed.path)
def start(self):
"""Start downloading a file."""
if not self.downloader:
@ -67,7 +73,7 @@ class DownloadProgressBox(Gtk.Box):
from lutris.gui.dialogs import ErrorDialog
ErrorDialog(ex.args[0])
self.emit("cancel", {})
self.emit("cancel")
return None
timer_id = GLib.timeout_add(500, self._progress)
@ -77,12 +83,28 @@ class DownloadProgressBox(Gtk.Box):
self.downloader.start()
return timer_id
def cancel(self, _widget=None):
def set_retry_button(self):
"""Transform the cancel button into a retry button"""
self.cancel_button.set_label(_("Retry"))
self.cancel_button.disconnect(self.cancel_cb_id)
self.cancel_cb_id = self.cancel_button.connect("clicked", self.on_retry_clicked)
self.cancel_button.set_sensitive(True)
def on_retry_clicked(self, button):
logger.debug("Retrying download")
button.set_label(_("Cancel"))
button.disconnect(self.cancel_cb_id)
self.cancel_cb_id = button.connect("clicked", self.on_cancel_clicked)
self.downloader.reset()
self.start()
def on_cancel_clicked(self, _widget=None):
"""Cancel the current download."""
logger.debug("Download cancel requested")
if self.downloader:
self.downloader.cancel()
self.cancel_button.set_sensitive(False)
self.emit("cancel", {})
self.emit("cancel")
def _progress(self):
"""Show download progress."""
@ -91,10 +113,9 @@ class DownloadProgressBox(Gtk.Box):
self.progressbar.set_fraction(0)
if self.downloader.state == self.downloader.CANCELLED:
self._set_text(_("Download interrupted"))
self.emit("cancel")
else:
self._set_text(str(self.downloader.error)[:80])
if self.downloader.state == self.downloader.CANCELLED:
self.emit("cancel", {})
return False
self.progressbar.set_fraction(progress)
megabytes = 1024 * 1024

View file

@ -23,7 +23,13 @@ class Downloader:
Stop with cancel().
"""
(INIT, DOWNLOADING, CANCELLED, ERROR, COMPLETED) = list(range(5))
(
INIT,
DOWNLOADING,
CANCELLED,
ERROR,
COMPLETED
) = list(range(5))
def __init__(self, url, dest, overwrite=False, referer=None, callback=None):
self.url = url
@ -44,7 +50,6 @@ class Downloader:
self.speed = 0
self.average_speed = 0
self.time_left = "00:00:00" # Based on average speed
self.last_size = 0
self.last_check_time = 0
self.last_speeds = []
@ -52,9 +57,12 @@ class Downloader:
self.time_left_check_time = 0
self.file_pointer = None
def __str__(self):
return "downloader for %s" % self.url
def start(self):
"""Start download job."""
logger.debug("Starting download of:\n %s", self.url)
logger.debug(" %s", self.url)
self.state = self.DOWNLOADING
self.last_check_time = get_time()
if self.overwrite and os.path.isfile(self.dest):
@ -63,6 +71,24 @@ class Downloader:
self.thread = jobs.AsyncCall(self.async_download, self.download_cb)
self.stop_request = self.thread.stop_request
def reset(self):
"""Reset the state of the downloader"""
self.state = self.INIT
self.error = None
self.downloaded_size = 0 # Bytes
self.full_size = 0 # Bytes
self.progress_fraction = 0
self.progress_percentage = 0
self.speed = 0
self.average_speed = 0
self.time_left = "00:00:00" # Based on average speed
self.last_size = 0
self.last_check_time = 0
self.last_speeds = []
self.speed_check_time = 0
self.time_left_check_time = 0
self.file_pointer = None
def check_progress(self):
"""Append last downloaded chunk to dest file and store stats.
@ -73,7 +99,7 @@ class Downloader:
def cancel(self):
"""Request download stop and remove destination file."""
logger.debug("Download of %s cancelled", self.url)
logger.debug("%s", self.url)
self.state = self.CANCELLED
if self.stop_request:
self.stop_request.set()

View file

@ -236,7 +236,7 @@ class WinePrefixManager:
"""Disables some joypad devices"""
key = self.hkcu_prefix + "/Software/Wine/DirectInput/Joysticks"
self.clear_registry_key(key)
for device, joypad_name in joypad.get_joypads():
for _device, joypad_name in joypad.get_joypads():
# Attempt at disabling mice that register as joysticks.
# Although, those devices aren't returned by `get_joypads`
# A better way would be to read /dev/input files directly.

View file

@ -1,11 +1,9 @@
"""Manipulate Wine registry files"""
# Standard Library
import os
import re
from collections import OrderedDict
from datetime import datetime
# Lutris Modules
from lutris.util import system
from lutris.util.log import logger
from lutris.util.wine.wine import WINE_DEFAULT_ARCH