21 #include <sys/types.h>
27 #include <QtCore/QDate>
28 #include <QtCore/QFile>
29 #include <QtCore/QTextStream>
30 #include <QtCore/QTextCodec>
32 #include <QtCore/QDir>
36 #include <kconfiggroup.h>
39 #include <kcmdlineargs.h>
41 #include <kstandarddirs.h>
42 #include <kaboutdata.h>
43 #include <kcomponentdata.h>
44 #include <ktemporaryfile.h>
54 QStringList findUpdateFiles(
bool dirtyOnly);
57 QTextStream &logFileError();
59 bool checkFile(
const QString &filename);
60 void checkGotFile(
const QString &_file,
const QString &
id);
62 bool updateFile(
const QString &filename);
64 void gotId(
const QString &_id);
65 void gotFile(
const QString &_file);
66 void gotGroup(
const QString &_group);
67 void gotRemoveGroup(
const QString &_group);
68 void gotKey(
const QString &_key);
69 void gotRemoveKey(
const QString &_key);
72 void gotOptions(
const QString &_options);
73 void gotScript(
const QString &_script);
74 void gotScriptArguments(
const QString &_arguments);
77 void copyGroup(
const KConfigBase *cfg1,
const QString &group1,
78 KConfigBase *cfg2,
const QString &group2);
79 void copyGroup(
const KConfigGroup &cg1, KConfigGroup &cg2);
80 void copyOrMoveKey(
const QStringList &srcGroupPath,
const QString &srcKey,
const QStringList &dstGroupPath,
const QString &dstKey);
81 void copyOrMoveGroup(
const QStringList &srcGroupPath,
const QStringList &dstGroupPath);
87 QString m_currentFilename;
95 QString m_newFileName;
96 KConfig *m_oldConfig1;
97 KConfig *m_oldConfig2;
100 QStringList m_oldGroup;
101 QStringList m_newGroup;
105 bool m_bUseConfigInfo;
107 QTextStream *m_textStream;
113 KonfUpdate::KonfUpdate()
114 : m_textStream(0), m_file(0)
116 bool updateAll =
false;
121 m_config =
new KConfig(
"kconf_updaterc");
122 KConfigGroup cg(m_config, QString());
124 QStringList updateFiles;
125 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
127 m_debug = args->isSet(
"debug");
129 m_bUseConfigInfo =
false;
130 if (args->isSet(
"check")) {
131 m_bUseConfigInfo =
true;
132 QString file = KStandardDirs::locate(
"data",
"kconf_update/" + args->getOption(
"check"));
133 if (file.isEmpty()) {
134 qWarning(
"File '%s' not found.", args->getOption(
"check").toLocal8Bit().data());
135 log() <<
"File '" << args->getOption(
"check") <<
"' passed on command line not found" << endl;
138 updateFiles.append(file);
139 }
else if (args->count()) {
140 for (
int i = 0; i < args->count(); i++) {
141 KUrl url = args->url(i);
142 if (!url.isLocalFile()) {
143 KCmdLineArgs::usageError(i18n(
"Only local files are supported."));
145 updateFiles.append(url.toLocalFile());
148 if (cg.readEntry(
"autoUpdateDisabled",
false))
150 updateFiles = findUpdateFiles(
true);
154 for (QStringList::ConstIterator it = updateFiles.constBegin();
155 it != updateFiles.constEnd();
160 if (updateAll && !cg.readEntry(
"updateInfoAdded",
false)) {
161 cg.writeEntry(
"updateInfoAdded",
true);
162 updateFiles = findUpdateFiles(
false);
164 for (QStringList::ConstIterator it = updateFiles.constBegin();
165 it != updateFiles.constEnd();
173 KonfUpdate::~KonfUpdate()
180 QTextStream &
operator<<(QTextStream & stream,
const QStringList & lst)
182 stream << lst.join(
", ");
190 QString file = KStandardDirs::locateLocal(
"data",
"kconf_update/log/update.log");
191 m_file =
new QFile(file);
192 if (m_file->open(QIODevice::WriteOnly | QIODevice::Append)) {
193 m_textStream =
new QTextStream(m_file);
196 m_textStream =
new QTextStream(stderr, QIODevice::WriteOnly);
200 (*m_textStream) << QDateTime::currentDateTime().toString(Qt::ISODate) <<
" ";
202 return *m_textStream;
206 KonfUpdate::logFileError()
208 return log() << m_currentFilename <<
':' << m_lineCount <<
":'" << m_line <<
"': ";
211 QStringList KonfUpdate::findUpdateFiles(
bool dirtyOnly)
214 const QStringList list = KGlobal::dirs()->findAllResources(
"data",
"kconf_update/*.upd",
215 KStandardDirs::NoDuplicates);
216 for (QStringList::ConstIterator it = list.constBegin();
217 it != list.constEnd();
220 KDE_struct_stat buff;
221 if (KDE::stat(file, &buff) == 0) {
222 int i = file.lastIndexOf(
'/');
224 file = file.mid(i + 1);
226 KConfigGroup cg(m_config, file);
227 time_t ctime = cg.readEntry(
"ctime", 0);
228 time_t mtime = cg.readEntry(
"mtime", 0);
230 (ctime != buff.st_ctime) || (mtime != buff.st_mtime)) {
238 bool KonfUpdate::checkFile(
const QString &filename)
240 m_currentFilename = filename;
241 int i = m_currentFilename.lastIndexOf(
'/');
243 m_currentFilename = m_currentFilename.mid(i + 1);
246 QFile file(filename);
247 if (!file.open(QIODevice::ReadOnly)) {
251 QTextStream ts(&file);
252 ts.setCodec(QTextCodec::codecForName(
"ISO-8859-1"));
256 while (!ts.atEnd()) {
257 QString line = ts.readLine().trimmed();
259 if (line.isEmpty() || (line[0] ==
'#')) {
262 if (line.startsWith(
"Id=")) {
263 id = m_currentFilename +
':' + line.mid(3);
264 }
else if (line.startsWith(
"File=")) {
265 checkGotFile(line.mid(5), id);
272 void KonfUpdate::checkGotFile(
const QString &_file,
const QString &
id)
275 int i = _file.indexOf(
',');
277 file = _file.trimmed();
279 file = _file.mid(i + 1).trimmed();
284 KConfig cfg(file, KConfig::SimpleConfig);
285 KConfigGroup cg(&cfg,
"$Version");
286 QStringList ids = cg.readEntry(
"update_info", QStringList());
287 if (ids.contains(
id)) {
291 cg.writeEntry(
"update_info", ids);
313 bool KonfUpdate::updateFile(
const QString &filename)
315 m_currentFilename = filename;
316 int i = m_currentFilename.lastIndexOf(
'/');
318 m_currentFilename = m_currentFilename.mid(i + 1);
321 QFile file(filename);
322 if (!file.open(QIODevice::ReadOnly)) {
326 log() <<
"Checking update-file '" << filename <<
"' for new updates" << endl;
328 QTextStream ts(&file);
329 ts.setCodec(QTextCodec::codecForName(
"ISO-8859-1"));
332 while (!ts.atEnd()) {
333 m_line = ts.readLine().trimmed();
335 if (m_line.isEmpty() || (m_line[0] ==
'#')) {
338 if (m_line.startsWith(QLatin1String(
"Id="))) {
339 gotId(m_line.mid(3));
342 }
else if (m_line.startsWith(QLatin1String(
"Options="))) {
343 gotOptions(m_line.mid(8));
344 }
else if (m_line.startsWith(QLatin1String(
"File="))) {
345 gotFile(m_line.mid(5));
346 }
else if (m_skipFile) {
348 }
else if (m_line.startsWith(QLatin1String(
"Group="))) {
349 gotGroup(m_line.mid(6));
350 }
else if (m_line.startsWith(QLatin1String(
"RemoveGroup="))) {
351 gotRemoveGroup(m_line.mid(12));
353 }
else if (m_line.startsWith(QLatin1String(
"Script="))) {
354 gotScript(m_line.mid(7));
356 }
else if (m_line.startsWith(QLatin1String(
"ScriptArguments="))) {
357 gotScriptArguments(m_line.mid(16));
358 }
else if (m_line.startsWith(QLatin1String(
"Key="))) {
359 gotKey(m_line.mid(4));
361 }
else if (m_line.startsWith(QLatin1String(
"RemoveKey="))) {
362 gotRemoveKey(m_line.mid(10));
364 }
else if (m_line ==
"AllKeys") {
367 }
else if (m_line ==
"AllGroups") {
371 logFileError() <<
"Parse error" << endl;
377 KDE_struct_stat buff;
378 KDE::stat(filename, &buff);
379 KConfigGroup cg(m_config, m_currentFilename);
380 cg.writeEntry(
"ctime",
int(buff.st_ctime));
381 cg.writeEntry(
"mtime",
int(buff.st_mtime));
388 void KonfUpdate::gotId(
const QString &_id)
390 if (!m_id.isEmpty() && !m_skip) {
391 KConfigGroup cg(m_config, m_currentFilename);
393 QStringList ids = cg.readEntry(
"done", QStringList());
394 if (!ids.contains(m_id)) {
396 cg.writeEntry(
"done", ids);
403 KConfigGroup cg(m_config, m_currentFilename);
405 QStringList ids = cg.readEntry(
"done", QStringList());
406 if (!_id.isEmpty()) {
407 if (ids.contains(_id)) {
409 if (!m_bUseConfigInfo) {
417 if (m_bUseConfigInfo) {
418 log() << m_currentFilename <<
": Checking update '" << _id <<
"'" << endl;
420 log() << m_currentFilename <<
": Found new update '" << _id <<
"'" << endl;
425 void KonfUpdate::gotFile(
const QString &_file)
430 if (!m_oldFile.isEmpty()) {
435 KConfigGroup cg(m_oldConfig2,
"$Version");
436 QStringList ids = cg.readEntry(
"update_info", QStringList());
437 QString cfg_id = m_currentFilename +
':' + m_id;
438 if (!ids.contains(cfg_id) && !m_skip) {
440 cg.writeEntry(
"update_info", ids);
446 QString file = KStandardDirs::locateLocal(
"config", m_oldFile);
447 KDE_struct_stat s_buf;
448 if (KDE::stat(file, &s_buf) == 0) {
449 if (s_buf.st_size == 0) {
457 if (!m_newFile.isEmpty()) {
459 KConfigGroup cg(m_newConfig,
"$Version");
460 QStringList ids = cg.readEntry(
"update_info", QStringList());
461 QString cfg_id = m_currentFilename +
':' + m_id;
462 if (!ids.contains(cfg_id) && !m_skip) {
464 cg.writeEntry(
"update_info", ids);
474 int i = _file.indexOf(
',');
476 m_oldFile = _file.trimmed();
478 m_oldFile = _file.left(i).trimmed();
479 m_newFile = _file.mid(i + 1).trimmed();
480 if (m_oldFile == m_newFile) {
485 if (!m_oldFile.isEmpty()) {
486 m_oldConfig2 =
new KConfig(m_oldFile, KConfig::NoGlobals);
487 QString cfg_id = m_currentFilename +
':' + m_id;
488 KConfigGroup cg(m_oldConfig2,
"$Version");
489 QStringList ids = cg.readEntry(
"update_info", QStringList());
490 if (ids.contains(cfg_id)) {
493 log() << m_currentFilename <<
": Skipping update '" << m_id <<
"'" << endl;
496 if (!m_newFile.isEmpty()) {
497 m_newConfig =
new KConfig(m_newFile, KConfig::NoGlobals);
498 KConfigGroup cg(m_newConfig,
"$Version");
499 ids = cg.readEntry(
"update_info", QStringList());
500 if (ids.contains(cfg_id)) {
502 log() << m_currentFilename <<
": Skipping update '" << m_id <<
"'" << endl;
505 m_newConfig = m_oldConfig2;
508 m_oldConfig1 =
new KConfig(m_oldFile, KConfig::NoGlobals);
512 m_newFileName = m_newFile;
513 if (m_newFileName.isEmpty()) {
514 m_newFileName = m_oldFile;
518 if (!m_oldFile.isEmpty()) {
519 if (m_oldConfig1 != NULL
520 && (m_oldConfig1->groupList().isEmpty()
521 || (m_oldConfig1->groupList().count() == 1 && m_oldConfig1->groupList().first() ==
"$Version"))) {
522 log() << m_currentFilename <<
": File '" << m_oldFile <<
"' does not exist or empty, skipping" << endl;
534 logFileError() << error;
539 void KonfUpdate::gotGroup(
const QString &_group)
541 QString group = _group.trimmed();
542 if (group.isEmpty()) {
543 m_oldGroup = m_newGroup = QStringList();
547 QStringList tokens = group.split(
',');
549 if (tokens.count() == 1) {
550 m_newGroup = m_oldGroup;
556 void KonfUpdate::gotRemoveGroup(
const QString &_group)
561 logFileError() <<
"RemoveGroup without previous File specification" << endl;
571 log() << m_currentFilename <<
": RemoveGroup removes group " << m_oldFile <<
":" << m_oldGroup << endl;
575 void KonfUpdate::gotKey(
const QString &_key)
577 QString oldKey, newKey;
578 int i = _key.indexOf(
',');
580 oldKey = _key.trimmed();
583 oldKey = _key.left(i).trimmed();
584 newKey = _key.mid(i + 1).trimmed();
587 if (oldKey.isEmpty() || newKey.isEmpty()) {
588 logFileError() <<
"Key specifies invalid key" << endl;
592 logFileError() <<
"Key without previous File specification" << endl;
595 copyOrMoveKey(m_oldGroup, oldKey, m_newGroup, newKey);
598 void KonfUpdate::copyOrMoveKey(
const QStringList &srcGroupPath,
const QString &srcKey,
const QStringList &dstGroupPath,
const QString &dstKey)
601 if (!m_bOverwrite && dstCg.hasKey(dstKey)) {
602 log() << m_currentFilename <<
": Skipping " << m_newFileName <<
":" << dstCg.name() <<
":" << dstKey <<
", already exists." << endl;
607 QString value = srcCg.readEntry(srcKey, QString());
608 log() << m_currentFilename <<
": Updating " << m_newFileName <<
":" << dstCg.name() <<
":" << dstKey <<
" to '" << value <<
"'" << endl;
609 dstCg.writeEntry(dstKey, value);
616 if (m_oldConfig2 == m_newConfig
617 && srcGroupPath == dstGroupPath
618 && srcKey == dstKey) {
622 srcCg2.deleteEntry(srcKey);
623 log() << m_currentFilename <<
": Removing " << m_oldFile <<
":" << srcCg2.name() <<
":" << srcKey <<
", moved." << endl;
626 void KonfUpdate::copyOrMoveGroup(
const QStringList &srcGroupPath,
const QStringList &dstGroupPath)
631 Q_FOREACH(
const QString &key, cg.keyList()) {
632 copyOrMoveKey(srcGroupPath, key, dstGroupPath, key);
636 Q_FOREACH(
const QString &group, cg.groupList()) {
637 QStringList groupPath = QStringList() << group;
638 copyOrMoveGroup(srcGroupPath + groupPath, dstGroupPath + groupPath);
642 void KonfUpdate::gotRemoveKey(
const QString &_key)
644 QString key = _key.trimmed();
647 logFileError() <<
"RemoveKey specifies invalid key" << endl;
652 logFileError() <<
"Key without previous File specification" << endl;
657 if (!cg1.hasKey(key)) {
660 log() << m_currentFilename <<
": RemoveKey removes " << m_oldFile <<
":" << m_oldGroup <<
":" << key << endl;
664 cg2.deleteEntry(key);
670 void KonfUpdate::gotAllKeys()
673 logFileError() <<
"AllKeys without previous File specification" << endl;
677 copyOrMoveGroup(m_oldGroup, m_newGroup);
680 void KonfUpdate::gotAllGroups()
683 logFileError() <<
"AllGroups without previous File specification" << endl;
687 const QStringList allGroups = m_oldConfig1->groupList();
688 for (QStringList::ConstIterator it = allGroups.begin();
689 it != allGroups.end(); ++it) {
690 m_oldGroup = QStringList() << *it;
691 m_newGroup = m_oldGroup;
696 void KonfUpdate::gotOptions(
const QString &_options)
698 const QStringList options = _options.split(
',');
699 for (QStringList::ConstIterator it = options.begin();
702 if ((*it).toLower().trimmed() ==
"copy") {
706 if ((*it).toLower().trimmed() ==
"overwrite") {
712 void KonfUpdate::copyGroup(
const KConfigBase *cfg1,
const QString &group1,
713 KConfigBase *cfg2,
const QString &group2)
715 KConfigGroup cg1(cfg1, group1);
716 KConfigGroup cg2(cfg2, group2);
720 void KonfUpdate::copyGroup(
const KConfigGroup &cg1, KConfigGroup &cg2)
723 QMap<QString, QString> list = cg1.entryMap();
724 for (QMap<QString, QString>::ConstIterator it = list.constBegin();
725 it != list.constEnd(); ++it) {
726 if (m_bOverwrite || !cg2.hasKey(it.key())) {
727 cg2.writeEntry(it.key(), it.value());
732 Q_FOREACH(
const QString &group, cg1.groupList()) {
733 copyGroup(&cg1, group, &cg2, group);
737 void KonfUpdate::gotScriptArguments(
const QString &_arguments)
739 m_arguments = _arguments;
742 void KonfUpdate::gotScript(
const QString &_script)
744 QString script, interpreter;
745 int i = _script.indexOf(
',');
747 script = _script.trimmed();
749 script = _script.left(i).trimmed();
750 interpreter = _script.mid(i + 1).trimmed();
754 if (script.isEmpty()) {
755 logFileError() <<
"Script fails to specify filename";
762 QString path = KStandardDirs::locate(
"data",
"kconf_update/" + script);
763 if (path.isEmpty()) {
764 if (interpreter.isEmpty()) {
765 path = KStandardDirs::locate(
"lib",
"kconf_update_bin/" + script);
768 if (path.isEmpty()) {
769 logFileError() <<
"Script '" << script <<
"' not found" << endl;
775 if (!m_arguments.isNull()) {
776 log() << m_currentFilename <<
": Running script '" << script <<
"' with arguments '" << m_arguments <<
"'" << endl;
778 log() << m_currentFilename <<
": Running script '" << script <<
"'" << endl;
782 if (interpreter.isEmpty()) {
785 cmd = interpreter +
' ' + path;
788 if (!m_arguments.isNull()) {
793 KTemporaryFile scriptIn;
795 KTemporaryFile scriptOut;
797 KTemporaryFile scriptErr;
803 scriptIn.setAutoRemove(
false);
804 log() <<
"Script input stored in " << scriptIn.fileName() << endl;
806 KConfig cfg(scriptIn.fileName(), KConfig::SimpleConfig);
808 if (m_oldGroup.isEmpty()) {
810 const QStringList grpList = m_oldConfig1->groupList();
811 for (QStringList::ConstIterator it = grpList.begin();
814 copyGroup(m_oldConfig1, *it, &cfg, *it);
818 KConfigGroup cg2(&cfg, QString());
823 result = system(QFile::encodeName(QString(
"%1 < %2 > %3 2> %4").arg(cmd, scriptIn.fileName(), scriptOut.fileName(), scriptErr.fileName())));
825 QString path_ = QDir::convertSeparators ( QFileInfo ( cmd ).absoluteFilePath() );
826 QString file_ = QFileInfo ( cmd ).fileName();
827 SHELLEXECUTEINFO execInfo;
828 memset ( &execInfo,0,
sizeof ( execInfo ) );
829 execInfo.cbSize =
sizeof ( execInfo );
830 execInfo.fMask = SEE_MASK_FLAG_NO_UI;
831 execInfo.lpVerb = L
"open";
832 execInfo.lpFile = (LPCWSTR) path_.utf16();
833 execInfo.lpDirectory = (LPCWSTR) file_.utf16();
834 execInfo.lpParameters = (LPCWSTR) QString(
" < %1 > %2 2> %3").arg( scriptIn.fileName(), scriptOut.fileName(), scriptErr.fileName()).utf16();
835 result = ShellExecuteEx ( &execInfo );
848 result = system(QFile::encodeName(QString(
"%1 2> %2").arg(cmd, scriptErr.fileName())));
850 QString path_ = QDir::convertSeparators ( QFileInfo ( cmd ).absoluteFilePath() );
851 QString file_ = QFileInfo ( cmd ).fileName();
852 SHELLEXECUTEINFO execInfo;
853 memset ( &execInfo,0,
sizeof ( execInfo ) );
854 execInfo.cbSize =
sizeof ( execInfo );
855 execInfo.fMask = SEE_MASK_FLAG_NO_UI;
856 execInfo.lpVerb = L
"open";
857 execInfo.lpFile = (LPCWSTR) path_.utf16();
858 execInfo.lpDirectory = (LPCWSTR) file_.utf16();
859 execInfo.lpParameters = (LPCWSTR) QString(
" 2> %1").arg( scriptErr.fileName()).utf16();
860 result = ShellExecuteEx ( &execInfo );
874 QFile output(scriptErr.fileName());
875 if (output.open(QIODevice::ReadOnly)) {
876 QTextStream ts(&output);
877 ts.setCodec(QTextCodec::codecForName(
"UTF-8"));
878 while (!ts.atEnd()) {
879 QString line = ts.readLine();
880 log() <<
"[Script] " << line << endl;
886 log() << m_currentFilename <<
": !! An error occurred while running '" << cmd <<
"'" << endl;
895 scriptOut.setAutoRemove(
false);
896 log() <<
"Script output stored in " << scriptOut.fileName() << endl;
901 QStringList group = m_oldGroup;
902 QFile output(scriptOut.fileName());
903 if (output.open(QIODevice::ReadOnly)) {
904 QTextStream ts(&output);
905 ts.setCodec(QTextCodec::codecForName(
"UTF-8"));
906 while (!ts.atEnd()) {
907 QString line = ts.readLine();
908 if (line.startsWith(
'[')) {
910 }
else if (line.startsWith(QLatin1String(
"# DELETE "))) {
911 QString key = line.mid(9);
913 int j = key.lastIndexOf(
']') + 1;
921 log() << m_currentFilename <<
": Script removes " << m_oldFile <<
":" << group <<
":" << key << endl;
925 }
else if (line.startsWith(QLatin1String(
"# DELETEGROUP"))) {
926 QString str = line.mid(13).trimmed();
927 if (!str.isEmpty()) {
932 log() << m_currentFilename <<
": Script removes group " << m_oldFile <<
":" << group << endl;
939 KConfig scriptOutConfig(scriptOut.fileName(), KConfig::NoGlobals);
940 if (m_newGroup.isEmpty()) {
942 copyGroup(&scriptOutConfig, QString(), m_newConfig, QString());
947 copyGroup(srcCg, dstCg);
949 Q_FOREACH(
const QString &group, scriptOutConfig.groupList()) {
950 copyGroup(&scriptOutConfig, group, m_newConfig, group);
954 void KonfUpdate::resetOptions()
957 m_bOverwrite =
false;
962 extern "C" KDE_EXPORT
int kdemain(
int argc,
char **argv)
964 KCmdLineOptions options;
965 options.add(
"debug", ki18n(
"Keep output results from scripts"));
966 options.add(
"check <update-file>", ki18n(
"Check whether config file itself requires updating"));
967 options.add(
"+[file]", ki18n(
"File to read update instructions from"));
969 KAboutData aboutData(
"kconf_update", 0, ki18n(
"KConf Update"),
971 ki18n(
"KDE Tool for updating user configuration files"),
972 KAboutData::License_GPL,
973 ki18n(
"(c) 2001, Waldo Bastian"));
975 aboutData.addAuthor(ki18n(
"Waldo Bastian"), KLocalizedString(),
"bastian@kde.org");
977 KCmdLineArgs::init(argc, argv, &aboutData);
978 KCmdLineArgs::addCmdLineOptions(options);
980 KComponentData componentData(&aboutData);
982 KonfUpdate konfUpdate;