blob: 18b6f77dac3d7ebd752527e50d44a0eb4da39361 [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
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -080063 Scheduler::scheduleOneTimeTask (m_scheduler, 1,
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080064 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 ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080079 if (!filesystem::exists (filesystem::path (absPathTriggeredDir)))
80 {
81 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
82 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath),
83 "r-" + dirPath.toStdString ()); // only one task will be scheduled per directory
84 }
85 else
86 {
87 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
88 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
89 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
90 dirPath.toStdString ()); // only one task will be scheduled per directory
91 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080092}
93
94void
95FsWatcher::DidFileChanged (QString filePath)
96{
Alexander Afanasyev69e13172013-01-25 17:16:27 -080097 if (!filePath.startsWith (m_dirPath))
98 {
99 _LOG_ERROR ("Got notification about a file not from the monitored directory");
100 return;
101 }
102 filesystem::path absPathTriggeredFile (filePath.toStdString ());
103 filePath.remove (0, m_dirPath.size ());
104
105 filesystem::path triggeredFile (filePath.toStdString ());
106 if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
107 {
108 _LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800109 // m_onChange (triggeredFile.relative_path ());
110
111 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
112 bind (m_onChange, triggeredFile.relative_path ()),
113 triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800114 }
115 else
116 {
117 _LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800118 // m_onDelete (triggeredFile.relative_path ());
119
120 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
121 bind (m_onDelete, triggeredFile.relative_path ()),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800122 "r-" + triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800123 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800124}
125
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800126void
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800127FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800128{
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800129 _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute");
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800130
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800131 // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
132 QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800133
134 QDirIterator dirIterator (dirPath,
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800135 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800136 QDirIterator::Subdirectories); // directory iterator (recursive)
137
138 // iterate through directory recursively
139 while (dirIterator.hasNext ())
140 {
141 dirIterator.next ();
142
143 // Get FileInfo
144 QFileInfo fileInfo = dirIterator.fileInfo ();
145
146 QString name = fileInfo.fileName ();
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800147 _LOG_DEBUG ("+++ Scanning: " << name.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800148
149 if (!exclude.exactMatch (name))
150 {
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800151 _LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800152 QString absFilePath = fileInfo.absoluteFilePath ();
153
154 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
155 m_watcher->addPath (absFilePath);
156
157 if (fileInfo.isFile ())
158 {
159 DidFileChanged (absFilePath);
160 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800161 }
162 else
163 {
164 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
165 }
166 }
167}
168
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800169
170void
171FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath)
172{
173 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
174
175 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
176 dirPath.remove (0, m_dirPath.size ());
177
178 filesystem::path triggeredDir (dirPath.toStdString ());
179
180 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.generic_string ());
181 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
182 {
183 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
184 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
185
186 if (!filesystem::exists (testFile))
187 {
188 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
189 m_onDelete (file->filename ());
190 }
191 }
192}
193
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800194// std::vector<sEventInfo> FsWatcher::reconcileDirectory(QHash<QString, qint64> currentState, QString dirPath)
195// {
196// // list of files changed
197// std::vector<sEventInfo> dirChanges;
198
199// // compare result (database/stored snapshot) to fileList (current snapshot)
200// QMutableHashIterator<QString, qint64> i(m_storedState);
201
202// while(i.hasNext())
203// {
204// i.next();
205
206// QString absFilePath = i.key();
207// qint64 storedCreated = i.value();
208
209// // if this file is in a level higher than
210// // this directory, ignore
211// if(!absFilePath.startsWith(dirPath))
212// {
213// continue;
214// }
215
216// // check file existence
217// if(currentState.contains(absFilePath))
218// {
219// qint64 currentCreated = currentState.value(absFilePath);
220
221// if(storedCreated != currentCreated)
222// {
223// // update stored state
224// i.setValue(currentCreated);
225
226// // this file has been modified
227// sEventInfo eventInfo;
228// eventInfo.event = MODIFIED;
229// eventInfo.absFilePath = absFilePath.toStdString();
230// dirChanges.push_back(eventInfo);
231// }
232
233// // delete this file from fileList we have processed it
234// currentState.remove(absFilePath);
235// }
236// else
237// {
238// // delete from stored state
239// i.remove();
240
241// // this file has been deleted
242// sEventInfo eventInfo;
243// eventInfo.event = DELETED;
244// eventInfo.absFilePath = absFilePath.toStdString();
245// dirChanges.push_back(eventInfo);
246// }
247// }
248
249// // any files left in fileList have been added
250// for(QHash<QString, qint64>::iterator i = currentState.begin(); i != currentState.end(); ++i)
251// {
252// QString absFilePath = i.key();
253// qint64 currentCreated = i.value();
254
255// m_storedState.insert(absFilePath, currentCreated);
256
257// // this file has been added
258// sEventInfo eventInfo;
259// eventInfo.event = ADDED;
260// eventInfo.absFilePath = absFilePath.toStdString();
261// dirChanges.push_back(eventInfo);
262// }
263
264// return dirChanges;
265// }
266
267// QByteArray FsWatcher::calcChecksum(QString absFilePath)
268// {
269// // initialize checksum
270// QCryptographicHash crypto(QCryptographicHash::Md5);
271
272// // open file
273// QFile file(absFilePath);
274// file.open(QFile::ReadOnly);
275
276// // calculate checksum
277// while(!file.atEnd())
278// {
279// crypto.addData(file.read(8192));
280// }
281
282// return crypto.result();
283// }
284
285// void FsWatcher::printChanges(std::vector<sEventInfo> dirChanges)
286// {
287// if(!dirChanges.empty())
288// {
289// for(size_t i = 0; i < dirChanges.size(); i++)
290// {
291// QString tempString;
292
293// eEvent event = dirChanges[i].event;
294// QString absFilePath = QString::fromStdString(dirChanges[i].absFilePath);
295
296// switch(event)
297// {
298// case ADDED:
299// tempString.append("ADDED: ");
300// break;
301// case MODIFIED:
302// tempString.append("MODIFIED: ");
303// break;
304// case DELETED:
305// tempString.append("DELETED: ");
306// break;
307// }
308
309// tempString.append(absFilePath);
310
311// _LOG_DEBUG ("\t" << tempString.toStdString ());
312// }
313// }
314// else
315// {
316// _LOG_DEBUG ("\t[EMPTY]");
317// }
318// }
319
320#if WAF
321#include "fs-watcher.moc"
322#include "fs-watcher.cc.moc"
323#endif