comparison src/fswatcher.cpp @ 548:dca5bd5b2a06

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