blob: 65305a4197fa2a83ed7d45449f628804c6369d47 [file] [log] [blame]
Alexander Afanasyevfa2f6622016-12-25 12:28:00 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Alexander Afanasyev1cf5c432017-01-13 23:22:15 -08003 * Copyright (c) 2013-2017, 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
Alexander Afanasyev1cf5c432017-01-13 23:22:15 -080033_LOG_INIT(FsWatcher);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080034
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080035FsWatcher::FsWatcher(QString dirPath, LocalFile_Change_Callback onChange,
36 LocalFile_Change_Callback onDelete, QObject* parent)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080037 : QObject(parent)
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080038 , m_watcher(new QFileSystemWatcher())
39 , m_scheduler(new Scheduler())
40 , m_dirPath(dirPath)
41 , m_onChange(onChange)
42 , m_onDelete(onDelete)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080043{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080044 _LOG_DEBUG("Monitor dir: " << m_dirPath.toStdString());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080045 // add main directory to monitor
Alexander Afanasyev69e13172013-01-25 17:16:27 -080046
Zhenkai Zhud1756272013-02-01 17:02:18 -080047 initFileStateDb();
48
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080049 m_watcher->addPath(m_dirPath);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080050
51 // register signals (callback functions)
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080052 connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(DidDirectoryChanged(QString)));
53 connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(DidFileChanged(QString)));
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080054
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080055 m_scheduler->start();
Alexander Afanasyev583449a2013-01-28 17:04:06 -080056
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080057 Scheduler::scheduleOneTimeTask(m_scheduler, 0,
58 bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this,
59 m_dirPath),
60 "rescan-r-" +
61 m_dirPath.toStdString()); // only one task will be scheduled per directory
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -080062
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080063 Scheduler::scheduleOneTimeTask(m_scheduler, 0, bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute,
64 this, m_dirPath),
65 "rescan-" +
66 m_dirPath.toStdString()); // only one task will be scheduled per directory
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080067}
68
69FsWatcher::~FsWatcher()
70{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -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
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080076FsWatcher::DidDirectoryChanged(QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080077{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080078 _LOG_DEBUG("Triggered DirPath: " << dirPath.toStdString());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -080079
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080080 filesystem::path absPathTriggeredDir(dirPath.toStdString());
81 if (!filesystem::exists(filesystem::path(absPathTriggeredDir))) {
82 Scheduler::scheduleOneTimeTask(m_scheduler, 0.5,
83 bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this,
84 dirPath),
85 "r-" + dirPath.toStdString()); // only one task will be scheduled per directory
86 }
87 else {
88 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
89 Scheduler::scheduleOneTimeTask(m_scheduler, 0.5,
90 bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this,
91 dirPath),
92 dirPath.toStdString()); // only one task will be scheduled per directory
Alexander Afanasyevc80207d2013-01-29 20:07:39 -080093
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -080094 // m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
95 Scheduler::scheduleOneTimeTask(m_scheduler, 300,
96 bind(&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this,
97 dirPath),
98 "rescan-" +
99 dirPath.toStdString()); // only one task will be scheduled per directory
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800100
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800101 Scheduler::scheduleOneTimeTask(m_scheduler, 300,
102 bind(&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this,
103 m_dirPath),
104 "rescan-r-" +
105 m_dirPath.toStdString()); // only one task will be scheduled per directory
106 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800107}
108
109void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800110FsWatcher::DidFileChanged(QString filePath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800111{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800112 if (!filePath.startsWith(m_dirPath)) {
113 _LOG_ERROR(
114 "Got notification about a file not from the monitored directory: " << filePath.toStdString());
115 return;
116 }
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800117 QString absFilePath = filePath;
118
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800119 filesystem::path absPathTriggeredFile(filePath.toStdString());
120 filePath.remove(0, m_dirPath.size());
Alexander Afanasyev69e13172013-01-25 17:16:27 -0800121
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800122 filesystem::path triggeredFile(filePath.toStdString());
123 if (filesystem::exists(filesystem::path(absPathTriggeredFile))) {
124 _LOG_DEBUG("Triggered UPDATE of file: " << triggeredFile.relative_path().generic_string());
125 // m_onChange (triggeredFile.relative_path ());
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800126
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800127 m_watcher->removePath(absFilePath);
128 m_watcher->addPath(absFilePath);
Alexander Afanasyeve70a2d82013-02-19 22:49:10 -0800129
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800130 Scheduler::scheduleOneTimeTask(m_scheduler, 0.5, bind(m_onChange, triggeredFile.relative_path()),
131 triggeredFile.relative_path().string());
132 }
133 else {
134 _LOG_DEBUG("Triggered DELETE of file: " << triggeredFile.relative_path().generic_string());
135 // m_onDelete (triggeredFile.relative_path ());
Yingdi Yu57f667b2013-07-11 10:37:59 -0700136
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800137 m_watcher->removePath(absFilePath);
Alexander Afanasyev583449a2013-01-28 17:04:06 -0800138
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800139 deleteFile(triggeredFile.relative_path());
140 Scheduler::scheduleOneTimeTask(m_scheduler, 0.5, bind(m_onDelete, triggeredFile.relative_path()),
141 "r-" + triggeredFile.relative_path().string());
142 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800143}
144
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800145void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800146FsWatcher::ScanDirectory_NotifyUpdates_Execute(QString dirPath)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800147{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800148 _LOG_TRACE(" >> ScanDirectory_NotifyUpdates_Execute");
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
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800151 QRegExp exclude("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800152
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800153 QDirIterator dirIterator(dirPath,
154 QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks |
155 QDir::NoDotAndDotDot,
156 QDirIterator::Subdirectories); // directory iterator (recursive)
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800157
158 // iterate through directory recursively
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800159 while (dirIterator.hasNext()) {
160 dirIterator.next();
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800161
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800162 // Get FileInfo
163 QFileInfo fileInfo = dirIterator.fileInfo();
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800164
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800165 QString name = fileInfo.fileName();
166 _LOG_DEBUG("+++ Scanning: " << name.toStdString());
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800167
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800168 if (!exclude.exactMatch(name)) {
169 _LOG_DEBUG("Not excluded file/dir: " << fileInfo.absoluteFilePath().toStdString());
170 QString absFilePath = fileInfo.absoluteFilePath();
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800171
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800172 // _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
173 m_watcher->removePath(absFilePath);
174 m_watcher->addPath(absFilePath);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800175
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800176 if (fileInfo.isFile()) {
177 QString relFile = absFilePath;
178 relFile.remove(0, m_dirPath.size());
179 filesystem::path aFile(relFile.toStdString());
Alexander Afanasyevf695aed2013-01-30 13:28:42 -0800180
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800181 if (
182 //!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
183 !fileExists(aFile.relative_path()) /*file does not exist in db, but exists in fs: add */) {
184 addFile(aFile.relative_path());
185 DidFileChanged(absFilePath);
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800186 }
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800187 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800188 }
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800189 else {
190 // _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
191 }
192 }
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800193}
194
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800195
196void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800197FsWatcher::ScanDirectory_NotifyRemovals_Execute(QString dirPath)
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800198{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800199 _LOG_DEBUG("Triggered DirPath: " << dirPath.toStdString());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800200
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800201 filesystem::path absPathTriggeredDir(dirPath.toStdString());
202 dirPath.remove(0, m_dirPath.size());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800203
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800204 filesystem::path triggeredDir(dirPath.toStdString());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800205
Zhenkai Zhud1756272013-02-01 17:02:18 -0800206 /*
Alexander Afanasyev506ff222013-01-29 18:12:55 -0800207 FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800208 for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
209 {
210 filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
211 _LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
212
213 if (!filesystem::exists (testFile))
214 {
Alexander Afanasyev7a647002013-01-30 11:54:52 -0800215 if (removeIncomplete || file->is_complete ())
216 {
217 _LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
218 m_onDelete (file->filename ());
219 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800220 }
221 }
Zhenkai Zhud1756272013-02-01 17:02:18 -0800222 */
223
224 vector<string> files;
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800225 getFilesInDir(triggeredDir.relative_path(), files);
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800226 for (vector<string>::iterator file = files.begin(); file != files.end(); file++) {
227 filesystem::path targetFile = filesystem::path(m_dirPath.toStdString()) / *file;
228 if (!filesystem::exists(targetFile)) {
Alexander Afanasyev89024ac2013-02-14 09:38:10 -0800229 deleteFile(*file);
230 m_onDelete(*file);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800231 }
232 }
Alexander Afanasyev0a30a0c2013-01-29 17:25:42 -0800233}
234
Zhenkai Zhud1756272013-02-01 17:02:18 -0800235const string INIT_DATABASE = "\
236CREATE TABLE IF NOT EXISTS \n\
237 Files( \n\
238 filename TEXT NOT NULL, \n\
239 PRIMARY KEY (filename) \n\
240); \n\
241CREATE INDEX filename_index ON Files (filename); \n\
242";
243
244void
245FsWatcher::initFileStateDb()
246{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800247 filesystem::path dbFolder =
248 filesystem::path(m_dirPath.toStdString()) / ".chronoshare" / "fs_watcher";
Zhenkai Zhud1756272013-02-01 17:02:18 -0800249 filesystem::create_directories(dbFolder);
250
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800251 int res = sqlite3_open((dbFolder / "filestate.db").string().c_str(), &m_db);
252 if (res != SQLITE_OK) {
253 BOOST_THROW_EXCEPTION(Error::Db() << errmsg_info_str("Cannot open database: " +
254 (dbFolder / "filestate.db").string()));
Zhenkai Zhud1756272013-02-01 17:02:18 -0800255 }
256
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800257 char* errmsg = 0;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800258 res = sqlite3_exec(m_db, INIT_DATABASE.c_str(), NULL, NULL, &errmsg);
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800259 if (res != SQLITE_OK && errmsg != 0) {
260 // _LOG_TRACE ("Init \"error\": " << errmsg);
261 cout << "FS-Watcher DB error: " << errmsg << endl;
262 sqlite3_free(errmsg);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800263 }
264}
265
266bool
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800267FsWatcher::fileExists(const filesystem::path& filename)
Zhenkai Zhud1756272013-02-01 17:02:18 -0800268{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800269 sqlite3_stmt* stmt;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800270 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename = ?;", -1, &stmt, 0);
271 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
272 bool retval = false;
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800273 if (sqlite3_step(stmt) == SQLITE_ROW) {
Zhenkai Zhud1756272013-02-01 17:02:18 -0800274 retval = true;
275 }
276 sqlite3_finalize(stmt);
277
278 return retval;
279}
280
281void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800282FsWatcher::addFile(const filesystem::path& filename)
Zhenkai Zhud1756272013-02-01 17:02:18 -0800283{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800284 sqlite3_stmt* stmt;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800285 sqlite3_prepare_v2(m_db, "INSERT OR IGNORE INTO Files (filename) VALUES (?);", -1, &stmt, 0);
286 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
287 sqlite3_step(stmt);
288 sqlite3_finalize(stmt);
289}
290
291void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800292FsWatcher::deleteFile(const filesystem::path& filename)
Zhenkai Zhud1756272013-02-01 17:02:18 -0800293{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800294 sqlite3_stmt* stmt;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800295 sqlite3_prepare_v2(m_db, "DELETE FROM Files WHERE filename = ?;", -1, &stmt, 0);
296 sqlite3_bind_text(stmt, 1, filename.c_str(), -1, SQLITE_STATIC);
297 sqlite3_step(stmt);
298 sqlite3_finalize(stmt);
299}
300
301void
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800302FsWatcher::getFilesInDir(const filesystem::path& dir, vector<string>& files)
Zhenkai Zhud1756272013-02-01 17:02:18 -0800303{
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800304 sqlite3_stmt* stmt;
Zhenkai Zhud1756272013-02-01 17:02:18 -0800305 sqlite3_prepare_v2(m_db, "SELECT * FROM Files WHERE filename LIKE ?;", -1, &stmt, 0);
306
307 string dirStr = dir.string();
308 ostringstream escapedDir;
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800309 for (string::const_iterator ch = dirStr.begin(); ch != dirStr.end(); ch++) {
310 if (*ch == '%')
311 escapedDir << "\\%";
312 else
313 escapedDir << *ch;
314 }
315 escapedDir << "/"
316 << "%";
317 string escapedDirStr = escapedDir.str();
318 sqlite3_bind_text(stmt, 1, escapedDirStr.c_str(), escapedDirStr.size(), SQLITE_STATIC);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800319
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800320 while (sqlite3_step(stmt) == SQLITE_ROW) {
321 string filename(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)),
322 sqlite3_column_bytes(stmt, 0));
Zhenkai Zhud1756272013-02-01 17:02:18 -0800323 files.push_back(filename);
324 }
325
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800326 sqlite3_finalize(stmt);
Zhenkai Zhud1756272013-02-01 17:02:18 -0800327}
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800328
329#if WAF
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800330#include "fs-watcher.cc.moc"
Alexander Afanasyeveda3b7a2016-12-25 11:26:40 -0800331#include "fs-watcher.moc"
Alexander Afanasyev9e5a4702013-01-24 13:15:23 -0800332#endif