fs-watcher: Switch code to use ndn-cxx

Implementation moved to ndn::chronoshare namespace

Change-Id: I78a7f08dc2aafe7a2d578f78d99c72db7ea414b6
diff --git a/.jenkins.d/20-tests.sh b/.jenkins.d/20-tests.sh
index 6f466ed..db8cf30 100755
--- a/.jenkins.d/20-tests.sh
+++ b/.jenkins.d/20-tests.sh
@@ -47,6 +47,9 @@
 # Give NFD a few seconds to start
 sleep 10
 
+#open the NDN LOG
+export NDN_LOG=*=ALL
+
 # First run all tests as unprivileged user
 ./build/unit-tests $(ut_log_args)
 
diff --git a/fs-watcher/fs-watcher.cpp b/fs-watcher/fs-watcher.cpp
index 65305a4..6e637cf 100644
--- a/fs-watcher/fs-watcher.cpp
+++ b/fs-watcher/fs-watcher.cpp
@@ -19,24 +19,23 @@
  */
 
 #include "fs-watcher.hpp"
-#include "db-helper.hpp"
-#include "logging.hpp"
-
-#include <boost/bind.hpp>
+#include "core/logging.hpp"
 
 #include <QDirIterator>
 #include <QRegExp>
 
-using namespace std;
-using namespace boost;
+namespace ndn {
+namespace chronoshare {
 
 _LOG_INIT(FsWatcher);
 
-FsWatcher::FsWatcher(QString dirPath, LocalFile_Change_Callback onChange,
+namespace fs = boost::filesystem;
+
+FsWatcher::FsWatcher(boost::asio::io_service& io, QString dirPath, LocalFile_Change_Callback onChange,
                      LocalFile_Change_Callback onDelete, QObject* parent)
   : QObject(parent)
-  , m_watcher(new QFileSystemWatcher())
-  , m_scheduler(new Scheduler())
+  , m_watcher(new QFileSystemWatcher(this))
+  , m_scheduler(io)
   , m_dirPath(dirPath)
   , m_onChange(onChange)
   , m_onDelete(onDelete)
@@ -48,61 +47,56 @@
 
   m_watcher->addPath(m_dirPath);
 
-  // register signals (callback functions)
+  // register signals(callback functions)
   connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(DidDirectoryChanged(QString)));
   connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(DidFileChanged(QString)));
 
-  m_scheduler->start();
+  rescheduleEvent("rescan", m_dirPath.toStdString(), time::seconds(0),
+                  bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath));
 
-  Scheduler::scheduleOneTimeTask(m_scheduler, 0,
-                                 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),
-                                 "rescan-" +
-                                   m_dirPath.toStdString()); // only one task will be scheduled per directory
+  rescheduleEvent("rescan-r", m_dirPath.toStdString(), time::seconds(0),
+                  bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath));
 }
 
 FsWatcher::~FsWatcher()
 {
-  m_scheduler->shutdown();
   sqlite3_close(m_db);
 }
 
 void
+FsWatcher::rescheduleEvent(const std::string& eventType, const std::string& path,
+                           const time::milliseconds& period, const Scheduler::Event& callback)
+{
+  util::scheduler::ScopedEventId event(m_scheduler);
+  event = m_scheduler.scheduleEvent(period, callback);
+
+  // only one task per directory/file
+  std::string key = eventType + ":" + path;
+  m_events.erase(key);
+  m_events.insert(std::make_pair(key, std::move(event)));
+}
+
+void
 FsWatcher::DidDirectoryChanged(QString dirPath)
 {
   _LOG_DEBUG("Triggered DirPath: " << dirPath.toStdString());
 
-  filesystem::path absPathTriggeredDir(dirPath.toStdString());
-  if (!filesystem::exists(filesystem::path(absPathTriggeredDir))) {
-    Scheduler::scheduleOneTimeTask(m_scheduler, 0.5,
-                                   bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this,
-                                        dirPath),
-                                   "r-" + dirPath.toStdString()); // only one task will be scheduled per directory
+  fs::path absPathTriggeredDir(dirPath.toStdString());
+  if (!fs::exists(fs::path(absPathTriggeredDir))) {
+    rescheduleEvent("r-", dirPath.toStdString(), time::milliseconds(500),
+                    bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath));
   }
   else {
-    // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
-    Scheduler::scheduleOneTimeTask(m_scheduler, 0.5,
-                                   bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this,
-                                        dirPath),
-                                   dirPath.toStdString()); // only one task will be scheduled per directory
+    rescheduleEvent("", dirPath.toStdString(), time::milliseconds(500),
+                    bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
 
-    // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
-    Scheduler::scheduleOneTimeTask(m_scheduler, 300,
-                                   bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this,
-                                        dirPath),
-                                   "rescan-" +
-                                     dirPath.toStdString()); // only one task will be scheduled per directory
+    // rescan updated folder for updates
+    rescheduleEvent("rescan", dirPath.toStdString(), time::seconds(300),
+                    bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
 
-    Scheduler::scheduleOneTimeTask(m_scheduler, 300,
-                                   bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this,
-                                        m_dirPath),
-                                   "rescan-r-" +
-                                     m_dirPath.toStdString()); // only one task will be scheduled per directory
+    // rescan whole folder for deletions
+    rescheduleEvent("rescan-r", m_dirPath.toStdString(), time::seconds(300),
+                    bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath));
   }
 }
 
@@ -116,29 +110,30 @@
   }
   QString absFilePath = filePath;
 
-  filesystem::path absPathTriggeredFile(filePath.toStdString());
+  fs::path absPathTriggeredFile(filePath.toStdString());
   filePath.remove(0, m_dirPath.size());
 
-  filesystem::path triggeredFile(filePath.toStdString());
-  if (filesystem::exists(filesystem::path(absPathTriggeredFile))) {
+  fs::path triggeredFile(filePath.toStdString());
+  if (fs::exists(fs::path(absPathTriggeredFile))) {
     _LOG_DEBUG("Triggered UPDATE of file:  " << triggeredFile.relative_path().generic_string());
-    // m_onChange (triggeredFile.relative_path ());
+    // m_onChange(triggeredFile.relative_path());
 
     m_watcher->removePath(absFilePath);
     m_watcher->addPath(absFilePath);
 
-    Scheduler::scheduleOneTimeTask(m_scheduler, 0.5, bind(m_onChange, triggeredFile.relative_path()),
-                                   triggeredFile.relative_path().string());
+    rescheduleEvent("", triggeredFile.relative_path().string(), time::milliseconds(500),
+                    bind(m_onChange, triggeredFile.relative_path()));
   }
   else {
     _LOG_DEBUG("Triggered DELETE of file: " << triggeredFile.relative_path().generic_string());
-    // m_onDelete (triggeredFile.relative_path ());
+    // m_onDelete(triggeredFile.relative_path());
 
     m_watcher->removePath(absFilePath);
 
     deleteFile(triggeredFile.relative_path());
-    Scheduler::scheduleOneTimeTask(m_scheduler, 0.5, bind(m_onDelete, triggeredFile.relative_path()),
-                                   "r-" + triggeredFile.relative_path().string());
+
+    rescheduleEvent("r", triggeredFile.relative_path().string(), time::milliseconds(500),
+                    bind(m_onDelete, triggeredFile.relative_path()));
   }
 }
 
@@ -147,13 +142,13 @@
 {
   _LOG_TRACE(" >> ScanDirectory_NotifyUpdates_Execute");
 
-  // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
+  // exclude working only on last component, not the full path; iterating through all directories,
+  // even excluded from monitoring
   QRegExp exclude("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
 
-  QDirIterator dirIterator(dirPath,
-                           QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks |
-                             QDir::NoDotAndDotDot,
-                           QDirIterator::Subdirectories); // directory iterator (recursive)
+  QDirIterator dirIterator(dirPath, QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks |
+                                      QDir::NoDotAndDotDot,
+                           QDirIterator::Subdirectories); // directory iterator(recursive)
 
   // iterate through directory recursively
   while (dirIterator.hasNext()) {
@@ -169,17 +164,18 @@
       _LOG_DEBUG("Not excluded file/dir: " << fileInfo.absoluteFilePath().toStdString());
       QString absFilePath = fileInfo.absoluteFilePath();
 
-      // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
+      // _LOG_DEBUG("Attempt to add path to watcher: " << absFilePath.toStdString());
       m_watcher->removePath(absFilePath);
       m_watcher->addPath(absFilePath);
 
       if (fileInfo.isFile()) {
         QString relFile = absFilePath;
         relFile.remove(0, m_dirPath.size());
-        filesystem::path aFile(relFile.toStdString());
+        fs::path aFile(relFile.toStdString());
 
         if (
-          //!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
+          //!m_fileState->LookupFile(aFile.relative_path().generic_string())
+          ///* file does not exist there, but exists locally: added */)
           !fileExists(aFile.relative_path()) /*file does not exist in db, but exists in fs: add */) {
           addFile(aFile.relative_path());
           DidFileChanged(absFilePath);
@@ -187,7 +183,7 @@
       }
     }
     else {
-      // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
+      // _LOG_DEBUG("Excluded file/dir: " << fileInfo.filePath().toStdString());
     }
   }
 }
@@ -198,73 +194,71 @@
 {
   _LOG_DEBUG("Triggered DirPath: " << dirPath.toStdString());
 
-  filesystem::path absPathTriggeredDir(dirPath.toStdString());
+  fs::path absPathTriggeredDir(dirPath.toStdString());
   dirPath.remove(0, m_dirPath.size());
 
-  filesystem::path triggeredDir(dirPath.toStdString());
+  fs::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 ++)
+  FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively(triggeredDir.relative_path().generic_string());
+  for (std::list<FileItem>::iterator file = files->begin(); file != files->end(); file ++)
     {
-      filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
-      _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
+      fs::path testFile = fs::path(m_dirPath.toStdString()) / file->filename();
+      _LOG_DEBUG("Check file for deletion [" << testFile.generic_string() << "]");
 
-      if (!filesystem::exists (testFile))
+      if (!fs::exists(testFile))
         {
-          if (removeIncomplete || file->is_complete ())
+          if (removeIncomplete || file->is_complete())
             {
-              _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
-              m_onDelete (file->filename ());
+              _LOG_DEBUG("Notifying about removed file [" << file->filename() << "]");
+              m_onDelete(file->filename());
             }
         }
     }
     */
 
-  vector<string> files;
+  std::vector<std::string> files;
   getFilesInDir(triggeredDir.relative_path(), files);
-  for (vector<string>::iterator file = files.begin(); file != files.end(); file++) {
-    filesystem::path targetFile = filesystem::path(m_dirPath.toStdString()) / *file;
-    if (!filesystem::exists(targetFile)) {
+  for (std::vector<std::string>::iterator file = files.begin(); file != files.end(); file++) {
+    fs::path targetFile = fs::path(m_dirPath.toStdString()) / *file;
+    if (!fs::exists(targetFile)) {
       deleteFile(*file);
       m_onDelete(*file);
     }
   }
 }
 
-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\
+const std::string INIT_DATABASE = "\
+CREATE TABLE IF NOT EXISTS                                     \n\
+    Files(                                                     \n\
+    filename      TEXT NOT NULL,                               \n\
+    PRIMARY KEY(filename)                                      \n\
+);                                                             \n\
+CREATE INDEX IF NOT EXISTS filename_index ON Files(filename);  \n\
 ";
 
 void
 FsWatcher::initFileStateDb()
 {
-  filesystem::path dbFolder =
-    filesystem::path(m_dirPath.toStdString()) / ".chronoshare" / "fs_watcher";
-  filesystem::create_directories(dbFolder);
+  fs::path dbFolder = fs::path(m_dirPath.toStdString()) / ".chronoshare" / "fs_watcher";
+  fs::create_directories(dbFolder);
 
   int res = sqlite3_open((dbFolder / "filestate.db").string().c_str(), &m_db);
   if (res != SQLITE_OK) {
-    BOOST_THROW_EXCEPTION(Error::Db() << errmsg_info_str("Cannot open database: " +
-                                                         (dbFolder / "filestate.db").string()));
+    BOOST_THROW_EXCEPTION(Error("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;
+    // _LOG_TRACE("Init \"error\": " << errmsg);
+    std::cout << "FS-Watcher DB error: " << errmsg << std::endl;
     sqlite3_free(errmsg);
   }
 }
 
 bool
-FsWatcher::fileExists(const filesystem::path& filename)
+FsWatcher::fileExists(const fs::path& filename)
 {
   sqlite3_stmt* stmt;
   sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename = ?;", -1, &stmt, 0);
@@ -279,17 +273,17 @@
 }
 
 void
-FsWatcher::addFile(const filesystem::path& filename)
+FsWatcher::addFile(const fs::path& filename)
 {
   sqlite3_stmt* stmt;
-  sqlite3_prepare_v2(m_db, "INSERT OR IGNORE INTO Files (filename) VALUES (?);", -1, &stmt, 0);
+  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)
+FsWatcher::deleteFile(const fs::path& filename)
 {
   sqlite3_stmt* stmt;
   sqlite3_prepare_v2(m_db, "DELETE FROM Files WHERE filename = ?;", -1, &stmt, 0);
@@ -299,14 +293,14 @@
 }
 
 void
-FsWatcher::getFilesInDir(const filesystem::path& dir, vector<string>& files)
+FsWatcher::getFilesInDir(const fs::path& dir, std::vector<std::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++) {
+  std::string dirStr = dir.string();
+  std::ostringstream escapedDir;
+  for (std::string::const_iterator ch = dirStr.begin(); ch != dirStr.end(); ch++) {
     if (*ch == '%')
       escapedDir << "\\%";
     else
@@ -314,19 +308,22 @@
   }
   escapedDir << "/"
              << "%";
-  string escapedDirStr = escapedDir.str();
+  std::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));
+    std::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.cc.moc"
+} // namespace chronoshare
+} // namespace ndn
+
+#ifdef WAF
 #include "fs-watcher.moc"
+// #include "fs-watcher.cpp.moc"
 #endif
diff --git a/fs-watcher/fs-watcher.hpp b/fs-watcher/fs-watcher.hpp
index e68920c..d94eed8 100644
--- a/fs-watcher/fs-watcher.hpp
+++ b/fs-watcher/fs-watcher.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016, Regents of the University of California.
+ * Copyright (c) 2013-2017, Regents of the University of California.
  *
  * This file is part of ChronoShare, a decentralized file sharing application over NDN.
  *
@@ -17,26 +17,45 @@
  *
  * See AUTHORS.md for complete list of ChronoShare authors and contributors.
  */
-#ifndef FS_WATCHER_H
-#define FS_WATCHER_H
 
-#include <boost/filesystem.hpp>
+#ifndef CHRONOSHARE_FS_WATCHER_FS_WATCHER_HPP
+#define CHRONOSHARE_FS_WATCHER_FS_WATCHER_HPP
+
+#include "db-helper.hpp"
+#include "core/chronoshare-common.hpp"
+
 #include <QFileSystemWatcher>
 #include <sqlite3.h>
 #include <vector>
 
-#include "scheduler.hpp"
+#include <ndn-cxx/util/scheduler-scoped-event-id.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+#include <boost/asio/io_service.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace chronoshare {
 
 class FsWatcher : public QObject
 {
   Q_OBJECT
 
 public:
-  typedef boost::function<void(const boost::filesystem::path&)> LocalFile_Change_Callback;
+  class Error : public DbHelper::Error
+  {
+  public:
+    explicit Error(const std::string& what)
+      : DbHelper::Error(what)
+    {
+    }
+  };
+
+  typedef std::function<void(const boost::filesystem::path&)> LocalFile_Change_Callback;
 
   // constructor
-  FsWatcher(QString dirPath, LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
-            QObject* parent = 0);
+  FsWatcher(boost::asio::io_service& io, QString dirPath, LocalFile_Change_Callback onChange,
+            LocalFile_Change_Callback onDelete, QObject* parent = 0);
 
   // destructor
   ~FsWatcher();
@@ -77,9 +96,13 @@
   void
   getFilesInDir(const boost::filesystem::path& dir, std::vector<std::string>& files);
 
+  void
+  rescheduleEvent(const std::string& eventType, const std::string& dirPath,
+                  const time::milliseconds& period, const Scheduler::Event& callback);
+
 private:
   QFileSystemWatcher* m_watcher; // filesystem watcher
-  SchedulerPtr m_scheduler;
+  Scheduler m_scheduler;
 
   QString m_dirPath; // monitored path
 
@@ -87,6 +110,11 @@
   LocalFile_Change_Callback m_onDelete;
 
   sqlite3* m_db;
+
+  std::map<std::string, util::scheduler::ScopedEventId> m_events;
 };
 
-#endif // FILESYSTEMWATCHER_H
+} // namespace chronoshare
+} // namespace ndn
+
+#endif // CHRONOSHARE_FS_WATCHER_FS_WATCHER_HPP
diff --git a/tests/unit-tests/fs-watcher.t.cpp b/tests/integrated-tests/fs-watcher.t.cpp
similarity index 62%
rename from tests/unit-tests/fs-watcher.t.cpp
rename to tests/integrated-tests/fs-watcher.t.cpp
index 22b1838..05f9764 100644
--- a/tests/unit-tests/fs-watcher.t.cpp
+++ b/tests/integrated-tests/fs-watcher.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016, Regents of the University of California.
+ * Copyright (c) 2013-2017, Regents of the University of California.
  *
  * This file is part of ChronoShare, a decentralized file sharing application over NDN.
  *
@@ -17,79 +17,129 @@
  *
  * See AUTHORS.md for complete list of ChronoShare authors and contributors.
  */
+
 #include "fs-watcher.hpp"
+#include "test-common.hpp"
+
 #include <boost/bind.hpp>
 #include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/make_shared.hpp>
 #include <boost/test/unit_test.hpp>
 #include <boost/thread/thread.hpp>
-#include <QtGui>
 #include <fstream>
+#include <iostream>
+#include <thread>
 #include <set>
 
+#include "fs-watcher.t.hpp"
+
 using namespace std;
-using namespace boost;
 namespace fs = boost::filesystem;
 
-BOOST_AUTO_TEST_SUITE(TestFsWatcher)
+_LOG_INIT(Test.FSWatcher);
 
-void
-onChange(set<string>& files, const fs::path& file)
+namespace ndn {
+namespace chronoshare {
+namespace tests {
+
+fsWatcherApp::fsWatcherApp(int& argc, char** argv)
+  : QCoreApplication(argc, argv)
 {
-  cerr << "onChange called" << endl;
-  files.insert(file.string());
+  connect(this, SIGNAL(stopApp()), this, SLOT(quit()), Qt::QueuedConnection);
 }
 
-void
-onDelete(set<string>& files, const fs::path& file)
-{
-  files.erase(file.string());
-}
+fsWatcherApp::~fsWatcherApp() = default;
 
-void
-create_file(const fs::path& ph, const std::string& contents)
+class TestFSWatcherFixture : public IdentityManagementFixture
 {
-  std::ofstream f(ph.string().c_str());
-  if (!f) {
-    abort();
+public:
+  TestFSWatcherFixture()
+    : dir(fs::path(UNIT_TEST_CONFIG_PATH) / "TestFsWatcher")
+    , argc(0)
+  {
+    if (fs::exists(dir)) {
+      fs::remove_all(dir);
+    }
+
+    fs::create_directory(dir);
   }
-  if (!contents.empty()) {
-    f << contents;
-  }
-}
 
-void
-run(fs::path dir, FsWatcher::LocalFile_Change_Callback c, FsWatcher::LocalFile_Change_Callback d)
-{
-  int x = 0;
-  QCoreApplication app(x, 0);
-  FsWatcher watcher(dir.string().c_str(), c, d);
-  app.exec();
-  sleep(100);
-}
+  ~TestFSWatcherFixture(){
+    // cleanup
+    if (fs::exists(dir)) {
+      _LOG_DEBUG("clean all");
+      fs::remove_all(dir);
+    }
+  }
+
+  void
+  advanceClocks(std::chrono::seconds delay)
+  {
+    std::chrono::milliseconds step = delay;
+    step /= 50;
+    for (int i = 0; i < 50; ++i) {
+      std::this_thread::sleep_for(step);
+      m_io.poll();
+      m_io.reset();
+    }
+  }
+
+  void
+  onChange(set<string>& files, const fs::path& file)
+  {
+    _LOG_DEBUG("on change, file: " << file);
+    files.insert(file.string());
+  }
+
+  void
+  onDelete(set<string>& files, const fs::path& file)
+  {
+    _LOG_DEBUG("on delete, file: " << file);
+    files.erase(file.string());
+  }
+
+  void
+  create_file(const fs::path& ph, const std::string& contents)
+  {
+    std::ofstream f(ph.string().c_str());
+    if (!f) {
+      abort();
+    }
+    if (!contents.empty()) {
+      f << contents;
+    }
+  }
+
+  void
+  run()
+  {
+    app = new fsWatcherApp(argc, nullptr);
+    new FsWatcher(m_io, dir.string().c_str(),
+                  std::bind(&TestFSWatcherFixture::onChange, this, std::ref(files), _1),
+                  std::bind(&TestFSWatcherFixture::onDelete, this, std::ref(files), _1),
+                  app);
+    app->exec();
+  }
+
+public:
+  fs::path dir;
+  set<string> files;
+  int argc;
+  fsWatcherApp* app;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestFsWatcher, TestFSWatcherFixture)
 
 BOOST_AUTO_TEST_CASE(TestFsWatcher)
 {
-  fs::path dir = fs::absolute(fs::path("TestFsWatcher"));
-  if (fs::exists(dir)) {
-    fs::remove_all(dir);
-  }
-
-  fs::create_directory(dir);
-
-  set<string> files;
-
-  FsWatcher::LocalFile_Change_Callback fileChange = boost::bind(onChange, ref(files), _1);
-  FsWatcher::LocalFile_Change_Callback fileDelete = boost::bind(onDelete, ref(files), _1);
-
-  thread workThread(run, dir, fileChange, fileDelete);
-  //FsWatcher watcher (dir.string().c_str(), fileChange, fileDelete);
+  std::thread workThread(boost::bind(&TestFSWatcherFixture::run, this));
+  this->advanceClocks(std::chrono::seconds(2));
 
   // ============ check create file detection ================
   create_file(dir / "test.txt", "hello");
-  // have to at least wait 0.5 seconds
-  usleep(1000000);
+  this->advanceClocks(std::chrono::seconds(2));
   // test.txt
   BOOST_CHECK_EQUAL(files.size(), 1);
   BOOST_CHECK(files.find("test.txt") != files.end());
@@ -101,8 +151,7 @@
     string filename = boost::lexical_cast<string>(i);
     create_file(subdir / filename.c_str(), boost::lexical_cast<string>(i));
   }
-  // have to at least wait 0.5 * 2 seconds
-  usleep(1100000);
+  this->advanceClocks(std::chrono::seconds(2));
   // test.txt
   // sub/0..9
   BOOST_CHECK_EQUAL(files.size(), 11);
@@ -117,10 +166,9 @@
   fs::copy_directory(subdir, subdir1);
   for (int i = 0; i < 5; i++) {
     string filename = boost::lexical_cast<string>(i);
-    fs::copy_file(subdir / filename.c_str(), subdir1 / filename.c_str());
+    fs::copy(subdir / filename.c_str(), subdir1 / filename.c_str());
   }
-  // have to at least wait 0.5 * 2 seconds
-  usleep(1100000);
+  this->advanceClocks(std::chrono::seconds(2));
   // test.txt
   // sub/0..9
   // sub1/sub2/0..4
@@ -135,7 +183,7 @@
     string filename = boost::lexical_cast<string>(i);
     fs::remove(subdir / filename.c_str());
   }
-  usleep(1100000);
+  this->advanceClocks(std::chrono::seconds(2));
   // test.txt
   // sub/7..9
   // sub1/sub2/0..4
@@ -148,14 +196,15 @@
       BOOST_CHECK(files.find("sub/" + filename) != files.end());
   }
 
-  // =================== check remove files again, remove the whole dir this time ===================
+  // =================== check remove files again, remove the whole dir this time
+  // ===================
   // before remove check
   for (int i = 0; i < 5; i++) {
     string filename = boost::lexical_cast<string>(i);
     BOOST_CHECK(files.find("sub1/sub2/" + filename) != files.end());
   }
   fs::remove_all(subdir1);
-  usleep(1100000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK_EQUAL(files.size(), 4);
   // test.txt
   // sub/7..9
@@ -169,7 +218,7 @@
     string filename = boost::lexical_cast<string>(i);
     fs::rename(subdir / filename.c_str(), dir / filename.c_str());
   }
-  usleep(1100000);
+  this->advanceClocks(std::chrono::seconds(2));
   // test.txt
   // 7
   // 8
@@ -183,33 +232,36 @@
   }
 
   create_file(dir / "add-removal-check.txt", "add-removal-check");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") != files.end());
 
   fs::remove(dir / "add-removal-check.txt");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") == files.end());
 
   create_file(dir / "add-removal-check.txt", "add-removal-check");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") != files.end());
 
   fs::remove(dir / "add-removal-check.txt");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") == files.end());
 
   create_file(dir / "add-removal-check.txt", "add-removal-check");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") != files.end());
 
   fs::remove(dir / "add-removal-check.txt");
-  usleep(1200000);
+  this->advanceClocks(std::chrono::seconds(2));
   BOOST_CHECK(files.find("add-removal-check.txt") == files.end());
 
-  // cleanup
-  if (fs::exists(dir)) {
-    fs::remove_all(dir);
-  }
+  emit app->stopApp();
+
+  workThread.join();
 }
 
 BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace chronoshare
+} // namespace ndn
diff --git a/tests/integrated-tests/fs-watcher.t.hpp b/tests/integrated-tests/fs-watcher.t.hpp
new file mode 100644
index 0000000..40c524b
--- /dev/null
+++ b/tests/integrated-tests/fs-watcher.t.hpp
@@ -0,0 +1,42 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017, Regents of the University of California.
+ *
+ * This file is part of ChronoShare, a decentralized file sharing application over NDN.
+ *
+ * ChronoShare is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ChronoShare is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ChronoShare, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ChronoShare authors and contributors.
+ */
+
+#include <QtWidgets>
+
+namespace ndn {
+namespace chronoshare {
+namespace tests {
+
+class fsWatcherApp : public QCoreApplication
+{
+Q_OBJECT
+
+signals:
+  void
+  stopApp();
+
+public:
+  fsWatcherApp(int& argc, char** argv);
+  ~fsWatcherApp();
+};
+
+} // namespace tests
+} // namespace chronoshare
+} // namespace ndn
diff --git a/tests/unit-tests/fs-watcher-delay.t.cpp b/tests/unit-tests/fs-watcher-delay.t.cpp
deleted file mode 100644
index 0ccd5cc..0000000
--- a/tests/unit-tests/fs-watcher-delay.t.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2016, Regents of the University of California.
- *
- * This file is part of ChronoShare, a decentralized file sharing application over NDN.
- *
- * ChronoShare is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoShare is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received copies of the GNU General Public License along with
- * ChronoShare, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ChronoShare authors and contributors.
- */
-
-#include "fs-watcher.h"
-#include <boost/bind.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/make_shared.hpp>
-#include <boost/test/unit_test.hpp>
-#include <boost/thread/thread.hpp>
-#include <QtGui>
-#include <fstream>
-#include <iostream>
-#include <set>
-
-using namespace std;
-using namespace boost;
-namespace fs = boost::filesystem;
-
-BOOST_AUTO_TEST_SUITE(TestFsWatcherDelay)
-
-void
-onChange(const fs::path& file)
-{
-  cerr << "onChange called" << endl;
-}
-
-void
-onDelete(const fs::path& file)
-{
-  cerr << "onDelete called" << endl;
-}
-
-void
-run(fs::path dir, FsWatcher::LocalFile_Change_Callback c, FsWatcher::LocalFile_Change_Callback d)
-{
-  int x = 0;
-  QCoreApplication app(x, 0);
-  FsWatcher watcher(dir.string().c_str(), c, d);
-  app.exec();
-  sleep(100);
-}
-
-void
-SlowWrite(fs::path& file)
-{
-  fs::ofstream off(file, std::ios::out);
-
-  for (int i = 0; i < 10; i++) {
-    off << i << endl;
-    usleep(200000);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(TestFsWatcherDelay)
-{
-  fs::path dir = fs::absolute(fs::path("TestFsWatcher"));
-  if (fs::exists(dir)) {
-    fs::remove_all(dir);
-  }
-
-  fs::create_directory(dir);
-
-  FsWatcher::LocalFile_Change_Callback fileChange = boost::bind(onChange, _1);
-  FsWatcher::LocalFile_Change_Callback fileDelete = boost::bind(onDelete, _1);
-
-  fs::path file = dir / "test.text";
-
-  thread watcherThread(run, dir, fileChange, fileDelete);
-
-  thread writeThread(SlowWrite, file);
-
-
-  usleep(10000000);
-
-  // cleanup
-  if (fs::exists(dir)) {
-    fs::remove_all(dir);
-  }
-
-  usleep(1000000);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/wscript b/tests/wscript
index 3cb795b..e900761 100644
--- a/tests/wscript
+++ b/tests/wscript
@@ -8,32 +8,32 @@
     if not bld.env['WITH_TESTS']:
         return
 
-    Logs.error("Many unit tests are temporary disabled")
-
     bld(features='cxx',
-        target='unit-tests-main',
-        name='unit-tests-main',
-        source='main.cpp',
-        use='BOOST',
+        target='tests-base',
+        name='tests-base',
+        source=bld.path.ant_glob(['*.cpp'], excl='main.cpp'),
+        use='core-objects',
         includes='.. .',
-        defines=['BOOST_TEST_MODULE=ChronoShare Unit Tests'])
+        defines='UNIT_TEST_CONFIG_PATH=\"%s/tmp-files/\"' % (bld.bldnode)
+      )
 
-    unit_tests = bld.program(
-            target='../unit-tests',
-            features='cxx cxxprogram',
-            source=bld.path.ant_glob(['*.cpp',
-                                      'unit-tests/dummy-forwarder.cpp',
-                                      'unit-tests/sync-*.t.cpp',
-                                      'unit-tests/action-log.t.cpp',
-                                      'unit-tests/object-*.t.cpp',
-                                      'unit-tests/fetch*.t.cpp',
-                                      'unit-tests/serve-and-fetch.t.cpp',
-                                      'unit-tests/content-server.t.cpp',
-                                      'unit-tests/dispatcher.t.cpp',
-                                      ],
-                                     excl=['main.cpp']),
-            use='chronoshare core-objects unit-tests-main',
+    for module, name in {"unit": "Unit Tests",
+                         "integrated": "Integrated Tests"}.items():
+        bld(target='%s-tests-main' % module,
+            name='%s-tests-main' % module,
+            features='cxx',
+            use='core-objects BOOST',
+            source='main.cpp',
+            defines=['BOOST_TEST_MODULE=%s' % name]
+        )
+
+        tests = bld.program(
+            target='../%s-tests' % module,
+            features='qt5 cxx cxxprogram',
+            moc='' if module == 'unit' else 'integrated-tests/fs-watcher.t.hpp',
+            source=bld.path.ant_glob(['%s-tests/*.cpp' % module]),
+            use='chronoshare core-objects fs-watcher tests-base %s-tests-main QT5CORE QT5WIDGETS' % module,
             install_path=None,
-            defines='UNIT_TEST_CONFIG_PATH=\"%s/tmp-files/\"' % (bld.bldnode),
+            defines=['UNIT_TEST_CONFIG_PATH=\"%s/tmp-files/\"' % (bld.bldnode)],
             includes='.. ../src .',
-          )
+        )
diff --git a/wscript b/wscript
index 12c7f3d..34df183 100644
--- a/wscript
+++ b/wscript
@@ -118,16 +118,16 @@
         )
     Logs.error("Most of Chronoshare source compilation is temporary disabled")
 
-    # fs_watcher = bld(
-    #     features=['qt5', 'cxx'],
-    #     target='fs-watcher',
-    #     defines='WAF',
-    #     source=bld.path.ant_glob('fs-watcher/*.cpp'),
-    #     use='chronoshare QT5CORE',
-    #     includes='fs-watcher',
-    #     export_includes='fs-watcher',
-    #     )
-    Logs.error("fs-watcher compilation is temporary disabled")
+    fs_watcher = bld(
+          features=['qt5', 'cxx'],
+          target='fs-watcher',
+          defines='WAF',
+          moc = "fs-watcher/fs-watcher.hpp",
+          source=bld.path.ant_glob('fs-watcher/*.cpp'),
+          use='chronoshare QT5CORE',
+          includes='fs-watcher',
+          export_includes='fs-watcher',
+          )
 
     http_server = bld(
           target = "http_server",