blob: 06e849fb31ec617fac947945ebdb36be99365239 [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"
Zhenkai Zhud1756272013-02-01 17:02:18 -080024#include "db-helper.h"
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080025#include "logging.h"
26
27#include <boost/bind.hpp>
28
29#include <QDirIterator>
30#include <QRegExp>
31
32using namespace std;
33using namespace boost;
34
35INIT_LOGGER ("FsWatcher");
36
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080037FsWatcher::FsWatcher (QString dirPath,
38 LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
39 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 Afanasyev9e5a4702013-01-24 13:15:23 -080046{
47 _LOG_DEBUG ("Monitor dir: " << m_dirPath.toStdString ());
48 // add main directory to monitor
Alexander Afanasyev69e13172013-01-25 17:16:27 -080049
Zhenkai Zhud1756272013-02-01 17:02:18 -080050 initFileStateDb();
51
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080052 m_watcher->addPath (m_dirPath);
53
54 // register signals (callback functions)
55 connect (m_watcher, SIGNAL (directoryChanged (QString)), this, SLOT (DidDirectoryChanged (QString)));
56 connect (m_watcher, SIGNAL (fileChanged (QString)), this, SLOT (DidFileChanged (QString)));
57
Alexander Afanasyev583449a2013-01-28 17:04:06 -080058 m_scheduler->start ();
59
Alexander Afanasyev7943c6b2013-01-29 18:43:24 -080060 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Zhenkai Zhud1756272013-02-01 17:02:18 -080061 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080062 "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080063
Alexander Afanasyev7943c6b2013-01-29 18:43:24 -080064 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Zhenkai Zhud1756272013-02-01 17:02:18 -080065 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080066 "rescan-" +m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080067}
68
69FsWatcher::~FsWatcher()
70{
Alexander Afanasyev583449a2013-01-28 17:04:06 -080071 m_scheduler->shutdown ();
Zhenkai Zhud1756272013-02-01 17:02:18 -080072 sqlite3_close(m_db);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080073}
74
75void
76FsWatcher::DidDirectoryChanged (QString dirPath)
77{
Yingdi Yua264b872013-07-10 18:07:23 -070078 _LOG_DEBUG ("Triggered DirPath(DidDirectoryChanged): " << dirPath.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080079
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080080 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080081 if (!filesystem::exists (filesystem::path (absPathTriggeredDir)))
82 {
83 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
Zhenkai Zhud1756272013-02-01 17:02:18 -080084 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080085 "r-" + dirPath.toStdString ()); // only one task will be scheduled per directory
86 }
87 else
88 {
89 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
90 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
Zhenkai Zhud1756272013-02-01 17:02:18 -080091 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080092 dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080093
94 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
95 Scheduler::scheduleOneTimeTask (m_scheduler, 300,
Zhenkai Zhud1756272013-02-01 17:02:18 -080096 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080097 "rescan-"+dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev7a647002013-01-30 11:54:52 -080098
99 Scheduler::scheduleOneTimeTask (m_scheduler, 300,
Zhenkai Zhud1756272013-02-01 17:02:18 -0800100 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800101 "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800102 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800103}
104
105void
106FsWatcher::DidFileChanged (QString filePath)
107{
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800108 if (!filePath.startsWith (m_dirPath))
109 {
Zhenkai Zhud1756272013-02-01 17:02:18 -0800110 _LOG_ERROR ("Got notification about a file not from the monitored directory: " << filePath.toStdString());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800111 return;
112 }
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800113 QString absFilePath = filePath;
114
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800115 filesystem::path absPathTriggeredFile (filePath.toStdString ());
116 filePath.remove (0, m_dirPath.size ());
117
118 filesystem::path triggeredFile (filePath.toStdString ());
119 if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
120 {
121 _LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800122 // m_onChange (triggeredFile.relative_path ());
123
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800124 m_watcher->removePath (absFilePath);
125 m_watcher->addPath (absFilePath);
126
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800127 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
128 bind (m_onChange, triggeredFile.relative_path ()),
129 triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800130 }
131 else
132 {
133 _LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800134 // m_onDelete (triggeredFile.relative_path ());
135
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800136 m_watcher->removePath (absFilePath);
137
Zhenkai Zhu50746f62013-02-19 21:20:25 -0800138 deleteFile(triggeredFile.relative_path());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800139 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
140 bind (m_onDelete, triggeredFile.relative_path ()),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800141 "r-" + triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800142 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800143}
144
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800145void
Zhenkai Zhud1756272013-02-01 17:02:18 -0800146FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800147{
Yingdi Yua264b872013-07-10 18:07:23 -0700148 _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute " << dirPath.toStdString());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800149
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800150 // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
151 QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800152
153 QDirIterator dirIterator (dirPath,
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800154 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800155 QDirIterator::Subdirectories); // directory iterator (recursive)
156
157 // iterate through directory recursively
158 while (dirIterator.hasNext ())
159 {
160 dirIterator.next ();
161
162 // Get FileInfo
163 QFileInfo fileInfo = dirIterator.fileInfo ();
164
165 QString name = fileInfo.fileName ();
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800166 _LOG_DEBUG ("+++ Scanning: " << name.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800167
168 if (!exclude.exactMatch (name))
169 {
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800170 _LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800171 QString absFilePath = fileInfo.absoluteFilePath ();
172
173 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
Alexander Afanasyevc4942042013-02-19 13:54:25 -0800174 m_watcher->removePath (absFilePath);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800175 m_watcher->addPath (absFilePath);
176
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800177 if (fileInfo.isFile ())
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800178 {
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800179 QString relFile = absFilePath;
180 relFile.remove (0, m_dirPath.size ());
181 filesystem::path aFile (relFile.toStdString ());
182
Zhenkai Zhud1756272013-02-01 17:02:18 -0800183 if (
184 //!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800185 !fileExists(aFile.relative_path()) /*file does not exist in db, but exists in fs: add */)
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800186 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800187 addFile(aFile.relative_path());
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800188 DidFileChanged (absFilePath);
189 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800190 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800191 }
192 else
193 {
194 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
195 }
196 }
197}
198
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800199
200void
Zhenkai Zhud1756272013-02-01 17:02:18 -0800201FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath)
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800202{
203 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
204
205 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
206 dirPath.remove (0, m_dirPath.size ());
207
208 filesystem::path triggeredDir (dirPath.toStdString ());
209
Zhenkai Zhud1756272013-02-01 17:02:18 -0800210 /*
Alexander Afanasyev506ff222013-01-29 18:12:55 -0800211 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800212 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
213 {
214 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
215 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
216
217 if (!filesystem::exists (testFile))
218 {
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800219 if (removeIncomplete || file->is_complete ())
220 {
221 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
222 m_onDelete (file->filename ());
223 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800224 }
225 }
Zhenkai Zhud1756272013-02-01 17:02:18 -0800226 */
227
228 vector<string> files;
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800229 getFilesInDir(triggeredDir.relative_path(), files);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800230 for (vector<string>::iterator file = files.begin(); file != files.end(); file++)
231 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800232 filesystem::path targetFile = filesystem::path (m_dirPath.toStdString()) / *file;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800233 if (!filesystem::exists (targetFile))
234 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800235 deleteFile(*file);
236 m_onDelete(*file);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800237 }
238 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800239}
240
Zhenkai Zhud1756272013-02-01 17:02:18 -0800241const string INIT_DATABASE = "\
242CREATE TABLE IF NOT EXISTS \n\
243 Files( \n\
244 filename TEXT NOT NULL, \n\
245 PRIMARY KEY (filename) \n\
246); \n\
247CREATE INDEX filename_index ON Files (filename); \n\
248";
249
250void
251FsWatcher::initFileStateDb()
252{
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800253 filesystem::path dbFolder = filesystem::path (m_dirPath.toStdString()) / ".chronoshare" / "fs_watcher";
Zhenkai Zhud1756272013-02-01 17:02:18 -0800254 filesystem::create_directories(dbFolder);
255
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800256 int res = sqlite3_open((dbFolder / "filestate.db").string ().c_str (), &m_db);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800257 if (res != SQLITE_OK)
258 {
259 BOOST_THROW_EXCEPTION(Error::Db() << errmsg_info_str("Cannot open database: " + (dbFolder / "filestate.db").string()));
260 }
261
262 char *errmsg = 0;
263 res = sqlite3_exec(m_db, INIT_DATABASE.c_str(), NULL, NULL, &errmsg);
264 if (res != SQLITE_OK && errmsg != 0)
265 {
266 // _LOG_TRACE ("Init \"error\": " << errmsg);
267 cout << "FS-Watcher DB error: " << errmsg << endl;
268 sqlite3_free (errmsg);
269 }
270}
271
272bool
273FsWatcher::fileExists(const filesystem::path &filename)
274{
275 sqlite3_stmt *stmt;
276 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename = ?;", -1, &stmt, 0);
277 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
278 bool retval = false;
279 if (sqlite3_step (stmt) == SQLITE_ROW)
280 {
281 retval = true;
282 }
283 sqlite3_finalize(stmt);
284
285 return retval;
286}
287
288void
289FsWatcher::addFile(const filesystem::path &filename)
290{
291 sqlite3_stmt *stmt;
292 sqlite3_prepare_v2(m_db, "INSERT OR IGNORE INTO Files (filename) VALUES (?);", -1, &stmt, 0);
293 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
294 sqlite3_step(stmt);
295 sqlite3_finalize(stmt);
296}
297
298void
299FsWatcher::deleteFile(const filesystem::path &filename)
300{
301 sqlite3_stmt *stmt;
302 sqlite3_prepare_v2(m_db, "DELETE FROM Files WHERE filename = ?;", -1, &stmt, 0);
303 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
304 sqlite3_step(stmt);
305 sqlite3_finalize(stmt);
306}
307
308void
309FsWatcher::getFilesInDir(const filesystem::path &dir, vector<string> &files)
310{
311 sqlite3_stmt *stmt;
312 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename LIKE ?;", -1, &stmt, 0);
313
314 string dirStr = dir.string();
315 ostringstream escapedDir;
316 for (string::const_iterator ch = dirStr.begin (); ch != dirStr.end (); ch ++)
317 {
318 if (*ch == '%')
319 escapedDir << "\\%";
320 else
321 escapedDir << *ch;
322 }
323 escapedDir << "/" << "%";
324 string escapedDirStr = escapedDir.str ();
325 sqlite3_bind_text (stmt, 1, escapedDirStr.c_str (), escapedDirStr.size (), SQLITE_STATIC);
326
327 while(sqlite3_step(stmt) == SQLITE_ROW)
328 {
329 string filename(reinterpret_cast<const char *>(sqlite3_column_text (stmt, 0)), sqlite3_column_bytes(stmt, 0));
330 files.push_back(filename);
331 }
332
333 sqlite3_finalize (stmt);
334
335}
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800336
337#if WAF
338#include "fs-watcher.moc"
339#include "fs-watcher.cc.moc"
340#endif