Moving out FileState operations into a separate class/database

Change-Id: I9314ef92632253aaa843dee98933795f2d8e076f
diff --git a/src/file-state.cc b/src/file-state.cc
index 343d6a5..e4a2c1b 100644
--- a/src/file-state.cc
+++ b/src/file-state.cc
@@ -20,3 +20,354 @@
  */
 
 #include "file-state.h"
+#include "logging.h"
+
+INIT_LOGGER ("FileState");
+
+using namespace boost;
+using namespace std;
+
+const std::string INIT_DATABASE = "\
+                                                                        \n\
+CREATE TABLE FileState (                                                \n\
+    type        INTEGER NOT NULL, /* 0 - newest, 1 - oldest */          \n\
+    filename    TEXT NOT NULL,                                          \n\
+    directory   TEXT,                                                   \n\
+    device_name BLOB NOT NULL,                                          \n\
+    seq_no      INTEGER NOT NULL,                                       \n\
+    file_hash   BLOB NOT NULL,                                          \n\
+    file_atime  TIMESTAMP,                                              \n\
+    file_mtime  TIMESTAMP,                                              \n\
+    file_ctime  TIMESTAMP,                                              \n\
+    file_chmod  INTEGER,                                                \n\
+    file_seg_num INTEGER,                                               \n\
+    is_complete INTEGER,                                               \n\
+                                                                        \n\
+    PRIMARY KEY (type, filename)                                        \n\
+);                                                                      \n\
+                                                                        \n\
+CREATE INDEX FileState_device_name_seq_no ON FileState (device_name, seq_no); \n\
+CREATE INDEX FileState_type_file_hash ON FileState (type, file_hash);   \n\
+";
+
+FileState::FileState (const boost::filesystem::path &path)
+  : DbHelper (path / ".chronoshare", "file-state.db")
+{
+  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 ()
+{
+}
+
+void
+FileState::UpdateFile (const std::string &filename, const Hash &hash, const Ccnx::CcnxCharbuf &device_name, sqlite3_int64 seq_no,
+                       time_t atime, time_t mtime, time_t ctime, int mode, int seg_num)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db, "UPDATE FileState "
+                      "SET "
+                      "device_name=?, seq_no=?, "
+                      "file_hash=?,"
+                      "file_atime=datetime(?, 'unixepoch'),"
+                      "file_mtime=datetime(?, 'unixepoch'),"
+                      "file_ctime=datetime(?, 'unixepoch'),"
+                      "file_chmod=?, "
+                      "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_int64 (stmt, 2, seq_no);
+  sqlite3_bind_blob  (stmt, 3, hash.GetHash (), hash.GetHashBytes (), SQLITE_TRANSIENT);
+  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_step (stmt);
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_ROW && sqlite3_errcode (m_db) != SQLITE_DONE,
+                   sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
+  int affected_rows = sqlite3_changes (m_db);
+  if (affected_rows == 0) // file didn't exist
+    {
+      sqlite3_stmt *stmt;
+      sqlite3_prepare_v2 (m_db, "INSERT INTO FileState "
+                          "(type,filename,device_name,seq_no,file_hash,file_atime,file_mtime,file_ctime,file_chmod,file_seg_num) "
+                          "VALUES (0, ?, ?, ?, ?, "
+                          "datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), ?, ?)", -1, &stmt, 0);
+
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+      sqlite3_bind_text  (stmt, 1, filename.c_str (), -1, SQLITE_STATIC);
+      sqlite3_bind_blob  (stmt, 2, device_name.buf (), device_name.length (), SQLITE_STATIC);
+      sqlite3_bind_int64 (stmt, 3, seq_no);
+      sqlite3_bind_blob  (stmt, 4, hash.GetHash (), hash.GetHashBytes (), SQLITE_STATIC);
+      sqlite3_bind_int64 (stmt, 5, atime);
+      sqlite3_bind_int64 (stmt, 6, mtime);
+      sqlite3_bind_int64 (stmt, 7, ctime);
+      sqlite3_bind_int   (stmt, 8, mode);
+      sqlite3_bind_int   (stmt, 9, seg_num);
+      // sqlite3_bind_text  (stmt, 10, filename.c_str (), -1, SQLITE_STATIC);
+
+      sqlite3_step (stmt);
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE,
+                       sqlite3_errmsg (m_db));
+      sqlite3_finalize (stmt);
+
+      sqlite3_prepare_v2 (m_db, "UPDATE FileState SET directory=directory_name(filename) WHERE filename=?", -1, &stmt, 0);
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+
+      sqlite3_bind_text  (stmt, 1, filename.c_str (), -1, SQLITE_STATIC);
+      sqlite3_step (stmt);
+      _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE,
+                       sqlite3_errmsg (m_db));
+      sqlite3_finalize (stmt);
+    }
+}
+
+
+void
+FileState::DeleteFile (const std::string &filename)
+{
+
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db, "DELETE FROM FileState WHERE type=0 AND filename=?", -1, &stmt, 0);
+  sqlite3_bind_text (stmt, 1, filename.c_str (), -1, SQLITE_STATIC);
+
+  _LOG_DEBUG ("Delete " << filename);
+
+  sqlite3_step (stmt);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE,
+                   sqlite3_errmsg (m_db));
+  sqlite3_finalize (stmt);
+}
+
+
+void
+FileState::SetFileComplete (const std::string &filename)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db,
+                      "UPDATE FileState SET is_complete=1 WHERE type = 0 AND filename = ?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+  sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
+
+  sqlite3_step (stmt);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+}
+
+
+/**
+ * @todo Implement checking modification time and permissions
+ */
+FileItemPtr
+FileState::LookupFile (const std::string &filename)
+{
+  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 filename = ?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+  sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
+
+  FileItemPtr retval;
+  if (sqlite3_step (stmt) == SQLITE_ROW)
+  {
+    retval = make_shared<FileItem> ();
+    retval->set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 0)), sqlite3_column_bytes (stmt, 0));
+    retval->set_device_name (sqlite3_column_blob  (stmt, 1), sqlite3_column_bytes (stmt, 1));
+    retval->set_seq_no      (sqlite3_column_int64 (stmt, 2));
+    retval->set_file_hash   (sqlite3_column_blob  (stmt, 3), sqlite3_column_bytes (stmt, 3));
+    retval->set_mtime       (sqlite3_column_int   (stmt, 4));
+    retval->set_mode        (sqlite3_column_int   (stmt, 5));
+    retval->set_seg_num     (sqlite3_column_int64 (stmt, 6));
+    retval->set_is_complete (sqlite3_column_int   (stmt, 7));
+  }
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+  sqlite3_finalize (stmt);
+
+  return retval;
+}
+
+FileItemsPtr
+FileState::LookupFilesForHash (const Hash &hash)
+{
+  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 file_hash = ?", -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+  sqlite3_bind_blob(stmt, 1, hash.GetHash (), hash.GetHashBytes (), SQLITE_STATIC);
+  _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;
+      file.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 0)), sqlite3_column_bytes (stmt, 0));
+      file.set_device_name (sqlite3_column_blob  (stmt, 1), sqlite3_column_bytes (stmt, 1));
+      file.set_seq_no      (sqlite3_column_int64 (stmt, 2));
+      file.set_file_hash   (sqlite3_column_blob  (stmt, 3), sqlite3_column_bytes (stmt, 3));
+      file.set_mtime       (sqlite3_column_int   (stmt, 4));
+      file.set_mode        (sqlite3_column_int   (stmt, 5));
+      file.set_seg_num     (sqlite3_column_int64 (stmt, 6));
+      file.set_is_complete (sqlite3_column_int   (stmt, 7));
+
+      retval->push_back (file);
+    }
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
+  return retval;
+}
+
+
+FileItemsPtr
+FileState::LookupFilesInFolder (const std::string &folder)
+{
+  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);
+  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> ();
+  while (sqlite3_step (stmt) == SQLITE_ROW)
+    {
+      FileItem file;
+      file.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 0)), sqlite3_column_bytes (stmt, 0));
+      file.set_device_name (sqlite3_column_blob  (stmt, 1), sqlite3_column_bytes (stmt, 1));
+      file.set_seq_no      (sqlite3_column_int64 (stmt, 2));
+      file.set_file_hash   (sqlite3_column_blob  (stmt, 3), sqlite3_column_bytes (stmt, 3));
+      file.set_mtime       (sqlite3_column_int   (stmt, 4));
+      file.set_mode        (sqlite3_column_int   (stmt, 5));
+      file.set_seg_num     (sqlite3_column_int64 (stmt, 6));
+      file.set_is_complete (sqlite3_column_int   (stmt, 7));
+
+      retval->push_back (file);
+    }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+
+  return retval;
+}
+
+FileItemsPtr
+FileState::LookupFilesInFolderRecursively (const std::string &folder)
+{
+  _LOG_DEBUG ("LookupFilesInFolderRecursively: [" << folder << "]");
+
+  sqlite3_stmt *stmt;
+  if (folder != "")
+    {
+      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);
+      _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));
+    }
+  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);
+    }
+
+  _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;
+      file.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 0)), sqlite3_column_bytes (stmt, 0));
+      file.set_device_name (sqlite3_column_blob  (stmt, 1), sqlite3_column_bytes (stmt, 1));
+      file.set_seq_no      (sqlite3_column_int64 (stmt, 2));
+      file.set_file_hash   (sqlite3_column_blob  (stmt, 3), sqlite3_column_bytes (stmt, 3));
+      file.set_mtime       (sqlite3_column_int   (stmt, 4));
+      file.set_mode        (sqlite3_column_int   (stmt, 5));
+      file.set_seg_num     (sqlite3_column_int64 (stmt, 6));
+      file.set_is_complete (sqlite3_column_int   (stmt, 7));
+
+      retval->push_back (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)
+{
+  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;
+    }
+
+  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);
+    }
+}