Create utility method to generate .torrent-files

Change-Id: Ib8aa32f178399c020ecfeab4f905933889dadb19
Refs: #3432
diff --git a/src/torrent-file.cpp b/src/torrent-file.cpp
index a9797fd..2b8fd15 100644
--- a/src/torrent-file.cpp
+++ b/src/torrent-file.cpp
@@ -21,9 +21,16 @@
 
 #include "torrent-file.hpp"
 
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+
 #include <algorithm>
 
 #include <boost/range/adaptors.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+namespace fs = boost::filesystem;
 
 namespace ndn {
 
@@ -59,18 +66,6 @@
   this->wireDecode(block);
 }
 
-const Name&
-TorrentFile::getName() const
-{
-  return Data::getName();
-}
-
-const Name&
-TorrentFile::getCommonPrefix() const
-{
-  return m_commonPrefix;
-}
-
 void
 TorrentFile::createSuffixCatalog()
 {
@@ -186,14 +181,14 @@
   // Check whether there is a TorrentFilePtr
   auto element = content.elements_begin();
   if (content.elements_end() == element) {
-    BOOST_THROW_EXCEPTION(Error(".Torrent-file with empty content"));
+    BOOST_THROW_EXCEPTION(Error("Torrent-file with empty content"));
   }
   element->parse();
   Name name(*element);
   if (name.empty())
-    BOOST_THROW_EXCEPTION(Error("Empty name included in the .torrent-file"));
+    BOOST_THROW_EXCEPTION(Error("Empty name included in the torrent-file"));
 
-  if (name.get(name.size() - 3) == name::Component(".torrent-file")) {
+  if (name.get(name.size() - 3) == name::Component("torrent-file")) {
     m_torrentFilePtr = name;
     ++element;
     m_commonPrefix = Name(*element);
@@ -209,11 +204,11 @@
     element->parse();
     Name fileManifestSuffix(*element);
     if (fileManifestSuffix.empty())
-      BOOST_THROW_EXCEPTION(Error("Empty manifest file name included in the .torrent-file"));
+      BOOST_THROW_EXCEPTION(Error("Empty manifest file name included in the torrent-file"));
     this->insertToSuffixCatalog(fileManifestSuffix);
   }
   if (m_suffixCatalog.size() == 0) {
-    BOOST_THROW_EXCEPTION(Error(".Torrent-file with empty catalog of file manifest names"));
+    BOOST_THROW_EXCEPTION(Error("Torrent-file with empty catalog of file manifest names"));
   }
 }
 
@@ -235,6 +230,74 @@
   m_suffixCatalog.clear();
 }
 
+std::pair<std::vector<TorrentFile>,
+          std::vector<std::pair<std::vector<FileManifest>,
+                                std::vector<Data>>>>
+TorrentFile::generate(const std::string& directoryPath,
+                      size_t namesPerSegment,
+                      size_t subManifestSize,
+                      size_t dataPacketSize,
+                      bool returnData)
+{
+  BOOST_ASSERT(0 < namesPerSegment);
+
+  std::vector<TorrentFile> torrentSegments;
+
+  fs::path path(directoryPath);
+  if (!fs::exists(path)) {
+    BOOST_THROW_EXCEPTION(Error(directoryPath + ": no such directory."));
+  }
+
+  Name directoryPathName(directoryPath);
+  fs::recursive_directory_iterator directoryPtr(fs::system_complete(directoryPath).string());
+
+  Name commonPrefix("/NTORRENT" +
+                    directoryPathName.getSubName(directoryPathName.size() - 1).toUri());
+
+  Name torrentName(commonPrefix.toUri() + "/torrent-file");
+  TorrentFile currentTorrentFile(torrentName, commonPrefix, {});
+  std::vector<std::pair<std::vector<FileManifest>, std::vector<Data>>> manifestPairs;
+
+  size_t manifestFileCounter = 0u;
+  for (fs::recursive_directory_iterator i = directoryPtr;
+       i != fs::recursive_directory_iterator();
+       ++i) {
+    Name manifestPrefix("/NTORRENT" +
+                        directoryPathName.getSubName(directoryPathName.size() - 1).toUri());
+    std::pair<std::vector<FileManifest>, std::vector<Data>> currentManifestPair =
+                                                    FileManifest::generate((*i).path().string(),
+                                                    manifestPrefix, subManifestSize,
+                                                    dataPacketSize, returnData);
+
+    if (manifestFileCounter != 0 && 0 == manifestFileCounter % namesPerSegment) {
+      torrentSegments.push_back(currentTorrentFile);
+      Name currentTorrentName = torrentName;
+      currentTorrentName.appendSequenceNumber(static_cast<int>(manifestFileCounter));
+      currentTorrentFile = TorrentFile(currentTorrentName, commonPrefix, {});
+    }
+    currentTorrentFile.insert(currentManifestPair.first[0].getName());
+    currentManifestPair.first.shrink_to_fit();
+    currentManifestPair.second.shrink_to_fit();
+    manifestPairs.push_back(currentManifestPair);
+    ++manifestFileCounter;
+  }
+
+  // Sign and append the last torrent-file
+  security::KeyChain keyChain;
+  keyChain.sign(currentTorrentFile, signingWithSha256());
+  torrentSegments.push_back(currentTorrentFile);
+
+  for (auto it = torrentSegments.rbegin() + 1; it != torrentSegments.rend(); ++it) {
+    auto next = it - 1;
+    it->setTorrentFilePtr(next->getFullName());
+    keyChain.sign(*it, signingWithSha256());
+  }
+
+  torrentSegments.shrink_to_fit();
+  manifestPairs.shrink_to_fit();
+  return std::make_pair(torrentSegments, manifestPairs);
+}
+
 } // namespace ntorrent
 
 } // namespace ndn
diff --git a/src/torrent-file.hpp b/src/torrent-file.hpp
index ea095ac..98e222c 100644
--- a/src/torrent-file.hpp
+++ b/src/torrent-file.hpp
@@ -22,6 +22,8 @@
 #ifndef TORRENT_FILE_HPP
 #define TORRENT_FILE_HPP
 
+#include "file-manifest.hpp"
+
 #include <ndn-cxx/name.hpp>
 #include <ndn-cxx/encoding/block.hpp>
 #include <ndn-cxx/data.hpp>
@@ -45,14 +47,14 @@
   };
 
   /**
-   * @brief Create a new empty .TorrentFile.
+   * @brief Create a new empty TorrentFile.
    */
   TorrentFile() = default;
 
   /**
-   * @brief Create a new .TorrentFile.
-   * @param torrentFileName The name of the .torrent-file
-   * @param torrentFilePtr A pointer (name) to the next segment of the .torrent-file
+   * @brief Create a new TorrentFile.
+   * @param torrentFileName The name of the torrent-file
+   * @param torrentFilePtr A pointer (name) to the next segment of the torrent-file
    * @param commonPrefix The common name prefix of the manifest file names included in the catalog
    * @param catalog The catalog containing the name of each file manifest
    */
@@ -62,8 +64,8 @@
               const std::vector<ndn::Name>& catalog);
 
   /**
-   * @brief Create a new .TorrentFile.
-   * @param torrentFileName The name of the .torrent-file
+   * @brief Create a new TorrentFile.
+   * @param torrentFileName The name of the torrent-file
    * @param commonPrefix The common name prefix of the manifest file names included in the catalog
    * @param catalog The catalog containing the name of each file manifest
    */
@@ -72,26 +74,26 @@
               const std::vector<ndn::Name>& catalog);
 
   /**
-   * @brief Create a new .TorrentFile
-   * @param block The block format of the .torrent-file
+   * @brief Create a new TorrentFile
+   * @param block The block format of the torrent-file
    */
   explicit
   TorrentFile(const Block& block);
 
   /**
-   * @brief Get the name of the .TorrentFile
+   * @brief Get the name of the TorrentFile
    */
   const Name&
   getName() const;
 
   /**
-   * @brief Get the common prefix of the file manifest names of this .torrent-file
+   * @brief Get the common prefix of the file manifest names of this torrent-file
    */
   const Name&
   getCommonPrefix() const;
 
   /**
-   * @brief Get a shared pointer to the name of the next segment of the .torrent-file.
+   * @brief Get a shared pointer to the name of the next segment of the torrent-file.
    *
    * If there is no next segment, it returns a nullptr
    */
@@ -111,10 +113,10 @@
   wireDecode(const Block& wire);
 
   /**
-   * @brief Finalize .torrent-file before signing the data packet
+   * @brief Finalize torrent-file before signing the data packet
    *
    * This method has to be called (every time) right before signing or encoding
-   * the .torrent-file
+   * the torrent-file
    */
   void
   finalize();
@@ -137,9 +139,33 @@
   size_t
   catalogSize() const;
 
+  /**
+   * @brief Given a directory path for the torrent file, it generates the torrent file
+   *
+   * @param directoryPath The path to the directory for which we are to create a torrent-file
+   * @param torrentFilePrefix The prefix to be used for the name of this torrent-file
+   * @param namesPerSegment The number of manifest names to be included in each segment of the
+   *        torrent-file
+   * @param returnData Determines whether the data would be returned in memory or it will be
+   *        stored on disk without being returned
+   *
+   * Generates the torrent-file for the directory at the specified 'directoryPath',
+   * splitting the torrent-file into multiple segments, each one of which contains
+   * at most 'namesPerSegment' number of manifest names
+   *
+   **/
+  static std::pair<std::vector<TorrentFile>,
+            std::vector<std::pair<std::vector<FileManifest>,
+                                  std::vector<Data>>>>
+  generate(const std::string& directoryPath,
+           size_t namesPerSegment,
+           size_t subManifestSize,
+           size_t dataPacketSize,
+           bool returnData = false);
+
 protected:
   /**
-   * @brief prepend .torrent file as a Content block to the encoder
+   * @brief prepend torrent file as a Content block to the encoder
    */
   template<encoding::Tag TAG>
   size_t
@@ -153,7 +179,7 @@
 
 private:
   /**
-   * @brief Check whether the .torrent-file has a pointer to the next segment
+   * @brief Check whether the torrent-file has a pointer to the next segment
    */
   bool
   hasTorrentFilePtr() const;
@@ -172,7 +198,7 @@
    * @brief Construct the catalog of long names from a catalog of suffixes for the file
    *        manifests' name
    *
-   * After decoding a .torrent-file from its wire format, we construct the catalog of
+   * After decoding a torrent-file from its wire format, we construct the catalog of
    * long names from the decoded common prefix and suffixes
    *
    */
@@ -185,6 +211,12 @@
   void
   insertToSuffixCatalog(const PartialName& suffix);
 
+  /**
+   * @brief Set the pointer of the current torrent-file segment to the next segment
+   */
+  void
+  setTorrentFilePtr(const Name& ptrName);
+
 private:
   Name m_commonPrefix;
   Name m_torrentFilePtr;
@@ -222,6 +254,25 @@
   m_suffixCatalog.push_back(suffix);
 }
 
+inline void
+TorrentFile::setTorrentFilePtr(const Name& ptrName)
+{
+  m_torrentFilePtr = ptrName;
+}
+
+inline const Name&
+TorrentFile::getName() const
+{
+  return Data::getName();
+}
+
+inline const Name&
+TorrentFile::getCommonPrefix() const
+{
+  return m_commonPrefix;
+}
+
+
 } // namespace ntorrent
 
 } // namespace ndn