ACC SHELL
{
/**
* Module to provide simple API for working with the One Click Install metapackages.
* Enables removal of non-UI logic from UI module.
**/
textdomain "oneclickinstall";
module "OneClickInstall";
import "XML";
import "Product";
import "Language";
import "YPX";
/**
* repositories =
* $[ url =>
* $[
* name,
* summary,
* description,
* recommended
* ]
*
* ]
**/
map<string, map<string,string> > repositories = $[];
/**
* software =
* $[ name =>
* $[
* summary,
* description,
* recommended
* ]
* ]
**/
map<string, map<string,string> > software = $[];
//Whether the user should remain subscribed to these repositories post installation.
boolean remainSubscribed = true;
//The name of this software bundle.
string name = "";
//The summary of this software bundle.
string summary = "";
//The description of this software bundle.
string description = "";
/**
*
* Load the Metapackage from the URL supplied for further processing.
* Converts from original form into a simple two lists, one of repositories, other of software.
* Uses the Product.ycp to obtain the correct version for our product.
* Uses the Language.ycp to obtain correct strings for our language.
*
* Picks the strings, mirror, and product at evaluation time.
* N.B. This must be called before any of the rest of the methods.
*
* Internally the following format is used:
* repositories =
* $[ url =>
* $[
* name,
* summary,
* description,
* recommended
* ]
*
* ]
*
* software =
* $[ name =>
* $[
* summary,
* description,
* action,
* type,
* recommended
* ]
* ]
* @param url The file to load the xml from.
**/
global void Load(string url)
{
//Load the XML from file.
any xml = YPX::Load(url);
//Load returns false on error
if (xml == false)
return;
//Try and load the name.
name = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/name");
if (name == "")
name = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/name");
string rs = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/remainSubscribed");
if (rs == "")
rs = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/remainSubscribed");
if (rs == "false")
remainSubscribed = false;
else
remainSubscribed = true;
//Try and load the summary.
summary = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/summary[@lang='" + Language::language + "']");
if (summary == "")
summary = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/summary[not(@lang)]");
if (summary == "")
summary = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/summary[@lang='" + Language::language + "']");
if (summary == "")
summary = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/summary[not(@lang)]");
//Try and load the description.
description = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/description[@lang='" + Language::language + "']");
if (description == "")
description = YPX::SelectValue(xml,"/metapackage/group[@distversion='" + Product::name + "']/description[not(@lang)]");
if (description == "")
description = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/description[@lang='" + Language::language + "']");
if (description == "")
description = YPX::SelectValue(xml,"/metapackage/group[not(@distversion)]/description[not(@lang)]");
//Load the repository details into our internal format from xml.
//We want to load details for our specific version.
string REPO_XPATH = "/metapackage/group[@distversion='" + Product::name + "']/repositories/repository";
//If that fails, use any.
string FALLBACK_REPO_XPATH = "/metapackage/group[not(@distversion)]/repositories/repository";
//Select the repository URLs from the XML.
list<string> repoURLs = YPX::SelectValues(xml,REPO_XPATH + "/url");
//If we didn't have any try fallback xpath.
if (size(repoURLs) == 0)
{
REPO_XPATH = FALLBACK_REPO_XPATH;
repoURLs = YPX::SelectValues(xml,REPO_XPATH + "/url");
}
//Loop through the repo URLs and query the other details from the XML.
foreach (string url, repoURLs,
{
//Construct xpath to query details of this specific repository
string THIS_REPO_XPATH = REPO_XPATH + "[url='" + url + "']/";
string recommended = YPX::SelectValue(xml,THIS_REPO_XPATH + "@recommended");
//If recommended not specified we default to true.
if (recommended != "false")
recommended = "true";
//Get the name in our language
string name = YPX::SelectValue(xml,THIS_REPO_XPATH + "name[@lang='" + Language::language + "']");
//If that failed, try without a language
if (name == "")
name = YPX::SelectValue(xml,THIS_REPO_XPATH + "name[not(@lang)]");
//Find the summary of this repository, in our language.
string summary = YPX::SelectValue(xml,THIS_REPO_XPATH + "summary[@lang='"+ Language::language +"']");
//If that failed, try without a language.
if (summary == "")
summary = YPX::SelectValue(xml,THIS_REPO_XPATH + "summary[not(@lang)]");
//Find the description of this repository, in our language.
string description = YPX::SelectValue(xml,THIS_REPO_XPATH + "description[@lang='"+ Language::language +"']");
//If that failed, try without a language.
if (description == "")
description = YPX::SelectValue(xml,THIS_REPO_XPATH + "description[not(@lang)]");
//Store this repository details in our list.
map<string,string> repoDetails =
$[
"name":name,
"summary":summary,
"description":description,
"recommended":recommended
];
repositories = add(repositories,url,repoDetails);
});
//Load package names for this distversion.
string SOFTWARE_XPATH = "/metapackage/group[@distversion='" + Product::name + "']/software/item";
//Incase that isn't specified use any where distversion is not specified.
string FALLBACK_SOFTWARE_XPATH = "/metapackage/group[not(@distversion)]/software/item";
list<string> softwareNames = YPX::SelectValues(xml,SOFTWARE_XPATH+ "/name");
//If we didn't have any try fallback xpath.
if (size(softwareNames) == 0)
{
SOFTWARE_XPATH = FALLBACK_SOFTWARE_XPATH;
softwareNames = YPX::SelectValues(xml,SOFTWARE_XPATH + "/name");
}
foreach (string name, softwareNames,
{
//Construct xpath to query details of this specific software.
string THIS_SOFTWARE_XPATH = SOFTWARE_XPATH + "[name='" + name + "']/";
//Check whether it was recommended.
string recommended = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "@recommended");
//If recommended not specified we default to true.
if (recommended != "false")
recommended = "true";
string action = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "@action");
//If action not specified we default to install.
if (action != "remove")
action = "install";
string type = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "@type");
//If action not specified we default to install.
if (type != "pattern")
type = "package";
//Find the summary for this software, preferably in our language.
string summary = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "summary[@lang='"+ Language::language +"']");
if (summary == "")
summary = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "summary[not(@lang)]");
//Find the description of this software, preferably in our language.
string description = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "description[@lang='"+ Language::language +"']");
if (description == "")
description = YPX::SelectValue(xml,THIS_SOFTWARE_XPATH + "description[not(@lang)]");
//Store these software details in our list.
map<string,string> softwareDetails =
$[
"summary":summary,
"description":description,
"action":action,
"type":type,
"recommended":recommended
];
software = add(software,name,softwareDetails);
});
}
/** <region name="Repositories"> **/
/**
* @return a list of the URLs of the repositories currently selected for addition.
**/
global list<string> GetRequiredRepositories()
{
list<string> repoURLs = [];
foreach (string repoURL, map<string,string> repoDetails, repositories,
{
if (repoDetails["recommended"]:"false" == "true")
repoURLs = add(repoURLs, repoURL);
});
return repoURLs;
}
/**
* Ensures that the repository with the specified URL is selected for addition.
* @param url the url of the repository to ensure is selected for addition.
**/
global void SetRequiredRepository(string url)
{
map<string,string> repoDetails = repositories[url]:nil;
if (repoDetails == nil)
return;
repoDetails = add(repoDetails,"recommended","true");
repositories = add(repositories,url,repoDetails);
}
/**
* @return a list of the URLs of the repositories currently NOT selected for addition.
**/
global list<string> GetNonRequiredRepositories()
{
list<string> repoURLs = [];
foreach (string repoURL, map<string,string> repoDetails, repositories,
{
if (repoDetails["recommended"]:"false" == "false")
repoURLs = add(repoURLs, repoURL);
});
return repoURLs;
}
/**
* Ensures that the repository with the specified URL is NOT selected for addition.
* @param url the url to ensure is not selected for addition
**/
global void SetNonRequiredRepository(string url)
{
map<string,string> repoDetails = repositories[url]:nil;
if (repoDetails == nil)
return;
repoDetails = add(repoDetails,"recommended","false");
repositories = add(repositories,url,repoDetails);
}
/**
* Ensures that the repositories with specified URLs are selected for addition, and all others are not.
* @param urls the urls to ensure are selected.
**/
global void SetRequiredRepositories(list<string> urls)
{
foreach (string url, map<string,string> repoDetails, repositories,
{
if (contains(urls,url))
{
SetRequiredRepository(url);
} else
{
SetNonRequiredRepository(url);
}
});
}
/**
* @return the name of the repository with the specified name.
**/
global string GetRepositoryName(string url)
{
map<string,string> repoDetails = repositories[url]:nil;
if (repoDetails == nil)
return "";
return repoDetails["name"]:"";
}
/**
* @return the summary of the repository with the specified name.
* This will be in the user's current language if there was a localised summary available.
**/
global string GetRepositorySummary(string url)
{
map<string,string> repoDetails = repositories[url]:nil;
if (repoDetails == nil)
return "";
return repoDetails["summary"]:"";
}
/**
* @return the description of the repository with the specified name.
* This will be in the user's current language if there was a localised description available.
**/
global string GetRepositoryDescription(string url)
{
map<string,string> repoDetails = repositories[url]:nil;
if (repoDetails == nil)
return "";
return repoDetails["description"]:"";
}
/** </region> **/
/** <region name="Software"> **/
/**
* @return a list of the names of the software currently selected for installation.
**/
global list<string> GetRequiredSoftware()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["recommended"]:"false" == "true") && (softwareDetails["action"]:"install" == "install"))
names = add(names, name);
});
return names;
}
global list<string> GetRequiredPackages()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["recommended"]:"false" == "true") &&
(softwareDetails["action"]:"install" == "install") &&
(softwareDetails["type"]:"package" == "package")
)
{
names = add(names, name);
}
});
return names;
}
global list<string> GetRequiredPatterns()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["recommended"]:"false" == "true") &&
(softwareDetails["action"]:"install" == "install") &&
(softwareDetails["type"]:"package" == "pattern")
)
{
names = add(names, name);
}
});
return names;
}
/**
* @return a list of the names of the software currently selected for removal.
**/
global list<string> GetRequiredRemoveSoftware()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["recommended"]:"false" == "true") && (softwareDetails["action"]:"install" == "remove"))
names = add(names, name);
});
return names;
}
/**
* Ensures the software with the specified name is selected for installation or removal.
* @param the name of the software to ensure is selected for installation.
**/
global void SetRequiredSoftware(string name)
{
map<string,string> softwareDetails = software[name]:nil;
if (softwareDetails == nil)
return;
softwareDetails = add(softwareDetails,"recommended","true");
software = add(software,name,softwareDetails);
}
/**
* @return a list of the names of the software currently NOT selected for installation.
**/
global list<string> GetNonRequiredSoftware()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["recommended"]:"false" == "false") && (softwareDetails["action"]:"install" == "install"))
names = add(names, name);
});
return names;
}
/**
* @return a list of the names of the software currently selected for removal.
**/
global list<string> GetNonRequiredRemoveSoftware()
{
list<string> names = [];
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["recommended"]:"false" == "false") && (softwareDetails["action"]:"install" == "remove"))
names = add(names, name);
});
return names;
}
/**
* Ensures the software with the specified name is NOT selected for installation or removal.
* @param the name of the software to ensure is NOT selected for installation.
**/
global void SetNonRequiredSoftware(string name)
{
map<string,string> softwareDetails = software[name]:nil;
if (softwareDetails == nil)
return;
softwareDetails = add(softwareDetails,"recommended","false");
software = add(software,name,softwareDetails);
}
/**
* Ensures that the repositories with specified URLs are selected for addition, and all others are not.
* Invalid pluralisation due to lack of proper overloading :(
* @param the names of the software to ensure is selected for installation.
**/
global void SetRequiredSoftwares(list<string> names)
{
foreach (string name, map<string,string> softwareDetails, software,
{
if (contains(names,name))
{
SetRequiredSoftware(name);
} else
{
SetNonRequiredSoftware(name);
}
});
}
/**
* @return the summary for the software with specified name.
* This will be in the user's current language if there was a localised summary available.
**/
global string GetSoftwareSummary(string name)
{
map<string,string> softwareDetails = software[name]:nil;
if (softwareDetails == nil)
return "";
return softwareDetails["summary"]:"";
}
/**
* @return the description for the software with specified name.
* This will be in the user's current language if there was a localised description available.
**/
global string GetSoftwareDescription(string name)
{
map<string,string> softwareDetails = software[name]:nil;
if (softwareDetails == nil)
return "";
return softwareDetails["description"]:"";
}
/** </region> **/
/** <region name="Processing"> **/
/**
* Specify whether the user should remain subscribed to the repositories after installation of this software is complete.
* @param the boolean value indicating whether the user should remain subscribed.
**/
global void SetRemainSubscribed(boolean value)
{
remainSubscribed = value;
}
/**
* @return the current setting of whether the user should remain subscribed to repositories after installation.
**/
global boolean GetRemainSubscribed()
{
return remainSubscribed;
}
/**
* @return the name for this software bundle.
**/
global string GetName()
{
return name;
}
/**
* @return the summary for this software bundle.
* This will be in the user's current language if there was a localised summary available.
**/
global string GetSummary()
{
return summary;
}
/**
* @return the description for this software bundle.
* This will be in the user's current language if there was a localised description available.
**/
global string GetDescription()
{
return description;
}
/**
* @return Find out whether we have any repositories that need to be added for this installation.
* Useful to find out whether to display this wizard step.
**/
global boolean HaveRepositories()
{
return (size(repositories) > 0);
}
/**
* @return Find out whether we have any software that needs to be installed for this installation.
* Useful to find out whether to display this wizard step.
**/
global boolean HaveSoftware()
{
boolean haveSoftware = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["action"]:"install") == "install")
{
haveSoftware = true;
return haveSoftware;
}
});
return haveSoftware;
}
global boolean HavePackagesToInstall()
{
boolean have = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["recommended"]:"false" == "true") &&
(softwareDetails["action"]:"install" == "install") &&
(softwareDetails["type"]:"package" == "package")
)
{
have = true;
return have;
}
});
return have;
}
global boolean HavePatternsToInstall()
{
boolean have = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["recommended"]:"false" == "true") &&
(softwareDetails["action"]:"install" == "install") &&
(softwareDetails["type"]:"package" == "pattern")
)
{
have = true;
return have;
}
});
return have;
}
global boolean HaveRepositoriesToInstall()
{
boolean have = false;
foreach (string url, map<string,string> repoDetails, repositories,
{
if (
(repoDetails["recommended"]:"false" == "true")
)
{
have = true;
return have;
}
});
return have;
}
global boolean HaveRemovalsToInstall()
{
boolean have = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["action"]:"install" == "remove") &&
(softwareDetails["recommended"]:"false" == "true")
)
{
have = true;
return have;
}
});
return have;
}
/**
* @return Find out whether we have any software that needs to be removed for this installation.
* Useful to find out whether to display this wizard step.
**/
global boolean HaveRemovals()
{
boolean haveSoftware = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if ((softwareDetails["action"]:"install") == "remove")
{
haveSoftware = true;
return haveSoftware;
}
});
return haveSoftware;
}
/**
* @return Whether we have anything to do
* Determine whether we have a proper metapackage, useful as we can't throw exceptions.
**/
global boolean HaveAnythingToDo()
{
return ((size(repositories) > 0) && (size(software) > 0));
}
/**
* @return Whether we have a bundle description for the whole bundle
* Build service isn't currently generating one for YMPs for individual packages.
**/
global boolean HaveBundleDescription()
{
return ((description != "") && (summary != "") && ( name != ""));
}
/**
* @return Whether we have any recommended repositories or packages
* If not we will have to show advanced view.
**/
global boolean HaveAnyRecommended()
{
boolean rec = false;
foreach (string name, map<string,string> softwareDetails, software,
{
if (
(softwareDetails["action"]:"install" == "install") &&
(softwareDetails["recommended"]:"false" == "true")
)
rec = true;
return true;
});
foreach (string url, map<string,string> repoDetails, repositories,
{
if (
(repoDetails["recommended"]:"false" == "true")
)
{
rec = true;
return true;
}
});
return rec;
}
/**
* Converts our map -> map structure to a list of maps with a "key" element.
* This is friendly for yast's XML serialisation support.
**/
list<map<string,string> > makeXMLFriendly(map<string,map<string,string> > toFlatten)
{
list<map<string,string> > flattened = [];
foreach(string key,map<string,string> value, toFlatten,
{
flattened = add(flattened,add(value,"key",key));
});
return flattened;
}
/**
* Converts back from the above to our original structure
**/
map<string,map<string,string> > fromXMLFriendly(list<map<string,string> > toUnFlatten)
{
map<string,map<string,string> > unflattened = $[];
foreach (map<string,string> item, toUnFlatten,
{
string key = item["key"]:"nokey";
unflattened = add(unflattened,key,remove(item,"key"));
});
return unflattened;
}
/**
* Sets up a doctype for YaST's XML serialisation.
**/
void SetupXML()
{
map doc = $[];
doc["listEntries"] =
$[
"repositories":"repository"
];
doc["cdataSections"] = [];
doc["rootElement"] = "OneClickInstall";
doc["systemID"] = "/un/defined";
doc["nameSpace"] = "http://www.suse.com/1.0/yast2ns";
doc["typeNamespace"] = "http://www.suse.com/1.0/configns";
XML::xmlCreateDoc(`OneClickInstall,doc);
}
/**
* Serialises this data structure to XML.
* @param filename the file to write the XML to.
**/
global void ToXML(string filename)
{
SetupXML();
map<string,any > toSerialise = $[];
toSerialise = add(toSerialise,"software",makeXMLFriendly(software));
toSerialise = add(toSerialise,"repositories",makeXMLFriendly(repositories));
toSerialise = add(toSerialise,"remainSubscribed",remainSubscribed);
toSerialise = add(toSerialise,"name",name);
toSerialise = add(toSerialise,"summary",summary);
toSerialise = add(toSerialise,"description",description);
boolean success = XML::YCPToXMLFile(`OneClickInstall,toSerialise, filename);
}
/**
* DeSerialises this data structure from XML.
* @param filename the file to read the XML from.
**/
global void FromXML(string filename)
{
SetupXML();
map<string,any> deSerialised = (map<string,any >)XML::XMLToYCPFile(filename);
software = fromXMLFriendly((list<map<string,string> >)deSerialised["software"]:[]);
repositories = fromXMLFriendly((list<map<string,string> >)deSerialised["repositories"]:[]);
remainSubscribed = deSerialised["remainSubscribed"]:false;
summary = deSerialised["summary"]:"";
description = deSerialised["description"]:"";
name = deSerialised["name"]:"";
}
/** </region> **/
}
ACC SHELL 2018