changeset 540:fc2df97920e8 fswatcher

Introduce FsWatcher into main window (though it isn't fully rigged up yet)
author Chris Cannam
date Mon, 13 Feb 2012 17:29:06 +0000
parents 3935a7e621ca
children 0a16db274f2c
files src/fswatcher.cpp src/fswatcher.h src/mainwindow.cpp src/mainwindow.h
diffstat 4 files changed, 111 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/src/fswatcher.cpp	Fri Feb 10 21:49:27 2012 +0000
+++ b/src/fswatcher.cpp	Mon Feb 13 17:29:06 2012 +0000
@@ -16,12 +16,15 @@
 */
 
 #include "fswatcher.h"
+#include "debug.h"
 
 #include <QMutexLocker>
 #include <QDir>
 
 #include <deque>
 
+#define DEBUG_FSWATCHER 1
+
 /*
  * Watching the filesystem is trickier than it seems at first glance.
  *
@@ -36,9 +39,12 @@
  * to test properties of the filesystem. So we need to know to ignore
  * those files; unfortunately, when watching a directory (which is how
  * we find out about the creation of new files) we are notified only
- * that the directory has changed -- we aren't told what changed. So
- * we need to rescan the directory merely to find out whether to
- * ignore the change or not.
+ * that the directory has changed -- we aren't told what changed.
+ *
+ * This means that, when a directory changes, we need to rescan the
+ * directory to learn whether the set of files in it _excluding_ files
+ * matching our ignore patterns differs from the previous scan, and
+ * ignore the change if it doesn't.
  */
 
 FsWatcher::FsWatcher() :
@@ -63,6 +69,7 @@
     m_watcher.removePaths(m_watcher.files());
     m_workDirPath = path;
     addWorkDirectory(path);
+    debugPrint();
 }
 
 void
@@ -73,6 +80,7 @@
     foreach (QString path, paths) {
 	m_watcher.addPath(path);
     }
+    debugPrint();
 }
 
 void
@@ -94,6 +102,7 @@
         pending.pop_front();
         if (!alreadyWatched.contains(path)) {
             m_watcher.addPath(path);
+            m_dirContents[path] = scanDirectory(path);
         }
 
         QDir d(path);
@@ -153,22 +162,80 @@
 {
     {
 	QMutexLocker locker(&m_mutex);
+
 	if (shouldIgnore(path)) return;
-	size_t counter = ++m_lastCounter;
-	m_changes[path] = counter;
+
+        QSet<QString> files = scanDirectory(path);
+        if (files == m_dirContents[path]) {
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring" << std::endl;
+#endif
+            return;
+        }
+        else m_dirContents[path] = files;
+
+        size_t counter = ++m_lastCounter;
+        m_changes[path] = counter;
     }
+
     emit changed();
 }
 
 void
 FsWatcher::fsFileChanged(QString path)
 {
-    QMutexLocker locker(&m_mutex);
+    {
+        QMutexLocker locker(&m_mutex);
+
+        // We don't check whether the file matches an ignore pattern,
+        // because we are only notified for file changes if we are
+        // watching the file explicitly, i.e. the file is in the
+        // tracked file paths list. So we never want to ignore them
+
+        size_t counter = ++m_lastCounter;
+        m_changes[path] = counter;
+    }
+
+    emit changed();
 }
 
 bool
 FsWatcher::shouldIgnore(QString path)
 {
-    
+    QFileInfo fi(path);
+    QString fn(fi.fileName());
+    foreach (QString pfx, m_ignoredPrefixes) {
+        if (fn.startsWith(pfx)) return true;
+    }
+    foreach (QString sfx, m_ignoredSuffixes) {
+        if (fn.endsWith(sfx)) return true;
+    }
+    return false;
 }
 
+QSet<QString>
+FsWatcher::scanDirectory(QString path)
+{
+    QSet<QString> files;
+    QDir d(path);
+    if (d.exists()) {
+        d.setFilter(QDir::Files | QDir::NoDotAndDotDot |
+                    QDir::Readable | QDir::NoSymLinks);
+        foreach (QString entry, d.entryList()) {
+            if (entry.startsWith('.')) continue;
+            if (shouldIgnore(entry)) continue;
+            files.insert(entry);
+        }
+    }
+    return files;
+}
+
+void
+FsWatcher::debugPrint()
+{
+#ifdef DEBUG_FSWATCHER
+    std::cerr << "FsWatcher: Now watching " << m_watcher.directories().size()
+              << " directories and " << m_watcher.files().size()
+              << " files" << std::endl;
+#endif
+}
--- a/src/fswatcher.h	Fri Feb 10 21:49:27 2012 +0000
+++ b/src/fswatcher.h	Mon Feb 13 17:29:06 2012 +0000
@@ -96,6 +96,12 @@
     // call with lock already held
     bool shouldIgnore(QString path);
 
+    // call with lock already held. Returns set of non-ignored files in dir
+    QSet<QString> scanDirectory(QString path);
+
+    // call with lock already held
+    void debugPrint();
+
 private:
     /**
      * A change associates a filename with a counter (the
@@ -116,6 +122,15 @@
      */
     QMap<int, size_t> m_tokenMap;
 
+    /**
+     * Associates a directory path with a list of all the files in it
+     * that do not match our ignore patterns. When a directory is
+     * signalled as having changed, then we need to rescan it and
+     * compare against this list before we can determine whether to
+     * notify about the change or not.
+     */
+    QHash<QString, QSet<QString> > m_dirContents;
+
     QStringList m_ignoredPrefixes;
     QStringList m_ignoredSuffixes;
 
--- a/src/mainwindow.cpp	Fri Feb 10 21:49:27 2012 +0000
+++ b/src/mainwindow.cpp	Mon Feb 13 17:29:06 2012 +0000
@@ -52,14 +52,12 @@
 #include "workstatuswidget.h"
 #include "hgignoredialog.h"
 #include "versiontester.h"
+#include "fswatcher.h"
 
 
 MainWindow::MainWindow(QString myDirPath) :
     m_myDirPath(myDirPath),
-    m_helpDialog(0),
-    m_fsWatcherGeneralTimer(0),
-    m_fsWatcherRestoreTimer(0),
-    m_fsWatcherSuspended(false)
+    m_helpDialog(0)
 {
     setWindowIcon(QIcon(":images/easyhg-icon.png"));
 
@@ -67,7 +65,11 @@
 
     m_showAllFiles = false;
 
-    m_fsWatcher = 0;
+    m_fsWatcher = new FsWatcher();
+    m_fsWatcherToken = m_fsWatcher->getNewToken();
+    m_commandSequenceInProgress = false;
+    connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(fsWatcherChanged()));
+
     m_commitsSincePush = 0;
     m_shouldHgStat = true;
 
@@ -1301,14 +1303,6 @@
     m_mergeCommitComment = "";
     m_stateUnknown = true;
     m_needNewLog = true;
-    if (m_fsWatcher) {
-        delete m_fsWatcherGeneralTimer;
-        m_fsWatcherGeneralTimer = 0;
-        delete m_fsWatcherRestoreTimer;
-        m_fsWatcherRestoreTimer = 0;
-        delete m_fsWatcher;
-        m_fsWatcher = 0;
-    }
 }
 
 void MainWindow::hgServe()
@@ -1821,126 +1815,16 @@
     }
 }
 
-void MainWindow::updateFileSystemWatcher()
-{
-    bool justCreated = false;
-    if (!m_fsWatcher) {
-        m_fsWatcher = new QFileSystemWatcher();
-        justCreated = true;
-    }
-
-    // QFileSystemWatcher will refuse to add a file or directory to
-    // its watch list that it is already watching -- fine, that's what
-    // we want -- but it prints a warning when this happens, which is
-    // annoying because it would be the normal case for us.  So we'll
-    // check for duplicates ourselves.
-    QSet<QString> alreadyWatched;
-    QStringList dl(m_fsWatcher->directories());
-    foreach (QString d, dl) alreadyWatched.insert(d);
-    
-    std::deque<QString> pending;
-    pending.push_back(m_workFolderPath);
-
-    while (!pending.empty()) {
-
-        QString path = pending.front();
-        pending.pop_front();
-        if (!alreadyWatched.contains(path)) {
-            m_fsWatcher->addPath(path);
-            DEBUG << "Added to file system watcher: " << path << endl;
-        }
-
-        QDir d(path);
-        if (d.exists()) {
-            d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot |
-                        QDir::Readable | QDir::NoSymLinks);
-            foreach (QString entry, d.entryList()) {
-                if (entry.startsWith('.')) continue;
-                QString entryPath = d.absoluteFilePath(entry);
-                pending.push_back(entryPath);
-            }
-        }
-    }
-
-    // The general timer isn't really related to the fs watcher
-    // object, it just does something similar -- every now and then we
-    // do a refresh just to update the history dates etc
-
-    m_fsWatcherGeneralTimer = new QTimer(this);
-    connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()),
-            this, SLOT(checkFilesystem()));
-    m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour
-    m_fsWatcherGeneralTimer->start();
-
-    if (justCreated) {
-        connect(m_fsWatcher, SIGNAL(directoryChanged(QString)),
-                this, SLOT(fsDirectoryChanged(QString)));
-        connect(m_fsWatcher, SIGNAL(fileChanged(QString)),
-                this, SLOT(fsFileChanged(QString)));
-    }
-}
-
-void MainWindow::suspendFileSystemWatcher()
-{
-    DEBUG << "MainWindow::suspendFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = true;
-        if (m_fsWatcherRestoreTimer) {
-            delete m_fsWatcherRestoreTimer;
-            m_fsWatcherRestoreTimer = 0;
-        }
-        m_fsWatcherGeneralTimer->stop();
-    }
-}
-
-void MainWindow::restoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::restoreFileSystemWatcher" << endl;
-    if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer;
-        
-    // The restore timer is used to leave a polite interval between
-    // being asked to restore the watcher and actually doing so.  It's
-    // a single shot timer each time it's used, but we don't use
-    // QTimer::singleShot because we want to stop the previous one if
-    // it's running (via deleting it)
-
-    m_fsWatcherRestoreTimer = new QTimer(this);
-    connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()),
-            this, SLOT(actuallyRestoreFileSystemWatcher()));
-    m_fsWatcherRestoreTimer->setInterval(1000);
-    m_fsWatcherRestoreTimer->setSingleShot(true);
-    m_fsWatcherRestoreTimer->start();
-}
-
-void MainWindow::actuallyRestoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = false;
-        m_fsWatcherGeneralTimer->start();
-    }
-}
-
 void MainWindow::checkFilesystem()
 {
     DEBUG << "MainWindow::checkFilesystem" << endl;
     hgRefresh();
 }
 
-void MainWindow::fsDirectoryChanged(QString d)
+void MainWindow::fsWatcherChanged()
 {
-    DEBUG << "MainWindow::fsDirectoryChanged " << d << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
-    }
-}
-
-void MainWindow::fsFileChanged(QString f)
-{
-    DEBUG << "MainWindow::fsFileChanged " << f << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
-    }
+    DEBUG << "MainWindow::fsWatcherChanged" << endl;
+
 }
 
 QString MainWindow::format1(QString head)
@@ -2088,21 +1972,14 @@
 
 void MainWindow::commandStarting(HgAction action)
 {
-    // Annoyingly, hg stat actually modifies the working directory --
-    // it creates files called hg-checklink and hg-checkexec to test
-    // properties of the filesystem.  For safety's sake, suspend the
-    // fs watcher while running commands, and restore it shortly after
-    // a command has finished.
-
-    if (action.action == ACT_STAT) {
-        suspendFileSystemWatcher();
-    }
+    m_commandSequenceInProgress = true;
 }
 
 void MainWindow::commandFailed(HgAction action, QString stderr, QString stdout)
 {
     DEBUG << "MainWindow::commandFailed" << endl;
-    restoreFileSystemWatcher();
+
+    m_commandSequenceInProgress = false;
 
     QString setstr;
 #ifdef Q_OS_MAC
@@ -2183,6 +2060,7 @@
             return;
         } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) {
             // success: hg 2.1 starts returning failure code for empty pull/push
+            m_commandSequenceInProgress = true; // there may be further commands
             commandCompleted(action, stdout);
             return;
         }
@@ -2199,6 +2077,7 @@
             return;
         } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) {
             // success: hg 2.1 starts returning failure code for empty pull/push
+            m_commandSequenceInProgress = true; // there may be further commands
             commandCompleted(action, stdout);
             return;
         }
@@ -2209,6 +2088,7 @@
         // problem, something else will fail too).  Pretend it
         // succeeded, so that any further actions that are contingent
         // on the success of the heads query get carried out properly.
+        m_commandSequenceInProgress = true; // there may be further commands
         commandCompleted(action, "");
         return;
     case ACT_FOLDERDIFF:
@@ -2261,7 +2141,6 @@
 {
 //    std::cerr << "commandCompleted: " << completedAction.action << std::endl;
 
-    restoreFileSystemWatcher();
     HGACTIONS action = completedAction.action;
 
     if (action == ACT_NONE) return;
@@ -2320,7 +2199,6 @@
 
     case ACT_STAT:
         m_lastStatOutput = output;
-        updateFileSystemWatcher();
         break;
 
     case ACT_RESOLVE_LIST:
@@ -2624,6 +2502,7 @@
     }
 
     if (noMore) {
+        m_commandSequenceInProgress = false;
         m_stateUnknown = false;
         enableDisableActions();
         m_hgTabs->updateHistory();
--- a/src/mainwindow.h	Fri Feb 10 21:49:27 2012 +0000
+++ b/src/mainwindow.h	Mon Feb 13 17:29:06 2012 +0000
@@ -27,7 +27,6 @@
 
 #include <QMainWindow>
 #include <QListWidget>
-#include <QFileSystemWatcher>
 
 QT_BEGIN_NAMESPACE
 class QAction;
@@ -36,6 +35,7 @@
 QT_END_NAMESPACE
 
 class WorkStatusWidget;
+class FsWatcher;
 
 class MainWindow : public QMainWindow
 {
@@ -113,10 +113,8 @@
     void hgIgnoreFiles(QStringList);
     void hgUnIgnoreFiles(QStringList);
 
-    void fsDirectoryChanged(QString);
-    void fsFileChanged(QString);
+    void fsWatcherChanged();
     void checkFilesystem();
-    void actuallyRestoreFileSystemWatcher();
 
     void newerVersionAvailable(QString);
 
@@ -176,10 +174,6 @@
 
     void clearState();
 
-    void updateFileSystemWatcher();
-    void suspendFileSystemWatcher();
-    void restoreFileSystemWatcher();
-
     void updateClosedHeads();
 
     void updateWorkFolderAndRepoNames();
@@ -257,10 +251,9 @@
     QString getMergeBinaryName();
     QString getEditorBinaryName();
 
-    QFileSystemWatcher *m_fsWatcher;
-    QTimer *m_fsWatcherGeneralTimer;
-    QTimer *m_fsWatcherRestoreTimer;
-    bool m_fsWatcherSuspended;
+    FsWatcher *m_fsWatcher;
+    int m_fsWatcherToken;
+    bool m_commandSequenceInProgress;
 
     QString m_lastStatOutput;
     QStringList m_lastRevertedFiles;