[Digikam-devel] [digikam] /: On Linux, use Inotify directly for file notification changes

Marcel Wiesweg marcel.wiesweg at gmx.de
Mon Nov 7 22:06:29 GMT 2011


Git commit dcf2ba9d7e92d51b049bdd354f82ddbf75e51a31 by Marcel Wiesweg.
Committed on 07/11/2011 at 22:47.
Pushed by mwiesweg into branch 'master'.

On Linux, use Inotify directly for file notification changes

Use Sebastian Trueg's KInotify wrapper, if Inotify is available.
This gives much more detailed reports and especially info when a file
has been closed after write. For a detailed explanation, see
http://trueg.wordpress.com/2011/10/13/taking-a-break-from-crash-fixing-for-usability/
Separate the file watch code from AlbumManager to a new class, AlbumWatch.
Do not watch directories recursively (convenient API, but often inefficient implementation
out of our reach). We scan the directories anyway, it is possible and efficient to
add each directory = album separately.
If Inotify is not available (non-linux), the previous code based on
KDirWatch and KIO is still used.

CCMAIL: digikam-devel at kde.org

M  +5    -2    CMakeLists.txt
M  +1    -1    digikam/CMakeLists.txt
M  +7    -209  digikam/album/albummanager.cpp
M  +0    -5    digikam/album/albummanager.h
A  +512  -0    digikam/album/albumwatch.cpp     [License: GPL (v2+)]
A  +90   -0    digikam/album/albumwatch.h     [License: GPL (v2+)]
A  +517  -0    libs/3rdparty/kinotify/kinotify.cpp     [License: LGPL (v2+)]
A  +198  -0    libs/3rdparty/kinotify/kinotify.h     [License: LGPL (v2+)]

http://commits.kde.org/digikam/dcf2ba9d7e92d51b049bdd354f82ddbf75e51a31

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7f2ed8b..327dd79 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1293,9 +1293,10 @@ IF(DIGIKAM_CAN_BE_COMPILED)
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/sqlite2/where.c
        )
 
-    SET(libkmemoryinfo_SRCS
+    SET(libkde3rdparty_SRCS
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/kmemoryinfo/kmemoryinfo.cpp
-       )
+        ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/kinotify/kinotify.cpp
+      )
 
     SET(libhaar_SRCS
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/database/haar/haar.cpp
@@ -1383,6 +1384,7 @@ IF(DIGIKAM_CAN_BE_COMPILED)
         ${CMAKE_CURRENT_SOURCE_DIR}/digikam/album/albumselectwidget.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/digikam/album/albumthumbnailloader.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/digikam/album/albumtreeview.cpp
+        ${CMAKE_CURRENT_SOURCE_DIR}/digikam/album/albumwatch.cpp
 
         ${CMAKE_CURRENT_SOURCE_DIR}/digikam/database/databaseguierrorhandler.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/digikam/database/dbstatdlg.cpp
@@ -1496,6 +1498,7 @@ IF(DIGIKAM_CAN_BE_COMPILED)
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/lprof
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/sqlite2
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/kmemoryinfo
+        ${CMAKE_CURRENT_SOURCE_DIR}/libs/3rdparty/kinotify
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/database
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/database/haar
         ${CMAKE_CURRENT_SOURCE_DIR}/libs/database/imagehistory
diff --git a/digikam/CMakeLists.txt b/digikam/CMakeLists.txt
index 1f9ed01..e015354 100644
--- a/digikam/CMakeLists.txt
+++ b/digikam/CMakeLists.txt
@@ -39,7 +39,7 @@ SET(digikamcore_LIB_SRCS
         ${libtthread_SRCS}
         ${libversionmanager_SRCS}
         ${libkgeomaphelper_SRCS}
-        ${libkmemoryinfo_SRCS}
+        ${libkde3rdparty_SRCS}
 
         # widgets and dialogs
         ${libcommonwidgets_SRCS}
diff --git a/digikam/album/albummanager.cpp b/digikam/album/albummanager.cpp
index b7ea3e3..a198516 100644
--- a/digikam/album/albummanager.cpp
+++ b/digikam/album/albummanager.cpp
@@ -80,6 +80,7 @@ extern "C"
 #include "albumdb.h"
 #include "album.h"
 #include "albumsettings.h"
+#include "albumwatch.h"
 #include "collectionlocation.h"
 #include "collectionmanager.h"
 #include <config-digikam.h>
@@ -164,7 +165,7 @@ public:
         dateListJob(0),
         tagListJob(0),
         personListJob(0),
-        dirWatch(0),
+        albumWatch(0),
         rootPAlbum(0),
         rootTAlbum(0),
         rootDAlbum(0),
@@ -191,16 +192,12 @@ public:
     int                         dbPort;
     bool                        dbInternalServer;
 
-    QList<QDateTime>            dbPathModificationDateList;
-    QList<QString>              dirWatchBlackList;
-
     KIO::TransferJob*           albumListJob;
     KIO::TransferJob*           dateListJob;
     KIO::TransferJob*           tagListJob;
     KIO::TransferJob*           personListJob;
 
-    KDirWatch*                  dirWatch;
-    QStringList                 dirWatchAddedDirs;
+    AlbumWatch*                 albumWatch;
 
     PAlbum*                     rootPAlbum;
     TAlbum*                     rootTAlbum;
@@ -233,24 +230,6 @@ public:
 
 public:
 
-    QList<QDateTime> buildDirectoryModList(const QFileInfo& dbFile)
-    {
-        // retrieve modification dates
-        QList<QDateTime> modList;
-        QFileInfoList    fileInfoList = dbFile.dir().entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
-
-        // build list
-        foreach (const QFileInfo& info, fileInfoList)
-        {
-            // ignore digikam4.db and journal and other temporary files
-            if (!dirWatchBlackList.contains(info.fileName()))
-            {
-                modList << info.lastModified();
-            }
-        }
-        return modList;
-    }
-
     QString labelForAlbumRootAlbum(const CollectionLocation& location)
     {
         QString label = location.label();
@@ -309,10 +288,7 @@ AlbumManager::AlbumManager()
     : d(new AlbumManagerPriv)
 {
     internalInstance = this;
-    d->dirWatch      = new KDirWatch(this);
-
-    connect(d->dirWatch, SIGNAL(dirty(QString)),
-            this, SLOT(slotDirWatchDirty(QString)));
+    d->albumWatch = new AlbumWatch(this);
 
     // these operations are pretty fast, no need for long queuing
     d->scanPAlbumsTimer = new QTimer(this);
@@ -670,20 +646,10 @@ bool AlbumManager::setDatabase(const DatabaseParameters& params, bool priority,
         disconnect(DatabaseAccess::databaseWatch(), 0, this, 0);
     }
 
-    d->dbPathModificationDateList.clear();
+    d->albumWatch->clear();
 
     cleanUp();
 
-    foreach (const QString& addedDirectory, d->dirWatchAddedDirs)
-    {
-        d->dirWatch->removeDir(addedDirectory);
-    }
-    d->dirWatchAddedDirs.clear();
-
-    QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FileMoved", 0, 0);
-    QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded", 0, 0);
-    QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved", 0, 0);
-
     d->currentAlbum = 0;
     emit signalAlbumCurrentChanged(0);
     emit signalAlbumsCleared();
@@ -741,6 +707,8 @@ bool AlbumManager::setDatabase(const DatabaseParameters& params, bool priority,
         return true;
     }
 
+    d->albumWatch->setDatabaseParameters(params);
+
     // still suspended from above
     ScanController::instance()->resumeCollectionScan();
 
@@ -984,16 +952,9 @@ bool AlbumManager::setDatabase(const DatabaseParameters& params, bool priority,
 
     // -- ---------------------------------------------------------
 
-    d->dirWatchBlackList.clear();
-
 #ifdef USE_THUMBS_DB
     QApplication::setOverrideCursor(Qt::WaitCursor);
 
-    if (params.isSQLite())
-    {
-        d->dirWatchBlackList << "thumbnails-digikam.db" << "thumbnails-digikam.db-journal";
-    }
-
     ThumbnailLoadThread::initializeThumbnailDatabase(DatabaseAccess::parameters().thumbnailParameters(),
                                                      new DatabaseThumbnailInfoProvider());
 
@@ -1005,18 +966,6 @@ bool AlbumManager::setDatabase(const DatabaseParameters& params, bool priority,
 
     // -- ---------------------------------------------------------
 
-    // measures to filter out KDirWatch signals caused by database operations
-    if (params.isSQLite())
-    {
-        QFileInfo dbFile(params.SQLiteDatabaseFile());
-        d->dirWatchBlackList << dbFile.fileName() << dbFile.fileName() + "-journal";
-
-        // ensure this is done after setting up the black list
-        d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
-    }
-
-    // -- ---------------------------------------------------------
-
 #ifdef HAVE_NEPOMUK
 
     if (checkNepomukService())
@@ -1107,33 +1056,6 @@ void AlbumManager::startScan()
 
     d->changed = false;
 
-    KDirWatch::Method m = d->dirWatch->internalMethod();
-    QString           mName("FAM");
-
-    if (m == KDirWatch::DNotify)
-    {
-        mName = QString("DNotify");
-    }
-    else if (m == KDirWatch::Stat)
-    {
-        mName = QString("Stat");
-    }
-    else if (m == KDirWatch::INotify)
-    {
-        mName = QString("INotify");
-    }
-
-    kDebug() << "KDirWatch method = " << mName;
-
-    // connect to KDirNotify
-
-    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FileMoved",
-                                          this, SLOT(slotKioFileMoved(QString,QString)));
-    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded",
-                                          this, SLOT(slotKioFilesAdded(QString)));
-    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved",
-                                          this, SLOT(slotKioFilesDeleted(QStringList)));
-
     // create root albums
     d->rootPAlbum = new PAlbum(i18n("My Albums"));
     insertPAlbum(d->rootPAlbum, 0);
@@ -1230,12 +1152,6 @@ void AlbumManager::slotCollectionLocationPropertiesChanged(const CollectionLocat
 
 void AlbumManager::addAlbumRoot(const CollectionLocation& location)
 {
-    if (!d->dirWatch->contains(location.albumRootPath()))
-    {
-        d->dirWatchAddedDirs << location.albumRootPath();
-        d->dirWatch->addDir(location.albumRootPath(), KDirWatch::WatchSubDirs);
-    }
-
     PAlbum* album = d->albumRootAlbumHash.value(location.id());
 
     if (!album)
@@ -1251,7 +1167,6 @@ void AlbumManager::addAlbumRoot(const CollectionLocation& location)
 
 void AlbumManager::removeAlbumRoot(const CollectionLocation& location)
 {
-    d->dirWatch->removeDir(location.albumRootPath());
     // retrieve and remove from hash
     PAlbum* album = d->albumRootAlbumHash.take(location.id());
 
@@ -3399,121 +3314,4 @@ void AlbumManager::slotImageTagChange(const ImageTagChangeset& changeset)
     }
 }
 
-void AlbumManager::slotNotifyFileChange(const QString& path)
-{
-    //kDebug() << "Detected file change at" << path;
-    ScanController::instance()->scheduleCollectionScanRelaxed(path);
-}
-
-void AlbumManager::slotDirWatchDirty(const QString& path)
-{
-    // Filter out dirty signals triggered by changes on the database file
-    foreach (const QString& bannedFile, d->dirWatchBlackList)
-    {
-        if (path.endsWith(bannedFile))
-        {
-            return;
-        }
-    }
-
-    DatabaseParameters params = DatabaseAccess::parameters();
-
-    if (params.isSQLite())
-    {
-        QFileInfo info(path);
-        QDir dir;
-
-        if (info.isDir())
-        {
-            dir = QDir(path);
-        }
-        else
-        {
-            dir = info.dir();
-        }
-
-        QFileInfo dbFile(params.SQLiteDatabaseFile());
-
-        // Workaround for broken KDirWatch in KDE 4.2.4
-        if (path.startsWith(dbFile.filePath()))
-        {
-            return;
-        }
-
-        // is the signal for the directory containing the database file?
-        if (dbFile.dir() == dir)
-        {
-            // retrieve modification dates
-            QList<QDateTime> modList = d->buildDirectoryModList(dbFile);
-
-            // check for equality
-            if (modList == d->dbPathModificationDateList)
-            {
-                //kDebug() << "Filtering out db-file-triggered dir watch signal";
-                // we can skip the signal
-                return;
-            }
-
-            // set new list
-            d->dbPathModificationDateList = modList;
-        }
-    }
-
-    kDebug() << "KDirWatch detected change at" << path;
-
-    slotNotifyFileChange(path);
-}
-
-void AlbumManager::slotKioFileMoved(const QString& urlFrom, const QString& urlTo)
-{
-    kDebug() << urlFrom << urlTo;
-    handleKioNotification(KUrl(urlFrom));
-    handleKioNotification(KUrl(urlTo));
-}
-
-void AlbumManager::slotKioFilesAdded(const QString& url)
-{
-    kDebug() << url;
-    handleKioNotification(KUrl(url));
-}
-
-void AlbumManager::slotKioFilesDeleted(const QStringList& urls)
-{
-    kDebug() << urls;
-    foreach (const QString& url, urls)
-    {
-        handleKioNotification(KUrl(url));
-    }
-}
-
-void AlbumManager::handleKioNotification(const KUrl& url)
-{
-    if (url.isLocalFile())
-    {
-        QString path = url.directory();
-
-        //kDebug() << path << !CollectionManager::instance()->albumRootPath(path).isEmpty();
-        // check path is in our collection
-        if (CollectionManager::instance()->albumRootPath(path).isNull())
-        {
-            return;
-        }
-
-        kDebug() << "KDirNotify detected file change at" << path;
-
-        slotNotifyFileChange(path);
-    }
-    else
-    {
-        DatabaseUrl dbUrl(url);
-
-        if (dbUrl.isAlbumUrl())
-        {
-            QString path = dbUrl.fileUrl().directory();
-            kDebug() << "KDirNotify detected file change at" << path;
-            slotNotifyFileChange(path);
-        }
-    }
-}
-
 }  // namespace Digikam
diff --git a/digikam/album/albummanager.h b/digikam/album/albummanager.h
index d8a577d..4710a0f 100644
--- a/digikam/album/albummanager.h
+++ b/digikam/album/albummanager.h
@@ -658,11 +658,6 @@ private Q_SLOTS:
     void slotPeopleJobResult(KJob* job);
     void slotPeopleJobData(KIO::Job* job, const QByteArray& data);
 
-    void slotDirWatchDirty(const QString& path);
-    void slotKioFileMoved(const QString& urlFrom, const QString& urlTo);
-    void slotKioFilesDeleted(const QStringList& urls);
-    void slotKioFilesAdded(const QString& directory);
-    void slotNotifyFileChange(const QString& directory);
     void slotCollectionLocationStatusChanged(const CollectionLocation&, int);
     void slotCollectionLocationPropertiesChanged(const CollectionLocation& location);
     void slotAlbumChange(const AlbumChangeset& changeset);
diff --git a/digikam/album/albumwatch.cpp b/digikam/album/albumwatch.cpp
new file mode 100644
index 0000000..5011062
--- /dev/null
+++ b/digikam/album/albumwatch.cpp
@@ -0,0 +1,512 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date        : 2011-11-07
+ * Description : Directory watch interface
+ *
+ * Copyright (C) 2011 by Marcel Wiesweg <marcel.wiesweg at gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, or (at your option)
+ * any later version.
+ *
+ * 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.
+ *
+ * ============================================================ */
+
+#include "albumwatch.moc"
+
+// Qt includes
+
+#include <QDateTime>
+#include <QDBusConnection>
+#include <QDir>
+#include <QFileInfo>
+
+// KDE includes
+
+#include <kdebug.h>
+#include <kdirwatch.h>
+
+// Local includes
+
+#include "album.h"
+#include "albummanager.h"
+#include "collectionlocation.h"
+#include "collectionmanager.h"
+#include "databaseparameters.h"
+#include "kinotify.h"
+#include "scancontroller.h"
+
+namespace Digikam
+{
+
+enum Mode
+{
+    InotifyMode,
+    KDirWatchMode
+};
+
+class AlbumWatch::AlbumWatchPriv
+{
+public:
+
+    AlbumWatchPriv(AlbumWatch* q)
+        : inotify(0),
+          dirWatch(0),
+          connectedToKIO(false),
+          q(q)
+          
+    {
+    }
+
+    void determineMode();
+    bool isInotifyMode() const { return mode == InotifyMode; }
+
+    Mode               mode;
+
+    KInotify*          inotify;
+    KDirWatch*         dirWatch;
+    QStringList        dirWatchAddedDirs;
+    bool               connectedToKIO;
+
+    DatabaseParameters params;
+    QStringList        fileNameBlackList;
+    QList<QDateTime>   dbPathModificationDateList;
+
+    AlbumWatch* const  q;
+
+    bool inBlackList(const QString& path);
+    QList<QDateTime> buildDirectoryModList(const QFileInfo& dbFile);
+    bool inDirWatchParametersBlackList(const QFileInfo& info, const QString& path);
+};
+
+void AlbumWatch::AlbumWatchPriv::determineMode()
+{
+    if (KInotify().available())
+    {
+        mode = InotifyMode;
+    }
+    else
+    {
+        mode = KDirWatchMode;
+    }
+}
+
+bool AlbumWatch::AlbumWatchPriv::inBlackList(const QString& path)
+{
+    // Filter out dirty signals triggered by changes on the database file
+    foreach (const QString& bannedFile, fileNameBlackList)
+    {
+        if (path.endsWith(bannedFile))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+// --------- //
+
+AlbumWatch::AlbumWatch(AlbumManager* parent)
+    : QObject(parent),
+      d(new AlbumWatchPriv(this))
+{
+    d->determineMode();
+
+    if (d->isInotifyMode())
+    {
+        connectToKInotify();
+    }
+    else
+    {
+        connectToKDirWatch();
+        connectToKIO();
+    }
+
+    connect(parent, SIGNAL(signalAlbumAdded(Album*)),
+            this, SLOT(slotAlbumAdded(Album*)));
+    connect(parent, SIGNAL(signalAlbumAboutToBeDeleted(Album*)),
+            this, SLOT(slotAlbumAboutToBeDeleted(Album*)));
+}
+
+AlbumWatch::~AlbumWatch()
+{
+    delete d;
+}
+
+void AlbumWatch::clear()
+{
+    if (d->dirWatch)
+    {
+        foreach (const QString& addedDirectory, d->dirWatchAddedDirs)
+        {
+            d->dirWatch->removeDir(addedDirectory);
+        }
+        d->dirWatchAddedDirs.clear();
+    }
+
+    if (d->connectedToKIO)
+    {
+        QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FileMoved", 0, 0);
+        QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded", 0, 0);
+        QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved", 0, 0);
+
+        d->connectedToKIO = false;
+    }
+
+    if (d->inotify)
+    {
+        d->inotify->removeAllWatches();
+    }
+}
+
+void AlbumWatch::setDatabaseParameters(const DatabaseParameters& params)
+{
+    d->params = params;
+
+    d->fileNameBlackList.clear();
+    // filter out notifications caused by database operations
+    if (params.isSQLite())
+    {
+        d->fileNameBlackList << "thumbnails-digikam.db" << "thumbnails-digikam.db-journal";
+
+        QFileInfo dbFile(params.SQLiteDatabaseFile());
+        d->fileNameBlackList << dbFile.fileName() << dbFile.fileName() + "-journal";
+
+        // ensure this is done after setting up the black list
+        d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
+    }
+}
+
+void AlbumWatch::slotAlbumAdded(Album* a)
+{
+    if (a->isRoot() || a->type() != Album::PHYSICAL)
+    {
+        return;
+    }
+
+    PAlbum* album = static_cast<PAlbum*>(a);
+
+    CollectionLocation location = CollectionManager::instance()->locationForAlbumRootId(album->albumRootId());
+    if (!location.isAvailable())
+    {
+        return;
+    }
+    QString dir = album->folderPath();
+    if (dir.isEmpty())
+    {
+        return;
+    }
+
+    if (d->isInotifyMode())
+    {
+        d->inotify->watchDirectory(dir);
+    }
+    else
+    {
+        if (!d->dirWatch->contains(dir))
+        {
+            d->dirWatchAddedDirs << dir;
+            d->dirWatch->addDir(dir, KDirWatch::WatchFiles | KDirWatch::WatchDirOnly);
+        }
+    }
+}
+
+void AlbumWatch::slotAlbumAboutToBeDeleted(Album* a)
+{
+    if (a->isRoot() || a->type() != Album::PHYSICAL)
+    {
+        return;
+    }
+
+    PAlbum* album = static_cast<PAlbum*>(a);
+    QString dir = album->folderPath();
+    if (dir.isEmpty())
+    {
+        return;
+    }
+
+    if (d->isInotifyMode())
+    {
+        d->inotify->removeDirectory(dir);
+    }
+    else
+    {
+        d->dirWatch->removeDir(album->folderPath());
+    }
+}
+
+void AlbumWatch::rescanDirectory(const QString& dir)
+{
+    kDebug() << "Detected change, triggering rescan of directory" << dir;
+    ScanController::instance()->scheduleCollectionScanRelaxed(dir);
+}
+
+/* ---------- KInotify ---------- */
+
+void AlbumWatch::connectToKInotify()
+{
+    if (d->inotify)
+    {
+        return;
+    }
+
+    d->inotify = new KInotify(this);
+
+    connect( d->inotify, SIGNAL( moved( QString, QString ) ),
+             this, SLOT( slotFileMoved( QString, QString ) ) );
+    connect( d->inotify, SIGNAL( deleted( QString, bool ) ),
+             this, SLOT( slotFileDeleted( QString, bool ) ) );
+    connect( d->inotify, SIGNAL( created( QString, bool ) ),
+             this, SLOT( slotFileCreated( QString, bool ) ) );
+    connect( d->inotify, SIGNAL( closedWrite( QString ) ),
+             this, SLOT( slotFileClosedAfterWrite( QString ) ) );
+    connect( d->inotify, SIGNAL( watchUserLimitReached() ),
+             this, SLOT( slotInotifyWatchUserLimitReached() ) );
+}
+
+void AlbumWatch::slotFileMoved(const QString& from, const QString& to)
+{
+    // we could add a copyOrMoveHint here...but identical-file detection seems to work well
+    rescanPath(from);
+    rescanPath(to);
+}
+
+void AlbumWatch::slotFileDeleted(const QString& path, bool isDir)
+{
+    Q_UNUSED(isDir);
+    rescanPath(path);
+}
+
+void AlbumWatch::slotFileCreated(const QString& path, bool isDir)
+{
+    if (isDir)
+    {
+        rescanPath(path);
+    }
+    // for files, rely on ClosedAfterWrite only,
+    // which always comes after create if the operation has finished
+}
+
+void AlbumWatch::slotFileClosedAfterWrite(const QString& path)
+{
+    rescanPath(path);
+}
+
+void AlbumWatch::slotInotifyWatchUserLimitReached()
+{
+    kError() << "Reached inotify limit";
+}
+
+void AlbumWatch::rescanPath(const QString& path)
+{
+    if (d->inBlackList(path))
+    {
+        return;
+    }
+    KUrl url(path);
+    rescanDirectory(url.directory());
+}
+
+/* ---------- KDirWatch ---------- */
+
+QList<QDateTime> AlbumWatch::AlbumWatchPriv::buildDirectoryModList(const QFileInfo& dbFile)
+{
+    // retrieve modification dates
+    QList<QDateTime> modList;
+    QFileInfoList    fileInfoList = dbFile.dir().entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+
+    // build list
+    foreach (const QFileInfo& info, fileInfoList)
+    {
+        // ignore digikam4.db and journal and other temporary files
+        if (!fileNameBlackList.contains(info.fileName()))
+        {
+            modList << info.lastModified();
+        }
+    }
+    return modList;
+}
+
+bool AlbumWatch::AlbumWatchPriv::inDirWatchParametersBlackList(const QFileInfo& info, const QString& path)
+{
+    if (params.isSQLite())
+    {
+        QDir dir;
+
+        if (info.isDir())
+        {
+            dir = QDir(path);
+        }
+        else
+        {
+            dir = info.dir();
+        }
+
+        QFileInfo dbFile(params.SQLiteDatabaseFile());
+
+        // Workaround for broken KDirWatch in KDE 4.2.4
+        if (path.startsWith(dbFile.filePath()))
+        {
+            return true;
+        }
+
+        // is the signal for the directory containing the database file?
+        if (dbFile.dir() == dir)
+        {
+            // retrieve modification dates
+            QList<QDateTime> modList = buildDirectoryModList(dbFile);
+
+            // check for equality
+            if (modList == dbPathModificationDateList)
+            {
+                //kDebug() << "Filtering out db-file-triggered dir watch signal";
+                // we can skip the signal
+                return true;
+            }
+
+            // set new list
+            dbPathModificationDateList = modList;
+        }
+    }
+
+    return false;
+}
+
+void AlbumWatch::slotDirWatchDirty(const QString& path)
+{
+    if (d->inBlackList(path))
+    {
+        return;
+    }
+
+    QFileInfo info(path);
+    if (d->inDirWatchParametersBlackList(info, path))
+    {
+        return;
+    }
+
+    kDebug() << "KDirWatch detected change at" << path;
+
+    if (info.isDir())
+    {
+        rescanDirectory(path);
+    }
+    else
+    {
+        rescanDirectory(info.path());
+    }
+}
+
+void AlbumWatch::connectToKDirWatch()
+{
+    if (d->dirWatch)
+    {
+        return;
+    }
+
+    KDirWatch::Method m = d->dirWatch->internalMethod();
+    QString           mName("FAM");
+
+    if (m == KDirWatch::DNotify)
+    {
+        mName = QString("DNotify");
+    }
+    else if (m == KDirWatch::Stat)
+    {
+        mName = QString("Stat");
+    }
+    else if (m == KDirWatch::INotify)
+    {
+        mName = QString("INotify");
+    }
+
+    kDebug() << "KDirWatch method = " << mName;
+
+    d->dirWatch = new KDirWatch(this);
+
+    connect(d->dirWatch, SIGNAL(dirty(QString)),
+            this, SLOT(slotDirWatchDirty(QString)));
+}
+
+
+/* ---------- KIO ---------- */
+
+void AlbumWatch::connectToKIO()
+{
+    if (d->connectedToKIO)
+    {
+        return;
+    }
+
+    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FileMoved",
+                                          this, SLOT(slotKioFileMoved(QString,QString)));
+    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded",
+                                          this, SLOT(slotKioFilesAdded(QString)));
+    QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved",
+                                          this, SLOT(slotKioFilesDeleted(QStringList)));
+
+    d->connectedToKIO = true;
+}
+
+void AlbumWatch::slotKioFileMoved(const QString& urlFrom, const QString& urlTo)
+{
+    kDebug() << urlFrom << urlTo;
+    handleKioNotification(KUrl(urlFrom));
+    handleKioNotification(KUrl(urlTo));
+}
+
+void AlbumWatch::slotKioFilesDeleted(const QStringList& urls)
+{
+    kDebug() << urls;
+    foreach (const QString& url, urls)
+    {
+        handleKioNotification(KUrl(url));
+    }
+}
+
+void AlbumWatch::slotKioFilesAdded(const QString& url)
+{
+    kDebug() << url;
+    handleKioNotification(KUrl(url));
+}
+
+void AlbumWatch::handleKioNotification(const KUrl& url)
+{
+    if (url.isLocalFile())
+    {
+        QString path = url.directory();
+
+        //kDebug() << path << !CollectionManager::instance()->albumRootPath(path).isEmpty();
+        // check path is in our collection
+        if (CollectionManager::instance()->albumRootPath(path).isNull())
+        {
+            return;
+        }
+
+        kDebug() << "KDirNotify detected file change at" << path;
+
+        rescanDirectory(path);
+    }
+    else
+    {
+        DatabaseUrl dbUrl(url);
+
+        if (dbUrl.isAlbumUrl())
+        {
+            QString path = dbUrl.fileUrl().directory();
+            kDebug() << "KDirNotify detected file change at" << path;
+            rescanDirectory(path);
+        }
+    }
+}
+
+}
\ No newline at end of file
diff --git a/digikam/album/albumwatch.h b/digikam/album/albumwatch.h
new file mode 100644
index 0000000..62e89e1
--- /dev/null
+++ b/digikam/album/albumwatch.h
@@ -0,0 +1,90 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date        : 2011-11-07
+ * Description : Directory watch interface
+ *
+ * Copyright (C) 2011 by Marcel Wiesweg <marcel.wiesweg at gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, or (at your option)
+ * any later version.
+ *
+ * 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMWATCH_H
+#define ALBUMWATCH_H
+
+// Qt includes
+
+#include <QThread>
+#include <QString>
+
+// KDE includes
+
+#include <kurl.h>
+
+// Local includes
+
+namespace Digikam
+{
+
+class Album;
+class AlbumManager;
+class DatabaseParameters;
+
+class AlbumWatch : public QObject
+{
+    Q_OBJECT
+
+public:
+
+    AlbumWatch(AlbumManager* parent = 0);
+    ~AlbumWatch();
+
+    void clear();
+    void setDatabaseParameters(const DatabaseParameters& params);
+
+protected Q_SLOTS:
+    
+    void slotAlbumAdded(Album* album);
+    void slotAlbumAboutToBeDeleted(Album* album);
+
+    void slotFileMoved(const QString& from, const QString& to);
+    void slotFileDeleted(const QString& urlString, bool isDir);
+    void slotFileCreated(const QString& path, bool isDir);
+    void slotFileClosedAfterWrite(const QString&);
+    void slotInotifyWatchUserLimitReached();
+
+    void slotDirWatchDirty(const QString& path);
+    void slotKioFileMoved(const QString& urlFrom, const QString& urlTo);
+    void slotKioFilesDeleted(const QStringList& urls);
+    void slotKioFilesAdded(const QString& directory);
+
+private:
+
+    void rescanDirectory(const QString& dir);
+    void rescanPath(const QString& path);
+
+    void connectToKInotify();
+    void connectToKDirWatch();
+    void connectToKIO();
+    void handleKioNotification(const KUrl& url);
+
+    class AlbumWatchPriv;
+    AlbumWatchPriv* const d;
+
+};
+
+}
+
+#endif
diff --git a/libs/3rdparty/kinotify/kinotify.cpp b/libs/3rdparty/kinotify/kinotify.cpp
new file mode 100644
index 0000000..676f727
--- /dev/null
+++ b/libs/3rdparty/kinotify/kinotify.cpp
@@ -0,0 +1,517 @@
+/* This file is part of the KDE libraries
+   Copyright (C) 2007-2010 Sebastian Trueg <trueg at kde.org>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+// BASED ON kinotify.cpp, Nepomuk Core, 689b7b57f60945ca0dfd175877d3073560d73ffc, 2011/10/27
+
+#include "kinotify.h"
+
+// INotify is Linux-only
+#if defined(Q_OS_LINUX)
+
+#include <QtCore/QSocketNotifier>
+#include <QtCore/QHash>
+#include <QtCore/QDirIterator>
+#include <QtCore/QFile>
+#include <QtCore/QQueue>
+#include <QtCore/QScopedArrayPointer>
+
+#include <kdebug.h>
+
+#include <sys/inotify.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+
+namespace {
+    const int EVENT_STRUCT_SIZE = sizeof( struct inotify_event );
+
+    // we need one event to fit into the buffer, the problem is that the name
+    // is a variable length array
+    const int EVENT_BUFFER_SIZE = EVENT_STRUCT_SIZE + 1024*16;
+
+    QByteArray stripTrailingSlash( const QByteArray& path ) {
+        QByteArray p( path );
+        if ( p.endsWith( '/' ) )
+            p.truncate( p.length()-1 );
+        return p;
+    }
+
+    QByteArray concatPath( const QByteArray& p1, const QByteArray& p2 ) {
+        QByteArray p(p1);
+        if( p.isEmpty() || p[p.length()-1] != '/' )
+            p.append('/');
+        p.append(p2);
+        return p;
+    }
+}
+
+namespace Digikam
+{
+
+class KInotify::Private
+{
+public:
+    Private( KInotify* parent )
+        : watchHiddenFolders( false ),
+          m_inotifyFd( -1 ),
+          m_notifier( 0 ),
+          q( parent) {
+    }
+
+    ~Private() {
+        close();
+    }
+
+    QHash<int, QByteArray> cookies;
+    QHash<int, QByteArray> watchPathHash;
+    QHash<QByteArray, int> pathWatchHash;
+
+    /// queue of paths to install watches for
+    QQueue<QByteArray> pathsToWatch;
+
+    unsigned char eventBuffer[EVENT_BUFFER_SIZE];
+
+    // FIXME: only stored from the last addWatch call
+    WatchEvents mode;
+    WatchFlags flags;
+
+    bool watchHiddenFolders;
+
+    int inotify() {
+        if ( m_inotifyFd < 0 ) {
+            open();
+        }
+        return m_inotifyFd;
+    }
+
+    void close() {
+        delete m_notifier;
+        m_notifier = 0;
+
+        ::close( m_inotifyFd );
+        m_inotifyFd = -1;
+    }
+
+    bool addWatch( const QByteArray& path ) {
+        // we always need the unmount event to maintain our path hash
+        WatchEvents newMode = mode;
+        WatchFlags newFlags = flags;
+
+        if( !q->filterWatch( path, newMode, newFlags ) ) {
+            return true;
+        }
+        const int mask = newMode|newFlags|EventUnmount;
+
+        int wd = inotify_add_watch( inotify(), path.data(), mask );
+        if ( wd > 0 ) {
+//            kDebug() << "Successfully added watch for" << path << pathHash.count();
+            QByteArray normalized = stripTrailingSlash( path );
+            watchPathHash.insert( wd, normalized );
+            pathWatchHash.insert( normalized, wd );
+            return true;
+        }
+        else {
+            kDebug() << "Failed to create watch for" << path;
+            static bool userLimitReachedSignaled = false;
+            if ( !userLimitReachedSignaled && errno == ENOSPC ) {
+                kDebug() << "User limit reached. Please raise the inotify user watch limit.";
+                userLimitReachedSignaled = true;
+                emit q->watchUserLimitReached();
+            }
+            return false;
+        }
+    }
+
+    bool addWatchesRecursively( const QByteArray& path )
+    {
+        if ( !addWatch( path ) )
+            return false;
+
+        const int len = offsetof(struct dirent, d_name) +
+                pathconf(path.data(), _PC_NAME_MAX) + 1;
+        QScopedArrayPointer<char> entryData( new char[len] );
+        struct dirent* entry = ( struct dirent* )entryData.data();
+
+        DIR* dir = opendir( path.data() );
+        if ( dir ) {
+            struct dirent *result = 0;
+            while ( !readdir_r( dir, entry, &result ) ) {
+
+                if ( !result ) {
+                    // end of folder
+                    break;
+                }
+
+                if ( ( entry->d_type == DT_UNKNOWN ||
+                      entry->d_type == DT_DIR ) &&
+                        ( watchHiddenFolders ||
+                         qstrncmp( entry->d_name, ".", 1 ) ) &&
+                        qstrcmp( entry->d_name, "." ) &&
+                        qstrcmp( entry->d_name, ".." ) ) {
+                    bool isDir = true;
+                    QByteArray subDir = concatPath( path, QByteArray::fromRawData( entry->d_name, qstrlen( entry->d_name ) ) );
+                    if ( entry->d_type == DT_UNKNOWN ) {
+                        struct stat buf;
+                        lstat( subDir.data(), &buf );
+                        isDir = S_ISDIR( buf.st_mode );
+                    }
+
+                    if ( isDir ) {
+                        pathsToWatch.enqueue( subDir );
+                    }
+                }
+            }
+
+            closedir( dir );
+            return true;
+        }
+        else {
+            kDebug() << "Could not open dir" << path;
+            return false;
+        }
+    }
+
+    void removeWatch( int wd ) {
+        //kDebug() << wd << watchPathHash[wd];
+        pathWatchHash.remove( watchPathHash.take( wd ) );
+        inotify_rm_watch( inotify(), wd );
+    }
+
+    void _k_addWatches() {
+        // add the next batch of paths
+        for ( int i = 0; i < 100; ++i ) {
+            if ( pathsToWatch.isEmpty() ||
+                 !addWatchesRecursively( pathsToWatch.dequeue() ) ) {
+                return;
+            }
+        }
+
+        // asyncroneously add the next batch
+        if ( !pathsToWatch.isEmpty() ) {
+            QMetaObject::invokeMethod( q, "_k_addWatches", Qt::QueuedConnection );
+        }
+        else {
+            //kDebug() << "All watches installed";
+        }
+    }
+
+private:
+    void open() {
+        m_inotifyFd = inotify_init();
+        delete m_notifier;
+        if ( m_inotifyFd > 0 ) {
+            fcntl( m_inotifyFd, F_SETFD, FD_CLOEXEC );
+            kDebug() << "Successfully opened connection to inotify:" << m_inotifyFd;
+            m_notifier = new QSocketNotifier( m_inotifyFd, QSocketNotifier::Read );
+            connect( m_notifier, SIGNAL( activated( int ) ), q, SLOT( slotEvent( int ) ) );
+        }
+    }
+
+    int m_inotifyFd;
+    QSocketNotifier* m_notifier;
+
+    KInotify* q;
+};
+
+
+KInotify::KInotify( QObject* parent )
+    : QObject( parent ),
+      d( new Private( this ) )
+{
+}
+
+
+KInotify::~KInotify()
+{
+    delete d;
+}
+
+
+bool KInotify::available() const
+{
+    if( d->inotify() > 0 ) {
+        // trueg: Copied from KDirWatch.
+        struct utsname uts;
+        int major, minor, patch;
+        if ( uname(&uts) < 0 ) {
+            return false; // *shrug*
+        }
+        else if ( sscanf( uts.release, "%d.%d.%d", &major, &minor, &patch) != 3 ) {
+            return false; // *shrug*
+        }
+        else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
+            kDebug(7001) << "Can't use INotify, Linux kernel too old";
+            return false;
+        }
+
+        return true;
+    }
+    else {
+        return false;
+    }
+}
+
+
+bool KInotify::watchingPath( const QString& path ) const
+{
+    const QByteArray p( stripTrailingSlash( QFile::encodeName( path ) ) );
+    return d->pathWatchHash.contains(p);
+}
+
+
+bool KInotify::addWatch( const QString& path, WatchEvents mode, WatchFlags flags )
+{
+    //kDebug() << path;
+
+    d->mode = mode;
+    d->flags = flags;
+    d->pathsToWatch.append( QFile::encodeName( path ) );
+    d->_k_addWatches();
+    return true;
+}
+
+bool KInotify::watchDirectory(const QString& path)
+{
+    d->mode  = WatchEvents(EventMove | EventDelete | EventDeleteSelf | EventCloseWrite | EventCreate);
+    d->flags = WatchFlags();
+    return d->addWatch(QFile::encodeName(path));
+}
+
+bool KInotify::watchDirectoryAndSubdirs(const QString& path)
+{
+    return addWatch(path,
+                    WatchEvents(EventMove | EventDelete | EventDeleteSelf | EventCloseWrite | EventCreate),
+                    WatchFlags());
+}
+
+bool KInotify::removeWatch( const QString& path )
+{
+    //kDebug() << path;
+    QByteArray encodedPath = QFile::encodeName( path );
+    QHash<int, QByteArray>::iterator it = d->watchPathHash.begin();
+    while ( it != d->watchPathHash.end() ) {
+        if ( it.value().startsWith( encodedPath ) ) {
+            inotify_rm_watch( d->inotify(), it.key() );
+            d->pathWatchHash.remove(it.value());
+            it = d->watchPathHash.erase( it );
+        }
+        else {
+            ++it;
+        }
+    }
+    return true;
+}
+
+bool KInotify::removeDirectory( const QString& path )
+{
+    QByteArray encodedPath = QFile::encodeName( path );
+
+    int wd = d->pathWatchHash.value(encodedPath);
+    if (wd)
+    {
+        d->removeWatch(wd);
+    }
+    return true;
+}
+
+bool KInotify::removeAllWatches()
+{
+    foreach (int wd, d->pathWatchHash)
+    {
+        d->removeWatch(wd);
+    }
+    return true;
+}
+
+
+bool KInotify::filterWatch( const QString & path, WatchEvents & modes, WatchFlags & flags )
+{
+    Q_UNUSED( path );
+    Q_UNUSED( modes );
+    Q_UNUSED( flags );
+    return true;
+}
+
+
+void KInotify::slotEvent( int socket )
+{
+    // read at least one event
+    const int len = read( socket, d->eventBuffer, EVENT_BUFFER_SIZE );
+    int i = 0;
+    while ( i < len && len-i >= EVENT_STRUCT_SIZE  ) {
+        const struct inotify_event* event = ( struct inotify_event* )&d->eventBuffer[i];
+
+        QByteArray path;
+
+        // the event name only contains an interesting value if we get an event for a file/folder inside
+        // a watched folder. Otherwise we should ignore it
+        if ( event->mask & (EventDeleteSelf|EventMoveSelf) ) {
+            path = d->watchPathHash.value( event->wd );
+        }
+        else {
+            // we cannot use event->len here since it contains the size of the buffer and not the length of the string
+            const QByteArray eventName = QByteArray::fromRawData( event->name, qstrlen(event->name) );
+            const QByteArray hashedPath = d->watchPathHash.value( event->wd );
+            path = concatPath( hashedPath, eventName );
+        }
+
+        Q_ASSERT( !path.isEmpty() || event->mask & EventIgnored );
+        Q_ASSERT( path != "/" || event->mask & EventIgnored );
+
+        // now signal the event
+        if ( event->mask & EventAccess) {
+//            kDebug() << path << "EventAccess";
+            emit accessed( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventAttributeChange ) {
+//            kDebug() << path << "EventAttributeChange";
+            emit attributeChanged( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventCloseWrite ) {
+//            kDebug() << path << "EventCloseWrite";
+            emit closedWrite( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventCloseRead ) {
+//            kDebug() << path << "EventCloseRead";
+            emit closedRead( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventCreate ) {
+//            kDebug() << path << "EventCreate";
+            /* Disable auto-recursion
+            if ( event->mask & IN_ISDIR ) {
+                // FIXME: store the mode and flags somewhere
+                addWatch( path, d->mode, d->flags );
+            }
+            */
+            emit created( QFile::decodeName(path), event->mask & IN_ISDIR );
+        }
+        if ( event->mask & EventDeleteSelf ) {
+            //kDebug() << path << "EventDeleteSelf";
+            d->removeWatch( event->wd );
+            emit deleted( QFile::decodeName(path), event->mask & IN_ISDIR );
+        }
+        if ( event->mask & EventDelete ) {
+//            kDebug() << path << "EventDelete";
+            /* Disable auto-recursion
+            // we watch all folders recursively. Thus, folder removing is reported in DeleteSelf.
+            if( !(event->mask & IN_ISDIR) )
+                */
+                emit deleted( QFile::decodeName(path), false );
+        }
+        if ( event->mask & EventModify ) {
+//            kDebug() << path << "EventModify";
+            emit modified( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventMoveSelf ) {
+//            kDebug() << path << "EventMoveSelf";
+            kWarning() << "EventMoveSelf: THIS CASE IS NOT HANDLED PROPERLY!";
+        }
+        if ( event->mask & EventMoveFrom ) {
+//            kDebug() << path << "EventMoveFrom";
+            d->cookies[event->cookie] = path;
+        }
+        if ( event->mask & EventMoveTo ) {
+            // check if we have a cookie for this one
+            if ( d->cookies.contains( event->cookie ) ) {
+                const QByteArray oldPath = d->cookies.take(event->cookie);
+
+                // update the path cache
+                if( event->mask & IN_ISDIR ) {
+                    QHash<QByteArray, int>::iterator it = d->pathWatchHash.find(oldPath);
+                    if( it != d->pathWatchHash.end() ) {
+                        //kDebug() << oldPath << path;
+                        const int wd = it.value();
+                        d->watchPathHash[wd] = path;
+                        d->pathWatchHash.erase(it);
+                        d->pathWatchHash.insert( path, wd );
+                    }
+                }
+//                kDebug() << oldPath << "EventMoveTo" << path;
+                emit moved( QFile::encodeName(oldPath), QFile::decodeName(path) );
+            }
+            else {
+                kDebug() << "No cookie for move information of" << path;
+            }
+        }
+        if ( event->mask & EventOpen ) {
+//            kDebug() << path << "EventOpen";
+            emit opened( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventUnmount ) {
+//            kDebug() << path << "EventUnmount. removing from path hash";
+            if ( event->mask & IN_ISDIR ) {
+                d->removeWatch( event->wd );
+            }
+            emit unmounted( QFile::decodeName(path) );
+        }
+        if ( event->mask & EventQueueOverflow ) {
+            // This should not happen since we grab all events as soon as they arrive
+            kDebug() << path << "EventQueueOverflow";
+//            emit queueOverflow();
+        }
+        if ( event->mask & EventIgnored ) {
+            kDebug() << path << "EventIgnored";
+        }
+
+        i += EVENT_STRUCT_SIZE + event->len;
+    }
+
+    if ( len < 0 ) {
+        kDebug() << "Failed to read event.";
+    }
+}
+
+} // namespace Digikam
+
+#else  // defined(Q_OS_LINUX)
+
+// Dummy implementation for non-linux
+
+namespace Digikam
+{
+
+class KInotify::Private
+{
+public:
+    void _k_addWatches() {}
+};
+KInotify::KInotify(QObject* parent) : QObject(parent), d(0) {}
+KInotify::~KInotify() {}
+bool KInotify::available() const { return false; }
+bool KInotify::watchingPath( const QString&) const { return false; }
+bool KInotify::filterWatch( const QString &, WatchEvents & , WatchFlags&) { return false; }
+bool KInotify::addWatch( const QString&, WatchEvents, WatchFlags) { return false; }
+bool KInotify::removeWatch( const QString&) { return false; }
+bool KInotify::removeAllWatches() { return false; }
+bool KInotify::watchDirectory(const QString& ) { return false; }
+bool KInotify::watchDirectoryAndSubdirs(const QString&) { return false; }
+void KInotify::slotEvent( int ) { }
+
+}
+
+#endif // defined(Q_OS_LINUX)
+
+// must be at the bottom for Q_PRIVATE_SLOT
+#include "kinotify.moc"
+
diff --git a/libs/3rdparty/kinotify/kinotify.h b/libs/3rdparty/kinotify/kinotify.h
new file mode 100644
index 0000000..ec619ad
--- /dev/null
+++ b/libs/3rdparty/kinotify/kinotify.h
@@ -0,0 +1,198 @@
+/* This file is part of the KDE libraries
+   Copyright (C) 2007-2010 Sebastian Trueg <trueg at kde.org>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KINOTIFY_H_
+#define _KINOTIFY_H_
+
+#include <QtCore/QObject>
+#include <QtCore/QFlags>
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+/**
+ * A simple wrapper around inotify which only allows
+ * to add folders recursively.
+ *
+ * Warning: moving of top-level folders is not supported and
+ * results in undefined behaviour.
+ */
+class KInotify : public QObject
+{
+    Q_OBJECT
+
+public:
+    KInotify( QObject* parent = 0 );
+    virtual ~KInotify();
+
+    /**
+     * Inotify events that can occur. Use with addWatch
+     * to define the events that should be watched.
+     *
+     * These flags correspond to the native Linux inotify flags.
+     */
+    enum WatchEvent {
+        EventAccess = 0x00000001, /**< File was accessed (read, compare inotify's IN_ACCESS) */
+        EventAttributeChange = 0x00000004, /**< Metadata changed (permissions, timestamps, extended attributes, etc., compare inotify's IN_ATTRIB) */
+        EventCloseWrite = 0x00000008, /**< File opened for writing was closed (compare inotify's IN_CLOSE_WRITE) */
+        EventCloseRead = 0x00000010, /**< File not opened for writing was closed (compare inotify's IN_CLOSE_NOWRITE) */
+        EventCreate = 0x00000100, /** File/directory created in watched directory (compare inotify's IN_CREATE) */
+        EventDelete = 0x00000200, /**< File/directory deleted from watched directory (compare inotify's IN_DELETE) */
+        EventDeleteSelf = 0x00000400, /**< Watched file/directory was itself deleted (compare inotify's IN_DELETE_SELF) */
+        EventModify = 0x00000002, /**< File was modified (compare inotify's IN_MODIFY) */
+        EventMoveSelf = 0x00000800, /**< Watched file/directory was itself moved (compare inotify's IN_MOVE_SELF) */
+        EventMoveFrom = 0x00000040, /**< File moved out of watched directory (compare inotify's IN_MOVED_FROM) */
+        EventMoveTo = 0x00000080, /**< File moved into watched directory (compare inotify's IN_MOVED_TO) */
+        EventOpen = 0x00000020, /**< File was opened (compare inotify's IN_OPEN) */
+        EventUnmount = 0x00002000, /**< Backing fs was unmounted (compare inotify's IN_UNMOUNT) */
+        EventQueueOverflow = 0x00004000, /**< Event queued overflowed (compare inotify's IN_Q_OVERFLOW) */
+        EventIgnored = 0x00008000, /**< File was ignored (compare inotify's IN_IGNORED) */
+        EventMove = ( EventMoveFrom|EventMoveTo),
+        EventAll = ( EventAccess|
+                     EventAttributeChange|
+                     EventCloseWrite|
+                     EventCloseRead|
+                     EventCreate|
+                     EventDelete|
+                     EventDeleteSelf|
+                     EventModify|
+                     EventMoveSelf|
+                     EventMoveFrom|
+                     EventMoveTo|
+                     EventOpen )
+    };
+    Q_DECLARE_FLAGS(WatchEvents, WatchEvent)
+
+    /**
+     * Watch flags
+     *
+     * These flags correspond to the native Linux inotify flags.
+     */
+    enum WatchFlag {
+        FlagOnlyDir = 0x01000000, /**< Only watch the path if it is a directory (IN_ONLYDIR) */
+        FlagDoNotFollow = 0x02000000, /**< Don't follow a sym link (IN_DONT_FOLLOW) */
+        FlagOneShot = 0x80000000 /**< Only send event once (IN_ONESHOT) */
+    };
+    Q_DECLARE_FLAGS(WatchFlags, WatchFlag)
+
+    /**
+     * \return \p true if inotify is available and usable.
+     */
+    bool available() const;
+
+    bool watchingPath( const QString& path ) const;
+
+protected:
+    /**
+     * Called for every folder that is being watched.
+     * Returns true if the watch should be add or false if it should NOT be added.
+     */
+    virtual bool filterWatch( const QString & path, WatchEvents & modes, WatchFlags & flags );
+
+public Q_SLOTS:
+    virtual bool addWatch( const QString& path, WatchEvents modes, WatchFlags flags = WatchFlags() );
+    bool removeWatch( const QString& path );
+    bool removeAllWatches();
+
+    bool watchDirectory(const QString& path);
+    bool watchDirectoryAndSubdirs(const QString& path);
+    bool removeDirectory( const QString& path );
+
+Q_SIGNALS:
+    /**
+     * Emitted if a file is accessed (KInotify::EventAccess)
+     */
+    void accessed( const QString& file );
+
+    /**
+     * Emitted if file attributes are changed (KInotify::EventAttributeChange)
+     */
+    void attributeChanged( const QString& file );
+
+    /**
+     * Emitted if FIXME (KInotify::EventCloseWrite)
+     */
+    void closedWrite( const QString& file );
+
+    /**
+     * Emitted if FIXME (KInotify::EventCloseRead)
+     */
+    void closedRead( const QString& file );
+
+    /**
+     * Emitted if a new file has been created in one of the watched
+     * folders (KInotify::EventCreate)
+     */
+    void created( const QString& file, bool isDir );
+
+    /**
+     * Emitted if a watched file or folder has been deleted.
+     * This includes files in watched foldes (KInotify::EventDelete and KInotify::EventDeleteSelf)
+     */
+    void deleted( const QString& file, bool isDir );
+
+    /**
+     * Emitted if a watched file is modified (KInotify::EventModify)
+     */
+    void modified( const QString& file );
+
+    /**
+     * Emitted if a file or folder has been moved or renamed.
+     *
+     * \warning The moved signal will only be emitted if both the source and target folder
+     * are being watched.
+     */
+    void moved( const QString& oldName, const QString& newName );
+
+    /**
+     * Emitted if a file is opened (KInotify::EventOpen)
+     */
+    void opened( const QString& file );
+
+    /**
+     * Emitted if a watched path has been unmounted (KInotify::EventUnmount)
+     */
+    void unmounted( const QString& file );
+
+    /**
+     * Emitted if during updating the internal watch structures (recursive watches)
+     * the inotify user watch limit was reached.
+     *
+     * This means that not all requested paths can be watched until the user watch
+     * limit is increased.
+     *
+     * This signal will only be emitted once.
+     */
+    void watchUserLimitReached();
+
+private Q_SLOTS:
+    void slotEvent( int );
+
+private:
+    class Private;
+    Private* const d;
+
+    Q_PRIVATE_SLOT( d, void _k_addWatches() )
+};
+
+}
+
+#endif



More information about the Digikam-devel mailing list