| 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@538 | 8     Copyright (c) 2011 Chris Cannam | 
| Chris@538 | 9     Copyright (c) 2011 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@538 | 19 | 
| Chris@538 | 20 #include <QMutexLocker> | 
| Chris@538 | 21 #include <QDir> | 
| Chris@538 | 22 | 
| Chris@538 | 23 #include <deque> | 
| Chris@538 | 24 | 
| Chris@539 | 25 /* | 
| Chris@539 | 26  * Watching the filesystem is trickier than it seems at first glance. | 
| Chris@539 | 27  * | 
| Chris@539 | 28  * We ideally should watch every directory, and every file that is | 
| Chris@539 | 29  * tracked by Hg. If a new file is created in a directory, then we | 
| Chris@539 | 30  * need to respond in order to show it as a potential candidate to be | 
| Chris@539 | 31  * added. | 
| Chris@539 | 32  * | 
| Chris@539 | 33  * Complicating matters though is that Hg itself might modify the | 
| Chris@539 | 34  * filesystem. This can happen even in "read-only" operations: for | 
| Chris@539 | 35  * example, hg stat creates files called hg-checklink and hg-checkexec | 
| Chris@539 | 36  * to test properties of the filesystem. So we need to know to ignore | 
| Chris@539 | 37  * those files; unfortunately, when watching a directory (which is how | 
| Chris@539 | 38  * we find out about the creation of new files) we are notified only | 
| Chris@539 | 39  * that the directory has changed -- we aren't told what changed. So | 
| Chris@539 | 40  * we need to rescan the directory merely to find out whether to | 
| Chris@539 | 41  * ignore the change or not. | 
| Chris@539 | 42  */ | 
| Chris@539 | 43 | 
| Chris@538 | 44 FsWatcher::FsWatcher() : | 
| Chris@538 | 45     m_lastToken(0), | 
| Chris@538 | 46     m_lastCounter(0) | 
| Chris@538 | 47 { | 
| Chris@538 | 48     connect(&m_watcher, SIGNAL(directoryChanged(QString)), | 
| Chris@538 | 49 	    this, SLOT(fsDirectoryChanged(QString))); | 
| Chris@538 | 50     connect(&m_watcher, SIGNAL(fileChanged(QString)), | 
| Chris@538 | 51 	    this, SLOT(fsFileChanged(QString))); | 
| Chris@538 | 52 } | 
| Chris@538 | 53 | 
| Chris@538 | 54 FsWatcher::~FsWatcher() | 
| Chris@538 | 55 { | 
| Chris@538 | 56 } | 
| Chris@538 | 57 | 
| Chris@538 | 58 void | 
| Chris@538 | 59 FsWatcher::setWorkDirPath(QString path) | 
| Chris@538 | 60 { | 
| Chris@538 | 61     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 62     m_watcher.removePaths(m_watcher.directories()); | 
| Chris@538 | 63     m_watcher.removePaths(m_watcher.files()); | 
| Chris@538 | 64     m_workDirPath = path; | 
| Chris@538 | 65     addWorkDirectory(path); | 
| Chris@538 | 66 } | 
| Chris@538 | 67 | 
| Chris@538 | 68 void | 
| Chris@539 | 69 FsWatcher::setTrackedFilePaths(QStringList paths) | 
| Chris@539 | 70 { | 
| Chris@539 | 71     QMutexLocker locker(&m_mutex); | 
| Chris@539 | 72     m_watcher.removePaths(m_watcher.files()); | 
| Chris@539 | 73     foreach (QString path, paths) { | 
| Chris@539 | 74 	m_watcher.addPath(path); | 
| Chris@539 | 75     } | 
| Chris@539 | 76 } | 
| Chris@539 | 77 | 
| Chris@539 | 78 void | 
| Chris@538 | 79 FsWatcher::addWorkDirectory(QString path) | 
| Chris@538 | 80 { | 
| Chris@538 | 81     // QFileSystemWatcher will refuse to add a file or directory to | 
| Chris@538 | 82     // its watch list that it is already watching -- fine -- but it | 
| Chris@538 | 83     // prints a warning when this happens, which we wouldn't want.  So | 
| Chris@538 | 84     // we'll check for duplicates ourselves. | 
| Chris@538 | 85     QSet<QString> alreadyWatched = | 
| Chris@538 | 86 	QSet<QString>::fromList(m_watcher.directories()); | 
| Chris@538 | 87 | 
| Chris@538 | 88     std::deque<QString> pending; | 
| Chris@538 | 89     pending.push_back(path); | 
| Chris@538 | 90 | 
| Chris@538 | 91     while (!pending.empty()) { | 
| Chris@538 | 92 | 
| Chris@538 | 93         QString path = pending.front(); | 
| Chris@538 | 94         pending.pop_front(); | 
| Chris@538 | 95         if (!alreadyWatched.contains(path)) { | 
| Chris@538 | 96             m_watcher.addPath(path); | 
| Chris@538 | 97         } | 
| Chris@538 | 98 | 
| Chris@538 | 99         QDir d(path); | 
| Chris@538 | 100         if (d.exists()) { | 
| Chris@538 | 101             d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | | 
| Chris@538 | 102                         QDir::Readable | QDir::NoSymLinks); | 
| Chris@538 | 103             foreach (QString entry, d.entryList()) { | 
| Chris@538 | 104                 if (entry.startsWith('.')) continue; | 
| Chris@538 | 105                 QString entryPath = d.absoluteFilePath(entry); | 
| Chris@538 | 106                 pending.push_back(entryPath); | 
| Chris@538 | 107             } | 
| Chris@538 | 108         } | 
| Chris@538 | 109     } | 
| Chris@538 | 110 } | 
| Chris@538 | 111 | 
| Chris@538 | 112 void | 
| Chris@538 | 113 FsWatcher::setIgnoredFilePrefixes(QStringList prefixes) | 
| Chris@538 | 114 { | 
| Chris@538 | 115     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 116     m_ignoredPrefixes = prefixes; | 
| Chris@538 | 117 } | 
| Chris@538 | 118 | 
| Chris@538 | 119 void | 
| Chris@538 | 120 FsWatcher::setIgnoredFileSuffixes(QStringList suffixes) | 
| Chris@538 | 121 { | 
| Chris@538 | 122     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 123     m_ignoredSuffixes = suffixes; | 
| Chris@538 | 124 } | 
| Chris@538 | 125 | 
| Chris@538 | 126 int | 
| Chris@538 | 127 FsWatcher::getNewToken() | 
| Chris@538 | 128 { | 
| Chris@538 | 129     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 130     int token = ++m_lastToken; | 
| Chris@538 | 131     m_tokenMap[token] = m_lastCounter; | 
| Chris@538 | 132     return token; | 
| Chris@538 | 133 } | 
| Chris@538 | 134 | 
| Chris@538 | 135 QSet<QString> | 
| Chris@538 | 136 FsWatcher::getChangedPaths(int token) | 
| Chris@538 | 137 { | 
| Chris@538 | 138     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 139     size_t lastUpdatedAt = m_tokenMap[token]; | 
| Chris@538 | 140     QSet<QString> changed; | 
| Chris@538 | 141     for (QHash<QString, size_t>::const_iterator i = m_changes.begin(); | 
| Chris@538 | 142 	 i != m_changes.end(); ++i) { | 
| Chris@538 | 143 	if (i.value() > lastUpdatedAt) { | 
| Chris@538 | 144 	    changed.insert(i.key()); | 
| Chris@538 | 145 	} | 
| Chris@538 | 146     } | 
| Chris@538 | 147     m_tokenMap[token] = m_lastCounter; | 
| Chris@538 | 148     return changed; | 
| Chris@538 | 149 } | 
| Chris@538 | 150 | 
| Chris@538 | 151 void | 
| Chris@538 | 152 FsWatcher::fsDirectoryChanged(QString path) | 
| Chris@538 | 153 { | 
| Chris@538 | 154     { | 
| Chris@538 | 155 	QMutexLocker locker(&m_mutex); | 
| Chris@538 | 156 	if (shouldIgnore(path)) return; | 
| Chris@538 | 157 	size_t counter = ++m_lastCounter; | 
| Chris@538 | 158 	m_changes[path] = counter; | 
| Chris@538 | 159     } | 
| Chris@538 | 160     emit changed(); | 
| Chris@538 | 161 } | 
| Chris@538 | 162 | 
| Chris@538 | 163 void | 
| Chris@538 | 164 FsWatcher::fsFileChanged(QString path) | 
| Chris@538 | 165 { | 
| Chris@538 | 166     QMutexLocker locker(&m_mutex); | 
| Chris@538 | 167 } | 
| Chris@538 | 168 | 
| Chris@538 | 169 bool | 
| Chris@538 | 170 FsWatcher::shouldIgnore(QString path) | 
| Chris@538 | 171 { | 
| Chris@538 | 172 | 
| Chris@538 | 173 } | 
| Chris@538 | 174 |