ACC SHELL
/**
* File:
* include/add-on/add-on-workflow.ycp
*
* Module:
* System installation
*
* Summary:
* Add-on product installation workflow
*
* Authors:
* Jiri Srain <jsrain@suse.cz>
*
*
*/
{
textdomain "add-on";
import "AddOnProduct";
import "WorkflowManager";
import "Linuxrc";
import "Mode";
import "Popup";
import "Report";
import "Sequencer";
import "SourceManager";
import "PackageSystem";
import "SuSEFirewall";
import "Stage";
import "Wizard";
import "Confirm";
import "GetInstArgs";
import "Installation";
import "PackageCallbacks";
import "PackagesUI";
include "packager/inst_source_dialogs.ycp";
include "packager/repositories_include.ycp";
include "add-on/misc.ycp";
boolean going_back_in_workflow = GetInstArgs::going_back();
/**
* Initialize current inst. sources
*/
symbol Read () {
Pkg::SourceStartManager(true);
return `next;
}
/**
* Write (changed) inst. sources
*/
symbol Write () {
Pkg::SourceSaveAll();
return `next;
}
// Used for adding sources
symbol createResult = `again;
/**
* Checks whether some network is available in the current moment,
* see the bug #170147 for more information.
*/
boolean IsAnyNetworkAvailable () {
boolean ret = false;
string command = "TERM=dumb /sbin/ip -o address show | grep inet | grep -v scope.host";
y2milestone("Running %1", command);
map cmd_run = (map) SCR::Execute(.target.bash_output, command);
y2milestone("Command returned: %1", cmd_run);
// command failed
if (cmd_run["exit"]:-1 != 0) {
// some errors were there, we don't know the status, rather return that it's available
// `grep` also returns non zero exit code when there is nothing to do...
if (cmd_run["stdout"]:"" != "") {
y2error("Checking the network failed");
ret = true;
}
// some devices are listed
} else if (cmd_run["stdout"]:"" != nil && cmd_run["stdout"]:"" != "" && cmd_run["stdout"]:"" != "\n") {
ret = true;
}
return ret;
}
/**
* Returns begining string for source type
*
* @param symbol source_type
* @return string url begins with...
*/
string GetURLBeginsWith (symbol source_type) {
string url = "";
if ( source_type == `ftp ) url = "ftp://";
else if ( source_type == `http ) url = "http://";
else if ( source_type == `https ) url = "https://";
else if ( source_type == `samba ) url = "smb://";
else if ( source_type == `nfs ) url = "nfs://";
else if ( source_type == `cd ) url = "cd:///";
else if ( source_type == `dvd ) url = "dvd:///";
else if ( source_type == `local_dir ) url = "dir://";
return url;
}
// used Add-Ons are stored in AddOnProduct::add_on_products
// bnc #393620
void AddAddOnToStore (integer src_id) {
if (src_id == nil) {
y2error ("Wrong src_id: %1", src_id);
return;
}
// BNC #450274
// Prevent from adding one product twice
list <map> matching_products = filter (map one_product, AddOnProduct::add_on_products, {
return (one_product["media"]:-1 == src_id);
});
if (size (matching_products) > 0) {
y2milestone ("Product already added: %1", matching_products);
return;
}
map source_data = Pkg::SourceGeneralData (src_id);
AddOnProduct::add_on_products = add (AddOnProduct::add_on_products, $[
"media" : AddOnProduct::src_id,
// table cell
"product" : source_data["name"]:source_data["alias"]:_("No product found in the repository"),
"media_url" : source_data["url"]:"",
"product_dir" : source_data["product_dir"]:"",
]);
}
/**
* Run dialog for selecting the media
* @return symbol for wizard sequencer
*/
symbol MediaSelect () {
map<string,any> aliases = $[
"type" : ``(TypeDialog()),
"edit" : ``(EditDialog ()),
"store" : ``(StoreSource ()),
];
list <integer> sources_before = Pkg::SourceGetCurrent (false);
y2milestone ("Sources before adding new one: %1", sources_before);
map sequence = $[
"ws_start" : "type",
"type" : $[
`next : "edit",
// bnc #392083
`finish : "store",
`abort : `abort,
],
"edit" : $[
`next : "store",
// bnc #392083
`finish : "store",
`abort : `abort,
],
"store" : $[
`next : `next,
// bnc #392083
`finish : `next,
`abort : `abort,
],
];
y2milestone ("Starting repository sequence");
symbol ret = Sequencer::Run (aliases, sequence);
list <integer> sources_after = Pkg::SourceGetCurrent (false);
y2milestone ("Sources with new one added: %1", sources_after);
// bnc #393011
// AddOnProduct::src_id must be set to the latest source ID
boolean src_id_found = false;
foreach (integer one_source, sources_after, {
if (! contains (sources_before, one_source)) {
AddOnProduct::src_id = one_source;
y2milestone ("Added source ID is: %1", AddOnProduct::src_id);
src_id_found = true;
break;
}
});
if (src_id_found) {
// used add-ons are stored in a special list
AddAddOnToStore (AddOnProduct::src_id);
} else {
AddOnProduct::src_id = sources_after[size(sources_after) - 1]:0;
y2warning ("Fallback src_id: %1", AddOnProduct::src_id);
}
// BNC #441380
// Refresh and load the added source, this is needed since the unified
// functions from packager are used.
Pkg::SourceRefreshNow (AddOnProduct::src_id);
Pkg::SourceLoad();
// BNC #468449
// It may happen that the add-on control file contains some code that
// would drop the changes made, so it's better to save the soruces now
if (Mode::normal()) {
y2milestone ("Saving all sources");
Pkg::SourceSaveAll();
}
AddOnProduct::last_ret = ret;
y2milestone ("MediaSelect Dialog ret: %1", ret);
return ret;
}
string new_addon_name = "";
// bugzilla #304659
/**
* Sets the Add-On's product name
*
* @param integer src_id (source ID)
*/
void SetAddOnProductName (integer src_id) {
new_addon_name = "";
if (src_id == nil) {
y2error ("Cannot set name, no ID!");
return;
}
new_addon_name = SourceDialogs::GetRepoName();
// no name to change to
if (new_addon_name == nil || new_addon_name == "") {
y2milestone ("No special name set");
return;
}
list <map <string,any> > new_addon_set = [
$[ "SrcId" : src_id, "name" : new_addon_name ]
];
boolean result = Pkg::SourceEditSet (new_addon_set);
y2milestone ("Adjusting Add-On: %1 returned: %2", new_addon_set, result);
// do not use it next time
SourceDialogs::SetRepoName ("");
}
/**
* Run dialog for selecting the catalog on the media (if more than one present)
* @return symbol for wizard sequencer
*/
symbol CatalogSelect () {
list<integer> sources = SourceManager::newSources;
y2milestone("New sources: %1", sources);
if (size (sources) == 0)
{
// error report
Report::Error (_("No software repository found on medium."));
y2milestone ("CatalogSelect Dialog ret: %1", `back);
return `back;
}
if (size (sources) == 1)
{
if (AddOnProduct::last_ret != `next)
{
y2milestone ("Deleting source %1", sources[0]:0);
Pkg::SourceDelete(sources[0]:0);
y2milestone ("CatalogSelect Dialog ret: %1", AddOnProduct::last_ret);
return AddOnProduct::last_ret;
}
// busy message
UI::OpenDialog(`Label(`id (`add_on_popup_id), _("Initializing new source...")));
integer src_id = SourceManager::newSources[0]:0;
map data = Pkg::SourceGeneralData (src_id);
y2milestone("Adding product: %1", data);
string url = data["url"]:"";
string product_dir = data["product_dir"]:"";
y2milestone ("Deleting source %1", src_id);
Pkg::SourceDelete (src_id);
src_id = Pkg::SourceCreate (url, product_dir);
SourceManager::newSources = [src_id];
// a little hack because of packager leaving
// windows open...
if (UI::WidgetExists(`add_on_popup_id)) {
UI::CloseDialog();
} else if (UI::WidgetExists(`contents)) {
y2warning ("Already in base dialog!");
} else {
y2error ("Error in packager, closing current dialog!");
while (! UI::WidgetExists(`contents)) {
y2milestone ("Calling UI::CloseDialog");
UI::CloseDialog();
}
}
if (src_id == -1)
{
// error report
Report::Error (_("Failed to initialize the software repository."));
AddOnProduct::ClearRegistrationRequest (src_id);
y2milestone ("CatalogSelect Dialog ret: %1", AddOnProduct::last_ret);
return `finish;
}
AddOnProduct::src_id = src_id;
SourceManager::newSources = [src_id];
y2milestone ("Only one source available - skipping dialog");
y2milestone ("CatalogSelect Dialog ret: %1", AddOnProduct::last_ret);
return AddOnProduct::last_ret;
}
y2milestone ("Running catalog select dialog");
list catalogs = maplist (integer src, sources, {
map data = Pkg::SourceGeneralData (src);
// placeholder for unknown directory
string dir = data["product_dir"]:_("Unknown");
if (dir == "")
dir = "/";
return `item (`id (src), sformat (_("URL: %1, Directory: %2"),
// place holder for unknown URL
data["url"]:_("Unknown"), dir));
});
// dialog caption
string title = _("Software Repository Selection");
// help text
string help_text = _("<p><big><b>Software Repository Selection</b></big><br>
Multiple repositories were found on the selected medium.
Select the repository to use.</p>
");
term contents = `HBox (`HSpacing(4), `VBox (
`VSpacing (2),
`SelectionBox (`id (`catalogs), _("Repositories &Found"), catalogs),
`VSpacing (2)
), `HSpacing (4));
Wizard::SetContents (title, contents, help_text, true, true);
symbol ret = nil;
integer selected = nil;
while (ret == nil)
{
ret = (symbol)UI::UserInput ();
if (ret == `abort || ret == `cancel)
{
ret = `abort;
// if (Stage::initial())
// {
// if (Popup::ConfirmAbort (`painless))
// break;
// }
// else
// {
// yes-no popup
if (Popup::YesNo (_("Really abort add-on product installation?")))
break;
// }
continue;
}
else if (ret == `back)
{
break;
}
else if (ret == `next)
{
selected = (integer)UI::QueryWidget (`id (`catalogs), `CurrentItem);
if (selected == nil)
{
ret = nil;
// popup message
Popup::Message (_("Select a repository."));
}
}
}
if (ret != `next)
{
foreach (integer src, SourceManager::newSources, {
y2milestone ("Deleting source %1", src);
Pkg::SourceDelete (src);
});
}
else
{
foreach (integer src, SourceManager::newSources, {
if (src != selected)
{
y2milestone ("Deleting source %1", src);
Pkg::SourceDelete (src);
}
});
map data = Pkg::SourceGeneralData (selected);
string url = data["url"]:"";
string product_dir = data["product_dir"]:"";
y2milestone ("Deleting source %1", selected);
Pkg::SourceDelete (selected);
selected = Pkg::SourceCreate (url, product_dir);
SourceManager::newSources = [selected];
if (selected == -1)
{
// error report
Report::Error (_("Failed to initialize the software repository."));
AddOnProduct::ClearRegistrationRequest (selected);
y2milestone ("CatalogSelect Dialog ret: %1", `finish);
return `finish;
}
AddOnProduct::src_id = selected;
SourceManager::newSources = [selected];
}
AddOnProduct::last_ret = ret;
y2milestone ("CatalogSelect Dialog ret: %1", AddOnProduct::last_ret);
return ret;
}
// BNC #474745
// Installs all the products from just added add-on media
symbol InstallProduct () {
y2milestone ("AddOnID: %1", AddOnProduct::src_id);
if (AddOnProduct::src_id == nil || AddOnProduct::src_id < 0) {
y2error ("No source has been added");
return `next;
}
list <map <string, any> > all_products = Pkg::ResolvableProperties ("", `product, "");
// `selected and `installed products
// each map contains "name" and "version"
list <map> s_a_i_products = [];
foreach (map <string, any> one_product, all_products, {
s_a_i_products = add (s_a_i_products,
$["name":one_product["name"]:"xyz", "version":one_product["version"]:"abc"]
);
});
foreach (map <string, any> one_product, all_products, {
// map this_product = $["name":one_product["name"]:"zyx", "version":one_product["version"]:"cba"];
// Product doesn't match the new source ID
if (one_product["source"]:-255 != AddOnProduct::src_id) {
return;
}
// Product is not available (either `installed or `selected or ...)
if (one_product["status"]:`available != `available) {
return;
}
// // Available but also already installed or selected
// if (contains (s_a_i_products, this_product)) {
// y2warning ("Product %1 is already installed", this_product);
// return;
// }
string product_name = one_product["name"]:"-Unknown-Product-";
y2milestone (
"Selecting product '%1' for installation -> %2",
product_name,
Pkg::ResolvableInstall (product_name, `product)
);
});
return `next;
}
symbol ProductSelect () {
list<map<string,any> > all_products = Pkg::ResolvableProperties ("", `product, "");
map <string, string> already_used_urls = $[];
// getting all source urls and product_dirs
foreach (map<string,any> p, all_products, {
integer one_src_id = tointeger(p["source"]:nil);
if (one_src_id == nil) return;
// the last source (just added)
if (one_src_id == AddOnProduct::src_id) return;
map <string, any> src_general_data = Pkg::SourceGeneralData (one_src_id);
string source_url = src_general_data["url"]:"";
if (source_url != "" && source_url != nil) {
already_used_urls[source_url] = src_general_data["product_dir"]:"";
}
});
y2milestone ("Already used urls with product_dirs: %1", already_used_urls);
list<map<string,any> > installed_products = filter (map<string,any> p, all_products, {
return p["status"]:nil == `selected || p["status"]:nil == `installed;
});
y2milestone ("Already installed/selected products: %1", installed_products);
list<map<string,any> > products = filter (map<string,any> p, all_products, {
return p["source"]:-1 == AddOnProduct::src_id;
});
y2milestone ("Products on the media: %1", products);
// there are no product on the given url
if (size (products) == 0)
{
y2milestone ("No poduct found on the media, but anyway, using it :-)");
// Display /media.1/info.txt if such file exists
// Display license and wait for agreement
// FIXME the same code is below
boolean license_ret = AddOnProduct::AcceptedLicenseAndInfoFile(AddOnProduct::src_id);
if (license_ret != true) {
y2milestone("Removing the current source ID %1", AddOnProduct::src_id);
Pkg::SourceDelete(AddOnProduct::src_id);
y2milestone ("ProductSelect Dialog ret: %1", `abort);
return `abort;
}
map data = Pkg::SourceGeneralData (AddOnProduct::src_id);
string url = data["url"]:"";
string product_dir = data["product_dir"]:"";
// bugzilla #304659
SetAddOnProductName (AddOnProduct::src_id);
AddOnProduct::add_on_products = add (AddOnProduct::add_on_products, $[
"media" : AddOnProduct::src_id,
// table cell
"product" : ((new_addon_name != "" && new_addon_name != nil) ? new_addon_name : _("No product found in the repository")),
"media_url" : url,
"product_dir" : product_dir,
]);
if (Mode::config ())
{
AddOnProduct::mode_config_sources
= add (AddOnProduct::mode_config_sources, AddOnProduct::src_id);
}
y2milestone ("ProductSelect Dialog ret: %1", `next);
return `next;
}
// disabling functionality (partly bugzilla #227605)
// we know whether this product is already installed
// but we can't know whether the source hasn't been already removed
//
// products = filter (map<string,any> prod, products, {
// boolean installed = false;
// find (map<string,any> p, installed_products, {
// if (p["name"]:"" == prod["name"]:"" && p["version"]:"" == prod["version"]:"")
// {
// installed = true;
// y2milestone ("Product %1 installed", p);
// return true;
// }
// return false;
// });
// if (installed)
// {
// y2milestone ("Removing %1 from the list of available products", prod);
// return false;
// }
// return true;
// });
// if (size (products) == 0)
// {
// // message popup
// Popup::Message (_("The product on the media is already installed
//or selected for installation."));
// y2milestone ("Deleting installatino source %1", AddOnProduct::src_id);
// Pkg::SourceDelete (AddOnProduct::src_id);
// AddOnProduct::ClearRegistrationRequest (AddOnProduct::src_id);
// return `finish;
// }
// Display /media.1/info.txt if such file exists
// Display license and wait for agreement
// FIXME the same code is above
boolean license_ret = AddOnProduct::AcceptedLicenseAndInfoFile(AddOnProduct::src_id);
if (license_ret != true) {
y2milestone("Removing the current source ID %1", AddOnProduct::src_id);
Pkg::SourceDelete(AddOnProduct::src_id);
y2milestone ("ProductSelect Dialog ret: %1", `abort);
return `abort;
}
// there is only one product on the given url
if (size (products) == 1)
{
// bugzilla #227605
// this product with this url has been already installed or selected for installation
map <string, any> src_general_data = Pkg::SourceGeneralData (AddOnProduct::src_id);
string current_url = src_general_data["url"]:"";
if (current_url != "" && current_url != nil) {
y2milestone ("%1", already_used_urls);
if (already_used_urls[current_url]:"" == src_general_data["product_dir"]:"") {
// error pop-up
Popup::Message (_("The product on the media is already installed
or selected for installation."));
y2milestone ("Deleting installation source %1 %2 (%3)",
AddOnProduct::src_id, current_url, src_general_data["product_dir"]:"");
Pkg::SourceDelete (AddOnProduct::src_id);
AddOnProduct::ClearRegistrationRequest (AddOnProduct::src_id);
y2milestone ("ProductSelect Dialog ret: %1", `finish);
return `finish;
}
}
y2milestone ("Only one product available - skipping dialog");
map<string,any> prod = products[0]:$[];
if (! AddOnProduct::CheckProductDependencies ([prod["name"]:""]))
{
Pkg::ResolvableRemove (prod["name"]:"", `product);
// message popup
Popup::Message (_("Dependencies of the add-on product cannot be fulfilled."));
AddOnProduct::last_ret = `back;
y2milestone ("ProductSelect Dialog ret: %1", `back);
return `back;
}
// check whether the product is already available on some media - it is similar as below
integer found_source = -1;
foreach (map<string,any> p, all_products, {
if (p["name"]:"" == prod["name"]:"" && p["version"]:"" == prod["version"]:""
&& p["media"]:-2 != prod["media"]:-3)
{
y2milestone ("Product %1 already available on media %2", p, p["media"]:-1);
found_source = p["media"]:-1;
break;
}
});
if (found_source != -1)
{
y2milestone ("Deleting source %1", AddOnProduct::src_id);
Pkg::SourceDelete (AddOnProduct::src_id);
AddOnProduct::src_id = found_source;
}
Pkg::ResolvableInstall (prod["name"]:"", `product);
map data = Pkg::SourceGeneralData (AddOnProduct::src_id);
string url = data["url"]:"";
string product_dir = data["product_dir"]:"";
// bugzilla #304659
SetAddOnProductName (AddOnProduct::src_id);
AddOnProduct::add_on_products = add (AddOnProduct::add_on_products, $[
"media" : AddOnProduct::src_id,
"product" : ((new_addon_name != "" && new_addon_name != nil) ? new_addon_name : prod["display_name"]:prod["short_name"]:prod["name"]:""),
"autoyast_product" : prod["name"]:"",
"media_url" : url,
"product_dir" : product_dir,
]);
if (found_source == -1 && Mode::config ())
{
AddOnProduct::mode_config_sources =
add (AddOnProduct::mode_config_sources, AddOnProduct::src_id);
}
y2milestone ("ProductSelect Dialog ret: %1", `next);
return `next;
}
// there are more than one products on the given url
y2milestone ("Running product selection dialog");
symbol ret = nil;
list items = maplist (map<string,any> product, products, {
return `item (`id (product["name"]:""), product["name"]:"");
});
// dialog caption
string title = _("Product Selection");
term contents = `HBox (`HStretch(), `VBox (
`VStretch (),
// multi selection list
`MultiSelectionBox (`id (`products), _("Available Products"), items),
`VStretch ()
), `HStretch ());
// help text
string help_text = _("<p><b><big>Product Selection</big></b><br/>
Multiple products were found in the repository. Select the products
to install.</p>
");
Wizard::SetContents (title, contents, help_text, true, true);
while (ret == nil)
{
ret = (symbol)UI::UserInput();
if (ret == `cancel || ret == `abort)
{
ret = `abort;
// if (Stage::initial())
// {
// if (Popup::ConfirmAbort (`painless))
// break;
// }
// else
// {
// yes-no popup
if (Popup::YesNo (_("Really abort add-on product installation?")))
break;
// }
continue;
}
if (ret == `next)
{
list<string> selected = (list<string>)UI::QueryWidget (`id (`products), `SelectedItems);
// check whether the product is already available on some media - it is similar as above
list<map<string,any> > prods = filter (map<string,any> p, products, {
return contains (selected, p["name"]:"");
});
boolean all_found = true;
map<string,integer> prod2src = $[];
foreach (map<string,any> prod, prods, {
boolean product_found = false;
foreach (map<string,any> p, all_products, {
if (p["name"]:"" == prod["name"]:"" && p["version"]:"" == prod["version"]:""
&& p["media"]:-2 != prod["media"]:-3)
{
product_found = true;
prod2src[prod["name"]:""] = p["media"]:-3;
break;
}
});
all_found = all_found && product_found;
});
if (all_found)
{
y2milestone ("Deleting source %1", AddOnProduct::src_id);
Pkg::SourceDelete (AddOnProduct::src_id);
AddOnProduct::src_id = -1;
}
foreach (string product, selected, {
Pkg::ResolvableInstall (product, `product);
});
if (! AddOnProduct::CheckProductDependencies (selected))
{
foreach (string product, selected, {
Pkg::ResolvableRemove (product, `product);
});
// message popup
Popup::Message (_("Dependencies of the selected add-on products cannot be fulfilled."));
ret = nil;
continue;
}
map data = Pkg::SourceGeneralData (AddOnProduct::src_id);
string url = data["url"]:"";
string product_dir = data["product_dir"]:"";
// bugzilla #227605
// this product with this url has been already installed or selected for installation
if (url != "" && url != nil) {
if (already_used_urls[url]:"" == product_dir) {
// error pop-up
Popup::Message (_("The product on the media is already installed
or selected for installation."));
y2milestone ("Deleting installation source %1 %2 (%3)",
AddOnProduct::src_id, url, product_dir);
Pkg::SourceDelete (AddOnProduct::src_id);
AddOnProduct::ClearRegistrationRequest (AddOnProduct::src_id);
y2milestone ("ProductSelect Dialog ret: %1", `finish);
return `finish;
}
}
foreach (string product, selected, {
integer src_id = (AddOnProduct::src_id == -1 ? prod2src[product]:-1 : AddOnProduct::src_id);
// bugzilla #304659
SetAddOnProductName (AddOnProduct::src_id);
AddOnProduct::add_on_products = add (AddOnProduct::add_on_products, $[
"media" : src_id,
"product" : ((new_addon_name != "" && new_addon_name != nil) ? new_addon_name : product),
"autoyast_prouduct" : product,
"media_url" : url,
"product_dir" : product_dir,
]);
});
if ( AddOnProduct::src_id != -1 && Mode::config ())
{
AddOnProduct::mode_config_sources =
add (AddOnProduct::mode_config_sources, AddOnProduct::src_id);
}
}
else if (ret != `back)
{
ret = nil;
}
}
if (ret == `abort)
{
y2milestone ("Deleting source %1", AddOnProduct::src_id);
Pkg::SourceDelete (AddOnProduct::src_id);
}
AddOnProduct::last_ret = ret;
y2milestone ("ProductSelect Dialog ret: %1", AddOnProduct::last_ret);
return ret;
}
symbol RunWizard() {
map aliases = $[
"media" : ``(MediaSelect ()),
// "catalog" : ``(CatalogSelect ()),
// "product" : ``(ProductSelect ()),
"install_product" : ``(InstallProduct ()),
];
map sequence = $[
"ws_start" : "media",
"media" : $[
`abort : `abort,
`next : "install_product",
`finish : "install_product",
],
// "catalog" : $[
// `abort : `abort,
// `next : "product",
// `finish : `next,
// ],
// "product" : $[
// `abort : `abort,
// `next : `next,
// `finish : `next,
// ],
"install_product" : $[
`abort : `abort,
`next : `next,
`finish : `next,
],
];
return Sequencer::Run(aliases, sequence);
}
symbol RunAutorunWizard() {
map aliases = $[
"catalog" : ``(CatalogSelect ()),
"product" : ``(ProductSelect ()),
];
map sequence = $[
"ws_start" : "catalog",
"catalog" : $[
`abort : `abort,
`next : "product",
`finish : `next,
],
"product" : $[
`abort : `abort,
`next : `next,
`finish : `next,
],
];
return Sequencer::Run(aliases, sequence);
}
void Redraw (boolean enable_back, boolean enable_next, boolean enable_abort,
string back_button, string next_button, string abort_button) {
y2milestone ("Called Redraw()");
// main screen heading
string title = _("Add-On Product Installation");
// Help for add-on products
string help = _("<p><big><b>Add-On Product Installation</b></big></br>
Here see all add-on products that are selected for installation.
To add a new product, click <b>Add</b>. To remove an already added one,
select it and click <b>Delete</b>.</p>");
y2milestone ("Current products: %1", AddOnProduct::add_on_products);
integer index = -1;
list items = maplist (map<string,any> product, AddOnProduct::add_on_products, {
y2milestone ("%1", product);
index = index + 1;
map data = $[];
// BNC #464162, In AytoYaST, there is no media nr. yet
if (haskey (product, "media") && product["media"]:-1 > -1) {
data = Pkg::SourceGeneralData (product["media"]:-1);
} else {
data = product;
if (haskey (data, "media_url")) data["url"] = data["media_url"]:"";
}
// placeholder for unknown path
string dir = data["product_dir"]:_("Unknown");
if (dir == "")
dir = "/";
// table cell, %1 is URL, %2 is directory name
string media = sformat (_("%1, Directory: %2"),
// placeholder for unknown URL
data["url"]:_("Unknown"), dir);
return `item (
`id (index),
// sformat (_("Product %1"), product["product"]:"")
product["product"]:"",
media
);
});
term contents = `VBox (
`Table (
`id (`summary),
`header (
// table header
_("Product"),
// table header
_("Media")
),
items
),
`Left(
`HBox (
`PushButton (`id (`add), Label::AddButton ()),
`PushButton (`id (`delete), Label::DeleteButton ()),
`HStretch ()
)
)
);
Wizard::SetContentsButtons (title, contents, help, back_button, next_button);
Wizard::SetAbortButton (`abort, abort_button);
// Disable next button according to settings
if (! enable_next) Wizard::DisableNextButton();
// If back or abort buttons should not be enabled, hide them
// -> [Cancel] [OK] dialog
if (! enable_back) Wizard::HideBackButton();
if (! enable_abort) Wizard::HideAbortButton();
Wizard::SetTitleIcon("yast-addon");
// disable delete button if no items listed
// bug #203809
if (size(items) == 0) {
UI::ChangeWidget(`id (`delete), `Enabled, false);
}
}
void RemoveSelectedAddOn (integer selected) {
y2milestone ("Deleting %1 %2", selected, AddOnProduct::add_on_products[selected]:nil);
// remove whole media if the product is the only one on the media
integer media = AddOnProduct::add_on_products[selected, "media"]:-1;
integer med_count = size (filter (map<string,any> prod, AddOnProduct::add_on_products, {
return prod["media"]:-1 == media;
}));
if (med_count == 1) {
y2milestone ("Deleting source %1", media);
Pkg::SourceDelete (media);
}
// remove the selected record
AddOnProduct::add_on_products[selected] = nil;
AddOnProduct::add_on_products = filter (map<string,any> prod, AddOnProduct::add_on_products, {
return prod != nil;
});
// Remove product from add-ons
AddOnProduct::Disintegrate (media);
// remove product from list of product to register (FATE #301312)
AddOnProduct::RemoveRegistrationFlag (media);
}
// bugzilla #221377
// the original control file is stored as /control.xml
// the other (added) control files are under the
// /tmp/$yast_tmp/control_files/ directory
// as $srcid.xml files
//
// bugzilla #237297
// in the installation workflow - back/ next buttons
// in the installation proposal - cancel / accept buttons
//
// bugzilla #449773
// added enable_abort, abort_button
//
symbol RunAddOnMainDialog (boolean enable_back, boolean enable_next, boolean enable_abort,
string back_button, string next_button, string abort_button,
boolean confirm_abort) {
symbol ret = nil;
boolean not_enough_memory = (Stage::initial() && HasInsufficientMemory());
boolean no_addons = (size (AddOnProduct::add_on_products) == 0);
// bugzilla #239630
// It might be dangerous to add more installation sources in installation
// on machine with less memory
// Do not report when some add-ons are already in use
if (not_enough_memory && ! no_addons) {
if (! ContinueIfInsufficientMemory ()) {
// next time, it will be skipped too
Installation::add_on_selected = false;
Installation::productsources_selected = false;
return `next;
}
}
// FATE #301928 - Saving one click
// Bugzilla #305809 if "going_back", do not save that click!
if (no_addons && ! going_back_in_workflow) {
y2milestone ("Skipping to media_select");
// only once
going_back_in_workflow = false;
ret = `first_time;
}
// Show Add-Ons table
Redraw (enable_back, enable_next, enable_abort, back_button, next_button, abort_button);
// store the initial settings, only once
WorkflowManager::SetBaseWorkflow (false);
// added / removed
boolean some_addon_changed = false;
repeat {
// FATE #301928 - Saving one click
if (ret == `first_time) {
ret = `add;
} else {
ret = (symbol) UI::UserInput();
}
// aborting
if (ret == `abort || ret == `cancel) {
// User should confirm that
if (confirm_abort == true) {
if (Popup::ConfirmAbort (`incomplete)) {
ret = `abort;
break;
} else {
ret = nil;
}
// Running system
} else {
break;
}
// removing add-on
} else if (ret == `delete) {
integer selected = (integer) UI::QueryWidget (`id (`summary), `CurrentItem);
if (selected == nil) {
// message report
Report::Message (_("Select a product to delete."));
continue;
}
// bugzilla #305802
if (! Confirm::DeleteSelected()) {
continue;
}
// TRANSLATORS: busy message
UI::OpenDialog (`Label (_("Removing selected add-on...")));
RemoveSelectedAddOn (selected);
some_addon_changed = true;
UI::CloseDialog();
Redraw (enable_back, enable_next, enable_abort, back_button, next_button, abort_button);
// adding new add-on
} else if (ret == `add) {
// bugzilla #293428
// Release all sources before adding a new one
// because of CD/DVD + url cd://
Pkg::SourceReleaseAll();
// bugzilla #305788
// Use new wizard window for adding new Add-On.
// Do not use "Steps" dialog.
Wizard::OpenNextBackDialog();
Wizard::SetTitleIcon("yast-addon");
symbol ret = RunWizard ();
Wizard::CloseDialog();
if (ret == `next) {
// Add-On product has been added, integrate it (change workflow, use y2update)
AddOnProduct::Integrate (AddOnProduct::src_id);
// check whether it requests registration (FATE #301312)
AddOnProduct::PrepareForRegistration (AddOnProduct::src_id);
some_addon_changed = true;
}
Redraw (enable_back, enable_next, enable_abort, back_button, next_button, abort_button);
// bugzilla #293428
// Release all sources after adding a new one
// because of CD/DVD + url cd://
Pkg::SourceReleaseAll();
}
} until ( ret == `next || ret == `back );
y2milestone ("Ret: %1, Some Add-on Added/Removed: %2", ret, some_addon_changed);
y2milestone ("Registration will be requested: %1", AddOnProduct::ProcessRegistration());
// First stage installation, #247892
// installation, update or autoinstallation
if (Stage::initial()) {
// bugzilla #221377
if (some_addon_changed) {
AddOnProduct::ReIntegrateFromScratch();
}
}
// bugzilla #293428
// Release all sources after all Add-Ons are added and merged
y2milestone ("Releasing all sources...");
Pkg::SourceReleaseAll();
// bugzilla #305788
Wizard::RestoreBackButton();
Wizard::RestoreAbortButton();
Wizard::RestoreNextButton();
return ret;
}
// AddOnsOverviewDialog -->
void CreateAddOnsOverviewDialog () {
y2milestone ("Creating OverviewDialog");
Wizard::SetContents (
// TRANSLATORS: dialog caption
_("Installed Add-On Products"),
`VBox (
`Table (
`id ("list_of_addons"),
`opt (`notify, `immediate),
`header (
// TRANSLATORS: table header item
_("Add-On Product"),
// TRANSLATORS: table header item
_("URL")
),
[]
),
`VSquash(
// Rich text plus border
`MinHeight (6,
`RichText (`id ("product_details"), "")
)
),
`HBox (
`PushButton (`id (`add), Label::AddButton()),
`HSpacing (1),
`PushButton (`id (`delete), Label::DeleteButton()),
`HStretch(),
// TRANSLATORS: push button
`PushButton (`id (`packager), _("Run &Software Manager..."))
)
),
// TRANSLATORS: dialog help adp/1
_("<p>Here you can see all of the add-on products which are installed on your system.</p>") +
// TRANSLATORS: dialog help adp/2
_("<p>Use the <b>Add</b> button to add a new add-on product, or the <b>Delete</b> button to remove an add-on which is in use.</p>"),
false,
true
);
Wizard::SetTitleIcon ("yast-addon");
Wizard::HideBackButton();
Wizard::SetAbortButton (`abort, Label::CancelButton());
Wizard::SetNextButton (`next, Label::OKButton());
}
map product_infos = $[];
map ReturnCurrentlySelectedProductInfo () {
if (! UI::WidgetExists (`id ("list_of_addons"))) {
y2error ("No such widget: %1", "list_of_addons");
return nil;
}
string item_id = (string) UI::QueryWidget (`id ("list_of_addons"), `CurrentItem);
// no items
if (item_id == nil) {
return nil;
}
if (! regexpmatch (item_id, "product_")) {
y2error ("Wrong product ID '%1'", item_id);
return nil;
}
item_id = substring (item_id, 8);
return product_infos[item_id]:$[];
}
void AdjustInfoWidget () {
map pi = ReturnCurrentlySelectedProductInfo();
if (pi == nil || pi == $[]) {
UI::ChangeWidget (`id ("product_details"), `Value, "");
return;
}
string rt_description = sformat (
"<p>%1
%2
%3
%4</p>",
sformat (
_("<b>Vendor:</b> %1<br>"),
pi["product","vendor"]:_("Unknown vendor")
),
sformat (
_("<b>Version:</b> %1<br>"),
pi["product","version"]:_("Unknown version")
),
sformat (
_("<b>Repository URL:</b> %1<br>"),
(size (pi["info","URLs"]:[]) > 0 ?
mergestring (pi["info","URLs"]:[], ",")
:
_("Unknown repository URL")
)
),
(size (pi["info","aliases"]:[]) > 0 ?
sformat (_("<b>Repository Alias:</b> %1<br>"), mergestring (pi["info","aliases"]:[], ","))
:
""
)
);
UI::ChangeWidget (`id ("product_details"), `Value, rt_description);
}
/**
* Logs wrong product with 'log_this' error and returns 'return_this'.
* Added because of bnc #459461
*/
string LogWrongProduct (map <string,any> one_product, string log_this, string return_this) {
y2error ("Erroneous product: %1: %2", log_this, one_product);
return return_this;
}
/**
* Modifies repository info (adds some missing pieces).
*/
void AdjustRepositoryInfo (map <string, list> & info) {
foreach (integer one_repo, (list <integer>) info["IDs"]:[], {
if (one_repo == nil || one_repo == -1) {
y2warning ("Wrong repo ID: %1", one_repo);
return;
}
map source_data = Pkg::SourceGeneralData (one_repo);
if (source_data != nil && haskey (source_data, "base_urls")) {
info["URLs"] = source_data["base_urls"]:[];
} else {
y2error ("No URLs for repo ID %1", one_repo);
}
if (source_data != nil && haskey (source_data, "alias")) {
info["aliases"] = [source_data["alias"]:""];
}
});
}
/**
* @struct $[
* "IDs" : [8, 9, 12],
* "URLs" : ["dvd://", "http://some/URL/", "ftp://another/URL/"],
* "aliases" : ["alias1", "alias2", "alias3"],
* ]
*/
map <string, list> GetRepoInfo (map <string,any> & this_product, list <map <string,any> > & all_products) {
map <string, list> ret = $["IDs" : [], "URLs" : [], "aliases" : []];
string product_arch = this_product["arch"]:"";
string product_name = this_product["name"]:"";
string product_version = this_product["version"]:"";
foreach (map <string,any> one_product, all_products, {
// if (one_product["status"]:`unknown != `available) return;
if (one_product["arch"]:"" != product_arch) return;
if (one_product["name"]:"" != product_name) return;
if (one_product["version"]:"" != product_version) return;
if (haskey (one_product, "source") && one_product["source"]:-1 != -1)
ret["IDs"] = add (ret["IDs"]:[], one_product["source"]:-1);
});
AdjustRepositoryInfo (ret);
return ret;
}
void RedrawAddOnsOverviewTable () {
list <map <string,any> > all_products = Pkg::ResolvableProperties ("", `product, "");
all_products = maplist (map <string,any> one_product, all_products, {
// otherwise it fills the log too much
foreach (string key, ["license", "description"], {
if (haskey (one_product, key)) {
one_product[key] = substring (one_product[key]:"", 0, 40) + "...";
}
});
return one_product;
});
integer source_nr = nil;
map repository_info = nil;
integer counter = -1;
list <term> products = [];
product_infos = $[];
list <map <string,any> > installed_products = filter (map <string,any> one_product, all_products, {
// Do not list the base product
if (one_product["category"]:"addon" == "base")
return false;
// BNC #475591: Only those `installed or `selected ones should be actually visible
return (one_product["status"]:`unknown == `installed || one_product["status"]:`unknown == `selected);
});
foreach (map <string,any> one_product, installed_products, {
// only add-on products should be listed
if (haskey (one_product, "type") && one_product["type"]:"addon" != "addon") {
y2milestone ("Skipping product: %1", one_product["display_name"]:one_product["name"]:"");
return;
}
counter = counter + 1;
y2milestone ("Product: %1, Info: %2", one_product, repository_info);
if (repository_info == nil) {
y2warning ("No matching repository found for product listed above");
}
repository_info = GetRepoInfo (one_product, all_products);
product_infos[tostring(counter)] = $[
"product" : one_product,
"info" : repository_info,
];
products = add (products, `item (
`id (sformat ("product_%1", counter)),
one_product["display_name"]:one_product["name"]:_("Unknown product"),
repository_info["URLs", 0]:_("Unknown URL")
));
});
UI::ChangeWidget (`id ("list_of_addons"), `Items, products);
AdjustInfoWidget();
// Nothing to do delete when there are no product listed
UI::ChangeWidget (`id(`delete), `Enabled, (size (installed_products) > 0));
}
boolean RunPackageSelector () {
boolean solve_ret = Pkg::PkgSolve (false);
y2milestone ("Calling Solve() returned: %1", solve_ret);
symbol result = PackagesUI::RunPackageSelector ($["mode":`summaryMode]);
if (result != `accept) {
return false;
}
Wizard::OpenNextBackDialog();
y2milestone ("Calling inst_rpmcopy");
WFM::call ("inst_rpmcopy");
y2milestone ("Done");
Wizard::CloseDialog();
return true;
}
/**
* Removes the currently selected Add-On
*
* @return boolean whether something has changed its state
*/
boolean RemoveProductWithDependencies () {
map pi = ReturnCurrentlySelectedProductInfo();
if (pi == nil || pi == $[]) {
y2error ("Cannot remove unknown product");
return nil;
}
string product_name = pi["product","display_name"]:pi["product","name"]:_("Unknown product");
if (! Popup::AnyQuestion (
Label::WarningMsg(),
sformat (_("Deleting the add-on product %1 may result in removing all the packages
installed from this add-on.
Are sure you want to delete it?"), product_name),
Label::DeleteButton (),
Label::CancelButton (),
`focus_no
)) {
y2milestone ("Deleting '%1' canceled", product_name);
return nil;
}
// TRANSLATORS: busy popup message
UI::OpenDialog (`Label(_("Removing product dependencies...")));
// OpenDialog-BusyMessage
list <integer> src_ids = pi["info","IDs"]:[];
// Temporary definitions
boolean pack_ret = false;
string package_string = "";
// ["pkg1 version release arch", "pkg2 version release arch", ... ]
list <string> installed_packages = maplist (string inst_package, Pkg::GetPackages (`installed, false), {
// ... but we need ["pkg1 version-release arch", "pkg2 version-release arch", ... ]
return regexpsub (inst_package, "(.*) (.*) (.*) (.*)", "\\1 \\2-\\3 \\4");
});
// y2milestone ("Installed packages: %1", installed_packages);
// All packages from Add-On / Repository
list <map <string,any> > packages_from_repo = Pkg::ResolvableProperties ("", `package, "");
packages_from_repo = filter (map <string, any> one_package, packages_from_repo, {
// Package is not at the repositories to be deleted
if (! contains (src_ids, one_package["source"]:-1))
return false;
// Package *is* at the repository to delete
// "name version-release arch", "version" already contains a release
package_string = sformat ("%1 %2 %3", one_package["name"]:"", one_package["version"]:"", one_package["arch"]:"");
// The very same package (which is avaliable at the source) is also installed
return contains (installed_packages, package_string);
});
y2milestone ("%1 packages installed from repository", size (packages_from_repo));
// Removing selected product, whatever it means
// It might remove several products when they use the same name
if ((pi["product","status"]:`unknown == `installed || pi["product","status"]:`unknown == `selected) && pi["product","name"]:"" != "") {
y2milestone ("Removing product: '%1'", pi["product","name"]:"");
Pkg::ResolvableRemove (pi["product","name"]:"", `product);
} else {
y2milestone ("Product is neither `installed nor `selected");
}
// Removing repositories of the selected product
y2milestone ("Removing repositories: %1, url(s): %2", src_ids, pi["info","URLs"]:[]);
foreach (integer src_id, src_ids, {
if (src_id > -1) {
y2milestone ("Removing repository ID: %1", src_id);
Pkg::SourceDelete (src_id);
} else {
y2milestone ("Product doesn't have any repository in use");
}
});
// The product repository is already removed, checking all installed packages
// All available packages
list <string> available_packages = maplist (string inst_package, Pkg::GetPackages (`available, false), {
// ... but we need ["pkg1 version-release arch", "pkg2 version-release arch", ... ]
return regexpsub (inst_package, "(.*) (.*) (.*) (.*)", "\\1 \\2-\\3 \\4");
});
list <string> available_package_names = Pkg::GetPackages (`available, true);
y2milestone ("%1 available packages", size (available_package_names));
boolean status_changed = false;
// check all packages installed from the just removed repository
foreach (map <string, any> one_package, packages_from_repo, {
// "name version-release arch", "version" already contains a release
package_string = sformat ("%1 %2 %3", one_package["name"]:"", one_package["version"]:"", one_package["arch"]:"");
// installed package is not available anymore
if (! contains (available_packages, package_string)) {
status_changed = true;
// it must be removed
y2milestone ("Removing: %1", package_string);
Pkg::ResolvableRemove (one_package["name"]:"~~~", `package);
// but if another version is present, select if for installation
if (contains (available_package_names, one_package["name"]:"~~~")) {
y2milestone ("Installing another version of %1", one_package["name"]:"");
Pkg::ResolvableInstall (one_package["name"]:"", `package);
}
}
});
// See OpenDialog-BusyMessage
UI::CloseDialog();
if (status_changed) {
return RunPackageSelector();
}
return true;
}
void RunAddProductWorkflow () {
// feedback heading
string heading = _("Add-On Product Installation");
// feedback message
string message
= _("Reading packages available at the installation repositories...");
Popup::ShowFeedback (heading, message);
y2milestone ("syncing srcid %1 to zmd", AddOnProduct::src_id);
boolean synced
= SourceManager::SyncAddedAndDeleted ([AddOnProduct::src_id], []);
y2milestone ("sync status: %1", synced);
Popup::ClearFeedback ();
WFM::CallFunction ("inst_addon_update_sources", []);
AddOnProduct::DoInstall();
// Write only when there are some changes
Write();
Pkg::SourceReleaseAll();
}
// Cleanup UI - Prepare it for progress callbacks
void SetWizardWindowInProgress () {
Wizard::SetContents (
_("Add-On Products"),
`Label (_("Initializing...")),
_("<p>Initializing add-on products...</p>"),
false,
false
);
Wizard::SetTitleIcon ("yast-addon");
}
// BNC #476417: When user cancels removing an add-on, we have to neutralize all
// libzypp resolvables to their inital states
void NeutralizeAllResolvables () {
foreach (symbol one_type, [`product, `patch, `package, `srcpackage, `pattern], {
y2milestone ("Neutralizing all: %1", one_type);
Pkg::ResolvableNeutral ("", one_type, true);
});
}
// Either there are no repositories now or they
// were changed, neutralized, etc.
void LoadLibzyppNow () {
y2milestone ("Reloading libzypp");
SetWizardWindowInProgress();
// Reinitialize
Pkg::TargetInitialize (Installation::destdir);
Pkg::TargetLoad();
Pkg::SourceStartManager (true);
}
symbol RunAddOnsOverviewDialog () {
y2milestone ("Overview Dialog");
symbol ret = `next;
// to see which products are installed
Pkg::PkgSolve (true);
CreateAddOnsOverviewDialog();
RedrawAddOnsOverviewTable();
any userret = nil;
while (true) {
userret = UI::UserInput();
// Abort
if (userret == `abort || userret == `cancel) {
y2warning ("Aborting...");
ret = `abort;
break;
// Closing
} else if (userret == `next || userret == `finish) {
y2milestone ("Finishing...");
ret = `next;
break;
// Addin new product
} else if (userret == `add) {
y2milestone ("Using new Add-On...");
if (RunWizard() == `next) {
RunAddProductWorkflow();
}
// Something has disabled all the repositories or finished
// libzypp, reload it
list <integer> current_repos = Pkg::SourceGetCurrent(true);
if (current_repos == nil || size (current_repos) == 0) {
LoadLibzyppNow();
}
CreateAddOnsOverviewDialog();
RedrawAddOnsOverviewTable();
// Removing product
} else if (userret == `delete) {
y2milestone ("Removing selected product...");
boolean rpwd = RemoveProductWithDependencies();
y2milestone ("RPWD result was: %1", rpwd);
// nil == user decided not to remove the product
if (rpwd == nil) {
y2milestone ("User decided not to remove the selected product");
continue;
// false == user decided not confirm the add-on removal
// libzypp has been already changed
// BNC #476417: Getting libzypp to the previous state
} else if (rpwd == false) {
y2milestone ("User aborted the package manager");
SetWizardWindowInProgress();
// Neutralizing all resolvables (some are usually marked for removal)
NeutralizeAllResolvables();
Pkg::SourceFinishAll();
LoadLibzyppNow();
// true == packages and sources have been removed
} else {
// Store sources state
Pkg::SourceSaveAll();
}
CreateAddOnsOverviewDialog();
RedrawAddOnsOverviewTable();
// Redrawing info widget
} else if (userret == "list_of_addons") {
AdjustInfoWidget();
// Calling packager directly
} else if (userret == `packager) {
y2milestone ("Calling packager...");
RunPackageSelector();
CreateAddOnsOverviewDialog();
RedrawAddOnsOverviewTable();
// Everything else
} else {
y2error ("Uknown ret: %1", userret);
}
}
Wizard::RestoreBackButton();
return ret;
}
// <-- AddOnsOverviewDialog
} //end of include
ACC SHELL 2018