ACC SHELL

Path : /usr/share/YaST2/clients/
File Upload :
Current File : //usr/share/YaST2/clients/inst_productsources.ycp

{
    /***
     * 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