blob: 573ddda4516c1037869cdc4398da404275f2f8a4 [file] [log] [blame]
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2013-2016, Regents of the University of California.
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -08004 *
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -08005 * This file is part of ChronoShare, a decentralized file sharing application over NDN.
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -08006 *
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -08007 * ChronoShare is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080010 *
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -080011 * ChronoShare is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080014 *
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -080015 * You should have received copies of the GNU General Public License along with
16 * ChronoShare, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * See AUTHORS.md for complete list of ChronoShare authors and contributors.
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080019 */
20
Alexander Afanasyevf4cde4e2016-12-25 13:42:57 -080021#include "fs-watcher.hpp"
22#include "db-helper.hpp"
23#include "logging.hpp"
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080024
25#include <boost/bind.hpp>
26
27#include <QDirIterator>
28#include <QRegExp>
29
30using namespace std;
31using namespace boost;
32
33INIT_LOGGER ("FsWatcher");
34
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080035FsWatcher::FsWatcher (QString dirPath,
36 LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
37 QObject* parent)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080038 : QObject(parent)
39 , m_watcher (new QFileSystemWatcher())
Alexander Afanasyev583449a2013-01-28 17:04:06 -080040 , m_scheduler (new Scheduler ())
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080041 , m_dirPath (dirPath)
Alexander Afanasyev9ca444e2013-01-25 16:29:35 -080042 , m_onChange (onChange)
43 , m_onDelete (onDelete)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080044{
45 _LOG_DEBUG ("Monitor dir: " << m_dirPath.toStdString ());
46 // add main directory to monitor
Alexander Afanasyev69e13172013-01-25 17:16:27 -080047
Zhenkai Zhud1756272013-02-01 17:02:18 -080048 initFileStateDb();
49
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080050 m_watcher->addPath (m_dirPath);
51
52 // register signals (callback functions)
53 connect (m_watcher, SIGNAL (directoryChanged (QString)), this, SLOT (DidDirectoryChanged (QString)));
54 connect (m_watcher, SIGNAL (fileChanged (QString)), this, SLOT (DidFileChanged (QString)));
55
Alexander Afanasyev583449a2013-01-28 17:04:06 -080056 m_scheduler->start ();
57
Alexander Afanasyev7943c6b2013-01-29 18:43:24 -080058 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Zhenkai Zhud1756272013-02-01 17:02:18 -080059 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080060 "rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080061
Alexander Afanasyev7943c6b2013-01-29 18:43:24 -080062 Scheduler::scheduleOneTimeTask (m_scheduler, 0,
Zhenkai Zhud1756272013-02-01 17:02:18 -080063 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080064 "rescan-" +m_dirPath.toStdString ()); // only one task will be scheduled per directory
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080065}
66
67FsWatcher::~FsWatcher()
68{
Alexander Afanasyev583449a2013-01-28 17:04:06 -080069 m_scheduler->shutdown ();
Zhenkai Zhud1756272013-02-01 17:02:18 -080070 sqlite3_close(m_db);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080071}
72
73void
74FsWatcher::DidDirectoryChanged (QString dirPath)
75{
Yingdi Yua264b872013-07-10 18:07:23 -070076 _LOG_DEBUG ("Triggered DirPath(DidDirectoryChanged): " << 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,
Zhenkai Zhud1756272013-02-01 17:02:18 -080082 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath),
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,
Zhenkai Zhud1756272013-02-01 17:02:18 -080089 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
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,
Zhenkai Zhud1756272013-02-01 17:02:18 -080094 bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath),
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080095 "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,
Zhenkai Zhud1756272013-02-01 17:02:18 -080098 bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath),
Alexander Afanasyev7a647002013-01-30 11:54:52 -080099 "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 {
Zhenkai Zhud1756272013-02-01 17:02:18 -0800108 _LOG_ERROR ("Got notification about a file not from the monitored directory: " << filePath.toStdString());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800109 return;
110 }
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800111 QString absFilePath = filePath;
112
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800113 filesystem::path absPathTriggeredFile (filePath.toStdString ());
114 filePath.remove (0, m_dirPath.size ());
115
116 filesystem::path triggeredFile (filePath.toStdString ());
117 if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
118 {
119 _LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800120 // m_onChange (triggeredFile.relative_path ());
121
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800122 m_watcher->removePath (absFilePath);
123 m_watcher->addPath (absFilePath);
124
Yingdi Yu57f667b2013-07-11 10:37:59 -0700125 Scheduler::scheduleDelayOneTimeTask (m_scheduler, 0.5,
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800126 bind (m_onChange, triggeredFile.relative_path ()),
127 triggeredFile.relative_path ().string());
Yingdi Yu57f667b2013-07-11 10:37:59 -0700128
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800129 }
130 else
131 {
132 _LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800133 // m_onDelete (triggeredFile.relative_path ());
134
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800135 m_watcher->removePath (absFilePath);
136
Zhenkai Zhu50746f62013-02-19 21:20:25 -0800137 deleteFile(triggeredFile.relative_path());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800138 Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
139 bind (m_onDelete, triggeredFile.relative_path ()),
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800140 "r-" + triggeredFile.relative_path ().string());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800141 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800142}
143
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800144void
Zhenkai Zhud1756272013-02-01 17:02:18 -0800145FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800146{
Yingdi Yua264b872013-07-10 18:07:23 -0700147 _LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute " << dirPath.toStdString());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800148
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800149 // exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
150 QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800151
152 QDirIterator dirIterator (dirPath,
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800153 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800154 QDirIterator::Subdirectories); // directory iterator (recursive)
155
156 // iterate through directory recursively
157 while (dirIterator.hasNext ())
158 {
159 dirIterator.next ();
160
161 // Get FileInfo
162 QFileInfo fileInfo = dirIterator.fileInfo ();
163
164 QString name = fileInfo.fileName ();
Alexander Afanasyev4a8781c2013-01-29 17:59:44 -0800165 _LOG_DEBUG ("+++ Scanning: " << name.toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800166
167 if (!exclude.exactMatch (name))
168 {
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800169 _LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800170 QString absFilePath = fileInfo.absoluteFilePath ();
171
172 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
Alexander Afanasyevc4942042013-02-19 13:54:25 -0800173 m_watcher->removePath (absFilePath);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800174 m_watcher->addPath (absFilePath);
175
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800176 if (fileInfo.isFile ())
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800177 {
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800178 QString relFile = absFilePath;
179 relFile.remove (0, m_dirPath.size ());
180 filesystem::path aFile (relFile.toStdString ());
181
Zhenkai Zhud1756272013-02-01 17:02:18 -0800182 if (
183 //!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800184 !fileExists(aFile.relative_path()) /*file does not exist in db, but exists in fs: add */)
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800185 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800186 addFile(aFile.relative_path());
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800187 DidFileChanged (absFilePath);
188 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800189 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800190 }
191 else
192 {
193 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
194 }
195 }
196}
197
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800198
199void
Zhenkai Zhud1756272013-02-01 17:02:18 -0800200FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath)
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800201{
202 _LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
203
204 filesystem::path absPathTriggeredDir (dirPath.toStdString ());
205 dirPath.remove (0, m_dirPath.size ());
206
207 filesystem::path triggeredDir (dirPath.toStdString ());
208
Zhenkai Zhud1756272013-02-01 17:02:18 -0800209 /*
Alexander Afanasyev506ff222013-01-29 18:12:55 -0800210 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800211 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
212 {
213 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
214 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
215
216 if (!filesystem::exists (testFile))
217 {
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800218 if (removeIncomplete || file->is_complete ())
219 {
220 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
221 m_onDelete (file->filename ());
222 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800223 }
224 }
Zhenkai Zhud1756272013-02-01 17:02:18 -0800225 */
226
227 vector<string> files;
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800228 getFilesInDir(triggeredDir.relative_path(), files);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800229 for (vector<string>::iterator file = files.begin(); file != files.end(); file++)
230 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800231 filesystem::path targetFile = filesystem::path (m_dirPath.toStdString()) / *file;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800232 if (!filesystem::exists (targetFile))
233 {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800234 deleteFile(*file);
235 m_onDelete(*file);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800236 }
237 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800238}
239
Zhenkai Zhud1756272013-02-01 17:02:18 -0800240const string INIT_DATABASE = "\
241CREATE TABLE IF NOT EXISTS \n\
242 Files( \n\
243 filename TEXT NOT NULL, \n\
244 PRIMARY KEY (filename) \n\
245); \n\
246CREATE INDEX filename_index ON Files (filename); \n\
247";
248
249void
250FsWatcher::initFileStateDb()
251{
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800252 filesystem::path dbFolder = filesystem::path (m_dirPath.toStdString()) / ".chronoshare" / "fs_watcher";
Zhenkai Zhud1756272013-02-01 17:02:18 -0800253 filesystem::create_directories(dbFolder);
254
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800255 int res = sqlite3_open((dbFolder / "filestate.db").string ().c_str (), &m_db);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800256 if (res != SQLITE_OK)
257 {
258 BOOST_THROW_EXCEPTION(Error::Db() << errmsg_info_str("Cannot open database: " + (dbFolder / "filestate.db").string()));
259 }
260
261 char *errmsg = 0;
262 res = sqlite3_exec(m_db, INIT_DATABASE.c_str(), NULL, NULL, &errmsg);
263 if (res != SQLITE_OK && errmsg != 0)
264 {
265 // _LOG_TRACE ("Init \"error\": " << errmsg);
266 cout << "FS-Watcher DB error: " << errmsg << endl;
267 sqlite3_free (errmsg);
268 }
269}
270
271bool
272FsWatcher::fileExists(const filesystem::path &filename)
273{
274 sqlite3_stmt *stmt;
275 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename = ?;", -1, &stmt, 0);
276 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
277 bool retval = false;
278 if (sqlite3_step (stmt) == SQLITE_ROW)
279 {
280 retval = true;
281 }
282 sqlite3_finalize(stmt);
283
284 return retval;
285}
286
287void
288FsWatcher::addFile(const filesystem::path &filename)
289{
290 sqlite3_stmt *stmt;
291 sqlite3_prepare_v2(m_db, "INSERT OR IGNORE INTO Files (filename) VALUES (?);", -1, &stmt, 0);
292 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
293 sqlite3_step(stmt);
294 sqlite3_finalize(stmt);
295}
296
297void
298FsWatcher::deleteFile(const filesystem::path &filename)
299{
300 sqlite3_stmt *stmt;
301 sqlite3_prepare_v2(m_db, "DELETE FROM Files WHERE filename = ?;", -1, &stmt, 0);
302 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
303 sqlite3_step(stmt);
304 sqlite3_finalize(stmt);
305}
306
307void
308FsWatcher::getFilesInDir(const filesystem::path &dir, vector<string> &files)
309{
310 sqlite3_stmt *stmt;
311 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename LIKE ?;", -1, &stmt, 0);
312
313 string dirStr = dir.string();
314 ostringstream escapedDir;
315 for (string::const_iterator ch = dirStr.begin (); ch != dirStr.end (); ch ++)
316 {
317 if (*ch == '%')
318 escapedDir << "\\%";
319 else
320 escapedDir << *ch;
321 }
322 escapedDir << "/" << "%";
323 string escapedDirStr = escapedDir.str ();
324 sqlite3_bind_text (stmt, 1, escapedDirStr.c_str (), escapedDirStr.size (), SQLITE_STATIC);
325
326 while(sqlite3_step(stmt) == SQLITE_ROW)
327 {
328 string filename(reinterpret_cast<const char *>(sqlite3_column_text (stmt, 0)), sqlite3_column_bytes(stmt, 0));
329 files.push_back(filename);
330 }
331
332 sqlite3_finalize (stmt);
333
334}
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800335
336#if WAF
337#include "fs-watcher.moc"
338#include "fs-watcher.cc.moc"
339#endif