diff options
Diffstat (limited to 'kconf_update')
-rw-r--r-- | kconf_update/Mainpage.dox | 31 | ||||
-rw-r--r-- | kconf_update/Makefile.am | 33 | ||||
-rw-r--r-- | kconf_update/README.kconf_update | 251 | ||||
-rw-r--r-- | kconf_update/kconf_update.cpp | 959 |
4 files changed, 1274 insertions, 0 deletions
diff --git a/kconf_update/Mainpage.dox b/kconf_update/Mainpage.dox new file mode 100644 index 000000000..77d486cea --- /dev/null +++ b/kconf_update/Mainpage.dox @@ -0,0 +1,31 @@ +/** @mainpage ./kconf_update + +kconf_update is a tool designed to update config files. Over time applications +sometimes need to rearrange the way configuration options are stored. Since +such an update shouldn't influence the configuration options that the user +has selected, the application must take care that the options stored in the +old way will still be honored. + +What used to happen is that the application looks up both the old and the +new configuration option and then decides which one to use. This method has +several drawbacks: +- The application may need to read more configuration files than strictly + needed, resulting in a slower startup. +- The application becomes bigger with code that will only be used once. + +kconf_update addresses these problems by offering a framework to update +configuration files without adding code to the application itself. + +See the <a href="http://websvn.kde.org/trunk/KDE/kdelibs/kconf_update/README.kconf_update?view=markup">README file</a> for more information. + +@authors +Waldo Bastian \<bastian@kde.org\> + +@maintainers +[Unknown/None] + +@licenses +@lgpl + +*/ +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/kconf_update/Makefile.am b/kconf_update/Makefile.am new file mode 100644 index 000000000..334ffdb18 --- /dev/null +++ b/kconf_update/Makefile.am @@ -0,0 +1,33 @@ +# This file is part of the KDE libraries +# Copyright (C) 2001 Waldo Bastian <bastian@kde.org> + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. + +# 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +INCLUDES= -I../kded $(all_includes) + +bin_PROGRAMS = +lib_LTLIBRARIES = +kdeinit_LTLIBRARIES = kconf_update.la + +kconf_update_la_SOURCES = kconf_update.cpp +kconf_update_la_LIBADD = $(LIB_KDECORE) +kconf_update_la_LDFLAGS = $(all_libraries) -module -avoid-version + +METASOURCES = AUTO + +# Internal +# noinst_HEADERS = kconf_update.h + diff --git a/kconf_update/README.kconf_update b/kconf_update/README.kconf_update new file mode 100644 index 000000000..14d50ad66 --- /dev/null +++ b/kconf_update/README.kconf_update @@ -0,0 +1,251 @@ +README kconf_update + +Version: 1.1 +Author: Waldo Bastian <bastian@kde.org>, <bastian@suse.com> + +What it does +============ + +kconf_update is a tool designed to update config files. Over time applications +sometimes need to rearrange the way configuration options are stored. Since +such an update shouldn't influence the configuration options that the user +has selected, the application must take care that the options stored in the +old way will still be honored. + +What used to happen is that the application looks up both the old and the +new configuration option and then decides which one to use. This method has +several drawbacks: +* The application may need to read more configuration files than strictly +needed, resulting in a slower startup. +* The application becomes bigger with code that will only be used once. + +kconf_update addresses these problems by offering a framework to update +configuration files without adding code to the application itself. + + +How it works +============ + +Applications can install so called "update files" under +$KDEDIR/share/apps/kconf_update. An update file has ".upd" as extension and +contains instructions for transferring/converting configuration information +from one place to another. + +Updating the configuration happens automatically, either when KDE gets started +or when kded detects a new update file in the above mentioned location. + +Update files are separated into sections. Each section has an Id. When a +section describing a configuration change has been applied, the Id will be +stored in the file "kconf_updaterc". This information is used to make sure +that a configuration update is only performed once. + +If you overwrite an existing update file with a new version that contains a +new section, only the update instructions from this extra section will be +performed. + +File format of the update file +============================== + +Empty lines or lines that start with '#' are considered comments. +Commas (,) are used to seperate fields and may not occur as part +of any field and all of the keywords are case-sensitive, i.e. you +cannot say "key" instead of "Key" for example. + +For the rest the file is parsed and executed sequentially from top to bottom. +Each line can contain one entry. The following entries are recognized: + + +Id=<id> + +With <id> identifying the group of update entries that follows. Once a group +of entries have been applied, their <id> is stored and this group of entries +will not be applied again. + + +File=<oldfile>,<newfile> +File=<oldfile> + +Specifies that configuration information is read from <oldfile> and written +to <newfile>. If you only specify <oldfile>, the information is read from +as well as written to <oldfile>. + +Script=<script>[,<interpreter>] + +All entries from <oldfile> are piped into <script>. The output of script +is used as new entries for <newfile>. Existing entries can be deleted by +adding lines with "# DELETE [group]key" in the output of the script. +To delete a whole group use "# DELETEGROUP [group]". + +<script> should be installed into $(kde_datadir)/kconf_update, or +kconf_update will not be able to find it. It is not portable to install +binary applications in $kde_datadir, so you have to stick with interpreted +scripts like sh or perl scripts. From KDE 3.2 onwards it's also possible +to install kconf_update applications in $(kde_bindir)/kconf_update_bin, +which opens the door to kconf_update applications that are written in C++ +and use Qt's powerful string API instead. + +A workaround for KDE 3.1.x and older is to install a .sh script in +$(kde_datadir) that contains a simple exec: + + exec "`kde-config --prefix`/bin/kconf_update_bin/my_update_app" + +This is equivalent to what KDE 3.2 can do directly, but of course the .upd +file now points to the .sh script instead of the binary application. + +If Script was issued after a "Group" command the behavior is slightly +different: +All entries from <oldfile>/<oldgroup> are piped into <script>. The output +of script is used as new entries for <newfile>/<newgroup>, unless a different +group is specified with "[group]". Existing entries can be deleted from +<oldgroup> by adding lines with "# DELETE key" in the output of the script. +To delete <oldgroup> use "# DELETEGROUP". + +<interpreter> can be something like "perl". + +Since KDE 3.3 it is also possible to have a Script without specifying +<oldfile> or <newfile>. In that case the script is run but it will not be +fed any input and its output will simply be discarded. + +ScriptArguments=<arguments> + +If specified, the arguments will be passed to <script>. +IMPORTANT: It has to be specified before Script=. + +Group=<oldgroup>,<newgroup> +Group=<oldgroup> + +Specifies that configuration information is read from the group <oldgroup> +and written to <newgroup>. If you only specify <oldgroup>, the information +is read from as well as written to <oldgroup>. You can use <default> to +specify keys that are not under any group. + +RemoveGroup=<oldgroup> + +Specifies that <oldgroup> is removed entirely. This can be used +to remove obsolete entries or to force a revert to default values. + +Options=<option1>, <option2>, .... + +With this entry you can specify options that apply to the next "Script", +"Key" or "AllKeys" entry (only to the first!). Possible options are: + +- "copy" Copy the configuration item instead of moving it. This means that + the configuration item will not be deleted from <oldfile>/<oldgroup>. + +- "overwrite" Normally, a configuration item is not moved if an item with the + new name already exists. When this option is specified the old + configuration item will overwrite any existing item. + + +Key=<oldkey>,<newkey> +Key=<oldkey> + +Specifies that configuration information is read from the key <oldkey> +and written to <newkey>. If you only specify <oldkey>, the information +is read from as well as written to <oldkey>. + + +AllKeys + +Specifies that all configuration information in the selected group should +be moved (All keys). + +AllGroups + +Specifies that all configuration information from all keys in ALL +groups should be moved. + + +RemoveKey=<oldkey> + +Specifies that <oldkey> is removed from the selected group. This can be used +to remove obsolete entries or to force a revert to default values. + + +Example update file +=================== + +# This is comment +Id=kde2.2 +File=kioslaverc,kio_httprc +Group=Proxy Settings +Key=NoProxyFor +Key=UseProxy +Key=httpProxy,Proxy +Group=Cache Settings,Cache +Key=MaxCacheSize +Key=UseCache +Group=UserAgent +AllKeys +RemoveGroup=KDE +# End of file + + +The above update file extracts config information from the file "kioslaverc" +and stores it into the file "kio_httprc". + +It reads the keys "NoProxyFor", "UseProxy" and "httpProxy" from the group +"Proxy Settings" in the "kioslaverc" file. If any of these options are present +they are written to the keys "NoProxyFor", "UseProxy" and "Proxy" (!) in +the group "Proxy Settings" in the "kio_httprc" file. + +It also reads the keys "MaxCacheSize" and "UseCache" from the group +"Cache Settings" in the "kioslaverc" file and writes this information to the +keys "MaxCacheSize" and "UseCache" in the group "Cache" (!) in the +"kio_httprc" file. + +Then it takes all keys in the "UserAgent" group of the file "kioslaverc" +and moves then to the "UserAgent" group in the "kio_httprc" file. + +Finally it removes the entire "KDE" group in the kioslaverc file. + + +Debugging and testing +===================== + +If you are developing a kconf_update script and want to test or debug it you +need to make sure kconf_update runs again after each of your changes. There +are a number of ways to achieve this. + +The easiest is to not install the kconf_update script in the first place, but +manually call it through a pipe. If you want to test the update script for +your application KHello's config file khellorc, you can test by using + + cat ~/.kde/share/config/khellorc | khello_conf_update.sh + +(assuming khello_conf_update.sh is the kconf_update script and ~/.kde is your +$KDEHOME). This is easier than making install every time, but has the obvious +downside that you need to 'parse' your script's output yourself instead of +letting kconf_update do it and check the resulting output file. + +After 'make install' the kconf_update script is run by kded, but it does so +only once. This is of course the idea behind it, but while developing it can +be a problem. You can increase the revision number for each subsequent run +of 'make install' to force a new kconf_update run, but there's a better +approach that doesn't skyrocket the version number for a mediocre debug +session. + +kded doesn't really ignore scripts that it has already run right away. +Instead it checks the affected config file every time a .upd file is added +or changed. The reason it still doesn't run again on your config file lies +in the traces kconf_update leaves behind: it adds a special config group +'[$Version]' with a key 'update_info'. This key lists all kconf_update +scripts that have already been run on this config file. Just remove your +file's entry, 'make install', and kconf_update will happily run your script +again, without you having to increase the version number. + +If you want to know what kconf_update has been up to lately, have a look +at $KDEHOME/share/apps/kconf_update/update.log + + +Common Problems +=============== + +* kconf_update refuses to update an entry +If you change the value of an entry without changing the key or file, +make sure to tell kconf_update that it should overwrite the old entry +by adding "Options=overwrite". + + +Have fun, +Waldo diff --git a/kconf_update/kconf_update.cpp b/kconf_update/kconf_update.cpp new file mode 100644 index 000000000..7bd9480a1 --- /dev/null +++ b/kconf_update/kconf_update.cpp @@ -0,0 +1,959 @@ +/* + * + * This file is part of the KDE libraries + * Copyright (c) 2001 Waldo Bastian <bastian@kde.org> + * + * $Id$ + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + **/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> + +#include <qfile.h> +#include <qtextstream.h> + +#include <kconfig.h> +#include <ksimpleconfig.h> +#include <klocale.h> +#include <kcmdlineargs.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <kinstance.h> +#include <ktempfile.h> + +static KCmdLineOptions options[] = +{ + { "debug", I18N_NOOP("Keep output results from scripts"), 0 }, + { "check <update-file>", I18N_NOOP("Check whether config file itself requires updating"), 0 }, + { "+[file]", I18N_NOOP("File to read update instructions from"), 0 }, + KCmdLineLastOption +}; + +class KonfUpdate +{ +public: + KonfUpdate(); + ~KonfUpdate(); + QStringList findUpdateFiles(bool dirtyOnly); + + QTextStream &log(); + + bool checkFile(const QString &filename); + void checkGotFile(const QString &_file, const QString &id); + + bool updateFile(const QString &filename); + + void gotId(const QString &_id); + void gotFile(const QString &_file); + void gotGroup(const QString &_group); + void gotRemoveGroup(const QString &_group); + void gotKey(const QString &_key); + void gotRemoveKey(const QString &_key); + void gotAllKeys(); + void gotAllGroups(); + void gotOptions(const QString &_options); + void gotScript(const QString &_script); + void gotScriptArguments(const QString &_arguments); + void resetOptions(); + + void copyGroup(KConfigBase *cfg1, const QString &grp1, + KConfigBase *cfg2, const QString &grp2); + +protected: + KConfig *config; + QString currentFilename; + bool skip; + bool debug; + QString id; + + QString oldFile; + QString newFile; + QString newFileName; + KConfig *oldConfig1; // Config to read keys from. + KConfig *oldConfig2; // Config to delete keys from. + KConfig *newConfig; + + QString oldGroup; + QString newGroup; + QString oldKey; + QString newKey; + + bool m_bCopy; + bool m_bOverwrite; + bool m_bUseConfigInfo; + QString m_arguments; + QTextStream *m_textStream; + QFile *m_file; + QString m_line; + int m_lineCount; +}; + +KonfUpdate::KonfUpdate() + : m_textStream(0), m_file(0) +{ + bool updateAll = false; + oldConfig1 = 0; + oldConfig2 = 0; + newConfig = 0; + + config = new KConfig("kconf_updaterc"); + + QStringList updateFiles; + KCmdLineArgs *args=KCmdLineArgs::parsedArgs(); + + debug = args->isSet("debug"); + + m_bUseConfigInfo = false; + if (args->isSet("check")) + { + m_bUseConfigInfo = true; + QString file = locate("data", "kconf_update/"+QFile::decodeName(args->getOption("check"))); + if (file.isEmpty()) + { + qWarning("File '%s' not found.", args->getOption("check").data()); + log() << "File '" << QFile::decodeName(args->getOption("check")) << "' passed on command line not found" << endl; + return; + } + updateFiles.append(file); + } + else if (args->count()) + { + for(int i = 0; i < args->count(); i++) + { + KURL url = args->url(i); + if (!url.isLocalFile()) + KCmdLineArgs::usage(i18n("Only local files are supported.")); + updateFiles.append(url.path()); + } + } + else + { + if (config->readBoolEntry("autoUpdateDisabled", false)) + return; + updateFiles = findUpdateFiles(true); + updateAll = true; + } + + for(QStringList::ConstIterator it = updateFiles.begin(); + it != updateFiles.end(); + ++it) + { + QString file = *it; + updateFile(file); + } + + config->setGroup(QString::null); + if (updateAll && !config->readBoolEntry("updateInfoAdded", false)) + { + config->writeEntry("updateInfoAdded", true); + updateFiles = findUpdateFiles(false); + + for(QStringList::ConstIterator it = updateFiles.begin(); + it != updateFiles.end(); + ++it) + { + QString file = *it; + checkFile(file); + } + updateFiles.clear(); + } +} + +KonfUpdate::~KonfUpdate() +{ + delete config; + delete m_file; + delete m_textStream; +} + +QTextStream & +KonfUpdate::log() +{ + if (!m_textStream) + { + QString file = locateLocal("data", "kconf_update/log/update.log"); + m_file = new QFile(file); + if (m_file->open(IO_WriteOnly | IO_Append)) + { + m_textStream = new QTextStream(m_file); + } + else + { + // Error + m_textStream = new QTextStream(stderr, IO_WriteOnly); + } + } + + (*m_textStream) << QDateTime::currentDateTime().toString( Qt::ISODate ) << " "; + + return *m_textStream; +} + +QStringList KonfUpdate::findUpdateFiles(bool dirtyOnly) +{ + QStringList result; + QStringList list = KGlobal::dirs()->findAllResources("data", "kconf_update/*.upd", false, true); + for(QStringList::ConstIterator it = list.begin(); + it != list.end(); + ++it) + { + QString file = *it; + struct stat buff; + if (stat( QFile::encodeName(file), &buff) == 0) + { + int i = file.findRev('/'); + if (i != -1) + file = file.mid(i+1); + config->setGroup(file); + time_t ctime = config->readUnsignedLongNumEntry("ctime"); + time_t mtime = config->readUnsignedLongNumEntry("mtime"); + if (!dirtyOnly || + (ctime != buff.st_ctime) || (mtime != buff.st_mtime)) + { + result.append(*it); + } + } + } + return result; +} + +bool KonfUpdate::checkFile(const QString &filename) +{ + currentFilename = filename; + int i = currentFilename.findRev('/'); + if (i != -1) + currentFilename = currentFilename.mid(i+1); + skip = true; + QFile file(filename); + if (!file.open(IO_ReadOnly)) + return false; + + QTextStream ts(&file); + ts.setEncoding(QTextStream::Latin1); + int lineCount = 0; + resetOptions(); + QString id; + while(!ts.atEnd()) + { + QString line = ts.readLine().stripWhiteSpace(); + lineCount++; + if (line.isEmpty() || (line[0] == '#')) + continue; + if (line.startsWith("Id=")) + id = currentFilename+":"+line.mid(3); + else if (line.startsWith("File=")) + checkGotFile(line.mid(5), id); + } + + return true; +} + +void KonfUpdate::checkGotFile(const QString &_file, const QString &id) +{ + QString file; + int i = _file.find(','); + if (i == -1) + { + file = _file.stripWhiteSpace(); + } + else + { + file = _file.mid(i+1).stripWhiteSpace(); + } + +// qDebug("File %s, id %s", file.latin1(), id.latin1()); + + KSimpleConfig cfg(file); + cfg.setGroup("$Version"); + QStringList ids = cfg.readListEntry("update_info"); + if (ids.contains(id)) + return; + ids.append(id); + cfg.writeEntry("update_info", ids); +} + +/** + * Syntax: + * # Comment + * Id=id + * File=oldfile[,newfile] + * AllGroups + * Group=oldgroup[,newgroup] + * RemoveGroup=oldgroup + * Options=[copy,][overwrite,] + * Key=oldkey[,newkey] + * RemoveKey=ldkey + * AllKeys + * Keys= [Options](AllKeys|(Key|RemoveKey)*) + * ScriptArguments=arguments + * Script=scriptfile[,interpreter] + * + * Sequence: + * (Id,(File(Group,Keys)*)*)* + **/ +bool KonfUpdate::updateFile(const QString &filename) +{ + currentFilename = filename; + int i = currentFilename.findRev('/'); + if (i != -1) + currentFilename = currentFilename.mid(i+1); + skip = true; + QFile file(filename); + if (!file.open(IO_ReadOnly)) + return false; + + log() << "Checking update-file '" << filename << "' for new updates" << endl; + + QTextStream ts(&file); + ts.setEncoding(QTextStream::Latin1); + m_lineCount = 0; + resetOptions(); + while(!ts.atEnd()) + { + m_line = ts.readLine().stripWhiteSpace(); + m_lineCount++; + if (m_line.isEmpty() || (m_line[0] == '#')) + continue; + if (m_line.startsWith("Id=")) + gotId(m_line.mid(3)); + else if (skip) + continue; + else if (m_line.startsWith("Options=")) + gotOptions(m_line.mid(8)); + else if (m_line.startsWith("File=")) + gotFile(m_line.mid(5)); + else if (m_line.startsWith("Group=")) + gotGroup(m_line.mid(6)); + else if (m_line.startsWith("RemoveGroup=")) + { + gotRemoveGroup(m_line.mid(12)); + resetOptions(); + } + else if (m_line.startsWith("Script=")) + { + gotScript(m_line.mid(7)); + resetOptions(); + } + else if (m_line.startsWith("ScriptArguments=")) + gotScriptArguments(m_line.mid(16)); + else if (m_line.startsWith("Key=")) + { + gotKey(m_line.mid(4)); + resetOptions(); + } + else if (m_line.startsWith("RemoveKey=")) + { + gotRemoveKey(m_line.mid(10)); + resetOptions(); + } + else if (m_line == "AllKeys") + { + gotAllKeys(); + resetOptions(); + } + else if (m_line == "AllGroups") + { + gotAllGroups(); + resetOptions(); + } + else + { + log() << currentFilename << ": parse error in line " << m_lineCount << " : '" << m_line << "'" << endl; + } + } + // Flush. + gotId(QString::null); + + struct stat buff; + stat( QFile::encodeName(filename), &buff); + config->setGroup(currentFilename); + config->writeEntry("ctime", buff.st_ctime); + config->writeEntry("mtime", buff.st_mtime); + config->sync(); + return true; +} + + + +void KonfUpdate::gotId(const QString &_id) +{ + if (!id.isEmpty() && !skip) + { + config->setGroup(currentFilename); + QStringList ids = config->readListEntry("done"); + if (!ids.contains(id)) + { + ids.append(id); + config->writeEntry("done", ids); + config->sync(); + } + } + + // Flush pending changes + gotFile(QString::null); + + config->setGroup(currentFilename); + QStringList ids = config->readListEntry("done"); + if (!_id.isEmpty()) + { + if (ids.contains(_id)) + { + //qDebug("Id '%s' was already in done-list", _id.latin1()); + if (!m_bUseConfigInfo) + { + skip = true; + return; + } + } + skip = false; + id = _id; + if (m_bUseConfigInfo) + log() << currentFilename << ": Checking update '" << _id << "'" << endl; + else + log() << currentFilename << ": Found new update '" << _id << "'" << endl; + } +} + +void KonfUpdate::gotFile(const QString &_file) +{ + // Reset group + gotGroup(QString::null); + + if (!oldFile.isEmpty()) + { + // Close old file. + delete oldConfig1; + oldConfig1 = 0; + + oldConfig2->setGroup("$Version"); + QStringList ids = oldConfig2->readListEntry("update_info"); + QString cfg_id = currentFilename + ":" + id; + if (!ids.contains(cfg_id) && !skip) + { + ids.append(cfg_id); + oldConfig2->writeEntry("update_info", ids); + } + oldConfig2->sync(); + delete oldConfig2; + oldConfig2 = 0; + + QString file = locateLocal("config", oldFile); + struct stat s_buf; + if (stat(QFile::encodeName(file), &s_buf) == 0) + { + if (s_buf.st_size == 0) + { + // Delete empty file. + unlink(QFile::encodeName(file)); + } + } + + oldFile = QString::null; + } + if (!newFile.isEmpty()) + { + // Close new file. + newConfig->setGroup("$Version"); + QStringList ids = newConfig->readListEntry("update_info"); + QString cfg_id = currentFilename + ":" + id; + if (!ids.contains(cfg_id) && !skip) + { + ids.append(cfg_id); + newConfig->writeEntry("update_info", ids); + } + newConfig->sync(); + delete newConfig; + newConfig = 0; + + newFile = QString::null; + } + newConfig = 0; + + int i = _file.find(','); + if (i == -1) + { + oldFile = _file.stripWhiteSpace(); + } + else + { + oldFile = _file.left(i).stripWhiteSpace(); + newFile = _file.mid(i+1).stripWhiteSpace(); + if (oldFile == newFile) + newFile = QString::null; + } + + if (!oldFile.isEmpty()) + { + oldConfig2 = new KConfig(oldFile, false, false); + QString cfg_id = currentFilename + ":" + id; + oldConfig2->setGroup("$Version"); + QStringList ids = oldConfig2->readListEntry("update_info"); + if (ids.contains(cfg_id)) + { + skip = true; + newFile = QString::null; + log() << currentFilename << ": Skipping update '" << id << "'" << endl; + } + + if (!newFile.isEmpty()) + { + newConfig = new KConfig(newFile, false, false); + newConfig->setGroup("$Version"); + ids = newConfig->readListEntry("update_info"); + if (ids.contains(cfg_id)) + { + skip = true; + log() << currentFilename << ": Skipping update '" << id << "'" << endl; + } + } + else + { + newConfig = oldConfig2; + } + + oldConfig1 = new KConfig(oldFile, true, false); + } + else + { + newFile = QString::null; + } + newFileName = newFile; + if (newFileName.isEmpty()) + newFileName = oldFile; +} + +void KonfUpdate::gotGroup(const QString &_group) +{ + int i = _group.find(','); + if (i == -1) + { + oldGroup = _group.stripWhiteSpace(); + newGroup = oldGroup; + } + else + { + oldGroup = _group.left(i).stripWhiteSpace(); + newGroup = _group.mid(i+1).stripWhiteSpace(); + } +} + +void KonfUpdate::gotRemoveGroup(const QString &_group) +{ + oldGroup = _group.stripWhiteSpace(); + + if (!oldConfig1) + { + log() << currentFilename << ": !! RemoveGroup without previous File specification in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + + if (!oldConfig1->hasGroup(oldGroup)) + return; + // Delete group. + oldConfig2->deleteGroup(oldGroup, true); + log() << currentFilename << ": RemoveGroup removes group " << oldFile << ":" << oldGroup << endl; +} + + +void KonfUpdate::gotKey(const QString &_key) +{ + int i = _key.find(','); + if (i == -1) + { + oldKey = _key.stripWhiteSpace(); + newKey = oldKey; + } + else + { + oldKey = _key.left(i).stripWhiteSpace(); + newKey = _key.mid(i+1).stripWhiteSpace(); + } + + if (oldKey.isEmpty() || newKey.isEmpty()) + { + log() << currentFilename << ": !! Key specifies invalid key in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + if (!oldConfig1) + { + log() << currentFilename << ": !! Key without previous File specification in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + oldConfig1->setGroup(oldGroup); + if (!oldConfig1->hasKey(oldKey)) + return; + QString value = oldConfig1->readEntry(oldKey); + newConfig->setGroup(newGroup); + if (!m_bOverwrite && newConfig->hasKey(newKey)) + { + log() << currentFilename << ": Skipping " << newFileName << ":" << newGroup << ":" << newKey << ", already exists."<< endl; + return; + } + log() << currentFilename << ": Updating " << newFileName << ":" << newGroup << ":" << newKey << " to '" << value << "'" << endl; + newConfig->writeEntry(newKey, value); + + if (m_bCopy) + return; // Done. + + // Delete old entry + if ((oldConfig2 == newConfig) && + (oldGroup == newGroup) && + (oldKey == newKey)) + return; // Don't delete! + oldConfig2->setGroup(oldGroup); + oldConfig2->deleteEntry(oldKey, false); + log() << currentFilename << ": Removing " << oldFile << ":" << oldGroup << ":" << oldKey << ", moved." << endl; + if (oldConfig2->deleteGroup(oldGroup, false)) { // Delete group if empty. + log() << currentFilename << ": Removing empty group " << oldFile << ":" << oldGroup << endl; + } +} + +void KonfUpdate::gotRemoveKey(const QString &_key) +{ + oldKey = _key.stripWhiteSpace(); + + if (oldKey.isEmpty()) + { + log() << currentFilename << ": !! RemoveKey specifies invalid key in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + + if (!oldConfig1) + { + log() << currentFilename << ": !! Key without previous File specification in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + + oldConfig1->setGroup(oldGroup); + if (!oldConfig1->hasKey(oldKey)) + return; + log() << currentFilename << ": RemoveKey removes " << oldFile << ":" << oldGroup << ":" << oldKey << endl; + + // Delete old entry + oldConfig2->setGroup(oldGroup); + oldConfig2->deleteEntry(oldKey, false); + if (oldConfig2->deleteGroup(oldGroup, false)) { // Delete group if empty. + log() << currentFilename << ": Removing empty group " << oldFile << ":" << oldGroup << endl; + } +} + +void KonfUpdate::gotAllKeys() +{ + if (!oldConfig1) + { + log() << currentFilename << ": !! AllKeys without previous File specification in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + + QMap<QString, QString> list = oldConfig1->entryMap(oldGroup); + for(QMap<QString, QString>::Iterator it = list.begin(); + it != list.end(); ++it) + { + gotKey(it.key()); + } +} + +void KonfUpdate::gotAllGroups() +{ + if (!oldConfig1) + { + log() << currentFilename << ": !! AllGroups without previous File specification in line " << m_lineCount << " : '" << m_line << "'" << endl; + return; + } + + QStringList allGroups = oldConfig1->groupList(); + for(QStringList::ConstIterator it = allGroups.begin(); + it != allGroups.end(); ++it) + { + oldGroup = *it; + newGroup = oldGroup; + gotAllKeys(); + } +} + +void KonfUpdate::gotOptions(const QString &_options) +{ + QStringList options = QStringList::split(',', _options); + for(QStringList::ConstIterator it = options.begin(); + it != options.end(); + ++it) + { + if ( (*it).lower().stripWhiteSpace() == "copy") + m_bCopy = true; + + if ( (*it).lower().stripWhiteSpace() == "overwrite") + m_bOverwrite = true; + } +} + +void KonfUpdate::copyGroup(KConfigBase *cfg1, const QString &grp1, + KConfigBase *cfg2, const QString &grp2) +{ + cfg1->setGroup(grp1); + cfg2->setGroup(grp2); + QMap<QString, QString> list = cfg1->entryMap(grp1); + for(QMap<QString, QString>::Iterator it = list.begin(); + it != list.end(); ++it) + { + cfg2->writeEntry(it.key(), cfg1->readEntry(it.key())); + } +} + +void KonfUpdate::gotScriptArguments(const QString &_arguments) +{ + m_arguments = _arguments; +} + +void KonfUpdate::gotScript(const QString &_script) +{ + QString script, interpreter; + int i = _script.find(','); + if (i == -1) + { + script = _script.stripWhiteSpace(); + } + else + { + script = _script.left(i).stripWhiteSpace(); + interpreter = _script.mid(i+1).stripWhiteSpace(); + } + + + if (script.isEmpty()) + { + log() << currentFilename << ": !! Script fails to specifiy filename in line " << m_lineCount << " : '" << m_line << "'" << endl; + skip = true; + return; + } + + + + QString path = locate("data","kconf_update/"+script); + if (path.isEmpty()) + { + if (interpreter.isEmpty()) + path = locate("lib", "kconf_update_bin/"+script); + + if (path.isEmpty()) + { + log() << currentFilename << ": !! Script '" << script << "' not found in line " << m_lineCount << " : '" << m_line << "'" << endl; + skip = true; + return; + } + } + + if( !m_arguments.isNull()) + log() << currentFilename << ": Running script '" << script << "' with arguments '" << m_arguments << "'" << endl; + else + log() << currentFilename << ": Running script '" << script << "'" << endl; + + QString cmd; + if (interpreter.isEmpty()) + cmd = path; + else + cmd = interpreter + " " + path; + + if( !m_arguments.isNull()) + { + cmd += ' '; + cmd += m_arguments; + } + + KTempFile tmp1; + tmp1.setAutoDelete(true); + KTempFile tmp2; + tmp2.setAutoDelete(true); + KTempFile tmp3; + tmp3.setAutoDelete(true); + + int result; + if (oldConfig1) + { + if (debug) + { + tmp1.setAutoDelete(false); + log() << "Script input stored in " << tmp1.name() << endl; + } + KSimpleConfig cfg(tmp1.name()); + + if (oldGroup.isEmpty()) + { + // Write all entries to tmpFile; + QStringList grpList = oldConfig1->groupList(); + for(QStringList::ConstIterator it = grpList.begin(); + it != grpList.end(); + ++it) + { + copyGroup(oldConfig1, *it, &cfg, *it); + } + } + else + { + copyGroup(oldConfig1, oldGroup, &cfg, QString::null); + } + cfg.sync(); + result = system(QFile::encodeName(QString("%1 < %2 > %3 2> %4").arg(cmd, tmp1.name(), tmp2.name(), tmp3.name()))); + } + else + { + // No config file + result = system(QFile::encodeName(QString("%1 2> %2").arg(cmd, tmp3.name()))); + } + + // Copy script stderr to log file + { + QFile output(tmp3.name()); + if (output.open(IO_ReadOnly)) + { + QTextStream ts( &output ); + ts.setEncoding(QTextStream::UnicodeUTF8); + while(!ts.atEnd()) + { + QString line = ts.readLine(); + log() << "[Script] " << line << endl; + } + } + } + + if (result) + { + log() << currentFilename << ": !! An error occured while running '" << cmd << "'" << endl; + return; + } + + if (!oldConfig1) + return; // Nothing to merge + + if (debug) + { + tmp2.setAutoDelete(false); + log() << "Script output stored in " << tmp2.name() << endl; + } + + // Deleting old entries + { + QString group = oldGroup; + QFile output(tmp2.name()); + if (output.open(IO_ReadOnly)) + { + QTextStream ts( &output ); + ts.setEncoding(QTextStream::UnicodeUTF8); + while(!ts.atEnd()) + { + QString line = ts.readLine(); + if (line.startsWith("[")) + { + int j = line.find(']')+1; + if (j > 0) + group = line.mid(1, j-2); + } + else if (line.startsWith("# DELETE ")) + { + QString key = line.mid(9); + if (key[0] == '[') + { + int j = key.find(']')+1; + if (j > 0) + { + group = key.mid(1,j-2); + key = key.mid(j); + } + } + oldConfig2->setGroup(group); + oldConfig2->deleteEntry(key, false); + log() << currentFilename << ": Script removes " << oldFile << ":" << group << ":" << key << endl; + if (oldConfig2->deleteGroup(group, false)) { // Delete group if empty. + log() << currentFilename << ": Removing empty group " << oldFile << ":" << group << endl; + } + } + else if (line.startsWith("# DELETEGROUP")) + { + QString key = line.mid(13).stripWhiteSpace(); + if (key[0] == '[') + { + int j = key.find(']')+1; + if (j > 0) + { + group = key.mid(1,j-2); + } + } + if (oldConfig2->deleteGroup(group, true)) { // Delete group + log() << currentFilename << ": Script removes group " << oldFile << ":" << group << endl; + } + } + } + } + } + + // Merging in new entries. + m_bCopy = true; + { + KConfig *saveOldConfig1 = oldConfig1; + QString saveOldGroup = oldGroup; + QString saveNewGroup = newGroup; + oldConfig1 = new KConfig(tmp2.name(), true, false); + + // For all groups... + QStringList grpList = oldConfig1->groupList(); + for(QStringList::ConstIterator it = grpList.begin(); + it != grpList.end(); + ++it) + { + oldGroup = *it; + if (oldGroup != "<default>") + { + newGroup = oldGroup; + } + gotAllKeys(); // Copy all keys + } + delete oldConfig1; + oldConfig1 = saveOldConfig1; + oldGroup = saveOldGroup; + newGroup = saveNewGroup; + } +} + +void KonfUpdate::resetOptions() +{ + m_bCopy = false; + m_bOverwrite = false; + m_arguments = QString::null; +} + + +extern "C" KDE_EXPORT int kdemain(int argc, char **argv) +{ + KAboutData aboutData("kconf_update", I18N_NOOP("KConf Update"), + "1.0.2", + I18N_NOOP("KDE Tool for updating user configuration files"), + KAboutData::License_GPL, + "(c) 2001, Waldo Bastian"); + + aboutData.addAuthor("Waldo Bastian", 0, "bastian@kde.org"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions(options); + + KInstance instance(&aboutData); + + KonfUpdate konfUpdate; + + return 0; +} |