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

Gilles Caulier caulier.gilles at gmail.com
Mon Nov 7 22:14:12 GMT 2011


Marcel,

very interesting solution. I'm sure that some bugzilla files will be
closed in the future.

KInotify do not exist in KDELibs yet ? Or do you have customized this
implementation for digiKam ?

Best

Gilles

2011/11/7 Marcel Wiesweg <marcel.wiesweg at gmx.de>:
> 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
> _______________________________________________
> Digikam-devel mailing list
> Digikam-devel at kde.org
> https://mail.kde.org/mailman/listinfo/digikam-devel
>



More information about the Digikam-devel mailing list