Logo Search packages:      
Sourcecode: kat version File versions  Download package

inotify-qt.cpp

/***************************************************************************
 *   Copyright (C) 2005 by Roberto Cappuccio and the Kat team              *
 *   Roberto Cappuccio : roberto.cappuccio@gmail.com                       *
 *   Praveen Kandikuppa : praveen9@gmail.com                               *
 *                                                                         *
 *   This whole implementation has been copied from beagle                 *
 *   Credits actually go to Beagle Team for writing this implementation    *
 *   of inotify                                                            *
 *                                                                         *
 *   --------------------------------------------------------------------- *
 *   Copyright (C) 2004 Novell, Inc.                                       *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files            *
 *   (the "Software"), to deal in the Software without restriction,        *
 *   including without limitation the rights to use, copy, modify, merge,  *
 *   publish, distribute, sublicense, and/or sell copies of the Software,  *
 *   and to permit persons to whom the Software is furnished to do so,     *
 *   subject to the following conditions:                                  *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT *
 *   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  *
 *   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  *
 *   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     *
 *   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                *
 *   --------------------------------------------------------------------- *
 *                                                                         *
 *   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 of the License, 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.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.           *
 ***************************************************************************/

#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <qptrqueue.h>
#include <qthread.h>
#include <kdebug.h>
#include <klibloader.h>
#include <kgenericfactory.h>
#include <time.h>
#include <sys/poll.h>
#include "katwatcher.moc"

#include "inotify-qt.h"
#include "inotify-syscalls.h"

#define PROCFS_PREFIX           "/proc/sys/fs/inotify"

#define PROCFS_MAX_USER_DEVICES  PROCFS_PREFIX "/max_user_instances"
#define PROCFS_MAX_USER_WATCHES  PROCFS_PREFIX "/max_user_watches"
#define PROCFS_MAX_QUEUED_EVENTS PROCFS_PREFIX "/max_queued_events"

/* Inotify sysfs knobs, initialized to their pre-sysfs defaults */
static int max_user_instances = 8;
static int max_user_watches = 8192;
static int max_queued_events = 256;

#define MAX_PENDING_COUNT           5
#define PENDING_PAUSE_NANOSECONDS  2000000
#define PENDING_THRESHOLD(qsize)    ((unsigned int)(qsize) >> 1)
#define PENDING_MARGINAL_COST(p)    ((unsigned int)(1 << (p)))

static void read_int( const char* filename, int* var );
static int inotify_glue_init( void );
static int inotify_glue_watch( int fd, const char* filename, __u32 mask );
static int inotify_glue_ignore( int fd, __u32 wd );

static QMutex watched_wd_mutex, event_queue_mutex;

extern bool ddebug;

void print_mask(int mask)
{
    if (mask & IN_ACCESS)
        printf("ACCESS ");
    if (mask & IN_MODIFY)
        printf("MODIFY ");
    if (mask & IN_ATTRIB)
        printf("ATTRIB ");
    if (mask & IN_CLOSE)
        printf("CLOSE ");
    if (mask & IN_OPEN)
        printf("OPEN ");
    if (mask & IN_MOVED_FROM)
        printf("MOVE_FROM ");
    if (mask & IN_MOVED_TO)
        printf("MOVE_TO ");
    if (mask & IN_DELETE)
        printf("DELETE ");
    if (mask & IN_CREATE)
        printf("CREATE ");
    if (mask & IN_DELETE_SELF)
        printf("DELETE_SELF ");
    if (mask & IN_UNMOUNT)
        printf("UNMOUNT ");
    if (mask & IN_Q_OVERFLOW)
        printf("Q_OVERFLOW ");
    if (mask & IN_IGNORED)
        printf("IGNORED " );
    if (mask & IN_ISDIR)
        printf("(dir) ");
    else
        printf("(file) ");

    printf("0x%08x\n", mask);
}

void print_event (struct inotify_event *event)
{
    printf ("EVENT ON WD=%d\n", event->wd);
    print_mask (event->mask);
    if (event->len)
        printf ("FILENAME=%s\n", event->name);
    printf("\n");
}

/* Paranoid code to read an integer from a sysfs (well, any) file. */
static void  read_int (const char *filename, int *var)
{
    int fd, n;
    char buffer[32];
    char *buffer_endptr = NULL;

    fd = open (filename, O_RDONLY);
    if (fd == -1)
        return;
    if (read (fd, buffer, 31) > 0) {
        n = (int) strtol (buffer, &buffer_endptr, 10);
        if (*buffer != '\0' && *buffer_endptr == '\0')
            *var = n;
    }
    close (fd);
}

static int inotify_glue_init (void)
{
    static int fd = 0;
    if (fd)
        return fd;

    fd = inotify_init();
    if (fd < 0) {
          int err = errno;
          perror ("inotify_init");
          if (err == ENOSYS)
            {
                fprintf(stderr, "Inotify not supported!  You need a "
                      "2.6.13 kernel or later with CONFIG_INOTIFY "
                      "enabled.");
          }
    }
    
    read_int (PROCFS_MAX_USER_DEVICES, &max_user_instances);
    read_int (PROCFS_MAX_USER_WATCHES, &max_user_watches);
    read_int (PROCFS_MAX_QUEUED_EVENTS, &max_queued_events);

    return fd;
}


static int inotify_glue_watch (int fd, const char *filename, __u32 mask)
{
    int wd;

    wd = inotify_add_watch (fd, filename, mask);
    if (wd < 0) {
      int err = errno;
        perror ("inotify_add_watch");
      if (err == ENOSPC)
            fprintf(stderr, "Maximum watch limit hit. "
                  "Try adjusting " PROCFS_MAX_USER_WATCHES ".\n");
    }

    return wd;
}

static int inotify_glue_ignore (int fd, __u32 wd)
{
    int ret;

    ret = inotify_rm_watch (fd, wd);
    if (ret < 0)
        perror ("inotify_rm_watch");

    return ret;
}

static void inotify_snarf_events (int fd, int timeout_ms, int *nr, void **buffer_out)
{
      struct pollfd pollfd = { fd, POLLIN | POLLPRI, 0 };
      unsigned int prev_pending = 0, pending_count = 0;
      static struct inotify_event *buffer = NULL;
      static size_t buffer_size;
      int ret;

      /* Allocate our buffer the first time we try to read events. */
      if (buffer == NULL) {
            /* guess the avg len */
            buffer_size = sizeof (struct inotify_event) + 16;
            buffer_size *= max_queued_events;
            buffer = (struct inotify_event *) malloc (buffer_size);
            if (!buffer) {
                  perror ("malloc");
                  *buffer_out = NULL;
                  return;
            }
      }

      /* Set nr to 0, so it will be sure to contain something
         valid if the select times out. */
      *nr = 0;

      /* Wait for the file descriptor to be ready to read. */

      ret = poll (&pollfd, 1, timeout_ms);
      if (ret == -1) {
            perror ("poll");
            return;
      } else if (ret == 0)
            return;

      /* Reading events in groups significantly helps performance.
       * If there are some events (but not too many!) ready, wait a
       * bit more to see if more events come in. */

      while (pending_count < MAX_PENDING_COUNT) {
            struct timespec ts = {0, PENDING_PAUSE_NANOSECONDS};
            unsigned int pending;

            if (ioctl (fd, FIONREAD, &pending) == -1)
                  break;

            /* Don't wait if the number of pending events is too close
             * to the maximum queue size. */
            pending /= sizeof (struct inotify_event) + 16;  
            if (pending > PENDING_THRESHOLD (max_queued_events))
                  break;

            /* With each successive iteration, the minimum rate for
             * further sleep doubles. */
            if (pending-prev_pending < PENDING_MARGINAL_COST(pending_count))
                  break;

            prev_pending = pending;
            ++pending_count;

            nanosleep (&ts, NULL);
      }

      *nr = read (fd, buffer, buffer_size);

      *buffer_out = buffer;
}

class InotifyStopEvent : public QCustomEvent
{
    public:
        InotifyStopEvent() : QCustomEvent( 9050 ) {};
};

class InotifyQueueEvent : public QCustomEvent
{
    public:
        InotifyQueueEvent() : QCustomEvent( 9051 ) {};
};

class Watched {
    public:
        Watched()
        {
            parent = 0;
        };

        int       wd;
        QString   path;
        bool      isDirectory;
        Inotify::EventType mask;

        QPtrList<Watched> children;
        Watched   *parent;
};

class QueuedEvent {
    public:
        int       wd;
        Inotify::EventType type;
        QString    filename;
        uint      cookie;

        bool        analyzed;
        bool        dispatched;
        QDateTime   holdUntil;
        QueuedEvent *pairedMove;

        // Measured in milliseconds; 57ms is totally random
        int defaultHoldTime;

        QueuedEvent()
        {
            analyzed = false;
            dispatched = false;

            defaultHoldTime = 57;
            // Set a default holdUntil time
            holdUntil = QDateTime::currentDateTime();
            holdUntil.setTime(QTime::currentTime().addMSecs(defaultHoldTime));

            pairedMove = 0;
        };

        void addMilliSeconds (int x)
        {
            holdUntil.setTime(holdUntil.time().addMSecs(x));
        };

        void pairWith (QueuedEvent *other)
        {
            pairedMove = other;
            other->pairedMove = this;

            if (holdUntil < other->holdUntil)
                holdUntil = other->holdUntil;
            other->holdUntil = holdUntil;
        };
};

class PendingMove {
    public:
        Watched   *watch;
        QString    srcname;
        QDateTime  time;
        uint      cookie;

        PendingMove (Watched *watched, QString srcname, QDateTime time, uint cookie) {
            watch = watched;
            this->srcname = srcname;
            this->time = time;
            this->cookie = cookie;
        };
};

class SnarfThread : public QObject, public QThread {
    public:
        SnarfThread (Inotify *parent) : QObject(0, "snarfthread"), QThread(), in(parent), m_running (false)  { };
        ~SnarfThread();
        virtual void run();
    private:
        Inotify *in;
        bool m_running;

        void customEvent( QCustomEvent* );
};

class DispatchThread : public QObject, public QThread {
    public:
        DispatchThread( Inotify *parent) : QObject(0, "dispatchthread"), QThread(), in(parent), m_running (false) {};
        ~DispatchThread();

        // Clean up the queue, removing dispatched objects.
        // We assume that the called holds the event_queue lock.
        void cleanQueue();

        // Apply high-level processing to the queue.  Pair moves,
        // coalesce events, etc.
        // We assume that the caller holds the event_queue lock.
        void analyzeQueue();

        virtual void run();
    private:
        Inotify *in;
        bool m_running;
        QWaitCondition m_eventQeueueCond;

        void customEvent( QCustomEvent* );
};

Inotify::Inotify(QObject *parent, const char* name, const QStringList&)
    : KatWatcher(parent, name)
{
    if (getenv ("INOTIFY_QT_VERBOSE") != NULL)
        verbose = true;
    else
        verbose = false;


    base_mask = (EventType) (MovedFrom | MovedTo);

    inotify_glue_init ();

    inotify_fd = inotify_glue_init();
    if (inotify_fd == -1)
    {
        kdDebug(ddebug) << "Could not open /dev/inotify" << endl;
        dispatch_thread = 0;
        snarf_thread = 0;
    }
    else
    {
        // Create snarf/dispatch threads.
        dispatch_thread = new DispatchThread (this);
        snarf_thread = new SnarfThread (this);
    }
}

Inotify::~Inotify()
{
    if ( enabled() )
    {
        // Remove all events which have not been dispatched
        event_queue.clear();
        pending_move_cookies.clear();

        // Remove all watches
        QIntDictIterator<Watched> it( watched_by_wd );
        while( it.current() )
        {
            int retval = inotify_glue_ignore (inotify_fd, it.currentKey());
            if (retval < 0)
                kdDebug(ddebug) << "Attempt to ignore " << it.current()->path << " failed!" << endl;

            kdDebug(ddebug) << "Attempt to forget " << it.current()->path << endl;

            forget ( it.current() );

            // NOTE - No need to increment iterator as the current item is going to
            // be removed by a call to "forget" which will automatically increment
            // the iterator
        }

        Q_ASSERT ( watched_by_path.isEmpty() );

        delete snarf_thread;
        snarf_thread = 0;

        delete dispatch_thread;
        dispatch_thread = 0;

        /* Close inotify_fd */
        close( inotify_fd );
        inotify_fd = -1;
    }
    else
    {
        Q_ASSERT( event_queue.empty() );
        Q_ASSERT( pending_move_cookies.empty() );
        Q_ASSERT( watched_by_wd.isEmpty() );
        Q_ASSERT( watched_by_path.isEmpty() );
    }
}

bool Inotify::enabled()
{
    return ( (inotify_fd != -1) && snarf_thread && dispatch_thread );
}

bool Inotify::isWatching (const QString &path)
{
    if ( watched_by_path.find(path) != NULL )
        return true;
    else
        return false;
}

Watched* Inotify::lookup (int wd)
{
    watched_wd_mutex.lock();
    Watched *watched = watched_by_wd.find (wd);
    watched_wd_mutex.unlock();

    return watched;
}

void Inotify::forget (Watched* watched)
{
    if ( watched->parent )
        watched->parent->children.remove (watched->wd);

    watched_by_wd.remove (watched->wd);
    watched_by_path.remove (watched->path);

    delete watched;
    watched = 0;
}

int Inotify::watch (QString path, KatWatcher::EventType mask)
{
    if ( !enabled() )
    {
        kdDebug(ddebug) << "Inotify is not properly initialized " << endl;
        return (-1);
    }

    Inotify::EventType imask = (Inotify::EventType)0;

    if (mask & KatWatcher::Access)
        imask = (Inotify::EventType) (imask|Access);
    if (mask & KatWatcher::Attrib)
        imask = (Inotify::EventType) (imask|Attrib);
    if (mask & KatWatcher::Modify)
        imask = (Inotify::EventType) (imask|Modify);
    if (mask & KatWatcher::Close)
        imask = (Inotify::EventType) (imask|Close);
    if (mask & KatWatcher::Open)
        imask = (Inotify::EventType) (imask|Open);
    if (mask & KatWatcher::Move)
        imask = (Inotify::EventType) (imask|MovedFrom|MovedTo);
    if (mask & KatWatcher::Create)
        imask = (Inotify::EventType) (imask|Create);
    if (mask & KatWatcher::Delete)
        imask = (Inotify::EventType) (imask|Delete);
    if (mask & KatWatcher::DeleteSelf)
        imask = (Inotify::EventType) (imask|DeleteSelf);
    if (mask & KatWatcher::Unmount)
        imask = (Inotify::EventType) (imask|Unmount);

    return watch (path, imask);
}

int Inotify::watch (QString path, EventType mask)
{
    if ( !enabled() )
    {
        kdDebug(ddebug) << "Inotify is not properly initialized " << endl;
        return (-1);
    }

    int wd = -1;

    QFileInfo finfo(path);

    bool is_directory = false;
    if (finfo.isDir())
        is_directory = true;
    else if (! finfo.exists()) {
        kdDebug(ddebug) << "Asked to watch a non-existent file/directory " << finfo.absFilePath() << endl;
        return (-1);
    }

    watched_wd_mutex.lock();

    Watched *watched = watched_by_path [path];

    if (watched != NULL) {
        if (watched->mask == mask) {
            watched_wd_mutex.unlock();
            return watched->wd;
        }
        forget (watched);
    }

    wd = inotify_glue_watch (inotify_fd, path.latin1(), mask | base_mask);
    if (wd < 0) {
        kdDebug(ddebug) << "Attempt to watch " << finfo.absFilePath() << " failed!" << endl;
        watched_wd_mutex.unlock();
        return (-1);
    }

    watched = new Watched();
    watched->wd = wd;
    watched->path = path;
    watched->isDirectory = is_directory;
    watched->mask = mask;

    QDir parent(QString(path).append("/.."));

    if (parent.exists())
    {
        Watched *wparent = watched_by_path.find (parent.absPath());
        if (wparent != NULL) {
            watched->parent = wparent;
            watched->parent->children.append( watched );
        }
    }

    watched_by_wd.insert (watched->wd, watched);
    watched_by_path.insert (watched->path,watched);

    watched_wd_mutex.unlock();

    return wd;
}

int Inotify::ignore (const QString &fpath)
{
    int wd = 0;

    watched_wd_mutex.lock();

    Watched *watched = watched_by_path [fpath];

    // If we aren't actually watching that path,
    // silently return.
    if (watched == NULL) {
        watched_wd_mutex.unlock();
        return 0;
    }

    wd = watched->wd;

    int retval = inotify_glue_ignore (inotify_fd, wd);
    if (retval < 0) {
        kdDebug(ddebug) << "Attempt to ignore " << watched->path << " failed!" << endl;
        watched_wd_mutex.unlock();
        return (-1);
    }

    forget (watched);

    watched_wd_mutex.unlock();
    return wd;
}

void Inotify::startWatcher ()
{
    if ( !enabled() )
        return;

    if ( !snarf_thread || !dispatch_thread )
    {
        kdDebug() << "Snarf/Dispatch thread has dissappeared" << endl;
        return;
    }

    event_queue_mutex.lock();

    snarf_thread->start();
    dispatch_thread->start();

    event_queue_mutex.unlock();
}

void Inotify::stopWatcher ()
{
    if ( !enabled() )
        return;

    if ( !running() )
    {
        kdDebug() << "Snarf/Dispatch thread is not running" << endl;
        return;
    }

    QApplication::postEvent (snarf_thread, new InotifyStopEvent());
    QApplication::postEvent (dispatch_thread, new InotifyStopEvent());

    // Remove all events which have not been dispatched
    event_queue.clear();
    pending_move_cookies.clear();

    // Remove all watches
    QIntDictIterator<Watched> it( watched_by_wd );
    while( it.current() )
    {
        if (inotify_glue_ignore (inotify_fd, it.currentKey()) < 0)
            kdDebug(ddebug) << "Attempt to ignore " << it.current()->path << " failed!" << endl;

        forget ( it.current() );

        // NOTE - No need to increment iterator as the current item is going to
        // be removed by a call to "forget" which will automatically increment
        // the iterator
    }

    Q_ASSERT ( watched_by_path.isEmpty() );
}

void SnarfThread::customEvent( QCustomEvent* e )
{
    if ( e->type() == 9050 ) { //stop event
        m_running = false;
    }
}

SnarfThread::~SnarfThread()
{
    m_running = false;

    if ( !wait( 1500 ) )
    {
        terminate();
        kdDebug() << k_funcinfo << " Snarf thread did not exit successfully " << endl;
    }
}

void SnarfThread::run ()
{
    if ( !in->enabled() )
    {
        kdDebug() << " Inotify has not been properly initialized" << endl;
        return;
    }

    m_running = true;

    while (m_running) {

        // We get much better performance if we wait a tiny bit
        // between reads in order to let events build up.
        // FIXME: We need to be smarter here to avoid queue overflows.

        QThread::msleep (15);
      int event_size = sizeof (struct inotify_event);
      char *buffer = NULL;
        int nr;

      inotify_snarf_events (in->inotify_fd, 
                        1000, 
                        &nr,
                        (void **) &buffer);

        /* If we are not running anymore */
        if ( !m_running )
        {
            break;
        }

        EventQueue new_events;

        bool saw_overflow = false;
        struct inotify_event *pevent;

        while (nr > 0) {
            /* Parse events and queue them ! */
            // Read the low-level event struct from the buffer.
            pevent = (struct inotify_event *)(buffer);
          buffer = buffer + event_size;

            if ((pevent->mask & Inotify::QueueOverflow) != 0)
                saw_overflow = true;

            //print_event(pevent);
            // Now we convert our low-level event struct into a nicer object.
            QueuedEvent qe;
            qe.wd = pevent->wd;
            qe.type = (Inotify::EventType)pevent->mask;
            qe.cookie = pevent->cookie;
            if (pevent->len)
                qe.filename = pevent->name;
            else
                qe.filename = "";

            new_events.append (qe);

            nr -= event_size + pevent->len;
        }

        if (saw_overflow)
            kdDebug(ddebug) << "Inotify queue overflow!" << endl;

        event_queue_mutex.lock();
        in->event_queue += new_events;

        event_queue_mutex.unlock();

        QApplication::postEvent (in->dispatch_thread, new InotifyQueueEvent());
    }
}

// Update the watched_by_path hash and the path stored inside the watch
// in response to a move event.
void Inotify::moveWatch (Watched *watch, QString name)
{
    watched_by_path.remove (watch->path);
    watch->path = name;
    watched_by_path.insert (watch->path, watch);

    if (verbose)
        kdDebug(ddebug) <<  "*** inotify: Moved Watch to " << watch->path << endl ;
}

void Inotify::handleMove (QString srcpath, QString dstpath)
{
    Watched *start = watched_by_path.find(srcpath);   // not the same as src!
    if (start == NULL) {
        kdDebug(ddebug) << "Lookup failed for " << srcpath << endl;
        return;
    }

    // Queue our starting point, then walk its subdirectories, invoking MoveWatch() on
    // each, repeating for their subdirectories.  The relationship between start, child
    // and dstpath is fickle and important.
    QPtrQueue<Watched> queue;
    queue.enqueue (start);
    do {
        Watched *target = queue.dequeue ();
        if (target != NULL) {
            Watched *child;
            for (child = target->children.first(); child; child = target->children.next()) {
                QString name = dstpath;
                name.append("/").append(child->path.latin1()+start->path.length() + 1);
                moveWatch (child, name);
                queue.enqueue (child);
            }
        }
    } while (queue.count() > 0);

    // Ultimately, fixup the original watch, too
    moveWatch (start, dstpath);
}

void Inotify::sendEvent (Watched *watched, QString filename, QString srcpath, EventType mask)
{
    // Does the watch care about this event?
    if ((watched->mask & mask) == 0)
        return;

    if (mask & Access)
        emit onEvent (KatWatcher::Access, QString(watched->path).append("/").append(filename), QString());
    else if (mask & Attrib)
        emit onEvent (KatWatcher::Attrib, QString(watched->path).append("/").append(filename), QString());
    else if (mask & Modify)
        emit onEvent (KatWatcher::Modify, QString(watched->path).append("/").append(filename), QString());
    else if ((mask & CloseWrite) || (mask & CloseNoWrite))
        emit onEvent (KatWatcher::Close, QString(watched->path).append("/").append(filename), QString());
    else if (mask & Open)
        emit onEvent (KatWatcher::Open, QString(watched->path).append("/").append(filename), QString());
    else if (mask & MovedTo)
        emit onEvent (KatWatcher::Move, QString(watched->path).append("/").append(filename), srcpath);
    else if (mask & MovedFrom)
        Q_ASSERT ((mask & MovedFrom) != 0);
    else if (mask & Delete)
        emit onEvent (KatWatcher::Delete, QString(watched->path).append("/").append(filename), QString());
    else if (mask & Create)
        emit onEvent (KatWatcher::Create, QString(watched->path).append("/").append(filename), QString());
    else if (mask & DeleteSelf)
        emit onEvent (KatWatcher::DeleteSelf, QString(watched->path).append("/").append(filename), QString());
    else if (mask & Unmount)
        emit onEvent (KatWatcher::Unmount, QString(watched->path).append("/").append(filename), QString());
}

////////////////////////////////////////////////////////////////////////////////////////////////////

// Dispatch-time operations on the event queue

void DispatchThread::cleanQueue()
{
    EventQueueIterator eit = in->event_queue.begin();

    while (eit != in->event_queue.end()) {
        if (!(*eit).dispatched)
            break;

        if ((*eit).cookie != 0)
            in->pending_move_cookies.remove ((*eit).cookie);

        eit = in->event_queue.remove (eit);
    }
}

// Apply high-level processing to the queue.  Pair moves,
// coalesce events, etc.
// We assume that the caller holds the event_queue lock.
void DispatchThread::analyzeQueue()
{
    EventQueueIterator eit = in->event_queue.begin();

    while ((eit != in->event_queue.end()) && (*eit).analyzed )
        ++eit;

    if (eit == in->event_queue.end())
        return;

    // Walk across the unanalyzed events...
    while (eit != in->event_queue.end()) {

        // Pair off the MovedFrom and MovedTo events.
        if ((*eit).cookie != 0) {
            if (((*eit).type & Inotify::MovedFrom) != 0) {
                in->pending_move_cookies [(*eit).cookie] = *eit;

                // This increases the MovedFrom's holdUntil time,
                // giving us more time for the matching MovedTo to
                // show up.
                // (1024 ms is totally arbitrary)
                (*eit).addMilliSeconds (1024); 
            } else if (((*eit).type & Inotify::MovedTo) != 0) {
                QMap<uint,QueuedEvent>::iterator pmit = in->pending_move_cookies.find ((*eit).cookie);
                if (pmit != in->pending_move_cookies.end()) {
                    (*pmit).dispatched = true;
                    (*eit).pairedMove = &(*pmit);
                }
            }
        }
        (*eit).analyzed = true;

        ++eit;
    }
}

void DispatchThread::customEvent( QCustomEvent* e )
{
    if ( e->type() == 9050 ) { //Stop event
        m_running = false;
        m_eventQeueueCond.wakeAll();
    } else if ( e->type() == 9051 ) { //EventQueue event
        m_eventQeueueCond.wakeAll();
    }
}

DispatchThread::~DispatchThread()
{
    m_running = false;
    m_eventQeueueCond.wakeAll();

    if ( !wait( 200 ) )
        terminate();
}


void DispatchThread::run()
{
    if ( !in->enabled() )
    {
        kdDebug() << " Inotify has not been properly initialized" << endl;
        return;
    }

    EventQueueIterator eit;

    m_running = true;

    while (m_running) {
        // Until we find something we want to dispatch, we will stay
        // inside the following block of code.
        while (m_running) {
            event_queue_mutex.lock();
            cleanQueue();

            analyzeQueue();

            // Now look for an event to dispatch.
            bool holdForever = true;
            QDateTime now = QDateTime::currentDateTime();
            QDateTime min_hold_until = now.addYears(2000);

            for (eit = in->event_queue.begin(); eit != in->event_queue.end(); ++eit) {

                if ((*eit).dispatched)
                    continue;

                // It should actually be ((*eit).holdUntil <= now)
                if ((now.secsTo ((*eit).holdUntil) <= 0) && (now.time().msecsTo((*eit).holdUntil.time()) <= 100))
                    break;

                if ((*eit).holdUntil < min_hold_until) {
                    min_hold_until = (*eit).holdUntil;
                    holdForever = false;
                }
            }

            event_queue_mutex.unlock();

            // If we found an event, break out of this block and dispatch it.
            if (eit != in->event_queue.end()) {
                break;
            }

            // If we didn't find an event to dispatch, we can sleep
            // (1) until the next hold-until time
            // (2) until the lock pulses (which means something changed, so
            //     we need to check that we are still running, new events
            //     are on the queue, etc.)
            // and then we go back up and try to find something to dispatch
            // all over again.

            // Find seconds to hold -- granularity of 1 second
            int holdsecs;

            if (holdForever) {
                m_eventQeueueCond.wait();
            } else {
                holdsecs = now.secsTo(min_hold_until);
                if (holdsecs < 1)
                    m_eventQeueueCond.wait(now.time().msecsTo(min_hold_until.time()));
                else
                    m_eventQeueueCond.wait(holdsecs*1000);
            }
        }

        if ( !m_running )
            break;

        // NOTE - eit now referernces next_event

        // If "running" gets set to false, we might get a null next_event as the above
        // loop terminates
        //if (eit == in->event_queue.end())
        //    return;

        // Now we have an event, so we release the event_queue lock and do
        // the actual dispatch.
        (*eit).dispatched = true;

        Watched *watched = in->lookup ((*eit).wd);

        if (watched == NULL) {
            continue;
        }

        QString srcpath, dstpath;

        // If this event is a paired MoveTo, there is extra work to do.
        if (((*eit).type & Inotify::MovedTo) != 0) {
            Watched *paired_watched = NULL;
            if ((*eit).pairedMove != NULL) {
                paired_watched = in->lookup ((*eit).pairedMove->wd);
                // Now we junk the pairedMove and pass this off as MovedTo event which is translated as Move
                // event_queue while sending the event
                (*eit).pairedMove->dispatched = true;
            }

            if (paired_watched != NULL) {
                //Set the source path accordingly.
                srcpath = paired_watched->path;
                srcpath.append("/").append((*eit).pairedMove->filename);

                // Handle the internal rename of the directory.
                dstpath = watched->path;
                dstpath.append("/").append((*eit).filename);

                QDir dstdir(dstpath);
                if (dstdir.exists())
                    in->handleMove (srcpath, dstpath);


                // Now dispatch the event
                in->sendEvent (watched, (*eit).filename, srcpath, (*eit).type);
            } else {
                // pass this off as Create Event -- Since there is no corresponding MovedFrom Event
                //  As regards the directory being watched this is appropriate
                    in->sendEvent (watched, (*eit).filename, srcpath, Inotify::Create);
            }
        } else if (((*eit).type & Inotify::MovedFrom) != 0) {
            // pass this off as Create Event -- Since there is no corresponding MovedFrom Event
            // As regards the directory being watched this is appropriate
                in->sendEvent (watched, (*eit).filename, srcpath, Inotify::Delete);
        } else
            in->sendEvent (watched, (*eit).filename, srcpath, (*eit).type);

        // If a directory we are watching gets ignored, we need
        // to remove it from the watchedByFoo hashes.
        if (((*eit).type & Inotify::Ignored) != 0) {
            watched_wd_mutex.lock();
            in->forget (watched);
            watched_wd_mutex.unlock();
        }
    }
}

bool Inotify::running()
{
    if (snarf_thread && dispatch_thread)
        return (snarf_thread->running() && dispatch_thread->running());
    else
        return false;
}

#include "inotify-qt.moc"

Generated by  Doxygen 1.6.0   Back to index