Integration - TDB write operations

- TDB::gc rewritten.
- TDB::commit fixed.
- Corrected usage of handleRecurringTasks wrt TDB.
- Unit tests for TDB.
This commit is contained in:
Paul Beckingham 2009-06-15 00:52:24 -04:00
parent 314ce572e1
commit 7ff178cecc
12 changed files with 211 additions and 125 deletions

2
.gitignore vendored
View file

@ -5,7 +5,7 @@ config.h.in
config.status
src/.deps
src/Makefile
*/task
*/*task
stamp-h1
Makefile
Makefile.in

View file

@ -57,16 +57,17 @@ Record::~Record ()
//
// [ Att::composeF4 ... ] \n
//
std::string Record::composeF4 ()
std::string Record::composeF4 () const
{
std::string ff4 = "[";
bool first = true;
foreach (att, (*this))
std::map <std::string, Att>::const_iterator it;
for (it = this->begin (); it != this->end (); ++it)
{
if (att->second.value () != "")
if (it->second.value () != "")
{
ff4 += (first ? "" : " ") + att->second.composeF4 ();
ff4 += (first ? "" : " ") + it->second.composeF4 ();
first = false;
}
}

View file

@ -39,8 +39,8 @@ public:
Record (const std::string&); // Copy constructor
virtual ~Record (); // Destructor
std::string composeF4 ();
std::string composeCSV ();
std::string composeF4 () const;
std::string composeCSV () const;
void parse (const std::string&);
bool has (const std::string&) const;

View file

@ -27,6 +27,7 @@
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/file.h>
@ -53,7 +54,7 @@
// | | |
// | | +- TDB::add (T)
// | | |
// | | +- TDB::update (T, T')
// | | +- TDB::update (T)
// | | |
// | | +- TDB::commit
// | | write all
@ -133,8 +134,11 @@ void TDB::unlock ()
foreach (location, mLocations)
{
fflush (location->pending);
fclose (location->pending);
location->pending = NULL;
fflush (location->completed);
fclose (location->completed);
location->completed = NULL;
}
@ -257,16 +261,16 @@ int TDB::loadCompleted (std::vector <Task>& tasks, Filter& filter)
////////////////////////////////////////////////////////////////////////////////
// TODO Write to transaction log.
// Note: mLocations[0] is where all tasks are written.
void TDB::add (Task& task)
void TDB::add (const Task& task)
{
mNew.push_back (task);
}
////////////////////////////////////////////////////////////////////////////////
// TODO Write to transaction log.
void TDB::update (Task& before, Task& after)
void TDB::update (const Task& task)
{
mModified.push_back (after);
mModified.push_back (task);
}
////////////////////////////////////////////////////////////////////////////////
@ -308,11 +312,9 @@ int TDB::commit ()
mNew.clear ();
// Write out all pending.
fseek (mLocations[0].pending, 0, SEEK_SET);
// TODO Do I need to truncate the file? Does file I/O even work that way
// any more? I forget.
foreach (task, mPending)
fputs (task->composeF4 ().c_str (), mLocations[0].pending);
if (fseek (mLocations[0].pending, 0, SEEK_SET) == 0)
foreach (task, mPending)
fputs (task->composeF4 ().c_str (), mLocations[0].pending);
}
return quantity;
@ -337,50 +339,70 @@ void TDB::upgrade ()
int TDB::gc ()
{
int count = 0;
/*
// Read everything from the pending file.
std::vector <T> all;
allPendingT (all);
// A list of the truly pending tasks.
std::vector <T> pending;
// Set up a second TDB.
Filter filter;
TDB tdb;
tdb.location (mLocations[0].path);
tdb.lock ();
std::vector<T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
std::vector <Task> pending;
tdb.loadPending (pending, filter);
std::vector <Task> completed;
tdb.loadCompleted (completed, filter);
// Now move completed and deleted tasks from the pending list to the
// completed list. Isn't garbage collection easy?
foreach (task, pending)
{
// Some tasks stay in the pending file.
if (it->getStatus () == T::pending ||
it->getStatus () == T::recurring)
if (task->getStatus () == Task::completed ||
task->getStatus () == Task::deleted)
{
pending.push_back (*it);
}
// Others are transferred to the completed file.
else
{
writeCompleted (*it);
completed.push_back (*task);
pending.erase (task);
++count;
}
}
// Dump all clean tasks into pending. But don't bother unless at least one
// task was transferred.
if (count)
overwritePending (pending);
*/
// No commit - all updates performed manually.
if (count > 0)
{
if (fseek (tdb.mLocations[0].pending, 0, SEEK_SET) == 0)
{
ftruncate (fileno (tdb.mLocations[0].pending), 0);
foreach (task, pending)
fputs (task->composeF4 ().c_str (), tdb.mLocations[0].pending);
}
if (fseek (tdb.mLocations[0].completed, 0, SEEK_SET) == 0)
{
ftruncate (fileno (tdb.mLocations[0].completed), 0);
foreach (task, completed)
fputs (task->composeF4 ().c_str (), tdb.mLocations[0].completed);
}
}
// Close files.
tdb.unlock ();
return count;
}
////////////////////////////////////////////////////////////////////////////////
FILE* TDB::openAndLock (const std::string& file)
{
// TODO Need provision here for read-only locations.
// Check for access.
if (access (file.c_str (), F_OK | R_OK | W_OK))
throw std::string ("Task does not have the correct permissions for '") +
file + "'.";
bool exists = access (file.c_str (), F_OK) ? false : true;
if (exists)
if (access (file.c_str (), R_OK | W_OK))
throw std::string ("Task does not have the correct permissions for '") +
file + "'.";
// Open the file.
FILE* in = fopen (file.c_str (), "r+");
FILE* in = fopen (file.c_str (), (exists ? "r+" : "w+"));
if (!in)
throw std::string ("Could not open '") + file + "'.";

View file

@ -53,11 +53,11 @@ public:
int loadPending (std::vector <Task>&, Filter&);
int loadCompleted (std::vector <Task>&, Filter&);
void add (Task&); // Single task add to pending
void update (Task&, Task&); // Single task update to pending
int commit (); // Write out all tasks
void upgrade (); // Convert both files to FF4
int gc (); // Clean up pending
void add (const Task&); // Single task add to pending
void update (const Task&); // Single task update to pending
int commit (); // Write out all tasks
void upgrade (); // Convert both files to FF4
int gc (); // Clean up pending
private:
FILE* openAndLock (const std::string&);

View file

@ -40,6 +40,25 @@ Task::Task ()
set ("uuid", uuid ());
}
////////////////////////////////////////////////////////////////////////////////
Task::Task (const Task& other)
: Record (other)
, id (other.id)
{
}
////////////////////////////////////////////////////////////////////////////////
Task& Task::operator= (const Task& other)
{
if (this != &other)
{
Record::operator= (other);
id = other.id;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
// Attempt an FF4 parse first, using Record::parse, and in the event of an error
// try a legacy parse (F3, FF2). Note that FF1 is no longer supported.
@ -296,7 +315,7 @@ void Task::legacyParse (const std::string& line)
}
////////////////////////////////////////////////////////////////////////////////
std::string Task::composeCSV ()
std::string Task::composeCSV () const
{
std::stringstream out;
@ -421,7 +440,7 @@ void Task::addTags (const std::vector <std::string>& tags)
}
////////////////////////////////////////////////////////////////////////////////
void Task::getTags (std::vector<std::string>& tags)
void Task::getTags (std::vector<std::string>& tags) const
{
split (tags, get ("tags"), ',');
}

View file

@ -36,11 +36,13 @@ class Task : public Record
{
public:
Task (); // Default constructor
Task (const Task&); // Copy constructor
Task& operator= (const Task&); // Assignment operator
Task (const std::string&); // Parse
~Task (); // Destructor
void parse (const std::string&);
std::string composeCSV ();
std::string composeCSV () const;
// Status values.
enum status {pending, completed, deleted, recurring /* , retired, deferred */};
@ -61,7 +63,7 @@ public:
bool hasTag (const std::string&);
void addTag (const std::string&);
void addTags (const std::vector <std::string>&);
void getTags (std::vector<std::string>&);
void getTags (std::vector<std::string>&) const;
void removeTag (const std::string&);
void getAnnotations (std::vector <Att>&) const;

View file

@ -82,6 +82,7 @@ std::string handleAdd ()
context.tdb.lock (context.config.get ("locking", true));
context.tdb.add (context.task);
context.tdb.commit ();
context.tdb.unlock ();
return out.str ();
@ -97,6 +98,8 @@ std::string handleProjects ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
int quantity = context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// Scan all the tasks for their project name, building a map using project
@ -154,6 +157,8 @@ std::string handleTags ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
int quantity = context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// Scan all the tasks for their project name, building a map using project
@ -663,8 +668,9 @@ std::string handleExport ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
foreach (task, tasks)
{

View file

@ -86,9 +86,12 @@ std::string handleCustomReport (const std::string& report)
context.tdb.lock (context.config.get ("locking", true));
// TODO Include filter from custom report.
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Filter sequence.
context.filter.applySequence (tasks, context.sequence);
// Initialize colorization for subsequent auto colorization.
initializeColorRules ();

View file

@ -45,7 +45,7 @@ void gatherNextTasks (std::vector <Task>&, std::vector <int>&);
void onChangeCallback ();
// recur.cpp
void handleRecurrence ();
void handleRecurrence (std::vector <Task>&);
Date getNextRecurrence (Date&, std::string&);
bool generateDueDates (Task&, std::vector <Date>&);
void updateRecurrenceMask (/*TDB&,*/ std::vector <Task>&, Task&);

View file

@ -271,8 +271,9 @@ std::string handleInfo ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Filter sequence.
context.filter.applySequence (tasks, context.sequence);
@ -487,8 +488,9 @@ std::string handleReportSummary ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Generate unique list of project names from all pending tasks.
std::map <std::string, bool> allProjects;
@ -651,8 +653,9 @@ std::string handleReportNext ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Restrict to matching subset.
std::vector <int> matching;
@ -814,8 +817,9 @@ std::string handleReportHistory ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
foreach (task, tasks)
{
@ -967,8 +971,9 @@ std::string handleReportGHistory ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
foreach (task, tasks)
{
@ -1157,8 +1162,9 @@ std::string handleReportTimesheet ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Just do this once.
int width = context.getWidth ();
@ -1487,8 +1493,9 @@ std::string handleReportCalendar ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.loadPending (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
// Find the oldest pending due date.
Date oldest;
@ -1594,8 +1601,9 @@ std::string handleReportStats ()
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.load (tasks, context.filter);
handleRecurrence (tasks);
context.tdb.commit ();
context.tdb.unlock ();
// TODO handleRecurrence (tdb, tasks);
Date now;
time_t earliest = time (NULL);

View file

@ -32,88 +32,111 @@
Context context;
////////////////////////////////////////////////////////////////////////////////
void get (std::vector <Task>& pending, std::vector <Task>& 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 (38);
UnitTest t (22);
try
{
/*
// Remove any residual test file.
unlink ("./pending.data");
unlink ("./completed.data");
// Try reading an empty database.
Filter filter;
std::vector <Task> all;
std::vector <Task> pending;
std::vector <Task> 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.dataDirectory (".");
std::vector <T> all;
t.ok (!tdb.pendingT (all), "TDB::pendingT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
t.is ((int) all.size (), 0, "empty db");
tdb.location (".");
tdb.lock ();
Task task ("[name:\"value\"]");
tdb.add (task);
tdb.unlock ();
// Add a new task.
T t1;
t1.setId (1);
t1.setStatus (T::pending);
t1.setAttribute ("project", "p1");
t1.setDescription ("task 1");
t.diag (t1.compose ());
t.ok (tdb.addT (t1), "TDB::addT t1");
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");
// Verify as above.
t.ok (tdb.pendingT (all), "TDB::pendingT read db");
t.is ((int) all.size (), 1, "empty db");
t.ok (tdb.allPendingT (all), "TDB::allPendingT read db");
t.is ((int) all.size (), 1, "empty db");
t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
t.is ((int) all.size (), 0, "empty db");
// Add with commit.
tdb.lock ();
tdb.add (task);
tdb.commit ();
tdb.unlock ();
// TODO Modify task.
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");
// Complete task.
t1.setStatus (T::completed);
t.ok (tdb.modifyT (t1), "TDB::modifyT (completed) t1");;
t.ok (tdb.pendingT (all), "TDB::pendingT read db");
t.is ((int) all.size (), 0, "empty db");
t.ok (tdb.allPendingT (all), "TDB::allPendingT read db");
t.is ((int) all.size (), 1, "empty db");
t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
t.is ((int) all.size (), 0, "empty db");
// Update with commit.
tdb.lock ();
pending.clear ();
completed.clear ();
tdb.load (all, context.filter);
all[0].set ("name", "value2");
tdb.update (all[0]);
tdb.commit ();
tdb.unlock ();
t.is (tdb.gc (), 1, "TDB::gc");
t.ok (tdb.pendingT (all), "TDB::pendingT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (tdb.allPendingT (all), "TDB::allPendingT read empty db");
t.is ((int) all.size (), 0, "empty db");
t.ok (tdb.completedT (all), "TDB::completedT read db");
t.is ((int) all.size (), 1, "empty db");
t.ok (tdb.allCompletedT (all), "TDB::allCompletedT read db");
t.is ((int) all.size (), 1, "empty db");
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");
// Add a new task.
T t2;
t2.setId (1);
t2.setAttribute ("project", "p2");
t2.setDescription ("task 2");
t.ok (tdb.addT (t2), "TDB::addT t2");
// GC.
tdb.lock ();
all.clear ();
tdb.loadPending (all, context.filter);
all[0].setStatus (Task::completed);
tdb.update (all[0]);
Task t2 ("[foo:\"bar\" status:\"pending\"]");
tdb.add (t2);
tdb.commit ();
tdb.unlock ();
// Delete task.
t2.setStatus (T::deleted);
t.ok (tdb.modifyT (t2), "TDB::modifyT (deleted) t2");
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");
// GC the files.
t.is (tdb.gc (), 1, "1 <- TDB::gc");
*/
tdb.gc ();
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)
@ -128,8 +151,10 @@ int main (int argc, char** argv)
return -2;
}
/*
unlink ("./pending.data");
unlink ("./completed.data");
*/
return 0;
}