diff --git a/kio/kdirwatch.cpp b/kio/kdirwatch.cpp index 4487f7a15f..10d561f5d1 100644 --- a/kio/kdirwatch.cpp +++ b/kio/kdirwatch.cpp @@ -1,426 +1,492 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Sven Radej This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include +#include #include #include #include #include #include "kdirwatch.h" #include #ifdef HAVE_FAM #include #endif +enum directoryStatus { Normal = 0, NonExistent }; + class KDirWatchPrivate { public: KDirWatchPrivate() { } ~KDirWatchPrivate() { } struct Entry { time_t m_ctime; int m_clients; + directoryStatus m_status; #ifdef HAVE_FAM FAMRequest fr; + QString watchPath; #endif }; typedef QMap EntryMap; QTimer *timer; EntryMap m_mapDirs; int freq; static KDirWatch* s_pSelf; #ifdef HAVE_FAM QSocketNotifier *sn; FAMConnection fc; bool use_fam; bool emitEvents; #endif }; #define NO_NOTIFY (time_t) 0 KDirWatch* KDirWatch::s_pSelf = 0L; // CHANGES: // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de) // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven) // May 23. 1998 - Removed static pointer - you can have more instances. // It was Needed for KRegistry. KDirWatch now emits signals and doesn't // call (or need) KFM. No more URL's - just plain paths. (sven) // Mar 29. 1998 - added docs, stop/restart for particular Dirs and // deep copies for list of dirs. (sven) // Mar 28. 1998 - Created. (sven) KDirWatch* KDirWatch::self() { if ( !s_pSelf ) s_pSelf = new KDirWatch; return s_pSelf; } KDirWatch::KDirWatch (int _freq) { d = new KDirWatchPrivate; d->timer = new QTimer(this); connect (d->timer, SIGNAL(timeout()), this, SLOT(slotRescan())); d->freq = _freq; #ifdef HAVE_FAM // It's possible that FAM server can't be started if (FAMOpen(&(d->fc)) ==0) { kdDebug(7001) << "KDirWatch: Using FAM" << endl; d->use_fam=true; d->emitEvents = true; d->sn = new QSocketNotifier( FAMCONNECTION_GETFD(&(d->fc)), QSocketNotifier::Read, this); connect( d->sn, SIGNAL(activated(int)), this, SLOT(famEventReceived()) ); } else { kdDebug(7001) << "KDirWatch: Can't use FAM" << endl; d->use_fam=false; } #endif } KDirWatch::~KDirWatch() { d->timer->stop(); // delete d->timer; timer was created with 'this' as parent! #ifdef HAVE_FAM if (d->use_fam) { FAMClose(&(d->fc)); kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl; } #endif delete d; d = 0; } +#ifdef HAVE_FAM +static QString findWatchPath(const QString &path) +{ + struct stat statbuff; + QString watchPath = path; + while (stat(QFile::encodeName(watchPath), &statbuff) == -1) + watchPath = QDir::cleanDirPath(watchPath+"/.."); + return watchPath; +} +#endif + void KDirWatch::addDir( const QString& _path ) { QString path = _path; if ( path.length() > 1 && path.right(1) == "/" ) path.truncate( path.length() - 1 ); KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.find( path ); if ( it != d->m_mapDirs.end() ) { (*it).m_clients++; return; } struct stat statbuff; - stat( QFile::encodeName(path), &statbuff ); KDirWatchPrivate::Entry e; e.m_clients = 1; - e.m_ctime = statbuff.st_ctime; + if (stat( QFile::encodeName(path), &statbuff ) == 0) + { + e.m_ctime = statbuff.st_ctime; + e.m_status = Normal; + } + else + { + e.m_ctime = NO_NOTIFY; + e.m_status = NonExistent; + } #ifdef HAVE_FAM + QString famPath = path; + if (e.m_status == NonExistent) + { + // If the directory does not exist we watch the first parent directory + // that DOES exist and wait for the directory to be created. + famPath = findWatchPath(famPath); + e.watchPath = famPath; + } if (d->use_fam) { - FAMMonitorDirectory(&(d->fc), QFile::encodeName(path), &(e.fr), 0); - // kdDebug(7001) << "KDirWatch added " << - // QFile::encodeName(path) << " -> FAMReq " << FAMREQUEST_GETREQNUM(&(e.fr)) << endl; + FAMMonitorDirectory(&(d->fc), QFile::encodeName(famPath), &(e.fr), 0); + kdDebug(7001) << "KDirWatch added " << + QFile::encodeName(path) << " -> FAMReq " << FAMREQUEST_GETREQNUM(&(e.fr)) << endl; } #endif d->m_mapDirs.insert( path, e ); #ifdef HAVE_FAM // if FAM server can't be used, fall back to good old timer... if (!d->use_fam) #endif if ( d->m_mapDirs.count() == 1 ) // if this was first entry (=timer was stopped) d->timer->start(d->freq); // then start the timer } time_t KDirWatch::ctime( const QString &_path ) { if ( d->m_mapDirs.isEmpty() ) return 0; QString path = _path; if ( path.right(1) == "/" ) path.truncate( path.length() - 1 ); KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.find( path ); if ( it == d->m_mapDirs.end() ) return 0; return (*it).m_ctime; } void KDirWatch::removeDir( const QString& _path ) { if ( d->m_mapDirs.isEmpty() ) return; QString path = _path; if ( path.right(1) == "/" ) path.truncate( path.length() - 1 ); KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.find( path ); if ( it == d->m_mapDirs.end() ) return; (*it).m_clients--; if ( (*it).m_clients > 0 ) return; #ifdef HAVE_FAM if (d->use_fam) { FAMCancelMonitor(&(d->fc), &((*it).fr) ); // kdDebug(7001) << "KDirWatch deleted: " << // QFile::encodeName(path) << " (FAMReq " << FAMREQUEST_GETREQNUM(&((*it).fr)) << ")" << endl; } #endif d->m_mapDirs.remove( it ); #ifdef HAVE_FAM if (!d->use_fam) #endif if( d->m_mapDirs.isEmpty() ) d->timer->stop(); // stop timer if list empty } bool KDirWatch::stopDirScan( const QString& _path ) { if ( d->m_mapDirs.isEmpty() ) return false; QString path = _path; if ( path.right(1) == "/" ) path.truncate( path.length() - 1 ); KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.find( path ); if ( it == d->m_mapDirs.end() ) return false; (*it).m_ctime = NO_NOTIFY; #ifdef HAVE_FAM if (d->use_fam) { FAMSuspendMonitor(&(d->fc), &((*it).fr) ); } #endif return true; } bool KDirWatch::restartDirScan( const QString& _path ) { if ( d->m_mapDirs.isEmpty() ) return false; QString path = _path; if ( path.right(1) == "/" ) path.truncate( path.length() - 1 ); KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.find( path ); if ( it == d->m_mapDirs.end() ) return false; struct stat statbuff; stat( QFile::encodeName(path), &statbuff ); (*it).m_ctime = statbuff.st_ctime; #ifdef HAVE_FAM if (d->use_fam) { FAMResumeMonitor(&(d->fc), &((*it).fr) ); } #endif return true; } void KDirWatch::stopScan() { #ifdef HAVE_FAM if (d->use_fam) d->emitEvents = false; else #endif d->timer->stop(); } void KDirWatch::startScan( bool notify, bool skippedToo ) { if (!notify) resetList(skippedToo); #ifdef HAVE_FAM if (d->use_fam) d->emitEvents = true; else #endif d->timer->start(d->freq); } // Protected: void KDirWatch::resetList( bool skippedToo ) { if ( d->m_mapDirs.isEmpty() ) return; KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.begin(); for( ; it != d->m_mapDirs.end(); ++it ) { - if ( (*it).m_ctime != NO_NOTIFY || skippedToo ) + if ( (*it).m_ctime != NO_NOTIFY || skippedToo) { struct stat statbuff; - stat( QFile::encodeName(it.key()), &statbuff ); - (*it).m_ctime = statbuff.st_ctime; + if (stat( QFile::encodeName(it.key()), &statbuff ) == 0) + { + (*it).m_ctime = statbuff.st_ctime; + (*it).m_status = Normal; + } + else + { + (*it).m_ctime = NO_NOTIFY; + (*it).m_status = NonExistent; + } } } } void KDirWatch::slotRescan() { QStringList del; KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.begin(); for( ; it != d->m_mapDirs.end(); ++it ) { struct stat statbuff; if ( stat( QFile::encodeName(it.key()), &statbuff ) == -1 ) { - kdDebug(7001) << "KDirWatch emitting deleted " << it.key() << endl; - emit deleted( it.key() ); - del.append( it.key() ); + if ((*it).m_status == Normal) + { + kdDebug(7001) << "KDirWatch emitting deleted " << it.key() << endl; + emit deleted( it.key() ); + del.append( it.key() ); + } continue; // iterator incremented } - if ( statbuff.st_ctime != (*it).m_ctime && - (*it).m_ctime != NO_NOTIFY) + if ( ((statbuff.st_ctime != (*it).m_ctime) && + ((*it).m_ctime != NO_NOTIFY)) || + ((*it).m_status == NonExistent) ) { (*it).m_ctime = statbuff.st_ctime; + (*it).m_status = Normal; kdDebug(7001) << "KDirWatch emitting dirty " << it.key() << endl; emit dirty( it.key() ); } } QStringList::Iterator it2 = del.begin(); for( ; it2 != del.end(); ++it2 ) d->m_mapDirs.remove( *it2 ); } bool KDirWatch::contains( const QString& _path ) const { QString path = _path; if ( path.right(1) == "/" ) path.truncate( path.length() - 1 ); return d->m_mapDirs.contains( path ); } void KDirWatch::setFileDirty( const QString & _file ) { emit fileDirty( _file ); } #ifdef HAVE_FAM void KDirWatch::famEventReceived() { if (!d->use_fam || !d->emitEvents) return; FAMEvent fe; if (FAMNextEvent(&(d->fc), &fe) == -1) { kdWarning(7001) << "FAM connection problem, switching to polling." << endl; d->use_fam = false; delete d->sn; d->sn = 0; startScan(false, false); return; } int reqNum = FAMREQUEST_GETREQNUM(&(fe.fr)); // Don't be too verbose ;-) if ((fe.code == FAMExists) || (fe.code == FAMEndExist)) return; kdDebug(7001) << "KDirWatch processing FAM event (" << ((fe.code == FAMChanged) ? "FAMChanged" : (fe.code == FAMDeleted) ? "FAMDeleted" : (fe.code == FAMStartExecuting) ? "FAMStartExecuting" : (fe.code == FAMStopExecuting) ? "FAMStopExecuting" : (fe.code == FAMCreated) ? "FAMCreated" : (fe.code == FAMMoved) ? "FAMMoved" : (fe.code == FAMAcknowledge) ? "FAMAcknowledge" : (fe.code == FAMExists) ? "FAMExists" : (fe.code == FAMEndExist) ? "FAMEndExist" : "Unknown Code") << ", " << &(fe.filename[0]) << ", Req " << reqNum << ")" << endl; if (fe.code == FAMDeleted) { // WABA: We ignore changes to ".directory*" files because they // tend to be generated as a result of 'dirty' events. Which // leads to a never ending stream of cause & result. if (strncmp(fe.filename, ".directory", 10) == 0) return; KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.begin(); for( ; it != d->m_mapDirs.end(); ++it ) if ( FAMREQUEST_GETREQNUM( &((*it).fr) ) == reqNum ) { + if ((*it).m_status == NonExistent) return; // Ignore if (fe.filename[0] == '/') { kdDebug(7001) << "KDirWatch emitting deleted " << it.key() << endl; emit deleted ( it.key() ); - d->m_mapDirs.remove( it.key() ); + (*it).m_status = NonExistent; +// d->m_mapDirs.remove( it.key() ); } else { kdDebug(7001) << "KDirWatch emitting dirty " << it.key() << endl; emit dirty( it.key() ); } return; } } else if ((fe.code == FAMChanged) || (fe.code == FAMCreated)) { // WABA: We ignore changes to ".directory*" files because they // tend to be generated as a result of 'dirty' events. Which // leads to a never ending stream of cause & result. if (strncmp(fe.filename, ".directory", 10) == 0) return; KDirWatchPrivate::EntryMap::Iterator it = d->m_mapDirs.begin(); - for( ; it != d->m_mapDirs.end(); ++it ) + for( ; it != d->m_mapDirs.end();++it) if ( FAMREQUEST_GETREQNUM( &((*it).fr) ) == reqNum ) { - kdDebug(7001) << "KDirWatch emitting dirty " << it.key() << endl; - emit dirty( it.key() ); + QString path = it.key(); + const KDirWatchPrivate::Entry &e = (*it); + + if (e.m_status == NonExistent) + { + // A change occurred in the parent of a non-existing directory. + // check if we need to update the directory to watch and + // if the directory we are interested in happened to be created. + QString watchPath = findWatchPath(path); + if (watchPath != e.watchPath) + { + removeDir(path); // This deletes 'e' and invalidates 'it'!! + addDir(path); + } + if (path != watchPath) + return; + // If the watch path is the same as the path, + // we have been created! + } + kdDebug(7001) << "KDirWatch emitting dirty " << path << endl; + emit dirty( path ); return; } } } #else void KDirWatch::famEventReceived() {} #endif #include "kdirwatch.moc" //sven diff --git a/kio/kdirwatch.h b/kio/kdirwatch.h index 3e63e8e38f..3b41f91422 100644 --- a/kio/kdirwatch.h +++ b/kio/kdirwatch.h @@ -1,192 +1,195 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Sven Radej This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _KDIRWATCH_H #define _KDIRWATCH_H #include #include #include #include #include #define kdirwatch KDirWatch::self() class KDirWatchPrivate; /** * Watch directories for changes. * * It uses @p stat (2) and * compares stored and actual changed time of directories. If * there is a difference it notifies about the change. Directories can be * added, removed from the list and scanning of particular directories * can be stopped and restarted. The whole class can be stopped and * restarted. Directories can be added/removed from list in * any state. * When a watched directory is changed, @ref KDirWatch will emit * the signal @ref dirty(). * * If a watched directory gets deleted, @ref KDirwatch will remove it from * the list, and emit the signal @ref deleted(). * + * It's possible to watch a directory that doesn't exist yet. + * @ref KDirWatch will emit a @ref dirty() signal when it is created. + * * @short Class for watching directory changes. * @author Sven Radej */ class KDirWatch : public QObject { Q_OBJECT public: /** * Constructor. * * Does not begin scanning until @ref startScan() * is called. Default frequency is 500 ms. The created list of * directories has deep copies. */ KDirWatch ( int freq = 500 ); /** * Destructor. * * Stops scanning and cleans up. */ ~KDirWatch(); /** * Add a directory to the list of directories to be watched. * * The list makes deep copies. */ void addDir(const QString& path); /** * Retrieve the time the directory was last changed. */ time_t ctime(const QString& path); /** * Remove a directory from the list of scanned directories. * * If specified path is not in the list this does nothing. */ void removeDir(const QString& path); /** * Stop scanning the specified path. * * The @p path is not deleted from the interal just, it is just skipped. * Call this function when you perform an huge operation * on this directory (copy/move big files or many files). When finished, * call @ref restartDirScan (path). * Returns @p false if specified path is not in list, @p true otherwise. */ bool stopDirScan(const QString& path); /** * Restart scanning for specified path. * * Resets ctime. It doesn't notify * the change (by emitted a signal), since the ctime value is reset. * * Call it when you are finished with big operations on that path, * @em and when @em you have refreshed that path. Returns @p false * if specified path is not in list, @p true otherwise. */ bool restartDirScan(const QString& path); /** * Start scanning of all dirs in list. * * If notify is @p true, all changed directories (since @ref * stopScan() call) will be notified for refresh. If notify is * @p false, all ctimes will be reset (except those who are stopped, * but only if @p skippedToo is @p false) and changed dirs won't be * notified. You can start scanning even if the list is * empty. First call should be called with @p false or else all * directories * in list will be notified. If * @p skippedToo is true, the skipped directoris (scanning of which was * stopped with @ref stopDirScan() ) will be reset and notified * for change. Otherwise, stopped directories will continue to be * unnotified. */ void startScan( bool notify=false, bool skippedToo=false ); /** * Stop scanning of all directories in internal list. * * The timer is stopped, but the list is not cleared. */ void stopScan(); bool contains( const QString& path ) const; /** @ref signal fileDirty() */ void setFileDirty( const QString & _file ); static KDirWatch* self(); signals: /** * Emitted when a directory is changed. * * The new ctime is set * before the signal is emited. */ void dirty (const QString& dir); /** * Emitted when @ref KDirWatch learns that the file * @p _file has changed. * * This happens for instance when a .desktop file * gets a new icon - but this isn't automatic, one has to call * @ref setFileDirty() for this signal to be emitted. * * Note that KDirNotify is network transparent and * broadcasts to all processes, so it sort of supersedes this. */ void fileDirty (const QString& _file); /** * Emitted when directory is deleted. * * When you receive * this signal, the directory is not yet deleted from the list. You will * receive this signal only once, because one directory cannot be * deleted more than once. Please, forget the last^H^H^H^Hprevious * sentence. */ void deleted (const QString& dir); protected: void resetList (bool reallyall); protected slots: void slotRescan(); void famEventReceived(); private: KDirWatchPrivate *d; static KDirWatch* s_pSelf; }; #endif