import os
from gettext import gettext as _
from typing import Callable, Optional
from gi.repository import Gio, Gtk
from lutris.gui.config.base_config_box import BaseConfigBox
from lutris.gui.dialogs import NoticeDialog
from lutris.runtime import RuntimeUpdater
from lutris.services.lutris import sync_media
from lutris.util.jobs import AsyncCall
from lutris.util.log import logger
from lutris.util.strings import gtk_safe
LUTRIS_EXPERIMENTAL_FEATURES_ENABLED = os.environ.get("LUTRIS_EXPERIMENTAL_FEATURES_ENABLED") == "1"
class UpdatesBox(BaseConfigBox):
def populate(self):
self.add(self.get_section_label(_("Runtime updates")))
self.add(self.get_description_label(_("Runtime components include DXVK, VKD3D and Winetricks.")))
self.update_runtime_box = UpdateButtonBox("", _("Check for Updates"), clicked=self.on_runtime_update_clicked)
update_runtime_box = self.get_setting_box(
"auto_update_runtime",
_("Automatically Update the Lutris runtime"),
default=True,
extra_widget=self.update_runtime_box,
)
self.pack_start(self._get_framed_options_list_box([update_runtime_box]), False, False, 0)
self.add(self.get_section_label(_("Media updates")))
self.update_media_box = UpdateButtonBox("", _("Download Missing Media"), clicked=self.on_download_media_clicked)
self.pack_start(self._get_framed_options_list_box([self.update_media_box]), False, False, 0)
def _get_radio_button(
self, label_markup: str, active: bool, group: Gtk.RadioButton, margin: int = 12
) -> Gtk.RadioButton:
radio_button = Gtk.RadioButton.new_from_widget(group)
radio_button.set_active(active)
radio_button.set_margin_left(margin)
radio_button.set_margin_right(margin)
radio_button.set_margin_top(margin)
radio_button.set_margin_bottom(margin)
radio_button.set_visible(True)
radio_button.set_label("") # creates Gtk.Label child
label: Optional[Gtk.Label] = radio_button.get_child()
if label:
label.set_markup(label_markup)
label.set_margin_left(6)
label.props.wrap = True
return radio_button
def on_download_media_clicked(self, _widget):
self.update_media_box.show_running_markup(_("Checking for missing media..."))
AsyncCall(sync_media, self.on_media_updated)
def on_media_updated(self, result, error):
if error:
self.update_media_box.show_error(error)
elif not result:
self.update_media_box.show_completion_markup("", _("Nothing to update"))
elif any(result.values()):
update_text = _("Updated: ")
names = {
("banners", False): _("banner"),
("icons", False): _("icon"),
("covers", False): _("cover"),
("banners", True): _("banners"),
("icons", True): _("icons"),
("covers", True): _("covers"),
}
for key, value in result.items():
if value:
if not update_text.endswith(": "):
update_text += ", "
update_text += f"{value} {names[(key, value > 1)]}"
self.update_media_box.show_completion_markup("", update_text)
else:
self.update_media_box.show_completion_markup("", _("No new media found."))
def _get_main_window(self):
application = Gio.Application.get_default()
if not application or not application.window:
logger.error("No application or window found, how does this happen?")
return
return application.window
def on_runtime_update_clicked(self, _widget):
def get_updater():
updater = RuntimeUpdater(force=True)
updater.update_runners = False
return updater
self._trigger_updates(get_updater, self.update_runtime_box)
def _trigger_updates(self, updater_factory: Callable, update_box: "UpdateButtonBox") -> None:
window = self._get_main_window()
if not window:
return
updater = updater_factory()
component_updaters = updater.create_component_updaters()
if component_updaters:
def on_complete(_result):
component_names = [updater.name for updater in component_updaters]
if len(component_names) == 1:
update_box.show_completion_markup("", _("%s has been updated.") % component_names[0])
else:
update_box.show_completion_markup("", _("%s have been updated.") % ", ".join(component_names))
started = window.install_runtime_component_updates(
component_updaters, updater, completion_function=on_complete, error_function=update_box.show_error
)
if started:
update_box.show_running_markup(_("Checking for updates..."))
else:
NoticeDialog(_("Updates are already being downloaded and installed."), parent=self.get_toplevel())
else:
update_box.show_completion_markup("", _("No updates are required at this time."))
class UpdateButtonBox(Gtk.Box):
"""A box containing a button to start updating something, with methods to show a result
when done."""
def __init__(self, label: str, button_label: str, clicked: Callable[[Gtk.Widget], None]):
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, margin=12, spacing=6, visible=True)
self.label = Gtk.Label(visible=True, xalign=0)
self.label.set_markup(label)
self.pack_start(self.label, True, True, 0)
self.button = Gtk.Button(label=button_label, visible=True)
self.button.connect("clicked", clicked)
self.pack_end(self.button, False, False, 0)
self.spinner = Gtk.Spinner()
self.pack_end(self.spinner, False, False, 0)
self.result_label = Gtk.Label(wrap=True)
self.pack_end(self.result_label, False, False, 0)
def show_running_markup(self, markup: str) -> None:
self.button.hide()
self.result_label.set_markup(markup)
self.result_label.show()
self.spinner.show()
self.spinner.start()
def show_completion_markup(self, label_markup: str, completion_markup: str) -> None:
self.button.hide()
self.result_label.show()
self.spinner.stop()
self.spinner.hide()
self.label.set_markup(label_markup)
self.result_label.set_markup(completion_markup)
def show_error(self, error: Exception) -> None:
self.button.hide()
self.result_label.show()
self.spinner.stop()
self.spinner.hide()
self.result_label.set_markup("Error:%s" % gtk_safe(str(error)))