GUI+RPC: Action history for individual files

In .js GUI now it is possible to click on file to get history for
individual files

Change-Id: Ia50d1d14019b4ea0d39940da566b43927dd73fa2
diff --git a/gui/html/chronoshare.js b/gui/html/chronoshare.js
index cc1f9ae..ffd9724 100644
--- a/gui/html/chronoshare.js
+++ b/gui/html/chronoshare.js
@@ -130,15 +130,11 @@
                     $(this).toggleClass('highlighted');
                 });
 
-                // fileHistoryUrl = new Name ()
-                //     .add (this.chronoshare.actions)
-                //     .add (file.filename)
-                //     .add ("nonce")
-                //     .addSegment (0);
-                // row.attr ("history", fileHistoryUrl.to_uri ());
-                row.bind('click', function () {
+                row.attr ("filename", encodeURIComponent(encodeURIComponent(file.filename)));
+
+                row.bind('click', function (e) {
                     url = "#fileHistory";
-                    url += "&item=" + encodeURIComponent(encodeURIComponent(file.filename));
+                    url += "&item=" + $(this).attr ("filename");
                     pos = URIPARAMS.indexOf ("&");
                     if (pos >= 0) {
                         url += URIPARAMS.substring (pos)
@@ -209,14 +205,17 @@
                     $(this).toggleClass('highlighted');
                 });
 
-                fileHistoryUrl = new Name ()
-                    .add (this.chronoshare.actions)
-                    .add (action.filename)
-                    .add ("nonce")
-                    .addSegment (0);
-                // row.attr ("history", fileHistoryUrl.to_uri ());
-                row.bind('click', function () {
-                    // alert (fileHistoryUrl.to_uri ());
+                row.attr ("filename", encodeURIComponent(encodeURIComponent(action.filename)));
+
+                row.bind('click', function (e) {
+                    url = "#fileHistory";
+                    url += "&item=" + $(this).attr ("filename");
+                    pos = URIPARAMS.indexOf ("&");
+                    if (pos >= 0) {
+                        url += URIPARAMS.substring (pos)
+                    }
+
+                    document.location = url;
                 });
 
 	        row.append ($("<td />", {"class": "border-left"}).text (action.filename));
@@ -247,7 +246,7 @@
          this.username = new Name (username);
          this.files = new Name ("/localhost").add (this.username).add ("chronoshare").add (foldername).add ("info").add ("files").add ("folder");
 
-         this.actions = new Name ("/localhost").add (this.username).add ("chronoshare").add (foldername).add ("info").add ("actions").add ("folder");
+         this.actions = new Name ("/localhost").add (this.username).add ("chronoshare").add (foldername).add ("info").add ("actions");
 
          this.restore = new Name ("/localhost").add (this.username).add ("chronoshare").add (foldername).add ("cmd").add ("restore").add ("file");
 
@@ -267,7 +266,7 @@
              this.ndn.expressInterest (request, new FilesClosure (this));
          }
          else if (PAGE == "folderHistory") {
-             request = new Name ().add (this.actions)./*add (folder_in_question).*/add ("nonce").addSegment (0);
+             request = new Name ().add (this.actions).add ("folder")./*add (folder_in_question).*/add ("nonce").addSegment (0);
              console.log (request.to_uri ());
              this.ndn.expressInterest (request, new HistoryClosure (this));
          }
@@ -278,7 +277,7 @@
                  $("#error").removeClass ("hidden");
                  return;
              }
-             request = new Name ().add (this.actions).add (PARAMS.item).add ("nonce").addSegment (0);
+             request = new Name ().add (this.actions).add ("file").add (PARAMS.item).add ("nonce").addSegment (0);
              console.log (request.to_uri ());
              this.ndn.expressInterest (request, new HistoryClosure (this));
          }
diff --git a/src/action-log.cc b/src/action-log.cc
index 740430f..71ee782 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -727,6 +727,78 @@
   return (limit == 1); // more data is available
 }
 
+/**
+ * @todo Figure out the way to minimize code duplication
+ */
+bool
+ActionLog::LookupActionsForFile (const boost::function<void (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &)> &visitor,
+                                 const std::string &file, int offset/*=0*/, int limit/*=-1*/)
+{
+  _LOG_DEBUG ("LookupActionsInFolderRecursively: [" << file << "]");
+  if (file.empty ())
+    return false;
+
+  if (limit >= 0)
+    limit += 1; // to check if there is more data
+
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db,
+                      "SELECT device_name,seq_no,action,filename,directory,version,strftime('%s', action_timestamp), "
+                      "       file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num, "
+                      "       parent_device_name,parent_seq_no "
+                      "   FROM ActionLog "
+                      "   WHERE filename=? "
+                      "   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, file.c_str (), file.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);
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+  while (sqlite3_step (stmt) == SQLITE_ROW)
+    {
+      if (limit == 1)
+        break;
+
+      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);
+      limit --;
+    }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
+  return (limit == 1); // more data is available
+}
+
+
 void
 ActionLog::LookupRecentFileActions(const boost::function<void (const string &, int, int)> &visitor, int limit)
 {
diff --git a/src/action-log.h b/src/action-log.h
index 6345281..96ccd51 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -109,6 +109,10 @@
   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);
 
+  bool
+  LookupActionsForFile (const boost::function<void (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &)> &visitor,
+                        const std::string &file, int offset=0, int limit=-1);
+
   void
   LookupRecentFileActions(const boost::function<void (const std::string &, int, int)> &visitor, int limit = 5);
 
diff --git a/src/state-server.cc b/src/state-server.cc
index fa8517c..43f15e3 100644
--- a/src/state-server.cc
+++ b/src/state-server.cc
@@ -79,6 +79,7 @@
 
   // <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment>  get list of all actions
   m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"), bind(&StateServer::info_actions_folder, this, _1));
+  m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("actions")("file"),   bind(&StateServer::info_actions_file, this, _1));
 
   // <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
   m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("files")("folder"), bind(&StateServer::info_files_folder, this, _1));
@@ -91,37 +92,11 @@
 StateServer::deregisterPrefixes ()
 {
   m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"));
+  m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("actions")("file"));
   m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("files")("folder"));
   m_ccnx->clearInterestFilter (Name (m_PREFIX_CMD) ("restore")("file"));
 }
 
-// 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
 StateServer::formatActionJson (json_spirit::Array &actions,
                                const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &action)
@@ -206,14 +181,29 @@
       return;
     }
 
-  _LOG_DEBUG (">> info_actions_all: " << interest);
-  m_executor.execute (bind (&StateServer::info_actions_folder_Execute, this, interest));
+  _LOG_DEBUG (">> info_actions_folder: " << interest);
+  m_executor.execute (bind (&StateServer::info_actions_fileOrFolder_Execute, this, interest, true));
 }
 
 void
-StateServer::info_actions_folder_Execute (const Name &interest)
+StateServer::info_actions_file (const Name &interest)
 {
-  // <PREFIX_INFO>/"actions"/"folder"/<folder>/<nonce>/<offset>  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_file: " << interest);
+  m_executor.execute (bind (&StateServer::info_actions_fileOrFolder_Execute, this, interest, false));
+}
+
+
+void
+StateServer::info_actions_fileOrFolder_Execute (const Ccnx::Name &interest, bool isFolder/* = true*/)
+{
+  // <PREFIX_INFO>/"actions"/"folder|file"/<folder|file>/<nonce>/<offset>  get list of all actions
 
   try
     {
@@ -221,11 +211,11 @@
 
       /// @todo !!! add security checking
 
-      string folder;
+      string fileOrFolderName;
       if (interest.size () - m_PREFIX_INFO.size () == 5)
-        folder = interest.getCompFromBackAsString (2);
+        fileOrFolderName = interest.getCompFromBackAsString (2);
       else // == 4
-        folder = "";
+        fileOrFolderName = "";
 /*
  *   {
  *      "actions": [
@@ -241,9 +231,19 @@
       Object json;
 
       Array actions;
-      bool more = m_actionLog->LookupActionsInFolderRecursively
-        (boost::bind (StateServer::formatActionJson, boost::ref(actions), _1, _2, _3),
-         folder, offset*10, 10);
+      bool more;
+      if (isFolder)
+        {
+          m_actionLog->LookupActionsInFolderRecursively
+            (boost::bind (StateServer::formatActionJson, boost::ref(actions), _1, _2, _3),
+             fileOrFolderName, offset*10, 10);
+        }
+      else
+        {
+          m_actionLog->LookupActionsForFile
+            (boost::bind (StateServer::formatActionJson, boost::ref(actions), _1, _2, _3),
+             fileOrFolderName, offset*10, 10);
+        }
 
       json.push_back (Pair ("actions", actions));
 
@@ -331,7 +331,7 @@
       return;
     }
 
-  _LOG_DEBUG (">> info_filestate_folder: " << interest);
+  _LOG_DEBUG (">> info_files_folder: " << interest);
   m_executor.execute (bind (&StateServer::info_files_folder_Execute, this, interest));
 }
 
diff --git a/src/state-server.h b/src/state-server.h
index 4a68594..22caf89 100644
--- a/src/state-server.h
+++ b/src/state-server.h
@@ -163,7 +163,10 @@
   info_actions_folder (const Ccnx::Name &interest);
 
   void
-  info_actions_folder_Execute (const Ccnx::Name &interest);
+  info_actions_file (const Ccnx::Name &interest);
+
+  void
+  info_actions_fileOrFolder_Execute (const Ccnx::Name &interest, bool isFolder = true);
 
   void
   info_files_folder (const Ccnx::Name &interest);