PDA

View Full Version : File rename detection



bunjee
23rd April 2009, 22:27
Hey guys,

I've developped a class using QFileInfo to monitor a very large amount of files / folders.

I didn't use QFileSystemWatcher because of its "file descriptors" limitation.

Basically I have a callback that checks every folders.

It's working pretty good.
I detect File / folder modification, delete, create.

My only issue is when a file gets renamed.
Because the QFileInfo::lastModified() is not updated.

Has anyone got a multi-platform way of detecting file renaming ?

Thanks.

aamer4yu
24th April 2009, 07:11
You could compare the file name,,, cant you ???
I dont know how you are checking for changes on file...but if you are storing previous info, you can check name too i guess instead of lastModified

bunjee
24th April 2009, 14:38
You cannot do that.

Say you keep the QFileInfo of 3 files.
You rename those 3 files.

Your 3 QFileInfo are no longer valid.

How do you know which is which ?

drhex
24th April 2009, 15:44
On unix at least, inode numbers remain unchanged after a rename within the filesystem. Otherwise I guess you'll have to keep a hash of the file contents.

bunjee
24th April 2009, 21:01
I guess we can conclude that:

Multiplatform file renaming detection != trivial.

bunjee
23rd July 2009, 15:21
Since I had a request,

Here is my fileWatcher implementation.

It uses Pimpl and some custom functions, it won't work out of the box.

qkFileWatcher.h :

#ifndef QKFILEWATCHER_H
#define QKFILEWATCHER_H

#include <qkGlobalHeader.h>

// Qt includes
#include <qobject>
#include <qdateTime>

class qkFileWatcherPrivate;

class qkFileWatcher;

class QKCORE_EXPORT qkFileWatch
{
public: // Enums
enum FileType
{
invalidType = 0,
fileType,
folderType
};

public: // Ctor
qkFileWatch(qkFileWatcher * watcher);
qkFileWatch(const QString & path, qkFileWatcher * watcher);
qkFileWatch(FileType type, const QString & path, qkFileWatcher * watcher);
virtual ~qkFileWatch() {}

public: // Interface
virtual void checkChange();
void refreshDate();

protected: // Functions
void request_fileModified(const QString parentPath, const QString fileName);
void request_filesCreated(const QString parentPath, const QStringList fileNames);
void request_filesDeleted(const QString parentPath, const QStringList fileNames);

void request_folderModified(const QString parentPath, const QString folderName);
void request_foldersCreated(const QString parentPath, const QStringList folderNames);
void request_foldersDeleted(const QString parentPath, const QStringList folderNames);

protected: // Variables
QString mPath;
QString mAbsolutePath;
QString mName;

FileType mType;

QDateTime mLastModified;

qint64 mSize;

qkFileWatcher * mWatcher;

public: // Properties
QString path() const;
QString absolutePath() const;
QString name() const;

bool isValid() const;

bool isFile() const;
bool isDirectory() const;
bool exists() const;

FileType type() const;
};

class QKCORE_EXPORT qkFolderWatch : public qkFileWatch
{
public: // Ctor
qkFolderWatch(const QString & path, qkFileWatcher * watcher);

public: // qkFileWatch interface reimplementation
bool contains(const QString & path);
virtual void checkChange();

private: // Functions
void recurseDirectories();
void checkDeleted();

int getFolderIndex_from_path(const QString & path);
int getFileIndex_from_path(const QString & path);

private: // Variables
QList<qkFileWatch> mFileWatchs;
QList<qkFolderWatch> mFolderWatchs;
};

class QKCORE_EXPORT qkFileWatcher : public QObject, public qkPrivatable
{
Q_OBJECT
friend class qkFileWatch;
friend class qkFolderWatch;
private:
QK_DECLARE_PRIVATE(qkFileWatcher);

public:
qkFileWatcher(QObject * parent = 0);
virtual ~qkFileWatcher();

public: // Interface
void addPath(const QString & path);
void removePath(const QString & path);

bool contains(const QString & path);

private slots:
void onCheckForChange();

private: // Functions
void request_folderModified(const QString parentPath, const QString folderName);
void request_foldersCreated(const QString parentPath, const QStringList folderNames);
void request_foldersDeleted(const QString parentPath, const QStringList folderNames);

void request_fileModified(const QString parentPath, const QString fileName);
void request_filesCreated(const QString parentPath, const QStringList fileNames);
void request_filesDeleted(const QString parentPath, const QStringList fileNames);

signals:
// Folder
void folderModified(const QString parentPath, const QString folderName);
void foldersCreated(const QString parentPath, const QStringList folderNames);
void foldersDeleted(const QString parentPath, const QStringList folderNames);

// File
void fileModified(const QString parentPath, const QString fileName);
void filesCreated(const QString parentPath, const QStringList fileNames);
void filesDeleted(const QString parentPath, const QStringList fileNames);
};

#endif // QKFILEWATCHER_H


qkFileWatcher.cpp part1 :


#include "qkFileWatcher.h"

// Qt includes
#include <qtimer>

// qk includes
#include <qkFileController.h>

//================================================== ===========================
// qkFileWatch
//================================================== ===========================

qkFileWatch::qkFileWatch(FileType type, const QString & path, qkFileWatcher * watcher)
{
QFileInfo info(path);

mWatcher = watcher;

mPath = path;
mAbsolutePath = info.absolutePath();
mName = info.fileName();
mType = type;
mSize = -1;

refreshDate();
}

qkFileWatch::qkFileWatch(const QString & path, qkFileWatcher * watcher)
{
QFileInfo info(path);

mWatcher = watcher;

mPath = path;
mAbsolutePath = info.absolutePath();
mName = info.fileName();
mType = fileType;
mSize = -1;

refreshDate();
}

qkFileWatch::qkFileWatch(qkFileWatcher * watcher)
{
mWatcher = watcher;

mType = invalidType;
mSize = -1;

refreshDate();
}

//================================================== ===========================

void qkFileWatch::request_fileModified(const QString parentPath, const QString fileName)
{
mWatcher->request_fileModified(parentPath, fileName);
}

void qkFileWatch::request_filesCreated(const QString parentPath, const QStringList fileNames)
{
mWatcher->request_filesCreated(parentPath, fileNames);
}

void qkFileWatch::request_filesDeleted(const QString parentPath, const QStringList fileNames)
{
mWatcher->request_filesDeleted(parentPath, fileNames);
}

//================================================== ===========================

void qkFileWatch::request_folderModified(const QString parentPath, const QString folderName)
{
mWatcher->request_folderModified(parentPath, folderName);
}

void qkFileWatch::request_foldersCreated(const QString parentPath, const QStringList folderNames)
{
mWatcher->request_foldersCreated(parentPath, folderNames);
}

void qkFileWatch::request_foldersDeleted(const QString parentPath, const QStringList folderNames)
{
mWatcher->request_foldersDeleted(parentPath, folderNames);
}

//================================================== ===========================

void qkFileWatch::refreshDate()
{
QFileInfo info(mPath);
if (info.exists() == false)
{
qkDebug("Deleted %s !", mPath.C_STR);

mSize = -1;
mLastModified = QDateTime();

return;
}

mSize = info.size();
mLastModified = info.lastModified();
}

/* virtual */ void qkFileWatch::checkChange()
{
QDateTime oldModified = mLastModified;
qint64 oldSize = mSize;

refreshDate();

// We check both modified date and size for greater precision
if (oldModified < mLastModified || oldSize != mSize)
{
if (mType == qkFileWatch::folderType)
{
request_folderModified(mAbsolutePath, mName);
}
else if (mType == qkFileWatch::fileType)
{
request_fileModified(mAbsolutePath, mName);
}
}
}

//================================================== ===========================

QString qkFileWatch::path() const { return mPath; }

QString qkFileWatch::absolutePath() const { return mAbsolutePath; }

QString qkFileWatch::name() const { return mName; }

bool qkFileWatch::isValid() const
{
if (mType == invalidType) return false;
else return true;
}

bool qkFileWatch::isFile() const
{
if (mType == fileType) return true;
else return false;
}

bool qkFileWatch::isDirectory() const
{
if (mType == folderType) return true;
else return false;
}

bool qkFileWatch::exists() const
{
if (mSize == -1) return false;
else return true;
}

qkFileWatch::FileType qkFileWatch::type() const
{
return mType;
}

//================================================== ===========================
// qkFolderWatch
//================================================== ===========================

qkFolderWatch::qkFolderWatch(const QString & path, qkFileWatcher * watcher)
: qkFileWatch(folderType, path, watcher)
{
recurseDirectories();
}

//================================================== ===========================

bool qkFolderWatch::contains(const QString & path)
{
for (int i = 0; i < mFolderWatchs.size(); i++)
{
if (mFolderWatchs[i].path() == path) return true;
}

for (int i = 0; i < mFileWatchs.size(); i++)
{
if (mFileWatchs[i].path() == path) return true;
}

return false;
}

void qkFolderWatch::checkChange()
{
QDateTime oldModified = mLastModified;

qkFileWatch::checkChange();

// Checking all sub-directories
for (int i = 0; i < mFolderWatchs.size(); i++)
{
mFolderWatchs[i].checkChange();
}

// Note: on windows we have to check files even if the directory has not changed
for (int i = 0; i < mFileWatchs.size(); i++)
{
mFileWatchs[i].checkChange();
}

// Checking files only if the directory has been modified
if (oldModified != mLastModified)
{
// Checking for deleted files
checkDeleted();

// Recurse for new files
recurseDirectories();
}
}

bunjee
23rd July 2009, 15:22
qkFileWatcher.cpp part2:



//================================================== ===========================

void qkFolderWatch::recurseDirectories()
{
QDir dir(mPath);
QFileInfoList list = dir.entryInfoList();
QStringList newFolders;
QStringList newFiles;

foreach (QFileInfo info, list)
{
if (contains(info.filePath())) continue;
if (info.fileName() == ".." || info.fileName() == ".") continue;
if (info.isHidden()) continue;

if (info.isDir())
{
qkFolderWatch folder(info.filePath(), mWatcher);
mFolderWatchs.push_back(folder);

newFolders += info.fileName();
}
else if (info.isFile())
{
qkFileWatch file(info.filePath(), mWatcher);
mFileWatchs.push_back(file);

newFiles += info.fileName();
}
}

if (newFolders.size())
{
request_foldersCreated(mPath, newFolders);
}

if (newFiles.size())
{
request_filesCreated(mPath, newFiles);
}
}

void qkFolderWatch::checkDeleted()
{
QStringList deletedFolders;
QStringList deletedFiles;

for (int i = 0; i < mFolderWatchs.size(); i++)
{
if (mFolderWatchs[i].exists() == false)
{
deletedFolders += mFolderWatchs[i].name();

mFolderWatchs.removeAt(i);
i = -1;
}
}

for (int i = 0; i < mFileWatchs.size(); i++)
{
if (mFileWatchs[i].exists() == false)
{
deletedFiles += mFileWatchs[i].name();

mFileWatchs.removeAt(i);
i = -1;
}
}

if (deletedFolders.size())
{
request_foldersDeleted(mPath, deletedFolders);
}

if (deletedFiles.size())
{
request_filesDeleted(mPath, deletedFiles);
}
}

//================================================== ===========================

int qkFolderWatch::getFolderIndex_from_path(const QString & path)
{
for (int i = 0; i < mFolderWatchs.size(); i++)
{
if (mFolderWatchs[i].path() == path) return i;
}
return -1;
}

int qkFolderWatch::getFileIndex_from_path(const QString & path)
{
for (int i = 0; i < mFileWatchs.size(); i++)
{
if (mFileWatchs[i].path() == path) return i;
}
return -1;
}

//================================================== ===========================
// Private
//================================================== ===========================

#include <qkGlobalHeader_p.h>

class qkFileWatcherPrivate : public qkPrivate
{
protected:
QK_DECLARE_PUBLIC(qkFileWatcher);

public:
qkFileWatcherPrivate(qkFileWatcher * p);
void init();

private: // Functions
void addPath(const QString & path);
void removePath(const QString & path);

int getFolderIndex_from_path(const QString & path);
int getFileIndex_from_path(const QString & path);

private: // Variables
QTimer timer;

QList<qkFileWatch> fileWatchs;
QList<qkFolderWatch> folderWatchs;
};

qkFileWatcherPrivate::qkFileWatcherPrivate(qkFileW atcher * p)
: qkPrivate(p)
{
}

void qkFileWatcherPrivate::init()
{
Q_Q(qkFileWatcher);

QObject::connect(&timer, SIGNAL(timeout()), q, SLOT(onCheckForChange()));

timer.start(100);
}

//================================================== ===========================

void qkFileWatcherPrivate::addPath(const QString & path)
{
Q_Q(qkFileWatcher);

QFileInfo info(path);

if (info.isDir())
{
qkFolderWatch folder(path, q);
folderWatchs.push_back(folder);
}
else
{
qkFileWatch file(path, q);
fileWatchs.push_back(file);
}
}

void qkFileWatcherPrivate::removePath(const QString & path)
{
int index = getFolderIndex_from_path(path);
if (index != -1) folderWatchs.removeAt(index);

index = getFileIndex_from_path(path);
if (index != -1) fileWatchs.removeAt(index);
}

//================================================== ===========================

int qkFileWatcherPrivate::getFolderIndex_from_path(con st QString & path)
{
for (int i = 0; i < folderWatchs.size(); i++)
{
if (folderWatchs[i].path() == path) return i;
}
return -1;
}

int qkFileWatcherPrivate::getFileIndex_from_path(const QString & path)
{
for (int i = 0; i < fileWatchs.size(); i++)
{
if (fileWatchs[i].path() == path) return i;
}
return -1;
}

//================================================== ===========================
// Ctor / dtor
//================================================== ===========================

qkFileWatcher::qkFileWatcher(QObject * parent)
: QObject(parent), qkPrivatable(new qkFileWatcherPrivate(this))
{
Q_D(qkFileWatcher);
d->init();
}

qkFileWatcher::~qkFileWatcher()
{
}

//================================================== ===========================
// Interface
//================================================== ===========================

void qkFileWatcher::addPath(const QString & path)
{
Q_D(qkFileWatcher);

if (contains(path)) return;

d->addPath(path);
}

void qkFileWatcher::removePath(const QString & path)
{
Q_D(qkFileWatcher);

if (contains(path) == false) return;

d->removePath(path);
}

bool qkFileWatcher::contains(const QString & path)
{
Q_D(qkFileWatcher);

for (int i = 0; i < d->folderWatchs.size(); i++)
{
if (d->folderWatchs[i].path() == path) return true;
}

for (int i = 0; i < d->fileWatchs.size(); i++)
{
if (d->fileWatchs[i].path() == path) return true;
}

return false;
}

//================================================== ===========================
// Private slots
//================================================== ===========================

void qkFileWatcher::onCheckForChange()
{
Q_D(qkFileWatcher);

QFileInfo info;

for (int i = 0; i < d->fileWatchs.size(); i++)
{
d->fileWatchs[i].checkChange();

if (d->fileWatchs[i].exists() == false)
{
request_filesDeleted(d->fileWatchs[i].absolutePath(),
QStringList() << d->fileWatchs[i].name());

d->fileWatchs.removeAt(i);
i = -1;
}
}

for (int i = 0; i < d->folderWatchs.size(); i++)
{
d->folderWatchs[i].checkChange();

if (d->folderWatchs[i].exists() == false)
{
request_foldersDeleted(d->folderWatchs[i].absolutePath(),
QStringList() << d->folderWatchs[i].name());

d->folderWatchs.removeAt(i);
i = -1;
}

}
}

//================================================== ===========================
// Private functions
//================================================== ===========================

void qkFileWatcher::request_folderModified(const QString parentPath, const QString folderName)
{
qkDebug("Folder modified %s %s", parentPath.C_STR, folderName.C_STR);

emit folderModified(parentPath, folderName);
}

void qkFileWatcher::request_foldersCreated(const QString parentPath, const QStringList folderNames)
{
foreach (QString name, folderNames)
{
qkDebug("Folder created %s %s", parentPath.C_STR, name.C_STR);
}

emit foldersCreated(parentPath, folderNames);
}

void qkFileWatcher::request_foldersDeleted(const QString parentPath, const QStringList folderNames)
{
foreach (QString name, folderNames)
{
qkDebug("Folder deleted %s %s", parentPath.C_STR, name.C_STR);
}

emit foldersDeleted(parentPath, folderNames);
}

//================================================== ===========================

void qkFileWatcher::request_fileModified(const QString parentPath, const QString fileName)
{
qkDebug("File modified %s %s", parentPath.C_STR, fileName.C_STR);

emit fileModified(parentPath, fileName);
}

void qkFileWatcher::request_filesCreated(const QString parentPath, const QStringList fileNames)
{
foreach (QString name, fileNames)
{
qkDebug("File created %s %s", parentPath.C_STR, name.C_STR);
}

emit filesCreated(parentPath, fileNames);
}

void qkFileWatcher::request_filesDeleted(const QString parentPath, const QStringList fileNames)
{
foreach (QString name, fileNames)
{
qkDebug("File deleted %s %s", parentPath.C_STR, name.C_STR);
}

emit filesDeleted(parentPath, fileNames);
}