blob: 7eff4109e6570ab8c6e3b21ea7fc390197fa9cd9 [file] [log] [blame]
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -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 * Alexander Afanasyev <alexander.afanasyev@ucla.edu>
20 * Zhenkai Zhu <zhenkai@cs.ucla.edu>
21 */
22
23#include "fs-watcher.h"
24#include "logging.h"
25
26#include <boost/bind.hpp>
27
28#include <QDirIterator>
29#include <QRegExp>
30
31using namespace std;
32using namespace boost;
33
34INIT_LOGGER ("FsWatcher");
35
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080036FsWatcher::FsWatcher (QString dirPath,
37 LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080038 FileState *fileState,
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080039 QObject* parent)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080040 : QObject(parent)
41 , m_watcher (new QFileSystemWatcher())
Alexander Afanasyev583449a2013-01-28 17:04:06 -080042 , m_scheduler (new Scheduler ())
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080043 , m_dirPath (dirPath)
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080044 , m_onChange (onChange)
45 , m_onDelete (onDelete)
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080046 , m_fileState (fileState)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080047{
48 _LOG_DEBUG ("Monitor dir: " << m_dirPath.toStdString ());
49 // add main directory to monitor
Alexander Afanasyev69e13172013-01-25 17:16:27 -080050
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080051 m_watcher->addPath (m_dirPath);
52
53 // register signals (callback functions)
54 connect (m_watcher, SIGNAL (directoryChanged (QString)), this, SLOT (DidDirectoryChanged (QString)));
55 connect (m_watcher, SIGNAL (fileChanged (QString)), this, SLOT (DidFileChanged (QString)));
56
Alexander Afanasyev583449a2013-01-28 17:04:06 -080057 m_scheduler->start ();
58
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080059 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
60 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
61 "r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
62
63 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
64 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath),
Alexander Afanasyev583449a2013-01-28 17:04:06 -080065 m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080066}
67
68FsWatcher::~FsWatcher()
69{
Alexander Afanasyev583449a2013-01-28 17:04:06 -080070 m_scheduler->shutdown ();
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080071}
72
73void
74FsWatcher::DidDirectoryChanged (QString dirPath)
75{
Alexander Afanasyev583449a2013-01-28 17:04:06 -080076 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080077
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080078 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
79 dirPath.remove (0, m_dirPath.size ());
80
81 filesystem::path triggeredDir (dirPath.toStdString ());
82 if (!filesystem::exists (filesystem::path (absPathTriggeredDir)))
83 {
84 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
85 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath),
86 "r-" + dirPath.toStdString ()); // only one task will be scheduled per directory
87 }
88 else
89 {
90 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
91 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
92 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
93 dirPath.toStdString ()); // only one task will be scheduled per directory
94 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080095}
96
97void
98FsWatcher::DidFileChanged (QString filePath)
99{
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800100 if (!filePath.startsWith (m_dirPath))
101 {
102 _LOG_ERROR ("Got notification about a file not from the monitored directory");
103 return;
104 }
105 filesystem::path absPathTriggeredFile (filePath.toStdString ());
106 filePath.remove (0, m_dirPath.size ());
107
108 filesystem::path triggeredFile (filePath.toStdString ());
109 if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
110 {
111 _LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800112 // m_onChange (triggeredFile.relative_path ());
113
114 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
115 bind (m_onChange, triggeredFile.relative_path ()),
116 triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800117 }
118 else
119 {
120 _LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800121 // m_onDelete (triggeredFile.relative_path ());
122
123 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
124 bind (m_onDelete, triggeredFile.relative_path ()),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800125 "r-" + triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800126 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800127}
128
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800129void
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800130FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800131{
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800132 // _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute");
133
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800134 // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
135 QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800136
137 QDirIterator dirIterator (dirPath,
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800138 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800139 QDirIterator::Subdirectories); // directory iterator (recursive)
140
141 // iterate through directory recursively
142 while (dirIterator.hasNext ())
143 {
144 dirIterator.next ();
145
146 // Get FileInfo
147 QFileInfo fileInfo = dirIterator.fileInfo ();
148
149 QString name = fileInfo.fileName ();
150
151 if (!exclude.exactMatch (name))
152 {
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800153 _LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800154 QString absFilePath = fileInfo.absoluteFilePath ();
155
156 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
157 m_watcher->addPath (absFilePath);
158
159 if (fileInfo.isFile ())
160 {
161 DidFileChanged (absFilePath);
162 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800163 }
164 else
165 {
166 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
167 }
168 }
169}
170
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800171
172void
173FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath)
174{
175 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
176
177 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
178 dirPath.remove (0, m_dirPath.size ());
179
180 filesystem::path triggeredDir (dirPath.toStdString ());
181
182 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.generic_string ());
183 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
184 {
185 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
186 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
187
188 if (!filesystem::exists (testFile))
189 {
190 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
191 m_onDelete (file->filename ());
192 }
193 }
194}
195
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800196// std::vector<sEventInfo> FsWatcher::reconcileDirectory(QHash<QString, qint64> currentState, QString dirPath)
197// {
198// // list of files changed
199// std::vector<sEventInfo> dirChanges;
200
201// // compare result (database/stored snapshot) to fileList (current snapshot)
202// QMutableHashIterator<QString, qint64> i(m_storedState);
203
204// while(i.hasNext())
205// {
206// i.next();
207
208// QString absFilePath = i.key();
209// qint64 storedCreated = i.value();
210
211// // if this file is in a level higher than
212// // this directory, ignore
213// if(!absFilePath.startsWith(dirPath))
214// {
215// continue;
216// }
217
218// // check file existence
219// if(currentState.contains(absFilePath))
220// {
221// qint64 currentCreated = currentState.value(absFilePath);
222
223// if(storedCreated != currentCreated)
224// {
225// // update stored state
226// i.setValue(currentCreated);
227
228// // this file has been modified
229// sEventInfo eventInfo;
230// eventInfo.event = MODIFIED;
231// eventInfo.absFilePath = absFilePath.toStdString();
232// dirChanges.push_back(eventInfo);
233// }
234
235// // delete this file from fileList we have processed it
236// currentState.remove(absFilePath);
237// }
238// else
239// {
240// // delete from stored state
241// i.remove();
242
243// // this file has been deleted
244// sEventInfo eventInfo;
245// eventInfo.event = DELETED;
246// eventInfo.absFilePath = absFilePath.toStdString();
247// dirChanges.push_back(eventInfo);
248// }
249// }
250
251// // any files left in fileList have been added
252// for(QHash<QString, qint64>::iterator i = currentState.begin(); i != currentState.end(); ++i)
253// {
254// QString absFilePath = i.key();
255// qint64 currentCreated = i.value();
256
257// m_storedState.insert(absFilePath, currentCreated);
258
259// // this file has been added
260// sEventInfo eventInfo;
261// eventInfo.event = ADDED;
262// eventInfo.absFilePath = absFilePath.toStdString();
263// dirChanges.push_back(eventInfo);
264// }
265
266// return dirChanges;
267// }
268
269// QByteArray FsWatcher::calcChecksum(QString absFilePath)
270// {
271// // initialize checksum
272// QCryptographicHash crypto(QCryptographicHash::Md5);
273
274// // open file
275// QFile file(absFilePath);
276// file.open(QFile::ReadOnly);
277
278// // calculate checksum
279// while(!file.atEnd())
280// {
281// crypto.addData(file.read(8192));
282// }
283
284// return crypto.result();
285// }
286
287// void FsWatcher::printChanges(std::vector<sEventInfo> dirChanges)
288// {
289// if(!dirChanges.empty())
290// {
291// for(size_t i = 0; i < dirChanges.size(); i++)
292// {
293// QString tempString;
294
295// eEvent event = dirChanges[i].event;
296// QString absFilePath = QString::fromStdString(dirChanges[i].absFilePath);
297
298// switch(event)
299// {
300// case ADDED:
301// tempString.append("ADDED: ");
302// break;
303// case MODIFIED:
304// tempString.append("MODIFIED: ");
305// break;
306// case DELETED:
307// tempString.append("DELETED: ");
308// break;
309// }
310
311// tempString.append(absFilePath);
312
313// _LOG_DEBUG ("\t" << tempString.toStdString ());
314// }
315// }
316// else
317// {
318// _LOG_DEBUG ("\t[EMPTY]");
319// }
320// }
321
322#if WAF
323#include "fs-watcher.moc"
324#include "fs-watcher.cc.moc"
325#endif