FolderWatcher: Detect own changes. #2297

This commit is contained in:
Christian Kamm 2014-11-07 11:41:21 +01:00
parent 6d09f1b6c0
commit 0fe7a69b39
12 changed files with 122 additions and 35 deletions

View file

@ -540,6 +540,37 @@ int Folder::slotWipeBlacklist()
return _journal.wipeBlacklist(); return _journal.wipeBlacklist();
} }
void Folder::slotWatchedPathChanged(const QString& path)
{
// When no sync is running or it's in the prepare phase, we can
// always schedule a new sync.
if (! _engine || _syncResult.status() == SyncResult::SyncPrepare) {
emit scheduleToSync(alias());
return;
}
// The folder watcher fires a lot of bogus notifications during
// a sync operation, both for actual user files and the database
// and log. Therefore we check notifications against operations
// the sync is doing to filter out our own changes.
bool ownChange = false;
#ifdef Q_OS_MAC
// On OSX the folder watcher does not report changes done by our
// own process. Therefore nothing needs to be done here!
#else
// Use the path to figure out whether it was our own change
const auto maxNotificationDelay = 15*1000;
qint64 time = _engine->timeSinceFileTouched(path);
if (time != -1 && time < maxNotificationDelay) {
ownChange = true;
}
#endif
if (! ownChange) {
emit scheduleToSync(alias());
}
}
void Folder::setConfigFile( const QString& file ) void Folder::setConfigFile( const QString& file )
{ {
_configFile = file; _configFile = file;

View file

@ -154,6 +154,13 @@ public slots:
int slotWipeBlacklist(); int slotWipeBlacklist();
int blackListEntryCount(); int blackListEntryCount();
/**
* Triggered by the folder watcher when a file/dir in this folder
* changes. Needs to check whether this change should trigger a new
* sync run to be scheduled.
*/
void slotWatchedPathChanged(const QString& path);
private slots: private slots:
void slotSyncStarted(); void slotSyncStarted();
void slotSyncError(const QString& ); void slotSyncError(const QString& );

View file

@ -55,10 +55,6 @@ FolderMan::FolderMan(QObject *parent) :
connect(_folderChangeSignalMapper, SIGNAL(mapped(const QString &)), connect(_folderChangeSignalMapper, SIGNAL(mapped(const QString &)),
this, SIGNAL(folderSyncStateChange(const QString &))); this, SIGNAL(folderSyncStateChange(const QString &)));
_folderWatcherSignalMapper = new QSignalMapper(this);
connect(_folderWatcherSignalMapper, SIGNAL(mapped(const QString&)),
this, SLOT(slotScheduleSync(const QString&)));
ne_sock_init(); ne_sock_init();
Q_ASSERT(!_instance); Q_ASSERT(!_instance);
_instance = this; _instance = this;
@ -100,8 +96,6 @@ void FolderMan::unloadFolder( const QString& alias )
_folderChangeSignalMapper->removeMappings(f); _folderChangeSignalMapper->removeMappings(f);
if( _folderWatchers.contains(alias)) { if( _folderWatchers.contains(alias)) {
FolderWatcher *fw = _folderWatchers[alias];
_folderWatcherSignalMapper->removeMappings(fw);
_folderWatchers.remove(alias); _folderWatchers.remove(alias);
} }
_folderMap.remove( alias ); _folderMap.remove( alias );
@ -135,7 +129,7 @@ void FolderMan::registerFolderMonitor( Folder *folder )
if( !folder ) return; if( !folder ) return;
if( !_folderWatchers.contains(folder->alias() ) ) { if( !_folderWatchers.contains(folder->alias() ) ) {
FolderWatcher *fw = new FolderWatcher(folder->path(), this); FolderWatcher *fw = new FolderWatcher(folder->path(), folder);
MirallConfigFile cfg; MirallConfigFile cfg;
fw->addIgnoreListFile( cfg.excludeFile(MirallConfigFile::SystemScope) ); fw->addIgnoreListFile( cfg.excludeFile(MirallConfigFile::SystemScope) );
fw->addIgnoreListFile( cfg.excludeFile(MirallConfigFile::UserScope) ); fw->addIgnoreListFile( cfg.excludeFile(MirallConfigFile::UserScope) );
@ -143,8 +137,7 @@ void FolderMan::registerFolderMonitor( Folder *folder )
// Connect the pathChanged signal, which comes with the changed path, // Connect the pathChanged signal, which comes with the changed path,
// to the signal mapper which maps to the folder alias. The changed path // to the signal mapper which maps to the folder alias. The changed path
// is lost this way, but we do not need it for the current implementation. // is lost this way, but we do not need it for the current implementation.
connect(fw, SIGNAL(pathChanged(QString)), _folderWatcherSignalMapper, SLOT(map())); connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(slotWatchedPathChanged(QString)));
_folderWatcherSignalMapper->setMapping(fw, folder->alias());
_folderWatchers.insert(folder->alias(), fw); _folderWatchers.insert(folder->alias(), fw);
// This is at the moment only for the behaviour of the SocketApi. // This is at the moment only for the behaviour of the SocketApi.
@ -444,20 +437,6 @@ void FolderMan::slotScheduleSync( const QString& alias )
return; return;
} }
#ifndef Q_OS_MAC
// The folder watcher fires a lot of bogus notifications during
// a sync operation, both for actual user files and the database
// and log. Never enqueue a folder for sync while it is syncing.
// We lose some genuine sync requests that way, but that can't be
// helped.
// This works fine on OSX, where the folder watcher does not
// report changes done by our own process.
if( _currentSyncFolder == alias ) {
qDebug() << "folder " << alias << " is currently syncing. NOT scheduling.";
return;
}
#endif
if( _socketApi ) { if( _socketApi ) {
// We want the SocketAPI to already now update so that it can show the EVAL icon // We want the SocketAPI to already now update so that it can show the EVAL icon
// for files/folders. Only do this when not syncing, else we might get a lot // for files/folders. Only do this when not syncing, else we might get a lot

View file

@ -150,7 +150,6 @@ private:
Folder::Map _folderMap; Folder::Map _folderMap;
QString _folderConfigPath; QString _folderConfigPath;
QSignalMapper *_folderChangeSignalMapper; QSignalMapper *_folderChangeSignalMapper;
QSignalMapper *_folderWatcherSignalMapper;
QString _currentSyncFolder; QString _currentSyncFolder;
bool _syncEnabled; bool _syncEnabled;
QMap<QString, FolderWatcher*> _folderWatchers; QMap<QString, FolderWatcher*> _folderWatchers;

View file

@ -457,6 +457,26 @@ void OwncloudPropagator::scheduleNextJob()
} }
} }
void OwncloudPropagator::addTouchedFile(const QString& fn)
{
QString file = QDir::cleanPath(fn);
QElapsedTimer timer;
timer.start();
QMutexLocker lock(&_touchedFilesMutex);
_touchedFiles.insert(file, timer);
}
qint64 OwncloudPropagator::timeSinceFileTouched(const QString& fn) const
{
QMutexLocker lock(&_touchedFilesMutex);
if (! _touchedFiles.contains(fn)) {
return -1;
}
return _touchedFiles[fn].elapsed();
}
// ================================================================================ // ================================================================================

View file

@ -24,6 +24,7 @@
#include <QTimer> #include <QTimer>
#include <QPointer> #include <QPointer>
#include <QIODevice> #include <QIODevice>
#include <QMutex>
#include "syncfileitem.h" #include "syncfileitem.h"
#include "syncjournaldb.h" #include "syncjournaldb.h"
@ -291,6 +292,19 @@ public:
// timeout in seconds // timeout in seconds
static int httpTimeout(); static int httpTimeout();
/** Records that a file was touched by a job.
*
* Thread-safe.
*/
void addTouchedFile(const QString& fn);
/** Get the ms since a file was touched, or -1 if it wasn't.
*
* Thread-safe.
*/
qint64 timeSinceFileTouched(const QString& fn) const;
private slots: private slots:
/** Emit the finished signal and make sure it is only emit once */ /** Emit the finished signal and make sure it is only emit once */
@ -312,6 +326,11 @@ signals:
*/ */
void adjustTotalTransmissionSize( qint64 adjust ); void adjustTotalTransmissionSize( qint64 adjust );
private:
/** Stores the time since a job touched a file. */
QHash<QString, QElapsedTimer> _touchedFiles;
mutable QMutex _touchedFilesMutex;
}; };
// Job that wait for all the poll jobs to be completed // Job that wait for all the poll jobs to be completed

View file

@ -476,6 +476,7 @@ void PropagateDownloadFileQNAM::downloadFinished()
FileSystem::setFileHidden(_tmpFile.fileName(), false); FileSystem::setFileHidden(_tmpFile.fileName(), false);
QString error; QString error;
_propagator->addTouchedFile(fn);
if (!FileSystem::renameReplace(_tmpFile.fileName(), fn, &error)) { if (!FileSystem::renameReplace(_tmpFile.fileName(), fn, &error)) {
// If we moved away the original file due to a conflict but can't // If we moved away the original file due to a conflict but can't
// put the downloaded file in its place, we are in a bad spot: // put the downloaded file in its place, we are in a bad spot:

View file

@ -58,13 +58,18 @@ void PropagateRemoteMove::start()
qDebug() << Q_FUNC_INFO << _item._file << _item._renameTarget; qDebug() << Q_FUNC_INFO << _item._file << _item._renameTarget;
QString targetFile(_propagator->getFilePath(_item._renameTarget));
if (_item._file == _item._renameTarget) { if (_item._file == _item._renameTarget) {
// The parents has been renamed already so there is nothing more to do. // The parents has been renamed already so there is nothing more to do.
finalize(); finalize();
return; return;
} else if (AbstractNetworkJob::preOc7WasDetected && _item._file == QLatin1String("Shared") ) { } else if (AbstractNetworkJob::preOc7WasDetected && _item._file == QLatin1String("Shared") ) {
// Check if it is the toplevel Shared folder and do not propagate it. // Check if it is the toplevel Shared folder and do not propagate it.
if( QFile::rename( _propagator->_localDir + _item._renameTarget, _propagator->_localDir + QLatin1String("Shared")) ) { QString originalFile(_propagator->getFilePath(QLatin1String("Shared")));
_propagator->addTouchedFile(originalFile);
_propagator->addTouchedFile(targetFile);
if( QFile::rename( targetFile, originalFile) ) {
done(SyncFileItem::NormalError, tr("This folder must not be renamed. It is renamed back to its original name.")); done(SyncFileItem::NormalError, tr("This folder must not be renamed. It is renamed back to its original name."));
} else { } else {
done(SyncFileItem::NormalError, tr("This folder must not be renamed. Please name it back to Shared.")); done(SyncFileItem::NormalError, tr("This folder must not be renamed. Please name it back to Shared."));
@ -131,7 +136,7 @@ void PropagateRemoteMove::slotMoveJobFinished()
void PropagateRemoteMove::finalize() void PropagateRemoteMove::finalize()
{ {
_propagator->_journal->deleteFileRecord(_item._originalFile); _propagator->_journal->deleteFileRecord(_item._originalFile);
SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget); SyncJournalFileRecord record(_item, _propagator->getFilePath(_item._renameTarget));
record._path = _item._renameTarget; record._path = _item._renameTarget;
_propagator->_journal->setFileRecord(record); _propagator->_journal->setFileRecord(record);

View file

@ -712,6 +712,7 @@ void PropagateDownloadFileLegacy::start()
FileSystem::setFileHidden(tmpFile.fileName(), false); FileSystem::setFileHidden(tmpFile.fileName(), false);
QString error; QString error;
_propagator->addTouchedFile(fn);
if (!FileSystem::renameReplace(tmpFile.fileName(), fn, &error)) { if (!FileSystem::renameReplace(tmpFile.fileName(), fn, &error)) {
done(SyncFileItem::NormalError, error); done(SyncFileItem::NormalError, error);
return; return;

View file

@ -131,12 +131,14 @@ void PropagateLocalRename::start()
if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return; return;
QString existingFile = _propagator->getFilePath(_item._file);
QString targetFile = _propagator->getFilePath(_item._renameTarget);
// if the file is a file underneath a moved dir, the _item.file is equal // if the file is a file underneath a moved dir, the _item.file is equal
// to _item.renameTarget and the file is not moved as a result. // to _item.renameTarget and the file is not moved as a result.
if (_item._file != _item._renameTarget) { if (_item._file != _item._renameTarget) {
emit progress(_item, 0); emit progress(_item, 0);
qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget; qDebug() << "MOVE " << existingFile << " => " << targetFile;
QFile file(_propagator->_localDir + _item._file);
if (QString::compare(_item._file, _item._renameTarget, Qt::CaseInsensitive) != 0 if (QString::compare(_item._file, _item._renameTarget, Qt::CaseInsensitive) != 0
&& _propagator->localFileNameClash(_item._renameTarget)) { && _propagator->localFileNameClash(_item._renameTarget)) {
@ -149,7 +151,11 @@ void PropagateLocalRename::start()
.arg(QDir::toNativeSeparators(_item._file)).arg(QDir::toNativeSeparators(_item._renameTarget)) ); .arg(QDir::toNativeSeparators(_item._file)).arg(QDir::toNativeSeparators(_item._renameTarget)) );
return; return;
} }
if (!file.rename(_propagator->_localDir + _item._file, _propagator->_localDir + _item._renameTarget)) {
_propagator->addTouchedFile(existingFile);
_propagator->addTouchedFile(targetFile);
QFile file(existingFile);
if (!file.rename(targetFile)) {
done(SyncFileItem::NormalError, file.errorString()); done(SyncFileItem::NormalError, file.errorString());
return; return;
} }
@ -160,7 +166,7 @@ void PropagateLocalRename::start()
// store the rename file name in the item. // store the rename file name in the item.
_item._file = _item._renameTarget; _item._file = _item._renameTarget;
SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget); SyncJournalFileRecord record(_item, targetFile);
record._path = _item._renameTarget; record._path = _item._renameTarget;
if (!_item._isDirectory) { // Directory are saved at the end if (!_item._isDirectory) { // Directory are saved at the end

View file

@ -716,9 +716,6 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
} }
} }
if (_needsUpdate)
emit(started());
ne_session_s *session = 0; ne_session_s *session = 0;
// that call to set property actually is a get which will return the session // that call to set property actually is a get which will return the session
csync_set_module_property(_csync_ctx, "get_dav_session", &session); csync_set_module_property(_csync_ctx, "get_dav_session", &session);
@ -756,6 +753,10 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
deleteStaleBlacklistEntries(); deleteStaleBlacklistEntries();
_journal->commit("post stale entry removal"); _journal->commit("post stale entry removal");
// Emit the started signal only after the propagator has been set up.
if (_needsUpdate)
emit(started());
_propagator->start(_syncedItems); _propagator->start(_syncedItems);
} }
@ -842,9 +843,11 @@ void SyncEngine::finalize()
qDebug() << "CSync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished")); qDebug() << "CSync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished"));
_stopWatch.stop(); _stopWatch.stop();
_propagator.reset(0);
_syncRunning = false; _syncRunning = false;
emit finished(); emit finished();
// Delete the propagator only after emitting the signal.
_propagator.reset();
} }
void SyncEngine::slotProgress(const SyncFileItem& item, quint64 current) void SyncEngine::slotProgress(const SyncFileItem& item, quint64 current)
@ -1130,6 +1133,16 @@ bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s
return false; return false;
} }
qint64 SyncEngine::timeSinceFileTouched(const QString& fn) const
{
// This copy is essential for thread safety.
QSharedPointer<OwncloudPropagator> prop = _propagator;
if (prop) {
return prop->timeSinceFileTouched(fn);
}
return -1;
}
void SyncEngine::abort() void SyncEngine::abort()
{ {
csync_request_abort(_csync_ctx); csync_request_abort(_csync_ctx);

View file

@ -69,6 +69,12 @@ public:
bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s);
/** Get the ms since a file was touched, or -1 if it wasn't.
*
* Thread-safe.
*/
qint64 timeSinceFileTouched(const QString& fn) const;
signals: signals:
void csyncError( const QString& ); void csyncError( const QString& );
void csyncUnavailable(); void csyncUnavailable();
@ -140,7 +146,7 @@ private:
QString _remoteUrl; QString _remoteUrl;
QString _remotePath; QString _remotePath;
SyncJournalDb *_journal; SyncJournalDb *_journal;
QScopedPointer <OwncloudPropagator> _propagator; QSharedPointer <OwncloudPropagator> _propagator;
QString _lastDeleted; // if the last item was a path and it has been deleted QString _lastDeleted; // if the last item was a path and it has been deleted
QSet<QString> _seenFiles; QSet<QString> _seenFiles;
QThread _thread; QThread _thread;