blob: 18d004d4b6602443f947cec774073283b884a98a [file] [log] [blame]
/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
/*
* Copyright (c) 2012-2013 University of California, Los Angeles
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Jared Lindblom <lindblom@cs.ucla.edu>
* Alexander Afanasyev <alexander.afanasyev@ucla.edu>
* Zhenkai Zhu <zhenkai@cs.ucla.edu>
*/
#include "fs-watcher.h"
#include "logging.h"
#include <boost/bind.hpp>
#include <QDirIterator>
#include <QRegExp>
using namespace std;
using namespace boost;
INIT_LOGGER ("FsWatcher");
FsWatcher::FsWatcher (QString dirPath,
LocalFile_Change_Callback onChange, LocalFile_Change_Callback onDelete,
FileState *fileState,
QObject* parent)
: QObject(parent)
, m_watcher (new QFileSystemWatcher())
, m_scheduler (new Scheduler ())
, m_dirPath (dirPath)
, m_onChange (onChange)
, m_onDelete (onDelete)
, m_fileState (fileState)
{
_LOG_DEBUG ("Monitor dir: " << m_dirPath.toStdString ());
// add main directory to monitor
m_watcher->addPath (m_dirPath);
// register signals (callback functions)
connect (m_watcher, SIGNAL (directoryChanged (QString)), this, SLOT (DidDirectoryChanged (QString)));
connect (m_watcher, SIGNAL (fileChanged (QString)), this, SLOT (DidFileChanged (QString)));
m_scheduler->start ();
Scheduler::scheduleOneTimeTask (m_scheduler, 0,
bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
"rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
Scheduler::scheduleOneTimeTask (m_scheduler, 0,
bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, m_dirPath, true),
"rescan-" +m_dirPath.toStdString ()); // only one task will be scheduled per directory
}
FsWatcher::~FsWatcher()
{
m_scheduler->shutdown ();
}
void
FsWatcher::DidDirectoryChanged (QString dirPath)
{
_LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
filesystem::path absPathTriggeredDir (dirPath.toStdString ());
if (!filesystem::exists (filesystem::path (absPathTriggeredDir)))
{
Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, dirPath, true/* ignore incomplete file flag. the whole directory got removed*/),
"r-" + dirPath.toStdString ()); // only one task will be scheduled per directory
}
else
{
// m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, false),
dirPath.toStdString ()); // only one task will be scheduled per directory
// m_executor.execute (bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath));
Scheduler::scheduleOneTimeTask (m_scheduler, 300,
bind (&FsWatcher::ScanDirectory_NotifyUpdates_Execute, this, dirPath, true),
"rescan-"+dirPath.toStdString ()); // only one task will be scheduled per directory
Scheduler::scheduleOneTimeTask (m_scheduler, 300,
bind (&FsWatcher::ScanDirectory_NotifyRemovals_Execute, this, m_dirPath, false/* don't remove incomplete files*/),
"rescan-r-" + m_dirPath.toStdString ()); // only one task will be scheduled per directory
}
}
void
FsWatcher::DidFileChanged (QString filePath)
{
if (!filePath.startsWith (m_dirPath))
{
_LOG_ERROR ("Got notification about a file not from the monitored directory");
return;
}
filesystem::path absPathTriggeredFile (filePath.toStdString ());
filePath.remove (0, m_dirPath.size ());
filesystem::path triggeredFile (filePath.toStdString ());
if (filesystem::exists (filesystem::path (absPathTriggeredFile)))
{
_LOG_DEBUG ("Triggered UPDATE of file: " << triggeredFile.relative_path ().generic_string ());
// m_onChange (triggeredFile.relative_path ());
Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
bind (m_onChange, triggeredFile.relative_path ()),
triggeredFile.relative_path ().string());
}
else
{
_LOG_DEBUG ("Triggered DELETE of file: " << triggeredFile.relative_path ().generic_string ());
// m_onDelete (triggeredFile.relative_path ());
Scheduler::scheduleOneTimeTask (m_scheduler, 0.5,
bind (m_onDelete, triggeredFile.relative_path ()),
"r-" + triggeredFile.relative_path ().string());
}
}
void
FsWatcher::ScanDirectory_NotifyUpdates_Execute (QString dirPath, bool notifyCallbacks)
{
_LOG_TRACE (" >> ScanDirectory_NotifyUpdates_Execute");
// exclude working only on last component, not the full path; iterating through all directories, even excluded from monitoring
QRegExp exclude ("^(\\.|\\.\\.|\\.chronoshare|.*~|.*\\.swp)$");
QDirIterator dirIterator (dirPath,
QDir::Dirs | QDir::Files | /*QDir::Hidden |*/ QDir::NoSymLinks | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories); // directory iterator (recursive)
// iterate through directory recursively
while (dirIterator.hasNext ())
{
dirIterator.next ();
// Get FileInfo
QFileInfo fileInfo = dirIterator.fileInfo ();
QString name = fileInfo.fileName ();
_LOG_DEBUG ("+++ Scanning: " << name.toStdString ());
if (!exclude.exactMatch (name))
{
_LOG_DEBUG ("Not excluded file/dir: " << fileInfo.absoluteFilePath ().toStdString ());
QString absFilePath = fileInfo.absoluteFilePath ();
// _LOG_DEBUG ("Attempt to add path to watcher: " << absFilePath.toStdString ());
m_watcher->addPath (absFilePath);
if (fileInfo.isFile ())
{
QString relFile = absFilePath;
relFile.remove (0, m_dirPath.size ());
filesystem::path aFile (relFile.toStdString ());
if (notifyCallbacks ||
!m_fileState->LookupFile (aFile.relative_path ().generic_string ()) /* file does not exist there, but exists locally: added */)
{
DidFileChanged (absFilePath);
}
}
}
else
{
// _LOG_DEBUG ("Excluded file/dir: " << fileInfo.filePath ().toStdString ());
}
}
}
void
FsWatcher::ScanDirectory_NotifyRemovals_Execute (QString dirPath, bool removeIncomplete)
{
_LOG_DEBUG ("Triggered DirPath: " << dirPath.toStdString ());
filesystem::path absPathTriggeredDir (dirPath.toStdString ());
dirPath.remove (0, m_dirPath.size ());
filesystem::path triggeredDir (dirPath.toStdString ());
FileItemsPtr files = m_fileState->LookupFilesInFolderRecursively (triggeredDir.relative_path ().generic_string ());
for (std::list<FileItem>::iterator file = files->begin (); file != files->end (); file ++)
{
filesystem::path testFile = filesystem::path (m_dirPath.toStdString ()) / file->filename ();
_LOG_DEBUG ("Check file for deletion [" << testFile.generic_string () << "]");
if (!filesystem::exists (testFile))
{
if (removeIncomplete || file->is_complete ())
{
_LOG_DEBUG ("Notifying about removed file [" << file->filename () << "]");
m_onDelete (file->filename ());
}
}
}
}
#if WAF
#include "fs-watcher.moc"
#include "fs-watcher.cc.moc"
#endif