diff --git a/VERSION b/VERSION index 9ffb7f4..1f085b8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.17.13 +2.17.14 diff --git a/agent-snapper/src/SnapperAgent.cc b/agent-snapper/src/SnapperAgent.cc index 1fc4140..152e189 100644 --- a/agent-snapper/src/SnapperAgent.cc +++ b/agent-snapper/src/SnapperAgent.cc @@ -59,6 +59,40 @@ YCPList SnapperAgent::getListValue (const YCPMap &map, const YCPString &key) return YCPList(); } +/** + * Search the map for value of given key; + * key is string and value is YCPMap + */ +YCPMap SnapperAgent::getMapValue (const YCPMap &map, const YCPString &key) +{ + YCPValue val = map->value(key); + if (!val.isNull() && val->isMap()) + return val->asMap(); + else + return YCPMap(); +} + +YCPMap map2ycpmap (const map& userdata) +{ + YCPMap m; + for (map::const_iterator it = userdata.begin(); it != userdata.end(); ++it) + { + m->add (YCPString (it->first), YCPString (it->second)); + } + return m; +} + +map ycpmap2stringmap (const YCPMap &ycp_map) +{ + map m; + + for (YCPMapIterator i = ycp_map->begin(); i != ycp_map->end(); i++) { + string key = i.key()->asString()->value(); + m[key] = i.value()->asString()->value(); + } + return m; +} + /** * Constructor @@ -68,6 +102,7 @@ SnapperAgent::SnapperAgent() : SCRAgent() sh = NULL; snapper_initialized = false; snapper_error = ""; + } /** @@ -77,7 +112,8 @@ SnapperAgent::~SnapperAgent() { if (sh) { - deleteSnapper(sh); + delete sh; + sh = 0; } } @@ -186,17 +222,17 @@ YCPValue SnapperAgent::Read(const YCPPath &path, const YCPValue& arg, const YCPV s->add (YCPString ("num"), YCPInteger (it->getNum())); s->add (YCPString ("date"), YCPInteger (it->getDate())); + s->add (YCPString ("description"), YCPString (it->getDescription())); - if (it->getType() == SINGLE || it->getType() == PRE) + if (it->getType() == PRE) { - s->add (YCPString ("description"), YCPString (it->getDescription())); - if (it->getType() == PRE) - s->add (YCPString ("post_num"), YCPInteger (snapshots.findPost (it)->getNum ())); + s->add (YCPString ("post_num"), YCPInteger (snapshots.findPost (it)->getNum ())); } else if (it->getType() == POST) { s->add (YCPString ("pre_num"), YCPInteger (it->getPreNum())); } + s->add (YCPString ("userdata"), YCPMap (map2ycpmap (it->getUserdata()))); y2debug ("snapshot %s", s.toString().c_str()); retlist->add (s); @@ -322,11 +358,13 @@ YCPValue SnapperAgent::Execute(const YCPPath &path, const YCPValue& arg, if (sh) { y2milestone ("deleting existing snapper object"); - deleteSnapper(sh); + delete sh; + sh = 0; } string config_name = getValue (argmap, YCPString ("config"), "root"); - try { - sh = createSnapper (config_name); + try + { + sh = new Snapper(config_name); } catch (const ConfigNotFoundException& e) { @@ -354,10 +392,87 @@ YCPValue SnapperAgent::Execute(const YCPPath &path, const YCPValue& arg, if (path->length() == 1) { + if (PC(0) == "create") { + + string description = getValue (argmap, YCPString ("description"), ""); + string cleanup = getValue (argmap, YCPString ("cleanup"), ""); + string type = getValue (argmap, YCPString ("type"), "single"); + YCPMap userdata = getMapValue (argmap, YCPString ("userdata")); + + const Snapshots& snapshots = sh->getSnapshots(); + Snapshots::iterator snap; + + if (type == "single") { + snap = sh->createSingleSnapshot(description); + } + else if (type == "pre") { + snap = sh->createPreSnapshot(description); + } + else if (type == "post") { + // check if pre was given! + int pre = getIntValue (argmap, YCPString ("pre"), -1); + if (pre == -1) + { + snapper_error = "pre_not_given"; + return YCPBoolean (false); + } + else + { + Snapshots::const_iterator snap1 = snapshots.find (pre); + if (snap1 == snapshots.end()) + { + snapper_error = "pre_not_found"; + return YCPBoolean (false); + } + else + { + snap = sh->createPostSnapshot(description, snap1); + } + } + } + else { + snapper_error = "wrong_snapshot_type"; + return YCPBoolean (false); + } + + snap->setCleanup (cleanup); + snap->setUserdata (ycpmap2stringmap (userdata)); + snap->flushInfo(); + return ret; + } + else if (PC(0) == "modify") { + + int num = getIntValue (argmap, YCPString ("num"), 0); + + Snapshots& snapshots = sh->getSnapshots(); + Snapshots::iterator snap = snapshots.find(num); + if (snap == snapshots.end()) + { + y2error ("snapshot '%d' not found", num); + snapper_error = "snapshot_not_found"; + return YCPBoolean (false); + } + + if (!argmap->value(YCPString ("description")).isNull()) +// if (argmap->hasKey(YCPString ("description"))) + { + snap->setDescription (getValue (argmap, YCPString ("description"), "")); + } + if (!argmap->value(YCPString ("cleanup")).isNull()) + { + snap->setCleanup (getValue (argmap, YCPString ("cleanup"), "")); + } + if (!argmap->value(YCPString ("userdata")).isNull()) + { + snap->setUserdata (ycpmap2stringmap (getMapValue (argmap, YCPString ("userdata")))); + } + snap->flushInfo(); + return ret; + } /** * Rollback the list of given files from snapshot num1 to num2 (system by default) */ - if (PC(0) == "rollback") { + else if (PC(0) == "rollback") { unsigned int num1 = getIntValue (argmap, YCPString ("from"), 0); unsigned int num2 = getIntValue (argmap, YCPString ("to"), 0); diff --git a/agent-snapper/src/SnapperAgent.h b/agent-snapper/src/SnapperAgent.h index 39fdff3..a39566a 100644 --- a/agent-snapper/src/SnapperAgent.h +++ b/agent-snapper/src/SnapperAgent.h @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -53,6 +52,12 @@ class SnapperAgent : public SCRAgent */ YCPList getListValue (const YCPMap &map, const YCPString &key); + /** + * Search the map for value of given key; + * key is string and value is YCPMap + */ + YCPMap getMapValue (const YCPMap &map, const YCPString &key); + public: /** * Default constructor. diff --git a/package/_cvsignore b/package/_cvsignore deleted file mode 100644 index 65d0b1a..0000000 --- a/package/_cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -*.spec -*.bz2 diff --git a/package/yast2-snapper.changes b/package/yast2-snapper.changes index c0d65c3..bb28132 100644 --- a/package/yast2-snapper.changes +++ b/package/yast2-snapper.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Wed Sep 5 12:50:24 CEST 2012 - jsuchome@suse.cz + +- added support for creating and modifying snapshots (fate#313041) +- show and enable editing of userdata +- 2.17.14 + ------------------------------------------------------------------- Mon Jan 16 10:45:11 CET 2012 - jsuchome@suse.cz diff --git a/src/Snapper.ycp b/src/Snapper.ycp index 5acadba..61e497b 100644 --- a/src/Snapper.ycp +++ b/src/Snapper.ycp @@ -245,6 +245,61 @@ global boolean InitializeSnapper (string config) { } +/** + * Modify existing snapshot + * Return true on success + */ +global boolean ModifySnapshot (map args) { + + boolean success = (boolean) SCR::Execute (.snapper.modify, args); + if (!success) + { + map err_map = LastSnapperErrorMap (); + string type = err_map["type"]:""; + string details = _("Reason not known."); + + y2warning ("modification failed with '%1'", err_map); + // error popup + Report::Error (sformat (_("Failed to modify snapshot: +%1"), details)); + } + return success; +} + +/** + * Create new snapshot + * Return true on success + */ +global boolean CreateSnapshot (map args) { + + boolean success = (boolean) SCR::Execute (.snapper.create, args); + if (!success) + { + map err_map = LastSnapperErrorMap (); + string type = err_map["type"]:""; + string details = _("Reason not known."); + + if (type == "wrong_snapshot_type") + { + details = _("Wrong snapshot type given."); + } + else if (type == "pre_not_given") + { + details = _("'Pre' snapshot was not given."); + } + else if (type == "pre_not_found") + { + details = _("Given 'Pre' snapshot was not found."); + } + + y2warning ("creating failed with '%1'", err_map); + // error popup + Report::Error (sformat (_("Failed to create new snapshot: +%1"), details)); + } + return success; +} + /** * Read all snapper settings * @return true on success diff --git a/src/dialogs.ycp b/src/dialogs.ycp index d3cc6f4..0305898 100644 --- a/src/dialogs.ycp +++ b/src/dialogs.ycp @@ -59,6 +59,213 @@ symbol ReadDialog() { return ret ? `next : `abort; } +/** + * convert map of userdata to string + * $[ "a" : "b", "1" : "2" ] -> "a=b,1=2" + */ +string userdata2string (map userdata) { + + return mergestring ( + maplist (string key, string val, userdata, { + return sformat ("%1=%2", key, val); + }), + "," + ); +} + +// transform userdata from widget to map +map get_userdata (symbol id) { + + map u = $[]; + string user_s = (string) UI::QueryWidget (`id (id), `Value); + foreach (string line, splitstring (user_s, ","), { + list split = splitstring (line, "="); + if (size (split) > 1) + { + u[split[0]:""] = split[1]:""; + } + }); + return u; +} + + +/** + * Popup for modification of existing snapshot + * @return true if new snapshot was created + */ +boolean ModifySnapshotPopup (map snapshot) { + + boolean modified = false; + integer num = snapshot["num"]:0; + integer previous_num = snapshot["pre_num"]:num; + symbol type = snapshot["type"]:`none; + + integer pre_index = Snapper::id2index[previous_num]:0; + map pre_snapshot = Snapper::snapshots[pre_index]:$[]; + + term cont = `VBox ( + // popup label, %1 is number + `Label (sformat (_("Modify Snapshot %1"), num)), + // text entry label + `TextEntry (`id (`description), _("Description"), + snapshot["description"]:""), + // text entry label + `TextEntry (`id (`userdata), _("User data"), + userdata2string (snapshot["userdata"]:$[])) + ); + + if (type != `SINGLE) + { + cont = `VBox ( + // popup label, %1, %2 are numbers (range) + `Label (sformat (_("Modify Snapshots %1 - %2"), previous_num, num)), + // label + `Left (`Label (sformat (_("Pre (%1)"), previous_num))), + `HBox ( + `HSpacing (2), + // text entry label + `TextEntry (`id (`pre_description), _("Description"), + pre_snapshot["description"]:""), + // text entry label + `TextEntry (`id (`pre_userdata), _("User data"), + userdata2string (pre_snapshot["userdata"]:$[])) + ), + `VSpacing (), + // label + `Left (`Label (sformat (_("Post (%1)"), num))), + `HBox ( + `HSpacing (2), + // text entry label + `TextEntry (`id (`description), _("Description"), + snapshot["description"]:""), + // text entry label + `TextEntry (`id (`userdata), _("User data"), + userdata2string (snapshot["userdata"]:$[])) + ) + ); + } + + UI::OpenDialog (`opt (`decorated), `HBox (`HSpacing (1), `VBox( + `VSpacing (0.5), + `HSpacing (65), + cont, + `VSpacing (0.5), + `ButtonBox ( + `PushButton (`id(`ok), Label::OKButton()), + `PushButton (`id(`cancel), Label::CancelButton()) + ), + `VSpacing (0.5)), `HSpacing (1)) + ); + + any ret = nil; + map args = $[]; + map pre_args= $[]; + + while (true) + { + ret = UI::UserInput (); + args = $[ + "num" : num, + "description" : UI::QueryWidget (`id (`description), `Value), + "userdata" : get_userdata (`userdata) + ]; + if (type != `SINGLE) + { + pre_args = $[ + "num" : previous_num, + "description" : UI::QueryWidget (`id (`pre_description), `Value), + "userdata" : get_userdata (`pre_userdata) + ]; + } + if (ret == `ok || ret == `cancel) + { + break; + } + } + UI::CloseDialog (); + if (ret == `ok) + { + // TODO check if snapshots were really modified + modified = Snapper::ModifySnapshot (args); + if (modified && type != `SINGLE) + { + modified = Snapper::ModifySnapshot (pre_args); + } + } + + return modified; +} + +/** + * Popup for creating new snapshot + * @return true if new snapshot was created + */ +boolean CreateSnapshotPopup (list pre_snapshots) { + + boolean created = false; + list pre_items = maplist (integer s, pre_snapshots, { + return `item (`id (s), tostring (s)); + }); + + UI::OpenDialog (`opt (`decorated), `HBox (`HSpacing (1), `VBox( + `VSpacing (0.5), + `HSpacing (65), + // popup label + `Label (_("Create New Snapshot")), + // text entry label + `TextEntry (`id (`description), _("Description"), ""), + `RadioButtonGroup (`id(`rb_type), `Left (`HVSquash (`VBox ( + `Left (`RadioButton (`id ("single"), `opt (`notify), + // radio button label + _("Single snapshot"), true)), + `Left (`RadioButton (`id ("pre"), `opt (`notify), + // radio button label + _("Pre"), false)), + `VBox ( + `Left (`RadioButton (`id ("post"), `opt (`notify), + // radio button label, snapshot selection will follow + _("Post, paired with:"), false) + ), + `HBox ( + `HSpacing (2), + `Left (`ComboBox (`id (`pre_list), `opt (`notify), "", pre_items)) + ) + ) + )))), + // text entry label + `TextEntry (`id (`userdata), _("User data"), ""), + `VSpacing (0.5), + `ButtonBox ( + `PushButton (`id(`ok), Label::OKButton()), + `PushButton (`id(`cancel), Label::CancelButton()) + ), + `VSpacing (0.5)), `HSpacing (1)) + ); + + any ret = nil; + map args = $[]; + while (true) + { + ret = UI::UserInput (); + args = $[ + "type" : UI::QueryWidget (`id (`rb_type), `Value), + "description" : UI::QueryWidget (`id (`description), `Value), + "pre" : UI::QueryWidget (`id (`pre_list), `Value), + "userdata" : get_userdata (`userdata) + ]; + if (ret == `ok || ret == `cancel) + { + break; + } + } + UI::CloseDialog (); + if (ret == `ok) + { + created = Snapper::CreateSnapshot (args); + } + return created; +} + /** * Summary dialog * @return dialog result @@ -72,64 +279,100 @@ any SummaryDialog() { list configs = Snapper::configs; list snapshot_items = []; + list pre_snapshots = []; + // generate list of snapshot table items list get_snapshot_items () { integer i = -1; snapshot_items = []; + pre_snapshots = []; + foreach (map s, snapshots, { - i = i + 1; + i = i + 1; - integer num = s["num"]:0; - string date = ""; - if (num != 0) - date = timestring ("%c", s["date"]:0, false); + integer num = s["num"]:0; + string date = ""; + if (num != 0) + date = timestring ("%c", s["date"]:0, false); - if (s["type"]:`none == `SINGLE) - { - snapshot_items = add (snapshot_items, - `item (`id (i), num, _("Single"), date, "", s["description"]:"")); - } - else if (s["type"]:`none == `POST) - { - integer pre = s["pre_num"]:0; // pre canot be 0 - integer index = Snapper::id2index[pre]:-1; - if (pre == 0 || index == -1) - { - y2warning ("something wrong - pre:%1, index:%2", pre, index); - continue; - } - string desc = Snapper::snapshots[index,"description"]:""; - string pre_date = timestring ("%c", Snapper::snapshots[index,"date"]:0, false); - snapshot_items = add (snapshot_items, - `item (`id (i), sformat ("%1 - %2", pre, num), _("Pre & Post"), pre_date, date, desc)); - } - else - { - y2milestone ("skipping pre snapshot: %1", num); - } + string userdata = userdata2string (s["userdata"]:$[]); + + if (s["type"]:`none == `SINGLE) + { + snapshot_items = add (snapshot_items, + `item (`id (i), num, _("Single"), date, "", s["description"]:"", userdata)); + } + else if (s["type"]:`none == `POST) + { + integer pre = s["pre_num"]:0; // pre canot be 0 + integer index = Snapper::id2index[pre]:-1; + if (pre == 0 || index == -1) + { + y2warning ("something wrong - pre:%1, index:%2", pre, index); + continue; + } + string desc = Snapper::snapshots[index,"description"]:""; + string pre_date = timestring ("%c", Snapper::snapshots[index,"date"]:0, false); + snapshot_items = add (snapshot_items, + `item (`id (i), sformat ("%1 - %2", pre, num), _("Pre & Post"), pre_date, date, desc, userdata)); + } + else + { + integer post = s["post_num"]:0; // 0 means there's no post + if (post == 0) + { + y2milestone ("pre snappshot %1 does not have post", num); + snapshot_items = add (snapshot_items, + `item (`id (i), num, _("Pre"), date, "", s["description"]:"", userdata)); + } + else + { + y2milestone ("skipping pre snapshot: %1", num); + } + pre_snapshots = add (pre_snapshots, num); + } }); return snapshot_items; } + // update list of snapshots + void update_snapshots () { + + // busy popup message + Popup::ShowFeedback ("", _("Reading list of snapshots...")); + + Snapper::InitializeSnapper (Snapper::current_config); + Snapper::ReadSnapshots (); + snapshots = Snapper::snapshots; + Popup::ClearFeedback (); + + UI::ChangeWidget (`id (`snapshots_table), `Items, get_snapshot_items ()); + } + + term contents = `VBox ( - `HBox ( - // combo box label - `Label (_("Current Configuration")), - `ComboBox (`id (`configs), `opt (`notify), "", maplist (string config, configs, { - return `item (`id (config), config, config == Snapper::current_config); - })), - `HStretch () - ), - `Table (`id (`snapshots_table), `opt(`notify, `keepSorting), `header ( - // table header - _("ID"), _("Type"), _("Start Date"), _("End Date"), _("Description")), - get_snapshot_items () - ), - `HBox ( - `PushButton (`id (`show_c), `opt (`default), _("Show Changes")), - `HStretch () - ) + `HBox ( + // combo box label + `Label (_("Current Configuration")), + `ComboBox (`id (`configs), `opt (`notify), "", maplist (string config, configs, { + return `item (`id (config), config, config == Snapper::current_config); + })), + `HStretch () + ), + `Table (`id (`snapshots_table), `opt(`notify, `keepSorting), `header ( + // table header + _("ID"), _("Type"), _("Start Date"), _("End Date"), _("Description"), _("User Data")), + get_snapshot_items () + ), + `HBox ( + // button label + `PushButton (`id (`show_c), `opt (`default), _("Show Changes")), + `PushButton (`id (`create), Label::CreateButton ()), + // button label + `PushButton (`id (`modify), _("Modify")), + `HStretch () + ) ); Wizard::SetContentsButtons(caption, contents, HELPS["summary"]:"", @@ -147,43 +390,56 @@ any SummaryDialog() { any ret = nil; while(true) { - ret = UI::UserInput(); + ret = UI::UserInput(); - integer selected = 0; + integer selected = 0; - if (ret == `show_c || ret == `snapshots_table) { - selected = (integer) UI::QueryWidget (`id (`snapshots_table), `CurrentItem); - ret = `show; - } + if (ret == `show_c || ret == `snapshots_table) { + selected = (integer) UI::QueryWidget (`id (`snapshots_table), `CurrentItem); + ret = `show; + } - if(ret == `abort || ret == `cancel || ret == `back) { - if(ReallyAbort()) break; - else continue; - } + if(ret == `abort || ret == `cancel || ret == `back) { + if(ReallyAbort()) break; + else continue; + } else if (ret == `show) { - // `POST snapshot is selected from the couple - Snapper::selected_snapshot = snapshots[selected]:$[]; - Snapper::selected_snapshot_index = selected; + if (snapshots[selected,"type"]:nil == `PRE) + { + // popup message + Popup::Message (_("This 'Pre' snapshot is not paired with any 'Post' one yet. +Showing differences is not possible.")); + continue; + } + // `POST snapshot is selected from the couple + Snapper::selected_snapshot = snapshots[selected]:$[]; + Snapper::selected_snapshot_index = selected; break; } else if (ret == `configs) { string config = (string) UI::QueryWidget (`id (ret), `Value); if (config != Snapper::current_config) { - Snapper::current_config = config; - - // busy popup message - Popup::ShowFeedback ("", _("Reading list of snapshots...")); - - Snapper::InitializeSnapper (config); - Snapper::ReadSnapshots (); - snapshots = Snapper::snapshots; - Popup::ClearFeedback (); - - UI::ChangeWidget (`id (`snapshots_table), `Items, get_snapshot_items ()); - continue; + Snapper::current_config = config; + update_snapshots (); + continue; } } + else if (ret == `create) { + if (CreateSnapshotPopup (pre_snapshots)) + { + update_snapshots (); + continue; + } + } + else if (ret == `modify) { + selected = (integer) UI::QueryWidget (`id (`snapshots_table), `CurrentItem); + if (ModifySnapshotPopup (snapshots[selected]:$[])) + { + update_snapshots (); + continue; + } + } else if (ret == `next) { break; }