From 139e55fca3b7b600fb65f726eae63965b4b6a70d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 12 Apr 2011 20:44:54 -0400 Subject: [PATCH 1/3] Code Cleanup - Eliminated "itask" interactive mode code. --- src/Context.h | 1 - src/interactive.cpp | 12 ------------ src/main.cpp | 24 +----------------------- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/Context.h b/src/Context.h index bc82041c6..b44433a47 100644 --- a/src/Context.h +++ b/src/Context.h @@ -50,7 +50,6 @@ public: void initialize2 (int, char**); // all startup void initialize (); // for reinitializing int run (); // task classic - int interactive (); // task interactive (not implemented) int dispatch (std::string&); // command handler dispatch void shadow (); // shadow file update diff --git a/src/interactive.cpp b/src/interactive.cpp index b35f03a99..8c5d8c8fe 100644 --- a/src/interactive.cpp +++ b/src/interactive.cpp @@ -25,26 +25,14 @@ // //////////////////////////////////////////////////////////////////////////////// -//#include #include -//#include #include #include -//#include -//#include #include "Context.h" -//#include "text.h" -//#include "util.h" #include "main.h" #include "i18n.h" #include "../cmake.h" -//////////////////////////////////////////////////////////////////////////////// -int Context::interactive () -{ - return 0; -} - //////////////////////////////////////////////////////////////////////////////// int Context::getWidth () { diff --git a/src/main.cpp b/src/main.cpp index 047e194e2..d4db3f5dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,29 +64,7 @@ int main (int argc, char** argv) try { context.initialize (argc, argv); - -/* From old 2.0.0 - std::string::size_type task = context.program.find ("/task"); - std::string::size_type t = context.program.find ("/t"); - std::string::size_type cal = context.program.find ("/cal"); - - if (context.program != "task" && - context.program != "t" && - context.program != "cal" && - (task == std::string::npos || context.program.length () != task + 5) && - (t == std::string::npos || context.program.length () != t + 2) && - (cal == std::string::npos || context.program.length () != cal + 4)) - status = context.handleInteractive (); - else - status = context.run (); -*/ - - std::string::size_type itask = context.program.find ("/itask"); - if (context.program == "itask" || - (itask != std::string::npos && context.program.length () == itask + 5)) - status = context.interactive (); - else - status = context.run (); + status = context.run (); } catch (std::string& error) From 2ce54f10f8f325a08066309485de99426225d0ef Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 12 Apr 2011 23:01:49 -0400 Subject: [PATCH 2/3] Lua - Eliminated unimplemented API calls, which are all going to be replaced by DOM get/set. - Documented new 2.x hooks. --- src/API.cpp | 222 +------------------------------------------------- src/API.h | 8 +- src/Hooks.cpp | 16 ++++ 3 files changed, 22 insertions(+), 224 deletions(-) diff --git a/src/API.cpp b/src/API.cpp index 2ae171d98..0320b496d 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -49,8 +49,8 @@ #include #include -#include "Context.h" -#include "API.h" +#include +#include extern Context context; Task* the_task = NULL; @@ -122,14 +122,6 @@ static int api_task_feature (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// -static int api_task_aliases () -{ - return {}; -} -*/ - //////////////////////////////////////////////////////////////////////////////// // Returns values from .taskrc, by name. static int api_task_get_config (lua_State* L) @@ -139,54 +131,6 @@ static int api_task_get_config (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// --- Temporarily sets .taskrc values, by name. -static int api_task_set_config (name, value) -{ -} - -//////////////////////////////////////////////////////////////////////////////// --- Returns an internationalized string, by string ID, from the appropriate --- locale-based strings file. -static int api_task_i18n_string (id) -{ - return "le foo" -} - -//////////////////////////////////////////////////////////////////////////////// --- Returns a list of tips, from the appropriate locale-based tips file. -static int api_task_i18n_tips () -{ - return {} -} - -//////////////////////////////////////////////////////////////////////////////// --- Returns the name of the current command. -static int api_task_get_command () -{ - return "list" -} - -//////////////////////////////////////////////////////////////////////////////// --- Returns a list of string messages generated so far. -static int api_task_get_header_messages () -{ - return {} -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_get_footnote_messages () -{ - return {} -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_get_debug_messages (lua_State* L) -{ -} -*/ - //////////////////////////////////////////////////////////////////////////////// static int api_task_header_message (lua_State* L) { @@ -222,33 +166,6 @@ static int api_task_exit (lua_State* L) return 0; } -/* -//////////////////////////////////////////////////////////////////////////////// --- Shuts off the hook system for any subsequent hook calls for this command. -static int api_task_inhibit_further_hooks () -{ -} - -//////////////////////////////////////////////////////////////////////////////// --- Returns a table that contains a complete copy of the task. -static int api_task_get (lua_State* L) -{ - return 1; -} - -//////////////////////////////////////////////////////////////////////////////// --- Creates a new task from the data specified in the table t. -static int api_task_add (t) -{ -} - -//////////////////////////////////////////////////////////////////////////////// --- Modifies the task described in the table t. -static int api_task_modify (t) -{ -} -*/ - //////////////////////////////////////////////////////////////////////////////// // -- 'id' is the task id passed to the hook function. Date attributes are // -- returned as a numeric epoch offset. Tags and annotations are returned @@ -274,14 +191,6 @@ static int api_task_get_description (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// -static int api_task_get_annotations (id) -{ - return task.annotations -} -*/ - //////////////////////////////////////////////////////////////////////////////// static int api_task_get_project (lua_State* L) { @@ -304,14 +213,6 @@ static int api_task_get_priority (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// -static int api_task_get_tags (id) -{ - return task.tags -} -*/ - //////////////////////////////////////////////////////////////////////////////// static int api_task_get_status (lua_State* L) { @@ -391,14 +292,6 @@ static int api_task_get_end (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// -static int api_task_get_recur (id) -{ - return task.recur -} -*/ - //////////////////////////////////////////////////////////////////////////////// static int api_task_get_until (lua_State* L) { @@ -433,77 +326,6 @@ static int api_task_get_wait (lua_State* L) return 1; } -/* -//////////////////////////////////////////////////////////////////////////////// --- 'id' is the task id passed to the hook function. Date attributes are --- expected as numeric epoch offsets. Tags and annotations are expected --- as tables. A nil value indicates a missing value. -static int api_task_set_description (id, value) -{ - task.description = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_annotations (id, value) -{ - task.annotations = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_project (id, value) -{ - task.project = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_priority (id, value) -{ - task.priority = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_tags (id, value) -{ - task.tags = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_status (id, value) -{ - task.status = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_due (id, value) -{ - task.due_date = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_start (id, value) -{ - task.start_date = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_recur (id, value) -{ - task.recur = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_until (id, value) -{ - task.until_date = value -} - -//////////////////////////////////////////////////////////////////////////////// -static int api_task_set_wait (id, value) -{ - task.wait_date = value -} -*/ - //////////////////////////////////////////////////////////////////////////////// API::API () : L (NULL) @@ -532,62 +354,22 @@ void API::initialize () lua_pushcfunction (L, api_task_lua_version); lua_setglobal (L, "task_lua_version"); lua_pushcfunction (L, api_task_os); lua_setglobal (L, "task_os"); lua_pushcfunction (L, api_task_feature); lua_setglobal (L, "task_feature"); -/* - lua_pushcfunction (L, api_task_aliases); lua_setglobal (L, "task_aliases"); -*/ lua_pushcfunction (L, api_task_get_config); lua_setglobal (L, "task_get_config"); -/* - lua_pushcfunction (L, api_task_set_config); lua_setglobal (L, "task_set_config"); - lua_pushcfunction (L, api_task_i18n_string); lua_setglobal (L, "task_i18n_string"); - lua_pushcfunction (L, api_task_i18n_tips); lua_setglobal (L, "task_i18n_tips"); - lua_pushcfunction (L, api_task_get_command); lua_setglobal (L, "task_get_command"); - lua_pushcfunction (L, api_task_get_header_messages); lua_setglobal (L, "task_get_header_messages"); - lua_pushcfunction (L, api_task_get_footnote_messages); lua_setglobal (L, "task_get_footnote_messages"); - lua_pushcfunction (L, api_task_get_debug_messages); lua_setglobal (L, "task_get_debug_messages"); -*/ lua_pushcfunction (L, api_task_header_message); lua_setglobal (L, "task_header_message"); lua_pushcfunction (L, api_task_footnote_message); lua_setglobal (L, "task_footnote_message"); lua_pushcfunction (L, api_task_debug_message); lua_setglobal (L, "task_debug_message"); lua_pushcfunction (L, api_task_exit); lua_setglobal (L, "task_exit"); -/* - lua_pushcfunction (L, api_task_inhibit_further_hooks); lua_setglobal (L, "task_inhibit_further_hooks"); - lua_pushcfunction (L, api_task_get); lua_setglobal (L, "task_get"); - lua_pushcfunction (L, api_task_add); lua_setglobal (L, "task_add"); - lua_pushcfunction (L, api_task_modify); lua_setglobal (L, "task_modify"); -*/ lua_pushcfunction (L, api_task_get_uuid); lua_setglobal (L, "task_get_uuid"); lua_pushcfunction (L, api_task_get_description); lua_setglobal (L, "task_get_description"); -/* - lua_pushcfunction (L, api_task_get_annotations); lua_setglobal (L, "task_get_annotations"); -*/ lua_pushcfunction (L, api_task_get_project); lua_setglobal (L, "task_get_project"); lua_pushcfunction (L, api_task_get_priority); lua_setglobal (L, "task_get_priority"); -/* - lua_pushcfunction (L, api_task_get_tags); lua_setglobal (L, "task_get_tags"); -*/ lua_pushcfunction (L, api_task_get_status); lua_setglobal (L, "task_get_status"); lua_pushcfunction (L, api_task_get_due); lua_setglobal (L, "task_get_due"); lua_pushcfunction (L, api_task_get_entry); lua_setglobal (L, "task_get_entry"); lua_pushcfunction (L, api_task_get_start); lua_setglobal (L, "task_get_start"); lua_pushcfunction (L, api_task_get_end); lua_setglobal (L, "task_get_end"); -/* - lua_pushcfunction (L, api_task_get_recur); lua_setglobal (L, "task_get_recur"); -*/ lua_pushcfunction (L, api_task_get_until); lua_setglobal (L, "task_get_until"); lua_pushcfunction (L, api_task_get_wait); lua_setglobal (L, "task_get_wait"); -/* - lua_pushcfunction (L, api_task_set_description); lua_setglobal (L, "task_set_description"); - lua_pushcfunction (L, api_task_set_annotations); lua_setglobal (L, "task_set_annotations"); - lua_pushcfunction (L, api_task_set_project); lua_setglobal (L, "task_set_project"); - lua_pushcfunction (L, api_task_set_priority); lua_setglobal (L, "task_set_priority"); - lua_pushcfunction (L, api_task_set_tags); lua_setglobal (L, "task_set_tags"); - lua_pushcfunction (L, api_task_set_status); lua_setglobal (L, "task_set_status"); - lua_pushcfunction (L, api_task_set_due); lua_setglobal (L, "task_set_due"); - lua_pushcfunction (L, api_task_set_start); lua_setglobal (L, "task_set_start"); - lua_pushcfunction (L, api_task_set_recur); lua_setglobal (L, "task_set_recur"); - lua_pushcfunction (L, api_task_set_until); lua_setglobal (L, "task_set_until"); - lua_pushcfunction (L, api_task_set_wait); lua_setglobal (L, "task_set_wait"); -*/ } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/API.h b/src/API.h index 640f9b00c..01405dace 100644 --- a/src/API.h +++ b/src/API.h @@ -32,13 +32,13 @@ #include #include -#include "Task.h" +#include extern "C" { - #include "lua.h" - #include "lualib.h" - #include "lauxlib.h" + #include + #include + #include } class API diff --git a/src/Hooks.cpp b/src/Hooks.cpp index cae5a2f3e..6c089668f 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -73,6 +73,22 @@ Hook& Hook::operator= (const Hook& other) //////////////////////////////////////////////////////////////////////////////// Hooks::Hooks () { +/* + // New 2.x hooks. + validTaskEvents.push_back ("on-task-add"); + validTaskEvents.push_back ("on-task-modify"); + validTaskEvents.push_back ("on-task-complete"); + validTaskEvents.push_back ("on-task-delete"); + + validProgramEvents.push_back ("on-launch"); + validProgramEvents.push_back ("on-exit"); + validProgramEvents.push_back ("on-file-read"); + validProgramEvents.push_back ("on-file-write"); + validProgramEvents.push_back ("on-synch"); + validProgramEvents.push_back ("on-gc"); +*/ + + // Obsolete 1.x hooks. validProgramEvents.push_back ("post-start"); validProgramEvents.push_back ("post-commit"); validProgramEvents.push_back ("pre-fatal-error"); From 9b3f565e908030b05454ccfc7fa5bd2b56f34f88 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 12 Apr 2011 23:47:38 -0400 Subject: [PATCH 3/3] Enhancements - Added stubbed DOM code. - Added stubbed TDB2 code. --- src/CMakeLists.txt | 30 +- src/Context.cpp | 2 + src/Context.h | 4 + src/DOM.cpp | 96 +++ src/DOM.h | 55 ++ src/TDB2.cpp | 1696 +++++++++++++++++++++++++++++++++++++++++++ src/TDB2.h | 112 +++ test/.gitignore | 1 + test/CMakeLists.txt | 1 + test/tdb2.t.cpp | 175 +++++ 10 files changed, 2157 insertions(+), 15 deletions(-) create mode 100644 src/DOM.cpp create mode 100644 src/DOM.h create mode 100644 src/TDB2.cpp create mode 100644 src/TDB2.h create mode 100644 test/tdb2.t.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 62a58fd87..bf745560b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,21 +3,21 @@ include_directories (${CMAKE_SOURCE_DIR}/src set (task_SRCS API.cpp API.h Att.cpp Att.h Cmd.cpp Cmd.h Color.cpp Color.h Config.cpp Config.h Context.cpp Context.h Date.cpp Date.h - Directory.cpp Directory.h Duration.cpp Duration.h File.cpp - File.h Filter.cpp Filter.h feedback.cpp Grid.cpp Grid.h Hooks.cpp - Hooks.h JSON.cpp JSON.h Keymap.cpp Keymap.h Location.cpp - Location.h Nibbler.cpp Nibbler.h Path.cpp Path.h Permission.cpp - Permission.h Record.cpp Record.h Rectangle.cpp Rectangle.h - Sequence.cpp Sequence.h Subst.cpp Subst.h TDB.cpp TDB.h Table.cpp - Table.h Task.cpp Task.h Taskmod.cpp Taskmod.h Thread.cpp Thread.h - Timer.cpp Timer.h Transport.cpp Transport.h TransportSSH.cpp - TransportSSH.h TransportRSYNC.cpp TransportRSYNC.h - TransportCurl.cpp TransportCurl.h Tree.cpp Tree.h burndown.cpp - command.cpp custom.cpp dependency.cpp diag.cpp edit.cpp - export.cpp history.cpp i18n.h import.cpp interactive.cpp - recur.cpp report.cpp rules.cpp rx.cpp rx.h text.cpp text.h - utf8.cpp utf8.h util.cpp util.h Uri.cpp Uri.h Variant.cpp - Variant.h) + Directory.cpp Directory.h DOM.cpp DOM.h Duration.cpp Duration.h + File.cpp File.h Filter.cpp Filter.h feedback.cpp Grid.cpp Grid.h + Hooks.cpp Hooks.h JSON.cpp JSON.h Keymap.cpp Keymap.h + Location.cpp Location.h Nibbler.cpp Nibbler.h Path.cpp Path.h + Permission.cpp Permission.h Record.cpp Record.h Rectangle.cpp + Rectangle.h Sequence.cpp Sequence.h Subst.cpp Subst.h TDB.cpp + TDB.h Table.cpp TDB2.cpp TDB2.h Table.h Task.cpp Task.h + Taskmod.cpp Taskmod.h Thread.cpp Thread.h Timer.cpp Timer.h + Transport.cpp Transport.h TransportSSH.cpp TransportSSH.h + TransportRSYNC.cpp TransportRSYNC.h TransportCurl.cpp + TransportCurl.h Tree.cpp Tree.h burndown.cpp command.cpp + custom.cpp dependency.cpp diag.cpp edit.cpp export.cpp + history.cpp i18n.h import.cpp interactive.cpp recur.cpp + report.cpp rules.cpp rx.cpp rx.h text.cpp text.h utf8.cpp utf8.h + util.cpp util.h Uri.cpp Uri.h Variant.cpp Variant.h) add_library (task STATIC ${task_SRCS}) add_executable (task_executable main.cpp) diff --git a/src/Context.cpp b/src/Context.cpp index 04ae0cdfd..5a7292ebb 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -51,10 +51,12 @@ Context::Context () , subst () , task () , tdb () +, tdb2 () , program ("") , file_override ("") , var_overrides ("") , cmd () +, dom () , inShadow (false) , terminal_width (0) , terminal_height (0) diff --git a/src/Context.h b/src/Context.h index b44433a47..ab49b729b 100644 --- a/src/Context.h +++ b/src/Context.h @@ -35,7 +35,9 @@ #include "Cmd.h" #include "Task.h" #include "TDB.h" +#include "TDB2.h" #include "Hooks.h" +#include "DOM.h" class Context { @@ -83,6 +85,7 @@ public: Subst subst; Task task; TDB tdb; + TDB2 tdb2; std::string program; std::vector args; std::string file_override; @@ -92,6 +95,7 @@ public: std::vector tagAdditions; std::vector tagRemovals; Hooks hooks; + DOM dom; std::vector headers; std::vector footnotes; diff --git a/src/DOM.cpp b/src/DOM.cpp new file mode 100644 index 000000000..b400ad16c --- /dev/null +++ b/src/DOM.cpp @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// taskwarrior - a command line task list manager. +// +// Copyright 2011, Paul Beckingham, Federico Hernandez. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +//////////////////////////////////////////////////////////////////////////////// +DOM::DOM () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +DOM::~DOM () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +const int DOM::getInteger (const std::string& name) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +const double DOM::getReal (const std::string& name) +{ + return 0.0; +} + +//////////////////////////////////////////////////////////////////////////////// +const bool DOM::getBoolean (const std::string& name) +{ + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +const time_t DOM::getDate (const std::string& name) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string DOM::get (const std::string& name) +{ + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +void DOM::set (const std::string& name, const bool value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void DOM::set (const std::string& name, const int value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void DOM::set (const std::string& name, const double value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void DOM::set (const std::string& name, const time_t value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void DOM::set (const std::string& name, const std::string& value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/DOM.h b/src/DOM.h new file mode 100644 index 000000000..6c4da7c1f --- /dev/null +++ b/src/DOM.h @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// taskwarrior - a command line task list manager. +// +// Copyright 2011, Paul Beckingham, Federico Hernandez. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_DOM +#define INCLUDED_DOM + +#include +#include + +class DOM +{ +public: + DOM (); + ~DOM (); + + const int getInteger (const std::string&); + const double getReal (const std::string&); + const bool getBoolean (const std::string&); + const time_t getDate (const std::string&); + const std::string get (const std::string&); + + void set (const std::string&, const bool); + void set (const std::string&, const int); + void set (const std::string&, const double); + void set (const std::string&, const time_t); + void set (const std::string&, const std::string&); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB2.cpp b/src/TDB2.cpp new file mode 100644 index 000000000..f6cd261d1 --- /dev/null +++ b/src/TDB2.cpp @@ -0,0 +1,1696 @@ +//////////////////////////////////////////////////////////////////////////////// +// taskwarrior - a command line task list manager. +// +// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#if 0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "text.h" +#include "util.h" +#include "TDB.h" +#include "Directory.h" +#include "File.h" +#include "Table.h" +#include "Timer.h" +#include "Color.h" +#include "main.h" + +#define NDEBUG +#include "assert.h" +#include "Taskmod.h" + +#define DEBUG_OUTPUT 0 + +#if DEBUG_OUTPUT > 0 + #define DEBUG_STR(str) std::cout << "DEBUG: " << str << "\n"; std::cout.flush() + #define DEBUG_STR_PART(str) std::cout << "DEBUG: " << str; std::cout.flush() + #define DEBUG_STR_END(str) std::cout << str << "\n"; std::cout.flush() +#else + #define DEBUG_STR(str) + #define DEBUG_STR_PART(str) + #define DEBUG_STR_END(str) +#endif + +extern Context context; + +//////////////////////////////////////////////////////////////////////////////// +// Helper function for TDB::merge +void readTaskmods (std::vector &input, + std::vector ::iterator &start, + std::list &list) +{ + std::string line; + Taskmod tmod_tmp; + + DEBUG_STR ("reading taskmods from file: "); + + for ( ; start != input.end (); ++start) + { + line = *start; + + if (line.substr (0, 4) == "time") + { + std::stringstream stream (line.substr (5)); + long ts; + stream >> ts; + + if (stream.fail ()) + throw std::string ("There was a problem reading the timestamp from the undo.data file."); + + // 'time' is the first line of a modification + // thus we will (re)set the taskmod object + tmod_tmp.reset (ts); + + } + else if (line.substr (0, 3) == "old") + { + tmod_tmp.setBefore (Task (line.substr (4))); + + } + else if (line.substr (0, 3) == "new") + { + tmod_tmp.setAfter (Task (line.substr (4))); + + // 'new' is the last line of a modification, + // thus we can push to the list + list.push_back (tmod_tmp); + + assert (tmod_tmp.isValid ()); + DEBUG_STR (" taskmod complete"); + } + } + + DEBUG_STR ("DONE"); +} + +//////////////////////////////////////////////////////////////////////////////// +// The ctor/dtor do nothing. +// The lock/unlock methods hold the file open. +// There should be only one commit. +// +// +- TDB::TDB +// | +// | +- TDB::lock +// | | open +// | | [lock] +// | | +// | | +- TDB::load (Filter) +// | | | read all +// | | | apply filter +// | | | return subset +// | | | +// | | +- TDB::add (T) +// | | | +// | | +- TDB::update (T) +// | | | +// | | +- TDB::commit +// | | | write all +// | | | +// | | +- TDB::undo +// | | +// | +- TDB::unlock +// | [unlock] +// | close +// | +// +- TDB::~TDB +// [TDB::unlock] +// +TDB::TDB () +: mLock (true) +, mAllOpenAndLocked (false) +, mId (1) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +TDB::~TDB () +{ + if (mAllOpenAndLocked) + unlock (); +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::clear () +{ + mLocations.clear (); + mLock = true; + + if (mAllOpenAndLocked) + unlock (); + + mAllOpenAndLocked = false; + mId = 1; + mPending.clear (); + mNew.clear (); + mCompleted.clear (); + mModified.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::location (const std::string& path) +{ + Directory d (path); + if (!d.exists ()) + throw std::string ("Data location '") + + path + + "' does not exist, or is not readable and writable."; + + mLocations.push_back (Location (d)); +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::lock (bool lockFile /* = true */) +{ + if (context.hooks.trigger ("pre-file-lock")) + { + mLock = lockFile; + + mPending.clear (); + mNew.clear (); + mCompleted.clear (); + mModified.clear (); + + foreach (location, mLocations) + { + location->pending = openAndLock (location->path + "/pending.data"); + location->completed = openAndLock (location->path + "/completed.data"); + location->undo = openAndLock (location->path + "/undo.data"); + } + + mAllOpenAndLocked = true; + context.hooks.trigger ("post-file-lock"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::unlock () +{ + // Do not clear out these items, as they may be used in a read-only fashion. + // mPending.clear (); + // mNew.clear (); + // mModified.clear (); + + foreach (location, mLocations) + { + fflush (location->pending); + fclose (location->pending); + location->pending = NULL; + + fflush (location->completed); + fclose (location->completed); + location->completed = NULL; + + fflush (location->undo); + fclose (location->undo); + location->completed = NULL; + } + + mAllOpenAndLocked = false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns number of filtered tasks. +// Note: tasks.clear () is deliberately not called, to allow the combination of +// multiple files. +int TDB::load (std::vector & tasks, Filter& filter) +{ + // Special optimization: if the filter contains Att ('status', '', 'pending'), + // and no other 'status' filters, then loadCompleted can be skipped. + int numberStatusClauses = 0; + int numberSimpleStatusClauses = 0; + foreach (att, filter) + { + if (att->name () == "status") + { + ++numberStatusClauses; + + if (att->mod () == "" && + (att->value () == "pending" || + att->value () == "waiting")) + ++numberSimpleStatusClauses; + } + } + + loadPending (tasks, filter); + + if (numberStatusClauses == 0 || + numberStatusClauses != numberSimpleStatusClauses) + loadCompleted (tasks, filter); + else + context.debug ("load optimization short circuit"); + + return tasks.size (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns number of filtered tasks. +// Note: tasks.clear () is deliberately not called, to allow the combination of +// multiple files. +int TDB::loadPending (std::vector & tasks, Filter& filter) +{ + Timer t ("TDB::loadPending"); + + std::string file; + int line_number = 1; + + try + { + // Only load if not already loaded. + if (mPending.size () == 0) + { + mId = 1; + char line[T_LINE_MAX]; + foreach (location, mLocations) + { + line_number = 1; + file = location->path + "/pending.data"; + + fseek (location->pending, 0, SEEK_SET); + while (fgets (line, T_LINE_MAX, location->pending)) + { + int length = strlen (line); + if (length > 3) // []\n + { + // TODO Add hidden attribute indicating source? + Task task (line); + + Task::status status = task.getStatus (); + task.id = mId++; + + mPending.push_back (task); + + // Maintain mapping for ease of link/dependency resolution. + // Note that this mapping is not restricted by the filter, and is + // therefore a complete set. + mI2U[task.id] = task.get ("uuid"); + mU2I[task.get ("uuid")] = task.id; + } + + ++line_number; + } + } + } + + // Now filter and return. + if (filter.size ()) + { + foreach (task, mPending) + if (filter.pass (*task)) + tasks.push_back (*task); + } + else + { + foreach (task, mPending) + tasks.push_back (*task); + } + + // Hand back any accumulated additions, if TDB::loadPending is being called + // repeatedly. + int fakeId = mId; + if (filter.size ()) + { + foreach (task, mNew) + { + task->id = fakeId++; + if (filter.pass (*task)) + tasks.push_back (*task); + } + } + else + { + foreach (task, mNew) + { + task->id = fakeId++; + tasks.push_back (*task); + } + } + } + + catch (std::string& e) + { + std::stringstream s; + s << " in " << file << " at line " << line_number; + throw e + s.str (); + } + + return tasks.size (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns number of filtered tasks. +// Note: tasks.clear () is deliberately not called, to allow the combination of +// multiple files. +int TDB::loadCompleted (std::vector & tasks, Filter& filter) +{ + Timer t ("TDB::loadCompleted"); + + std::string file; + int line_number = 1; + + try + { + if (mCompleted.size () == 0) + { + char line[T_LINE_MAX]; + foreach (location, mLocations) + { + line_number = 1; + file = location->path + "/completed.data"; + + fseek (location->completed, 0, SEEK_SET); + while (fgets (line, T_LINE_MAX, location->completed)) + { + int length = strlen (line); + if (length > 3) // []\n + { + // TODO Add hidden attribute indicating source? + + Task task (line); + task.id = 0; // Need a value, just not a valid value. + + mCompleted.push_back (task); + } + + ++line_number; + } + } + } + + // Now filter and return. + if (filter.size ()) + { + foreach (task, mCompleted) + if (filter.pass (*task)) + tasks.push_back (*task); + } + else + { + foreach (task, mCompleted) + tasks.push_back (*task); + } + } + + catch (std::string& e) + { + std::stringstream s; + s << " in " << file << " at line " << line_number; + throw e + s.str (); + } + + return tasks.size (); +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector & TDB::getAllPending () +{ + return mPending; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector & TDB::getAllNew () +{ + return mNew; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector & TDB::getAllCompleted () +{ + return mCompleted; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector & TDB::getAllModified () +{ + return mModified; +} + +//////////////////////////////////////////////////////////////////////////////// +// Note: mLocations[0] is where all tasks are written. +void TDB::add (const Task& task) +{ + std::string unique; + Task t (task); + if (task.get ("uuid") == "") + unique = ::uuid (); + else + unique = task.get ("uuid"); + + t.set ("uuid", unique); + + // If the tasks are loaded, then verify that this uuid is not already in + // the file. + if (uuidAlreadyUsed (unique, mNew) || + uuidAlreadyUsed (unique, mModified) || + uuidAlreadyUsed (unique, mPending) || + uuidAlreadyUsed (unique, mCompleted)) + throw std::string ("Cannot add task because the uuid '") + unique + "' is not unique."; + + mNew.push_back (t); + mI2U[task.id] = unique; + mU2I[task.get ("uuid")] = t.id; +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::update (const Task& task) +{ + mModified.push_back (task); +} + +//////////////////////////////////////////////////////////////////////////////// +// Interestingly, only the pending file gets written to. The completed file is +// only modified by TDB::gc. +int TDB::commit () +{ + Timer t ("TDB::commit"); + context.hooks.trigger ("pre-commit"); + + int quantity = mNew.size () + mModified.size (); + + // This is an optimization. If there are only new tasks, and none were + // modified, simply seek to the end of pending and write. + if (mNew.size () && ! mModified.size ()) + { + fseek (mLocations[0].pending, 0, SEEK_END); + foreach (task, mNew) + { + mPending.push_back (*task); + fputs (task->composeF4 ().c_str (), mLocations[0].pending); + } + + fseek (mLocations[0].undo, 0, SEEK_END); + foreach (task, mNew) + writeUndo (*task, mLocations[0].undo); + + mNew.clear (); + context.hooks.trigger ("post-commit"); + return quantity; + } + + // The alternative is to potentially rewrite both files. + else if (mNew.size () || mModified.size ()) + { + // allPending is a copy of mPending, with all modifications included, and + // new tasks appended. + std::vector allPending; + allPending = mPending; + foreach (mtask, mModified) + { + foreach (task, allPending) + { + if (task->id == mtask->id) + { + *task = *mtask; + goto next_mod; + } + } + + next_mod: + ; + } + + foreach (task, mNew) + allPending.push_back (*task); + + // Write out all pending. + if (fseek (mLocations[0].pending, 0, SEEK_SET) == 0) + { + if (ftruncate (fileno (mLocations[0].pending), 0)) + throw std::string ("Failed to truncate pending.data file "); + + foreach (task, allPending) + fputs (task->composeF4 ().c_str (), mLocations[0].pending); + } + + // Update the undo log. + if (fseek (mLocations[0].undo, 0, SEEK_END) == 0) + { + foreach (mtask, mModified) + { + foreach (task, mPending) + { + if (task->id == mtask->id) + { + writeUndo (*task, *mtask, mLocations[0].undo); + goto next_mod2; + } + } + + next_mod2: + ; + } + + foreach (task, mNew) + writeUndo (*task, mLocations[0].undo); + } + + mPending = allPending; + + mModified.clear (); + mNew.clear (); + } + + context.hooks.trigger ("post-commit"); + return quantity; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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. +// Now reverts expired waiting tasks to pending. +// Now cleans up dangling dependencies. +int TDB::gc () +{ + Timer t ("TDB::gc"); + + // Allowed as a temporary override. + if (!context.config.getBoolean ("gc")) + return 0; + + int count_pending_changes = 0; + int count_completed_changes = 0; + Date now; + + if (mNew.size ()) + throw std::string ("Unexpected new tasks found during gc."); + + if (mModified.size ()) + throw std::string ("Unexpected modified tasks found during gc."); + + lock (); + + Filter filter; + std::vector ignore; + loadPending (ignore, filter); + + // Search for dangling dependencies. These are dependencies whose uuid cannot + // be converted to an id by TDB. + std::vector deps; + foreach (task, mPending) + { + if (task->has ("depends")) + { + deps.clear (); + task->getDependencies (deps); + foreach (dep, deps) + { + if (id (*dep) == 0) + { + task->removeDependency (*dep); + ++count_pending_changes; + context.debug ("GC: Removed dangling dependency " + + task->get ("uuid") + + " -> " + + *dep); + } + } + } + } + + // Now move completed and deleted tasks from the pending list to the + // completed list. Isn't garbage collection easy? + std::vector still_pending; + std::vector newly_completed; + foreach (task, mPending) + { + Task::status s = task->getStatus (); + if (s == Task::completed || + s == Task::deleted) + { + newly_completed.push_back (*task); + ++count_pending_changes; // removal + ++count_completed_changes; // addition + } + else if (s == Task::waiting) + { + // Wake up tasks that need to be woken. + Date wait_date (atoi (task->get ("wait").c_str ())); + if (now > wait_date) + { + task->setStatus (Task::pending); + task->remove ("wait"); + ++count_pending_changes; // modification + + context.debug (std::string ("TDB::gc waiting -> pending for ") + + task->get ("uuid")); + } + + still_pending.push_back (*task); + } + else + still_pending.push_back (*task); + } + + // No commit - all updates performed manually. + if (count_pending_changes > 0) + { + if (fseek (mLocations[0].pending, 0, SEEK_SET) == 0) + { + if (ftruncate (fileno (mLocations[0].pending), 0)) + throw std::string ("Failed to truncate pending.data file "); + + foreach (task, still_pending) + fputs (task->composeF4 ().c_str (), mLocations[0].pending); + + // Update cached copy. + mPending = still_pending; + } + } + + // Append the new_completed tasks to completed.data. No need to write out the + // whole list. + if (count_completed_changes > 0) + { + fseek (mLocations[0].completed, 0, SEEK_END); + foreach (task, newly_completed) + fputs (task->composeF4 ().c_str (), mLocations[0].completed); + } + + // Close files. + unlock (); + + std::stringstream s; + s << "gc " << (count_pending_changes + count_completed_changes) << " tasks"; + context.debug (s.str ()); + return count_pending_changes + count_completed_changes; +} + +//////////////////////////////////////////////////////////////////////////////// +int TDB::nextId () +{ + return mId++; +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::undo () +{ + context.hooks.trigger ("pre-undo"); + 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. + bool useColor = context.config.getBoolean ("color") || + context.config.getBoolean ("_forcecolor") ? true : false; + + Color color_red (useColor ? context.config.get ("color.undo.before") : ""); + Color color_green (useColor ? 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. + Table table; + table.setTableWidth (context.getWidth ()); + table.setTableIntraPadding (2); + table.addColumn (" "); + table.addColumn ("Prior Values"); + table.addColumn ("Current Values"); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + { + table.setColumnUnderline (1); + table.setColumnUnderline (2); + } + else + table.setTableDashedUnderline (); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); + table.setColumnWidth (2, Table::flexible); + + 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 = table.addRow (); + table.addCell (row, 0, *name); + table.addCell (row, 1, renderAttribute (*name, before.get (*name))); + table.setCellColor (row, 1, color_red); + } + + foreach (name, before) + { + std::string priorValue = before.get (name->first); + std::string currentValue = after.get (name->first); + + if (currentValue != "") + { + row = table.addRow (); + table.addCell (row, 0, name->first); + table.addCell (row, 1, renderAttribute (name->first, priorValue)); + table.addCell (row, 2, renderAttribute (name->first, currentValue)); + + if (priorValue != currentValue) + { + table.setCellColor (row, 1, color_red); + table.setCellColor (row, 2, color_green); + } + } + } + + foreach (name, afterOnly) + { + row = table.addRow (); + table.addCell (row, 0, *name); + table.addCell (row, 2, renderAttribute (*name, after.get (*name))); + table.setCellColor (row, 2, color_green); + } + } + else + { + int row; + foreach (name, after) + { + row = table.addRow (); + table.addCell (row, 0, name->first); + table.addCell (row, 2, renderAttribute (name->first, after.get (name->first))); + table.setCellColor (row, 2, color_green); + } + } + + std::cout << "\n" + << table.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. + Table table; + table.setTableWidth (context.getWidth ()); + table.setTableIntraPadding (2); + table.addColumn (" "); + table.addColumn (" "); + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); + table.setColumnJustification (0, Table::right); + table.setColumnJustification (1, Table::left); + + int row = table.addRow (); + table.addCell (row, 0, "--- previous state"); + table.addCell (row, 1, "Undo will restore this state"); + table.setRowColor (row, color_red); + + row = table.addRow (); + table.addCell (row, 0, "+++ current state "); // Note trailing space. + table.addCell (row, 1, "Change made " + lastChange.toString (context.config.get ("dateformat"))); + table.setRowColor (row, color_green); + + table.addRow (); + + // Add rows to table showing diffs. + std::vector all; + Att::allNames (all); + + // 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 = table.addRow (); + // table.addCell (row, 0, *a + ":"); + // table.addCell (row, 1, before_att); + } + + // Attribute deleted. + else if (before_att != "" && after_att == "") + { + row = table.addRow (); + table.addCell (row, 0, "-" + *a + ":"); + table.addCell (row, 1, before_att); + table.setRowColor (row, color_red); + + row = table.addRow (); + table.addCell (row, 0, "+" + *a + ":"); + table.setRowColor (row, color_green); + } + + // Attribute added. + else if (before_att == "" && after_att != "") + { + row = table.addRow (); + table.addCell (row, 0, "-" + *a + ":"); + table.setRowColor (row, color_red); + + row = table.addRow (); + table.addCell (row, 0, "+" + *a + ":"); + table.addCell (row, 1, after_att); + table.setRowColor (row, color_green); + } + + // Attribute changed. + else + { + row = table.addRow (); + table.addCell (row, 0, "-" + *a + ":"); + table.addCell (row, 1, before_att); + table.setRowColor (row, color_red); + + row = table.addRow (); + table.addCell (row, 0, "+" + *a + ":"); + table.addCell (row, 1, after_att); + table.setRowColor (row, color_green); + } + } + } + + std::cout << "\n" + << table.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"; + context.hooks.trigger ("post-undo"); + 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); + context.hooks.trigger ("post-undo"); + 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"; + context.hooks.trigger ("post-undo"); + 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"; +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::merge (const std::string& mergeFile) +{ + /////////////////////////////////////// + // Copyright 2010 - 2011, Johannes Schlatow. + /////////////////////////////////////// + + // list of modifications that we want to add to the local database + std::list mods; + + // list of modifications on the local database + // has to be merged with mods to create the new undo.data + std::list lmods; + + // will contain the NEW undo.data + std::vector undo; + + /////////////////////////////////////// + // initialize the files: + + // load merge file (undo file of right/remote branch) + std::vector r; + if (! File::read (mergeFile, r)) + throw std::string ("Could not read '") + mergeFile + "'."; + + // file has to contain at least one entry + if (r.size () < 3) + throw std::string ("There are no changes to merge."); + + // load undo file (left/local branch) + Directory location (context.config.get ("data.location")); + std::string undoFile = location.data + "/undo.data"; + + std::vector l; + if (! File::read (undoFile, l)) + throw std::string ("Could not read '") + undoFile + "'."; + + std::string rline, lline; + std::vector ::iterator rit, lit; + + // read first line + rit = r.begin (); + lit = l.begin (); + + if (rit != r.end()) + rline = *rit; + if (lit != l.end()) + lline = *lit; + + /////////////////////////////////////// + // find the branch-off point: + + // first lines are not equal => assuming mergeFile starts at a + // later point in time + if (lline.compare (rline) != 0) + { + // iterate in local file to find rline + for ( ; lit != l.end (); ++lit) + { + lline = *lit; + + // push the line to the new undo.data + undo.push_back (lline + "\n"); + + // found first matching lines? + if (lline.compare (rline) == 0) + break; + } + } + + // Add some color. + bool useColor = (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + ? true : false; + Color colorAdded (context.config.get ("color.sync.added")); + Color colorChanged (context.config.get ("color.sync.changed")); + Color colorRejected (context.config.get ("color.sync.rejected")); + + // at this point we can assume: (lline==rline) || (lit == l.end()) + // thus we search for the first non-equal lines or the EOF + bool found = false; + for ( ; (lit != l.end ()) && (rit != r.end ()); ++lit, ++rit) + { + lline = *lit; + rline = *rit; + + // found first non-matching lines? + if (lline.compare (rline) != 0) + { + found = true; + break; + } + else + { + // push the line to the new undo.data + undo.push_back (lline + "\n"); + } + } + + std::cout << "\n"; + + /////////////////////////////////////// + // branch-off point found: + if (found) + { + DEBUG_STR_PART ("Branch-off point found at: "); + DEBUG_STR_END (lline); + + std::list rmods; + + // helper lists + std::set uuid_new, uuid_left; + + // 1. read taskmods out of the remaining lines + readTaskmods (l, lit, lmods); + readTaskmods (r, rit, rmods); + + // 2. move new uuids into mods + DEBUG_STR_PART ("adding new uuids (left) to skip list..."); + + // modifications on the left side are already in the database + // we just need them to merge conflicts, so we add new the mods for + // new uuids to the skip-list 'uuid_left' + std::list::iterator lmod_it; + for (lmod_it = lmods.begin (); lmod_it != lmods.end (); lmod_it++) + { + if (lmod_it->isNew ()) + { +/* + std::cout << "New local task " + << (useColor ? colorAdded.colorize (lmod_it->getUuid ()) : lmod_it->getUuid ()) + << "\n"; +*/ + + uuid_left.insert (lmod_it->getUuid ()); + } + } + + DEBUG_STR_END ("done"); + DEBUG_STR_PART ("move new uuids (right) to redo list..."); + + // new items on the right side need to be inserted into the + // local database + std::list::iterator rmod_it; + for (rmod_it = rmods.begin (); rmod_it != rmods.end (); ) + { + // we have to save and increment the iterator because we may want to delete + // the object from the list + std::list::iterator current = rmod_it++; + Taskmod tmod = *current; + + // new uuid? + if (tmod.isNew ()) + { +/* + std::cout << "Adding new remote task " + << (useColor ? colorAdded.colorize (tmod.getUuid ()) : tmod.getUuid ()) + << "\n"; +*/ + + uuid_new.insert (tmod.getUuid ()); + mods.push_back (tmod); + rmods.erase (current); + } + else if (uuid_new.find (tmod.getUuid ()) != uuid_new.end ()) + { + // uuid of modification was new + mods.push_back (tmod); + rmods.erase (current); + } + } + + DEBUG_STR_END ("done"); + + /////////////////////////////////////// + // merge modifications: + DEBUG_STR ("Merging modifications:"); + + // we iterate backwards to resolve conflicts by timestamps (newest one wins) + std::list::reverse_iterator lmod_rit; + std::list::reverse_iterator rmod_rit; + for (lmod_rit = lmods.rbegin (); lmod_rit != lmods.rend (); ++lmod_rit) + { + Taskmod tmod_l = *lmod_rit; + std::string uuid = tmod_l.getUuid (); + + DEBUG_STR (" left uuid: " + uuid); + + // skip if uuid had already been merged + if (uuid_left.find (uuid) == uuid_left.end ()) + { + bool rwin = false; + bool lwin = false; + for (rmod_rit = rmods.rbegin (); rmod_rit != rmods.rend (); rmod_rit++) + { + Taskmod tmod_r = *rmod_rit; + + DEBUG_STR (" right uuid: " + tmod_r.getUuid ()); + if (tmod_r.getUuid () == uuid) + { + DEBUG_STR (" uuid match found for " + uuid); + + // we already decided to take the mods from the right side + // but we have to find the first modification newer than + // the one on the left side to merge the history too + if (rwin) + { + DEBUG_STR (" scanning right side"); + if (tmod_r > tmod_l) + mods.push_front (tmod_r); + + std::list::iterator tmp_it = rmod_rit.base (); + rmods.erase (--tmp_it); + rmod_rit--; + } + else if (lwin) + { + DEBUG_STR (" cleaning up right side"); + + std::list::iterator tmp_it = rmod_rit.base (); + rmods.erase (--tmp_it); + rmod_rit--; + } + else + { + // which one is newer? + if (tmod_r > tmod_l) + { + std::cout << "Found remote change to " + << (useColor ? colorChanged.colorize (uuid) : uuid) + << " \"" << cutOff (tmod_r.getBefore ().get ("description"), 10) << "\"" + << "\n"; + + mods.push_front(tmod_r); + + // delete tmod from right side + std::list::iterator tmp_it = rmod_rit.base (); + rmods.erase (--tmp_it); + rmod_rit--; + + rwin = true; + } + else + { + std::cout << "Retaining local changes to " + << (useColor ? colorRejected.colorize (uuid) : uuid) + << " \"" << cutOff (tmod_l.getBefore ().get ("description"), 10) << "\"" + << "\n"; + + // inserting right mod into history of local database + // so that it can be restored later + + // TODO feature: make rejected changes on the remote branch restorable +// Taskmod reverse_tmod; +// +// tmod_r.setBefore(lmod_rit->getAfter()); +// tmod_r.setTimestamp(lmod_rit->getTimestamp()+1); +// +// reverse_tmod.setAfter(tmod_r.getBefore()); +// reverse_tmod.setBefore(tmod_r.getAfter()); +// reverse_tmod.setTimestamp(tmod_r.getTimestamp()); +// +// mods.push_back(tmod_r); +// mods.push_back(reverse_tmod); + + // delete tmod from right side + std::list::iterator tmp_it = rmod_rit.base (); + rmods.erase (--tmp_it); + rmod_rit--; + + // mark this uuid as merged + uuid_left.insert (uuid); + lwin = true; + } + } + } + } // for + + if (rwin) + { + DEBUG_STR (" concat the first match to left branch"); + // concat the oldest (but still newer) modification on the right + // to the endpoint on the left + mods.front ().setBefore(tmod_l.getAfter ()); + } + } + } // for + + DEBUG_STR ("adding non-conflicting changes from the right branch"); + mods.splice (mods.begin (), rmods); + + DEBUG_STR ("sorting taskmod list"); + mods.sort (); + } + else if (rit == r.end ()) + { + // nothing happend on the remote branch + // local branch is up-to-date + + // nothing happend on the local branch either + + // break, to suppress autopush + if (lit == l.end ()) + { + mods.clear (); + lmods.clear (); + throw std::string ("Database is up-to-date, no merge required."); + } + + } + else // lit == l.end () + { + // nothing happend on the local branch +/* + std::cout << "No local changes detected.\n"; +*/ + + // add remaining lines (remote branch) to the list of modifications +/* + std::cout << "Remote changes detected.\n"; +*/ + readTaskmods (r, rit, mods); + } + + /////////////////////////////////////// + // Now apply the changes. + // redo command: + + if (!mods.empty ()) + { + std::string pendingFile = location.data + "/pending.data"; + std::vector pending; + + std::string completedFile = location.data + "/completed.data"; + std::vector completed; + + if (! File::read (pendingFile, pending)) + throw std::string ("Could not read '") + pendingFile + "'."; + + if (! File::read (completedFile, completed)) + throw std::string ("Could not read '") + completedFile + "'."; + + // iterate over taskmod list + std::list::iterator it; + for (it = mods.begin (); it != mods.end (); ) + { + std::list::iterator current = it++; + Taskmod tmod = *current; + + // Modification to an existing task. + if (!tmod.isNew ()) + { + std::string uuid = tmod.getUuid (); + Task::status statusBefore = tmod.getBefore().getStatus (); + Task::status statusAfter = tmod.getAfter().getStatus (); + + std::vector ::iterator it; + + bool found = false; + if ( (statusBefore == Task::completed) + || (statusBefore == Task::deleted) ) + { + // Find the same uuid in completed data + for (it = completed.begin (); it != completed.end (); ++it) + { + if (it->find ("uuid:\"" + uuid) != std::string::npos) + { + // Update the completed record. +/* + std::cout << "Modifying " + << (useColor ? colorChanged.colorize (uuid) : uuid) + << "\n"; +*/ + + // remove the \n from composeF4() string + std::string newline = tmod.getAfter ().composeF4 (); + newline = newline.substr (0, newline.length ()-1); + + // does the tasks move to pending data? + // this taskmod will not arise from + // normal usage of task, but those kinds of + // taskmods may be constructed to merge databases + if ( (statusAfter != Task::completed) + && (statusAfter != Task::deleted) ) + { + // insert task into pending data + pending.push_back (newline); + + // remove task from completed data + completed.erase (it); + + } + else + { + // replace the current line + *it = newline; + } + + found = true; + break; + } + } + } + else + { + // Find the same uuid in the pending data. + for (it = pending.begin (); it != pending.end (); ++it) + { + if (it->find ("uuid:\"" + uuid) != std::string::npos) + { + // Update the pending record. + std::cout << "Found remote change to " + << (useColor ? colorChanged.colorize (uuid) : uuid) + << " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\"" + << "\n"; + + // remove the \n from composeF4() string + // which will replace the current line + std::string newline = tmod.getAfter ().composeF4 (); + newline = newline.substr (0, newline.length ()-1); + + // does the tasks move to completed data + if ( (statusAfter == Task::completed) + || (statusAfter == Task::deleted) ) + { + // insert task into completed data + completed.push_back (newline); + + // remove task from pending data + pending.erase (it); + + } + else + { + // replace the current line + *it = newline; + } + + found = true; + break; + } + } + } + + if (!found) + { + std::cout << "Missing " + << (useColor ? colorRejected.colorize (uuid) : uuid) + << " \"" << cutOff (tmod.getBefore ().get ("description"), 10) << "\"" + << "\n"; + mods.erase (current); + } + } + else + { + // Check for dups. + std::string uuid = tmod.getAfter ().get ("uuid"); + + // Find the same uuid in the pending data. + bool found = false; + std::vector ::iterator pit; + for (pit = pending.begin (); pit != pending.end (); ++pit) + { + if (pit->find ("uuid:\"" + uuid) != std::string::npos) + { + found = true; + break; + } + } + + if (!found) + { + std::cout << "Merging new remote task " + << (useColor ? colorAdded.colorize (uuid) : uuid) + << " \"" << cutOff (tmod.getAfter ().get ("description"), 10) << "\"" + << "\n"; + + // remove the \n from composeF4() string + std::string newline = tmod.getAfter ().composeF4 (); + newline = newline.substr (0, newline.length ()-1); + pending.push_back (newline); + } + else + { + mods.erase (current); + } + } + } + + // write pending file + if (! File::write (pendingFile, pending)) + throw std::string ("Could not write '") + pendingFile + "'."; + + // write completed file + if (! File::write (completedFile, completed)) + throw std::string ("Could not write '") + completedFile + "'."; + + // at this point undo contains the lines up to the branch-off point + // now we merge mods (new modifications from mergefile) + // with lmods (part of old undo.data) + mods.merge (lmods); + + // generate undo.data format + for (it = mods.begin (); it != mods.end (); it++) + undo.push_back(it->toString ()); + + // write undo file + if (! File::write (undoFile, undo, false)) + throw std::string ("Could not write '") + undoFile + "'."; + } + + // delete objects + lmods.clear (); + mods.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string TDB::uuid (int id) const +{ + std::map ::const_iterator i; + if ((i = mI2U.find (id)) != mI2U.end ()) + return i->second; + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +int TDB::id (const std::string& uuid) const +{ + std::map ::const_iterator i; + if ((i = mU2I.find (uuid)) != mU2I.end ()) + return i->second; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +FILE* TDB::openAndLock (const std::string& file) +{ + // TODO Need provision here for read-only locations. + + // Check for access. + File f (file); + bool exists = f.exists (); + if (exists) + if (!f.readable () || !f.writable ()) + throw std::string ("Task does not have the correct permissions for '") + + file + "'."; + + // Open the file. + FILE* in = fopen (file.c_str (), (exists ? "r+" : "w+")); + if (!in) + throw std::string ("Could not open '") + file + "'."; + + // Lock if desired. Try three times before failing. + int retry = 0; + if (mLock) + while (flock (fileno (in), LOCK_NB | LOCK_EX) && ++retry <= 3) + { + std::cout << "Waiting for file lock...\n"; + while (flock (fileno (in), LOCK_NB | LOCK_EX) && ++retry <= 3) + delay (0.2); + } + + if (retry > 3) + throw std::string ("Could not lock '") + file + "'."; + + return in; +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::writeUndo (const Task& after, FILE* file) +{ + fprintf (file, + "time %u\nnew %s---\n", + (unsigned int) time (NULL), + after.composeF4 ().c_str ()); +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::writeUndo (const Task& before, const Task& after, FILE* file) +{ + fprintf (file, + "time %u\nold %snew %s---\n", + (unsigned int) time (NULL), + before.composeF4 ().c_str (), + after.composeF4 ().c_str ()); +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::uuidAlreadyUsed ( + const std::string& uuid, + const std::vector & all) +{ + std::vector ::const_iterator it; + for (it = all.begin (); it != all.end (); ++it) + if (it->get ("uuid") == uuid) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif + diff --git a/src/TDB2.h b/src/TDB2.h new file mode 100644 index 000000000..f419b5cd1 --- /dev/null +++ b/src/TDB2.h @@ -0,0 +1,112 @@ +//////////////////////////////////////////////////////////////////////////////// +// taskwarrior - a command line task list manager. +// +// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_TDB2 +#define INCLUDED_TDB2 + +#include +#include +#include + +class TDB2 +{ +public: + +private: +}; + + + + + +/* +#include "Location.h" +#include "Filter.h" +#include "Task.h" + +// Length of longest line. +#define T_LINE_MAX 32768 + +class TDB +{ +public: + TDB (); // Default constructor + ~TDB (); // Destructor + + TDB (const TDB&); + TDB& operator= (const TDB&); + + void clear (); + void location (const std::string&); + + void lock (bool lockFile = true); + void unlock (); + + int load (std::vector &, Filter&); + int loadPending (std::vector &, Filter&); + int loadCompleted (std::vector &, Filter&); + + const std::vector & getAllPending (); + const std::vector & getAllNew (); + const std::vector & getAllCompleted (); + const std::vector & getAllModified (); + + void add (const Task&); // Single task add to pending + void update (const Task&); // Single task update to pending + int commit (); // Write out all tasks + int gc (); // Clean up pending + int nextId (); + void undo (); + void merge (const std::string&); + + std::string uuid (int) const; + int id (const std::string&) const; + +private: + FILE* openAndLock (const std::string&); + void writeUndo (const Task&, FILE*); + void writeUndo (const Task&, const Task&, FILE*); + bool uuidAlreadyUsed (const std::string&); + bool uuidAlreadyUsed (const std::string&, const std::vector &); + +private: + std::vector mLocations; + bool mLock; + bool mAllOpenAndLocked; + int mId; + + std::vector mPending; // Contents of pending.data + std::vector mCompleted; // Contents of pending.data + std::vector mNew; // Uncommitted new tasks + std::vector mModified; // Uncommitted modified tasks + + std::map mI2U; // ID -> UUID map + std::map mU2I; // UUID -> ID map +}; +*/ + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/.gitignore b/test/.gitignore index 894dc81af..b512d39c0 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -27,6 +27,7 @@ t.benchmark.t t.t taskmod.t tdb.t +tdb2.t text.t transport.t tree.t diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5d66dc250..1fcbdfc68 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories (${CMAKE_SOURCE_DIR}/src ${TASK_INCLUDE_DIRS}) set (test_SRCS date.t t.t tdb.t duration.t t.benchmark.t text.t autocomplete.t + tdb2.t seq.t record.t att.t subst.t nibbler.t filt.t cmd.t config.t util.t color.t list.t path.t file.t grid.t directory.t rx.t taskmod.t rectangle.t tree.t tree2.t uri.t json.t variant.t) diff --git a/test/tdb2.t.cpp b/test/tdb2.t.cpp new file mode 100644 index 000000000..a9715bb37 --- /dev/null +++ b/test/tdb2.t.cpp @@ -0,0 +1,175 @@ +//////////////////////////////////////////////////////////////////////////////// +// taskwarrior - a command line task list manager. +// +// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include + +#include "main.h" +#include "test.h" + +Context context; + +//////////////////////////////////////////////////////////////////////////////// +void get (std::vector & pending, std::vector & completed) +{ + TDB tdb; + tdb.location ("."); + tdb.lock (); + tdb.loadPending (pending, context.filter); + tdb.loadCompleted (completed, context.filter); + tdb.unlock (); +} + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ + UnitTest t (1); + + try + { + t.pass ("sample"); +/* + // Remove any residual test file. + unlink ("./pending.data"); + unlink ("./completed.data"); + unlink ("./undo.data"); + + // Set the context to allow GC. + context.config.set ("gc", "on"); + + // Try reading an empty database. + Filter filter; + std::vector all; + std::vector pending; + std::vector completed; + get (pending, completed); + t.ok (pending.size () == 0, "TDB Read empty pending"); + t.ok (completed.size () == 0, "TDB Read empty completed"); + + // Add without commit. + TDB tdb; + tdb.location ("."); + tdb.lock (); + Task task ("[name:\"value\"]"); + tdb.add (task); // P0 C0 N1 M0 + tdb.unlock (); + + pending.clear (); + completed.clear (); + get (pending, completed); + + t.ok (pending.size () == 0, "TDB add -> no commit -> empty"); + t.ok (completed.size () == 0, "TDB add -> no commit -> empty"); + + // Add with commit. + tdb.lock (); + tdb.add (task); // P0 C0 N1 M0 + tdb.commit (); // P1 C0 N0 M0 + tdb.unlock (); + + get (pending, completed); + t.ok (pending.size () == 1, "TDB add -> commit -> saved"); + t.is (pending[0].get ("name"), "value", "TDB load name=value"); + t.is (pending[0].id, 1, "TDB load verification id=1"); + t.ok (completed.size () == 0, "TDB add -> commit -> saved"); + + // Update with commit. + pending.clear (); + completed.clear (); + + tdb.lock (); + tdb.load (all, context.filter); + all[0].set ("name", "value2"); + tdb.update (all[0]); // P1 C0 N0 M1 + tdb.commit (); // P1 C0 N0 M0 + tdb.unlock (); + + pending.clear (); + completed.clear (); + get (pending, completed); + + t.ok (all.size () == 1, "TDB update -> commit -> saved"); + t.is (all[0].get ("name"), "value2", "TDB load name=value2"); + t.is (all[0].id, 1, "TDB load verification id=1"); + + // GC. + all.clear (); + + tdb.lock (); + tdb.loadPending (all, context.filter); + all[0].setStatus (Task::completed); + tdb.update (all[0]); // P1 C0 N0 M1 + Task t2 ("[foo:\"bar\" status:\"pending\"]"); + tdb.add (t2); // P1 C0 N1 M1 + tdb.commit (); + tdb.unlock (); + + pending.clear (); + completed.clear (); + get (pending, completed); + + t.is (pending.size (), (size_t)2, "TDB before gc pending #2"); + t.is (pending[0].id, 1, "TDB before gc pending id 1"); + t.is (pending[0].getStatus (), Task::completed, "TDB before gc pending status completed"); + t.is (pending[1].id, 2, "TDB before gc pending id 2"); + t.is (pending[1].getStatus (), Task::pending, "TDB before gc pending status pending"); + t.is (completed.size (), (size_t)0, "TDB before gc completed 0"); + + tdb.gc (); // P1 C1 N0 M0 + + pending.clear (); + completed.clear (); + get (pending, completed); + + t.is (pending.size (), (size_t)1, "TDB after gc pending #1"); + t.is (pending[0].id, 1, "TDB after gc pending id 2"); + t.is (pending[0].getStatus (), Task::pending, "TDB after gc pending status pending"); + t.is (completed.size (), (size_t)1, "TDB after gc completed #1"); + t.is (completed[0].getStatus (), Task::completed, "TDB after gc completed status completed"); +*/ + } + + catch (std::string& error) + { + t.diag (error); + return -1; + } + + catch (...) + { + t.diag ("Unknown error."); + return -2; + } + + unlink ("./pending.data"); + unlink ("./completed.data"); + unlink ("./undo.data"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +