Пример #1
0
class UnpackTab(QtWidgets.QWidget):
    """The unpack window, that sets up a .RPZ file in a directory.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    unpacked = QtCore.Signal(str, object)

    def __init__(self, package='', **kwargs):
        super(UnpackTab, self).__init__(**kwargs)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QLabel("RPZ package:"), 0, 0)
        self.package_widget = QtWidgets.QLineEdit(package, enabled=False)
        layout.addWidget(self.package_widget, 0, 1)
        browse_pkg = QtWidgets.QPushButton("Browse")
        browse_pkg.clicked.connect(self._browse_pkg)
        layout.addWidget(browse_pkg, 0, 2)

        layout.addWidget(QtWidgets.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        ulayout = QtWidgets.QVBoxLayout()
        self.unpackers = QtWidgets.QButtonGroup()
        for i, name in enumerate(n for n, c in self.UNPACKERS):
            radio = QtWidgets.QRadioButton(name)
            radio.unpacker = name
            self.unpackers.addButton(radio, i)
            ulayout.addWidget(radio)
        layout.addLayout(ulayout, 1, 1, 1, 2)

        group = QtWidgets.QGroupBox(title="Unpacker options")
        group_layout = QtWidgets.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        self.unpackers.buttonClicked[int].connect(
            self.unpacker_options.setCurrentIndex)
        scroll = QtWidgets.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 2, 0, 1, 3)
        layout.setRowStretch(2, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(
            QtWidgets.QLabel("Select an unpacker to display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        layout.addWidget(QtWidgets.QLabel("Destination directory:"), 3, 0)
        self.directory_widget = QtWidgets.QLineEdit()
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 3, 1)
        browse_dir = QtWidgets.QPushButton("Browse")
        browse_dir.clicked.connect(self._browse_dir)
        layout.addWidget(browse_dir, 3, 2)

        buttons = QtWidgets.QHBoxLayout()
        buttons.addStretch(1)
        self.unpack_widget = QtWidgets.QPushButton("Unpack experiment",
                                                   enabled=False)
        self.unpack_widget.clicked.connect(self._unpack)
        buttons.addWidget(self.unpack_widget)
        layout.addLayout(buttons, 4, 0, 1, 3)

        self.setLayout(layout)

        self._package_changed()

    def replaceable(self):
        return not self.package_widget.text()

    def _browse_pkg(self):
        picked, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "Pick package file", QtCore.QDir.currentPath(),
            "ReproZip Packages (*.rpz)")
        if picked:
            record_usage(browse_pkg=True)
            self.package_widget.setText(picked)
            self._package_changed()

    def _package_changed(self, new_pkg=None):
        package = self.package_widget.text()
        if package.lower().endswith('.rpz'):
            self.directory_widget.setText(package[:-4])
            self._directory_changed()

    def _browse_dir(self):
        picked, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, "Pick directory", QtCore.QDir.currentPath())
        if picked:
            if os.path.exists(picked):
                error_msg(self, "This directory already exists", 'warning')
            else:
                self.directory_widget.setText(picked)
                self._directory_changed()

    def _directory_changed(self, new_dir=None):
        self.unpack_widget.setEnabled(bool(self.directory_widget.text()))

    def _unpack(self):
        directory = self.directory_widget.text()
        if not directory:
            return
        unpacker = self.unpackers.checkedButton()
        if unpacker:
            record_usage(unpacker=unpacker.text())
            options = self.unpacker_options.currentWidget().options()
            if options is None:
                return
            if handle_error(
                    self,
                    reprounzip.unpack(self.package_widget.text(),
                                      unpacker.unpacker, directory, options)):
                self.unpacked.emit(os.path.abspath(directory),
                                   options.get('root'))
        else:
            error_msg(self, "No unpacker selected", 'warning')
Пример #2
0
class RunTab(QtWidgets.QWidget):
    """The main window, that allows you to run/change an unpacked experiment.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    directory = None
    unpacker = None

    def __init__(self, unpacked_directory='', **kwargs):
        super(RunTab, self).__init__(**kwargs)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QLabel("Experiment directory:"), 0, 0)
        self.directory_widget = QtWidgets.QLineEdit(unpacked_directory)
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 0, 1)
        browse = QtWidgets.QPushButton("Browse")
        browse.clicked.connect(self._browse)
        layout.addWidget(browse, 0, 2)

        layout.addWidget(QtWidgets.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        self.unpacker_widget = QtWidgets.QLabel("-")
        layout.addWidget(self.unpacker_widget, 1, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Input/output files:"), 2, 0,
                         QtCore.Qt.AlignTop)
        self.files_button = QtWidgets.QPushButton("Manage files",
                                                  enabled=False)
        self.files_button.clicked.connect(self._open_files_manager)
        layout.addWidget(self.files_button, 2, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Runs:"), 3, 0, QtCore.Qt.AlignTop)
        self.runs_widget = QtWidgets.QListWidget(
            selectionMode=QtWidgets.QListWidget.MultiSelection)
        layout.addWidget(self.runs_widget, 3, 1, 3, 1)
        select_all = QtWidgets.QPushButton("Select All")
        select_all.clicked.connect(self.runs_widget.selectAll)
        layout.addWidget(select_all, 3, 2)
        deselect_all = QtWidgets.QPushButton("Deselect All")
        deselect_all.clicked.connect(self.runs_widget.clearSelection)
        layout.addWidget(deselect_all, 4, 2)

        layout.addWidget(QtWidgets.QLabel("Elevate privileges:"), 6, 0)
        self.root = QtWidgets.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        layout.addWidget(self.root, 6, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Jupyter integration:"), 7, 0)
        self.run_jupyter_notebook = QtWidgets.QCheckBox("Run notebook server",
                                                        checked=False,
                                                        enabled=False)
        layout.addWidget(self.run_jupyter_notebook, 7, 1, 1, 2)

        group = QtWidgets.QGroupBox(title="Unpacker options")
        group_layout = QtWidgets.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        scroll = QtWidgets.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 8, 0, 1, 3)
        layout.setRowStretch(8, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(
            QtWidgets.QLabel("Select a directory to display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        buttons = QtWidgets.QHBoxLayout()
        buttons.addStretch(1)
        self.run_widget = QtWidgets.QPushButton("Run experiment")
        self.run_widget.clicked.connect(self._run)
        buttons.addWidget(self.run_widget)
        self.destroy_widget = QtWidgets.QPushButton("Destroy unpacked "
                                                    "experiment")
        self.destroy_widget.clicked.connect(self._destroy)
        buttons.addWidget(self.destroy_widget)
        layout.addLayout(buttons, 9, 0, 1, 3)

        self.setLayout(layout)

        self._directory_changed()

    def _browse(self):
        picked = QtWidgets.QFileDialog.getExistingDirectory(
            self, "Pick directory", QtCore.QDir.currentPath())
        if picked:
            record_usage(browse_unpacked=True)
            self.directory_widget.setText(picked)
            self._directory_changed()

    def _directory_changed(self, new_dir=None, force=False):
        if not force and self.directory_widget.text() == self.directory:
            return
        self.directory = self.directory_widget.text()

        unpacker = reprounzip.check_directory(self.directory)

        self.run_jupyter_notebook.setChecked(False)
        self.run_jupyter_notebook.setEnabled(False)

        self.runs_widget.clear()
        if unpacker is not None:
            with open(self.directory + '/config.yml') as fp:
                self.config = yaml.safe_load(fp)
            self.run_widget.setEnabled(True)
            self.destroy_widget.setEnabled(True)
            self.files_button.setEnabled(True)
            self.unpacker = unpacker
            self.unpacker_widget.setText(unpacker)
            for run in self.config['runs']:
                self.runs_widget.addItem(' '.join(
                    reprounzip.shell_escape(arg) for arg in run['argv']))
            self.runs_widget.selectAll()
            self.unpacker_options.setCurrentIndex(
                dict((n, i)
                     for i, (n,
                             w) in enumerate(self.UNPACKERS)).get(unpacker, 4))

            if (unpacker == 'docker'
                    and reprounzip.find_command('reprozip-jupyter') is not None
                    and reprounzip.is_jupyter(self.directory)):
                self.run_jupyter_notebook.setEnabled(True)
                self.run_jupyter_notebook.setChecked(True)
        else:
            self.run_widget.setEnabled(False)
            self.destroy_widget.setEnabled(False)
            self.files_button.setEnabled(False)
            self.unpacker = None
            self.unpacker_widget.setText("-")
            self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

    def _run(self):
        options = self.unpacker_options.currentWidget().options()
        if options is None:
            return
        runs = sorted(i.row() for i in self.runs_widget.selectedIndexes())
        if not runs:
            error_msg(self, "No run selected", 'warning')
            return
        record_usage(run='%d/%d' % (len(runs), self.runs_widget.count()))
        handle_error(
            self,
            reprounzip.run(self.directory,
                           runs=runs,
                           unpacker=self.unpacker,
                           root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()],
                           jupyter=self.run_jupyter_notebook.isChecked(),
                           **options))

    def _destroy(self):
        handle_error(
            self,
            reprounzip.destroy(
                self.directory,
                unpacker=self.unpacker,
                root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()]))
        self._directory_changed(force=True)

    def _open_files_manager(self):
        manager = FilesManager(
            parent=self,
            directory=self.directory_widget.text(),
            unpacker=self.unpacker,
            root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()])
        manager.exec_()

    def set_directory(self, directory, root=None):
        self.root.setCurrentIndex(ROOT.OPTION_TO_INDEX[root])
        self.directory_widget.setText(directory)
        self._directory_changed(force=True)

    def should_exit(self):
        if self.unpacker:
            r = QtWidgets.QMessageBox.question(
                self, "Close Confirmation",
                "The experiment is still unpacked with '%s'. Are you sure you "
                "want to exit without removing it?" % self.unpacker,
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
            if r == QtWidgets.QMessageBox.Yes:
                record_usage(leave_unpacked=True)
                return True
            else:
                return False
        else:
            return True

    def replaceable(self):
        return not self.unpacker
Пример #3
0
class UnpackTab(QtWidgets.QWidget):
    """The unpack window, that sets up a .RPZ file in a directory.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    unpacked = QtCore.Signal(str, object)

    def __init__(self, package='', **kwargs):
        super(UnpackTab, self).__init__(**kwargs)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QLabel("RPZ package:"), 0, 0)
        self.package_widget = QtWidgets.QLineEdit(package, enabled=False)
        layout.addWidget(self.package_widget, 0, 1)
        browse_pkg = QtWidgets.QPushButton("Browse")
        browse_pkg.clicked.connect(self._browse_pkg)
        layout.addWidget(browse_pkg, 0, 2)

        layout.addWidget(QtWidgets.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        ulayout = QtWidgets.QVBoxLayout()
        self.unpackers = QtWidgets.QButtonGroup()
        for i, name in enumerate(n for n, c in self.UNPACKERS):
            radio = QtWidgets.QRadioButton(name)
            radio.unpacker = name
            self.unpackers.addButton(radio, i)
            ulayout.addWidget(radio)
        layout.addLayout(ulayout, 1, 1, 1, 2)

        group = QtWidgets.QGroupBox(title="Unpacker options")
        group_layout = QtWidgets.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        self.unpackers.buttonClicked[int].connect(
            self.unpacker_options.setCurrentIndex)
        scroll = QtWidgets.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 2, 0, 1, 3)
        layout.setRowStretch(2, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(
            QtWidgets.QLabel("Select an unpacker to display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        layout.addWidget(QtWidgets.QLabel("Destination directory:"), 3, 0)
        self.directory_widget = QtWidgets.QLineEdit()
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 3, 1)
        browse_dir = QtWidgets.QPushButton("Browse")
        browse_dir.clicked.connect(self._browse_dir)
        layout.addWidget(browse_dir, 3, 2)

        buttons = QtWidgets.QHBoxLayout()
        buttons.addStretch(1)
        self.unpack_widget = QtWidgets.QPushButton("Unpack experiment",
                                                   enabled=False)
        self.unpack_widget.clicked.connect(self._unpack)
        buttons.addWidget(self.unpack_widget)
        layout.addLayout(buttons, 4, 0, 1, 3)

        self.setLayout(layout)

        self._package_changed()

    def replaceable(self):
        return not self.package_widget.text()

    def _browse_pkg(self):
        picked, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "Pick package file",
            QtCore.QDir.currentPath(), "ReproZip Packages (*.rpz)")
        if picked:
            record_usage(browse_pkg=True)
            self.package_widget.setText(picked)
            self._package_changed()

    def _package_changed(self, new_pkg=None):
        package = self.package_widget.text()
        if package.lower().endswith('.rpz'):
            self.directory_widget.setText(package[:-4])
            self._directory_changed()

    def _browse_dir(self):
        picked, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, "Pick directory",
            QtCore.QDir.currentPath())
        if picked:
            if os.path.exists(picked):
                error_msg(self, "This directory already exists", 'warning')
            else:
                self.directory_widget.setText(picked)
                self._directory_changed()

    def _directory_changed(self, new_dir=None):
        self.unpack_widget.setEnabled(bool(self.directory_widget.text()))

    def _unpack(self):
        directory = self.directory_widget.text()
        if not directory:
            return
        unpacker = self.unpackers.checkedButton()
        if unpacker:
            record_usage(unpacker=unpacker.text())
            options = self.unpacker_options.currentWidget().options()
            if options is None:
                return
            if handle_error(self, reprounzip.unpack(
                    self.package_widget.text(),
                    unpacker.unpacker,
                    directory,
                    options)):
                self.unpacked.emit(os.path.abspath(directory),
                                   options.get('root'))
        else:
            error_msg(self, "No unpacker selected", 'warning')
Пример #4
0
class RunTab(QtWidgets.QWidget):
    """The main window, that allows you to run/change an unpacked experiment.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    directory = None
    unpacker = None

    def __init__(self, unpacked_directory='', **kwargs):
        super(RunTab, self).__init__(**kwargs)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QLabel("Experiment directory:"), 0, 0)
        self.directory_widget = QtWidgets.QLineEdit(unpacked_directory)
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 0, 1)
        browse = QtWidgets.QPushButton("Browse")
        browse.clicked.connect(self._browse)
        layout.addWidget(browse, 0, 2)

        layout.addWidget(QtWidgets.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        self.unpacker_widget = QtWidgets.QLabel("-")
        layout.addWidget(self.unpacker_widget, 1, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Input/output files:"), 2, 0,
                         QtCore.Qt.AlignTop)
        self.files_button = QtWidgets.QPushButton("Manage files",
                                                  enabled=False)
        self.files_button.clicked.connect(self._open_files_manager)
        layout.addWidget(self.files_button, 2, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Runs:"), 3, 0,
                         QtCore.Qt.AlignTop)
        self.runs_widget = QtWidgets.QListWidget(
            selectionMode=QtWidgets.QListWidget.MultiSelection)
        layout.addWidget(self.runs_widget, 3, 1, 3, 1)
        select_all = QtWidgets.QPushButton("Select All")
        select_all.clicked.connect(self.runs_widget.selectAll)
        layout.addWidget(select_all, 3, 2)
        deselect_all = QtWidgets.QPushButton("Deselect All")
        deselect_all.clicked.connect(self.runs_widget.clearSelection)
        layout.addWidget(deselect_all, 4, 2)

        layout.addWidget(QtWidgets.QLabel("Elevate privileges:"), 6, 0)
        self.root = QtWidgets.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        layout.addWidget(self.root, 6, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Jupyter integration:"),
                         7, 0)
        self.run_jupyter_notebook = QtWidgets.QCheckBox("Run notebook server",
                                                        checked=False,
                                                        enabled=False)
        layout.addWidget(self.run_jupyter_notebook, 7, 1, 1, 2)

        group = QtWidgets.QGroupBox(title="Unpacker options")
        group_layout = QtWidgets.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        scroll = QtWidgets.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 8, 0, 1, 3)
        layout.setRowStretch(8, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(
            QtWidgets.QLabel("Select a directory to display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        buttons = QtWidgets.QHBoxLayout()
        buttons.addStretch(1)
        self.run_widget = QtWidgets.QPushButton("Run experiment")
        self.run_widget.clicked.connect(self._run)
        buttons.addWidget(self.run_widget)
        self.destroy_widget = QtWidgets.QPushButton("Destroy unpacked "
                                                    "experiment")
        self.destroy_widget.clicked.connect(self._destroy)
        buttons.addWidget(self.destroy_widget)
        layout.addLayout(buttons, 9, 0, 1, 3)

        self.setLayout(layout)

        self._directory_changed()

    def _browse(self):
        picked = QtWidgets.QFileDialog.getExistingDirectory(
            self, "Pick directory",
            QtCore.QDir.currentPath())
        if picked:
            record_usage(browse_unpacked=True)
            self.directory_widget.setText(picked)
            self._directory_changed()

    def _directory_changed(self, new_dir=None, force=False):
        if not force and self.directory_widget.text() == self.directory:
            return
        self.directory = self.directory_widget.text()

        unpacker = reprounzip.check_directory(self.directory)

        self.run_jupyter_notebook.setChecked(False)
        self.run_jupyter_notebook.setEnabled(False)

        self.runs_widget.clear()
        if unpacker is not None:
            with open(self.directory + '/config.yml') as fp:
                self.config = yaml.safe_load(fp)
            self.run_widget.setEnabled(True)
            self.destroy_widget.setEnabled(True)
            self.files_button.setEnabled(True)
            self.unpacker = unpacker
            self.unpacker_widget.setText(unpacker)
            for run in self.config['runs']:
                self.runs_widget.addItem(' '.join(reprounzip.shell_escape(arg)
                                                  for arg in run['argv']))
            self.runs_widget.selectAll()
            self.unpacker_options.setCurrentIndex(
                dict((n, i) for i, (n, w) in enumerate(self.UNPACKERS))
                .get(unpacker, 4))

            if (unpacker == 'docker' and
                    reprounzip.find_command('reprozip-jupyter') is not None and
                    reprounzip.is_jupyter(self.directory)):
                self.run_jupyter_notebook.setEnabled(True)
                self.run_jupyter_notebook.setChecked(True)
        else:
            self.run_widget.setEnabled(False)
            self.destroy_widget.setEnabled(False)
            self.files_button.setEnabled(False)
            self.unpacker = None
            self.unpacker_widget.setText("-")
            self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

    def _run(self):
        options = self.unpacker_options.currentWidget().options()
        if options is None:
            return
        runs = sorted(i.row() for i in self.runs_widget.selectedIndexes())
        if not runs:
            error_msg(self, "No run selected", 'warning')
            return
        record_usage(run='%d/%d' % (len(runs), self.runs_widget.count()))
        handle_error(self, reprounzip.run(
            self.directory, runs=runs,
            unpacker=self.unpacker,
            root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()],
            jupyter=self.run_jupyter_notebook.isChecked(),
            **options))

    def _destroy(self):
        handle_error(self, reprounzip.destroy(
            self.directory, unpacker=self.unpacker,
            root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()]))
        self._directory_changed(force=True)

    def _open_files_manager(self):
        manager = FilesManager(
            parent=self,
            directory=self.directory_widget.text(),
            unpacker=self.unpacker,
            root=ROOT.INDEX_TO_OPTION[self.root.currentIndex()])
        manager.exec_()

    def set_directory(self, directory, root=None):
        self.root.setCurrentIndex(ROOT.OPTION_TO_INDEX[root])
        self.directory_widget.setText(directory)
        self._directory_changed(force=True)

    def should_exit(self):
        if self.unpacker:
            r = QtWidgets.QMessageBox.question(
                self, "Close Confirmation",
                "The experiment is still unpacked with '%s'. Are you sure you "
                "want to exit without removing it?" % self.unpacker,
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
            if r == QtWidgets.QMessageBox.Yes:
                record_usage(leave_unpacked=True)
                return True
            else:
                return False
        else:
            return True

    def replaceable(self):
        return not self.unpacker