annotate src/fswatcher.cpp @ 563:0a094020c2d4

Switch off more debug output
author Chris Cannam
date Tue, 28 Feb 2012 14:38:42 +0000
parents 65d77437b5e8
children 09b9849b9800
rev   line source
Chris@538 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@538 2
Chris@538 3 /*
Chris@538 4 EasyMercurial
Chris@538 5
Chris@538 6 Based on hgExplorer by Jari Korhonen
Chris@538 7 Copyright (c) 2010 Jari Korhonen
Chris@560 8 Copyright (c) 2012 Chris Cannam
Chris@560 9 Copyright (c) 2012 Queen Mary, University of London
Chris@538 10
Chris@538 11 This program is free software; you can redistribute it and/or
Chris@538 12 modify it under the terms of the GNU General Public License as
Chris@538 13 published by the Free Software Foundation; either version 2 of the
Chris@538 14 License, or (at your option) any later version. See the file
Chris@538 15 COPYING included with this distribution for more information.
Chris@538 16 */
Chris@538 17
Chris@538 18 #include "fswatcher.h"
Chris@540 19 #include "debug.h"
Chris@538 20
Chris@538 21 #include <QMutexLocker>
Chris@538 22 #include <QDir>
Chris@538 23
Chris@538 24 #include <deque>
Chris@538 25
Chris@562 26 //#define DEBUG_FSWATCHER 1
Chris@540 27
Chris@539 28 /*
Chris@539 29 * Watching the filesystem is trickier than it seems at first glance.
Chris@539 30 *
Chris@539 31 * We ideally should watch every directory, and every file that is
Chris@539 32 * tracked by Hg. If a new file is created in a directory, then we
Chris@539 33 * need to respond in order to show it as a potential candidate to be
Chris@539 34 * added.
Chris@539 35 *
Chris@539 36 * Complicating matters though is that Hg itself might modify the
Chris@539 37 * filesystem. This can happen even in "read-only" operations: for
Chris@539 38 * example, hg stat creates files called hg-checklink and hg-checkexec
Chris@539 39 * to test properties of the filesystem. So we need to know to ignore
Chris@539 40 * those files; unfortunately, when watching a directory (which is how
Chris@539 41 * we find out about the creation of new files) we are notified only
Chris@540 42 * that the directory has changed -- we aren't told what changed.
Chris@540 43 *
Chris@540 44 * This means that, when a directory changes, we need to rescan the
Chris@540 45 * directory to learn whether the set of files in it _excluding_ files
Chris@540 46 * matching our ignore patterns differs from the previous scan, and
Chris@540 47 * ignore the change if it doesn't.
Chris@539 48 */
Chris@539 49
Chris@538 50 FsWatcher::FsWatcher() :
Chris@538 51 m_lastToken(0),
Chris@538 52 m_lastCounter(0)
Chris@538 53 {
Chris@538 54 connect(&m_watcher, SIGNAL(directoryChanged(QString)),
Chris@538 55 this, SLOT(fsDirectoryChanged(QString)));
Chris@538 56 connect(&m_watcher, SIGNAL(fileChanged(QString)),
Chris@538 57 this, SLOT(fsFileChanged(QString)));
Chris@538 58 }
Chris@538 59
Chris@538 60 FsWatcher::~FsWatcher()
Chris@538 61 {
Chris@538 62 }
Chris@538 63
Chris@538 64 void
Chris@538 65 FsWatcher::setWorkDirPath(QString path)
Chris@538 66 {
Chris@538 67 QMutexLocker locker(&m_mutex);
Chris@541 68 if (m_workDirPath == path) return;
Chris@562 69 // annoyingly, removePaths prints a warning if given an empty list
Chris@562 70 if (!m_watcher.directories().empty()) {
Chris@562 71 m_watcher.removePaths(m_watcher.directories());
Chris@562 72 }
Chris@562 73 if (!m_watcher.files().empty()) {
Chris@562 74 m_watcher.removePaths(m_watcher.files());
Chris@562 75 }
Chris@538 76 m_workDirPath = path;
Chris@538 77 addWorkDirectory(path);
Chris@540 78 debugPrint();
Chris@538 79 }
Chris@538 80
Chris@538 81 void
Chris@539 82 FsWatcher::setTrackedFilePaths(QStringList paths)
Chris@539 83 {
Chris@539 84 QMutexLocker locker(&m_mutex);
Chris@541 85
Chris@541 86 QSet<QString> alreadyWatched =
Chris@541 87 QSet<QString>::fromList(m_watcher.files());
Chris@541 88
Chris@539 89 foreach (QString path, paths) {
Chris@542 90 path = m_workDirPath + QDir::separator() + path;
Chris@541 91 if (!alreadyWatched.contains(path)) {
Chris@541 92 m_watcher.addPath(path);
Chris@541 93 } else {
Chris@541 94 alreadyWatched.remove(path);
Chris@541 95 }
Chris@539 96 }
Chris@541 97
Chris@541 98 // Remove the remaining paths, those that were being watched
Chris@541 99 // before but that are not in the list we were given
Chris@541 100 foreach (QString path, alreadyWatched) {
Chris@541 101 m_watcher.removePath(path);
Chris@541 102 }
Chris@541 103
Chris@540 104 debugPrint();
Chris@539 105 }
Chris@539 106
Chris@539 107 void
Chris@538 108 FsWatcher::addWorkDirectory(QString path)
Chris@538 109 {
Chris@538 110 // QFileSystemWatcher will refuse to add a file or directory to
Chris@538 111 // its watch list that it is already watching -- fine -- but it
Chris@538 112 // prints a warning when this happens, which we wouldn't want. So
Chris@538 113 // we'll check for duplicates ourselves.
Chris@538 114 QSet<QString> alreadyWatched =
Chris@538 115 QSet<QString>::fromList(m_watcher.directories());
Chris@538 116
Chris@538 117 std::deque<QString> pending;
Chris@538 118 pending.push_back(path);
Chris@538 119
Chris@538 120 while (!pending.empty()) {
Chris@538 121
Chris@538 122 QString path = pending.front();
Chris@538 123 pending.pop_front();
Chris@538 124 if (!alreadyWatched.contains(path)) {
Chris@538 125 m_watcher.addPath(path);
Chris@540 126 m_dirContents[path] = scanDirectory(path);
Chris@538 127 }
Chris@538 128
Chris@538 129 QDir d(path);
Chris@538 130 if (d.exists()) {
Chris@538 131 d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot |
Chris@538 132 QDir::Readable | QDir::NoSymLinks);
Chris@538 133 foreach (QString entry, d.entryList()) {
Chris@538 134 if (entry.startsWith('.')) continue;
Chris@538 135 QString entryPath = d.absoluteFilePath(entry);
Chris@538 136 pending.push_back(entryPath);
Chris@538 137 }
Chris@538 138 }
Chris@538 139 }
Chris@538 140 }
Chris@538 141
Chris@538 142 void
Chris@538 143 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes)
Chris@538 144 {
Chris@538 145 QMutexLocker locker(&m_mutex);
Chris@538 146 m_ignoredPrefixes = prefixes;
Chris@538 147 }
Chris@538 148
Chris@538 149 void
Chris@538 150 FsWatcher::setIgnoredFileSuffixes(QStringList suffixes)
Chris@538 151 {
Chris@538 152 QMutexLocker locker(&m_mutex);
Chris@538 153 m_ignoredSuffixes = suffixes;
Chris@538 154 }
Chris@538 155
Chris@538 156 int
Chris@538 157 FsWatcher::getNewToken()
Chris@538 158 {
Chris@538 159 QMutexLocker locker(&m_mutex);
Chris@538 160 int token = ++m_lastToken;
Chris@538 161 m_tokenMap[token] = m_lastCounter;
Chris@538 162 return token;
Chris@538 163 }
Chris@538 164
Chris@538 165 QSet<QString>
Chris@538 166 FsWatcher::getChangedPaths(int token)
Chris@538 167 {
Chris@538 168 QMutexLocker locker(&m_mutex);
Chris@538 169 size_t lastUpdatedAt = m_tokenMap[token];
Chris@538 170 QSet<QString> changed;
Chris@538 171 for (QHash<QString, size_t>::const_iterator i = m_changes.begin();
Chris@538 172 i != m_changes.end(); ++i) {
Chris@538 173 if (i.value() > lastUpdatedAt) {
Chris@538 174 changed.insert(i.key());
Chris@538 175 }
Chris@538 176 }
Chris@538 177 m_tokenMap[token] = m_lastCounter;
Chris@538 178 return changed;
Chris@538 179 }
Chris@538 180
Chris@538 181 void
Chris@538 182 FsWatcher::fsDirectoryChanged(QString path)
Chris@538 183 {
Chris@538 184 {
Chris@538 185 QMutexLocker locker(&m_mutex);
Chris@540 186
Chris@538 187 if (shouldIgnore(path)) return;
Chris@540 188
Chris@540 189 QSet<QString> files = scanDirectory(path);
Chris@540 190 if (files == m_dirContents[path]) {
Chris@540 191 #ifdef DEBUG_FSWATCHER
Chris@540 192 std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring" << std::endl;
Chris@540 193 #endif
Chris@540 194 return;
Chris@541 195 } else {
Chris@541 196 #ifdef DEBUG_FSWATCHER
Chris@541 197 std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl;
Chris@541 198 #endif
Chris@541 199 m_dirContents[path] = files;
Chris@540 200 }
Chris@540 201
Chris@540 202 size_t counter = ++m_lastCounter;
Chris@540 203 m_changes[path] = counter;
Chris@538 204 }
Chris@540 205
Chris@538 206 emit changed();
Chris@538 207 }
Chris@538 208
Chris@538 209 void
Chris@538 210 FsWatcher::fsFileChanged(QString path)
Chris@538 211 {
Chris@540 212 {
Chris@540 213 QMutexLocker locker(&m_mutex);
Chris@540 214
Chris@540 215 // We don't check whether the file matches an ignore pattern,
Chris@540 216 // because we are only notified for file changes if we are
Chris@540 217 // watching the file explicitly, i.e. the file is in the
Chris@540 218 // tracked file paths list. So we never want to ignore them
Chris@540 219
Chris@563 220 #ifdef DEBUG_FSWATCHER
Chris@541 221 std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl;
Chris@563 222 #endif
Chris@541 223
Chris@540 224 size_t counter = ++m_lastCounter;
Chris@540 225 m_changes[path] = counter;
Chris@540 226 }
Chris@540 227
Chris@540 228 emit changed();
Chris@538 229 }
Chris@538 230
Chris@538 231 bool
Chris@538 232 FsWatcher::shouldIgnore(QString path)
Chris@538 233 {
Chris@540 234 QFileInfo fi(path);
Chris@540 235 QString fn(fi.fileName());
Chris@540 236 foreach (QString pfx, m_ignoredPrefixes) {
Chris@541 237 if (fn.startsWith(pfx)) {
Chris@563 238 #ifdef DEBUG_FSWATCHER
Chris@541 239 std::cerr << "(ignoring: " << path << ")" << std::endl;
Chris@563 240 #endif
Chris@541 241 return true;
Chris@541 242 }
Chris@540 243 }
Chris@540 244 foreach (QString sfx, m_ignoredSuffixes) {
Chris@541 245 if (fn.endsWith(sfx)) {
Chris@563 246 #ifdef DEBUG_FSWATCHER
Chris@541 247 std::cerr << "(ignoring: " << path << ")" << std::endl;
Chris@563 248 #endif
Chris@541 249 return true;
Chris@541 250 }
Chris@540 251 }
Chris@540 252 return false;
Chris@538 253 }
Chris@538 254
Chris@540 255 QSet<QString>
Chris@540 256 FsWatcher::scanDirectory(QString path)
Chris@540 257 {
Chris@540 258 QSet<QString> files;
Chris@540 259 QDir d(path);
Chris@540 260 if (d.exists()) {
Chris@540 261 d.setFilter(QDir::Files | QDir::NoDotAndDotDot |
Chris@540 262 QDir::Readable | QDir::NoSymLinks);
Chris@540 263 foreach (QString entry, d.entryList()) {
Chris@540 264 if (entry.startsWith('.')) continue;
Chris@540 265 if (shouldIgnore(entry)) continue;
Chris@540 266 files.insert(entry);
Chris@540 267 }
Chris@540 268 }
Chris@541 269 // std::cerr << "scanDirectory:" << std::endl;
Chris@541 270 // foreach (QString f, files) std::cerr << f << std::endl;
Chris@540 271 return files;
Chris@540 272 }
Chris@540 273
Chris@540 274 void
Chris@540 275 FsWatcher::debugPrint()
Chris@540 276 {
Chris@540 277 #ifdef DEBUG_FSWATCHER
Chris@540 278 std::cerr << "FsWatcher: Now watching " << m_watcher.directories().size()
Chris@540 279 << " directories and " << m_watcher.files().size()
Chris@540 280 << " files" << std::endl;
Chris@540 281 #endif
Chris@540 282 }