state-server: Initial implementation of /info/ RPC calls

Nothing is published yet, but data is successfully passed to the state server

+ usages of scheduler replaced with executor

Change-Id: I9da9182edc4efe8e896e2452ef4f9f0d98a03da1
diff --git a/src/action-log.cc b/src/action-log.cc
index 84c2779..14b30ca 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -37,6 +37,7 @@
                                                                         \n\
     action      CHAR(1) NOT NULL, /* 0 for \"update\", 1 for \"delete\". */ \n\
     filename    TEXT NOT NULL,                                          \n\
+    directory   TEXT,                                                   \n\
                                                                         \n\
     version     INTEGER NOT NULL,                                       \n\
     action_timestamp TIMESTAMP NOT NULL,                                \n\
@@ -258,6 +259,17 @@
 
   sqlite3_finalize (stmt);
 
+  // I had a problem including directory_name assignment as part of the initial insert.
+  sqlite3_prepare_v2 (m_db, "UPDATE ActionLog SET directory=directory_name(filename) WHERE device_name=? AND seq_no=?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  sqlite3_bind_blob  (stmt, 1, device_name->buf (), device_name->length (), SQLITE_STATIC);
+  sqlite3_bind_int64 (stmt, 2, seq_no);
+  sqlite3_step (stmt);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
   sqlite3_exec (m_db, "END TRANSACTION;", 0,0,0);
 
   // set complete for local file
@@ -362,6 +374,17 @@
 
   sqlite3_finalize (stmt);
 
+  // I had a problem including directory_name assignment as part of the initial insert.
+  sqlite3_prepare_v2 (m_db, "UPDATE ActionLog SET directory=directory_name(filename) WHERE device_name=? AND seq_no=?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  sqlite3_bind_blob  (stmt, 1, device_name->buf (), device_name->length (), SQLITE_STATIC);
+  sqlite3_bind_int64 (stmt, 2, seq_no);
+  sqlite3_step (stmt);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
   sqlite3_exec (m_db, "END TRANSACTION;", 0,0,0);
 
   return item;
@@ -554,6 +577,17 @@
 
   sqlite3_finalize (stmt);
 
+  // I had a problem including directory_name assignment as part of the initial insert.
+  sqlite3_prepare_v2 (m_db, "UPDATE ActionLog SET directory=directory_name(filename) WHERE device_name=? AND seq_no=?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  sqlite3_bind_blob  (stmt, 1, device_name->buf (), device_name->length (), SQLITE_STATIC);
+  sqlite3_bind_int64 (stmt, 2, seqno);
+  sqlite3_step (stmt);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
   return action;
 }
 
@@ -609,6 +643,82 @@
   return retval;
 }
 
+
+void
+ActionLog::LookupActionsInFolderRecursively (const boost::function<void (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &)> &visitor,
+                                             const std::string &folder, int offset/*=0*/, int limit/*=-1*/)
+{
+  _LOG_DEBUG ("LookupActionsInFolderRecursively: [" << folder << "]");
+
+  sqlite3_stmt *stmt;
+  if (folder != "")
+    {
+      /// @todo Do something to improve efficiency of this query. Right now it is basically scanning the whole database
+
+      sqlite3_prepare_v2 (m_db,
+                          "SELECT device_name,seq_no,action,filename,directory,version,action_timestamp, "
+                          "       file_hash,file_mtime,file_chmod,file_seg_num, "
+                          "       parent_device_name,parent_seq_no "
+                          "   FROM ActionLog "
+                          "   WHERE is_prefix (?, directory)=1 "
+                          "   ORDER BY action_timestamp DESC "
+                          "   LIMIT ? OFFSET ?", -1, &stmt, 0); // there is a small ambiguity with is_prefix matching, but should be ok for now
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+      sqlite3_bind_text (stmt, 1, folder.c_str (), folder.size (), SQLITE_STATIC);
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+      sqlite3_bind_int (stmt, 2, limit);
+      sqlite3_bind_int (stmt, 3, offset);
+    }
+  else
+    {
+      sqlite3_prepare_v2 (m_db,
+                          "SELECT device_name,seq_no,action,filename,directory,version,action_timestamp, "
+                          "       file_hash,file_mtime,file_chmod,file_seg_num, "
+                          "       parent_device_name,parent_seq_no "
+                          "   FROM ActionLog "
+                          "   ORDER BY action_timestamp DESC "
+                          "   LIMIT ? OFFSET ?", -1, &stmt, 0);
+      sqlite3_bind_int (stmt, 1, limit);
+      sqlite3_bind_int (stmt, 2, offset);
+    }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  while (sqlite3_step (stmt) == SQLITE_ROW)
+    {
+      ActionItem action;
+
+      Ccnx::Name device_name (sqlite3_column_blob  (stmt, 0), sqlite3_column_bytes (stmt, 0));
+      sqlite3_int64 seq_no =  sqlite3_column_int64 (stmt, 1);
+      action.set_action      (static_cast<ActionItem_ActionType> (sqlite3_column_int   (stmt, 2)));
+      action.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 3)), sqlite3_column_bytes (stmt, 3));
+      std::string directory  (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 4)), sqlite3_column_bytes (stmt, 4));
+      if (action.action () == 0)
+        {
+          action.set_version     (sqlite3_column_int64 (stmt, 5));
+          action.set_timestamp   (sqlite3_column_int64 (stmt, 6));
+          action.set_file_hash   (sqlite3_column_blob  (stmt, 7), sqlite3_column_bytes (stmt, 7));
+          action.set_mtime       (sqlite3_column_int   (stmt, 8));
+          action.set_mode        (sqlite3_column_int   (stmt, 9));
+          action.set_seg_num     (sqlite3_column_int64 (stmt, 10));
+        }
+      if (sqlite3_column_bytes (stmt, 11) > 0)
+        {
+          action.set_parent_device_name (sqlite3_column_blob  (stmt, 11), sqlite3_column_bytes (stmt, 11));
+          action.set_parent_seq_no      (sqlite3_column_int64 (stmt, 12));
+        }
+
+      visitor (device_name, seq_no, action);
+    }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////////
diff --git a/src/action-log.h b/src/action-log.h
index fe96f10..cfcaf7a 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -102,6 +102,13 @@
   FileItemPtr
   LookupAction (const std::string &filename, sqlite3_int64 version, const Hash &filehash);
 
+  /**
+   * @brief Lookup up to [limit] actions starting [offset] in decreasing order (by timestamp) and calling visitor(device_name,seqno,action) for each action
+   */
+  void
+  LookupActionsInFolderRecursively (const boost::function<void (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &)> &visitor,
+                                    const std::string &folder, int offset=0, int limit=-1);
+
   //
   inline FileStatePtr
   GetFileState ();
diff --git a/src/db-helper.cc b/src/db-helper.cc
index 33b0136..538e137 100644
--- a/src/db-helper.cc
+++ b/src/db-helper.cc
@@ -61,6 +61,13 @@
                              << errmsg_info_str ("Cannot create function ``is_prefix''"));
     }
 
+  res = sqlite3_create_function (m_db, "directory_name", -1, SQLITE_ANY, 0, DbHelper::directory_name_xFun, 0, 0);
+  if (res != SQLITE_OK)
+    {
+      BOOST_THROW_EXCEPTION (Error::Db ()
+                             << errmsg_info_str ("Cannot create function ``directory_name''"));
+    }
+
   // Alex: determine if tables initialized. if not, initialize... not sure what is the best way to go...
   // for now, just attempt to create everything
   sqlite3_exec (m_db, INIT_DATABASE.c_str (), NULL, NULL, NULL);
@@ -173,3 +180,31 @@
     }
 }
 
+void
+DbHelper::directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+  if (argc != 1)
+    {
+      sqlite3_result_error (context, "``directory_name'' expects 1 text argument", -1);
+      sqlite3_result_null (context);
+      return;
+    }
+
+  if (sqlite3_value_bytes (argv[0]) == 0)
+    {
+      sqlite3_result_null (context);
+      return;
+    }
+
+  boost::filesystem::path filePath (std::string (reinterpret_cast<const char*> (sqlite3_value_text (argv[0])), sqlite3_value_bytes (argv[0])));
+  std::string dirPath = filePath.parent_path ().generic_string ();
+  // _LOG_DEBUG ("directory_name FUN: " << dirPath);
+  if (dirPath.size () == 0)
+    {
+      sqlite3_result_null (context);
+    }
+  else
+    {
+      sqlite3_result_text (context, dirPath.c_str (), dirPath.size (), SQLITE_TRANSIENT);
+    }
+}
diff --git a/src/db-helper.h b/src/db-helper.h
index c15621c..0bc1a56 100644
--- a/src/db-helper.h
+++ b/src/db-helper.h
@@ -48,6 +48,9 @@
   static void
   is_prefix_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
 
+  static void
+  directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
+
 protected:
   sqlite3 *m_db;
 };
diff --git a/src/file-state.cc b/src/file-state.cc
index fd982d9..63647c4 100644
--- a/src/file-state.cc
+++ b/src/file-state.cc
@@ -21,6 +21,7 @@
 
 #include "file-state.h"
 #include "logging.h"
+#include <boost/bind.hpp>
 
 INIT_LOGGER ("FileState");
 
@@ -55,15 +56,6 @@
 {
   sqlite3_exec (m_db, INIT_DATABASE.c_str (), NULL, NULL, NULL);
   _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
-
-  int res = sqlite3_create_function (m_db, "directory_name", -1, SQLITE_ANY, 0,
-                                     FileState::directory_name_xFun,
-                                     0, 0);
-  if (res != SQLITE_OK)
-    {
-      BOOST_THROW_EXCEPTION (Error::Db ()
-                             << errmsg_info_str ("Cannot create function ``directory_name''"));
-    }
 }
 
 FileState::~FileState ()
@@ -242,21 +234,23 @@
   return retval;
 }
 
-
-FileItemsPtr
-FileState::LookupFilesInFolder (const std::string &folder)
+void
+FileState::LookupFilesInFolder (const boost::function<void (const FileItem&)> &visitor, const std::string &folder, int offset/*=0*/, int limit/*=-1*/)
 {
   sqlite3_stmt *stmt;
   sqlite3_prepare_v2 (m_db,
                       "SELECT filename,device_name,seq_no,file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num,is_complete "
                       "   FROM FileState "
-                      "   WHERE type = 0 AND directory = ?", -1, &stmt, 0);
+                      "   WHERE type = 0 AND directory = ?"
+                      "   LIMIT ? OFFSET ?", -1, &stmt, 0);
   if (folder.size () == 0)
     sqlite3_bind_null (stmt, 1);
   else
     sqlite3_bind_text (stmt, 1, folder.c_str (), folder.size (), SQLITE_STATIC);
 
-  FileItemsPtr retval = make_shared<FileItems> ();
+  sqlite3_bind_int (stmt, 2, limit);
+  sqlite3_bind_int (stmt, 3, offset);
+
   while (sqlite3_step (stmt) == SQLITE_ROW)
     {
       FileItem file;
@@ -269,58 +263,59 @@
       file.set_seg_num     (sqlite3_column_int64 (stmt, 6));
       file.set_is_complete (sqlite3_column_int   (stmt, 7));
 
-      retval->push_back (file);
+      visitor (file);
     }
 
   _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
 
   sqlite3_finalize (stmt);
+}
+
+FileItemsPtr
+FileState::LookupFilesInFolder (const std::string &folder, int offset/*=0*/, int limit/*=-1*/)
+{
+  FileItemsPtr retval = make_shared<FileItems> ();
+  LookupFilesInFolder (boost::bind (&FileItems::push_back, retval.get (), _1), folder, offset, limit);
 
   return retval;
 }
 
 FileItemsPtr
-FileState::LookupFilesInFolderRecursively (const std::string &folder)
+FileState::LookupFilesInFolderRecursively (const boost::function<void (const FileItem&)> &visitor, const std::string &folder, int offset/*=0*/, int limit/*=-1*/)
 {
   _LOG_DEBUG ("LookupFilesInFolderRecursively: [" << folder << "]");
 
   sqlite3_stmt *stmt;
   if (folder != "")
     {
+      /// @todo Do something to improve efficiency of this query. Right now it is basically scanning the whole database
+
       sqlite3_prepare_v2 (m_db,
                           "SELECT filename,device_name,seq_no,file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num,is_complete "
                           "   FROM FileState "
-                          "   WHERE type = 0 AND (directory = ? OR directory LIKE ?)", -1, &stmt, 0);
+                          "   WHERE type = 0 AND is_prefix (?, directory)=1 "
+                          "   LIMIT ? OFFSET ?", -1, &stmt, 0); // there is a small ambiguity with is_prefix matching, but should be ok for now
       _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
 
       sqlite3_bind_text (stmt, 1, folder.c_str (), folder.size (), SQLITE_STATIC);
       _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
 
-      ostringstream escapedFolder;
-      for (string::const_iterator ch = folder.begin (); ch != folder.end (); ch ++)
-        {
-          if (*ch == '%')
-            escapedFolder << "\\%";
-          else
-            escapedFolder << *ch;
-        }
-      escapedFolder << "/" << "%";
-      string escapedFolderStr = escapedFolder.str ();
-
-      sqlite3_bind_text (stmt, 2, escapedFolderStr.c_str (), escapedFolderStr.size (), SQLITE_STATIC);
-      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+      sqlite3_bind_int (stmt, 2, limit);
+      sqlite3_bind_int (stmt, 3, offset);
     }
   else
     {
       sqlite3_prepare_v2 (m_db,
                           "SELECT filename,device_name,seq_no,file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num,is_complete "
                           "   FROM FileState "
-                          "   WHERE type = 0", -1, &stmt, 0);
+                          "   WHERE type = 0"
+                          "   LIMIT ? OFFSET ?", -1, &stmt, 0);
+      sqlite3_bind_int (stmt, 1, limit);
+      sqlite3_bind_int (stmt, 2, offset);
     }
 
   _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
 
-  FileItemsPtr retval = make_shared<FileItems> ();
   while (sqlite3_step (stmt) == SQLITE_ROW)
     {
       FileItem file;
@@ -333,41 +328,19 @@
       file.set_seg_num     (sqlite3_column_int64 (stmt, 6));
       file.set_is_complete (sqlite3_column_int   (stmt, 7));
 
-      retval->push_back (file);
+      visitor (file);
     }
 
   _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
 
   sqlite3_finalize (stmt);
-
-  return retval;
 }
 
-void
-FileState::directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv)
+FileItemsPtr
+FileState::LookupFilesInFolderRecursively (const std::string &folder, int offset/*=0*/, int limit/*=-1*/)
 {
-  if (argc != 1)
-    {
-      sqlite3_result_error (context, "``directory_name'' expects 1 text argument", -1);
-      sqlite3_result_null (context);
-      return;
-    }
+  FileItemsPtr retval = make_shared<FileItems> ();
+  LookupFilesInFolder (boost::bind (&FileItems::push_back, retval.get (), _1), folder, offset, limit);
 
-  if (sqlite3_value_bytes (argv[0]) == 0)
-    {
-      sqlite3_result_null (context);
-      return;
-    }
-
-  filesystem::path filePath (string (reinterpret_cast<const char*> (sqlite3_value_text (argv[0])), sqlite3_value_bytes (argv[0])));
-  string dirPath = filePath.parent_path ().generic_string ();
-  // _LOG_DEBUG ("directory_name FUN: " << dirPath);
-  if (dirPath.size () == 0)
-    {
-      sqlite3_result_null (context);
-    }
-  else
-    {
-      sqlite3_result_text (context, dirPath.c_str (), dirPath.size (), SQLITE_TRANSIENT);
-    }
+  return retval;
 }
diff --git a/src/file-state.h b/src/file-state.h
index 91bb3d2..6c4ace2 100644
--- a/src/file-state.h
+++ b/src/file-state.h
@@ -78,20 +78,28 @@
   LookupFilesForHash (const Hash &hash);
 
   /**
-   * @brief Lookup all files in the specified folder
+   * @brief Lookup all files in the specified folder and call visitor(file) for each file
    */
-  FileItemsPtr
-  LookupFilesInFolder (const std::string &folder);
+  void
+  LookupFilesInFolder (const boost::function<void (const FileItem&)> &visitor, const std::string &folder, int offset=0, int limit=-1);
 
   /**
-   * @brief Recursively lookup all files in the specified folder
+   * @brief Lookup all files in the specified folder (wrapper around the overloaded version)
    */
   FileItemsPtr
-  LookupFilesInFolderRecursively (const std::string &folder);
+  LookupFilesInFolder (const std::string &folder, int offset=0, int limit=-1);
 
-private:
-  static void
-  directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
+  /**
+   * @brief Recursively lookup all files in the specified folder and call visitor(file) for each file
+   */
+  FileItemsPtr
+  LookupFilesInFolderRecursively (const boost::function<void (const FileItem&)> &visitor, const std::string &folder, int offset=0, int limit=-1);
+
+  /**
+   * @brief Recursively lookup all files in the specified folder (wrapper around the overloaded version)
+   */
+  FileItemsPtr
+  LookupFilesInFolderRecursively (const std::string &folder, int offset=0, int limit=-1);
 };
 
 typedef boost::shared_ptr<FileState> FileStatePtr;
diff --git a/src/state-server.cc b/src/state-server.cc
index 98830d0..6f73950 100644
--- a/src/state-server.cc
+++ b/src/state-server.cc
@@ -45,7 +45,7 @@
   , m_objectManager (objectManager)
   , m_rootDir(rootDir)
   , m_freshness(freshness)
-  , m_scheduler (new Scheduler())
+  , m_executor (1)
   , m_userName (userName)
   , m_sharedFolderName (sharedFolderName)
   , m_appName (appName)
@@ -53,19 +53,19 @@
   // may be later /localhost should be replaced with /%C1.M.S.localhost
 
   // <PREFIX_INFO> = /localhost/<user's-device-name>/"chronoshare"/"info"
-  m_PREFIX_INFO = Name ("/localhost")(m_userName)("chronoshare")("info");
+  m_PREFIX_INFO = Name ("/localhost")(m_userName)("chronoshare")(m_sharedFolderName)("info");
 
   // <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd"
-  m_PREFIX_CMD = Name ("/localhost")(m_userName)("chronoshare")("cmd");
+  m_PREFIX_CMD = Name ("/localhost")(m_userName)("chronoshare")(m_sharedFolderName)("cmd");
 
-  m_scheduler->start ();
+  m_executor.start ();
 
   registerPrefixes ();
 }
 
 StateServer::~StateServer()
 {
-  m_scheduler->shutdown ();
+  m_executor.shutdown ();
 
   deregisterPrefixes ();
 }
@@ -77,10 +77,10 @@
   // will be extended to support all planned commands later
 
   // <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment>  get list of all actions
-  m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("actions")("all"), bind(&StateServer::info_actions_all, this, _1));
+  m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"), bind(&StateServer::info_actions_folder, this, _1));
 
-  // // <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
-  // m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("filestate")("all"), bind(&StateServer::info_filestate_all, this, _1));
+  // <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
+  m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("filestate")("folder"), bind(&StateServer::info_filestate_folder, this, _1));
 
   // <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
   m_ccnx->setInterestFilter (Name (m_PREFIX_CMD)("restore")("file"), bind(&StateServer::cmd_restore_file, this, _1));
@@ -89,34 +89,77 @@
 void
 StateServer::deregisterPrefixes ()
 {
-  m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("actions")("all"));
-  m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("filestate")("all"));
+  m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"));
+  m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("filestate")("folder"));
   m_ccnx->clearInterestFilter (Name (m_PREFIX_CMD) ("restore")("file"));
 }
 
-void
-StateServer::info_actions_all (const Name &interest)
+// void
+// StateServer::info_actions_all (const Name &interest)
+// {
+//   _LOG_DEBUG (">> info_actions_all: " << interest);
+//   m_executor.execute (bind (&StateServer::info_actions_all_Execute, this, interest));
+// }
+
+// void
+// StateServer::info_actions_all_Execute (const Name &interest)
+// {
+//   // <PREFIX_INFO>/"actions"/"all"/<nonce>/<offset>  get list of all actions
+
+//   try
+//     {
+//       int offset = interest.getCompFromBackAsInt (0);
+
+//       // LookupActionsInFolderRecursively
+//       /// @todo !!! add security checking
+//       m_ccnx->publishData (interest, "FAIL: Not implemented", 1);
+//     }
+//   catch (Ccnx::NameException &ne)
+//     {
+//       // ignore any unexpected interests and errors
+//       _LOG_ERROR (*boost::get_error_info<Ccnx::error_info_str>(ne));
+//     }
+// }
+
+void debugAction (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &action)
 {
-  _LOG_DEBUG (">> info_actions_all: " << interest);
-  m_scheduler->scheduleOneTimeTask (m_scheduler, 0, bind (&StateServer::info_actions_all_Execute, this, interest), boost::lexical_cast<string>(interest));
+  std::cout << name << ", " << seq_no << ", " << action.filename () << std::endl;
 }
 
 void
-StateServer::info_actions_all_Execute (const Name &interest)
+StateServer::info_actions_folder (const Name &interest)
 {
-  // <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment>  get list of all actions
+  if (interest.size () - m_PREFIX_INFO.size () != 4 &&
+      interest.size () - m_PREFIX_INFO.size () != 5)
+    {
+      _LOG_DEBUG ("Invalid interest: " << interest);
+      return;
+    }
+
+  _LOG_DEBUG (">> info_actions_all: " << interest);
+  m_executor.execute (bind (&StateServer::info_actions_folder_Execute, this, interest));
+}
+
+void
+StateServer::info_actions_folder_Execute (const Name &interest)
+{
+  // <PREFIX_INFO>/"actions"/"folder"/<folder>/<nonce>/<offset>  get list of all actions
 
   try
     {
-      int segment = interest.getCompFromBackAsInt (0);
-      if (segment != 0)
-        {
-          // ignore anything except segment 0, other stuff should be cached.. if not, then good luck
-          return;
-        }
+      int offset = interest.getCompFromBackAsInt (0);
 
       /// @todo !!! add security checking
-      m_ccnx->publishData (interest, "FAIL: Not implemented", 1);
+
+      string folder;
+      if (interest.size () - m_PREFIX_INFO.size () == 5)
+        folder = interest.getCompFromBackAsString (2);
+      else // == 4
+        folder = "";
+
+      m_actionLog->LookupActionsInFolderRecursively (debugAction, folder, offset*100, 100);
+
+      // m_ccnx->publishData (interest, "FAIL: Not implemented", 1);
     }
   catch (Ccnx::NameException &ne)
     {
@@ -125,11 +168,67 @@
     }
 }
 
+void debugFileState (const FileItem &file)
+{
+  std::cout << file.filename () << std::endl;
+}
+
+void
+StateServer::info_filestate_folder (const Ccnx::Name &interest)
+{
+  if (interest.size () - m_PREFIX_INFO.size () != 4 &&
+      interest.size () - m_PREFIX_INFO.size () != 5)
+    {
+      _LOG_DEBUG ("Invalid interest: " << interest << ", " << interest.size () - m_PREFIX_INFO.size ());
+      return;
+    }
+
+  _LOG_DEBUG (">> info_filestate_folder: " << interest);
+  m_executor.execute (bind (&StateServer::info_filestate_folder_Execute, this, interest));
+}
+
+
+void
+StateServer::info_filestate_folder_Execute (const Ccnx::Name &interest)
+{
+  // <PREFIX_INFO>/"filestate"/"folder"/<one-component-relative-folder-name>/<nonce>/<offset>
+  try
+    {
+      int offset = interest.getCompFromBackAsInt (0);
+
+      // /// @todo !!! add security checking
+
+      string folder;
+      if (interest.size () - m_PREFIX_INFO.size () == 5)
+        folder = interest.getCompFromBackAsString (2);
+      else // == 4
+        folder = "";
+
+      FileStatePtr fileState = m_actionLog->GetFileState ();
+      fileState->LookupFilesInFolderRecursively (debugFileState, folder, offset*100, 100);
+
+      // m_ccnx->publishData (interest, "FAIL: Not implemented", 1);
+    }
+  catch (Ccnx::NameException &ne)
+    {
+      // ignore any unexpected interests and errors
+      _LOG_ERROR (*boost::get_error_info<Ccnx::error_info_str>(ne));
+    }
+}
+
+
 void
 StateServer::cmd_restore_file (const Ccnx::Name &interest)
 {
+  if (interest.size () - m_PREFIX_CMD.size () != 4 &&
+      interest.size () - m_PREFIX_CMD.size () != 5)
+    {
+      _LOG_DEBUG ("Invalid interest: " << interest);
+      return;
+    }
+
   _LOG_DEBUG (">> cmd_restore_file: " << interest);
-  m_scheduler->scheduleOneTimeTask (m_scheduler, 0, bind (&StateServer::cmd_restore_file_Execute, this, interest), boost::lexical_cast<string>(interest));
+  m_executor.execute (bind (&StateServer::cmd_restore_file_Execute, this, interest));
 }
 
 void
@@ -141,25 +240,45 @@
 
   try
     {
-      Hash hash (head(interest.getCompFromBack (0)), interest.getCompFromBack (0).size());
-      int64_t version = interest.getCompFromBackAsInt (1);
-      string  filename = interest.getCompFromBackAsString (2); // should be safe even with full relative path
+      FileItemPtr file;
 
-      FileItemPtr file = m_actionLog->LookupAction (filename, version, hash);
+      if (interest.size () - m_PREFIX_CMD.size () == 5)
+        {
+          Hash hash (head(interest.getCompFromBack (0)), interest.getCompFromBack (0).size());
+          int64_t version = interest.getCompFromBackAsInt (1);
+          string  filename = interest.getCompFromBackAsString (2); // should be safe even with full relative path
+
+          file = m_actionLog->LookupAction (filename, version, hash);
+          if (!file)
+            {
+              _LOG_ERROR ("Requested file is not found: [" << filename << "] version [" << version << "] hash [" << hash.shortHash () << "]");
+            }
+        }
+      else
+        {
+          int64_t version = interest.getCompFromBackAsInt (0);
+          string  filename = interest.getCompFromBackAsString (1); // should be safe even with full relative path
+
+          file = m_actionLog->LookupAction (filename, version, Hash (0,0));
+          if (!file)
+            {
+              _LOG_ERROR ("Requested file is not found: [" << filename << "] version [" << version << "]");
+            }
+        }
+
       if (!file)
         {
           m_ccnx->publishData (interest, "FAIL: Requested file is not found", 1);
-          _LOG_ERROR ("Requested file is not found: [" << filename << "] version [" << version << "] hash [" << hash.shortHash () << "]");
           return;
         }
 
-      hash = Hash (file->file_hash ().c_str (), file->file_hash ().size ());
+      Hash hash = Hash (file->file_hash ().c_str (), file->file_hash ().size ());
 
       ///////////////////
       // now the magic //
       ///////////////////
 
-      boost::filesystem::path filePath = m_rootDir / filename;
+      boost::filesystem::path filePath = m_rootDir / file->filename ();
       Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
 
       try
diff --git a/src/state-server.h b/src/state-server.h
index 3eec803..24c9b05 100644
--- a/src/state-server.h
+++ b/src/state-server.h
@@ -30,7 +30,7 @@
 #include <map>
 #include <boost/thread/shared_mutex.hpp>
 #include <boost/thread/locks.hpp>
-#include "scheduler.h"
+#include "executor.h"
 
 /**
  * @brief Class serving state information from ChronoShare
@@ -39,7 +39,7 @@
  *
  * Information available:
  *
- * For now serving only locally (using <PREFIX> = /localhost/<user's-device-name>/"chronoshare"/"info")
+ * For now serving only locally (using <PREFIX> = /localhost/<user's-device-name>/"chronoshare"/<FOLDER>/"info")
  *
  * - state: get list of SyncNodes, their sequence numbers, and forwarding hint (almost the same as RECOVERY interest)
  *
@@ -47,28 +47,38 @@
  *
  * - action
  *
- *   <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment>      get list of all actions
- *   <PREFIX_INFO>/"actions"/"file"/<nonce>/<segment>     get list of actions for a file
+ *   Get list of actions for a folder (for all files under this folder)
+ *
+ *   <PREFIX_INFO>/"actions"/"folder"/<nonce>/<offset>   (all actions)
+ *   or
+ *   <PREFIX_INFO>/"actions"/"folder"/<one-component-relative-file-name>/<nonce>/<offset>
  *
  *   Actions are ordered in decreasing order (latest will go first).
- *   Each data packet contains up to 100 actions.  If there are more, they will be segmented. Data packet always
- *   contains a segment number (even if there are less than 100 actions available).
  *
- *   Number of segments is indicated in FinalBlockID of the first data packet (in <PREFIX>/"action"/"all"/<nonce>/%00)
+ *   Each data packet contains up to 100 actions.
+ *   If more items are available, application data will specify URL for the next packet
+ *
+ *   @todo SPECIFY FORMAT OF THIS FIELD
  *
  * - file
  *
- *   <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
+ *   <PREFIX_INFO>/"filestate"/"folder"/<nonce>/<offset>   (full filestate)
+ *   or
+ *   <PREFIX_INFO>/"filestate"/"folder"/<one-component-relative-folder-name>/<nonce>/<offset>
  *
- *   Each Data packets contains a list of up to 100 files, the rest is published in other segments.
- *   Number of segments is indicated in FinalBlockID of the first data packet (in <PREFIX>/"file"/"all"/<nonce>/%00).
+ *   Each Data packets contains a list of up to 100 files.
+ *   If more items are available, application data will specify URL for the next packet
+ *
+ *   @todo SPECIFY FORMAT OF THIS FIELD
  *
  * Commands available:
  *
- * For now serving only locally (using <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd")
+ * For now serving only locally (using <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/<FOLDER>/"cmd")
  *
  * - restore version of the file
  *
+ *   <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>
+ *   or
  *   <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
  *
  * - clean state log
@@ -86,10 +96,16 @@
 
 private:
   void
-  info_actions_all (const Ccnx::Name &interest);
+  info_actions_folder (const Ccnx::Name &interest);
 
   void
-  info_actions_all_Execute (const Ccnx::Name &interest);
+  info_actions_folder_Execute (const Ccnx::Name &interest);
+
+  void
+  info_filestate_folder (const Ccnx::Name &interest);
+
+  void
+  info_filestate_folder_Execute (const Ccnx::Name &interest);
 
   void
   cmd_restore_file (const Ccnx::Name &interest);
@@ -115,7 +131,7 @@
   boost::filesystem::path m_rootDir;
   int m_freshness;
 
-  SchedulerPtr     m_scheduler;
+  Executor    m_executor;
 
   Ccnx::Name  m_userName;
   std::string m_sharedFolderName;