/**
 * File:	modules/ProfileManager.ycp
 * Package:	Configuration of profile-manager
 * Summary:	Data for configuration of profile-manager, input and output funcs.
 * Authors:	Jiri Suchomel <jsuchome@suse.cz>
 *
 * $Id: ProfileManager.ycp 20797 2005-01-18 13:55:34Z jsuchome $
 *
 * Representation of the configuration of profile-manager.
 * Input and output routines.
 */

{

module "ProfileManager";
textdomain "profile-manager";

import "Directory";
import "Label";
import "Package";
import "Popup";
import "Progress";
import "Report";
import "String";


// function prototypes:
global define boolean ProgressPopup (string position,integer start,integer end);
global define boolean Wait ();


global string last_ret = "";

/**
 * how many times was progress bar used
 */
global integer hash_size = 0;


//include "profile-manager/progress.ycp";

/**
 * Is the module runing for the first time?
 */
global boolean first_time = false;

/**
 * Is SCPM enabled?
 */
global boolean enabled = false;

/**
 * Is SCPM re-initialization needed? (possibly after update)
 */
global boolean needs_reinit = false;

/**
 * Is SCPM recovery? (after something failed)
 */
global boolean needs_recover = false;

// for saving status of enabled/disabled radiobuttons in OptionsDialog
// when going to ConfigureResources
global boolean tmp_enabled = false;

/**
 * Is SCPM initialized?
 * This is set to true after first enabling.
 */
global boolean initialized = false;

/**
 * Is set "close popups automaticaly" check?
 */
global boolean close_popups = false;

/**
 * This shows if the "autoswitch" was set while adding a new profile.
 */
global boolean auto = false;

/**
 * Map of actual profiles and its descriptions.
 * It is read in ReadProfiles() and ReadProfilesDescription().
 * @example
 *  $[ "default" : "This is the default",
 *     "home" : "My home profile",
 *   ...
 *  ]
 */
global map profiles = $[];

/**
 * Map of selected profile's settings.
 * It is read in GetProfile() and written in SetProfile().
 * @example
 *  $[ "name" : "home",
 *     "description" : "My home profile",
 *     "prestart" : "",
 *     "postart" : "/tmp/poststart_script",
 *     "prestop" : "",
 *     "poststop" : ""
 *  ]
 */
global map profile_settings = $[];

/**
 * This is the name of the active profile.
 * It is read in ReadActiveProfile().
 */
global string active_profile = "";

/**
 * This is the name of the profile selected in some widget.
 */
global string selected_profile = "";

/**
 * The list of predefined resource sets.
 * @example
 *  [ "typical",
 *    "network_only"
 *  ]
 */
global list resource_sets_predefined = [];

/**
 * The list of user defined resource sets.
 */
global list resource_sets_individual = [];

/**
 * This variable defines the action whis will be done
 * with the profile in the ProfileSettingsDialog.
 * Can be "new", "copy" or "edit".
 */
global string profile_action = "";

global integer active_resource = 0;
global string active_resource_name = "";

/**
 *  When scpm action fails, the error message is stored here by SetError()
 */
global string scpm_error = "";

/**
 * Abort function
 * return boolean return true if abort
 */
global block<boolean> AbortFunction = nil;

/**
 * In this map are written the variables close_popups and first_time.
 * It is read from the file in ReadUserSettings()
 * and written to the file in WriteUserSettings().
 */
map state = $[];

/**
 * how many lines of progress has been pruducted
 */
integer progress_size = 0;


/**
 * The name of the file, where the progress state is beeing written.
 * (The same name must use the scpm agent!)
 */
string progressfile = "/scpm.progress";
string hashfile = "/scpm.hash";

/**
 * The name of the file, where are written the resource changes.
 * This file is checked after pushing "Show changes" button.
 * (The same name must use the scpm agent!)
 */
string changesfile = "/scpm.changes";
string tmpfile = "/scpm.tmp";
string progresspath = "";
string hashpath = "";
string changespath = "";
string tmppath = "";
string tmpdir = "";

/**
 * This map represents the switch_info struct.
 * It is read in PrepareSwitch() and used by Switch().
 * @example
 *  $[ "profile_name": "home",
 *     "profile_modified": true,
 *     "modified_resources":
 *      [ $[ "resource_name": "/etc/X11/XF86Config",
 *           "resource_type": "file",
 *           "is_new": false,
 *           "is_deleted": true,
 *           "save": true
 *        ]
 *      ]
 *  ]
 */
map switch_info = $[];


/**
 * Abort function
 * @return blah blah lahjk
 */
global define boolean Abort() ``{
    if (AbortFunction != nil)
	return eval (AbortFunction) == true;
    return false;
}

/**
 * This function is called when some scpm command failed.
 * The error message is read and shown in Popup::Error.
 */
define void ShowError () ``{

    string err = (string) SCR::Read (.scpm.error);
    if (err != nil && err != "")
        Popup::Error (err);
}

/**
 * This function is called when some scpm command failed.
 * The error message is read and stored in global variable scpm_error.
 */
define void SetError () ``{

    string err = (string) SCR::Read (.scpm.error);
    if (err != nil && err != "")
        scpm_error = err;
    else
        scpm_error = "";
}

// --------------------------------------------------------------------------
// ------- functions and variables for work with resource groups ------------
// --------------------------------------------------------------------------

/**
 * all resource groups available; filled by ReadResourceGroups()
 */
map resource_groups = $[];

/**
 * list of resource groups marked for deletion
 */
list resource_groups_deleted = [];

/**
 * this map holds the contents of scpm.conf configuration file
 */
global map scpm_conf = $[];

/**
 * helper structure for faster querying modified resource groups (prepared
 * for Switch) - is filled in GetModifiedGroups() function
 */
global map modified_groups = $[];

/**
 * default value of "save" flags of modified resources (groups)
 * TODO: when is changed?
 */
boolean default_save = true;

/**
 * if module was canceled (aborted)
 */
global boolean canceled = false;

/**
 * name of resource group currently beeing edited (selected in SwitchDialog)
 */
global string current_resource_group = "";

/**
 * Writes modified resource group.
 * @param groupname resource group name
 * @param resources list of resources belonging to this group
 * @param descr group description
 * @return success
 */
define boolean SetResourceGroup(string groupname, list resources, string descr) ``{

    boolean ret = SCR::Write(add(.scpm.rg.group, groupname), resources, descr);
    if (!ret)
    {
	SetError();
    }
    return ret;
}

/**
 * Gets resources of resource group.
 * @param groupname resource group name
 * @return list of resources
 */
global define list GetResourceGroupResources(string groupname) ``{

    list resource_group = (list) SCR::Read(.scpm.rg.group, groupname);
    if (resource_group == nil)
    {
	SetError();
	return [];
    }
    return resource_group;
}

/**
 * Reads all available resource groups.
 * @return true on success
 */
global define boolean ReadResourceGroups() ``{

    resource_groups = (map) SCR::Read(.scpm.rg);
    resource_groups_deleted = [];
    if (resource_groups == nil)
    {
	SetError();
	return false;
    }
    foreach (string name, map group, (map<string,map<string,any> >)resource_groups, ``{
	resource_groups[name,"resources"] = GetResourceGroupResources (name);
    });

    return true;
}

global define map ReadResourceGroup(string groupname) ``{

    map resource_group = (map) SCR::Read(.scpm.rg.group_map, groupname);
    if (resource_group == nil)
    {
	SetError();
	return $[];
    }
    return resource_group;
}

/**
 * Resets RG's to default values
 * @return success
 */
global define boolean ResetResourceGroups() ``{

    boolean ret = (boolean) SCR::Execute (.scpm.rg.reset);
    if (!ret)
	SetError();
    return ret;
}

/**
 * Resets one resource group to default values
 * @return success
 */
global define boolean ResetResourceGroup (string groupname) ``{

    boolean ret = (boolean) SCR::Execute (.scpm.rg.group.reset, groupname);
    if (!ret)
	SetError();
    return ret;
}


/**
 * Returns all available resource groups.
 * @return map of groups
 */
global define map GetResourceGroups() ``{

    return resource_groups;
}

/**
 * Can be resource group deleted by user?
 * @param groupname name of group
 * @return boolean
 */
global define boolean GroupDeletable (string groupname) ``{

    return resource_groups [groupname, "user_defined"]:false;
}

/**
 * Does given resource group exist? (Checked when user enters name of new group)
 * @param groupname name of group
 * @return boolean
 */
global define boolean GroupnameExists (string groupname) ``{

    return haskey (resource_groups, groupname);
}

/**
 * Returns map resource group
 * @param groupname name of group
 * @return map
 */
global define map GetGroup (string groupname) ``{

    return (map) eval (resource_groups [groupname]:$[]);
}

/**
 * Marks selected resource group for deletion (will not be shown in list)
 * @param groupname name of group
 */
global define void MarkGroupDeleted (string groupname) ``{

    resource_groups = remove (resource_groups, groupname);
    if (resource_groups[groupname, "what"]:"" != "added")
    {
	resource_groups_deleted = add (resource_groups_deleted, groupname);
    }
    y2debug ("resource group %1 marked for deletion", groupname);
}

/**
 * Changes "active" flag of selected resource group to opposite value
 * @param groupname name of group
 * @return new value
 */
global define boolean MarkGroupActive (string groupname) ``{

    boolean active = !resource_groups[groupname, "active"]:false;
    resource_groups[groupname,"active"] = active;
    resource_groups[groupname,"user_modified"] = true;
    return active;
}


/**
 * Updates global resource_groups map with currently edited group
 */
global define void ChangeGroup (map group) ``{

    if (group["org_name"]:"" != "")
	resource_groups = remove (resource_groups, group["org_name"]:"");
    resource_groups = add (resource_groups, group["name"]:"",group);
}

/**
 * Store error messages generated by multiple commands to one string
 */
define string AllErrorMessages (string error) ``{

    if (scpm_error != "")
    {
	if (error != "")
	    error = error + "\n";
	error = error + scpm_error;
	scpm_error = "";
    }
    return error;
}

/**
 * Save the resource groups edited in YaST UI
 * @return success
 */
global define boolean SaveResourceGroups () ``{

    scpm_error = "";
    string error = "";

    // deletion must be done in first cycle
    // (because some new group can have the name of deleted one)
    foreach (string name, (list<string>) resource_groups_deleted, ``{

	y2milestone ("deleting group %1", name);
	if (!SCR::Write (.scpm.rg.group.delete, name))
	    SetError();
	// deactivate deleted group
	SCR::Write (.scpm.rg.group.activate, name, false);
	error = AllErrorMessages (error);
    });
    // renaming must be done in second cycle
    // (because some new group can have the old name of renamed one)
    foreach (string name, map group, (map<string,map<string,any> >) resource_groups, ``{
	if (group["what"]:"" == "renamed")
	{
	    y2milestone ("renaming group %1 to %2", group["org_name"]:"", name);
	    if (!SCR::Write (
		    add (.scpm.rg.group.rename, group["org_name"]:name),
		    name))
		SetError();
	}
	error = AllErrorMessages (error);
    });
    // rest of modifications
    foreach (string name, map group, (map<string,map<string,any> >) resource_groups, ``{
	string what = group["what"]:"";

	if (what == "added" || what == "edited")
	{
	    y2milestone ("saving modified group %1", name);
	    if (!SCR::Write (
		    add (.scpm.rg.group.set, name),
		    group["resources"]:[],
		    group["description"]:""))
		SetError();
	}
	// do Activation/Deactivation (it is not done in Set/Rename)
	if (group["user_modified"]:false)
	{
	    boolean activate = group["active"]:true;
	    if (!SCR::Write (.scpm.rg.group.activate, name, activate))
		SetError();
	    y2milestone ("changed active status of %1 to %2", name, activate);
	}
	error = AllErrorMessages (error);
    });
    if (error != "")
	scpm_error = error;
    return (scpm_error == "");
}

/**
 * reads a contents of scpm config file (/etc/scpm.conf)
 * @return false when file doesn't exist
 */
global define boolean ReadConfigFile () ``{

    if (SCR::Read(.target.size, "/etc/scpm.conf") == -1)
	return false;

    scpm_conf = $[
	"verbose":	((string) SCR::Read(.sysconfig.scpm.VERBOSE) == "yes"),
	"switch_mode":	(string) SCR::Read(.sysconfig.scpm.SWITCH_MODE),
	"boot_mode":	(string) SCR::Read(.sysconfig.scpm.BOOT_MODE)
    ];
    y2debug ("scpm configuration: %1", scpm_conf);
    return true;
}

/**
 * writes configuration data to scpm config file (/etc/scpm.conf)
 * @return true
 */
global define boolean WriteConfigFile () ``{

    foreach (string key, any value, (map<string,any>) scpm_conf, ``{
	path agent = add(.sysconfig.scpm, toupper(key));
	if (is (value, boolean))
	    SCR::Write(agent, ((boolean) value) ? "yes" : "no");
	else
	    SCR::Write(agent, (string) value);
    });
    return true;
}

/**
 * gets the modified resources belonging to one group
 * @return map of format resources_of_modified_group (see GetModifiedGroups())
 */
global define map GetModifiedGroupResources(string groupname) ``{

    // we want to work with a local copy -> eval
    return (map) eval (modified_groups[groupname]:$[]);
}

/**
 * Uses "switch_info" map to generate helper structure "modified_groups"
 * @example
 * <b>modified_groups</b> structure
 * $[ modified_group: resources_of_modified_group ]
 *
 * <b>modified_group</b> is name of resource group
 *
 * <b>resources_of_modified_group</b> is a map of format:
 * $[
 *	0: $[
 *		"resource_name":	"/etc/inetd.conf",
 *		"resource_type":	"file",
 *		"save":			true,
 *		"save_mode":		"normal",
 *		"groups": [
 *			$[ "name":	"inetd",
 *			   "description": "Inetd services"
 *			]]
 *	],
 *	1: $[
 *		"resource_name":	"xinetd",
 *		"resource_type":	"service",
 *		"save":			true,
 *		"save_mode":		"normal",
 *		"groups": [
 *			$[ "name":	"inetd",
 *			   "description": "Inetd services"
 *			]]
 *	],
 *	"save"	:	true,
 *	"modified":	false,
 *	"description"	"Inetd services"
 * ]
 * numbers are indexes in the switch_info["modified_resources"]:[] list,
 * "save" and "modified" are flags of this group (<b>modified_group</b>)
 */
global define void GetModifiedGroups() ``{

    integer i = 0;
    modified_groups = $[];
    foreach (map resource, (list<map<string,any> >) switch_info["modified_resources"]:[], ``{
	// go through all groups owning this resource:
	foreach (map group, resource["groups"]:[], ``{
	    string groupname = group["name"]:"";
	    if (!haskey (modified_groups, groupname))
		modified_groups [groupname] = $[
		    "description":	group["description"]:"",
		    "save":		default_save,
		    "modified":		false
		];
	    // add a map of this resource to the group,
	    // key is resource's index in modified_resources list
	    modified_groups [groupname,i] = eval (resource);
	});
	i = i + 1;
    });
}

/**
 * Update global flags (save/m) of groups according to the state
 * of their resources
 * @param groups list of groups to check and possibly update
 */
global define boolean UpdateOtherGroups (list groups) ``{

    boolean group_save = false;
    foreach (string gname, (list<string>) groups, ``{
	group_save = false;
	// - check all the resources (some are possibly changed) of group
	// - switch_info is updated, modified_groups not yet
	foreach (any i, map resource, modified_groups[gname]:$[], ``{
	    if (!is(i, integer))
		return;
	    integer pos = (integer) i;
	    group_save = resource["save"]:default_save || group_save;
	    foreach (string key, ["save", "save_mode"], ``{
		// some resource flag was changed
		if (switch_info["modified_resources", pos, key]:nil !=
		    resource[key]:nil)
		{
		    // update modified_group and switch_info
		    modified_groups [gname, pos, key] =
			switch_info["modified_resources", pos,key]:nil;
		    // set modified flag of the group
		    modified_groups [gname, "modified"] = true;
		}
	    });
	});
	modified_groups[gname, "save"] = group_save;
    });
    return true;
}


/**
 * Check the modified resources belonging to specified group
 * If user changed anything (in DetailsSwitchDialog), global maps
 * modified_groups and switch_info are updated.
 *
 * Some of modified resource could be also contained in some other group
 * and all such groups have to be checked also (to update modified_groups).
 *
 * It could be the situation when resource should not be saved and it is
 * set in first group. But it is also the only resource of some other group
 * and so such group should be also marked not to save.
 * @param name group name
 * @param resources map of that group's modified resources (altered by user)
 */
global define boolean CheckAndUpdateResources (string name, map resources) ``{

    list groups_to_check = [];
    boolean group_save = false;
    // go through all resources of current group
    foreach (any i, any res, resources, ``{
	if (!is (i,integer))
	    return;
	map resource = $[];
	if (is (res,map))
	    resource        = (map) res;
	integer pos = (integer) i;
	group_save = resource["save"]:default_save || group_save;
	foreach (string key, ["save", "save_mode"], ``{
	    // some resource flag was changed
	    if (modified_groups [name, pos, key]:nil != resource[key]:nil)
	    {
		// update modified_group and switch_info
		modified_groups [name, pos, key] = resource[key]:nil;
		switch_info["modified_resources",pos,key] = resource[key]:nil;
		// set modified flag of the group
		modified_groups [name, "modified"] = true;
		// set other groups (this resource belongs to) for later check
		foreach (map g, (list<map<string,any> >) resource["groups"]:[], ``{
		    if (g["name"]:"" != name)
		    groups_to_check = union (groups_to_check, [ g["name"]:"" ]);
		});
	    }
	});
    });
    modified_groups[name, "save"] = group_save;
    UpdateOtherGroups (groups_to_check);
    return true;
}

/**
 * Sets the "save" flag of all resources belonging to selected resource group
 * to the opposite value. Also sets "save_mode" of each resource to "normal"
 * @param groupname resource group name
 * @return the new value
 */
global define boolean SetGroupSaveFlag (string groupname) ``{

    list groups_to_check = [];
    boolean ret = !modified_groups[groupname, "save"]:false;
    foreach (any index, map resource, modified_groups[groupname]:$[], ``{
	if (is (index, integer))
	{
	    integer pos = (integer) index;
	    resource["save"] = ret;
	    resource["save_mode"] = "normal";
	    switch_info["modified_resources", pos, "save"] = ret;
	    switch_info["modified_resources", pos, "save_mode"] = "normal";
	    // set other groups (this resource belongs to) for later check
	    foreach (map g, resource["groups"]:[], ``{
		if (g["name"]:"" != groupname)
		    groups_to_check = union (groups_to_check, [ g["name"]:"" ]);
	    });
	}
    });
    modified_groups [groupname, "save"] = ret;
    // unset "m" flag now
    modified_groups [groupname, "modified"] = false;
    UpdateOtherGroups (groups_to_check);
    return ret;
}

/**
 * Creates the item list for the Table widget (to use in Switch dialog).
 * @return list of items
 */
global define list GetGroupsAsItems() ``{

    list items = [];
    foreach (string name, map group, (map<string,map>) modified_groups, ``{
	string save = group ["save"]:default_save ? "X":"";
	if (group["modified"]:false)
	    save = "m";
	items = add (items,
	    `item (`id(name), save, name, group["description"]:""));
    });

    return items;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------

/**
 * Initialize the SCPM agent.
 * @return true on success
 */
define boolean Initialize() ``{

    // popup label, %1 is required application
    if (!Package::InstallAllMsg (["scpm"], _("<p>To use the profile manager, the package <b>%1</b> is required.<br>
Install it now?</p>
")))
         return false;

    tmpdir		= (string) SCR::Read(.target.tmpdir);
    progresspath	= tmpdir + progressfile;
    hashpath		= tmpdir + hashfile;
    changespath		= tmpdir + changesfile;
    tmppath		= tmpdir + tmpfile;
    y2debug("progresspath=%1, hashpath=%2", progresspath, hashpath);

    boolean ret		= (boolean) SCR::Execute(.scpm, tmpdir);
    if (ret == nil) ret = false;
    if (ret == false)
        SetError();

    return ret;
}

/**
 * Checks if SCPM is enabled/disabled/initialized.
 * @return true on success
 */
define boolean ReadSCPMStatus() ``{

    // get the scpm_status_t struct as a map
    map status = (map) SCR::Read (.scpm.status);
    if (status == nil)
    {
        SetError();
        return false;
    }

    enabled		= status["enabled"]:false;
    initialized		= status["initialized"]:false;
    needs_reinit	= initialized && status["needs_reinit"]:false;
    needs_recover	= status["needs_recover"]:false;
    return true;
}

/**
 * Calls SCPM::Recover (false) function, which replays journal after the crash
 * (started in separate agen'ts thread, return value must be checked with Wait)
 */
global define void Recover () {

    SCR::Execute (.scpm.recover, $[]);
}

/**
 * Calls SCPM::Recover (true)
 */
global define void Rollback () {

    SCR::Execute (.scpm.rollback, $[]);
}

/**
 * Reads the list of aviable resource sets.
 * @return true on success
 */
global define boolean ReadResourceSets() ``{

    list sets = (list) SCR::Read(.scpm.resources);

    if (sets == nil)
    {
        ShowError();
        return false;
    }

    resource_sets_predefined = sets[0]:[];
    resource_sets_individual = sets[1]:[];
    return true;
}



/**
 * Reads the list of aviable profiles.
 * @return true on success
 */
global define boolean ReadProfiles() ``{

    list profiles_l = (list) SCR::Read(.scpm.profiles);

    if (profiles_l == nil)
    {
        SetError();
        profiles_l = [];
    }
    foreach (string profile, (list<string>) profiles_l, ``{
        profiles = add (profiles, profile, "");
    });

    if (profiles_l == [])
        return false;
    return true;

}

/**
 * Reads the active profile.
 * @return true on success
 */
global define boolean ReadActiveProfile() ``{

    active_profile = (string) SCR::Read(.scpm.profiles.current);

    if (active_profile == nil)
    {
        SetError();
        active_profile = "";
        return false;
    }
    return true;
}

/**
 * Reads the descriptions of all aviable profiles.
 * @return true on success
 */
global define boolean ReadProfilesDescriptions() ``{

    // read the descriptions of each profile
    map profilesm = profiles;

    foreach (string profile, string d, (map<string,string>) profilesm, ``{
        string desc = (string) SCR::Read(.scpm.profiles.description, profile);
        if (desc == nil)
        {
            desc = "";
            SetError();
        }
        profiles = add (profiles, profile, desc);
    });
    return true;
}

/**
 * Check if the module is run for first time and
 * reads user settings (currently close_popups check)
 * @return true on success
 */
define boolean ReadUserSettings() ``{

    string file	= Directory::vardir + "/scpm.ycp";
    if (SCR::Read (.target.size, file) == -1)
    {
	SCR::Execute (.target.bash, sformat ("/bin/touch %1", file));
    }
    else
    {
	state = (map) SCR::Read (.target.ycp, "/var/lib/YaST2/scpm.ycp");
    }
    if (state == nil || state == $[])
	first_time = true;
    else
	close_popups = state["close_popups"]:false;
    return true;
}

/**
 * Ask user which action should be taken when scom recovery is necessary
 * @return symbol: `cancel, `recover, `rollback
 */
define symbol AskForRecoveryPopup () {

    symbol ret	= nil;

    string text	=
    // popup text
_("SCPM is currently locked and needs recovery. This may be
the result of an aborted profile switch or similar. You
can have SCPM <b>recover</b> the last command, which means your
database will be updated and outstanding actions will be
performed. You can also have SCPM <b>rollback</b> the command, which means
the old system status will be restored (if possible).
");

    UI::OpenDialog(`opt(`decorated), `HBox (`HSpacing(1.5),
        `VSpacing (14),
        `VBox (
            `HSpacing (50),
            `VSpacing (1),
            `RichText (text),
            `VSpacing (0.5),
	    `HBox(
		// Push button label
		`PushButton(`id(`recover),`opt(`default),_("&Recover")),
		`HSpacing(1),
                // Push button label
	        `PushButton(`id(`rollback), _("Ro&llback")),
		`HSpacing(1),
		`PushButton (`id(`cancel), Label::CancelButton ())
	    ),
            `VSpacing (0.5)
	),
        `HSpacing(1.5)
    ));

    ret = (symbol) UI::UserInput();
    UI::CloseDialog();
    return ret;
}


/**
 * Read all profile-manager settings
 * @return true on success
 */
global define boolean Read() ``{

    // profile-manager Read dialog caption:
    string caption = _("Initializing profile manager configuration");

    integer steps = 7;

    Progress::New( caption, " ", steps, [
	    // progress stage
	    _("Initialize SCPM"),
	    // progress stage
	    _("Read the SCPM state"),
	    // progress stage
	    _("Read the list of profiles"),
	    // progress stage
	    _("Read active profile"),
	    // progress stage
	    _("Read the profiles descriptions"),
	    // progress stage
	    _("Read user settings"),
	], [
	    // progress stage
	    _("Initializing SCPM..."),
	    // progress stage
	    _("Reading the SCPM state..."),
	    // progress stage
	    _("Reading the profiles..."),
	    // progress stage
	    _("Reading active profile..."),
	    // progress stage
	    _("Reading the profiles descriptions..."),
	    // progress stage
	    _("Reading user settings..."),
	    // progress stage
	    _("Finished")
	],
	""
    );
    // initialize
    if(Abort()) return false;
    Progress::NextStage();
    if(!Initialize())
    {
	// Error message: %1 is special error message, should be translated
	Report::Error(sformat(_("Cannot initialize SCPM:\n%1"), scpm_error));
        return false;
    }

    // read status
    if(Abort()) return false;
    Progress::NextStage();
    if(!ReadSCPMStatus())
    {
	// Error message: %1 is special error message, should be translated
	Report::Error(sformat(_("Cannot read SCPM status:\n%1"), scpm_error));


        return false;
    }
    if (needs_reinit) // FIXME: data will be lost! -> better solution!
    {
	// yes/no popup
	if (Popup::YesNo (_("SCPM is currently locked because your system
installation has changed.
This is usually caused by a system update. Therefore, the data saved in
your profiles may be inconsistent, making it necessary to reininitialize
SCPM. This also means the saved profiles will be lost and need to be
set up up again from scratch.
Perform the reinitialization now?
")))
	{
	    // force enabling
	    SCR::Write(.scpm.status.enabled.force, true);
	}
    }

    if (needs_recover)
    {
	symbol recover_ret	= AskForRecoveryPopup ();
	if (recover_ret == `recover)
	    Recover ();
	else if (recover_ret == `rollback)
	    Rollback ();
	else return false;
	ProgressPopup ("", 0, 100);
	if (!Wait())
	{
	    ShowError ();
	    return false;
	}
    }

    // read profiles
    if(Abort()) return false;
    Progress::NextStage();

    if (initialized && !ReadProfiles())
	// Error message: %1 is special error message, should be translated
	Report::Error(sformat(_("Cannot read profiles:\n%1"), scpm_error));

    // read active profile
    if(Abort()) return false;
    Progress::NextStage();

    if (initialized && !ReadActiveProfile())
	// Error message: %1 is special error message, should be translated
	Report::Error(sformat(_("Cannot read active profile:\n%1"),scpm_error));

    // read profile descriptions
    if(Abort()) return false;
    Progress::NextStep();

    if (initialized && !ReadProfilesDescriptions())
	// Error message: %1 is special error message, should be translated
	Report::Error(sformat(_("Cannot read profile descriptions:\n%1"),
            scpm_error));

    // read user settings
    if(Abort()) return false;
    Progress::NextStage();
    if(!ReadUserSettings())
	// Error message:
	Report::Error(_("Cannot read user settings!"));

    ReadConfigFile();// TODO progress?

    if(Abort()) return false;

    Progress::NextStage();
    // progress stage
    Progress::Title (_("Finished"));

    if(Abort()) return false;
    return true;
}

/**
 * Writes the user settings (currently close_popups check).
 * @return true on success
 */
global define boolean WriteUserSettings() ``{

    state = $[];
    state = add (state, "first_time", false);
    state = add (state, "close_popups", close_popups);
    boolean ret = SCR::Write(.target.ycp, "/var/lib/YaST2/scpm.ycp", state);

	return ret;
}

/**
 * Write all profile-manager settings
 * @return true on success
 */
global define boolean Write() ``{

    // profile-manager Write dialog caption:
    string caption = _("Writing profile manager configuration");

    list<string> stagelist = [
	// progress stage:
	_("Write the SCPM database")
    ];
    list steplist = [
        // progress step:
	_("Writing SCPM database...")
    ];

    if (!canceled)
    {
	stagelist = (list<string>) merge (stagelist, [
	    // progress stage:
	    _("Write SCPM settings"),
	    // progress stage:
	    _("Write user settings") ]);

	steplist = merge (steplist, [
	    // progress step:
	    _("Writing SCPM settings..."),
	    // progress step:
	    _("Writing the user settings...") ]);
    }

    integer steps = size (steplist);
    // progress step:
    steplist = add (steplist, _("Finished"));

    Progress::New (caption, " ", steps, stagelist, steplist, "");

    Progress::NextStage();
    if(!SCR::Write(.scpm, nil))
    {
        SetError();
	// Error message:
        Report::Error(sformat(_("Cannot write SCPM database:\n%1"),scpm_error));
    }

    if (!canceled) // only for proper finish
    {
    Progress::NextStage();
    if(!WriteConfigFile())
	// Error message:
        Report::Error (_("Cannot write SCPM settings."));

    Progress::NextStage();
    if(!WriteUserSettings())
	// Error message:
        Report::Error (_("Cannot write user settings!"));
    }

    Progress::NextStage();
    // progress stage
    Progress::Title (_("Finished"));

    return true;
}

/**
 * Reads the settings of the selected profile
 * (we already have description from ReadProfileDescriptions()).
 * @return true on success
 */
global define boolean GetProfile() ``{

    // the settings of not yet created profile
    if (profile_action == "new") {
        profile_settings = $[];
        return true;
    }

    profile_settings ["name"] = selected_profile;
    profile_settings ["description"] = profiles[selected_profile]:"";

    foreach (string setting,["prestart", "poststart", "prestop", "poststop"],``{

        string val = (string)
	    SCR::Read(add(.scpm.profiles, setting), selected_profile);
        if (val == nil)
        {
            ShowError();
            val = "";
        }
        profile_settings = add (profile_settings, setting, val);
    });

    return true;
}

/**
 * Creates a new profile.
 * @param name Name of the new profile
 * @return true
 */
global define boolean AddProfile(string name) ``{

    SCR::Execute(.scpm.profiles.add, name, auto);
    return true;
}

/**
 * Creates a new profile by copying old one.
 * @param source Name of the source profile
 * @param new Name of the new profile
 * @return true
 */
global define boolean CopyProfile(string source, string new) ``{

    SCR::Execute(.scpm.profiles.copy, source, new);
    return true;
}

/**
 * Renames a profile.
 * @param old Name of the affected profile
 * @param new New name of the profile
 * @return true on success
 */
global define boolean RenameProfile(string old, string new) ``{

    boolean ret = (boolean) SCR::Execute(.scpm.profiles.rename, old, new);
    if (ret)
    {
        string description = profiles[old]:"";
        profiles = filter (string name, string desc, (map<string,string>)
	    profiles, ``(name != old));
        profiles = add (profiles, new, description);
        if (active_profile == old)
            active_profile = new;
    }
    else
    {
        SetError();
    }
    return ret;
}


/**
 * Sets the parameters of the profile.
 * Uses the global variable profile_settings.
 * @param name Name of the affected profile
 * @param settings The map of profile settings
 * @return true on success
 */
global define boolean SetProfile(string name, map settings) ``{

    string desc = settings ["description"]:"";

    boolean ret = SCR::Write(.scpm.profiles.description, desc, name);

    ret = ret &&
	SCR::Write(.scpm.profiles.prestart, settings ["prestart"]:"", name) &&
	SCR::Write(.scpm.profiles.poststart, settings ["poststart"]:"", name) &&
	SCR::Write(.scpm.profiles.prestop, settings ["prestop"]:"", name) &&
	SCR::Write(.scpm.profiles.poststop, settings ["poststop"]:"", name);

    if (ret)
        profiles = add (profiles, name, desc);
    else
        SetError();

    return ret;
}


/**
 * Deletes the profile.
 * @param to_delete Name of the affected profile
 * @return true on success
 */
global define boolean DeleteProfile(string to_delete) ``{

    boolean ret = (boolean) SCR::Execute(.scpm.profiles.delete, to_delete);
    if (ret)
        profiles = filter (string name, any d, (map<string,any>) profiles,``(name != to_delete));
    else
        SetError();
    return ret;
}

/**
 * Calls PrepareSwitch(), which drops switch_info map to file
 * @param profile Name of profile to switch to
 */
global define void PrepareSwitch (string profile) ``{

    SCR::Execute (.scpm.switch.prepare, profile);
}

/**
 * Saves modified resources of the profile
 */
global define void Save () ``{

    y2debug ("Saving...");
    SCR::Execute(.scpm.save, switch_info);
}

/**
 * Switchs to the selected profile.
 */
global define void Switch () ``{

    y2milestone ("Switching from profile \"%1\" to profile \"%2\"...",
        active_profile, selected_profile);
    SCR::Execute (.scpm.switch, switch_info);
}

/**
 * Enables or disables SCPM
 * (depends on value of enabled variable).
 * @return true on success
 */
global define boolean WriteSCPMStatus() ``{

    boolean ret = SCR::Write(.scpm.status.enabled, enabled);
    if (!ret)
        SetError();
    return ret;

}

/**
 * Enables SCPM for the first time
 */
global define void WriteStatusFirst() ``{
    SCR::Execute(.scpm.enable.first);
}

/**
 * Rebuilds SCPM database after changing resources.
 * @return true on success
 */
global define boolean RebuildDB() ``{

    if (!initialized)
	return true; // no need for rebuild

    boolean ret = (boolean) SCR::Execute(.scpm.resources.rebuild);
    if (!ret)
        ShowError();
    return ret;

}

/**
 * Checks, what changes will be done to the selected resource after switch.
 * Uses global variable active_resource.
 * @return string The changes.
 */
global define string ShowChanges() ``{

    string changes = "";
    list resources	= switch_info["modified_resources"]:[];
    map resource	= resources[active_resource]:$[];

    string rt		= resource["resource_type"]:"";
    active_resource_name = resource["resource_name"]:"";

    if (!(boolean)SCR::Execute(.scpm.profiles.changes, rt,active_resource_name))
        ShowError();
    else
        changes = (string) SCR::Read(.target.string, changespath);

    if (changes == "" || changes == nil)
        changes = "";
    else
    {
        list lines = splitstring (changes, "\n");
        changes = mergestring ((list<string>)lines, "<br>");
    }
    return changes;
}

/**
 * Sets the "save" flag of selected resource to the opposite value.
 * @return true on success
 */
global define boolean SetSaveFlag() ``{

    boolean s = switch_info["modified_resources",active_resource,"save"]:false;
    switch_info["modified_resources",active_resource,"save"] = !s;

    return true;
}

/**
 * Creates the item list for the Table widget.
 * @return list of items
 */
global define list GetProfilesAsItems() ``{

    list items = [];
    items = maplist (string name, string descr, (map<string,string>) profiles, ``{
	// take 1st line from description
	descr = substring (descr, 0, find (descr, "\n"));
        if (name == active_profile)
            return `item(`id(name),"---->",name,descr);
        else
            return `item(`id(name),"",name,descr);
    });
    return items;
}

/**
 * Checks if the name of the new profile consists of
 * valid characters: [a-zA-Z0-9_-.]
 * @param name Name of the new profile.
 * @return true if valid
 */
global define boolean CheckNameValidity(string name) ``{

    if (size(name) < 1)
        return false;
    string additional	= "-_.@#"; // additional characters allowed
    if (name != filterchars (name, String::CAlnum () + additional))
    {
	y2milestone ("invalid characters in profile name");
        return false;
    }
    if (issubstring (additional, substring (name, 0, 1)))
    {
	y2milestone ("profile name must start with letter or digit");
        return false;
    }
    return true;
}

/**
 * Checks the existence of the scripts from
 * @param settings The map with paths to scripts
 * @return list of non-executable files (empty when all is OK)
 */
global define list CheckFilesValidity(map settings) ``{

    list not_executable = [];
    foreach (string type, ["prestart", "poststart", "prestop", "poststop" ], ``{

	string file = settings[type]:"";
	if (file != "" && (SCR::Execute (.target.bash,
	    sformat("/usr/bin/test -x %1 -a ! -d %1",file))) != 0)
	    not_executable = union (not_executable, [ file ]);
    });
    return not_executable;
}

/**
 * Sets the file permitions of scripts from profile_settings to 700.
 * @param settings The map with paths to scripts
 * @return true on success
 */
global define boolean SetFilePermitions (map settings) ``{

    boolean ret = true;
    foreach (string type, [ "prestart", "poststart", "prestop", "poststop" ],``{
	string file = settings[type]:"";
	if (file != "" && (SCR::Execute (.target.bash,
	    sformat("/bin/chmod 700 %1", file))) != 0)
	    ret = false;
    });
    return ret;
}


/**
 * Checks if the profile settings was modified.
 * @param new_settings The map of new profile settings.
 * @return true if modified
 */
global define boolean ModifiedSettings(map new_settings) ``{

    boolean ret = false;

    foreach (string type, any value, (map<string,any>)profile_settings, ``{
        if (type != "name")
            if (value != new_settings[type]:"") ret = true;
    });

    return ret;
}

/**
 * Gets the hash marks (to show in ProgressPopup) from the hashfile
 * @return number of characters in hashfile
 */
global define integer GetHashMarks() ``{

    string all = (string) SCR::Read(.target.string, hashpath);
    if (all == nil || all == "") return 0;
    return (size(all) - (100 * hash_size));
}

/**
 * Reads the file with the progress informations.
 * @param everything if set to true, returns the entire rest of file
 * @return string the text to show in ProgressPopup (one line in normal case)
 */
global define string GetProgressText(boolean everything) ``{

    string ret = "";
    string all = (string) SCR::Read(.target.string, progresspath);

    // check for non-existent file
    if (all == nil) return ret;

    list all_l		= splitstring (all, "\n");
    string current	= all_l[progress_size]:"";
    string last		= all_l[progress_size - 1]:"";

    if (!everything) // return only one line
    {
        // last line has been updated:
        if (last_ret != "" && last_ret != last)
        {
            ret = substring (last, size(last_ret), size(last)-size(last_ret));
            last_ret = all_l[progress_size - 1]:"";
        }
        // continue with the new line
        else if (progress_size < size(all_l) && current !="" && current !="\n")
        {
            ret = current;
            if (last_ret != "") {
                last_ret = ret;
                ret = "\n" + ret;
            }
            else last_ret = ret;
            progress_size = progress_size + 1;
        }
    }
    else // return everything to the end of file
    {
        if (last_ret != "" && last_ret != last)
        {
            ret = substring (last, size(last_ret), size(last)-size(last_ret));
        }
        while (progress_size < size(all_l) &&  current != "" && current != "\n")
        {
            ret = "\n" + current;
            progress_size = progress_size + 1;
            current = all_l[progress_size]:"";
        }
    }
    return ret;
}

/**
 * Reads the switch info map,
 * which was dumped to file by agent's thread
 * @return boolean true on success
 */
global define boolean ReadSwitchInfo() ``{

    switch_info = $[];
    switch_info = (map) SCR::Read(.target.ycp, tmppath);
    if (switch_info == nil)
    {
        switch_info = $[];
        return false;
    }
    y2debug("switch_info: %1",switch_info);
    // compute helper structure:
    GetModifiedGroups();
    return true;
}

/**
 * Checks the return value of agent's thread
 * @return boolean true if thread finished succesfully
 */
global define boolean Wait() ``{
    boolean ret = (boolean) SCR::Read(.scpm.exit_status );
    if (!ret)
        SetError();
    return ret;
}

/**
 * Popup for showing progress informations
 * @param position of this progress:
 *  (if "first", popup is not closed, for "last" popup is not created)
 * @param start start of progress (mainly 0)
 * @param end end of progress (mainly 100)
 * @return boolean true
 */
global define boolean ProgressPopup (string position, integer start, integer end) {

    if (position != "last")
    {
	UI::OpenDialog(`opt(`decorated), `HBox(`HSpacing(1.5), `VBox(
            `VSpacing(1),
			// LogView label:
            `LogView(`id(`progress),_("Progress information"), 10, 0),
            `VSpacing(1),
            `ProgressBar(`id(`pb), "", 100, 0),
            `VSpacing(1),
	    // checkbox label: ('SCPM Options' is another dialog name
            `CheckBox(`id(`close_ch), `opt(`notify), _("Close Progress Pop-ups &Automatically (reenable it in SCPM Options)"),close_popups),
            `VSpacing(1),
	        `HBox(
	            `Right(`PushButton(`id(`close), `opt(`default, `key_F10),
                    Label::CloseButton())),
	            `HSpacing(1)
	            ),
            `VSpacing(1)),
	`HSpacing(1.5)));

	UI::ChangeWidget(`id(`close), `Enabled, false);
    }

    string progresstext = "";
    integer hashmarks = start;
    last_ret = "";

    UI::BusyCursor();
    do
    {
	// TODO check the process status here!!!
        sleep(5);
        hashmarks = GetHashMarks();
        UI::ChangeWidget(`id(`pb), `Value, hashmarks);

        progresstext = GetProgressText(false);
        if (progresstext != "") {
	    string recoded = (string)
		UI::Recode(WFM::GetEnvironmentEncoding(),"UTF-8",progresstext);
            UI::ChangeWidget(`id(`progress), `LastLine, recoded);
        }
    }
    while (hashmarks < end);

    // next hundred of hash marks
    if (hashmarks >= 100)
        hash_size = hash_size + 1;

    // read to the end of file
    progresstext = GetProgressText(true);
    if (progresstext != "") {
	string recoded = (string)
	    UI::Recode (WFM::GetEnvironmentEncoding(),"UTF-8",progresstext);
        UI::ChangeWidget(`id(`progress), `LastLine, recoded);
    }
    if (position == "first") // hack: missing newline
        UI::ChangeWidget(`id(`progress), `LastLine, "\n");

    UI::NormalCursor();
    if (position != "first")
    {
        if (!close_popups)
        {
            UI::ChangeWidget(`id(`close), `Enabled, true);
            any ret = nil;
            while (ret != `close)
            {
                ret = UI::UserInput();
            }
        }
        close_popups = (boolean) UI::QueryWidget(`id(`close_ch), `Value);
        UI::CloseDialog();
    }
    return true;
}
/* EOF */
}
