blob: b8872920149be2e0633fbb1c54041b87720888a5 [file] [log] [blame]
Jared Lindblom2eead682013-01-12 20:26:21 -08001/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2012-2013 University of California, Los Angeles
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation;
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 * Author: Jared Lindblom <lindblom@cs.ucla.edu>
19 */
jareda9541812013-01-09 00:08:46 -080020
Jared Lindblom2eead682013-01-12 20:26:21 -080021#include "filesystemwatcher.h"
22
23FileSystemWatcher::FileSystemWatcher(QString dirPath, QObject* parent) :
24 QObject(parent),
jareda9541812013-01-09 00:08:46 -080025 m_watcher(new QFileSystemWatcher()),
Jared Lindblom2eead682013-01-12 20:26:21 -080026 m_timer(new QTimer()),
jareda9541812013-01-09 00:08:46 -080027 m_dirPath(dirPath)
28{
jareda9541812013-01-09 00:08:46 -080029 // add main directory to monitor
30 m_watcher->addPath(m_dirPath);
31
jareda9541812013-01-09 00:08:46 -080032 // register signals (callback functions)
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080033 connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherCallbackSlot(QString)));
34 connect(m_timer, SIGNAL(timeout()), this, SLOT(timerCallbackSlot()));
jareda9541812013-01-09 00:08:46 -080035
Jared Lindbloma9996302013-01-12 00:25:16 -080036 // bootstrap
Jared Lindblom2eead682013-01-12 20:26:21 -080037 QTimer::singleShot(1000, this, SLOT(bootstrap()));
Jared Lindbloma9996302013-01-12 00:25:16 -080038
39 // start timer
Jared Lindblom8278b982013-01-12 15:45:09 -080040 m_timer->start(300000);
jareda9541812013-01-09 00:08:46 -080041}
42
Jared Lindblom4d1d00a2013-01-11 01:14:23 -080043FileSystemWatcher::~FileSystemWatcher()
jareda9541812013-01-09 00:08:46 -080044{
45 // clean up
jareda9541812013-01-09 00:08:46 -080046 delete m_watcher;
Jared Lindbloma9996302013-01-12 00:25:16 -080047 delete m_timer;
jareda9541812013-01-09 00:08:46 -080048}
49
Jared Lindblom2eead682013-01-12 20:26:21 -080050void FileSystemWatcher::bootstrap()
51{
52 // bootstrap specific steps
Jared Lindblomd5ba95f2013-01-12 22:50:09 -080053#if DEBUG
54 qDebug() << endl << "[BOOTSTRAP]";
55#endif
Jared Lindblom2eead682013-01-12 20:26:21 -080056 timerCallbackSlot();
Jared Lindblomd5ba95f2013-01-12 22:50:09 -080057#if DEBUG
58 qDebug() << endl << "[\\BOOTSTRAP]";
59#endif
Jared Lindblom2eead682013-01-12 20:26:21 -080060}
61
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080062void FileSystemWatcher::watcherCallbackSlot(QString dirPath)
63{
64 // watcher specific steps
Jared Lindblomc1259722013-01-12 16:47:32 -080065#if DEBUG
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080066 qDebug() << endl << "[WATCHER] Triggered Path: " << dirPath;
Jared Lindblomc1259722013-01-12 16:47:32 -080067#endif
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080068 handleCallback(dirPath);
69}
70
71void FileSystemWatcher::timerCallbackSlot()
72{
73 // timer specific steps
Jared Lindblomc1259722013-01-12 16:47:32 -080074#if DEBUG
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080075 qDebug() << endl << "[TIMER] Triggered Path: " << m_dirPath;
Jared Lindblomc1259722013-01-12 16:47:32 -080076#endif
Jared Lindblomf8d32ad2013-01-12 11:32:27 -080077 handleCallback(m_dirPath);
78}
79
80void FileSystemWatcher::handleCallback(QString dirPath)
jareda9541812013-01-09 00:08:46 -080081{
Jared Lindblom4d1d00a2013-01-11 01:14:23 -080082 // scan directory and populate file list
Jared Lindblom8278b982013-01-12 15:45:09 -080083 QHash<QString, qint64> currentState = scanDirectory(dirPath);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -080084
Jared Lindblom264310d2013-01-12 01:18:58 -080085 // reconcile directory and report changes
Jared Lindblomc1259722013-01-12 16:47:32 -080086 std::vector<sEventInfo> dirChanges = reconcileDirectory(currentState, dirPath);
Jared Lindblomc1259722013-01-12 16:47:32 -080087#if DEBUG
88 // DEBUG: Print Changes
89 printChanges(dirChanges);
90#endif
Jared Lindblomd5ba95f2013-01-12 22:50:09 -080091 // emit the signal if not empty
92 if(!dirChanges.empty())
93 emit dirEventSignal(dirChanges);
jareda9541812013-01-09 00:08:46 -080094}
95
Jared Lindblom8278b982013-01-12 15:45:09 -080096QHash<QString, qint64> FileSystemWatcher::scanDirectory(QString dirPath)
Jared Lindbloma9996302013-01-12 00:25:16 -080097{
jareda9541812013-01-09 00:08:46 -080098 // list of files in directory
Jared Lindblom8278b982013-01-12 15:45:09 -080099 QHash<QString, qint64> currentState;
jareda9541812013-01-09 00:08:46 -0800100
101 // directory iterator (recursive)
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800102 QDirIterator dirIterator(dirPath, QDirIterator::Subdirectories |
jareda9541812013-01-09 00:08:46 -0800103 QDirIterator::FollowSymlinks);
104
105 // iterate through directory recursively
106 while(dirIterator.hasNext())
107 {
108 // Get Next File/Dir
109 dirIterator.next();
110
111 // Get FileInfo
112 QFileInfo fileInfo = dirIterator.fileInfo();
113
114 // if not this directory or previous directory
115 if(fileInfo.absoluteFilePath() != ".." && fileInfo.absoluteFilePath() != ".")
116 {
Jared Lindbloma9996302013-01-12 00:25:16 -0800117 QString absFilePath = fileInfo.absoluteFilePath();
118
jareda9541812013-01-09 00:08:46 -0800119 // if this is a directory
120 if(fileInfo.isDir())
121 {
122 QStringList dirList = m_watcher->directories();
123
124 // if the directory is not already being watched
Jared Lindbloma9996302013-01-12 00:25:16 -0800125 if (absFilePath.startsWith(m_dirPath) && !dirList.contains(absFilePath))
jareda9541812013-01-09 00:08:46 -0800126 {
127 // add this directory to the watch list
Jared Lindbloma9996302013-01-12 00:25:16 -0800128 m_watcher->addPath(absFilePath);
jareda9541812013-01-09 00:08:46 -0800129 }
130 }
131 else
132 {
133 // add this file to the file list
Jared Lindblom8278b982013-01-12 15:45:09 -0800134 currentState.insert(absFilePath, fileInfo.created().toMSecsSinceEpoch());
jareda9541812013-01-09 00:08:46 -0800135 }
136 }
137 }
138
Jared Lindbloma9996302013-01-12 00:25:16 -0800139 return currentState;
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800140}
141
Jared Lindblomc1259722013-01-12 16:47:32 -0800142std::vector<sEventInfo> FileSystemWatcher::reconcileDirectory(QHash<QString, qint64> currentState, QString dirPath)
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800143{
Jared Lindbloma9996302013-01-12 00:25:16 -0800144 // list of files changed
Jared Lindblomc1259722013-01-12 16:47:32 -0800145 std::vector<sEventInfo> dirChanges;
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800146
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800147 // compare result (database/stored snapshot) to fileList (current snapshot)
Jared Lindblom8278b982013-01-12 15:45:09 -0800148 QMutableHashIterator<QString, qint64> i(m_storedState);
Jared Lindblom264310d2013-01-12 01:18:58 -0800149
Jared Lindbloma9996302013-01-12 00:25:16 -0800150 while(i.hasNext())
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800151 {
Jared Lindbloma9996302013-01-12 00:25:16 -0800152 i.next();
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800153
Jared Lindbloma9996302013-01-12 00:25:16 -0800154 QString absFilePath = i.key();
Jared Lindblom8278b982013-01-12 15:45:09 -0800155 qint64 storedCreated = i.value();
Jared Lindbloma9996302013-01-12 00:25:16 -0800156
Jared Lindblom8278b982013-01-12 15:45:09 -0800157 // if this file is in a level higher than
158 // this directory, ignore
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800159 if(!absFilePath.startsWith(dirPath))
160 {
161 continue;
162 }
163
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800164 // check file existence
Jared Lindbloma9996302013-01-12 00:25:16 -0800165 if(currentState.contains(absFilePath))
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800166 {
Jared Lindblom8278b982013-01-12 15:45:09 -0800167 qint64 currentCreated = currentState.value(absFilePath);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800168
Jared Lindblom8278b982013-01-12 15:45:09 -0800169 if(storedCreated != currentCreated)
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800170 {
Jared Lindbloma9996302013-01-12 00:25:16 -0800171 // update stored state
Jared Lindblom8278b982013-01-12 15:45:09 -0800172 i.setValue(currentCreated);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800173
174 // this file has been modified
Jared Lindblom264310d2013-01-12 01:18:58 -0800175 sEventInfo eventInfo;
176 eventInfo.event = MODIFIED;
Jared Lindblom8278b982013-01-12 15:45:09 -0800177 eventInfo.absFilePath = absFilePath.toStdString();
Jared Lindblom264310d2013-01-12 01:18:58 -0800178 dirChanges.push_back(eventInfo);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800179 }
180
Jared Lindbloma9996302013-01-12 00:25:16 -0800181 // delete this file from fileList we have processed it
182 currentState.remove(absFilePath);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800183 }
184 else
185 {
Jared Lindbloma9996302013-01-12 00:25:16 -0800186 // delete from stored state
187 i.remove();
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800188
189 // this file has been deleted
Jared Lindblom264310d2013-01-12 01:18:58 -0800190 sEventInfo eventInfo;
191 eventInfo.event = DELETED;
Jared Lindblom8278b982013-01-12 15:45:09 -0800192 eventInfo.absFilePath = absFilePath.toStdString();
Jared Lindblom264310d2013-01-12 01:18:58 -0800193 dirChanges.push_back(eventInfo);
Jared Lindbloma9996302013-01-12 00:25:16 -0800194 }
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800195 }
196
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800197 // any files left in fileList have been added
Jared Lindblom8278b982013-01-12 15:45:09 -0800198 for(QHash<QString, qint64>::iterator i = currentState.begin(); i != currentState.end(); ++i)
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800199 {
200 QString absFilePath = i.key();
Jared Lindblom8278b982013-01-12 15:45:09 -0800201 qint64 currentCreated = i.value();
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800202
Jared Lindblom8278b982013-01-12 15:45:09 -0800203 m_storedState.insert(absFilePath, currentCreated);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800204
205 // this file has been added
Jared Lindblom264310d2013-01-12 01:18:58 -0800206 sEventInfo eventInfo;
207 eventInfo.event = ADDED;
Jared Lindblom8278b982013-01-12 15:45:09 -0800208 eventInfo.absFilePath = absFilePath.toStdString();
Jared Lindblom264310d2013-01-12 01:18:58 -0800209 dirChanges.push_back(eventInfo);
Jared Lindblom4d1d00a2013-01-11 01:14:23 -0800210 }
211
Jared Lindbloma9996302013-01-12 00:25:16 -0800212 return dirChanges;
jareda9541812013-01-09 00:08:46 -0800213}
Alexander Afanasyevff731962013-01-09 17:16:28 -0800214
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800215QByteArray FileSystemWatcher::calcChecksum(QString absFilePath)
216{
217 // initialize checksum
218 QCryptographicHash crypto(QCryptographicHash::Md5);
219
220 // open file
221 QFile file(absFilePath);
222 file.open(QFile::ReadOnly);
223
224 // calculate checksum
225 while(!file.atEnd())
226 {
227 crypto.addData(file.read(8192));
228 }
229
230 return crypto.result();
231}
232
Jared Lindblomc1259722013-01-12 16:47:32 -0800233void FileSystemWatcher::printChanges(std::vector<sEventInfo> dirChanges)
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800234{
Jared Lindblomc1259722013-01-12 16:47:32 -0800235 if(!dirChanges.empty())
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800236 {
Jared Lindblomc1259722013-01-12 16:47:32 -0800237 for(size_t i = 0; i < dirChanges.size(); i++)
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800238 {
239 QString tempString;
240
241 eEvent event = dirChanges[i].event;
Jared Lindblom8278b982013-01-12 15:45:09 -0800242 QString absFilePath = QString::fromStdString(dirChanges[i].absFilePath);
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800243
244 switch(event)
245 {
246 case ADDED:
247 tempString.append("ADDED: ");
248 break;
249 case MODIFIED:
250 tempString.append("MODIFIED: ");
251 break;
252 case DELETED:
253 tempString.append("DELETED: ");
254 break;
255 }
256
257 tempString.append(absFilePath);
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800258
Jared Lindblom2eead682013-01-12 20:26:21 -0800259 qDebug() << "\t" << tempString;
260 }
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800261 }
Jared Lindblomc1259722013-01-12 16:47:32 -0800262 else
263 {
Jared Lindblomc1259722013-01-12 16:47:32 -0800264 qDebug() << "\t[EMPTY]";
Jared Lindblomc1259722013-01-12 16:47:32 -0800265 }
Jared Lindblomf8d32ad2013-01-12 11:32:27 -0800266}
267
Alexander Afanasyevff731962013-01-09 17:16:28 -0800268#if WAF
269#include "filesystemwatcher.moc"
270#include "filesystemwatcher.cpp.moc"
271#endif