fs-watcher uses its own database
add test for fs-watcher

Change-Id: Iac54e5d5c748f80099ef798a6515993ab432c725
diff --git a/gui/fs-watcher.cc b/gui/fs-watcher.cc
index 18d004d..f646a9c 100644
--- a/gui/fs-watcher.cc
+++ b/gui/fs-watcher.cc
@@ -21,6 +21,7 @@
  */
 
 #include "fs-watcher.h"
+#include "db-helper.h"
 #include "logging.h"
 
 #include <boost/bind.hpp>
@@ -35,7 +36,6 @@
 
 FsWatcher::FsWatcher (QString dirPath,
                       LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
-                      FileState *fileState,
                       QObject* parent)
   : QObject(parent)
   , m_watcher (new QFileSystemWatcher())
@@ -43,11 +43,12 @@
   , m_dirPath (dirPath)
   , m_onChange (onChange)
   , m_onDelete (onDelete)
-  , m_fileState (fileState)
 {
   _LOG_DEBUG ("Monitor dir: " << m_dirPath.toStdString ());
   // add main directory to monitor
 
+  initFileStateDb();
+
   m_watcher->addPath (m_dirPath);
 
   // register signals (callback functions)
@@ -57,17 +58,18 @@
   m_scheduler->start ();
 
   Scheduler::scheduleOneTimeTask (m_scheduler, 0,
-                                  bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
+                                  bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
                                   "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
 
   Scheduler::scheduleOneTimeTask (m_scheduler, 0,
-                                  bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath, true),
+                                  bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath),
                                   "rescan-" +m_dirPath.toStdString ()); // only one task will be scheduled per directory
 }
 
 FsWatcher::~FsWatcher()
 {
   m_scheduler->shutdown ();
+  sqlite3_close(m_db);
 }
 
 void
@@ -79,23 +81,23 @@
   if (!filesystem::exists (filesystem::path (absPathTriggeredDir)))
     {
       Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
-                                      bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath, true/* ignore incomplete file flag. the whole directory got removed*/),
+                                      bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath),
                                       "r-" + dirPath.toStdString ()); // only one task will be scheduled per directory
     }
   else
     {
       // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
       Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
-                                      bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, false),
+                                      bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
                                       dirPath.toStdString ()); // only one task will be scheduled per directory
 
       // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
       Scheduler::scheduleOneTimeTask (m_scheduler, 300,
-                                      bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, true),
+                                      bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
                                       "rescan-"+dirPath.toStdString ()); // only one task will be scheduled per directory
 
       Scheduler::scheduleOneTimeTask (m_scheduler, 300,
-                                      bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
+                                      bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
                                       "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
     }
 }
@@ -105,7 +107,7 @@
 {
   if (!filePath.startsWith (m_dirPath))
     {
-      _LOG_ERROR ("Got notification about a file not from the monitored directory");
+      _LOG_ERROR ("Got notification about a file not from the monitored directory: " << filePath.toStdString());
       return;
     }
   filesystem::path absPathTriggeredFile (filePath.toStdString ());
@@ -133,7 +135,7 @@
 }
 
 void
-FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath, bool notifyCallbacks)
+FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath)
 {
   _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute");
 
@@ -169,9 +171,11 @@
               relFile.remove (0, m_dirPath.size ());
               filesystem::path aFile (relFile.toStdString ());
 
-              if (notifyCallbacks ||
-                  !m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
+              if (
+                  //!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
+                  !fileExists(aFile.relative_path().c_str())  /*file does not exist in db, but exists in fs: add */)
                 {
+                  addFile(aFile.relative_path().c_str());
                   DidFileChanged (absFilePath);
                 }
             }
@@ -185,7 +189,7 @@
 
 
 void
-FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath, bool removeIncomplete)
+FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath)
 {
   _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
 
@@ -194,6 +198,7 @@
 
   filesystem::path triggeredDir (dirPath.toStdString ());
 
+  /*
   FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
   for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
     {
@@ -209,8 +214,116 @@
             }
         }
     }
+    */
+
+  vector<string> files;
+  getFilesInDir(triggeredDir.relative_path().c_str(), files);
+  for (vector<string>::iterator file = files.begin(); file != files.end(); file++)
+  {
+    filesystem::path targetFile = filesystem::path (m_dirPath.toStdString()) / file->c_str();
+    if (!filesystem::exists (targetFile))
+    {
+      deleteFile(file->c_str());
+      m_onDelete(file->c_str());
+    }
+  }
 }
 
+const string INIT_DATABASE = "\
+CREATE TABLE IF NOT EXISTS                                      \n\
+    Files(                                                      \n\
+    filename      TEXT NOT NULL,                                \n\
+    PRIMARY KEY (filename)                                      \n\
+);                                                              \n\
+CREATE INDEX filename_index ON Files (filename);                \n\
+";
+
+void
+FsWatcher::initFileStateDb()
+{
+  filesystem::path dbFolder = filesystem::path (m_dirPath.toStdString().c_str()) / ".chronoshare" / "fs_watcher";
+  filesystem::create_directories(dbFolder);
+
+  int res = sqlite3_open((dbFolder / "filestate.db").c_str(), &m_db);
+  if (res != SQLITE_OK)
+  {
+    BOOST_THROW_EXCEPTION(Error::Db() << errmsg_info_str("Cannot open database: " + (dbFolder / "filestate.db").string()));
+  }
+
+  char *errmsg = 0;
+  res = sqlite3_exec(m_db, INIT_DATABASE.c_str(), NULL, NULL, &errmsg);
+  if (res != SQLITE_OK && errmsg != 0)
+  {
+      // _LOG_TRACE ("Init \"error\": " << errmsg);
+      cout << "FS-Watcher DB error: " << errmsg << endl;
+      sqlite3_free (errmsg);
+  }
+}
+
+bool
+FsWatcher::fileExists(const filesystem::path &filename)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename = ?;", -1, &stmt, 0);
+  sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
+  bool retval = false;
+  if (sqlite3_step (stmt) == SQLITE_ROW)
+  {
+    retval = true;
+  }
+  sqlite3_finalize(stmt);
+
+  return retval;
+}
+
+void
+FsWatcher::addFile(const filesystem::path &filename)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2(m_db, "INSERT OR IGNORE INTO Files (filename) VALUES (?);", -1, &stmt, 0);
+  sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
+  sqlite3_step(stmt);
+  sqlite3_finalize(stmt);
+}
+
+void
+FsWatcher::deleteFile(const filesystem::path &filename)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2(m_db, "DELETE FROM Files WHERE filename = ?;", -1, &stmt, 0);
+  sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
+  sqlite3_step(stmt);
+  sqlite3_finalize(stmt);
+}
+
+void
+FsWatcher::getFilesInDir(const filesystem::path &dir, vector<string> &files)
+{
+  sqlite3_stmt *stmt;
+  sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename LIKE ?;", -1, &stmt, 0);
+
+  string dirStr = dir.string();
+  ostringstream escapedDir;
+  for (string::const_iterator ch = dirStr.begin (); ch != dirStr.end (); ch ++)
+    {
+      if (*ch == '%')
+        escapedDir << "\\%";
+      else
+        escapedDir << *ch;
+    }
+  escapedDir << "/" << "%";
+  string escapedDirStr = escapedDir.str ();
+  sqlite3_bind_text (stmt, 1, escapedDirStr.c_str (), escapedDirStr.size (), SQLITE_STATIC);
+
+  while(sqlite3_step(stmt) == SQLITE_ROW)
+  {
+    string filename(reinterpret_cast<const char *>(sqlite3_column_text (stmt, 0)), sqlite3_column_bytes(stmt, 0));
+    files.push_back(filename);
+  }
+
+  sqlite3_finalize (stmt);
+
+}
 
 #if WAF
 #include "fs-watcher.moc"