annotate src/fswatcher.cpp @ 571:012ba1b83328

Show cancel button with progress bar only when running an operation that it makes sense to cancel (we don't really want people cancelling e.g. initial folder scan because it would leave things in an inconsistent state)
author Chris Cannam
date Thu, 01 Mar 2012 22:53:54 +0000
parents 0a094020c2d4
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 }