예제 #1
0
class InstallerFileBox(Gtk.VBox):
    """Container for an installer file downloader / selector"""

    __gsignals__ = {
        "file-available": (GObject.SIGNAL_RUN_FIRST, None, ()),
        "file-ready": (GObject.SIGNAL_RUN_FIRST, None, ()),
        "file-unready": (GObject.SIGNAL_RUN_FIRST, None, ()),
    }

    def __init__(self, installer_file):
        super().__init__()
        self.installer_file = installer_file
        self.cache_to_pga = self.installer_file.uses_pga_cache()
        self.started = False
        self.start_func = None
        self.stop_func = None
        self.state_label = None  # Use this label to display status update
        self.set_margin_left(12)
        self.set_margin_right(12)
        self.provider = self.installer_file.provider
        self.popover = self.get_popover()
        self.file_provider_widget = None
        self.add(self.get_widgets())

    @property
    def is_ready(self):
        """Whether the file is ready to be downloaded / fetched from its provider"""
        if (self.provider in ("user", "pga")
                and not system.path_exists(self.installer_file.dest_file)):
            return False
        return True

    def get_download_progress(self):
        """Return the widget for the download progress bar"""
        download_progress = DownloadProgressBox(
            {
                "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.show()
        if (not self.installer_file.uses_pga_cache()
                and system.path_exists(self.installer_file.dest_file)):
            os.remove(self.installer_file.dest_file)
        return download_progress

    def get_file_provider_widget(self):
        """Return the widget used to track progress of file"""
        box = Gtk.VBox(spacing=6)
        if self.provider == "download":
            download_progress = self.get_download_progress()
            self.start_func = download_progress.start
            self.stop_func = download_progress.cancel
            box.pack_start(download_progress, False, False, 0)
            return box
        if self.provider == "pga":
            url_label = InstallerLabel("In cache: %s" % self.get_file_label(),
                                       wrap=False)
            box.pack_start(url_label, False, False, 6)
            return box
        if self.provider == "user":
            user_label = InstallerLabel(gtk_safe(
                self.installer_file.human_url))
            box.pack_start(user_label, False, False, 0)
            return box
        if self.provider == "steam":
            steam_installer = SteamInstaller(self.installer_file.url,
                                             self.installer_file.id)
            steam_installer.connect("game-installed",
                                    self.on_download_complete)
            steam_installer.connect("state-changed", self.on_state_changed)
            self.start_func = steam_installer.install_steam_game
            self.stop_func = steam_installer.stop_func

            steam_box = Gtk.HBox(spacing=6)
            info_box = Gtk.VBox(spacing=6)
            steam_label = InstallerLabel(
                _("Steam game for {platform} (appid: <b>{appid}</b>)").format(
                    platform=steam_installer.platform,
                    appid=steam_installer.appid))
            info_box.add(steam_label)
            self.state_label = InstallerLabel("")
            info_box.add(self.state_label)
            steam_box.add(info_box)
            return steam_box
        raise ValueError("Invalid provider %s" % self.provider)

    def get_file_label(self):
        """Return a human readable label for installer files"""
        url = self.installer_file.url
        if url.startswith("http"):
            parsed = urlparse(url)
            label = "%s on %s" % (self.installer_file.filename, parsed.netloc)
        elif url.startswith("N/A"):
            label = url[3:].lstrip(":")
        else:
            label = url
        return add_url_tags(gtk_safe(label))

    def get_popover(self):
        """Return the popover widget to select file source"""
        popover = Gtk.Popover()
        popover.add(self.get_popover_menu())
        popover.set_position(Gtk.PositionType.BOTTOM)
        return popover

    def get_source_radiobutton(self, last_widget, label, source):
        """Return a radio button for the popover menu"""
        button = Gtk.RadioButton.new_with_label_from_widget(last_widget, label)
        if self.provider == source:
            button.set_active(True)
        button.connect("toggled", self.on_source_changed, source)
        return button

    def get_popover_menu(self):
        """Create the menu going into the popover"""
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        last_widget = None
        if "download" in self.installer_file.providers:
            download_button = self.get_source_radiobutton(
                last_widget, _("Download"), "download")
            vbox.pack_start(download_button, False, True, 10)
            last_widget = download_button
        if "pga" in self.installer_file.providers:
            pga_button = self.get_source_radiobutton(last_widget,
                                                     _("Use cache"), "pga")
            vbox.pack_start(pga_button, False, True, 10)
            last_widget = pga_button
        user_button = self.get_source_radiobutton(last_widget,
                                                  _("Select file"), "user")
        vbox.pack_start(user_button, False, True, 10)
        return vbox

    def replace_file_provider_widget(self):
        """Replace the file provider label and the source button with the actual widget"""
        self.file_provider_widget.destroy()
        widget_box = self.get_children()[0]
        if self.started:
            self.file_provider_widget = self.get_file_provider_widget()
            # Also remove the the source button
            for child in widget_box.get_children():
                child.destroy()
        else:
            self.file_provider_widget = self.get_file_provider_label()
        widget_box.pack_start(self.file_provider_widget, True, True, 0)
        widget_box.reorder_child(self.file_provider_widget, 0)
        widget_box.show_all()

    def on_source_changed(self, _button, source):
        """Change the source to a new provider, emit a new state"""
        if source == self.provider or not hasattr(self, "popover"):
            return
        self.provider = source
        self.replace_file_provider_widget()
        self.popover.popdown()
        button = self.popover.get_relative_to()
        if button:
            button.set_label(self.get_source_button_label())
        if self.provider == "user":
            self.emit("file-unready")
        else:
            self.emit("file-ready")

    def get_source_button_label(self):
        """Return the label for the source button"""
        provider_labels = {
            "download": _("Download"),
            "pga": _("Cache"),
            "user": _("Local"),
            "steam": _("Steam"),
        }
        if self.provider in provider_labels:
            return provider_labels[self.provider]
        raise ValueError("Unsupported provider %s" % self.provider)

    def get_file_provider_label(self):
        """Return the label displayed before the download starts"""
        if self.provider == "user":
            box = Gtk.VBox(spacing=6)
            label = InstallerLabel(self.get_file_label())
            label.props.can_focus = True
            box.pack_start(label, False, False, 0)
            location_entry = FileChooserEntry(self.installer_file.human_url,
                                              Gtk.FileChooserAction.OPEN,
                                              path=None)
            location_entry.entry.connect("changed", self.on_location_changed)
            location_entry.show()
            box.pack_start(location_entry, False, False, 0)
            if self.installer_file.uses_pga_cache(create=True):
                cache_option = Gtk.CheckButton(
                    _("Cache file for future installations"))
                cache_option.set_active(self.cache_to_pga)
                cache_option.connect("toggled", self.on_user_file_cached)
                box.pack_start(cache_option, False, False, 0)
            return box
        return InstallerLabel(self.get_file_label())

    def get_widgets(self):
        """Return the widget with the source of the file and a way to change its source"""
        box = Gtk.HBox(spacing=12, margin_top=6, margin_bottom=6)
        self.file_provider_widget = self.get_file_provider_label()
        box.pack_start(self.file_provider_widget, True, True, 0)
        source_box = Gtk.HBox()
        source_box.props.valign = Gtk.Align.START
        box.pack_start(source_box, False, False, 0)
        source_box.pack_start(InstallerLabel(_("Source:")), False, False, 0)
        button = Gtk.Button.new_with_label(self.get_source_button_label())
        button.connect("clicked", self.on_file_source_select)
        source_box.pack_start(button, False, False, 0)
        return box

    def on_file_source_select(self, button):
        """Open the popover to switch to a different source"""
        self.popover.set_relative_to(button)
        self.popover.show_all()
        self.popover.popup()

    def on_location_changed(self, widget):
        """Open a file picker when the browse button is clicked"""
        file_path = os.path.expanduser(widget.get_text())
        self.installer_file.dest_file = file_path
        if system.path_exists(file_path):
            self.emit("file-ready")
        else:
            self.emit("file-unready")

    def on_user_file_cached(self, checkbutton):
        """Enable or disable caching of user provided files"""
        self.cache_to_pga = checkbutton.get_active()

    def on_state_changed(self, _widget, state):
        """Update the state label with a new state"""
        self.state_label.set_text(state)

    def start(self):
        """Starts the download of the file"""
        self.started = True
        self.installer_file.prepare()
        self.replace_file_provider_widget()
        if self.provider in ("pga", "user") and self.is_ready:
            self.emit("file-available")
            self.cache_file()
            return
        if self.start_func:
            return self.start_func()

    def cache_file(self):
        """Copy file to the PGA cache"""
        if self.cache_to_pga:
            save_to_cache(self.installer_file.dest_file,
                          self.installer_file.cache_path)

    def on_download_cancelled(self):
        """Handle cancellation of installers"""

    def on_download_complete(self, widget, _data=None):
        """Action called on a completed download."""
        if isinstance(widget, SteamInstaller):
            self.installer_file.dest_file = widget.get_steam_data_path()
        self.emit("file-available")
        self.cache_file()
예제 #2
0
class InstallerFileBox(Gtk.VBox):
    """Container for an installer file downloader / selector"""

    __gsignals__ = {
        "file-available": (GObject.SIGNAL_RUN_FIRST, None, ()),
        "file-ready": (GObject.SIGNAL_RUN_FIRST, None, ()),
        "file-unready": (GObject.SIGNAL_RUN_FIRST, None, ()),
    }

    def __init__(self, installer_file):
        super().__init__()
        self.installer_file = installer_file
        self.cache_to_pga = self.installer_file.uses_pga_cache()
        self.started = False
        self.start_func = None
        self.stop_func = None
        self.state_label = None  # Use this label to display status update
        self.set_margin_left(12)
        self.set_margin_right(12)
        self.provider = self.installer_file.provider
        self.file_provider_widget = None
        self.add(self.get_widgets())

    @property
    def is_ready(self):
        """Whether the file is ready to be downloaded / fetched from its provider"""
        if (self.provider in ("user", "pga")
                and not system.path_exists(self.installer_file.dest_file)):
            return False
        return True

    def get_download_progress(self):
        """Return the widget for the download progress bar"""
        download_progress = DownloadProgressBox({
            "url":
            self.installer_file.url,
            "dest":
            self.installer_file.dest_file,
            "referer":
            self.installer_file.referer
        })
        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()
                and system.path_exists(self.installer_file.dest_file)):
            os.remove(self.installer_file.dest_file)
        return download_progress

    def get_file_provider_widget(self):
        """Return the widget used to track progress of file"""
        box = Gtk.VBox(spacing=6)
        if self.provider == "download":
            download_progress = self.get_download_progress()
            self.start_func = download_progress.start
            self.stop_func = download_progress.on_cancel_clicked
            box.pack_start(download_progress, False, False, 0)
            return box
        if self.provider == "pga":
            url_label = InstallerLabel("In cache: %s" % self.get_file_label(),
                                       wrap=False)
            box.pack_start(url_label, False, False, 6)
            return box
        if self.provider == "user":
            user_label = InstallerLabel(gtk_safe(
                self.installer_file.human_url))
            box.pack_start(user_label, False, False, 0)
            return box
        if self.provider == "steam":
            steam_installer = SteamInstaller(self.installer_file.url,
                                             self.installer_file.id)
            steam_installer.connect("steam-game-installed",
                                    self.on_download_complete)
            steam_installer.connect("steam-state-changed",
                                    self.on_state_changed)
            self.start_func = steam_installer.install_steam_game
            self.stop_func = steam_installer.stop_func

            steam_box = Gtk.HBox(spacing=6)
            info_box = Gtk.VBox(spacing=6)
            steam_label = InstallerLabel(
                _("Steam game <b>{appid}</b>").format(
                    appid=steam_installer.appid))
            info_box.add(steam_label)
            self.state_label = InstallerLabel("")
            info_box.add(self.state_label)
            steam_box.add(info_box)
            return steam_box
        raise ValueError("Invalid provider %s" % self.provider)

    def get_file_label(self):
        """Return a human readable label for installer files"""
        url = self.installer_file.url
        if url.startswith("http"):
            parsed = urlparse(url)
            label = _("{file} on {host}").format(
                file=self.installer_file.filename, host=parsed.netloc)
        elif url.startswith("N/A"):
            label = url[3:].lstrip(":")
        else:
            label = url
        return add_url_tags(gtk_safe(label))

    def get_combobox_model(self):
        """"Return the combobox's model"""
        model = Gtk.ListStore(str, str)
        if "download" in self.installer_file.providers:
            model.append(["download", "Download"])
        if "pga" in self.installer_file.providers:
            model.append(["pga", "Use Cache"])
        if "steam" in self.installer_file.providers:
            model.append(["steam", "Steam"])
        model.append(["user", "Select File"])
        return model

    def get_combobox(self):
        """Return the combobox widget to select file source"""
        combobox = Gtk.ComboBox.new_with_model(self.get_combobox_model())
        combobox.set_id_column(0)
        renderer_text = Gtk.CellRendererText()
        combobox.pack_start(renderer_text, True)
        combobox.add_attribute(renderer_text, "text", 1)
        combobox.connect("changed", self.on_source_changed)
        combobox.set_active_id(self.provider)
        return combobox

    def replace_file_provider_widget(self):
        """Replace the file provider label and the source button with the actual widget"""
        self.file_provider_widget.destroy()
        widget_box = self.get_children()[0]
        if self.started:
            self.file_provider_widget = self.get_file_provider_widget()
            # Also remove the the source button
            for child in widget_box.get_children():
                child.destroy()
        else:
            self.file_provider_widget = self.get_file_provider_label()
        widget_box.pack_start(self.file_provider_widget, True, True, 0)
        widget_box.reorder_child(self.file_provider_widget, 0)
        widget_box.show_all()

    def on_source_changed(self, combobox):
        """Change the source to a new provider, emit a new state"""
        tree_iter = combobox.get_active_iter()
        if tree_iter is None:
            return
        model = combobox.get_model()
        source = model[tree_iter][0]
        if source == self.provider:
            return
        self.provider = source
        self.replace_file_provider_widget()
        if self.provider == "user":
            self.emit("file-unready")
        else:
            self.emit("file-ready")

    def get_file_provider_label(self):
        """Return the label displayed before the download starts"""
        if self.provider == "user":
            box = Gtk.VBox(spacing=6)
            label = InstallerLabel(self.get_file_label())
            label.props.can_focus = True
            box.pack_start(label, False, False, 0)
            location_entry = FileChooserEntry(self.installer_file.human_url,
                                              Gtk.FileChooserAction.OPEN,
                                              path=None)
            location_entry.entry.connect("changed", self.on_location_changed)
            location_entry.show()
            box.pack_start(location_entry, False, False, 0)
            if self.installer_file.uses_pga_cache(create=True):
                cache_option = Gtk.CheckButton(
                    _("Cache file for future installations"))
                cache_option.set_active(self.cache_to_pga)
                cache_option.connect("toggled", self.on_user_file_cached)
                box.pack_start(cache_option, False, False, 0)
            return box
        return InstallerLabel(self.get_file_label())

    def get_widgets(self):
        """Return the widget with the source of the file and a way to change its source"""
        box = Gtk.HBox(spacing=12, margin_top=6, margin_bottom=6)
        self.file_provider_widget = self.get_file_provider_label()
        box.pack_start(self.file_provider_widget, True, True, 0)
        source_box = Gtk.HBox()
        source_box.props.valign = Gtk.Align.START
        box.pack_start(source_box, False, False, 0)
        source_box.pack_start(InstallerLabel(_("Source:")), False, False, 0)
        combobox = self.get_combobox()
        source_box.pack_start(combobox, False, False, 0)
        return box

    def on_location_changed(self, widget):
        """Open a file picker when the browse button is clicked"""
        file_path = os.path.expanduser(widget.get_text())
        self.installer_file.dest_file = file_path
        if system.path_exists(file_path):
            self.emit("file-ready")
        else:
            self.emit("file-unready")

    def on_user_file_cached(self, checkbutton):
        """Enable or disable caching of user provided files"""
        self.cache_to_pga = checkbutton.get_active()

    def on_state_changed(self, _widget, state):
        """Update the state label with a new state"""
        self.state_label.set_text(state)

    def start(self):
        """Starts the download of the file"""
        self.started = True
        self.installer_file.prepare()
        self.replace_file_provider_widget()
        if self.provider in ("pga", "user") and self.is_ready:
            self.emit("file-available")
            self.cache_file()
            return
        if self.start_func:
            return self.start_func()

    def cache_file(self):
        """Copy file to the PGA cache"""
        if self.cache_to_pga:
            save_to_cache(self.installer_file.dest_file,
                          self.installer_file.cache_path)

    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."""
        logger.info("Download completed")
        if isinstance(widget, SteamInstaller):
            self.installer_file.dest_file = widget.get_steam_data_path()
        else:
            self.cache_file()
        self.emit("file-available")