ACC SHELL
{
/***
* This is a stand-alone YaST client that allows you to add suggested
* repositories (repositories) to the libzypp.
*
* How it works:
* - First a list of servers/links is extracted from the YaST control file
* (/etc/YaST2/control.xml)
* - Then servers/links are asked one by one to provide the suggested sources
*
* Format of the initial list of servers:
* <?xml version="1.0"?>
* <productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
* <servers config:type="list">
* <item>
* <link>http://some.server/some_link.xml</link>
* <official config:type="boolean">true</official>
* <installation_repo config:type="boolean">true</installation_repo>
* </item>
* <item>
* <link>ftp://some.other.server/some_link.xml</link>
* <official config:type="boolean">false</official>
* </item>
* </servers>
* </productDefines>
*
* Only installation_repo=true (trusted) links are used during installation.
* See Bugzilla #293811.
*
* Format of Suggested sources:
* <?xml version="1.0"?>
* <metapackage xmlns:os="http://opensuse.org/Standards/One_Click_Install" xmlns="http://opensuse.org/Standards/One_Click_Install">
* <group distversion="openSUSE Factory">
* <repositories>
* <repository recommended="true" format="yast">
* <name>Some name</name>
* <name lang="en_GB">Some name</name>
* <summary>Summary...</summary>
* <summary lang="en_GB">Summary...</summary>
* <description>Description...</description>
* <url>http://some.server/some.dir/10.3/</url>
* </repository>
* <repository recommended="false" format="yast">
* <name>Another name</name>
* <summary>Summary...</summary>
* <description>Description...</description>
* <url>http://another.server/another.dir/10.3/</url>
* </repository>
* </repositories>
* </group>
* </metapackage>
*/
textdomain "packager";
import "Wizard";
import "Sequencer";
import "NetworkService";
import "Mode";
import "Popup";
import "Label";
import "Installation";
import "PackageLock";
import "ProductFeatures";
import "Directory";
import "Progress";
import "Stage";
import "Report";
import "CommandLine";
import "PackageCallbacks";
import "SourceManager";
import "FileUtils";
import "HTTP";
import "FTP";
import "XML";
import "ProductControl";
import "AddOnProduct";
import "GetInstArgs";
import "OneClickInstallStandard";
import "Language";
import "String";
import "URL";
include "installation/misc.ycp";
if (AddOnProduct::skip_add_ons) {
y2milestone ("Skipping module (as requested before)");
return `auto;
}
if (GetInstArgs::going_back()) {
y2milestone ("Going back...");
return `back;
}
list args = WFM::Args();
y2milestone("Script args: %1", args);
// similar to commandline mode but actually it's called from another YaST script
map script_noncmdline_args = $[];
if (is (args[0]:$[], map)) {
script_noncmdline_args = (map) args[0]:$[];
}
if (Mode::normal() && size (args)>0 && size(script_noncmdline_args)==0) {
CommandLine::Run ($["id":"inst_productsources"]);
return `auto;
}
// (Applicable only in inst-sys)
boolean preselect_recommended = true;
boolean skip_already_used_repos = false;
boolean script_called_from_another = false;
if (script_noncmdline_args["skip_already_used_repos"]:nil == true) {
y2milestone ("Already used repos will be hidden");
skip_already_used_repos = true;
script_called_from_another = true;
}
string main_link = "";
map <string, map> list_of_repos = $[];
list <map> list_of_servers = [];
// List of IDs of URLs to be added
list <string> repos_to_be_used = [];
string language_long = "";
string language_short = "";
// Map of already used suggested repositories
// $[ "($url|$path)" : src_id ]
map <string, integer> repos_already_used = $[];
string CreateRepoId (string s_url, string s_path) {
return sformat ("(%1|%2)", s_url, s_path);
}
// See bugzilla #309317
string GetUniqueAlias (string alias_orig) {
if (alias_orig == nil) alias_orig = "";
// all current aliases
list <string> aliases = maplist (integer i, Pkg::SourceGetCurrent(false), {
map info = Pkg::SourceGeneralData (i);
return info["alias"]:"";
});
// default
string alias = alias_orig;
// repository alias must be unique
// if it already exists add "_<number>" suffix to it
integer idx = 1;
while (contains (aliases, alias)) {
alias = sformat("%1_%2", alias_orig, idx);
idx = idx + 1;
}
if (alias_orig != alias) {
y2milestone ("Alias '%1' changed to '%2'", alias_orig, alias);
}
return alias;
}
// See bugzilla #307680
// Proxy needs to be read from sysconfig and
// set by setenv() builtin
void InitProxySettings () {
y2milestone ("Adjusting proxy settings");
map <string, string> proxy_items = $[
"http_proxy":"HTTP_PROXY", "FTP_PROXY":"FTP_PROXY",
"HTTPS_PROXY":"HTTPS_PROXY", "NO_PROXY":"NO_PROXY",
];
boolean use_proxy = false;
SCR::RegisterAgent (
.current_proxy_settings,
`ag_ini(
`SysConfigFile("/etc/sysconfig/proxy")
)
);
use_proxy = (SCR::Read(.sysconfig.proxy.PROXY_ENABLED) != "no");
string item_value = "";
foreach (string proxy_item, string sysconfig_item, proxy_items, {
item_value = (string) SCR::Read (add (.sysconfig.proxy, sysconfig_item));
if (item_value == nil) item_value = "";
if (use_proxy == true && item_value != "") {
y2milestone ("Adjusting '%1'='%2'", proxy_item, item_value);
setenv (proxy_item, item_value);
}
});
SCR::UnregisterAgent (.current_proxy_settings);
}
// Function returns whether user wants to abort the installation / configuration
// true - abort
// false - do not abort
//
// Bugzilla #298049
boolean UserWantsToAbort () {
any ret = UI::PollInput();
if (ret != `abort)
return false;
boolean function_ret = false;
// `abort pressed
if (Stage::initial()) {
function_ret = (Popup::ConfirmAbort (`painless));
} else {
function_ret = (Popup::ContinueCancelHeadline (
// TRANSLATORS: popup header
_("Aborting Configuration of Online Repository"),
// TRANSLATORS: popup question
_("Are you sure you want to abort the configuration?")
));
}
y2milestone ("User decided to abort: %1", function_ret);
// Clean-up the progress
if (function_ret == true) {
Progress::Finish();
}
return function_ret;
}
boolean NetworkRunning () {
boolean ret = false;
// bnc #327519
if (Mode::normal ()) {
if (! NetworkService::isNetworkRunning()) {
y2warning ("No network is running...");
return false;
}
}
while (true) {
if (NetworkService::isNetworkRunning()) {
ret = true;
break;
}
// Network is not running
if (! Popup::AnyQuestion (
// TRANSLATORS: popup header
_("Network is not configured"),
// TRANSLATORS: popup question
_("Online sources defined by product require an Internet connection.
Would you like to configure it?"),
Label::YesButton(),
Label::NoButton(),
`yes
)) {
y2milestone ("User decided not to setup the network");
ret = false;
break;
}
y2milestone ("User wants to setup the network");
// Call network-setup client
any netret = WFM::call("inst_network_setup");
if (netret == `abort) {
y2milestone ("Aborting the network setup");
break;
}
}
return ret;
}
/**
* Removes slashes from the end of the URL (or just string).
* Needed to fix bug #329629.
*/
string NormalizeURL (string url_string) {
if (url_string == nil || url_string == "")
return url_string;
if (regexpmatch (url_string, "/+$")) {
url_string = regexpsub (url_string, "(.*)/+$", "\\1");
}
// URL is escaped
if (regexpmatch (url_string, "%")) {
// unescape it
url_string = URL::UnEscapeString (url_string, URL::transform_map_filename);
}
return url_string;
}
/**
* Returns whether this URL/Path is already added as a source
* -1 == not added
* 0 or 1 or 2 ... or 'n' means 'added as source $id'
*/
integer IsAddOnAlreadySelected (string s_url, string s_path) {
// AddOnProduct::add_on_products, $[
// "media" : src_id,
// "product" : prod["label"]:prod["productname"]:prod["productversion"]:list_of_repos[url,"name"]:"",
// "autoyast_product" : prod["productname"]:"",
// "media_url" : url,
// "product_dir" : pth,
// ];
integer ret = -1;
s_url = NormalizeURL (s_url);
foreach (map one_add_on, AddOnProduct::add_on_products, {
one_add_on["media_url"] = NormalizeURL (one_add_on["media_url"]:"");
if (one_add_on["media_url"]:nil == s_url && one_add_on["product_dir"]:nil == s_path) {
ret = one_add_on["media"]:-1;
break;
}
});
if (contains (SourceManager::just_removed_sources, ret)) {
y2milestone ("Just deleted: %1", ret);
ret = -1;
}
return ret;
}
boolean InitializeSources () {
// if (Mode::installation()) {
// y2milestone ("Sources already initialized");
// return true;
// }
y2milestone ("Initializing...");
if (!PackageLock::Check()) return false;
Pkg::TargetInitialize (Installation::destdir);
// the fastest way
Pkg::SourceRestore();
if (! Mode::installation()) {
// repos_already_used
foreach (integer one_id, Pkg::SourceGetCurrent (true), {
map source_data = Pkg::SourceGeneralData (one_id);
if (IsAddOnAlreadySelected (source_data["url"]:"", source_data["product_dir"]:"") >= -1) {
AddOnProduct::add_on_products = add (
AddOnProduct::add_on_products,
$[
"media" : one_id,
"media_url" : source_data["url"]:"",
"product_dir" : source_data["product_dir"]:"",
"product" : "",
"autoyast_product" : "",
]
);
}
});
}
return true;
}
boolean ReadControlFile () {
map <string, any> software_features = ProductFeatures::GetSection ("software");
if (software_features != nil) {
main_link = software_features["external_sources_link"]:"";
} else {
main_link = "";
}
y2milestone ("Got link: %1", main_link);
if (main_link == nil || main_link == "") {
main_link = "";
y2warning ("No link");
return false;
}
y2milestone ("Using link: %1", main_link);
return (main_link != nil && main_link != "");
}
string UseDownloadFile () {
return sformat ("%1/inst_productsources_downloadfile", Directory::tmpdir);
}
boolean RemoveFileIfExists (string file) {
if (FileUtils::Exists (file)) {
y2milestone ("Removing file: %1", file);
return (boolean) SCR::Execute (.target.remove, file);
}
return true;
}
boolean DownloadFile (string from, string to) {
RemoveFileIfExists (to);
map server_response = $[];
if (regexpmatch (from, "^[hH][tT][tT][pP]://")) {
from = regexpsub (from, "^[hH][tT][tT][pP]://(.*)", "http://\\1");
server_response = HTTP::Get (from, to);
} else if (regexpmatch (from, "^[fF][tT][pP]://")) {
from = regexpsub (from, "^[fF][tT][pP]://(.*)", "ftp://\\1");
server_response = FTP::Get (from, to);
} else if (regexpmatch (from, "^[hH][tT][tT][pP][sS]://")) {
from = regexpsub (from, "^[hH][tT][tT][pP][sS]://(.*)", "https://\\1");
server_response = HTTP::Get (from, to);
} else {
y2error ("Not a supported type: %1", from);
return false;
}
y2milestone ("Server response: %1", server_response);
if (server_response == nil) {
return false;
}
return true;
}
boolean ParseListOfServers (string download_file) {
if (! FileUtils::Exists (download_file)) {
y2error ("File %1 does not exist", download_file);
return false;
}
map xml_file_content = XML::XMLToYCPFile (download_file);
if (xml_file_content == nil) {
y2error ("Reading file %1 failed", download_file);
return false;
}
if (xml_file_content == $[]) {
y2milestone ("XML file is empty");
return false;
}
if (xml_file_content["servers"]:[] == []) {
y2milestone ("List of servers is empty");
return false;
}
list_of_servers = xml_file_content["servers"]:[];
// bugzilla #293811
// only installation_repo (trusted) links are used during installation
if (Stage::initial()) {
list_of_servers = filter (map one_server, list_of_servers, {
if (one_server["installation_repo"]:false == true) {
return true;
} else {
y2milestone ("Sever %1 is not used during installation...", one_server);
return false;
}
});
}
return true;
}
boolean ParseListOfSources (string download_file, string url_from) {
if (! FileUtils::Exists (download_file)) {
y2error ("File %1 does not exist", download_file);
return false;
}
list <map <string, any> > xml_file_content =
OneClickInstallStandard::GetRepositoriesFromXML (download_file);
if (xml_file_content == nil) {
y2error ("Parsing file %1 failed", download_file);
return false;
}
if (xml_file_content == []) {
y2milestone ("XML file is empty");
return false;
}
foreach (map one_repo, (list <map <string, any> >) xml_file_content, {
one_repo["url_from"] = url_from;
string repo_id = CreateRepoId (one_repo["url"]:"", one_repo["path"]:"/");
// do not redefine already added one
if (! haskey (list_of_repos, repo_id)) {
list_of_repos[repo_id] = one_repo;
}
});
return true;
}
boolean DownloadAndParseSources () {
list_of_repos = $[];
list_of_servers = [];
if (! DownloadFile (main_link, UseDownloadFile())) {
y2error ("Unable to download list of online repositories");
return false;
}
if (! ParseListOfServers (UseDownloadFile())) {
y2error ("Unable to parse list of servers");
return false;
}
foreach (map one_server, list_of_servers, {
if (one_server["link"]:"" != "") {
y2milestone ("Downloading list of repos from %1", one_server["link"]:"");
if (! DownloadFile (one_server["link"]:"", UseDownloadFile())) {
y2error ("Unable to download list of online repositories");
return;
}
if (! ParseListOfSources (UseDownloadFile(), one_server["link"]:"")) {
y2error ("Unable to parse list of repositories");
return;
}
}
});
// just for debugging purposes
y2debug ("%1", list_of_repos);
return true;
}
string GetCurrentLang () {
map cmd = (map) SCR::Execute (.target.bash_output, "echo -n $LANG");
string ret = tostring (cmd["stdout"]:"");
if (ret == "C" || ret == "" || ret == "POSIX") ret = nil;
y2milestone ("Using lang: %1", ret);
return ret;
}
symbol ReadDialog () {
list <string> actions_todo = [
// TRANSLATORS: progress step
_("Check network configuration"),
// TRANSLATORS: progress step
_("Download list of online repositories"),
// TRANSLATORS: progress step
];
list <string> actions_doing = [
// TRANSLATORS: progress step
_("Checking network configuration..."),
// TRANSLATORS: progress step
_("Downloading list of online repositories..."),
];
list <string> icons_for_progress = [
"yast-network.png",
"yast-restore.png",
];
if (! Stage::initial()) {
// TRANSLATORS: progress step
actions_todo = add (actions_todo, _("Initialize the repository manager"));
// TRANSLATORS: progress step
actions_doing = add (actions_doing, _("Initializing the repository manager..."));
icons_for_progress = add (icons_for_progress, "yast-sw_source.png");
}
Progress::NewProgressIcons (
// TRANSLATORS: dialog caption
_("Reading List of Online Repositories"),
" ",
size (actions_todo),
actions_todo,
actions_doing,
// TRANSLATORS: dialog help
_("<p>Please wait while the packager is being initialized
and the list of servers downloaded from the Web.</p>
"),
[icons_for_progress]
);
Wizard::SetTitleIcon ("yast-network");
Progress::NextStage();
if (UserWantsToAbort()) return `abort;
// Bugzilla #305554
// Check if there is enough memory (only in inst-sys)
// Called via WFM::call because of breaking RPM dependencies
// on yast2-add-on package.
if (Stage::initial()) {
any client_ret = WFM::call ("inst_check_memsize");
if (client_ret == `skip) {
// do not use them next time
Installation::add_on_selected = false;
Installation::productsources_selected = false;
y2milestone ("Skipping inst_productsources");
return `skip;
}
}
if (! NetworkRunning()) {
y2warning ("Cannot proceed, no network configured...");
// TRANSLATORS: error report
Report::Error (_("Cannot download list of repositories,
no network configured."));
return `nosources;
}
if (UserWantsToAbort()) return `abort;
// In the installation, recommended repositories will be preselected
if (Stage::initial()) {
// Set preselect_recommended to the correct state
string filename = sformat ("%1/productsources_already_called", Directory::tmpdir);
// Client must have been already called
if (FileUtils::Exists (filename)) {
preselect_recommended = false;
// Really for the very first time
} else {
preselect_recommended = true;
SCR::Execute (.target.bash, sformat ("touch '%1'", String::Quote (filename)));
y2milestone ("Running for the first time...");
}
// ...but never on the running system
} else {
preselect_recommended = false;
}
Progress::NextStage();
if (UserWantsToAbort()) return `abort;
// hotfix for bug #307680
// yast2-transfer ignores proxy settings
// will be fixed globally after 10.3
if (Mode::normal()) {
InitProxySettings();
}
if (UserWantsToAbort()) return `abort;
// language used for possible translations
if (! Stage::initial())
language_long = GetCurrentLang ();
// fallback if no LANG variable set
if (language_long == nil || language_long == "")
language_long = Language::language;
// de_DE.UTF-8 --> de_DE
integer dot_pos = search (language_long, ".");
if (dot_pos != nil) {
language_long = substring (language_long, 0, dot_pos);
}
if (language_long != nil) {
if (size(language_long) >= 2) {
language_short = substring (language_long, 0, 2);
} else {
language_short = language_long;
}
}
y2milestone ("Preferred language: %1 %2", language_long, language_short);
if (UserWantsToAbort()) return `abort;
if (! ReadControlFile()) {
y2milestone ("Feature not supported by the product");
// TRANSLATORS: light-warning message
Report::Message (_("No product URL defined to download
list of repositories from."));
return `nosources;
}
if (UserWantsToAbort()) return `abort;
if (! DownloadAndParseSources()) {
y2error ("Cannot download or parse repositories");
// TRANSLATORS: warning message
Report::Warning (_("Unable to download list of repositories
or no repositories defined."));
return `nosources;
}
if (UserWantsToAbort()) return `abort;
if (! Stage::initial()) {
Progress::NextStage();
InitializeSources();
}
if (UserWantsToAbort()) return `abort;
Progress::Finish();
sleep (600);
return `next;
}
/**
* Returns a localized string using given parametters.
*
* @param string current_id to identify the source in list_of_repos map
* @param list <string> possible_keys containing which keys in the map
* should be tried (something is always better than amty string)
*
* @struct $[
* // key
* "description" : "repository description",
* // "localized" + key
* "localized_description" : $[
* "en_GB" : "repository description (localized to en_GB)",
* ...
* ],
* ]
*/
string GetLocalizedString (string current_id, list <string> possible_keys) {
string ret = "";
// try localized strings at first
foreach (string possible_key, possible_keys, {
string loc_key = sformat ("localized_%1", possible_key);
if (list_of_repos[current_id, loc_key, language_long]:"" != "") {
ret = list_of_repos[current_id, loc_key, language_long]:"";
break;
} else if (list_of_repos[current_id, loc_key, language_short]:"" != "") {
ret = list_of_repos[current_id, loc_key, language_short]:"";
break;
}
});
if (ret != "") return ret;
// try default strings
foreach (string possible_key, possible_keys, {
if (list_of_repos[current_id, possible_key]:"" != "") {
ret = list_of_repos[current_id, possible_key]:"";
break;
}
});
return ret;
}
void PrintRepositoryDescription () {
string current_id = (string) UI::QueryWidget (`id ("addon_repos"), `CurrentItem);
// Nothing selected, no description
if (current_id == nil || current_id == "") {
UI::ChangeWidget (`id ("addon_description"), `Value, "");
return;
}
string recommended = (list_of_repos[current_id, "recommended"]:false == true ?
sformat(
// TRANSLATORS: HTML-formatted summary text
// %1 is replaced with "Yes" (currently only "Yes")
// see *4
_("<b>Recommended:</b> %1<br>"),
// TRANSLATORS: used for "Recommended: Yes" (see *4)
_("Yes")
)
:
""
);
string description = sformat (
// TRANSLATORS: This is a complex HTML-formatted information about selecetd external repository
// It contains "key: value" pair, one per line, separated by <br> tags
// %1 is replaced with an URL of the selected repository
// %2 is replaced with an URL from which we've got this repository information
// %3 is replaced with a summary text for the selected repository
// %4 is replaced with a description text for the selected repository
// %5 is replaced with an emty string or "Recommended: Yes" (*4)
_("<p>
<b>URL:</b> %1<br>
<b>Linked from:</b> %2<br>
<b>Summary:</b> %3<br>
<b>Description:</b> %4<br>
%5
</p>"),
list_of_repos[current_id, "url"]:"",
list_of_repos[current_id, "url_from"]:"",
GetLocalizedString (current_id, ["summary", "name"]),
GetLocalizedString (current_id, ["description"]),
recommended
);
UI::ChangeWidget (`id ("addon_description"), `Value, description);
}
list <string> repos_visible_now = [];
list <string> already_selected_in_dialog = [];
boolean IsSelectedInDialog (string repo_id) {
return contains (already_selected_in_dialog, repo_id);
}
// visible but not selected items
// used for filter together with recommended repos
// not to select them 'again' when filter matches
list <string> currently_NOT_selected = [];
/*
* This function fills up the table repositories found on a web servers
* linked from control file.
*
* Order of appearance:
* Running system: sorted by repository name
* Inst-Sys: sorted by "recommended tag", then by name
*
* Preselections:
* Running sustem: no repositories are preselected
* Inst-Sys: "recommended" repositories are prelected
* but only for the first time when running this client
*
* @see bugzilla #297628
*/
void InitRepositoriesWidget (string filter_string, boolean first_init, string current_item) {
list <term> items = [];
list <string> recommended_items = [];
repos_visible_now = [];
integer counter = -1;
// used for recommended repos
boolean some_repo_already_selected = false;
// boolean current_item_is_listed = false;
foreach (string url, map one_repo, list_of_repos, {
string repo_id = CreateRepoId (one_repo["url"]:"", one_repo["path"]:"/");
integer src_id = IsAddOnAlreadySelected (one_repo["url"]:"", one_repo["path"]:"/");
boolean already_used = false;
// repository has been already initialized
if (src_id > -1) {
repos_already_used[repo_id] = src_id;
already_used = true;
// in some modes, it's required to hide alerady used repos
if (skip_already_used_repos) {
if (contains (SourceManager::just_removed_sources, src_id)) {
y2milestone ("Not skipping repo %1, known as removed ID %2", repo_id, src_id);
} else {
return;
}
}
// repository has been already selected
} else if (IsSelectedInDialog (repo_id)) {
already_used = true;
}
// If this variable is true, no recoomended repos are preselected
if (already_used) {
some_repo_already_selected = true;
// List of not-selected repos
} else if (! first_init) {
currently_NOT_selected = add (currently_NOT_selected, repo_id);
}
// bugzilla #358001
// filter works with localized names
string localized_name = GetLocalizedString (repo_id, ["name", "url"]);
// do filter (filter after some_repo_already_selected is set)
if (filter_string != "") {
// neither "url" nor "name" matching
if (
! regexpmatch (one_repo["url"]:"", filter_string)
&&
! regexpmatch (localized_name, filter_string)
) {
return;
}
}
counter = counter + 1;
if (url == "") {
y2error ("Repository %1 has no 'url'", one_repo);
return;
}
boolean recommended = false;
// Only in stage initial and if no other repository is selected:
// preselect recommended repositories...
// always fill-up this list -- later used for sorting using 'recommended' tag
// Bugzilla #297628
//if (Stage::initial()) {
recommended = one_repo["recommended"]:false;
if (recommended) {
recommended_items = add (recommended_items, repo_id);
}
//}
// // was 'current' and remains 'current'
// if (repo_id == current_item)
// current_item_is_listed = true;
items[counter] = `item (
`id (repo_id),
localized_name,
already_used
);
repos_visible_now[counter] = repo_id;
});
items = sort (term one_item_a, term one_item_b, items, ``(one_item_a[1]:"" < one_item_b[1]:""));
// Preselect the recommended repositories when ne repository has been selected yet
if (preselect_recommended) {
list <term> tmp_items = items;
integer counter = -1;
string current_repoid = "";
foreach (term one_item, tmp_items, {
counter = counter + 1;
current_repoid = one_item[0,0]:"---";
// recommended_items contain list of all recommended items (visible on the screen)
if (contains (recommended_items, current_repoid)) {
y2milestone ("Preselecting: %1", current_repoid);
one_item[2] = true;
items[counter] = one_item;
}
});
}
// In the initial stage, repos are additionally sorted whether they are recommended or not
// if (Stage::initial()) {
items = sort (term one_item_a, term one_item_b, items, ``(
contains (recommended_items, one_item_a[0,0]:"") > contains (recommended_items, one_item_b[0,0]:"")
));
// }
UI::ChangeWidget (`id ("addon_repos"), `Items, items);
// disabled
// if (current_item_is_listed) {
// UI::ChangeWidget (`id ("addon_repos"), `CurrentItem, current_item);
// } else if (size (items) > 0) {
// UI::ChangeWidget (`id ("addon_repos"), `CurrentItem, items[0,0,0]:"");
// }
PrintRepositoryDescription();
// Preselect recommended repos only once
preselect_recommended = false;
}
void StoreSelectedInDialog () {
// remember already selected items before filtering
list <string> currently_selected = (list <string>) UI::QueryWidget (`id ("addon_repos"), `SelectedItems);
// all visible repos - just now
foreach (string one_repo, repos_visible_now, {
// visible repository is not selected
if (! contains (currently_selected, one_repo)) {
// was already selected
if (contains (already_selected_in_dialog, one_repo)) {
already_selected_in_dialog = filter (string o_r, already_selected_in_dialog, {
return o_r != one_repo;
});
}
// visible repository is selected now
} else {
// wasn't selected
if (! contains (already_selected_in_dialog, one_repo)) {
// add it
already_selected_in_dialog = add (already_selected_in_dialog, one_repo);
}
}
});
}
boolean HandleSelectedSources () {
StoreSelectedInDialog();
repos_to_be_used = already_selected_in_dialog;
// FIXME: handle no repositories selected (warning)
// FIXME: a lot of repositories selected (warning)
return true;
}
string EscapeChars (string input) {
if (input == "" || input == nil) return input;
// \ must be the first character!
string escape = "\\(){}[]+^$|";
string ret = input;
integer i = 0;
integer sz = size (escape);
while (i < sz) {
string ch = substring (escape, i, 1);
y2debug ("Escaping %1", ch);
ret = mergestring (splitstring (ret, ch), "\\" + ch);
i = i + 1;
}
return ret;
}
string casesenschars = "^[abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ]$";
// Example:
// <- "aBc/iop"
// -> "[Aa][Bb][Cc]/[Ii][Oo][Pp]"
string MakeCaseInsensitiveRegexp (string input) {
if (input == nil || input == "") return input;
list <string> characters = [];
integer counter = 0;
integer input_size = size (input);
while (counter < input_size) {
characters[counter] = substring (input, counter, 1);
counter = counter + 1;
}
input = "";
foreach (string onechar, characters, {
if (regexpmatch (onechar, casesenschars)) {
onechar = sformat ("[%1%2]", toupper (onechar), tolower (onechar));
}
input = input + onechar;
});
return input;
}
void HandleFilterButton () {
StoreSelectedInDialog();
string filter_string = (string) UI::QueryWidget (`id ("filter_text"), `Value);
string current_item = (string) UI::QueryWidget (`id ("addon_repos"), `CurrentItem);
filter_string = EscapeChars (filter_string);
filter_string = MakeCaseInsensitiveRegexp (filter_string);
InitRepositoriesWidget (filter_string, false, current_item);
UI::SetFocus (`id ("filter_text"));
}
symbol SourcesDialog () {
Wizard::SetContents (
// TRANSLATORS: dialog caption
_("List of Online Repositories"),
`VBox (
`HBox (
`HVSquash(`MinWidth(20, `InputField (`id ("filter_text"), `opt(`hstretch), ""))),
// TRANSLATORS: push button
`Bottom(`PushButton (`id ("do_filter"), `opt(`default), _("&Filter"))),
`HStretch()
),
`VSpacing (0.5),
`VWeight (
2,
`MultiSelectionBox(
`id ("addon_repos"), `opt (`notify, `hstretch),
// TRANSLATORS: multi-selection box
_("&List of Repositories"), []
)
),
`VSpacing (0.5),
// TRANSLATORS: Rich-text widget (HTML)
`Left (`Label (_("Repository Description"))),
`VWeight (
1,
`RichText (`id ("addon_description"), "")
)
),
// TRANSLATORS: dialog help 1/3
_("<p>Here you can see default online repositories downloaded from the Internet.
Click on the repository to see its details.</p>
") +
(Stage::initial() ?
// TRANSLATORS: dialog help 2/3 (version for installation)
_("<p>If you want one or more online repositories to be used,
select those you want and click <b>Next</b>.</p>
")
:
// TRANSLATORS: dialog help 2/3 (version for running system)
_("<p>If you want one or more online repositories to be used,
select those you want and click <b>Finish</b>.</p>
")
) +
// TRANSLATORS: dialog help 3/3
(skip_already_used_repos != true ? _("<p>To remove a used repository, just deselect it.</p>"):""),
(Mode::installation() ? GetInstArgs::enable_back():false),
(Mode::installation() ? GetInstArgs::enable_next():true)
);
Wizard::SetTitleIcon ("yast-sw_source");
if (! Stage::initial()) {
Wizard::DisableBackButton();
if (script_called_from_another) {
Wizard::SetAbortButton (`cancel, Label::CancelButton());
Wizard::SetNextButton (`next, Label::OKButton());
} else {
Wizard::SetNextButton (`next, Label::FinishButton());
}
} else {
// Next button must be always enabled
// bnc #392111
Wizard::RestoreNextButton();
Wizard::EnableNextButton();
Wizard::DisableBackButton();
// from add-ons
if (script_called_from_another) {
Wizard::SetAbortButton (`cancel, Label::CancelButton());
} else {
Wizard::RestoreAbortButton ();
}
}
repos_already_used = $[];
InitRepositoriesWidget ("", true, nil);
any dialog_ret = nil;
while (true) {
dialog_ret = UI::UserInput();
if (dialog_ret == `back) {
y2milestone ("Going back");
dialog_ret = `special_go_back;
break;
} else if (dialog_ret == `next) {
if (HandleSelectedSources()) {
break;
} else {
continue;
}
} else if (dialog_ret == `abort || dialog_ret == `cancel) {
dialog_ret = `abort;
if (Stage::initial()) {
// from add-ons
if (script_called_from_another) {
y2milestone ("Back to add-ons");
break;
// from workflow
} else if (Popup::ConfirmAbort (`painless)) {
break;
}
} else {
if (script_called_from_another) {
break;
} else if (Popup::ContinueCancelHeadline (
// TRANSLATORS: popup header
_("Aborting Configuration of Online Repository"),
// TRANSLATORS: popup question
_("Are you sure you want to abort the configuration?")
)) {
break;
}
}
} else if (dialog_ret == "addon_repos") {
PrintRepositoryDescription();
} else if (dialog_ret == "do_filter") {
HandleFilterButton();
} else {
y2error ("Unknown ret: %1", dialog_ret);
}
}
Wizard::EnableBackButton();
Wizard::RestoreAbortButton();
return (symbol) dialog_ret;
}
void CreateAndAdjustWriteProgress (list <string> & actions_todo, list <string> actions_doing) {
y2milestone ("Creating new Write() progress");
Progress::New (
// TRANSLATORS: dialog caption
_("Writing List of Online Repositories"),
" ",
size (actions_todo),
actions_todo,
actions_doing,
// TRANSLATORS: dialog help
_("<p>Please wait while the repository manager downloads the repository details...</p>")
);
Wizard::SetTitleIcon ("yast-sw_source");
}
boolean CreateSource (
string url, string pth, string repo_name,
list <string> & actions_todo, list <string> & actions_doing,
boolean no_progress_updates
) {
integer src_id = nil;
string repo_type = Pkg::RepositoryProbe(url, pth);
y2milestone("Probed repository type: %1", repo_type);
// probing succeeded?
if (repo_type != nil && repo_type != "NONE")
{
// create alias in form "<hostname>-<last_path_element>"
map parsed_url = URL::Parse(url);
string alias = parsed_url["host"]:"";
list<string> path_parts = splitstring(parsed_url["path"]:"", "/");
// remove empty parts
path_parts = filter(string p, path_parts, {return size(p) > 0;});
if (size(path_parts) > 0)
{
string suffix = path_parts[size(path_parts) - 1]:"";
if (regexpmatch(suffix, "[0-9]+$") && size(path_parts) > 1)
{
y2milestone("Version string detected in path element");
suffix = path_parts[size(path_parts) - 2]:"";
}
alias = alias + "-" + suffix;
}
alias = GetUniqueAlias(alias);
y2milestone("Using alias: %1", alias);
src_id = Pkg::RepositoryAdd ($[
"enabled" : false,
"name" : repo_name,
"base_urls" : [url],
"prod_dir" : pth,
// alias needs to be unique
// bugzilla #309317
"alias" : alias,
"type" : repo_type,
]);
}
if (src_id == nil) {
Report::Error (sformat (
// TRANSLATORS: pop-up error message
// %1 is replaced with a repository name or URL
_("Adding repository %1 failed"),
(repo_name != "" ? repo_name : url)
));
// FIXME: retry ?
return false;
}
if (! AddOnProduct::AcceptedLicenseAndInfoFile (src_id)) {
Pkg::SourceDelete (src_id);
return false;
}
if (!Pkg::SourceRefreshNow (src_id))
{
Report::Error (sformat (
// TRANSLATORS: pop-up error message
// %1 is replaced with a repository name or URL
_("Adding repository %1 failed"),
(repo_name != "" ? repo_name : url))
+ "\n" + Pkg::LastError()
);
return false;
}
if (!Pkg::SourceSetEnabled (src_id, true))
{
Report::Error (sformat (
// TRANSLATORS: pop-up error message
// %1 is replaced with a repository name or URL
_("Adding repository %1 failed"),
(repo_name != "" ? repo_name : url))
+ "\n" + Pkg::LastError()
);
return false;
}
if (Stage::initial()) {
AddOnProduct::Integrate (src_id);
map <string, any> prod = (map <string, any>) Pkg::SourceProductData (src_id);
y2milestone ("Product Data: %1", prod);
string repo_id = CreateRepoId (url, pth);
y2milestone ("Addind repository with ID: %1", repo_id);
AddOnProduct::add_on_products = add (AddOnProduct::add_on_products, $[
"media" : src_id,
"product" : repo_name,
"autoyast_product" : prod["productname"]:"",
"media_url" : url,
"product_dir" : pth,
]);
}
}
symbol WriteDialog () {
list <string> actions_todo = [];
list <string> actions_doing = [];
boolean at_once = false;
list <integer> repos_to_be_deleted = [];
// repos_to_be_used
// repos_already_used
//y2milestone ("ToBeDeleted: %1", repos_to_be_deleted);
//y2milestone ("ReposAlreadyUsed: %1", repos_already_used);
//y2milestone ("ReposToBeUsed: %1", repos_to_be_used);
// go through all already initialized repositories
// add unselected repository to 'repos_to_be_deleted'
// remove already selected repository from 'repos_to_be_used'
// Currently, Mode::normal doesn't show already used repos
// (when 'skip_already_used_repos' is 'true')
// and thus doesn't support removing them
if (skip_already_used_repos != true) {
foreach (string id_used, integer src_id, repos_already_used, {
// was used, but isn't anymore
if (! contains (repos_to_be_used, id_used)) {
repos_to_be_deleted = add (repos_to_be_deleted, src_id);
// was used and remains used
} else {
y2milestone ("NotUsingAgain: %1", id_used);
repos_to_be_used = filter (string id_already_used, repos_to_be_used, {
return (id_used != id_already_used);
});
}
});
}
//y2milestone ("WillBeDeleted: %1", repos_to_be_deleted);
//y2milestone ("WillBeUsed: %1", repos_to_be_used);
if (repos_to_be_deleted != []) {
y2milestone ("Repos to be deleted: %1", repos_to_be_deleted);
// TRANSLATORS: progress step
actions_todo = [_("Delete deselected online repositories")];
// TRANSLATORS: progress step
actions_doing = [_("Deleting deselected online repositories...")];
}
if (size (repos_to_be_used) > 12) {
at_once = true;
// TRANSLATORS: progress step
actions_todo = add (actions_todo, _("Add all selected online repositories"));
// TRANSLATORS: progress step
actions_doing = add (actions_doing, _("Adding all selected online repositories..."));
} else {
foreach (string repo_id, repos_to_be_used, {
actions_todo = add (actions_todo, sformat (
// TRANSLATORS: progress step
// %1 is replaced with repository name or URL
_("Add repository: %1"),
GetLocalizedString (repo_id, ["name", "url"])
));
actions_doing = add (actions_doing, sformat (
// TRANSLATORS: progress step,
// %1 is replaced with repository name or URL
_("Adding repository: %1 ..."),
GetLocalizedString (repo_id, ["name", "url"])
));
});
}
if (size (actions_todo) == 0) {
y2milestone ("Nothing to do...");
return `next;
}
// Create writing dialog - initial state
CreateAndAdjustWriteProgress (actions_todo, actions_doing);
if (UserWantsToAbort()) return `abort;
if (repos_to_be_deleted != []) {
Progress::NextStage();
foreach (integer src_id, repos_to_be_deleted, {
boolean success = Pkg::SourceDelete (src_id);
if (! success) y2error ("Couldn't delete repository %1", src_id);
AddOnProduct::Disintegrate (src_id);
// filter it also from the list of Add-Ons
AddOnProduct::add_on_products = filter (map <string, any> one_addon, AddOnProduct::add_on_products, {
return (one_addon["media"]:-1 != src_id);
});
});
}
if (UserWantsToAbort()) return `abort;
// One progress stage for all repositories
if (at_once) Progress::NextStage();
foreach (string repo_id, repos_to_be_used, {
// If not at once, call one stage per repository
if (! at_once) Progress::NextStage();
if (UserWantsToAbort()) return `abort;
CreateSource (
list_of_repos[repo_id, "url"]:"",
list_of_repos[repo_id, "path"]:"/",
GetLocalizedString (repo_id, ["name"]),
actions_todo,
actions_doing,
at_once
);
});
if (UserWantsToAbort()) return `abort;
// Redraw installation wizard
if (Stage::initial()) {
UpdateWizardSteps();
// Store repositories
} else {
Pkg::SourceSaveAll();
}
Progress::Finish();
if (! Stage::initial()) {
sleep (1000);
}
return `next;
}
symbol RunMain () {
map aliases = $[
"read" : ``( ReadDialog() ),
"sources" : ``( SourcesDialog() ),
"write" : ``( WriteDialog() ),
];
map sequence = $[
"ws_start" : "read",
"read" : $[
`next : "sources",
// not enough memory
`skip : `next,
`nosources : `next,
`abort : `abort,
],
"sources" : $[
`special_go_back : `back,
`next : "write",
`abort : `abort,
],
"write" : $[
`next : `next,
`abort : `abort,
],
];
any ret = Sequencer::Run (aliases, sequence);
y2milestone ("Sequencer::Run %1", ret);
return (symbol) ret;
}
/***********************/
if (Mode::normal()) {
Wizard::CreateDialog();
}
symbol client_ret = RunMain();
if (Mode::normal()) {
Wizard::CloseDialog();
}
/***********************/
return client_ret;
}
ACC SHELL 2018