Rough implementation of the state server. Now it is possible to request restore of a particular file

See state-server.h for more details on command syntax.

// <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd"
// <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>

<file-hash> component is used solely to disambiguate file version and
need not be specified in full (or specified at all, but the component
need to be present). The system will only check that specified file-hash
is a prefix of the real hash of the file

Change-Id: I7a4d15a04eb1a1c59a3412da46174441c61a45c0
diff --git a/src/action-log.cc b/src/action-log.cc
index 0bc913c..84c2779 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -60,6 +60,7 @@
 CREATE INDEX ActionLog_filename_version ON ActionLog (filename,version);          \n\
 CREATE INDEX ActionLog_parent ON ActionLog (parent_device_name, parent_seq_no);   \n\
 CREATE INDEX ActionLog_action_name ON ActionLog (action_name);          \n\
+CREATE INDEX ActionLog_filename_version_hash ON ActionLog (filename,version,file_hash); \n\
                                                                         \n\
 CREATE TRIGGER ActionLogInsert_trigger                                  \n\
     AFTER INSERT ON ActionLog                                           \n\
@@ -445,6 +446,44 @@
   return action;
 }
 
+FileItemPtr
+ActionLog::LookupAction (const std::string &filename, sqlite3_int64 version, const Hash &filehash)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db,
+                      "SELECT device_name, seq_no, file_mtime, file_chmod, file_seg_num, file_hash "
+                      " FROM ActionLog "
+                      " WHERE action = 0 AND "
+                      "       filename=? AND "
+                      "       version=? AND "
+                      "       is_prefix (?, file_hash)=1", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  sqlite3_bind_text  (stmt, 1, filename.c_str (), filename.size (), SQLITE_STATIC);
+  sqlite3_bind_int64 (stmt, 2, version);
+  sqlite3_bind_blob  (stmt, 3, filehash.GetHash (), filehash.GetHashBytes (), SQLITE_STATIC);
+
+  FileItemPtr fileItem;
+
+  if (sqlite3_step (stmt) == SQLITE_ROW)
+    {
+      fileItem = make_shared<FileItem> ();
+      fileItem->set_filename (filename);
+      fileItem->set_device_name (sqlite3_column_blob (stmt, 0), sqlite3_column_bytes (stmt, 0));
+      fileItem->set_seq_no (sqlite3_column_int64 (stmt, 1));
+      fileItem->set_mtime   (sqlite3_column_int64 (stmt, 2));
+      fileItem->set_mode    (sqlite3_column_int64 (stmt, 3));
+      fileItem->set_seg_num (sqlite3_column_int64 (stmt, 4));
+
+      fileItem->set_file_hash (sqlite3_column_blob (stmt, 5), sqlite3_column_bytes (stmt, 5));
+    }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE || sqlite3_errcode (m_db) != SQLITE_ROW || sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  return fileItem;
+}
+
+
 ActionItemPtr
 ActionLog::AddRemoteAction (const Ccnx::Name &deviceName, sqlite3_int64 seqno, Ccnx::PcoPtr actionPco)
 {
diff --git a/src/action-log.h b/src/action-log.h
index f0ed9cd..fe96f10 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -99,6 +99,8 @@
   ActionItemPtr
   LookupAction (const Ccnx::Name &actionName);
 
+  FileItemPtr
+  LookupAction (const std::string &filename, sqlite3_int64 version, const Hash &filehash);
 
   //
   inline FileStatePtr
diff --git a/src/db-helper.cc b/src/db-helper.cc
index ac63cdf..33b0136 100644
--- a/src/db-helper.cc
+++ b/src/db-helper.cc
@@ -54,6 +54,13 @@
                              << errmsg_info_str ("Cannot create function ``hash''"));
     }
 
+  res = sqlite3_create_function (m_db, "is_prefix", 2, SQLITE_ANY, 0, DbHelper::is_prefix_xFun, 0, 0);
+  if (res != SQLITE_OK)
+    {
+      BOOST_THROW_EXCEPTION (Error::Db ()
+                             << errmsg_info_str ("Cannot create function ``is_prefix''"));
+    }
+
   // 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);
@@ -138,5 +145,31 @@
   EVP_MD_CTX_destroy (*hash_context);
 }
 
+void
+DbHelper::is_prefix_xFun (sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+  int len1 = sqlite3_value_bytes (argv[0]);
+  int len2 = sqlite3_value_bytes (argv[1]);
 
+  if (len1 == 0)
+    {
+      sqlite3_result_int (context, 1);
+      return;
+    }
+
+  if (len1 > len2) // first parameter should be at most equal in length to the second one
+    {
+      sqlite3_result_int (context, 0);
+      return;
+    }
+
+  if (memcmp (sqlite3_value_blob (argv[0]), sqlite3_value_blob (argv[1]), len1) == 0)
+    {
+      sqlite3_result_int (context, 1);
+    }
+  else
+    {
+      sqlite3_result_int (context, 0);
+    }
+}
 
diff --git a/src/db-helper.h b/src/db-helper.h
index 24f8c3f..c15621c 100644
--- a/src/db-helper.h
+++ b/src/db-helper.h
@@ -45,6 +45,9 @@
   static void
   hash_xFinal (sqlite3_context *context);
 
+  static void
+  is_prefix_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
+
 protected:
   sqlite3 *m_db;
 };
diff --git a/src/dispatcher.cc b/src/dispatcher.cc
index 3d3324b..7d6927f 100644
--- a/src/dispatcher.cc
+++ b/src/dispatcher.cc
@@ -69,6 +69,9 @@
   m_server->registerPrefix(Name("/"));
   m_server->registerPrefix(Name(BROADCAST_DOMAIN));
 
+  m_stateServer = new StateServer (make_shared<CcnxWrapper>(), m_actionLog, rootDir, m_localUserName, m_sharedFolder, CHRONOSHARE_APP, m_objectManager, CONTENT_FRESHNESS);
+  // no need to register, right now only listening on localhost prefix
+
   m_core = new SyncCore (m_syncLog, localUserName, Name("/"), syncPrefix,
                          bind(&Dispatcher::Did_SyncLog_StateChange, this, _1), ccnx, DEFAULT_SYNC_INTEREST_INTERVAL);
 
@@ -122,6 +125,12 @@
     delete m_server;
     m_server = NULL;
   }
+
+  if (m_stateServer != NULL)
+  {
+    delete m_stateServer;
+    m_stateServer = NULL;
+  }
 }
 
 void
@@ -168,11 +177,12 @@
   m_server->deregisterPrefix(oldLocalPrefix);
 }
 
-void
-Dispatcher::Restore_LocalFile (FileItemPtr file)
-{
-  m_executor.execute (bind (&Dispatcher::Restore_LocalFile_Execute, this, file));
-}
+// moved to state-server
+// void
+// Dispatcher::Restore_LocalFile (FileItemPtr file)
+// {
+//   m_executor.execute (bind (&Dispatcher::Restore_LocalFile_Execute, this, file));
+// }
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -476,35 +486,36 @@
     }
 }
 
-void
-Dispatcher::Restore_LocalFile_Execute (FileItemPtr file)
-{
-  _LOG_DEBUG ("Got request to restore local file [" << file->filename () << "]");
-  // the rest will gracefully fail if object-db is missing or incomplete
+// moved to state-server
+// void
+// Dispatcher::Restore_LocalFile_Execute (FileItemPtr file)
+// {
+//   _LOG_DEBUG ("Got request to restore local file [" << file->filename () << "]");
+//   // the rest will gracefully fail if object-db is missing or incomplete
 
-  boost::filesystem::path filePath = m_rootDir / file->filename ();
-  Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
-  Hash hash (file->file_hash ().c_str (), file->file_hash ().size ());
+//   boost::filesystem::path filePath = m_rootDir / file->filename ();
+//   Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
+//   Hash hash (file->file_hash ().c_str (), file->file_hash ().size ());
 
-  try
-    {
-      if (filesystem::exists (filePath) &&
-          filesystem::last_write_time (filePath) == file->mtime () &&
-          filesystem::status (filePath).permissions () == static_cast<filesystem::perms> (file->mode ()) &&
-          *Hash::FromFileContent (filePath) == hash)
-        {
-          _LOG_DEBUG ("Asking to assemble a file, but file already exists on a filesystem");
-          return;
-        }
-    }
-  catch (filesystem::filesystem_error &error)
-    {
-      _LOG_ERROR ("File operations failed on [" << filePath << "] (ignoring)");
-    }
+//   try
+//     {
+//       if (filesystem::exists (filePath) &&
+//           filesystem::last_write_time (filePath) == file->mtime () &&
+//           filesystem::status (filePath).permissions () == static_cast<filesystem::perms> (file->mode ()) &&
+//           *Hash::FromFileContent (filePath) == hash)
+//         {
+//           _LOG_DEBUG ("Asking to assemble a file, but file already exists on a filesystem");
+//           return;
+//         }
+//     }
+//   catch (filesystem::filesystem_error &error)
+//     {
+//       _LOG_ERROR ("File operations failed on [" << filePath << "] (ignoring)");
+//     }
 
-  m_objectManager.objectsToLocalFile (deviceName, hash, filePath);
+//   m_objectManager.objectsToLocalFile (deviceName, hash, filePath);
 
-  last_write_time (filePath, file->mtime ());
-  permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
+//   last_write_time (filePath, file->mtime ());
+//   permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
 
-}
+// }
diff --git a/src/dispatcher.h b/src/dispatcher.h
index 902ec62..0455ff0 100644
--- a/src/dispatcher.h
+++ b/src/dispatcher.h
@@ -29,6 +29,7 @@
 #include "object-db.h"
 #include "object-manager.h"
 #include "content-server.h"
+#include "state-server.h"
 #include "fetch-manager.h"
 
 #include <boost/function.hpp>
@@ -70,9 +71,6 @@
   void
   Restore_LocalFile (FileItemPtr file);
 
-  void
-  Restore_LocalFile_Execute (FileItemPtr file);
-
   // for test
   HashPtr
   SyncRoot() { return m_core->root(); }
@@ -84,6 +82,8 @@
   void
   Did_LocalFile_Delete_Execute (boost::filesystem::path relativeFilepath); // cannot be const & for Execute event!!! otherwise there will be segfault
 
+  void
+  Restore_LocalFile_Execute (FileItemPtr file);
 
 private:
   /**
@@ -174,6 +174,7 @@
 
   std::string m_sharedFolder;
   ContentServer *m_server;
+  StateServer   *m_stateServer;
   bool m_enablePrefixDiscovery;
 
   FetchManagerPtr m_actionFetcher;
diff --git a/src/file-state.cc b/src/file-state.cc
index e4a2c1b..fd982d9 100644
--- a/src/file-state.cc
+++ b/src/file-state.cc
@@ -86,15 +86,15 @@
                       "file_seg_num=? "
                       "WHERE type=0 AND filename=?", -1, &stmt, 0);
 
-  sqlite3_bind_blob  (stmt, 1, device_name.buf (), device_name.length (), SQLITE_TRANSIENT);
+  sqlite3_bind_blob  (stmt, 1, device_name.buf (), device_name.length (), SQLITE_STATIC);
   sqlite3_bind_int64 (stmt, 2, seq_no);
-  sqlite3_bind_blob  (stmt, 3, hash.GetHash (), hash.GetHashBytes (), SQLITE_TRANSIENT);
+  sqlite3_bind_blob  (stmt, 3, hash.GetHash (), hash.GetHashBytes (), SQLITE_STATIC);
   sqlite3_bind_int64 (stmt, 4, atime);
   sqlite3_bind_int64 (stmt, 5, mtime);
   sqlite3_bind_int64 (stmt, 6, ctime);
   sqlite3_bind_int   (stmt, 7, mode);
   sqlite3_bind_int   (stmt, 8, seg_num);
-  sqlite3_bind_text  (stmt, 9, filename.c_str (), -1, SQLITE_TRANSIENT);
+  sqlite3_bind_text  (stmt, 9, filename.c_str (), -1, SQLITE_STATIC);
 
   sqlite3_step (stmt);
 
diff --git a/src/state-server.cc b/src/state-server.cc
new file mode 100644
index 0000000..98830d0
--- /dev/null
+++ b/src/state-server.cc
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2013 University of California, Los Angeles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ *         Zhenkai Zhu <zhenkai@cs.ucla.edu>
+ */
+
+#include "state-server.h"
+#include "logging.h"
+#include <boost/make_shared.hpp>
+#include <utility>
+#include "task.h"
+#include "periodic-task.h"
+#include "simple-interval-generator.h"
+#include <boost/lexical_cast.hpp>
+
+INIT_LOGGER ("StateServer");
+
+using namespace Ccnx;
+using namespace std;
+using namespace boost;
+
+StateServer::StateServer(CcnxWrapperPtr ccnx, ActionLogPtr actionLog,
+                         const boost::filesystem::path &rootDir,
+                         const Ccnx::Name &userName, const std::string &sharedFolderName,
+                         const std::string &appName,
+                         ObjectManager &objectManager,
+                         int freshness/* = -1*/)
+  : m_ccnx(ccnx)
+  , m_actionLog(actionLog)
+  , m_objectManager (objectManager)
+  , m_rootDir(rootDir)
+  , m_freshness(freshness)
+  , m_scheduler (new Scheduler())
+  , m_userName (userName)
+  , m_sharedFolderName (sharedFolderName)
+  , m_appName (appName)
+{
+  // 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");
+
+  // <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd"
+  m_PREFIX_CMD = Name ("/localhost")(m_userName)("chronoshare")("cmd");
+
+  m_scheduler->start ();
+
+  registerPrefixes ();
+}
+
+StateServer::~StateServer()
+{
+  m_scheduler->shutdown ();
+
+  deregisterPrefixes ();
+}
+
+void
+StateServer::registerPrefixes ()
+{
+  // currently supporting limited number of command.
+  // 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));
+
+  // // <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
+  // m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("filestate")("all"), bind(&StateServer::info_filestate_all, 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));
+}
+
+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_CMD) ("restore")("file"));
+}
+
+void
+StateServer::info_actions_all (const Name &interest)
+{
+  _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));
+}
+
+void
+StateServer::info_actions_all_Execute (const Name &interest)
+{
+  // <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment>  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;
+        }
+
+      /// @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
+StateServer::cmd_restore_file (const Ccnx::Name &interest)
+{
+  _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));
+}
+
+void
+StateServer::cmd_restore_file_Execute (const Ccnx::Name &interest)
+{
+  // <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
+
+  /// @todo !!! add security checking
+
+  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 = m_actionLog->LookupAction (filename, version, hash);
+      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 ());
+
+      ///////////////////
+      // now the magic //
+      ///////////////////
+
+      boost::filesystem::path filePath = m_rootDir / filename;
+      Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
+
+      try
+        {
+          if (filesystem::exists (filePath) &&
+              filesystem::last_write_time (filePath) == file->mtime () &&
+              filesystem::status (filePath).permissions () == static_cast<filesystem::perms> (file->mode ()) &&
+              *Hash::FromFileContent (filePath) == hash)
+            {
+              m_ccnx->publishData (interest, "OK: File already exists", 1);
+              _LOG_DEBUG ("Asking to assemble a file, but file already exists on a filesystem");
+              return;
+            }
+        }
+      catch (filesystem::filesystem_error &error)
+        {
+          m_ccnx->publishData (interest, "FAIL: File operation failed", 1);
+          _LOG_ERROR ("File operations failed on [" << filePath << "] (ignoring)");
+        }
+
+      _LOG_TRACE ("Restoring file [" << filePath << "]");
+      if (m_objectManager.objectsToLocalFile (deviceName, hash, filePath))
+        {
+          last_write_time (filePath, file->mtime ());
+          permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
+          m_ccnx->publishData (interest, "OK", 1);
+        }
+      else
+        {
+          m_ccnx->publishData (interest, "FAIL: Unknown error while restoring file", 1);
+        }
+    }
+  catch (Ccnx::NameException &ne)
+    {
+      // ignore any unexpected interests and errors
+      _LOG_ERROR(*boost::get_error_info<Ccnx::error_info_str>(ne));
+    }
+}
diff --git a/src/state-server.h b/src/state-server.h
new file mode 100644
index 0000000..3eec803
--- /dev/null
+++ b/src/state-server.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2013 University of California, Los Angeles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Zhenkai Zhu <zhenkai@cs.ucla.edu>
+ *         Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ */
+
+#ifndef STATE_SERVER_H
+#define STATE_SERVER_H
+
+#include "ccnx-wrapper.h"
+#include "object-manager.h"
+#include "object-db.h"
+#include "action-log.h"
+#include <set>
+#include <map>
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/thread/locks.hpp>
+#include "scheduler.h"
+
+/**
+ * @brief Class serving state information from ChronoShare
+ *
+ * Eventually, the same info/actions can be made available via a global scope prefix
+ *
+ * Information available:
+ *
+ * For now serving only locally (using <PREFIX> = /localhost/<user's-device-name>/"chronoshare"/"info")
+ *
+ * - state: get list of SyncNodes, their sequence numbers, and forwarding hint (almost the same as RECOVERY interest)
+ *
+ *   <PREFIX_INFO>/"state"/<nonce>   (nonce should probably be the authentification code or authentication code should in addition somewhere)
+ *
+ * - 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
+ *
+ *   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)
+ *
+ * - file
+ *
+ *   <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
+ *
+ *   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).
+ *
+ * Commands available:
+ *
+ * For now serving only locally (using <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd")
+ *
+ * - restore version of the file
+ *
+ *   <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
+ *
+ * - clean state log
+ *   (this may not need to be here, if we implement periodic cleaning)
+ * - ? flatten action log (should be supported eventually, but not supported now)
+ */
+class StateServer
+{
+public:
+  StateServer(Ccnx::CcnxWrapperPtr ccnx, ActionLogPtr actionLog, const boost::filesystem::path &rootDir,
+              const Ccnx::Name &userName, const std::string &sharedFolderName, const std::string &appName,
+              ObjectManager &objectManager,
+              int freshness = -1);
+  ~StateServer();
+
+private:
+  void
+  info_actions_all (const Ccnx::Name &interest);
+
+  void
+  info_actions_all_Execute (const Ccnx::Name &interest);
+
+  void
+  cmd_restore_file (const Ccnx::Name &interest);
+
+  void
+  cmd_restore_file_Execute (const Ccnx::Name &interest);
+
+private:
+  void
+  registerPrefixes ();
+
+  void
+  deregisterPrefixes ();
+
+private:
+  Ccnx::CcnxWrapperPtr m_ccnx;
+  ActionLogPtr m_actionLog;
+  ObjectManager &m_objectManager;
+
+  Ccnx::Name m_PREFIX_INFO;
+  Ccnx::Name m_PREFIX_CMD;
+
+  boost::filesystem::path m_rootDir;
+  int m_freshness;
+
+  SchedulerPtr     m_scheduler;
+
+  Ccnx::Name  m_userName;
+  std::string m_sharedFolderName;
+  std::string m_appName;
+};
+#endif // CONTENT_SERVER_H