summaryrefslogtreecommitdiff
Side-by-side diff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--library/config.cpp195
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 {