Merge remote-tracking branch 'git.irl/master'

Conflicts:
	src/fetcher.cc
diff --git a/src/action-log.cc b/src/action-log.cc
index e33a221..9b2ee9c 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -90,7 +90,7 @@
     filename    TEXT NOT NULL,                                          \n\
     device_name BLOB NOT NULL,                                          \n\
     seq_no      INTEGER NOT NULL,                                       \n\
-    file_hash   BLOB, /* NULL if action is \"delete\" */                \n\
+    file_hash   BLOB NOT NULL,                                          \n\
     file_atime  TIMESTAMP,                                              \n\
     file_mtime  TIMESTAMP,                                              \n\
     file_ctime  TIMESTAMP,                                              \n\
@@ -101,6 +101,7 @@
 );                                                                      \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\
 ";
 
 
@@ -528,6 +529,22 @@
   return AddRemoteAction (deviceName, seqno, actionPco);
 }
 
+sqlite3_int64
+ActionLog::LogSize ()
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2 (m_db, "SELECT count(*) FROM ActionLog", -1, &stmt, 0);
+
+  sqlite3_int64 retval = -1;
+  if (sqlite3_step (stmt) == SQLITE_ROW)
+  {
+    retval = sqlite3_column_int64 (stmt, 0);
+  }
+
+  return retval;
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////////
 // SHOULD BE MOVED TO SEPARATE FILESTATE CLASS "EVENTUALLY"
 ///////////////////////////////////////////////////////////////////////////////////
@@ -636,36 +653,59 @@
 /**
  * @todo Implement checking modification time and permissions
  */
-bool
-ActionLog::KnownFileState(const std::string &filename, const Hash &hash
-                          /*, time_t mtime, int chmod*/)
+FileItemPtr
+ActionLog::LookupFile (const std::string &filename)
 {
   sqlite3_stmt *stmt;
-  sqlite3_prepare_v2 (m_db, "SELECT * FROM FileState WHERE filename = ? AND file_hash = ?;", -1, &stmt, 0);
+  sqlite3_prepare_v2 (m_db,
+                      "SELECT filename,device_name,seq_no,file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num "
+                      "       FROM FileState "
+                      "       WHERE type = 0 AND filename = ?", -1, &stmt, 0);
   sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
-  sqlite3_bind_blob(stmt, 2, hash.GetHash (), hash.GetHashBytes (), SQLITE_STATIC);
 
-  bool retval = false;
+  FileItemPtr retval;
   if (sqlite3_step (stmt) == SQLITE_ROW)
   {
-    retval = true;
+    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));
   }
   sqlite3_finalize (stmt);
 
   return retval;
 }
 
-sqlite3_int64
-ActionLog::LogSize ()
+FileItemsPtr
+ActionLog::LookupFilesForHash (const Hash &hash)
 {
   sqlite3_stmt *stmt;
-  sqlite3_prepare_v2 (m_db, "SELECT count(*) FROM ActionLog", -1, &stmt, 0);
+  sqlite3_prepare_v2 (m_db,
+                      "SELECT filename,device_name,seq_no,file_hash,strftime('%s', file_mtime),file_chmod,file_seg_num "
+                      "   FROM FileState "
+                      "   WHERE type = 0 AND file_hash = ?;", -1, &stmt, 0);
+  sqlite3_bind_blob(stmt, 1, hash.GetHash (), hash.GetHashBytes (), SQLITE_STATIC);
 
-  sqlite3_int64 retval = -1;
-  if (sqlite3_step (stmt) == SQLITE_ROW)
-  {
-    retval = sqlite3_column_int64 (stmt, 0);
-  }
+  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));
+
+      retval->push_back (file);
+    }
+
+  sqlite3_finalize (stmt);
 
   return retval;
 }
diff --git a/src/action-log.h b/src/action-log.h
index fec6e82..9833b92 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -25,6 +25,7 @@
 #include "db-helper.h"
 #include "sync-log.h"
 #include "action-item.pb.h"
+#include "file-item.pb.h"
 #include "ccnx-wrapper.h"
 #include "ccnx-pco.h"
 
@@ -34,6 +35,10 @@
 typedef boost::shared_ptr<ActionLog> ActionLogPtr;
 typedef boost::shared_ptr<ActionItem> ActionItemPtr;
 
+typedef std::list<FileItem> FileItems;
+typedef boost::shared_ptr<FileItem>  FileItemPtr;
+typedef boost::shared_ptr<FileItems> FileItemsPtr;
+
 class ActionLog : public DbHelper
 {
 public:
@@ -64,9 +69,6 @@
   ActionItemPtr
   AddLocalActionDelete (const std::string &filename);
 
-  bool
-  KnownFileState(const std::string &filename, const Hash &hash);
-
   //////////////////////////
   // Remote operations    //
   //////////////////////////
@@ -98,7 +100,14 @@
   ActionItemPtr
   LookupAction (const Ccnx::Name &actionName);
 
+  ///////////////////////////
+  // File state operations //
+  ///////////////////////////
+  FileItemPtr
+  LookupFile (const std::string &filename);
 
+  FileItemsPtr
+  LookupFilesForHash (const Hash &hash);
 
 public:
   // for test purposes
diff --git a/src/dispatcher.cc b/src/dispatcher.cc
index d9bc37a..ab2dc41 100644
--- a/src/dispatcher.cc
+++ b/src/dispatcher.cc
@@ -93,37 +93,31 @@
 Dispatcher::Did_LocalFile_AddOrModify_Execute (filesystem::path relativeFilePath)
 {
   filesystem::path absolutePath = m_rootDir / relativeFilePath;
-  if (filesystem::exists(absolutePath))
-    {
-      HashPtr hash = Hash::FromFileContent(absolutePath);
-      if (m_actionLog->KnownFileState(relativeFilePath.generic_string(), *hash))
-        {
-          // the file state is known; i.e. the detected changed file is identical to
-          // the file state kept in FileState table
-          // it is the case that backend puts the file fetched from remote;
-          // we should not publish action for this.
-        }
-      else
-        {
-          uintmax_t fileSize = filesystem::file_size(absolutePath);
-          int seg_num;
-          tie (hash, seg_num) = m_objectManager.localFileToObjects (absolutePath, m_localUserName);
-
-          time_t wtime = filesystem::last_write_time(absolutePath);
-          filesystem::file_status stat = filesystem::status(absolutePath);
-          int mode = stat.permissions();
-
-          m_actionLog->AddLocalActionUpdate (relativeFilePath.generic_string(), *hash, wtime, mode, seg_num);
-          // publish the file
-
-          // notify SyncCore to propagate the change
-          m_core->localStateChanged();
-        }
-    }
-  else
+  if (!filesystem::exists(absolutePath))
     {
       BOOST_THROW_EXCEPTION (Error::Dispatcher() << error_info_str("Update non exist file: " + absolutePath.string() ));
     }
+
+  FileItemPtr currentFile = m_actionLog->LookupFile (relativeFilePath.generic_string ());
+  if (currentFile &&
+      *Hash::FromFileContent (absolutePath) == Hash (currentFile->file_hash ().c_str (), currentFile->file_hash ().size ()) &&
+      last_write_time (absolutePath) == currentFile->mtime () &&
+      status (absolutePath).permissions () == static_cast<filesystem::perms> (currentFile->mode ()))
+    {
+      _LOG_ERROR ("Got notification about the same file [" << relativeFilePath << "]");
+      return;
+    }
+
+  int seg_num;
+  HashPtr hash;
+  tie (hash, seg_num) = m_objectManager.localFileToObjects (absolutePath, m_localUserName);
+
+  m_actionLog->AddLocalActionUpdate (relativeFilePath.generic_string(),
+                                     *hash,
+                                     last_write_time (absolutePath), status (absolutePath).permissions (), seg_num);
+
+  // notify SyncCore to propagate the change
+  m_core->localStateChanged();
 }
 
 void
@@ -135,6 +129,19 @@
 void
 Dispatcher::Did_LocalFile_Delete_Execute (filesystem::path relativeFilePath)
 {
+  filesystem::path absolutePath = m_rootDir / relativeFilePath;
+  if (!filesystem::exists(absolutePath))
+    {
+      BOOST_THROW_EXCEPTION (Error::Dispatcher() << error_info_str("Delete notification but file exists: " + absolutePath.string() ));
+    }
+
+  FileItemPtr currentFile = m_actionLog->LookupFile (relativeFilePath.generic_string ());
+  if (!currentFile)
+    {
+      _LOG_ERROR ("File already deleted [" << relativeFilePath << "]");
+      return;
+    }
+
   m_actionLog->AddLocalActionDelete (relativeFilePath.generic_string());
   // notify SyncCore to propagate the change
   m_core->localStateChanged();
@@ -267,24 +274,30 @@
 Dispatcher::Did_FetchManager_FileFetchComplete_Execute (Ccnx::Name deviceName, Ccnx::Name fileBaseName)
 {
   _LOG_DEBUG ("Finished fetching " << deviceName << ", fileBaseName: " << fileBaseName);
-  // int size = fileNamePrefix.size();
-  // Bytes hashBytes = fileNamePrefix.getComp(size - 1);
-  // Hash hash(head(hashBytes), hashBytes.size());
-  // ostringstream oss;
-  // oss << hash;
-  // string hashString = oss.str();
 
-  // if (m_objectDbMap.find(hashString) != m_objectDbMap.end())
-  // {
-  //   // remove the db handle
-  //   m_objectDbMap.erase(hashString);
-  // }
-  // else
-  // {
-  //   cout << "no db available for this file: " << fileNamePrefix << endl;
-  // }
+  const Bytes &hashBytes = fileBaseName.getCompFromBack (1);
+  Hash hash (head (hashBytes), hashBytes.size ());
 
-  // query the action table to get the path on local file system
-  // m_objectManager.objectsToLocalFile(deviceName, hash, relativeFilePath);
+  FileItemsPtr filesToAssemble = m_actionLog->LookupFilesForHash (hash);
 
+  for (FileItems::iterator file = filesToAssemble->begin ();
+       file != filesToAssemble->end ();
+       file++)
+    {
+      boost::filesystem::path filePath = m_rootDir / file->filename ();
+      m_objectManager.objectsToLocalFile (deviceName, hash, filePath);
+
+      last_write_time (filePath, file->mtime ());
+      permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
+    }
+
+  if (m_objectDbMap.find (hash) != m_objectDbMap.end())
+  {
+    // remove the db handle
+    m_objectDbMap.erase (hash);
+  }
+  else
+  {
+    _LOG_ERROR ("no db available for this file: " << hash);
+  }
 }
diff --git a/src/object-manager.cc b/src/object-manager.cc
index 4e98244..cf94a7b 100644
--- a/src/object-manager.cc
+++ b/src/object-manager.cc
@@ -96,6 +96,11 @@
       return false;
     }
 
+  if (!exists (file.parent_path ()))
+    {
+      create_directories (file.parent_path ());
+    }
+
   fs::ofstream off (file, std::ios::out | std::ios::binary);
   ObjectDb fileDb (m_folder, hashStr);