/*
* This file is part of Cockpit.
*
* Copyright (C) 2018 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
import cockpit from "cockpit";
import React from "react";
import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { WarningTriangleIcon } from "@patternfly/react-icons";
import { show_modal_dialog } from "cockpit-components-dialog.jsx";
import * as PK from "packagekit.js";
import "cockpit-components-install-dialog.css";
const _ = cockpit.gettext;
// TODO - generalize this to arbitrary number of arguments (when needed)
function format_to_fragments(fmt, arg) {
const index = fmt.indexOf("$0");
if (index >= 0)
return <>{fmt.slice(0, index)}{arg}{fmt.slice(index + 2)}>;
else
return fmt;
}
/* Calling install_dialog will open a dialog that lets the user
* install the given package.
*
* The install_dialog function returns a promise that is fulfilled when the dialog closes after
* a successful installation. The promise is rejected when the user cancels the dialog.
*
* If the package is already installed before the dialog opens, we still go
* through all the motions and the dialog closes successfully without doing
* anything when the use hits "Install".
*
* You shouldn't call install_dialog unless you know that PackageKit is available.
* (If you do anyway, the resulting D-Bus errors will be shown to the user.)
*/
export function install_dialog(pkg, options) {
let data = null;
let error_message = null;
let progress_message = null;
let cancel = null;
let done = null;
if (!Array.isArray(pkg))
pkg = [pkg];
options = options || { };
const prom = new Promise((resolve, reject) => { done = f => { if (f) resolve(); else reject(); } });
let dialog = null;
function update() {
let extra_details = null;
let remove_details = null;
let footer_message = null;
const missing_name = {pkg.join(", ")};
if (data && data.extra_names.length > 0)
extra_details = (
{_("Additional packages:")}
{data.extra_names.map(id => - {id}
)}
);
if (data && data.remove_names.length > 0)
remove_details = (
{_("Removals:")}
{data.remove_names.map(id => - {id}
)}
);
if (progress_message)
footer_message = (
{ progress_message }
);
else if (data?.download_size) {
footer_message = (
{ format_to_fragments(_("Total size: $0"), {cockpit.format_bytes(data.download_size)}) }
);
}
const body = {
id: "dialog",
title: options.title || _("Install software"),
body: (
{ format_to_fragments(options.text || _("$0 will be installed."), missing_name) }
{ remove_details }
{ extra_details }
),
static_error: error_message,
};
const footer = {
actions: [
{
caption: _("Install"),
style: "primary",
clicked: install_missing,
disabled: data == null
}
],
idle_message: footer_message,
dialog_done: f => { if (!f && cancel) cancel(); done(f) }
};
if (dialog) {
dialog.setProps(body);
dialog.setFooterProps(footer);
} else {
dialog = show_modal_dialog(body, footer);
}
}
function check_missing() {
PK.check_missing_packages(pkg,
p => {
cancel = p.cancel;
let pm = null;
if (p.waiting)
pm = _("Waiting for other software management operations to finish");
else
pm = _("Checking installed software");
if (pm != progress_message) {
progress_message = pm;
update();
}
})
.then(d => {
if (d.unavailable_names.length > 0)
error_message = cockpit.format(_("$0 is not available from any repository."),
d.unavailable_names[0]);
else
data = d;
progress_message = null;
cancel = null;
update();
})
.catch(e => {
progress_message = null;
cancel = null;
error_message = e.toString();
update();
});
}
function install_missing() {
// We need to return a Cockpit flavoured promise since we want
// to use progress notifications.
const dfd = cockpit.defer();
PK.install_missing_packages(data,
p => {
let text = null;
if (p.waiting) {
text = _("Waiting for other software management operations to finish");
} else if (p.package) {
let fmt;
if (p.info == PK.Enum.INFO_DOWNLOADING)
fmt = _("Downloading $0");
else if (p.info == PK.Enum.INFO_REMOVING)
fmt = _("Removing $0");
else
fmt = _("Installing $0");
text = format_to_fragments(fmt, {p.package});
}
dfd.notify(text, p.cancel);
})
.then(() => {
dfd.resolve();
})
.catch(error => {
dfd.reject(error);
});
return dfd.promise;
}
update();
check_missing();
return prom;
}