Index: /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.cpp
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.cpp	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.cpp	(revision 20590)
@@ -1,65 +1,93 @@
 #include "abstractlistaction.h"
+#include "OIDtools.h"
 #include "recptprototools.h"
 
 #include "oaitools.h"
 
-//--------------------------------------------------------------------------------------------------
+
+bool abstractlistaction::check_classifier(recptproto *protocol, const text_t &collection, const text_t &set_name)
+{
+  text_tset metadata;
+  FilterResponse_t response;
+  return get_info(set_name, collection, "", metadata, false, protocol, response, *this->logout);
+}
+
 
 bool abstractlistaction::output_content(ostream &output, recptproto *protocol, oaiargs &params)
 {
-  bool   prevDocSeen;
-  ResumptionToken *token = NULL;
-
-  // start the call; clear down the total number of output documents
-  this->outputDocs = 0;
-
-  // We don't actually handle resumptionTokens yet; if we get one, ignore it
-  if (params["resumptionToken"] != "") {
-    token = new ResumptionToken(params["resumptionToken"]);
-  }
-
-  this->replyToken = NULL;
-
-  // if we've been asked for a set, then use it!
-  if (params["set"] != "") {
-    // get the children of this set
-    text_t gsdlSet = params["set"];
-    text_t gsdlCollect = "";
-
-    // given 'demo:CL2', toGSDL returns 'demo' in gsdlCollect and 'CL2' in gsdlSet. If there is no further
-    // set specified after the name of the collection however, then gsdlSet is empty.
-    oaiclassifier::toGSDL(gsdlCollect, gsdlSet);
-
-    // If gsdlSet is empty, then the user is requesting all the identifiers for the collection, so 
-    // we simply output all docs via their oai_id tag. But if a specific subset IS requested, then
-    // use recurse_set() to traverse any sub classifiers to find the relevant docs.
-    if(gsdlSet == ""){
-      ColInfoResponse_t cinfo;
-      comerror_t err;
-      this->output_content_for_col(output, protocol, gsdlCollect, cinfo, err, params);
-    }
-    else {
-      if (this->check_classifier(protocol, gsdlCollect, gsdlSet)) {
-	this->recurse_set(output, protocol, gsdlCollect, gsdlSet, params, token);
-      }
-    }
-  }
-  // output all records in all hierarchies
-  else {
-    this->output_content_for_all(output, protocol, params);
-  }
-
-  // If - regardless of set required - no documents have been seen, throw an error.
-  if (this->configuration->getOAIVersion() >= 200 && this->prevDocSeen == false) {
+  // Reset variables
+  this->output_docs = 0;
+
+  text_t set_name = params["set"];
+  text_t position = params["position"];
+
+  // Process the resumptionToken if there is one
+  if (params["resumptionToken"] != "")
+  {
+    ResumptionToken resumption_token(params["resumptionToken"]);
+    set_name = resumption_token.getSet();
+    position = resumption_token.getPosition();
+  }
+
+  // Case for "set" argument present -- output just the records in the specified set
+  if (set_name != "")
+  {
+    // Separate the collection name and Greenstone classifier OID from the set name
+    text_t collection_name = "";
+    text_t gsdl_classifier_OID = set_name;
+    oaiclassifier::toGSDL(collection_name, gsdl_classifier_OID);
+
+    // If output_content_for_set() returns false a resumption token has been output, so it's time to stop
+    if (output_content_for_set(output, protocol, params, collection_name, gsdl_classifier_OID, set_name) == false)
+    {
+      return true;
+    }
+  }
+
+  // Case for no "set" argument present -- output all records in all collections
+  else
+  {
+    // Get a list of the collections available
+    text_tarray& collections = this->configuration->getCollectionsList();
+    if (collections.size() == 0)
+    {
+      return false;
+    }
+
+    // Get the current collection from the position value
+    text_t collection_name = "";
+    oaiclassifier::toGSDL(collection_name, position);
+
+    // Find the starting collection
+    text_tarray::iterator collection_iterator = collections.begin();
+    while (collection_iterator != collections.end())
+    {
+      if (collection_name == "" || collection_name == *collection_iterator)
+      {
+	break;
+      }
+
+      collection_iterator++;
+    }
+
+    // Now loop through the remaining collections
+    while (collection_iterator != collections.end())
+    {
+      // If output_content_for_set() returns false a resumption token has been output, so it's time to stop
+      if (output_content_for_set(output, protocol, params, *collection_iterator, "", "") == false)
+      {
+	return true;
+      }
+
+      collection_iterator++;
+    }
+  }
+
+  // If no records were output throw an error
+  if (this->configuration->getOAIVersion() >= 200 && this->output_docs == 0)
+  {
     errorType = "noRecordsMatch";
     this->output_error(output, errorType);
-
-    return false;
-  }
-
-  // do a resumption token if required; errors cancel a token...
-  if (this->replyToken != NULL && this->errorType == "") {
-    // Don't add any whitespace around the resumption token as it can confuse harvesters/validators
-    output << "  <resumptionToken>" << this->replyToken->getToken() << "</resumptionToken>" << endl;
+    return false;
   }
 
@@ -67,271 +95,130 @@
 }
 
-//--------------------------------------------------------------------------------------------------
-
-void abstractlistaction::output_content_for_col(ostream &output, recptproto *protocol, text_t &gsdlCollect,
-						ColInfoResponse_t &cinfo, comerror_t &err, oaiargs &params)
-{ int startDoc = 0;
-
-  text_t metadataPrefix = params["metadataPrefix"];
-
-  // check resumption token
-  if (params["resumptionToken"] != "") {
-    ResumptionToken token(params["resumptionToken"]);
-    if (token.getCollection() == gsdlCollect) {
-      startDoc = token.getPosition() - 1; // first document is said to be 1..
-      metadataPrefix = "oai_dc";  // TO DO: This should come from the resumption token
-    }
-  }
-
-  // Get the OAI nodes from the info db file
-  text_t oai_root_node = "oai";
+
+bool abstractlistaction::output_content_for_set(ostream &output, recptproto *protocol, oaiargs &params, text_t collection_name, text_t gsdl_classifier_OID, text_t set_name)
+{
+  // Check if the set is actually a collection
+  if (gsdl_classifier_OID == "")
+  {
+    gsdl_classifier_OID = "oai";
+  }
+
+  text_t metadata_prefix = params["metadataPrefix"];
+  text_t from = params["from"];
+  text_t until = params["until"];
+  text_t position = "";
+
+  // Process the resumptionToken if there is one
+  if (params["resumptionToken"] != "")
+  {
+    ResumptionToken resumption_token(params["resumptionToken"]);
+    metadata_prefix = resumption_token.getMetadataPrefix();
+    from = resumption_token.getFrom();
+    until = resumption_token.getUntil();
+    position = resumption_token.getPosition();
+  }
+
+  // Get the list of identifiers in this collection
+  // Collections should not contain too many identifiers otherwise this will use a lot of time and memory
   text_tset metadata;  // Must be empty for efficiency
+  FilterResponse_t identifiers_response;
+  get_children(gsdl_classifier_OID, collection_name, "", metadata, false, protocol, identifiers_response, *this->logout);
+
+  // Find the starting position, if necessary
+  ResultDocInfo_tarray::iterator identifier_iterator = identifiers_response.docInfo.begin();
+  if (output_docs == 0)
+  {
+    while (identifier_iterator != identifiers_response.docInfo.end())
+    {
+      if (position == "" || position == (collection_name + ":" + (*identifier_iterator).OID))
+      {
+	break;
+      }
+
+      identifier_iterator++;
+    }
+  }
+
+  // Now loop through displaying the next matching records
+  while (identifier_iterator != identifiers_response.docInfo.end())
+  {
+    position = (*identifier_iterator).OID;
+
+    text_t document_OID = position;
+    if (starts_with(document_OID, "oai."))
+    {
+      document_OID = oaiclassifier::getGSDL_OID(collection_name, document_OID, protocol, *this->logout);
+    }
+
+    // Check this OID is in the (optional) date range specified
+    if (this->in_date_range(output, protocol, params, collection_name, document_OID, from, until))
+    {
+      // If we've output the desired number of records return a resumptionToken and we're done
+      if (this->output_docs == this->configuration->resumeAfter())
+      {
+	// Get the buildDate from the build.cfg file
+	ColInfoResponse_t cinfo;
+	comerror_t err;
+	protocol->get_collectinfo(collection_name, cinfo, err, cerr);
+
+	ResumptionToken resumption_token(cinfo.buildDate, set_name, metadata_prefix, from, until, collection_name + ":" + position);
+
+	// Don't add any whitespace around the resumption token as it can confuse harvesters/validators
+	output << "  <resumptionToken>" << resumption_token.getResumptionTokenString() << "</resumptionToken>" << endl;
+	return false;
+      }
+
+      // Otherwise output this record and increment the count
+      this->output_document(output, protocol, collection_name, document_OID, metadata_prefix);
+      this->output_docs++;
+    }
+
+    identifier_iterator++;
+  }
+
+  return true;
+}
+
+
+bool abstractlistaction::in_date_range(ostream &output, recptproto *protocol, oaiargs &params,
+				       text_t& collection, text_t oai_OID, text_t from, text_t until)
+{
+  // If no "from" or "until" value is specified every record matches, so we don't need to go any further
+  if (from == "" && until == "")
+  {
+    return true;
+  }
+
+  // Get the datestamp from the document as sections do not have this metadata
+  text_t document_OID;
+  get_top(oai_OID, document_OID);
+
+  // Request the lastmodified value for this document
+  text_tset metadata;
+  metadata.insert("lastmodified");
   FilterResponse_t response;
-  get_children(oai_root_node, gsdlCollect, "", metadata, false, protocol, response, *this->logout);
-  
-  // If numDocs is 0, do nothing - this->prevDocSeen will stay false if this is the only collection
-  // looked at, or will keep whatever value it had prior to this col (ensures that if the flag has
-  // been set to true by a previous collection that this won't overwrite it to be false). 
-  if (response.docInfo.size() > 0) {
-    int errorCount      = 0; // Count the number of errors found in the given collection
-
-    for (long i = startDoc; i < response.docInfo.size(); ++i) {
-      if (errorCount > 3) { // If num errors reaches the cut-off value, bail.
-	cerr << "Error: too many records(" << errorCount << ") in the " << gsdlCollect 
-	     << " collection have invalid or non-existant oai_ids - skipping remainder of collection.\n";
-	return;
-      }
-      
-      text_t oai_id = "oai.";
-      oai_id += i;
-      
-      text_t gsdl_id = oaiclassifier::getGSDL_OID(gsdlCollect, oai_id, protocol, *this->logout);
-      
-      if (gsdl_id == "") { // If the string is empty, then the document didn't have an oai_id, so
-	++errorCount;     // increase error count
-	continue;
-      }
-
-
-      // Check that the item with the 0ID 'gsdl_id' has a lastmodified field that fits within
-      // the required date range (if specified).
-      if (this->inDateRange(params["from"], params["until"], gsdlCollect, gsdl_id, protocol, output)) {
-	if (this->output_document(output, protocol, gsdlCollect, gsdl_id, metadataPrefix)) {
-	  // this should be an IF statement, where prevDocSeen is only set to true if the above
-	  // function call returns true (indicating that the doc supported the metadata prefix) but
-	  // for some reason this is always false. This means that if no doc in the requested set supports
-	  // the metadata format, the "no records match" error that should be thrown won't be...
-	  //
-	  // GRB: the above comment is no longer true; proper checks are made
-	  this->prevDocSeen = true;
-	  ++this->outputDocs;
-	}
-      }
-
-      // if we've output the number of resumption documents; prepare a resumptionToken
-      if (this->outputDocs == this->configuration->resumeAfter()) {
-	this->replyToken = new ResumptionToken(gsdlCollect, "", "");
-	this->replyToken->setPosition("", i+2);
-	break;
-      }
-    }
-
-    cinfo.clear(); // Clear for next collection to use (if there is one).
-  }
-}
-
-//--------------------------------------------------------------------------------------------
-// Returns true if at least one document record is found
-void abstractlistaction::output_content_for_all(ostream &output, recptproto *protocol, oaiargs &params)
-{
-  ColInfoResponse_t cinfo;
-  comerror_t        err;
-  text_tarray       collections;
-  text_t            gsdlCollect = "";
-  ResumptionToken   *token = NULL;
-
-  // get a list of the collections available
-  collections = this->configuration->getCollectionsList();
-  //  protocol->get_collection_list(collections, err, output); 
-  
-  if (params["resumptionToken"] != "") {
-    token = new ResumptionToken(params["resumptionToken"]);
-  }
-
-  for(int current_col = 0; current_col < collections.size(); ++current_col){
-    gsdlCollect = collections[current_col];
-
-    // ignore all leading collections before the one that matches the resumptiontoken
-    if (token != NULL &&
-	token->getCollection() != gsdlCollect)
-    { continue;
-    }
-
-    this->output_content_for_col(output, protocol, gsdlCollect, cinfo, err, params);
-
-    // once we've output at least one collection, continue 
-    // outputting all others until the resumption total hits
-    token = NULL; 
-
-    if (this->outputDocs == this->configuration->resumeAfter()) {
-      break;
-    }
-  }
-}
-
-//-------------------------------------------------------------------------------------------------
-// Check that the requested from/until dates don't include a time, as this would be asking for too
-// fine a level of granularity, one that greenstone doesn't support. An OAI error must be thrown.
-/*
-bool abstractlistaction::granularityTooFine(text_t &from, text_t &until)
-{
-  if (from != "" && from.){
-    
-  }
-  
-}
-*/
-//-------------------------------------------------------------------------------------------------
-
-bool abstractlistaction::check_classifier(recptproto *protocol, const text_t &collection,
-					  const text_t &classifier)
-{ text_t topClass;
-  FilterResponse_t response;
-  text_tset        metadata;
-  ofstream         logout("oai.log", ios::app);
-
-  // exclude false children of a top-level classifier immediately...
-  if (!get_info(classifier, collection, "", metadata, false, protocol, response, logout)) {
-    return false;
-  }
-  
-  // now check the top-level parent
-  metadata.insert("supportsmemberof");
-
-  text_t::const_iterator dot = findchar(classifier.begin(), classifier.end(), '.');
-  if (dot != classifier.end()) {
-    topClass = substr(classifier.begin(), dot);
-  }
-  else {
-    topClass = classifier;
-  }
-
-  if (!get_info(topClass, collection, "", metadata, false, protocol, response, logout)) {
-    return false;
-  }
-  
-  if (response.docInfo[0].metadata["supportsmemberof"].values.size() == 0) {
-    return false;
-  }
-
-  if (response.docInfo[0].metadata["supportsmemberof"].values[0] != "true") {
-    return false;
-  }
-
+  if (!get_info(document_OID, collection, "", metadata, false, protocol, response, *this->logout))
+  {
+    return false;
+  }
+
+  text_t last_modified_date;
+  this->getLastModifiedDate(response.docInfo[0], last_modified_date);
+
+  // Check this record is not before the "from" value, if it exists
+  if (from != "" && last_modified_date < from)
+  {
+    // Too early
+    return false;
+  }
+
+  // Check this record is not after the "until" value, if it exists
+  if (until != "" && last_modified_date > until)
+  {
+    // Too late
+    return false;
+  }
+
+  // Just right
   return true;
 }
-
-void abstractlistaction::recurse_set(ostream &output, recptproto *protocol, const text_t &collection, 
-				     const text_t &classifier, oaiargs &params, ResumptionToken *resumptionToken)
-{
-  // metadata for this call
-  FilterResponse_t response;
-  text_tset        metadata;
-  ofstream         logout("oai.log", ios::app);
-  text_t           from = params["from"];
-  text_t           until = params["until"];
-  text_t           metadataPrefix = params["metadataPrefix"];
-  //  ResumptionToken  resumptionToken(params["resumptionToken"]);
-  int              startPos = 0;
-
-  // This is a recursive function, and so just because the current set is empty doesn't mean we necessarily
-  // want to throw a 'noRecordsMatch' error; another set (parent/sibling/child) may have had documents. It
-  // is therefore not enough to check that the response object in the current iteration has no docs - we
-  // must also verify that NO OTHER set has had any documents. This is done with the 'prevDocSeen' flag.
-  // It is set to FALSE initially, but as soon as we see a set that isn't empty, it is set to TRUE. The 
-  // 'noRecordsMatch' error will only be thrown if, after all appropriate sets have been recursed into, 
-  // the 'prevDocSeen' flag is still FALSE. The function returns false if no docs were seen, allowing us to 
-  // throw the noRecordsMatch error.
-  
-  //  bool prevDocSeen = false;
-
-  get_children(classifier, collection, "", metadata, false, protocol, response, *this->logout);
-
-  if (params["resumptionToken"] != "") {
-    // if we're at a resumptionToken
-    if (classifier == resumptionToken->getNode()) {
-      startPos = resumptionToken->getPosition();
-    }
-    else {
-      text_t fullNode = resumptionToken->getNode();
-      text_t::iterator leafIter = fullNode.begin() + classifier.size();
-
-      // if the next character isn't a dot, blow up!
-      if (*leafIter != '.') {
-	// fatal error;
-	exit(1);
-      }
-      
-      // get the first '.' after the current classifier point;
-      text_t::iterator separator = findchar(leafIter + 1, fullNode.end(), '.');
-
-      // now, create a new subpath
-      text_t nextNode = substr(fullNode.begin(), separator);
-
-      // seek forward; TODO: improve performance of this 
-      for (int c = 0; c < response.numDocs; ++c) {
-	if (response.docInfo[c].OID == nextNode) {
-	  startPos = c;
-	  break;
-	}
-      }
-    }
-
-    // We need to subtract one from the startPos value to turn it into an index value
-    startPos--;
-  }
-  
-  for (int c = startPos; c < response.numDocs; ++c) {
-    text_t child = response.docInfo[c].OID;
-    
-    // distinguish classifiers and documents by checking whether OID 
-    // starts with CL or not
-    text_t childHead;
-    text_t::const_iterator start = child.begin();
-    text_t::const_iterator here  = child.begin();
-    here += 2;
-    childHead = substr(start, here);
-    
-    // documents we output now
-    if (childHead != "CL") {
-      // Check that the item with the 0ID 'gsdl_id' has a lastmodified field that fits
-      // within the required date range (if specified)
-      if (this->inDateRange(from, until, collection, child, protocol, output)) {
-	// TODO: check that the document can be disseminated in the required metadataPrefix
-
-	if (this->output_document(output, protocol, collection, child, metadataPrefix)) {
-	  this->prevDocSeen = true;
-	  ++this->outputDocs;
-	}
-      }
-    }
-    // children which are classifiers are recursed
-    else {
-      if (resumptionToken != NULL) {
-	int depth = countchar(classifier.begin(), classifier.end(), '.');
-	resumptionToken->setOffset(depth, c+2);
-      }
-      this->recurse_set(output, protocol, collection, child, params, resumptionToken);
-    }
-
-    if (this->outputDocs == this->configuration->resumeAfter()) {
-      this->replyToken = new ResumptionToken(collection, params["set"], "");
-      this->replyToken->setPosition(classifier, c+2);      
-      break;
-    }
-  }
-}
-
-
-
-
Index: /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.h
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.h	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/abstractlistaction.h	(revision 20590)
@@ -8,18 +8,22 @@
 { 
  public:
-  abstractlistaction(const text_t &name) : oaiaction(name) {this->prevDocSeen = false;}
+  abstractlistaction(const text_t &name) : oaiaction(name) { }
+
   virtual bool output_document(ostream &output, recptproto *protocol, const text_t &collection, 
 			       const text_t &OID, const text_t &metadataPrefix) = 0;
+
   virtual bool output_content(ostream &output, recptproto *protocol, oaiargs &params);
-  virtual void output_content_for_all(ostream &output, recptproto *protocol, oaiargs &params);
-  virtual void output_content_for_col(ostream &output, recptproto *protocol, text_t &gsdlCollect, 
-				      ColInfoResponse_t &cinfo, comerror_t &err, oaiargs &params);
-  virtual void recurse_set(ostream &output, recptproto *protocol, const text_t &collection, 
-			   const text_t &classifier, oaiargs &params, ResumptionToken *resumptionToken);
+
+  virtual bool output_content_for_set(ostream &output, recptproto *protocol, oaiargs &params, text_t collection_name, text_t gsdl_classifier_OID, text_t set_name);
+
+
  protected:
-  bool prevDocSeen;
-  int  outputDocs;
-  ResumptionToken *replyToken;
-  bool check_classifier(recptproto *protocol, const text_t &collection, const text_t &classifier);
+  int output_docs;
+
+  bool check_classifier(recptproto *protocol, const text_t &collection, const text_t &set_name);
+
+  bool in_date_range(ostream &output, recptproto *protocol, oaiargs &params,
+		     text_t& collection, text_t oai_OID, text_t from, text_t until);
 };
+
 #endif
Index: /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.cpp
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.cpp	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.cpp	(revision 20590)
@@ -1,12 +1,8 @@
 #include "listsetsaction.h"
 
-#if defined(GSDL_USE_STL_H)
-#include <fstream.h>
-#else
-#include <fstream>
-#endif
-
+#include "resumptiontoken.h"
 #include "recptprototools.h"
 #include "oaitools.h"
+
 
 bool listsetsaction::validateAction(recptproto *protocol, oaiargs &params)
@@ -56,5 +52,5 @@
     // Check the resumption token is valid
     ResumptionToken token(params["resumptionToken"]);
-    if (true)  // TO DO: Fix this (the token.isValid() function is useless for ListSets)
+    if (token.isValid())
     {
       // Everything is fine, and we don't continue further because this is an exclusive argument
@@ -90,171 +86,151 @@
 bool listsetsaction::output_content(ostream &output, recptproto *protocol, oaiargs &params)
 {
-  // output the total list of classifier points
-
-  // variables required
-  text_t browseOID = "browse";
-  FilterResponse_t response;
-  comerror_t       err;
-  text_tarray &    collections = this->configuration->getCollectionsList();
-  text_tset        metadata;
-  ofstream         logout("oai.log", ios::app);
-  
-  // get a list of the collections available
-  //  protocol->get_collection_list(collections, err, output); 
-  if (collections.size() == 0) {
-    logout << "Found *no* OAI collections - check main.cfg for oaicollection items and read the OAI documentation.\n";
-  }
-
-  // check resumption token
-  int startSet = 0;
-  if (params["resumptionToken"] != "") {
-    ResumptionToken token(params["resumptionToken"]);
-    startSet = token.getPosition() - 1; // first document is said to be 1..
-  }
-  this->replyToken = NULL;
-
-  this->setNumber = 0;
+  // Reset variables
   this->setsOutput = 0;
-  for(int current_col = 0; current_col < collections.size(); ++current_col) {
-    // output the collection as a set, first, then its children
-    text_t gsdlCollect = collections[current_col];
-
+
+  text_t collection = "";
+
+  // Process the resumptionToken if there is one
+  if (params["resumptionToken"] != "")
+  {
+    ResumptionToken resumption_token(params["resumptionToken"]);
+    collection = resumption_token.getSet();
+  }
+
+  // Get a list of the collections available
+  text_tarray& collections = this->configuration->getCollectionsList();
+  if (collections.size() == 0)
+  {
+    return false;
+  }
+
+  // Find the starting collection
+  text_tarray::iterator collection_iterator = collections.begin();
+  while (collection_iterator != collections.end())
+  {
+    if (collection == "" || collection == *collection_iterator)
+    {
+      break;
+    }
+
+    collection_iterator++;
+  }
+
+  // Now loop through the remaining collections
+  while (collection_iterator != collections.end())
+  {
+    collection = (*collection_iterator);
+
+    // If we've output the desired number of records return a resumptionToken and we're done
     if (this->setsOutput == this->configuration->resumeAfter())
     {
-      this->replyToken = new ResumptionToken("", "", "");
-      this->replyToken->setPosition("", this->setNumber+1);
-      break;
-    }
-
-    if (this->setNumber >= startSet)
-    {
+      // Get the buildDate from the build.cfg file
+      ColInfoResponse_t cinfo;
+      comerror_t err;
+      protocol->get_collectinfo(collection, cinfo, err, cerr);
+
+      ResumptionToken resumption_token(cinfo.buildDate, collection, "", "", "", "");
+
+      // Don't add any whitespace around the resumption token as it can confuse harvesters/validators
+      output << "  <resumptionToken>" << resumption_token.getResumptionTokenString() << "</resumptionToken>" << endl;
+      return true;
+    }
+
+    // If output_content_for_col() returns false a resumption token has been output, so it's time to stop
+    if (output_content_for_col(output, protocol, params, collection) == false)
+    {
+      return true;
+    }
+
+    collection_iterator++;
+  }
+
+  return true;
+}
+
+
+bool listsetsaction::output_content_for_col(ostream &output, recptproto *protocol, oaiargs &params, text_t collection)
+{
+  text_t position = "";
+
+  // Process the resumptionToken if there is one
+  if (params["resumptionToken"] != "")
+  {
+    ResumptionToken resumption_token(params["resumptionToken"]);
+    position = resumption_token.getPosition();
+  }
+
+  // Get the list of sets in this collection
+  // Collections should not contain too many sets otherwise this will use a lot of time and memory
+  text_tset metadata;  // Must be empty for efficiency
+  FilterResponse_t sets_response;
+  get_children("browse", collection, "", metadata, false, protocol, sets_response, *this->logout);
+
+  // Find the starting position, if necessary
+  ResultDocInfo_tarray::iterator set_iterator = sets_response.docInfo.begin();
+  if (this->setsOutput == 0)
+  {
+    while (set_iterator != sets_response.docInfo.end())
+    {
+      if (position == "" || position == (*set_iterator).OID)
+      {
+	break;
+      }
+
+      set_iterator++;
+    }
+  }
+
+  // Output the collection as a set
+  if (position == "")
+  {
+    output << "  <set>" << endl;
+    output << "    <setSpec>" << collection << "</setSpec>" << endl;
+    output << "    <setName>" << collection << "</setName>" << endl;
+    output << "  </set>" << endl;
+    this->setsOutput++;
+  }
+
+  // Now loop through displaying the next matching records
+  while (set_iterator != sets_response.docInfo.end())
+  {
+    text_t set = (*set_iterator).OID;
+
+    // Only classifiers with supportsmemberof become OAI sets, for reasons I don't really understand
+    text_tset set_metadata;
+    set_metadata.insert("supportsmemberof");
+    set_metadata.insert("Title");
+    FilterResponse_t set_response;
+    get_info(set, collection, "", set_metadata, false, protocol, set_response, *this->logout);
+
+    if (set_response.docInfo[0].metadata["supportsmemberof"].values.size() > 0 && set_response.docInfo[0].metadata["supportsmemberof"].values[0] == "true")
+    {
+      // If we've output the desired number of records return a resumptionToken and we're done
+      if (this->setsOutput == this->configuration->resumeAfter())
+      {
+	// Get the buildDate from the build.cfg file
+	ColInfoResponse_t cinfo;
+	comerror_t err;
+	protocol->get_collectinfo(collection, cinfo, err, cerr);
+
+	ResumptionToken resumption_token(cinfo.buildDate, collection, "", "", "", set);
+
+	// Don't add any whitespace around the resumption token as it can confuse harvesters/validators
+	output << "  <resumptionToken>" << resumption_token.getResumptionTokenString() << "</resumptionToken>" << endl;
+	return false;
+      }
+
+      // Otherwise output this set and increment the count
+      text_t set_title = set_response.docInfo[0].metadata["Title"].values[0];
       output << "  <set>" << endl;
-      output << "    <setSpec>" << gsdlCollect << "</setSpec>" << endl;;
-      output << "    <setName>" << gsdlCollect << "</setName>" << endl;
+      output << "    <setSpec>" << collection << ":" << set << "</setSpec>" << endl;
+      output << "    <setName>" << collection << ":" << set_title << "</setName>" << endl;
       output << "  </set>" << endl;
       this->setsOutput++;
     }
-    setNumber++;
-
-    // get all the children of the (relevant) classifier data structures
-    get_children(browseOID, gsdlCollect, "", metadata, false, protocol, response, logout);
-    // and send them to the "recurse_content" list
-    for (int c = 0; c < response.numDocs; ++c) {
-      this->recurse_content(output, protocol, gsdlCollect, response.docInfo[c].OID, gsdlCollect, startSet);
-    }
-  }
-
-  // do a resumption token if required; errors cancel a token...
-  if (this->replyToken != NULL && this->errorType == "") {
-    // Don't add any whitespace around the resumption token as it can confuse harvesters/validators
-    output << "  <resumptionToken>" << this->replyToken->getToken() << "</resumptionToken>" << endl;
+
+    set_iterator++;
   }
 
   return true;
 }
-
-void listsetsaction::recurse_content(ostream &output, recptproto *protocol, text_t &collection, 
-				     const text_t &classifier, text_t setHierarchy, int startSet)
-{
-  // metadata for this call
-  FilterResponse_t response;
-  text_tset        metadata;
-  ofstream         logout("oai.log", ios::app);
-
-  if (this->setsOutput == this->configuration->resumeAfter())
-  {
-    this->replyToken = new ResumptionToken("", "", "");
-    this->replyToken->setPosition("", this->setNumber+1);
-    return;
-  }
-
-  metadata.insert("contains");
-  metadata.insert("Title");
-  metadata.insert("supportsmemberof");
-
-  // get the document information
-  if (!get_info(classifier, collection, "", metadata, false, protocol, response, logout)) {
-    //cerr << "recurse content: Bad identifier or protocol " << classifier << endl;
-    return;
-  }
-
-  // check for top-level classifiers, check if the set name includes a '.'; if 
-  // not, it is a top-level classifier: check for memberof support.  Those without
-  // memberof support will not be supported on OAI
-  if (findchar(classifier.begin(), classifier.end(), '.') == classifier.end()) {
-    if (response.docInfo[0].metadata["supportsmemberof"].values.size() > 0) {
-      text_t memberOf = response.docInfo[0].metadata["supportsmemberof"].values[0];
-      if (memberOf != "true") {
-	return;
-      }
-    }
-    else {
-      return;
-    }
-  }
-
-  MetadataInfo_tmap::iterator here = response.docInfo[0].metadata.begin();
-  MetadataInfo_tmap::iterator end  = response.docInfo[0].metadata.end();
-  text_t title;
-
-  while (here != end)
-  {
-    // Each set should only have one title - hence we only output one title here
-    // (it is a set title, not a collection)
-    if (here->first == "Title" && here->second.values.size() > 0) {	
-      title = here->second.values[0];
-    }
-
-    ++here;
-  }
-
-  // output the xml for this set; use the classifier id for the name
-  // if the title is blank
-  // curSet holds the colon-separated sequence of parent sets of the current set
-  text_t curSet;
-  if (this->setNumber >= startSet)
-  {
-    output << "  <set>" << endl;
-    text_t oai_classifier = classifier;
-    oaiclassifier::toOAI(collection, oai_classifier);
-    output << "    <setSpec>" << oai_classifier << "</setSpec>" << endl;
-    output << "    <setName>";
-    if (!title.empty()) {
-      curSet = setHierarchy + ":" + title;
-    }
-    else {
-      curSet = classifier; // Pretty much never gets here (shouldn't, at least)
-    }
-    output << curSet;
-    output << "</setName>" << endl;
-    output << "  </set>" << endl;
-    this->setsOutput++;
-  }
-  this->setNumber++;
-
-  // get the children of this classifier and iterate them
-  get_children(classifier, collection, "", metadata, false, protocol, response, logout);
-  for (int c = 0; c < response.numDocs; ++c) {
-    text_t child = response.docInfo[c].OID;
-
-    if (child == classifier)
-      continue;
-
-    // check for non classifier items and exclude them
-    text_t childHead;
-    text_t::const_iterator start = child.begin();
-    text_t::const_iterator here  = child.begin();
-    here += 2;
-    childHead = substr(start, here);
-
-    if (childHead != "CL")
-      continue;
-
-    // Recurse for "proper" classifier children. Pass curSet, the colon-separated list of 
-    // parent sets. curSet is pass-by-value, so that as we step out of recursion we remember
-    // old set hierarchies.
-    this->recurse_content(output, protocol, collection, child, curSet, startSet);
-  }
-  
-  return;
-}
Index: /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.h
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.h	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/listsetsaction.h	(revision 20590)
@@ -6,12 +6,12 @@
  public:
   listsetsaction() : oaiaction("ListSets") { };
+
   virtual bool validateAction(recptproto *protocol, oaiargs &params);
 
  protected:
-  int  setNumber;
-  int  setsOutput;
-  ResumptionToken *replyToken;
+  int setsOutput;
+
   bool output_content(ostream &output, recptproto *protocol, oaiargs &params);
-  void recurse_content(ostream &output, recptproto *protocol, text_t &collection, const text_t &classifier, 
-		       text_t setHierarchy, int startSet);
+
+  bool output_content_for_col(ostream &output, recptproto *protocol, oaiargs &params, text_t collection);
 };
Index: /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.cpp
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.cpp	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.cpp	(revision 20590)
@@ -1,157 +1,72 @@
 #include "resumptiontoken.h"
-#include "oaitools.h"
 
-/**
- *  Generate an initial resumption token from some basic details.
- *
- *  TODO: add optional argument to set the server name.
- */
-ResumptionToken::ResumptionToken(const text_t &collection, const text_t &node,
-				 const text_t &buildDate)
-{ this->collection = collection;
-  this->browseNode = node;
-  this->buildDate  = buildDate;
-  this->startItem  = 0;
+
+ResumptionToken::ResumptionToken(const text_t &build_date, const text_t &set, const text_t &metadata_prefix,
+				 const text_t &from, const text_t &until, const text_t &position)
+{
+  this->build_date = build_date;
+  this->set = set;
+  this->metadata_prefix = metadata_prefix;
+  this->from = from;
+  this->until = until;
+  this->position = position;
+  this->valid = true;
 }
 
-/**
- *  Generate a resumption token from a URN-style format.
- *
- *  See getToken() for details of the format.
- *
- *  TODO: support inclusion of an optional server name.
- */
-ResumptionToken::ResumptionToken(const text_t &URN)
-{ text_t::const_iterator first = URN.begin();
-  text_t::const_iterator last = URN.end(); 
-  text_t::const_iterator second;
 
-  this->collection = "";
-  this->browseNode = "";
-  this->startItem  = -1;
-  
-  text_t::const_iterator here = findchar(first, last, ':');
-  if (here == first) {
-    return;
+ResumptionToken::ResumptionToken(const text_t &resumption_token_string)
+{
+  this->build_date = "";
+  this->set = "";
+  this->metadata_prefix = "";
+  this->from = "";
+  this->until = "";
+  this->position = "";
+
+  // This uses custom code into of the text_t splitchar() function because that is buggy
+  text_tarray resumption_token_string_parts;
+  text_t resumption_token_string_part;
+  text_t::const_iterator resumption_token_string_iterator = resumption_token_string.begin();
+  while (resumption_token_string_iterator != resumption_token_string.end())
+  {
+    if (*resumption_token_string_iterator == ',')
+    {
+      resumption_token_string_parts.push_back(resumption_token_string_part);
+      resumption_token_string_part.clear();
+    }
+    else
+    {
+      resumption_token_string_part.push_back(*resumption_token_string_iterator);
+    }
+
+    resumption_token_string_iterator++;
   }
-  
-  text_t oainamespace = substr(first, here);
-  if (oainamespace != "gsdloai") {
+  resumption_token_string_parts.push_back(resumption_token_string_part);
+
+  if (resumption_token_string_parts.size() != 6)
+  {
+    // The resumption token is invalid -- there should be exactly 6 parts
+    this->valid = false;
     return;
   }
 
-  // increment past the first colon to get the location
-  first = ++here;  
-
-  // get the collection, browseNode
-  here = findchar(first, last, ',');
-  if (here == last) {
-    return;
-  }
-
-  second = findchar(first, here,'.');
-  this->collection = substr(first, second);
-
-  //  cerr << "Collection " << this->collection << endl;
-
-  if (second != here) {
-    // get past the '.'
-    ++second;
-    this->browseNode = substr(second, here);
-  }
-  else {
-    first = here;
-  }
-  // get past the ','
-  first = ++here;
-
-  // find the second ',' to delimit the position stack
-  second = findchar(first, last, ',');
-
-  // if not found, then get build and start item
-  if (second != first) {
-    // extract list and step past it
-    text_t offsetList = substr(first, second);
-    first = ++second;
-    
-    do {
-      second = findchar(offsetList.begin(), offsetList.end(), '.');
-      if (second == offsetList.end()) 
-	break;
-      
-      // extract and push the next position
-      text_t thisPos = substr(offsetList.begin(), second);
-      this->browsePosition.push_back(thisPos.getint());
-
-      // pop the position from the list
-      offsetList = substr(++second, offsetList.end());
-    } while (true);    
-    this->browsePosition.push_back(offsetList.getint());
-  }
-  else {
-    first ++;
-  }
-
-  // now find the build date marker
-  here = findchar(first, last, '-');
-  if (here == first) {
-    this->startItem = substr(first, last).getint();
-  }
-  else {
-    this->startItem = substr(first, here).getint();
-    this->buildDate = substr(++here, last); 
-  }
+  this->build_date = resumption_token_string_parts[0];
+  this->set = resumption_token_string_parts[1];
+  this->metadata_prefix = resumption_token_string_parts[2];
+  this->from = resumption_token_string_parts[3];
+  this->until = resumption_token_string_parts[4];
+  this->position = resumption_token_string_parts[5];
+  this->valid = true;
 }
 
-/**
- *  Get a resumption token in text_t format.
- * 
- *  Resumption tokens are in the format:
- *
- *    gsdloai:<serverName>:collectionname.browseNode,startItem-BuildDate
- *
- *  The resumption token format does not currently implement the use of
- *  the optional <serverName> item; it is taken to default to the name of
- *  the receiving server.
- *
- *  TODO: add server identity as an optional argument; also change
- *        ResumptionToken(text_t &) accordingly.
- */
-text_t ResumptionToken::getToken()
-{ text_t reply = "gsdloai:";
-  reply = reply + this->collection;
-  if (this->browseNode != "") {
-    reply = reply + "." + this->browseNode;
-  }
-  reply = reply + ",";
-  for (int i = 0; i < this->browsePosition.size(); i++) {
-    if (i != 0) {
-      reply.append(".");
-    }
-    reply.appendint(i);
-  }
-  reply = reply + ",";
-  reply.append(this->startItem);
-  reply = reply + "-" + buildDate;
 
-  return reply;
+text_t ResumptionToken::getResumptionTokenString()
+{
+  return this->build_date + "," + this->set + "," + this->metadata_prefix + "," + this->from + "," + this->until + "," + this->position;
 }
 
-/**
- *  Update the position of an existing resumption token
- */
-void ResumptionToken::setPosition(const text_t &node, int startItem)
-{ this->browseNode = node;
-  this->startItem  = startItem; 
+
+bool ResumptionToken::isValid()
+{
+  return this->valid;
 }
-
-/**
- *  Check if the resumption token is valid - only a very primitive
- *  check is done here; one ought to check for an existing collection
- *  and valid browse Node, build date and startItem
- *
- *  TODO: implement improved validation checking.
- */
-bool ResumptionToken::isValid()
-{ return this->collection != "";
-}
Index: /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.h
===================================================================
--- /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.h	(revision 20589)
+++ /gsdl/trunk/runtime-src/src/oaiservr/resumptiontoken.h	(revision 20590)
@@ -2,41 +2,35 @@
 #define _RESUMPTIONTOKEN_H_
 
-#include <vector>
-
 #include "text_t.h"
 
-// use the standard namespace
-#if !defined (GSDL_NAMESPACE_BROKEN)
-#if defined(GSDL_USE_OBJECTSPACE)
-using namespace ospace::std;
-#else
-using namespace std;
-#endif
-#endif
 
 class ResumptionToken
-{ private:
-  text_t  collection;
-  text_t  browseNode;
-  vector<int> browsePosition;
-  int     startItem;
-  text_t  buildDate;
-  // TODO: add a server name to the variables list; see getToken in resumptionToken.cpp
+{
+ private:
+  text_t build_date;
+  text_t set;
+  text_t metadata_prefix;
+  text_t from;
+  text_t until;
+  text_t position;
+
+  bool valid;
 
  public:
-  ResumptionToken(const text_t &collection, const text_t &rootNode, const text_t &buildDate);
-  ResumptionToken(const text_t &URN);
-  text_t  getToken();
+  ResumptionToken(const text_t &build_date, const text_t &set, const text_t &metadata_prefix,
+		  const text_t &from, const text_t &until, const text_t &position);
+  ResumptionToken(const text_t &resumption_token_string);
 
-  void setPosition(const text_t &node, int startItem);
+  text_t getBuildDate() { return build_date; }
+  text_t getSet() { return set; }
+  text_t getMetadataPrefix() { return metadata_prefix; }
+  text_t getFrom() { return from; }
+  text_t getUntil() { return until; }
+  text_t getPosition() { return position; }
 
-  text_t  getCollection() { return collection; }
-  text_t  getNode() { return browseNode; }
-  int     getPosition() { return startItem; }
-  int     getOffsetDepth() { return this->browsePosition.size(); }
-  void    setOffset(int depth, int position) { this->browsePosition[depth] = position; }
-  int getOffset(int offset) { return this->browsePosition[offset]; }
+  text_t getResumptionTokenString();
   bool isValid();
 };
 
+
 #endif
