tool: repo watching prefix

Change-Id: I7ecbba66816f7e33f99554f5b6040d5af148fb78
Refs: #1784
diff --git a/tools/ndnrepowatch.cpp b/tools/ndnrepowatch.cpp
new file mode 100644
index 0000000..a9b6d8b
--- /dev/null
+++ b/tools/ndnrepowatch.cpp
@@ -0,0 +1,397 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * repo-ng 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
+ * repo-ng, e.g., in COPYING.md file.  if (not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "../src/repo-command-parameter.hpp"
+#include "../src/repo-command-response.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+#include <fstream>
+#include <string>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace repo {
+
+using namespace ndn::time;
+
+static const uint64_t DEFAULT_INTEREST_LIFETIME = 4000;
+static const uint64_t DEFAULT_FRESHNESS_PERIOD = 10000;
+static const uint64_t DEFAULT_CHECK_PERIOD = 1000;
+
+enum CommandType
+{
+  START,
+  CHECK,
+  STOP
+};
+
+class NdnRepoWatch : ndn::noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+  NdnRepoWatch()
+    : freshnessPeriod(DEFAULT_FRESHNESS_PERIOD)
+    , interestLifetime(DEFAULT_INTEREST_LIFETIME)
+    , hasTimeout(false)
+    , watchTimeout(0)
+    , hasMaxInterestNum(false)
+    , maxInterestNum(0)
+    , status(START)
+    , isVerbose(false)
+
+    , m_scheduler(m_face.getIoService())
+    , m_checkPeriod(DEFAULT_CHECK_PERIOD)
+  {
+  }
+
+  void
+  run();
+
+private:
+
+  void
+  startWatchCommand();
+
+  void
+  onWatchCommandResponse(const ndn::Interest& interest, ndn::Data& data);
+
+  void
+  onWatchCommandTimeout(const ndn::Interest& interest);
+
+  void
+  stopProcess();
+
+  void
+  signData(ndn::Data& data);
+
+  void
+  startCheckCommand();
+
+  void
+  onCheckCommandTimeout(const ndn::Interest& interest);
+
+  void
+  onStopCommandResponse(const ndn::Interest& interest, ndn::Data& data);
+
+  void
+  onStopCommandTimeout(const ndn::Interest& interest);
+
+  ndn::Interest
+  generateCommandInterest(const ndn::Name& commandPrefix, const std::string& command,
+                          const RepoCommandParameter& commandParameter);
+
+public:
+  std::string identityForCommand;
+  milliseconds freshnessPeriod;
+  milliseconds interestLifetime;
+  bool hasTimeout;
+  milliseconds watchTimeout;
+  bool hasMaxInterestNum;
+  int64_t maxInterestNum;
+  CommandType status;
+  ndn::Name repoPrefix;
+  ndn::Name ndnName;
+  bool isVerbose;
+
+
+private:
+  ndn::Face m_face;
+  ndn::Scheduler m_scheduler;
+  milliseconds m_checkPeriod;
+
+  ndn::Name m_dataPrefix;
+  ndn::KeyChain m_keyChain;
+  typedef std::map<uint64_t, ndn::shared_ptr<ndn::Data> > DataContainer;
+};
+
+void
+NdnRepoWatch::run()
+{
+  m_dataPrefix = ndnName;
+  startWatchCommand();
+
+  if (hasTimeout)
+    m_scheduler.scheduleEvent(watchTimeout, ndn::bind(&NdnRepoWatch::stopProcess, this));
+
+  m_face.processEvents();
+}
+
+void
+NdnRepoWatch::startWatchCommand()
+{
+  RepoCommandParameter parameters;
+  parameters.setName(m_dataPrefix);
+
+  repoPrefix.append("watch");
+  if (status == START) {
+    if (hasMaxInterestNum) {
+      parameters.setMaxInterestNum(maxInterestNum);
+    }
+    if (hasTimeout) {
+      parameters.setWatchTimeout(watchTimeout);
+    }
+    ndn::Interest commandInterest = generateCommandInterest(repoPrefix, "start", parameters);
+    m_face.expressInterest(commandInterest,
+                           ndn::bind(&NdnRepoWatch::onWatchCommandResponse, this, _1, _2),
+                           ndn::bind(&NdnRepoWatch::onWatchCommandTimeout, this, _1));
+  }
+  else if (status == STOP){
+    ndn::Interest commandInterest = generateCommandInterest(repoPrefix, "stop", parameters);
+    m_face.expressInterest(commandInterest,
+                           ndn::bind(&NdnRepoWatch::onWatchCommandResponse, this, _1, _2),
+                           ndn::bind(&NdnRepoWatch::onWatchCommandTimeout, this, _1));
+  }
+  else if (status == CHECK){
+    ndn::Interest commandInterest = generateCommandInterest(repoPrefix, "check", parameters);
+    m_face.expressInterest(commandInterest,
+                           ndn::bind(&NdnRepoWatch::onWatchCommandResponse, this, _1, _2),
+                           ndn::bind(&NdnRepoWatch::onWatchCommandTimeout, this, _1));
+  }
+
+}
+
+void
+NdnRepoWatch::onWatchCommandResponse(const ndn::Interest& interest, ndn::Data& data)
+{
+  RepoCommandResponse response(data.getContent().blockFromValue());
+  int statusCode = response.getStatusCode();
+  if (statusCode >= 400) {
+    throw Error("Watch command failed with code " +
+                boost::lexical_cast<std::string>(statusCode));
+  }
+  else if (statusCode == 101) {
+    std::cerr << "Watching prefix is stopped!" <<std::endl;
+    m_face.getIoService().stop();
+    return;
+  }
+  else if (statusCode == 300) {
+    std::cerr << "Watching prefix is running!" <<std::endl;
+    m_scheduler.scheduleEvent(m_checkPeriod,
+                              ndn::bind(&NdnRepoWatch::startCheckCommand, this));
+    return;
+  }
+  else if (statusCode == 100) {
+    std::cerr << "Watching prefix starts!" <<std::endl;
+    m_scheduler.scheduleEvent(m_checkPeriod,
+                              ndn::bind(&NdnRepoWatch::startCheckCommand, this));
+    return;
+  }
+  else {
+    throw Error("Unrecognized Status Code " +
+                boost::lexical_cast<std::string>(statusCode));
+  }
+}
+
+void
+NdnRepoWatch::onWatchCommandTimeout(const ndn::Interest& interest)
+{
+  throw Error("command response timeout");
+}
+
+void
+NdnRepoWatch::stopProcess()
+{
+  m_face.getIoService().stop();
+}
+
+void
+NdnRepoWatch::startCheckCommand()
+{
+  repoPrefix.append("watch");
+  ndn::Interest checkInterest = generateCommandInterest(repoPrefix, "check",
+                                                        RepoCommandParameter()
+                                                          .setName(m_dataPrefix));
+  m_face.expressInterest(checkInterest,
+                         ndn::bind(&NdnRepoWatch::onWatchCommandResponse, this, _1, _2),
+                         ndn::bind(&NdnRepoWatch::onCheckCommandTimeout, this, _1));
+}
+
+void
+NdnRepoWatch::onCheckCommandTimeout(const ndn::Interest& interest)
+{
+  throw Error("check response timeout");
+}
+
+void
+NdnRepoWatch::onStopCommandResponse(const ndn::Interest& interest, ndn::Data& data)
+{
+  RepoCommandResponse response(data.getContent().blockFromValue());
+  int statusCode = response.getStatusCode();
+  if (statusCode != 101) {
+    throw Error("Watch stop command failed with code: " +
+                boost::lexical_cast<std::string>(statusCode));
+  }
+  else {
+    std::cerr << "Status code is 101. Watching prefix is stopped successfully!" << std::endl;
+    m_face.getIoService().stop();
+    return;
+  }
+}
+
+void
+NdnRepoWatch::onStopCommandTimeout(const ndn::Interest& interest)
+{
+  throw Error("stop response timeout");
+}
+
+ndn::Interest
+NdnRepoWatch::generateCommandInterest(const ndn::Name& commandPrefix, const std::string& command,
+                                      const RepoCommandParameter& commandParameter)
+{
+  ndn::Interest interest(ndn::Name(commandPrefix)
+                         .append(command)
+                         .append(commandParameter.wireEncode()));
+  interest.setInterestLifetime(interestLifetime);
+
+  if (identityForCommand.empty())
+    m_keyChain.sign(interest);
+  else {
+    m_keyChain.signByIdentity(interest, ndn::Name(identityForCommand));
+  }
+
+  return interest;
+}
+
+static void
+usage()
+{
+  fprintf(stderr,
+          "NdnRepoWatch [-I identity]"
+          "  [-x freshness] [-l lifetime] [-w watchtimeout]"
+          "  [-n maxinterestnum][-s stop] [-c check]repo-prefix ndn-name\n"
+          "\n"
+          " Write a file into a repo.\n"
+          "  -I: specify identity used for signing commands\n"
+          "  -x: FreshnessPeriod in milliseconds\n"
+          "  -l: InterestLifetime in milliseconds for each command\n"
+          "  -w: timeout in milliseconds for whole process (default unlimited)\n"
+          "  -n: total number of interests to be sent for whole process (default unlimited)\n"
+          "  -s: stop the whole process\n"
+          "  -c: check the process\n"
+          "  repo-prefix: repo command prefix\n"
+          "  ndn-name: NDN Name prefix for written Data\n"
+          );
+  exit(1);
+}
+
+int
+main(int argc, char** argv)
+{
+  NdnRepoWatch app;
+  int opt;
+  while ((opt = getopt(argc, argv, "x:l:w:n:scI:")) != -1) {
+    switch (opt) {
+    case 'x':
+      try {
+        app.freshnessPeriod = milliseconds(boost::lexical_cast<uint64_t>(optarg));
+      }
+      catch (boost::bad_lexical_cast&) {
+        std::cerr << "-x option should be an integer.";
+        return 1;
+      }
+      break;
+    case 'l':
+      try {
+        app.interestLifetime = milliseconds(boost::lexical_cast<uint64_t>(optarg));
+      }
+      catch (boost::bad_lexical_cast&) {
+        std::cerr << "-l option should be an integer.";
+        return 1;
+      }
+      break;
+    case 'w':
+      app.hasTimeout = true;
+      try {
+        app.watchTimeout = milliseconds(boost::lexical_cast<int64_t>(optarg));
+      }
+      catch (boost::bad_lexical_cast&) {
+        std::cerr << "-w option should be an integer.";
+        return 1;
+      }
+      break;
+    case 'n':
+      app.hasMaxInterestNum = true;
+      try {
+        app.maxInterestNum = boost::lexical_cast<int64_t>(optarg);
+      }
+      catch (boost::bad_lexical_cast&) {
+        std::cerr << "-n option should be an integer.";
+        return 1;
+      }
+      break;
+    case 's':
+      app.status = STOP;
+      break;
+    case 'c':
+      app.status = CHECK;
+      break;
+    case 'I':
+      app.identityForCommand = std::string(optarg);
+      break;
+    case 'v':
+      app.isVerbose = true;
+      break;
+    case 'h':
+      usage();
+      break;
+    default:
+      break;
+    }
+  }
+
+  argc -= optind;
+  argv += optind;
+
+  if (argc != 2)
+    usage();
+
+  app.repoPrefix = Name(argv[0]);
+  app.ndnName = Name(argv[1]);
+
+  app.run();
+
+  return 0;
+}
+
+} // namespace repo
+
+int
+main(int argc, char** argv)
+{
+  try {
+    return repo::main(argc, argv);
+  }
+  catch (std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+}