Deletion detection
diff --git a/src/action-log.cc b/src/action-log.cc
index 32820b6..2bab7ca 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -88,6 +88,7 @@
 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\
@@ -104,6 +105,10 @@
 CREATE INDEX FileState_type_file_hash ON FileState (type, file_hash);   \n\
 ";
 
+static void xTrace (void*, const char* q)
+{
+  _LOG_TRACE ("SQLITE: " << q);
+}
 
 ActionLog::ActionLog (Ccnx::CcnxWrapperPtr ccnx, const boost::filesystem::path &path,
                       SyncLogPtr syncLog,
@@ -127,6 +132,21 @@
       BOOST_THROW_EXCEPTION (Error::Db ()
                              << 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);
+  sqlite3_exec (m_db, "CREATE INDEX FileState_directory ON FileState (directory)", NULL, NULL, NULL);
+  sqlite3_exec (m_db, "UPDATE FileState SET directory = directory_name(filename) WHERE directory IS NULL", NULL, NULL, NULL);
 }
 
 tuple<sqlite3_int64 /*version*/, Ccnx::CcnxCharbufPtr /*device name*/, sqlite3_int64 /*seq_no*/>
@@ -263,7 +283,7 @@
 
   sqlite3_step (stmt);
 
-  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK && sqlite3_errcode (m_db) != SQLITE_ROW, sqlite3_errmsg (m_db));
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
 
   sqlite3_finalize (stmt);
 
@@ -314,14 +334,14 @@
   sqlite3_bind_blob  (stmt, 1, device_name->buf (), device_name->length (), SQLITE_TRANSIENT);
   sqlite3_bind_int64 (stmt, 2, seq_no);
   sqlite3_bind_int   (stmt, 3, 1);
-  sqlite3_bind_text  (stmt, 4, filename.c_str (), filename.size (), SQLITE_TRANSIENT);
+  sqlite3_bind_text  (stmt, 4, filename.c_str (), filename.size (), SQLITE_TRANSIENT);  // file
+
   sqlite3_bind_int64 (stmt, 5, version);
   sqlite3_bind_int64 (stmt, 6, action_time);
 
   sqlite3_bind_blob  (stmt, 7, parent_device_name->buf (), parent_device_name->length (), SQLITE_TRANSIENT);
   sqlite3_bind_int64 (stmt, 8, parent_seq_no);
 
-
   ActionItemPtr item = make_shared<ActionItem> ();
   item->set_action (ActionItem::DELETE);
   item->set_filename (filename);
@@ -342,7 +362,7 @@
 
   sqlite3_step (stmt);
 
-  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK && sqlite3_errcode (m_db) != SQLITE_ROW, sqlite3_errmsg (m_db));
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
 
   // cout << Ccnx::Name (parent_device_name) << endl;
 
@@ -417,7 +437,7 @@
     {
       _LOG_TRACE ("No action found for name: " << actionName);
     }
-  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK && sqlite3_errcode (m_db) != SQLITE_ROW, sqlite3_errmsg (m_db));
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_ROW, sqlite3_errmsg (m_db));
   sqlite3_finalize (stmt);
 
   return retval;
@@ -495,7 +515,7 @@
 
   // if action needs to be applied to file state, the trigger will take care of it
 
-  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK && sqlite3_errcode (m_db) != SQLITE_ROW, sqlite3_errmsg (m_db));
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
 
   sqlite3_finalize (stmt);
 
@@ -590,7 +610,7 @@
                           "file_mtime=datetime(?, 'unixepoch'),"
                           "file_ctime=datetime(?, 'unixepoch'),"
                           "file_chmod=?, "
-                          "file_seg_num=? "
+                          "file_seg_num=?, "
                           "WHERE type=0 AND filename=?", -1, &stmt, 0);
 
       sqlite3_bind_blob  (stmt, 1, device_name.buf (), device_name.length (), SQLITE_TRANSIENT);
@@ -605,7 +625,7 @@
 
       sqlite3_step (stmt);
 
-      _LOG_DEBUG_COND (sqlite3_errcode (the->m_db) != SQLITE_OK,
+      _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);
@@ -615,9 +635,9 @@
         {
           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) "
+                              "(type,filename,device_name,seq_no,file_hash,file_atime,file_mtime,file_ctime,file_chmod,file_seg_num,directory) "
                               "VALUES (0, ?, ?, ?, ?, "
-                              "datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), ?, ?)", -1, &stmt, 0);
+                              "datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), ?, ?, directory_name(?))", -1, &stmt, 0);
 
           sqlite3_bind_text  (stmt, 1, filename.c_str (), -1, SQLITE_TRANSIENT);
           sqlite3_bind_blob  (stmt, 2, device_name.buf (), device_name.length (), SQLITE_TRANSIENT);
@@ -628,9 +648,10 @@
           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_TRANSIENT);
 
           sqlite3_step (stmt);
-          _LOG_DEBUG_COND (sqlite3_errcode (the->m_db) != SQLITE_OK,
+          _LOG_DEBUG_COND (sqlite3_errcode (the->m_db) != SQLITE_DONE,
                            sqlite3_errmsg (the->m_db));
           sqlite3_finalize (stmt);
         }
@@ -711,3 +732,118 @@
 
   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 "
+                      "   FROM FileState "
+                      "   WHERE type = 0 AND directory = ?", -1, &stmt, 0);
+  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));
+
+      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)
+{
+  // sqlite3_trace(m_db, xTrace, NULL);
+
+  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 "
+                          "   FROM FileState "
+                          "   WHERE type = 0 AND (directory = ? OR directory LIKE ?)", -1, &stmt, 0);
+      sqlite3_bind_text (stmt, 1, folder.c_str (), folder.size (), SQLITE_STATIC);
+
+      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);
+    }
+  else
+    {
+      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", -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));
+
+      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 ();
+
+  sqlite3_result_text (context, dirPath.c_str (), dirPath.size (), SQLITE_TRANSIENT);
+}
+