-rw-r--r-- | libopie2/opiecore/ofilenotify.cpp | 264 | ||||
-rw-r--r-- | libopie2/opiecore/ofilenotify.h | 93 |
2 files changed, 135 insertions, 222 deletions
diff --git a/libopie2/opiecore/ofilenotify.cpp b/libopie2/opiecore/ofilenotify.cpp index 2a9bb8c..c221e58 100644 --- a/libopie2/opiecore/ofilenotify.cpp +++ b/libopie2/opiecore/ofilenotify.cpp @@ -1,17 +1,17 @@ /* This file is part of the Opie Project - =. Copyright (C) 2004 Michael 'Mickey' Lauer <mickey@Vanille.de> + =. Copyright (C) 2004-2005 Michael 'Mickey' Lauer <mickey@Vanille.de> .=l. Copyright (C) The Opie Team <opie-devel@handhelds.org> .>+-= _;:, .> :=|. This program is free software; you can .> <`_, > . <= redistribute it and/or modify it under :`=1 )Y*s>-.-- : the terms of the GNU Library General Public .="- .-=="i, .._ License as published by the Free Software -- . .-<_> .<> Foundation; either version 2 of the License, - ._= =} : or (at your option) any later version. +- . .-<_> .<> Foundation; version 2 of the License. + ._= =} : .%`+i> _;_. .i_,=:_. -<s. 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 @@ -30,138 +30,103 @@ _;:, .> :=|. This program is free software; you can using namespace Opie::Core; /* OPIE */ /* QT */ #include <qobject.h> +#include <qsocketnotifier.h> #include <qsignal.h> #include <qintdict.h> #include <qdir.h> /* STD */ #include <sys/types.h> #include <sys/stat.h> -#include <assert.h> +#include <sys/ioctl.h> #include <fcntl.h> +#include <assert.h> #include <string.h> #include <errno.h> #include <unistd.h> static QIntDict<OFileNotification> notification_list; +QSocketNotifier* OFileNotification::_sn; +int OFileNotification::_fd = -1; + +#define INOTIFY_DEVICE "/dev/inotify" + namespace Opie { namespace Core { OFileNotification::OFileNotification( QObject* parent, const char* name ) - :QObject( parent, name ), _active( false ) + :QObject( parent, name ), _active( false ), _multi( true ) { qDebug( "OFileNotification::OFileNotification()" ); } OFileNotification::~OFileNotification() { + stop(); qDebug( "OFileNotification::~OFileNotification()" ); } bool OFileNotification::isActive() const { return _active; } -int OFileNotification::start( const QString& path, bool sshot, OFileNotificationType type ) -{ - _path = QString::null; - _fd = 0; - if ( _active ) stop(); - QString dirpath; - - // check if path exists and whether it is a file or a directory, if it exists at all - int result = ::stat( (const char*) path, &_stat ); - if ( !(type & Create) && result == -1 ) - { - qWarning( "OFileNotification::start(): Can't stat '%s': %s.", (const char*) path, strerror( errno ) ); - return -1; - } - - // if it is not a directory, we need to find out in which directory the file is - bool isDirectory = S_ISDIR( _stat.st_mode ); - if ( !isDirectory ) +int OFileNotification::watch( const QString& path, bool sshot, OFileNotificationType type ) { - int slashpos; - slashpos = path.findRev( '/' ); - if ( slashpos > 0 ) - { - _path = path; - dirpath = path.left( slashpos ); - } - } - else /* isDirectory */ - { - qWarning( "FIXME FIXME FIXME = Directory Notification Not Yet Implemented!" ); - _path = path; - dirpath = path; - assert( 0 ); - } - - int fd = ::open( (const char*) dirpath, O_RDONLY ); - if ( fd != -1 ) + if ( QFile::exists( path ) ) { if ( notification_list.isEmpty() ) { - OFileNotification::registerSignalHandler(); + OFileNotification::registerEventHandler(); } - result = ::fcntl( fd, F_SETSIG, SIGRTMIN ); - if ( result == -1 ) - { - qWarning( "OFileNotification::start(): Can't subscribe to '%s': %s.", (const char*) dirpath, strerror( errno ) ); - return -1; - } - if ( !sshot ) type = static_cast<OFileNotificationType>( (int) type | (int) Multi ); - result = ::fcntl( fd, F_NOTIFY, type ); - if ( result == -1 ) + struct inotify_watch_request iwr; + ::memset( &iwr, 0, sizeof iwr ); + iwr.name = const_cast<char*>( (const char*) path ); + iwr.mask = type; + + _wd = ::ioctl( OFileNotification::_fd, INOTIFY_WATCH, &iwr ); + + if ( _wd < 0 ) { - qWarning( "OFileNotification::start(): Can't subscribe to '%s': %s.", (const char*) dirpath, strerror( errno ) ); + qWarning( "OFileNotification::watch(): inotify can't watch '%s': %s.", (const char*) path, strerror( errno ) ); return -1; } - qDebug( "OFileNotification::start(): Subscribed for changes to %s (fd = %d, mask = 0x%0x)", (const char*) dirpath, fd, type ); - notification_list.insert( fd, this ); + + notification_list.insert( _wd, this ); + _multi = !sshot; _type = type; - _fd = fd; _active = true; - ::memset( &_stat, 0, sizeof _stat ); - ::stat( _path, &_stat ); - return fd; + qDebug( "OFileNotification::watch(): watching '%s' [wd=%d].", (const char*) path, _wd ); + return _wd; } else { - qWarning( "OFileNotification::start(): Error with path '%s': %s.", (const char*) dirpath, strerror( errno ) ); + qWarning( "OFileNotification::watch(): Can't watch '%s': %s.", (const char*) path, strerror( errno ) ); return -1; } } void OFileNotification::stop() { - if ( !_active ) return; - - int result = ::fcntl( _fd, F_NOTIFY, 0 ); - if ( result == -1 ) - { - qWarning( "OFileNotification::stop(): Can't remove subscription to '%s': %s.", (const char*) _path, strerror( errno ) ); - } - else - { - ::close( _fd ); - _type = Single; + notification_list.remove( _wd ); _path = QString::null; - _fd = 0; + _wd = 0; _active = false; + if ( notification_list.isEmpty() ) + { + OFileNotification::unregisterEventHandler(); } } OFileNotificationType OFileNotification::type() const { @@ -172,151 +137,92 @@ OFileNotificationType OFileNotification::type() const QString OFileNotification::path() const { return _path; } -int OFileNotification::fileno() const -{ - return _fd; -} - - bool OFileNotification::activate() { - if ( hasChanged() ) - { emit triggered(); _signal.activate(); + if ( !_multi ) stop(); return true; } - else - return false; -} - - -bool OFileNotification::hasChanged() -{ - bool c = false; - - struct stat newstat; - ::memset( &newstat, 0, sizeof newstat ); - int result = ::stat( _path, &newstat ); // may fail if file has been renamed or deleted. that doesn't matter :) - - qDebug( "result of newstat call is %d (%s=%d)", result, result == -1 ? strerror( errno ) : "success", errno ); - qDebug( "stat.atime = %0lx, newstat.atime = %0lx", (long)_stat.st_atime, (long)newstat.st_atime ); - qDebug( "stat.mtime = %0lx, newstat.mtime = %0lx", (long)_stat.st_mtime, (long)newstat.st_mtime ); - qDebug( "stat.ctime = %0lx, newstat.ctime = %0lx", (long)_stat.st_ctime, (long)newstat.st_ctime ); - - if ( !c && (_type & Create) && - (long)_stat.st_atime == 0 && (long)_stat.st_mtime == 0 && (long)_stat.st_ctime == 0 && - (long)newstat.st_atime > 0 && (long)newstat.st_mtime > 0 && (long)newstat.st_ctime > 0) - { - qDebug( "OFileNotification::hasChanged(): file has been created" ); - c = true; - } - if ( !c && (_type & (Delete|Rename)) && (long)newstat.st_atime == 0 && (long)newstat.st_mtime == 0 && (long)newstat.st_ctime == 0) - { - qDebug( "OFileNotification::hasChanged(): file has been deleted or renamed" ); - c = true; - } - if ( !c && (_type & Access) && (long)_stat.st_atime < (long)newstat.st_atime ) - { - qDebug( "OFileNotification::hasChanged(): atime changed" ); - c = true; - } - if ( !c && (_type & Modify) && (long)_stat.st_mtime < (long)newstat.st_mtime ) - { - qDebug( "OFileNotification::hasChanged(): mtime changed" ); - c = true; - } - if ( !c && (_type & Attrib) && (long)_stat.st_ctime < (long)newstat.st_ctime ) - { - qDebug( "OFileNotification::hasChanged(): ctime changed" ); - c = true; - } - - return c; -} void OFileNotification::singleShot( const QString& path, QObject* receiver, const char* member, OFileNotificationType type ) { OFileNotification* ofn = new OFileNotification(); ofn->_signal.connect( receiver, member ); - ofn->start( path, true, type ); + ofn->watch( path, true, type ); } -void OFileNotification::__signalHandler( int sig, siginfo_t *si, void *data ) -{ - Q_UNUSED( sig ) - Q_UNUSED( data ) - qWarning( "OFileNotification::__signalHandler(): reached." ); - int fd = si->si_fd; - OFileNotification* fn = notification_list[fd]; - if ( fn ) +void OFileNotification::inotifyEventHandler() { - // check if it really was the file (dnotify triggers on directory granularity, not file granularity) - if ( !fn->activate() ) - { - qDebug( "OFileNotification::__signalHandler(): false alarm ;) Restarting the trigger (if it was single)..." ); - if ( !(fn->type() & Multi ) ) - { - int result = ::fcntl( fn->fileno(), F_NOTIFY, fn->type() ); - if ( result == -1 ) - { - qWarning( "OFileNotification::__signalHandler(): Can't restart the trigger: %s.", strerror( errno ) ); - } - } + qWarning( "OFileNotification::__eventHandler(): reached." ); + + char buffer[16384]; + size_t buffer_i; + struct inotify_event *pevent, *event; + ssize_t r; + size_t event_size; + int count = 0; + + r = ::read(_fd, buffer, 16384); + + if ( r <= 0 ) return; - } - #if 1 - if ( !(fn->type() & Multi) ) - { - qDebug( "OFileNotification::__signalHandler(): '%d' was singleShot. Removing from list.", fd ); - notification_list.remove( fd ); - if ( notification_list.isEmpty() ) + + buffer_i = 0; + while ( buffer_i < r ) { - OFileNotification::unregisterSignalHandler(); - } - } - #endif - } + /* Parse events and queue them ! */ + pevent = (struct inotify_event *)&buffer[buffer_i]; + event_size = sizeof(struct inotify_event) + pevent->len; + qDebug( "pevent->len = %d\n", pevent->len); + + OFileNotification* fn = notification_list[ pevent->wd ]; + if ( fn ) + fn->activate(); else - { - qWarning( "OFileNotification::__signalHandler(): D'oh! Called without fd in notification_list. Race condition?" ); + assert( false ); + + //event = malloc(event_size); + //memmove(event, pevent, event_size); + //queue_enqueue(event, q); + buffer_i += event_size; + count++; } + + qDebug( "received %d events...", count ); + return; } -bool OFileNotification::registerSignalHandler() +bool OFileNotification::registerEventHandler() { - struct sigaction act; - act.sa_sigaction = OFileNotification::__signalHandler; - ::sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - if ( ::sigaction( SIGRTMIN, &act, NULL ) == -1 ) + OFileNotification::_fd = ::open( INOTIFY_DEVICE, O_RDONLY ); + if ( OFileNotification::_fd < 0 ) { - qWarning( "OFileNotification::registerSignalHandler(): couldn't register signal handler: %s", strerror( errno ) ); + qWarning( "OFileNotification::registerEventHandler(): couldn't register event handler: %s", strerror( errno ) ); return false; } - qDebug( "OFileNotification::registerSignalHandler(): done" ); + + OFileNotification::_sn = new QSocketNotifier( _fd, QSocketNotifier::Read, this, "inotify event" ); + connect( OFileNotification::_sn, SIGNAL( activated(int) ), this, SLOT( inotifyEventHandler() ) ); + + qDebug( "OFileNotification::registerEventHandler(): done" ); return true; } -void OFileNotification::unregisterSignalHandler() +void OFileNotification::unregisterEventHandler() { - struct sigaction act; - act.sa_sigaction = ( void (*)(int, siginfo_t*, void*) ) SIG_DFL; - ::sigemptyset( &act.sa_mask ); - if ( ::sigaction( SIGRTMIN, &act, NULL ) == -1 ) - { - qWarning( "OFileNotification::unregisterSignalHandler(): couldn't deregister signal handler: %s", strerror( errno ) ); - } - qDebug( "OFileNotification::unregisterSignalHandler(): done" ); + if ( OFileNotification::_fd ) + ::close( OFileNotification::_fd ); + qDebug( "OFileNotification::unregisterEventHandler(): done" ); } } } diff --git a/libopie2/opiecore/ofilenotify.h b/libopie2/opiecore/ofilenotify.h index d820f7c..e3621cf 100644 --- a/libopie2/opiecore/ofilenotify.h +++ b/libopie2/opiecore/ofilenotify.h @@ -1,17 +1,17 @@ /* This file is part of the Opie Project - =. Copyright (C) 2004 Michael 'Mickey' Lauer <mickey@Vanille.de> + =. Copyright (C) 2004-2005 Michael 'Mickey' Lauer <mickey@Vanille.de> .=l. Copyright (C) The Opie Team <opie-devel@handhelds.org> .>+-= _;:, .> :=|. This program is free software; you can .> <`_, > . <= redistribute it and/or modify it under :`=1 )Y*s>-.-- : the terms of the GNU Library General Public .="- .-=="i, .._ License as published by the Free Software -- . .-<_> .<> Foundation; either version 2 of the License, - ._= =} : or (at your option) any later version. +- . .-<_> .<> Foundation; version 2 of the License. + ._= =} : .%`+i> _;_. .i_,=:_. -<s. 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 @@ -30,19 +30,18 @@ _;:, .> :=|. This program is free software; you can #define OFILENOTIFY_H #if defined (__GNUC__) && (__GNUC__ < 3) #define _GNU_SOURCE #endif /* QT */ -#include <qobject.h> +#include <qsocketnotifier.h> #include <qsignal.h> #include <qstring.h> /* STD */ -#include <signal.h> -#include <fcntl.h> +#include "inotify.h" namespace Opie { namespace Core { /*====================================================================================== * OFileNotificationType @@ -53,40 +52,59 @@ namespace Core { * * This enumerate provides a means to specify the type of events that you are interest in. * Valid values are: * <ul> * <li>Access: The file was accessed (read) * <li>Modify The file was modified (write,truncate) - * <li>Create = The file was created in the directory - * <li>Delete = The file was unlinked from directory - * <li>Rename = The file was renamed * <li>Attrib = The file had its attributes changed (chmod,chown,chgrp) + * <li>CloseWrite = Writable file was closed + * <li>CloseNoWrite = Unwritable file was closed + * <li>Open = File was opened + * <li>MovedFrom = File was moved from X + * <li>MovedTo = File was moved to Y + * <li>DeleteSubdir = Subdir was deleted + * <li>DeleteFile = Subfile was deleted + * <li>CreateSubdir = Subdir was created + * <li>CreateFile = Subfile was created + * <li>DeleteSelf = Self was deleted + * <li>Unmount = The backing filesystem was unmounted * </ul> * **/ -enum OFileNotificationType { Single = 0x0000000, - Multi = DN_MULTISHOT, - Access = DN_ACCESS, - Modify = DN_MODIFY, - Create = DN_CREATE, - Delete = DN_DELETE, - Rename = DN_RENAME, - Attrib = DN_ATTRIB }; +enum OFileNotificationType +{ + Access = IN_ACCESS, + Modify = IN_MODIFY, + Attrib = IN_ATTRIB, + CloseWrite = IN_CLOSE_WRITE, + CloseNoWrite = IN_CLOSE_NOWRITE, + Open = IN_OPEN, + MovedFrom = IN_MOVED_FROM, + MovedTo = IN_MOVED_TO, + DeleteSubdir = IN_DELETE_SUBDIR, + DeleteFile = IN_DELETE_FILE, + CreateSubdir = IN_CREATE_SUBDIR, + CreateFile = IN_CREATE_FILE, + DeleteSelf = IN_DELETE_SELF, + Unmount = IN_UNMOUNT, + _QueueOverflow = IN_Q_OVERFLOW, /* Internal, don't use this in client code */ + _Ignored = IN_IGNORED, /* Internal, don't use this in client code */ +}; /*====================================================================================== * OFileNotification *======================================================================================*/ /** * @brief Represents a file notification * * This class allows to watch for events happening to files. - * It uses the dnotify kernel interface which is a very efficient signalling interface. + * It uses the inotify kernel interface * - * @see <file:///usr/src/linux/Documentation/dnotify.txt> + * @see http://www.kernel.org/pub/linux/kernel/people/rml/inotify/ * * @author Michael 'Mickey' Lauer <mickey@vanille.de> * **/ class OFileNotification : public QObject @@ -103,35 +121,35 @@ class OFileNotification : public QObject * bother with a timerEvent or to create a local QTimer object. * * Example: * <pre> * * #include <opie2/oapplication.h> - * #include <opie2/onitify.h> + * #include <opie2/ofilenotify.h> * using namespace Opie::Core; * * int main( int argc, char **argv ) * { * OApplication a( argc, argv, "File Notification Example" ); * OFileNotification::singleShot( "/tmp/quit", &a, SLOT(quit()), Create ); * ... // create and show your widgets * return a.exec(); * } * </pre> * - * This sample program automatically terminates when the file "/tmp/quite" has been created. + * This sample program automatically terminates when the file "/tmp/quit" has been created. * * * The @a receiver is the receiving object and the @a member is the slot. **/ static void singleShot( const QString& path, QObject* receiver, const char* member, OFileNotificationType type = Modify ); /** * Starts to watch for @a type changes to @a path. Set @a sshot to True if you want to be notified only once. * Note that in that case it may be more convenient to use @ref OFileNotification::singleShot() then. **/ - int start( const QString& path, bool sshot = false, OFileNotificationType type = Modify ); + int watch( const QString& path, bool sshot = false, OFileNotificationType type = Modify ); /** * Stop watching for file events. **/ void stop(); /** * @returns the notification type as set by @ref start(). @@ -139,52 +157,41 @@ class OFileNotification : public QObject OFileNotificationType type() const; /** * @returns the path to the file being watched by this instance. **/ QString path() const; /** - * @returns the UNIX file descriptor for the file being watched. - **/ - int fileno() const; - /** * @returns if a file is currently being watched. **/ bool isActive() const; signals: /** * This signal is emitted if an event happens of the specified type happens to the file being watched. **/ void triggered(); protected: bool activate(); - virtual bool hasChanged(); - static bool registerSignalHandler(); - static void unregisterSignalHandler(); - static void __signalHandler( int sig, siginfo_t *si, void *data ); + + private slots: + void inotifyEventHandler(); private: + bool registerEventHandler(); + void unregisterEventHandler(); + QString _path; OFileNotificationType _type; QSignal _signal; - int _fd; bool _active; - struct stat _stat; -}; - -#if 0 - -class ODirectoryNotification : public OFileNotification -{ - - public: - virtual bool hasChanged() { return true; }; + bool _multi; + static QSocketNotifier* _sn; + int _wd; // inotify watch descriptor + static int _fd; // inotify device descriptor }; -#endif - } } #endif |