author | zecke <zecke> | 2004-12-26 13:32:10 (UTC) |
---|---|---|
committer | zecke <zecke> | 2004-12-26 13:32:10 (UTC) |
commit | d8f38f36ad533f93d46c8ff883c6b42f15c96c28 (patch) (side-by-side diff) | |
tree | 27f14def0ffc5d2b94cbd8aad7afbb66d532151d /library | |
parent | 4f5d6b7aff824a8a0f692cbab98b5cfe933de933 (diff) | |
download | opie-d8f38f36ad533f93d46c8ff883c6b42f15c96c28.zip opie-d8f38f36ad533f93d46c8ff883c6b42f15c96c28.tar.gz opie-d8f38f36ad533f93d46c8ff883c6b42f15c96c28.tar.bz2 |
Config is now cached
-rw-r--r-- | library/config.cpp | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/library/config.cpp b/library/config.cpp index bdfcb3f..f68c336 100644 --- a/library/config.cpp +++ b/library/config.cpp @@ -1,130 +1,303 @@ /********************************************************************** ** Copyright (C) 2000,2004 Trolltech AS. All rights reserved. ** ** This file is part of Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include <qdir.h> #include <qmessagebox.h> #if QT_VERSION <= 230 && defined(QT_NO_CODECS) #include <qtextcodec.h> #endif #include <qtextstream.h> #include <sys/stat.h> #include <sys/types.h> +#include <sys/time.h> #include <fcntl.h> #include <stdlib.h> +#include <time.h> #include <unistd.h> #define QTOPIA_INTERNAL_LANGLIST #include "config.h" #include "global.h" #include "qpeapplication.h" /* * Internal Class */ class ConfigPrivate { public: ConfigPrivate() : multilang(FALSE) {} ConfigPrivate(const ConfigPrivate& o) : trfile(o.trfile), trcontext(o.trcontext), multilang(o.multilang) {} ConfigPrivate& operator=(const ConfigPrivate& o) { trfile = o.trfile; trcontext = o.trcontext; multilang = o.multilang; return *this; } QString trfile; QCString trcontext; bool multilang; }; ///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// +#ifndef Q_OS_WIN32 + +//#define DEBUG_CONFIG_CACHE + +const int CONFIG_CACHE_SIZE = 8192; +const int CONFIG_CACHE_TIMEOUT = 1000; + +class ConfigData +{ +public: + ConfigData(const ConfigData& o) : + cfg(o.cfg), + priv(o.priv ? new ConfigPrivate(*o.priv) : 0), + mtime(o.mtime), + size(o.size), + used(o.used) + { } + + ConfigData& operator=(const ConfigData& o) + { + cfg = o.cfg; + delete priv; + priv = o.priv ? new ConfigPrivate(*o.priv) : 0; + mtime = o.mtime; + size = o.size; + used = o.used; + return *this; + } + + ConfigData() : priv(0) {} + ~ConfigData() { delete priv; } + + ConfigGroupMap cfg; + ConfigPrivate *priv; // Owned by this object + time_t mtime; + unsigned int size; + struct timeval used; +}; + +class ConfigCache : public QObject +{ +public: + ConfigCache(); + + void insert(const QString &filename, const ConfigGroupMap &cfg, const ConfigPrivate* priv); + bool find(const QString &filename, ConfigGroupMap &cfg, ConfigPrivate*& priv); + void remove(const QString &filename); + +protected: + void timerEvent(QTimerEvent *); + +private: + void removeLru(); + + QMap<QString, ConfigData> configData; + unsigned int totalsize; + int tid; +}; + +ConfigCache::ConfigCache() : QObject(), totalsize(0), tid(0) +{ +} + +void ConfigCache::insert(const QString &filename, const ConfigGroupMap &cfg, const ConfigPrivate* priv) +{ + // use stat() rather than QFileInfo for speed. + struct stat sbuf; + stat(filename.local8Bit().data(), &sbuf); + + if (sbuf.st_size < CONFIG_CACHE_SIZE/2) { + ConfigData data; + data.cfg = cfg; + data.priv = priv ? new ConfigPrivate(*priv) : 0; + data.mtime = sbuf.st_mtime; + data.size = sbuf.st_size; + gettimeofday(&data.used, 0); + + remove(filename); + configData.insert(filename, data); + + totalsize += data.size; +#ifdef DEBUG_CONFIG_CACHE + qDebug("++++++ insert %s", filename.latin1()); +#endif + } + + if (totalsize > (uint)CONFIG_CACHE_SIZE) { + // We'll delay deleting anything until later. + // This lets us grow quite large during some operations, + // but we'll be reduced to a decent size later. + // This works well with the main use case - app startup. + if (!tid) + tid = startTimer(CONFIG_CACHE_TIMEOUT); + } +} + +bool ConfigCache::find(const QString &filename, ConfigGroupMap &cfg, ConfigPrivate*& priv) +{ + QMap<QString, ConfigData>::Iterator it = configData.find(filename); + if (it != configData.end()) { + ConfigData data = *it; + // use stat() rather than QFileInfo for speed. + struct stat sbuf; + stat(filename.local8Bit().data(), &sbuf); + + if (data.mtime == sbuf.st_mtime && (int)data.size == sbuf.st_size) { + cfg = data.cfg; + delete priv; + priv = data.priv ? new ConfigPrivate(*data.priv) : 0; + gettimeofday(&data.used, 0); +#ifdef DEBUG_CONFIG_CACHE + qDebug("******* Cache hit: %s", filename.latin1()); +#endif + return TRUE; + } + } + +#ifdef DEBUG_CONFIG_CACHE + qDebug("------- Cache miss: %s", filename.latin1()); +#endif + + return FALSE; +} + +void ConfigCache::remove(const QString &filename) +{ + QMap<QString, ConfigData>::Iterator it = configData.find(filename); + if (it != configData.end()) { + totalsize -= (*it).size; + configData.remove(it); + } +} + +void ConfigCache::timerEvent(QTimerEvent *) +{ +#ifdef DEBUG_CONFIG_CACHE + qDebug( "cache size: %d", totalsize); +#endif + while (totalsize > (uint)CONFIG_CACHE_SIZE) + removeLru(); + killTimer(tid); + tid = 0; +} + +void ConfigCache::removeLru() +{ + QMap<QString, ConfigData>::Iterator it = configData.begin(); + QMap<QString, ConfigData>::Iterator lru = it; + ++it; + for (; it != configData.end(); ++it) { + if ((*it).used.tv_sec < (*lru).used.tv_sec || + ((*it).used.tv_sec == (*lru).used.tv_sec && + (*it).used.tv_usec < (*lru).used.tv_usec)) + lru = it; + } + +#ifdef DEBUG_CONFIG_CACHE + qDebug("Cache full, removing: %s", lru.key().latin1()); +#endif + totalsize -= (*lru).size; + configData.remove(lru); +} + +static ConfigCache *qpe_configCache = 0; + +#endif /* Q_OS_WIN32 */ + + +// ========================================================================== + + /*! \internal */ QString Config::configFilename(const QString& name, Domain d) { switch (d) { case File: return name; case User: { QDir dir = (QString(getenv("HOME")) + "/Settings"); if ( !dir.exists() ) mkdir(dir.path().local8Bit(),0700); return dir.path() + "/" + name + ".conf"; } } return name; } /* This cannot be made public because of binary compat issues */ void Config::read( QTextStream &s ) { #if QT_VERSION <= 230 && defined(QT_NO_CODECS) // The below should work, but doesn't in Qt 2.3.0 s.setCodec( QTextCodec::codecForMib( 106 ) ); #else s.setEncoding( QTextStream::UnicodeUTF8 ); #endif QStringList list = QStringList::split('\n', s.read() ); for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) { if ( !parse( *it ) ) { git = groups.end(); return; } } } /*! \class Config config.h \brief The Config class provides for saving application cofniguration state. You should keep a Config in existence only while you do not want others to be able to change the state. There is no locking currently, but there may be in the future. */ /*! \enum Config::ConfigGroup \internal */ /*! \enum Config::Domain \value File \value User See Config for details. */ /*! Constructs a config that will load or create a configuration with the given \a name in the given \a domain. @@ -504,182 +677,204 @@ QStringList Config::readListEntry( const QString &key, const QChar &sep ) /*! Removes all entries from the current group. */ void Config::clearGroup() { if ( git == groups.end() ) { qWarning( "no group set" ); return; } if ( !(*git).isEmpty() ) { ( *git ).clear(); changed = TRUE; } } /*! \internal */ void Config::write( const QString &fn ) { QString strNewFile; if ( !fn.isEmpty() ) filename = fn; strNewFile = filename + ".new"; QFile f( strNewFile ); if ( !f.open( IO_WriteOnly|IO_Raw ) ) { qWarning( "could not open for writing `%s'", strNewFile.latin1() ); git = groups.end(); return; } QString str; QCString cstr; QMap< QString, ConfigGroup >::Iterator g_it = groups.begin(); for ( ; g_it != groups.end(); ++g_it ) { str += "[" + g_it.key() + "]\n"; ConfigGroup::Iterator e_it = ( *g_it ).begin(); for ( ; e_it != ( *g_it ).end(); ++e_it ) str += e_it.key() + " = " + *e_it + "\n"; } cstr = str.utf8(); int total_length; total_length = f.writeBlock( cstr.data(), cstr.length() ); if ( total_length != int(cstr.length()) ) { QMessageBox::critical( 0, QObject::tr("Out of Space"), QObject::tr("There was a problem creating\nConfiguration Information \nfor this program.\n\nPlease free up some space and\ntry again.") ); f.close(); QFile::remove( strNewFile ); return; } f.close(); // now rename the file... if ( rename( strNewFile, filename ) < 0 ) { qWarning( "problem renaming the file %s to %s", strNewFile.latin1(), filename.latin1() ); QFile::remove( strNewFile ); return; } +#ifndef Q_OS_WIN32 + if (qpe_configCache) + qpe_configCache->insert(filename, groups, d); +#endif changed = FALSE; } /*! Returns whether the Config is in a valid state. */ bool Config::isValid() const { return groups.end() != git; } /*! \internal */ void Config::read() { changed = FALSE; QString readFilename(filename); if ( !QFile::exists(filename) ) { bool failed = TRUE; QFileInfo fi(filename); QString settingsDir = QDir::homeDirPath() + "/Settings"; if (fi.dirPath(TRUE) == settingsDir) { // User setting - see if there is a default in $OPIEDIR/etc/default/ QString dftlFile = QPEApplication::qpeDir() + "etc/default/" + fi.fileName(); if (QFile::exists(dftlFile)) { readFilename = dftlFile; failed = FALSE; } } if (failed) { git = groups.end(); return; } } +#ifndef Q_OS_WIN32 + if (!qpe_configCache) + qpe_configCache = new ConfigCache; + + if (qpe_configCache->find(readFilename, groups, d)) { + if ( d && d->multilang ) { + QStringList l = Global::languageList(); + lang = l[0]; + glang = l[1]; + } + git = groups.begin(); + return; + } +#endif QFile f( readFilename ); if ( !f.open( IO_ReadOnly ) ) { git = groups.end(); return; } if (f.getch()!='[') { git = groups.end(); return; } f.ungetch('['); QTextStream s( &f ); read( s ); f.close(); + +#ifndef Q_OS_WIN32 + qpe_configCache->insert(readFilename, groups, d); +#endif } /*! \internal */ bool Config::parse( const QString &l ) { QString line = l.stripWhiteSpace(); if ( line[ 0 ] == QChar( '[' ) ) { QString gname = line; gname = gname.remove( 0, 1 ); if ( gname[ (int)gname.length() - 1 ] == QChar( ']' ) ) gname = gname.remove( gname.length() - 1, 1 ); git = groups.insert( gname, ConfigGroup() ); } else if ( !line.isEmpty() ) { if ( git == groups.end() ) return FALSE; int eq = line.find( '=' ); if ( eq == -1 ) return FALSE; QString key = line.left(eq).stripWhiteSpace(); QString value = line.mid(eq+1).stripWhiteSpace(); if ( git.key() == "Translation" ) { if ( key == "File" ) { if ( !d ) d = new ConfigPrivate; d->trfile = value; } else if ( key == "Context" ) { if ( !d ) d = new ConfigPrivate; d->trcontext = value.latin1(); } else if ( key.startsWith("Comment") ) { return TRUE; // ignore comment for ts file } else { return FALSE; // Unrecognized } } int kl = key.length(); if ( kl > 1 && key[kl-1] == ']' && key[kl-2] != '[' ) { // Old-style translation (inefficient) if ( !d ) d = new ConfigPrivate; if ( !d->multilang ) { QStringList l = Global::languageList(); lang = l[0]; glang = l[1]; d->multilang = TRUE; } } ( *git ).insert( key, value ); } return TRUE; } bool Config::hasGroup( const QString& name )const { return ( groups. find ( name ) != groups. end ( )); }; QStringList Config::groupList()const { |