# HG changeset patch # User Chris Cannam # Date 1329154146 0 # Node ID fc2df97920e88e3f837e64ce8ec71a5eaf8e59f5 # Parent 3935a7e621ca2a1d011945569cc741fc8709776a Introduce FsWatcher into main window (though it isn't fully rigged up yet) diff -r 3935a7e621ca -r fc2df97920e8 src/fswatcher.cpp --- 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 #include #include +#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 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 +FsWatcher::scanDirectory(QString path) +{ + QSet 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 +} diff -r 3935a7e621ca -r fc2df97920e8 src/fswatcher.h --- 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 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 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 > m_dirContents; + QStringList m_ignoredPrefixes; QStringList m_ignoredSuffixes; diff -r 3935a7e621ca -r fc2df97920e8 src/mainwindow.cpp --- 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 alreadyWatched; - QStringList dl(m_fsWatcher->directories()); - foreach (QString d, dl) alreadyWatched.insert(d); - - std::deque 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(); diff -r 3935a7e621ca -r fc2df97920e8 src/mainwindow.h --- 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 #include -#include 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;