- lot of stability improvements

- the crash with threads goes away by using QMap
- add a polling timer
This commit is contained in:
Duncan Mac-Vicar P 2011-04-04 18:41:14 +02:00
parent 389507d92f
commit 4e45cab2c1
8 changed files with 190 additions and 32 deletions

View file

@ -44,10 +44,9 @@ signals:
void syncFinished();
protected:
FolderWatcher *_watcher;
private:
QString _path;
FolderWatcher *_watcher;
QAction *_openAction;
protected slots:

View file

@ -20,25 +20,38 @@ static const uint32_t standard_event_mask =
/* minimum amount of seconds between two
events to consider it a new event */
#define DEFAULT_EVENT_INTERVAL_SEC 5
#define DEFAULT_POLL_INTERVAL_SEC 30
namespace Mirall {
FolderWatcher::FolderWatcher(const QString &root, QObject *parent)
: QObject(parent),
_eventsEnabled(true),
_eventInterval(DEFAULT_EVENT_INTERVAL_SEC),
_pollInterval(DEFAULT_POLL_INTERVAL_SEC),
_root(root),
_processTimer(new QTimer(this)),
_pollTimer(new QTimer(this)),
_lastMask(0)
{
// this is not the best place for this
addIgnore("/**/.unison*");
_processTimer->setSingleShot(true);
QObject::connect(_processTimer, SIGNAL(timeout()), this, SLOT(slotProcessTimerTimeout()));
_pollTimer->setSingleShot(false);
_pollTimer->setInterval(pollInterval() * 1000);
QObject::connect(_pollTimer, SIGNAL(timeout()), this, SLOT(slotPollTimerTimeout()));
_pollTimer->start();
_inotify = new INotify(standard_event_mask);
slotAddFolderRecursive(root);
QObject::connect(_inotify, SIGNAL(notifyEvent(int, int, const QString &)),
SLOT(slotINotifyEvent(int, int, const QString &)));
// do a first synchronization to get changes while
// the application was not running
setProcessTimer();
}
FolderWatcher::~FolderWatcher()
@ -51,6 +64,11 @@ QString FolderWatcher::root() const
return _root;
}
void FolderWatcher::addIgnore(const QString &pattern)
{
_ignores.append(pattern);
}
bool FolderWatcher::eventsEnabled() const
{
return _eventsEnabled;
@ -84,6 +102,16 @@ void FolderWatcher::setEventInterval(int seconds)
_eventInterval = seconds;
}
int FolderWatcher::pollInterval() const
{
return _pollInterval;
}
void FolderWatcher::setPollInterval(int seconds)
{
_pollInterval = seconds;
}
QStringList FolderWatcher::folders() const
{
return _inotify->directories();
@ -102,6 +130,16 @@ void FolderWatcher::slotAddFolderRecursive(const QString &path)
if (folder.exists() && !watchedFolders.contains(folder.path())) {
subdirs++;
//qDebug() << "(+) Watcher:" << folder.path();
// check that it does not match the ignore list
foreach (QString pattern, _ignores) {
QRegExp regexp(pattern);
regexp.setPatternSyntax(QRegExp::Wildcard);
if (regexp.exactMatch(folder.path())) {
qDebug() << "* Not adding" << folder.path();
continue;
}
}
_inotify->addPath(folder.path());
}
else
@ -137,14 +175,17 @@ void FolderWatcher::slotINotifyEvent(int mask, int cookie, const QString &path)
if (mask & IN_CREATE) {
//qDebug() << cookie << " CREATE: " << path;
if (QFileInfo(path).isDir()) {
//setEventsEnabled(false);
slotAddFolderRecursive(path);
//setEventsEnabled(true);
}
}
else if (mask & IN_DELETE) {
//qDebug() << cookie << " DELETE: " << path;
if (_inotify->directories().contains(path));
if (_inotify->directories().contains(path) &&
QFileInfo(path).isDir());
qDebug() << "(-) Watcher:" << path;
_inotify->removePath(path);
_inotify->removePath(path);
}
else if (mask & IN_CLOSE_WRITE) {
//qDebug() << cookie << " WRITABLE CLOSED: " << path;
@ -156,6 +197,15 @@ void FolderWatcher::slotINotifyEvent(int mask, int cookie, const QString &path)
//qDebug() << cookie << " OTHER " << mask << " :" << path;
}
foreach (QString pattern, _ignores) {
QRegExp regexp(pattern);
regexp.setPatternSyntax(QRegExp::Wildcard);
if (regexp.exactMatch(path)) {
qDebug() << "* Discarded" << path;
return;
}
}
_pendingPaths.append(path);
slotProcessPaths();
}
@ -166,38 +216,45 @@ void FolderWatcher::slotProcessTimerTimeout()
slotProcessPaths();
}
void FolderWatcher::slotPollTimerTimeout()
{
qDebug() << "* Polling remote for changes";
emit folderChanged(QStringList());
}
void FolderWatcher::setProcessTimer()
{
if (!_processTimer->isActive()) {
qDebug() << "* Pending events will be processed in" << eventInterval() << "seconds. (" << _pendingPaths.size() << "events until now )";
_processTimer->start(eventInterval() * 1000);
qDebug() << "* Pending events will be processed in" << eventInterval() << "seconds (" << QTime::currentTime().addSecs(eventInterval()).toString("HH:mm:ss") << ")." << _pendingPaths.size() << "events until now )";
}
_processTimer->start(eventInterval() * 1000);
}
void FolderWatcher::slotProcessPaths()
{
QTime eventTime = QTime::currentTime();
QTime lastEventTime = _lastEventTime;
_lastEventTime = eventTime;
if (!eventsEnabled())
return;
if (!_lastEventTime.isNull() && (_lastEventTime.secsTo(eventTime) < eventInterval())) {
//qDebug() << "`-> Last event happened less than " << eventInterval() << " seconds ago...";
// schedule a forced queue cleanup later
// if the events are disabled or the last event happened
// recently eg: copying lot of ifles
if (!eventsEnabled() ||
( !lastEventTime.isNull() &&
(lastEventTime.secsTo(eventTime) < eventInterval()) ))
{
// in case this is the last file from a bulk copy
// set the process timer again so that we process the
// queue we are not processing now
setProcessTimer();
return;
}
// if the events will be processed because changed files and not
// because a forced update, stop any timer.
if (_processTimer->isActive())
_processTimer->stop();
_lastEventTime = eventTime;
QStringList notifyPaths(_pendingPaths);
_pendingPaths.clear();
//qDebug() << lastEventTime << eventTime;
qDebug() << " * Notify " << notifyPaths.size() << " changed items";
emit folderChanged(notifyPaths);
}

View file

@ -3,6 +3,7 @@
#ifndef MIRALL_FOLDERWATCHER_H
#define MIRALL_FOLDERWATCHER_H
#include <QList>
#include <QObject>
#include <QString>
#include <QStringList>
@ -42,6 +43,14 @@ public:
*/
QString root() const;
/**
* Add an ignore pattern that will not be
* notified
*
* You can use wildcards
*/
void addIgnore(const QString &pattern);
/**
* If true, folderChanged() events are sent
* at least as often as eventInterval() seconds.
@ -67,6 +76,19 @@ public:
*/
void setEventInterval(int seconds);
/**
* The minimum amounts of seconds to wait before
* doing a full sync to see if the remote changed
*/
int pollInterval() const;
/**
* Sets minimum amounts of seconds that will separate
* poll intervals
*/
void setPollInterval(int seconds);
signals:
/**
* Emitted when one of the paths is changed
@ -81,20 +103,29 @@ protected slots:
void slotAddFolderRecursive(const QString &path);
// called when the manually process timer triggers
void slotProcessTimerTimeout();
void slotPollTimerTimeout();
void slotProcessPaths();
private:
bool _eventsEnabled;
int _eventInterval;
int _pollInterval;
INotify *_inotify;
QString _root;
// paths pending to notified
QStringList _pendingPaths;
QTimer *_processTimer;
// poll timer for remote syncs
QTimer *_pollTimer;
QTime _lastEventTime;
// to cancel events that belong to the same action
int _lastMask;
QString _lastPath;
QStringList _ignores;
};
}

View file

@ -11,6 +11,7 @@ http://www.gnu.org/licenses/gpl.txt .
#include <cerrno>
#include <unistd.h>
#include <QDebug>
#include <QMutexLocker>
#include <QStringList>
#include "inotify.h"
@ -24,6 +25,7 @@ namespace Mirall {
// Allocate space for static members of class.
int INotify::s_fd;
INotify::INotifyThread* INotify::s_thread;
QMutex INotify::INotifyThread::s_mutex;
//INotify::INotify(int wd) : _wd(wd)
//{
@ -46,6 +48,7 @@ INotify::~INotify()
void INotify::addPath(const QString &path)
{
//QMutexLocker locker(&INotifyThread::s_mutex);
// Add an inotify watch.
path.toAscii().constData();
@ -58,6 +61,7 @@ void INotify::addPath(const QString &path)
void INotify::removePath(const QString &path)
{
QMutexLocker locker(&INotifyThread::s_mutex);
// Remove the inotify watch.
inotify_rm_watch(s_fd, _wds[path]);
_wds.remove(path);
@ -72,6 +76,7 @@ void
INotify::INotifyThread::unregisterForNotification(INotify* notifier)
{
//_map.remove(notifier->_wd);
//QMutexLocker locker(&INotifyThread::s_mutex);
QHash<int, INotify*>::iterator it;
for (it = _map.begin(); it != _map.end(); ++it) {
if (it.value() == notifier)
@ -82,14 +87,18 @@ INotify::INotifyThread::unregisterForNotification(INotify* notifier)
void
INotify::INotifyThread::registerForNotification(INotify* notifier, int wd)
{
//QMutexLocker locker(&INotifyThread::s_mutex);
_map[wd] = notifier;
}
void
INotify::fireEvent(int mask, int cookie, int wd, char* name)
{
QString path;
foreach (path, _wds.keys(wd))
//qDebug() << "****" << name;
//QMutexLocker locker(&INotifyThread::s_mutex);
QStringList paths(_wds.keys(wd));
foreach (QString path, paths)
emit notifyEvent(mask, cookie, path + "/" + QString::fromUtf8(name));
}
@ -170,12 +179,25 @@ INotify::INotifyThread::run()
continue;
}
n = _map[event->wd];
// dont allow addPath removePath clash here
{
//QThread::msleep(100);
// fire event
if (event->len > 0) {
//QMutexLocker locker(&s_mutex);
if (n)
n->fireEvent(event->mask, event->cookie, event->wd, event->name);
else
{
qWarning() << "n is NULL";
}
}
// increment counter
i += sizeof(struct inotify_event) + event->len;
}
// fire event
if (event->len > 0)
n->fireEvent(event->mask, event->cookie, event->wd, event->name);
// increment counter
i += sizeof(struct inotify_event) + event->len;
}
}
}

View file

@ -11,7 +11,8 @@ http://www.gnu.org/licenses/gpl.txt .
#define MIRALL_INOTIFY_H
#include <QObject>
#include <QHash>
#include <QMap>
#include <QMutex>
#include <QString>
#include <QThread>
@ -48,6 +49,9 @@ private:
~INotifyThread();
void registerForNotification(INotify*, int);
void unregisterForNotification(INotify*);
// fireEvent happens from the inotify thread
// but addPath comes from the main thread
static QMutex s_mutex;
protected:
void run();
private:
@ -62,7 +66,7 @@ private:
// the mask is shared for all paths
int _mask;
QHash<QString, int> _wds;
QMap<QString, int> _wds;
};
}

21
src/mirall/syncresult.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef MIRALL_SYNCRESULT_H
#define MIRALL_SYNCRESULT_H
#include <QStringList>
namespace Mirall
{
class SyncResult
{
public:
SyncResult();
~SyncResult();
private:
QStringList _deletedSource;
QStringList _deletedDestination;
};
}
#endif

View file

@ -1,7 +1,9 @@
#include <QDebug>
#include <QDir>
#include <QMutexLocker>
#include <QStringList>
#include <QDir>
#include <QTextStream>
#include "mirall/unisonfolder.h"
namespace Mirall {
@ -56,7 +58,7 @@ void UnisonFolder::startSync(const QStringList &pathList)
args << "-ui" << "text";
args << "-auto" << "-batch";
//args << "-confirmbigdel";
args << "-confirmbigdel=false";
// only use -path in after a full synchronization
// already happened, which we do only on the first
@ -72,6 +74,7 @@ void UnisonFolder::startSync(const QStringList &pathList)
args << path();
args << secondPath();
qDebug() << " * Unison: will use" << pathList.size() << "path arguments";
_unison->start(program, args);
}
@ -86,17 +89,35 @@ void UnisonFolder::slotStarted()
void UnisonFolder::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qDebug() << " * Unison process finished with status" << exitCode;
//if (exitCode != 0) {
qDebug() << _lastOutput;
//}
// parse a summary from here:
//[BGN] Copying zw.png from //piscola//space/store/folder1 to /space/mirall/folder1
//[BGN] Deleting gn.png from /space/mirall/folder1
//[END] Deleting gn.png
// from stderr:
//Reconciling changes
// <---- new file Package.h
_lastOutput.clear();
emit syncFinished();
}
void UnisonFolder::slotReadyReadStandardOutput()
{
qDebug() << _unison->readAllStandardOutput();;
QTextStream stream(&_lastOutput);
stream << _unison->readAllStandardOutput();;
}
void UnisonFolder::slotReadyReadStandardError()
{
//qDebug() << _unison->readAllStandardError();;
QTextStream stream(&_lastOutput);
stream << _unison->readAllStandardError();;
}
void UnisonFolder::slotStateChanged(QProcess::ProcessState state)

View file

@ -36,6 +36,9 @@ private:
QProcess *_unison;
QString _secondPath;
int _syncCount;
QString _lastOutput;
};
}