Mercurial > hg > easyhg
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 } |