from gi.repository import GObject, Gtk from lutris.gui.installer.file_box import InstallerFileBox from lutris.util.log import logger class InstallerFilesBox(Gtk.ListBox): """List box presenting all files needed for an installer""" max_downloads = 3 __gsignals__ = { "files-ready": (GObject.SIGNAL_RUN_LAST, None, (bool,)), "files-available": (GObject.SIGNAL_RUN_LAST, None, ()), } def __init__(self): super().__init__() self.installer = None self.ready_files = set() self.available_files = set() self.installer_files_boxes = {} self._file_queue = [] def load_installer(self, installer): self.stop_all() self.installer = installer self.available_files.clear() self.ready_files.clear() self.installer_files_boxes.clear() self._file_queue.clear() for child in self.get_children(): child.destroy() for installer_file in installer.files: installer_file_box = InstallerFileBox(installer_file) installer_file_box.connect("file-ready", self.on_file_ready) installer_file_box.connect("file-unready", self.on_file_unready) installer_file_box.connect("file-available", self.on_file_available) self.installer_files_boxes[installer_file.id] = installer_file_box self.add(installer_file_box) if installer_file_box.is_ready: self.ready_files.add(installer_file.id) self.show_all() self.check_files_ready() def start_all(self): """Iterates through installer files while keeping the number of simultaneously downloaded files down to a maximum number""" if len(self.available_files) == len(self.installer.files): logger.info("All files remain available") self.emit("files-available") return started_downloads = 0 for file_id, file_entry in self.installer_files_boxes.items(): if file_id not in self.available_files: if file_entry.provider == "download": started_downloads += 1 if started_downloads <= self.max_downloads: file_entry.start() else: self._file_queue.append(file_id) else: file_entry.start() def stop_all(self): """Stops all ongoing files gathering. Iterates through installer files, and call the "stop" command if they've been started and not available yet. """ self._file_queue.clear() for file_id, file_box in self.installer_files_boxes.items(): if file_box.started and file_id not in self.available_files and file_box.stop_func is not None: file_box.stop_func() @property def is_ready(self): """Return True if all files are ready to be fetched""" return len(self.ready_files) == len(self.installer.files) def check_files_ready(self): """Checks if all installer files are ready and emit a signal if so""" self.emit("files-ready", self.is_ready) def on_file_ready(self, widget): """Fired when a file has a valid provider. If the file is user provided, it must set to a valid path. """ file_id = widget.installer_file.id self.ready_files.add(file_id) self.check_files_ready() def on_file_unready(self, widget): """Fired when a file can't be provided. Blocks the installer from continuing. """ file_id = widget.installer_file.id self.ready_files.discard(file_id) self.check_files_ready() def on_file_available(self, widget): """A new file is available""" file_id = widget.installer_file.id logger.debug("%s is available", file_id) self.available_files.add(file_id) if self._file_queue: next_file_id = self._file_queue.pop() self.installer_files_boxes[next_file_id].start() if len(self.available_files) == len(self.installer.files): logger.info("All files available") self.emit("files-available") def get_game_files(self): """Return a mapping of the local files usable by the interpreter""" files = {} for installer_file in self.installer.files: files.update(installer_file.get_dest_files_by_id()) return files