blob: 18d004d4b6602443f947cec774073283b884a98a [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 Afanasyev7943c6b2013-01-29 18:43:24 -080059 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Alexander Afanasyev7a647002013-01-30 11:54:52 -080060 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
61 "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080062
Alexander Afanasyev7943c6b2013-01-29 18:43:24 -080063 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080064 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath, true),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080065 "rescan-" +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,
Alexander Afanasyev7a647002013-01-30 11:54:52 -080082 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath, true/* ignore incomplete file flag. the whole directory got removed*/),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080083 "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,
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080089 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, false),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080090 dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080091
92 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
93 Scheduler::scheduleOneTimeTask (m_scheduler, 300,
94 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, true),
95 "rescan-"+dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev7a647002013-01-30 11:54:52 -080096
97 Scheduler::scheduleOneTimeTask (m_scheduler, 300,
98 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
99 "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800100 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800101}
102
103void
104FsWatcher::DidFileChanged (QString filePath)
105{
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800106 if (!filePath.startsWith (m_dirPath))
107 {
108 _LOG_ERROR ("Got notification about a file not from the monitored directory");
109 return;
110 }
111 filesystem::path absPathTriggeredFile (filePath.toStdString ());
112 filePath.remove (0, m_dirPath.size ());
113
114 filesystem::path triggeredFile (filePath.toStdString ());
115 if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
116 {
117 _LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800118 // m_onChange (triggeredFile.relative_path ());
119
120 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
121 bind (m_onChange, triggeredFile.relative_path ()),
122 triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800123 }
124 else
125 {
126 _LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800127 // m_onDelete (triggeredFile.relative_path ());
128
129 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
130 bind (m_onDelete, triggeredFile.relative_path ()),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800131 "r-" + triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800132 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800133}
134
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800135void
Alexander Afanasyevc80207d2013-01-29 20:07:39 -0800136FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath, bool notifyCallbacks)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800137{
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800138 _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute");
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800139
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800140 // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
141 QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800142
143 QDirIterator dirIterator (dirPath,
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800144 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800145 QDirIterator::Subdirectories); // directory iterator (recursive)
146
147 // iterate through directory recursively
148 while (dirIterator.hasNext ())
149 {
150 dirIterator.next ();
151
152 // Get FileInfo
153 QFileInfo fileInfo = dirIterator.fileInfo ();
154
155 QString name = fileInfo.fileName ();
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800156 _LOG_DEBUG ("+++ Scanning: " << name.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800157
158 if (!exclude.exactMatch (name))
159 {
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800160 _LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800161 QString absFilePath = fileInfo.absoluteFilePath ();
162
163 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
164 m_watcher->addPath (absFilePath);
165
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800166 if (fileInfo.isFile ())
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800167 {
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800168 QString relFile = absFilePath;
169 relFile.remove (0, m_dirPath.size ());
170 filesystem::path aFile (relFile.toStdString ());
171
172 if (notifyCallbacks ||
173 !m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
174 {
175 DidFileChanged (absFilePath);
176 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800177 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800178 }
179 else
180 {
181 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
182 }
183 }
184}
185
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800186
187void
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800188FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath, bool removeIncomplete)
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800189{
190 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
191
192 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
193 dirPath.remove (0, m_dirPath.size ());
194
195 filesystem::path triggeredDir (dirPath.toStdString ());
196
Alexander Afanasyev506ff222013-01-29 18:12:55 -0800197 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800198 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
199 {
200 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
201 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
202
203 if (!filesystem::exists (testFile))
204 {
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800205 if (removeIncomplete || file->is_complete ())
206 {
207 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
208 m_onDelete (file->filename ());
209 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800210 }
211 }
212}
213
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800214
215#if WAF
216#include "fs-watcher.moc"
217#include "fs-watcher.cc.moc"
218#endif