diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 6f3a6f00e..6a3f4570f 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -567,6 +567,345 @@ void TDB2::synch () context.timer_synch.stop (); } +//////////////////////////////////////////////////////////////////////////////// +void TDB2::revert () +{ +/* + Directory location (context.config.get ("data.location")); + + std::string undoFile = location._data + "/undo.data"; + std::string pendingFile = location._data + "/pending.data"; + std::string completedFile = location._data + "/completed.data"; + + // load undo.data + std::vector u; + File::read (undoFile, u); + + if (u.size () < 3) + throw std::string ("There are no recorded transactions to undo."); + + // pop last tx + u.pop_back (); // separator. + + std::string current = u.back ().substr (4); + u.pop_back (); + + std::string prior; + std::string when; + if (u.back ().substr (0, 5) == "time ") + { + when = u.back ().substr (5); + u.pop_back (); + prior = ""; + } + else + { + prior = u.back ().substr (4); + u.pop_back (); + when = u.back ().substr (5); + u.pop_back (); + } + + Date lastChange (atoi (when.c_str ())); + + // Set the colors. + Color color_red (context.color () ? context.config.get ("color.undo.before") : ""); + Color color_green (context.color () ? context.config.get ("color.undo.after") : ""); + + if (context.config.get ("undo.style") == "side") + { + std::cout << "\n" + << "The last modification was made " + << lastChange.toString () + << "\n"; + + // Attributes are all there is, so figure the different attribute names + // between before and after. + ViewText view; + view.width (context.getWidth ()); + view.intraPadding (2); + view.add (Column::factory ("string", "")); + view.add (Column::factory ("string", "Prior Values")); + view.add (Column::factory ("string", "Current Values")); + + Task after (current); + + if (prior != "") + { + Task before (prior); + + std::vector beforeAtts; + foreach (att, before) + beforeAtts.push_back (att->first); + + std::vector afterAtts; + foreach (att, after) + afterAtts.push_back (att->first); + + std::vector beforeOnly; + std::vector afterOnly; + listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); + + int row; + foreach (name, beforeOnly) + { + row = view.addRow (); + view.set (row, 0, *name); + view.set (row, 1, renderAttribute (*name, before.get (*name)), color_red); + } + + foreach (name, before) + { + std::string priorValue = before.get (name->first); + std::string currentValue = after.get (name->first); + + if (currentValue != "") + { + row = view.addRow (); + view.set (row, 0, name->first); + view.set (row, 1, renderAttribute (name->first, priorValue), + (priorValue != currentValue ? color_red : Color ())); + view.set (row, 2, renderAttribute (name->first, currentValue), + (priorValue != currentValue ? color_green : Color ())); + } + } + + foreach (name, afterOnly) + { + row = view.addRow (); + view.set (row, 0, *name); + view.set (row, 2, renderAttribute (*name, after.get (*name)), color_green); + } + } + else + { + int row; + foreach (name, after) + { + row = view.addRow (); + view.set (row, 0, name->first); + view.set (row, 2, renderAttribute (name->first, after.get (name->first)), color_green); + } + } + + std::cout << "\n" + << view.render () + << "\n"; + } + + // This style looks like this: + // --- before 2009-07-04 00:00:25.000000000 +0200 + // +++ after 2009-07-04 00:00:45.000000000 +0200 + // + // - name: old // att deleted + // + name: + // + // - name: old // att changed + // + name: new + // + // - name: + // + name: new // att added + // + else if (context.config.get ("undo.style") == "diff") + { + // Create reference tasks. + Task before; + if (prior != "") + before.parse (prior); + + Task after (current); + + // Generate table header. + ViewText view; + view.width (context.getWidth ()); + view.intraPadding (2); + view.add (Column::factory ("string", "")); + view.add (Column::factory ("string", "")); + + int row = view.addRow (); + view.set (row, 0, "--- previous state", color_red); + view.set (row, 1, "Undo will restore this state", color_red); + + row = view.addRow (); + view.set (row, 0, "+++ current state ", color_green); // Note trailing space. + view.set (row, 1, "Change made " + lastChange.toString (context.config.get ("dateformat")), color_green); + + view.addRow (); + + // Add rows to table showing diffs. + std::vector all = context.getColumns (); + + // Now factor in the annotation attributes. + Task::iterator it; + for (it = before.begin (); it != before.end (); ++it) + if (it->first.substr (0, 11) == "annotation_") + all.push_back (it->first); + + for (it = after.begin (); it != after.end (); ++it) + if (it->first.substr (0, 11) == "annotation_") + all.push_back (it->first); + + // Now render all the attributes. + std::sort (all.begin (), all.end ()); + + std::string before_att; + std::string after_att; + std::string last_att; + foreach (a, all) + { + if (*a != last_att) // Skip duplicates. + { + last_att = *a; + + before_att = before.get (*a); + after_att = after.get (*a); + + // Don't report different uuid. + // Show nothing if values are the unchanged. + if (*a == "uuid" || + before_att == after_att) + { + // Show nothing - no point displaying that which did not change. + + // row = view.addRow (); + // view.set (row, 0, *a + ":"); + // view.set (row, 1, before_att); + } + + // Attribute deleted. + else if (before_att != "" && after_att == "") + { + row = view.addRow (); + view.set (row, 0, "-" + *a + ":", color_red); + view.set (row, 1, before_att, color_red); + + row = view.addRow (); + view.set (row, 0, "+" + *a + ":", color_green); + } + + // Attribute added. + else if (before_att == "" && after_att != "") + { + row = view.addRow (); + view.set (row, 0, "-" + *a + ":", color_red); + + row = view.addRow (); + view.set (row, 0, "+" + *a + ":", color_green); + view.set (row, 1, after_att, color_green); + } + + // Attribute changed. + else + { + row = view.addRow (); + view.set (row, 0, "-" + *a + ":", color_red); + view.set (row, 1, before_att, color_red); + + row = view.addRow (); + view.set (row, 0, "+" + *a + ":", color_green); + view.set (row, 1, after_att, color_green); + } + } + } + + std::cout << "\n" + << view.render () + << "\n"; + } + + // Output displayed, now confirm. + if (context.config.getBoolean ("confirmation") && + !confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?")) + { + std::cout << "No changes made.\n"; + return; + } + + // Extract identifying uuid. + std::string uuid; + std::string::size_type uuidAtt = current.find ("uuid:\""); + if (uuidAtt != std::string::npos) + uuid = current.substr (uuidAtt, 43); // 43 = uuid:"..." + else + throw std::string ("Cannot locate UUID in task to undo."); + + // load pending.data + std::vector p; + File::read (pendingFile, p); + + // is 'current' in pending? + foreach (task, p) + { + if (task->find (uuid) != std::string::npos) + { + context.debug ("TDB::undo - task found in pending.data"); + + // Either revert if there was a prior state, or remove the task. + if (prior != "") + { + *task = prior; + std::cout << "Modified task reverted.\n"; + } + else + { + p.erase (task); + std::cout << "Task removed.\n"; + } + + // Rewrite files. + File::write (pendingFile, p); + File::write (undoFile, u); + return; + } + } + + // load completed.data + std::vector c; + File::read (completedFile, c); + + // is 'current' in completed? + foreach (task, c) + { + if (task->find (uuid) != std::string::npos) + { + context.debug ("TDB::undo - task found in completed.data"); + + // If task now belongs back in pending.data + if (prior.find ("status:\"pending\"") != std::string::npos || + prior.find ("status:\"waiting\"") != std::string::npos || + prior.find ("status:\"recurring\"") != std::string::npos) + { + c.erase (task); + p.push_back (prior); + File::write (completedFile, c); + File::write (pendingFile, p); + File::write (undoFile, u); + std::cout << "Modified task reverted.\n"; + context.debug ("TDB::undo - task belongs in pending.data"); + } + else + { + *task = prior; + File::write (completedFile, c); + File::write (undoFile, u); + std::cout << "Modified task reverted.\n"; + context.debug ("TDB::undo - task belongs in completed.data"); + } + + std::cout << "Undo complete.\n"; + return; + } + } + + // Perhaps user hand-edited the data files? + // Perhaps the task was in completed.data, which was still in file format 3? + std::cout << "Task with UUID " + << uuid.substr (6, 36) + << " not found in data.\n" + << "No undo possible.\n"; +*/ +} + //////////////////////////////////////////////////////////////////////////////// // Scans the pending tasks for any that are completed or deleted, and if so, // moves them to the completed.data file. Returns a count of tasks moved. diff --git a/src/TDB2.h b/src/TDB2.h index e63b1d21e..8cf3cb25e 100644 --- a/src/TDB2.h +++ b/src/TDB2.h @@ -97,6 +97,7 @@ public: void modify (const Task&); void commit (); void synch (); + void revert (); int gc (); int next_id (); diff --git a/src/commands/CmdUndo.cpp b/src/commands/CmdUndo.cpp index 08635615e..f7705a3c9 100644 --- a/src/commands/CmdUndo.cpp +++ b/src/commands/CmdUndo.cpp @@ -46,11 +46,12 @@ CmdUndo::CmdUndo () //////////////////////////////////////////////////////////////////////////////// int CmdUndo::execute (std::string& output) { - // TODO Detect attemps to modify the task. + // Detect attemps to modify the task. + if (context.a3.extract_modifications ().size () > 0) + throw STRING_CMD_UNDO_MODS; - context.tdb.lock (context.config.getBoolean ("locking")); - context.tdb.undo (); - context.tdb.unlock (); + context.tdb2.revert (); + context.tdb2.commit (); return 0; } diff --git a/src/en-US.h b/src/en-US.h index 6276fa19e..37d7f0b34 100644 --- a/src/en-US.h +++ b/src/en-US.h @@ -203,6 +203,7 @@ #define STRING_CMD_INFO_MODIFICATION "Modification" #define STRING_CMD_INFO_TOTAL_ACTIVE "Total active time" #define STRING_CMD_UNDO_USAGE "Reverts the most recent change to a task." +#define STRING_CMD_UNDO_MODS "The undo command does not allow further task modification." #define STRING_CMD_STATS_USAGE "Shows task database statistics." #define STRING_CMD_STATS_CATEGORY "Category" #define STRING_CMD_STATS_DATA "Data"