Merge "timed-wait for fetcher in hope to solve crash problem"
diff --git a/src/action-log.cc b/src/action-log.cc
index 85a691c..19c5c9d 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -31,7 +31,6 @@
 INIT_LOGGER ("ActionLog");
 
 const std::string INIT_DATABASE = "\
-    PRAGMA foreign_keys = OFF;  /* foreign key here are very bad idea...*/   \n\
 CREATE TABLE ActionLog (                                                \n\
     device_name BLOB NOT NULL,                                          \n\
     seq_no      INTEGER NOT NULL,                                       \n\
@@ -80,32 +79,12 @@
                              strftime('%s', NEW.file_atime),strftime('%s', NEW.file_mtime),strftime('%s', NEW.file_ctime), \
                              NEW.file_chmod, NEW.file_seg_num); /* function that applies action and adds record the FileState */  \n \
     END;                                                                \n\
-                                                                        \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\
 ";
 
-static void xTrace (void*, const char* q)
-{
-  _LOG_TRACE ("SQLITE: " << q);
-}
+// static void xTrace (void*, const char* q)
+// {
+//   _LOG_TRACE ("SQLITE: " << q);
+// }
 
 ActionLog::ActionLog (Ccnx::CcnxWrapperPtr ccnx, const boost::filesystem::path &path,
                       SyncLogPtr syncLog,
@@ -134,29 +113,7 @@
                              << errmsg_info_str ("Cannot create function ``apply_action''"));
     }
 
-
-  res = sqlite3_create_function (m_db, "directory_name", -1, SQLITE_ANY, 0,
-                                     ActionLog::directory_name_xFun,
-                                     0, 0);
-  if (res != SQLITE_OK)
-    {
-      BOOST_THROW_EXCEPTION (Error::Db ()
-                             << errmsg_info_str ("Cannot create function ``directory_name''"));
-    }
-
-  // // "Upgrading" database
-  // sqlite3_exec (m_db, "ALTER TABLE FileState ADD COLUMN directory TEXT", NULL, NULL, NULL);
-  // _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
-
-  // sqlite3_exec (m_db, "CREATE INDEX FileState_directory ON FileState (directory)", NULL, NULL, NULL);
-  // _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
-
-  // sqlite3_exec (m_db, "UPDATE FileState SET directory = directory_name(filename) WHERE directory IS NULL", NULL, NULL, NULL);
-  // _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
-
-  // // Another "upgrade"
-  // sqlite3_exec (m_db, "BEGIN TRANSACTION; ALTER TABLE FileState ADD COLUMN is_complete INTEGER; UPDATE FileState SET is_complete = 1; END TRANSACTION;", NULL, NULL, NULL);
-  // _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+  m_fileState = make_shared<FileState> (path);
 }
 
 tuple<sqlite3_int64 /*version*/, Ccnx::CcnxCharbufPtr /*device name*/, sqlite3_int64 /*seq_no*/>
@@ -300,11 +257,11 @@
 
   sqlite3_finalize (stmt);
 
-  // set complete for local file
-  SetFileComplete(filename);
-
   sqlite3_exec (m_db, "END TRANSACTION;", 0,0,0);
 
+  // set complete for local file
+  m_fileState->SetFileComplete(filename);
+
   return item;
 }
 
@@ -601,9 +558,8 @@
   return retval;
 }
 
-
 ///////////////////////////////////////////////////////////////////////////////////
-// SHOULD BE MOVED TO SEPARATE FILESTATE CLASS "EVENTUALLY"
+///////////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////////
 
 void
@@ -638,85 +594,13 @@
 
       _LOG_DEBUG ("Update " << filename << " " << atime << " " << mtime << " " << ctime << " " << hash);
 
-      sqlite3_stmt *stmt;
-      sqlite3_prepare_v2 (the->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);
+      the->m_fileState->UpdateFile (filename, hash, device_name, seq_no, atime, mtime, ctime, mode, seg_num);
 
-      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 (the->m_db) != SQLITE_ROW && sqlite3_errcode (the->m_db) != SQLITE_DONE,
-                       sqlite3_errmsg (the->m_db));
-
-      sqlite3_finalize (stmt);
-
-      int affected_rows = sqlite3_changes (the->m_db);
-      if (affected_rows == 0) // file didn't exist
-        {
-          sqlite3_stmt *stmt;
-          sqlite3_prepare_v2 (the->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 (the->m_db) != SQLITE_OK, sqlite3_errmsg (the->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 (the->m_db) != SQLITE_DONE,
-                           sqlite3_errmsg (the->m_db));
-          sqlite3_finalize (stmt);
-
-          sqlite3_prepare_v2 (the->m_db, "UPDATE FileState SET directory=directory_name(filename) WHERE filename=?", -1, &stmt, 0);
-          _LOG_DEBUG_COND (sqlite3_errcode (the->m_db) != SQLITE_OK, sqlite3_errmsg (the->m_db));
-
-          sqlite3_bind_text  (stmt, 1, filename.c_str (), -1, SQLITE_STATIC);
-          sqlite3_step (stmt);
-          _LOG_DEBUG_COND (sqlite3_errcode (the->m_db) != SQLITE_DONE,
-                           sqlite3_errmsg (the->m_db));
-          sqlite3_finalize (stmt);
-
-        }
+      // no callback here
     }
   else if (action == 1) // delete
     {
-      sqlite3_stmt *stmt;
-      sqlite3_prepare_v2 (the->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 (the->m_db) != SQLITE_DONE,
-                       sqlite3_errmsg (the->m_db));
-      sqlite3_finalize (stmt);
+      the->m_fileState->DeleteFile (filename);
 
       the->m_onFileRemoved (filename);
     }
@@ -725,215 +609,3 @@
 }
 
 
-void
-ActionLog::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
-ActionLog::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
-ActionLog::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
-ActionLog::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
-ActionLog::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
-ActionLog::ActionLog::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);
-    }
-}
diff --git a/src/action-log.h b/src/action-log.h
index b175001..f0ed9cd 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -37,7 +37,6 @@
 typedef boost::shared_ptr<ActionItem> ActionItemPtr;
 
 class ActionLog : public DbHelper
-                , public FileState
 {
 public:
   typedef boost::function<void (std::string /*filename*/, Ccnx::Name /*device_name*/, sqlite3_int64 /*seq_no*/,
@@ -100,28 +99,10 @@
   ActionItemPtr
   LookupAction (const Ccnx::Name &actionName);
 
-  /**
-   * @brief Set "complete" flag
-   *
-   * The call will do nothing if FileState does not have a record for the file (e.g., file got subsequently deleted)
-   */
-  void
-  SetFileComplete (const std::string &filename);
 
-  ///////////////////////////
-  // File state operations //
-  ///////////////////////////
-  virtual FileItemPtr
-  LookupFile (const std::string &filename);
-
-  virtual FileItemsPtr
-  LookupFilesForHash (const Hash &hash);
-
-  virtual FileItemsPtr
-  LookupFilesInFolder (const std::string &folder);
-
-  virtual FileItemsPtr
-  LookupFilesInFolderRecursively (const std::string &folder);
+  //
+  inline FileStatePtr
+  GetFileState ();
 
 public:
   // for test purposes
@@ -135,11 +116,9 @@
   static void
   apply_action_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
 
-  static void
-  directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
-
 private:
   SyncLogPtr m_syncLog;
+  FileStatePtr m_fileState;
 
   Ccnx::CcnxWrapperPtr m_ccnx;
   std::string m_sharedFolderName;
@@ -153,5 +132,11 @@
 struct ActionLog : virtual boost::exception, virtual std::exception { };
 }
 
+inline FileStatePtr
+ActionLog::GetFileState ()
+{
+  return m_fileState;
+}
+
 
 #endif // ACTION_LOG_H
diff --git a/src/dispatcher.cc b/src/dispatcher.cc
index 22fbdbd..38ba37e 100644
--- a/src/dispatcher.cc
+++ b/src/dispatcher.cc
@@ -60,6 +60,8 @@
                                        // bind (&Dispatcher::Did_ActionLog_ActionApply_AddOrModify, this, _1, _2, _3, _4, _5, _6, _7),
                                        ActionLog::OnFileAddedOrChangedCallback (), // don't really need this callback
                                        bind (&Dispatcher::Did_ActionLog_ActionApply_Delete, this, _1));
+  m_fileState = m_actionLog->GetFileState ();
+
   Name syncPrefix = Name(BROADCAST_DOMAIN)(CHRONOSHARE_APP)(sharedFolder);
 
   // m_server needs a different ccnx face
@@ -157,7 +159,7 @@
       return;
     }
 
-  FileItemPtr currentFile = m_actionLog->LookupFile (relativeFilePath.generic_string ());
+  FileItemPtr currentFile = m_fileState->LookupFile (relativeFilePath.generic_string ());
   if (currentFile &&
       *Hash::FromFileContent (absolutePath) == Hash (currentFile->file_hash ().c_str (), currentFile->file_hash ().size ())
       // The following two are commented out to prevent front end from reporting intermediate files
@@ -214,7 +216,7 @@
       return;
     }
 
-  FileItemPtr currentFile = m_actionLog->LookupFile (relativeFilePath.generic_string ());
+  FileItemPtr currentFile = m_fileState->LookupFile (relativeFilePath.generic_string ());
   if (!currentFile)
     {
       _LOG_ERROR ("File already deleted [" << relativeFilePath << "]");
@@ -388,7 +390,7 @@
     _LOG_ERROR ("no db available for this file: " << hash);
   }
 
-  FileItemsPtr filesToAssemble = m_actionLog->LookupFilesForHash (hash);
+  FileItemsPtr filesToAssemble = m_fileState->LookupFilesForHash (hash);
 
   for (FileItems::iterator file = filesToAssemble->begin ();
        file != filesToAssemble->end ();
@@ -420,7 +422,7 @@
             last_write_time (filePath, file->mtime ());
             permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
 
-            m_actionLog->SetFileComplete (file->filename ());
+            m_fileState->SetFileComplete (file->filename ());
           }
         else
           {
diff --git a/src/dispatcher.h b/src/dispatcher.h
index 100123f..902ec62 100644
--- a/src/dispatcher.h
+++ b/src/dispatcher.h
@@ -77,9 +77,6 @@
   HashPtr
   SyncRoot() { return m_core->root(); }
 
-  FileState *
-  GetFileState () { return m_actionLog.get (); }
-
 private:
   void
   Did_LocalFile_AddOrModify_Execute (boost::filesystem::path relativeFilepath); // cannot be const & for Execute event!!! otherwise there will be segfault
@@ -164,6 +161,7 @@
   SyncCore *m_core;
   SyncLogPtr   m_syncLog;
   ActionLogPtr m_actionLog;
+  FileStatePtr m_fileState;
 
   boost::filesystem::path m_rootDir;
   Executor m_executor;
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);
+    }
+}
diff --git a/src/file-state.h b/src/file-state.h
index c7396f4..91bb3d2 100644
--- a/src/file-state.h
+++ b/src/file-state.h
@@ -22,6 +22,9 @@
 #ifndef FILE_STATE_H
 #define FILE_STATE_H
 
+#include "db-helper.h"
+
+#include "ccnx-name.h"
 #include "file-item.pb.h"
 #include "hash-helper.h"
 
@@ -35,22 +38,64 @@
 typedef boost::shared_ptr<FileItems> FileItemsPtr;
 
 
-class FileState
+class FileState : public DbHelper
 {
 public:
-  virtual FileItemPtr
-  LookupFile (const std::string &filename) = 0;
+  FileState (const boost::filesystem::path &path);
+  ~FileState ();
 
-  virtual FileItemsPtr
-  LookupFilesForHash (const Hash &hash) = 0;
+  /**
+   * @brief Update or add a file
+   */
+  void
+  UpdateFile (const std::string &filename, const Hash &hash, const Ccnx::CcnxCharbuf &device_name, sqlite3_int64 seqno,
+              time_t atime, time_t mtime, time_t ctime, int mode, int seg_num);
 
-  virtual FileItemsPtr
-  LookupFilesInFolder (const std::string &folder) = 0;
+  /**
+   * @brief Delete file
+   */
+  void
+  DeleteFile (const std::string &filename);
 
-  virtual FileItemsPtr
-  LookupFilesInFolderRecursively (const std::string &folder) = 0;
+  /**
+   * @brief Set "complete" flag
+   *
+   * The call will do nothing if FileState does not have a record for the file (e.g., file got subsequently deleted)
+   */
+  void
+  SetFileComplete (const std::string &filename);
+
+  /**
+   * @brief Lookup file state using file name
+   */
+  FileItemPtr
+  LookupFile (const std::string &filename) ;
+
+  /**
+   * @brief Lookup file state using content hash (multiple items may be returned)
+   */
+  FileItemsPtr
+  LookupFilesForHash (const Hash &hash);
+
+  /**
+   * @brief Lookup all files in the specified folder
+   */
+  FileItemsPtr
+  LookupFilesInFolder (const std::string &folder);
+
+  /**
+   * @brief Recursively lookup all files in the specified folder
+   */
+  FileItemsPtr
+  LookupFilesInFolderRecursively (const std::string &folder);
+
+private:
+  static void
+  directory_name_xFun (sqlite3_context *context, int argc, sqlite3_value **argv);
 };
 
+typedef boost::shared_ptr<FileState> FileStatePtr;
+
 namespace Error {
 struct FileState : virtual boost::exception, virtual std::exception { };
 }
diff --git a/src/sync-log.cc b/src/sync-log.cc
index 3ce8cfe..74506c6 100644
--- a/src/sync-log.cc
+++ b/src/sync-log.cc
@@ -124,26 +124,6 @@
   sqlite3_finalize (stmt);
 }
 
-// void
-// SyncLog::initYP(map<Name, Name> &yp)
-// {
-//   sqlite3_stmt *stmt;
-//   sqlite3_prepare_v2(m_db, "SELECT device_name, last_known_locator FROM SyncNodes;", -1, &stmt, 0);
-
-//   while (sqlite3_step(stmt) == SQLITE_ROW)
-//   {
-//     Name deviceName((const unsigned char *)sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0));
-//     Name locator;
-//     if (sqlite3_column_type(stmt, 1) == SQLITE_BLOB)
-//     {
-//       locator = Name((const unsigned char *)sqlite3_column_blob(stmt, 1), sqlite3_column_bytes(stmt, 1));
-//     }
-//     yp.insert(make_pair(deviceName, locator));
-//   }
-
-//   sqlite3_finalize(stmt);
-// }
-
 sqlite3_int64
 SyncLog::GetNextLocalSeqNo ()
 {
@@ -371,7 +351,7 @@
 SyncLog::UpdateLocator(const Name &deviceName, const Name &locator)
 {
   sqlite3_stmt *stmt;
-  sqlite3_prepare_v2 (m_db, "UPDATE SyncNodes SET last_known_locator=? WHERE device_name=?;", -1, &stmt, 0);
+  sqlite3_prepare_v2 (m_db, "UPDATE SyncNodes SET last_known_locator=?,last_update=datetime('now') WHERE device_name=?;", -1, &stmt, 0);
   Ccnx::CcnxCharbufPtr nameBuf = deviceName;
   Ccnx::CcnxCharbufPtr locatorBuf = locator;
   sqlite3_bind_blob (stmt, 1, locatorBuf->buf(), locatorBuf->length(), SQLITE_STATIC);