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 if (KDE::stat(filename, &buff) == 0) {
379 KConfigGroup cg(m_config, m_currentFilename);
380 cg.writeEntry(
"ctime",
int(buff.st_ctime));
381 cg.writeEntry(
"mtime",
int(buff.st_mtime));
389 void KonfUpdate::gotId(
const QString &_id)
391 if (!m_id.isEmpty() && !m_skip) {
392 KConfigGroup cg(m_config, m_currentFilename);
394 QStringList ids = cg.readEntry(
"done", QStringList());
395 if (!ids.contains(m_id)) {
397 cg.writeEntry(
"done", ids);
404 KConfigGroup cg(m_config, m_currentFilename);
406 QStringList ids = cg.readEntry(
"done", QStringList());
407 if (!_id.isEmpty()) {
408 if (ids.contains(_id)) {
410 if (!m_bUseConfigInfo) {
418 if (m_bUseConfigInfo) {
419 log() << m_currentFilename <<
": Checking update '" << _id <<
"'" << endl;
421 log() << m_currentFilename <<
": Found new update '" << _id <<
"'" << endl;
426 void KonfUpdate::gotFile(
const QString &_file)
431 if (!m_oldFile.isEmpty()) {
436 KConfigGroup cg(m_oldConfig2,
"$Version");
437 QStringList ids = cg.readEntry(
"update_info", QStringList());
438 QString cfg_id = m_currentFilename +
':' + m_id;
439 if (!ids.contains(cfg_id) && !m_skip) {
441 cg.writeEntry(
"update_info", ids);
447 QString file = KStandardDirs::locateLocal(
"config", m_oldFile);
448 KDE_struct_stat s_buf;
449 if (KDE::stat(file, &s_buf) == 0) {
450 if (s_buf.st_size == 0) {
458 if (!m_newFile.isEmpty()) {
460 KConfigGroup cg(m_newConfig,
"$Version");
461 QStringList ids = cg.readEntry(
"update_info", QStringList());
462 QString cfg_id = m_currentFilename +
':' + m_id;
463 if (!ids.contains(cfg_id) && !m_skip) {
465 cg.writeEntry(
"update_info", ids);
475 int i = _file.indexOf(
',');
477 m_oldFile = _file.trimmed();
479 m_oldFile = _file.left(i).trimmed();
480 m_newFile = _file.mid(i + 1).trimmed();
481 if (m_oldFile == m_newFile) {
486 if (!m_oldFile.isEmpty()) {
487 m_oldConfig2 =
new KConfig(m_oldFile, KConfig::NoGlobals);
488 QString cfg_id = m_currentFilename +
':' + m_id;
489 KConfigGroup cg(m_oldConfig2,
"$Version");
490 QStringList ids = cg.readEntry(
"update_info", QStringList());
491 if (ids.contains(cfg_id)) {
494 log() << m_currentFilename <<
": Skipping update '" << m_id <<
"'" << endl;
497 if (!m_newFile.isEmpty()) {
498 m_newConfig =
new KConfig(m_newFile, KConfig::NoGlobals);
499 KConfigGroup cg(m_newConfig,
"$Version");
500 ids = cg.readEntry(
"update_info", QStringList());
501 if (ids.contains(cfg_id)) {
503 log() << m_currentFilename <<
": Skipping update '" << m_id <<
"'" << endl;
506 m_newConfig = m_oldConfig2;
509 m_oldConfig1 =
new KConfig(m_oldFile, KConfig::NoGlobals);
513 m_newFileName = m_newFile;
514 if (m_newFileName.isEmpty()) {
515 m_newFileName = m_oldFile;
519 if (!m_oldFile.isEmpty()) {
520 if (m_oldConfig1 != NULL
521 && (m_oldConfig1->groupList().isEmpty()
522 || (m_oldConfig1->groupList().count() == 1 && m_oldConfig1->groupList().first() ==
"$Version"))) {
523 log() << m_currentFilename <<
": File '" << m_oldFile <<
"' does not exist or empty, skipping" << endl;
535 logFileError() << error;
540 void KonfUpdate::gotGroup(
const QString &_group)
542 QString group = _group.trimmed();
543 if (group.isEmpty()) {
544 m_oldGroup = m_newGroup = QStringList();
548 QStringList tokens = group.split(
',');
550 if (tokens.count() == 1) {
551 m_newGroup = m_oldGroup;
557 void KonfUpdate::gotRemoveGroup(
const QString &_group)
562 logFileError() <<
"RemoveGroup without previous File specification" << endl;
572 log() << m_currentFilename <<
": RemoveGroup removes group " << m_oldFile <<
":" << m_oldGroup << endl;
576 void KonfUpdate::gotKey(
const QString &_key)
578 QString oldKey, newKey;
579 int i = _key.indexOf(
',');
581 oldKey = _key.trimmed();
584 oldKey = _key.left(i).trimmed();
585 newKey = _key.mid(i + 1).trimmed();
588 if (oldKey.isEmpty() || newKey.isEmpty()) {
589 logFileError() <<
"Key specifies invalid key" << endl;
593 logFileError() <<
"Key without previous File specification" << endl;
596 copyOrMoveKey(m_oldGroup, oldKey, m_newGroup, newKey);
599 void KonfUpdate::copyOrMoveKey(
const QStringList &srcGroupPath,
const QString &srcKey,
const QStringList &dstGroupPath,
const QString &dstKey)
602 if (!m_bOverwrite && dstCg.hasKey(dstKey)) {
603 log() << m_currentFilename <<
": Skipping " << m_newFileName <<
":" << dstCg.name() <<
":" << dstKey <<
", already exists." << endl;
608 if (!srcCg.hasKey(srcKey))
610 QString value = srcCg.readEntry(srcKey, QString());
611 log() << m_currentFilename <<
": Updating " << m_newFileName <<
":" << dstCg.name() <<
":" << dstKey <<
" to '" << value <<
"'" << endl;
612 dstCg.writeEntry(dstKey, value);
619 if (m_oldConfig2 == m_newConfig
620 && srcGroupPath == dstGroupPath
621 && srcKey == dstKey) {
625 srcCg2.deleteEntry(srcKey);
626 log() << m_currentFilename <<
": Removing " << m_oldFile <<
":" << srcCg2.name() <<
":" << srcKey <<
", moved." << endl;
629 void KonfUpdate::copyOrMoveGroup(
const QStringList &srcGroupPath,
const QStringList &dstGroupPath)
634 Q_FOREACH(
const QString &key, cg.keyList()) {
635 copyOrMoveKey(srcGroupPath, key, dstGroupPath, key);
639 Q_FOREACH(
const QString &group, cg.groupList()) {
640 QStringList groupPath = QStringList() << group;
641 copyOrMoveGroup(srcGroupPath + groupPath, dstGroupPath + groupPath);
645 void KonfUpdate::gotRemoveKey(
const QString &_key)
647 QString key = _key.trimmed();
650 logFileError() <<
"RemoveKey specifies invalid key" << endl;
655 logFileError() <<
"Key without previous File specification" << endl;
660 if (!cg1.hasKey(key)) {
663 log() << m_currentFilename <<
": RemoveKey removes " << m_oldFile <<
":" << m_oldGroup <<
":" << key << endl;
667 cg2.deleteEntry(key);
673 void KonfUpdate::gotAllKeys()
676 logFileError() <<
"AllKeys without previous File specification" << endl;
680 copyOrMoveGroup(m_oldGroup, m_newGroup);
683 void KonfUpdate::gotAllGroups()
686 logFileError() <<
"AllGroups without previous File specification" << endl;
690 const QStringList allGroups = m_oldConfig1->groupList();
691 for (QStringList::ConstIterator it = allGroups.begin();
692 it != allGroups.end(); ++it) {
693 m_oldGroup = QStringList() << *it;
694 m_newGroup = m_oldGroup;
699 void KonfUpdate::gotOptions(
const QString &_options)
701 const QStringList options = _options.split(
',');
702 for (QStringList::ConstIterator it = options.begin();
705 if ((*it).toLower().trimmed() ==
"copy") {
709 if ((*it).toLower().trimmed() ==
"overwrite") {
715 void KonfUpdate::copyGroup(
const KConfigBase *cfg1,
const QString &group1,
716 KConfigBase *cfg2,
const QString &group2)
718 KConfigGroup cg1(cfg1, group1);
719 KConfigGroup cg2(cfg2, group2);
723 void KonfUpdate::copyGroup(
const KConfigGroup &cg1, KConfigGroup &cg2)
726 QMap<QString, QString> list = cg1.entryMap();
727 for (QMap<QString, QString>::ConstIterator it = list.constBegin();
728 it != list.constEnd(); ++it) {
729 if (m_bOverwrite || !cg2.hasKey(it.key())) {
730 cg2.writeEntry(it.key(), it.value());
735 Q_FOREACH(
const QString &group, cg1.groupList()) {
736 copyGroup(&cg1, group, &cg2, group);
740 void KonfUpdate::gotScriptArguments(
const QString &_arguments)
742 m_arguments = _arguments;
745 void KonfUpdate::gotScript(
const QString &_script)
747 QString script, interpreter;
748 int i = _script.indexOf(
',');
750 script = _script.trimmed();
752 script = _script.left(i).trimmed();
753 interpreter = _script.mid(i + 1).trimmed();
757 if (script.isEmpty()) {
758 logFileError() <<
"Script fails to specify filename";
765 QString path = KStandardDirs::locate(
"data",
"kconf_update/" + script);
766 if (path.isEmpty()) {
767 if (interpreter.isEmpty()) {
768 path = KStandardDirs::locate(
"lib",
"kconf_update_bin/" + script);
771 if (path.isEmpty()) {
772 logFileError() <<
"Script '" << script <<
"' not found" << endl;
778 if (!m_arguments.isNull()) {
779 log() << m_currentFilename <<
": Running script '" << script <<
"' with arguments '" << m_arguments <<
"'" << endl;
781 log() << m_currentFilename <<
": Running script '" << script <<
"'" << endl;
785 if (interpreter.isEmpty()) {
788 cmd = interpreter +
' ' + path;
791 if (!m_arguments.isNull()) {
796 KTemporaryFile scriptIn;
798 KTemporaryFile scriptOut;
800 KTemporaryFile scriptErr;
806 scriptIn.setAutoRemove(
false);
807 log() <<
"Script input stored in " << scriptIn.fileName() << endl;
809 KConfig cfg(scriptIn.fileName(), KConfig::SimpleConfig);
811 if (m_oldGroup.isEmpty()) {
813 const QStringList grpList = m_oldConfig1->groupList();
814 for (QStringList::ConstIterator it = grpList.begin();
817 copyGroup(m_oldConfig1, *it, &cfg, *it);
821 KConfigGroup cg2(&cfg, QString());
826 result = system(QFile::encodeName(QString(
"%1 < %2 > %3 2> %4").arg(cmd, scriptIn.fileName(), scriptOut.fileName(), scriptErr.fileName())));
828 QString path_ = QDir::convertSeparators ( QFileInfo ( cmd ).absoluteFilePath() );
829 QString file_ = QFileInfo ( cmd ).fileName();
830 SHELLEXECUTEINFO execInfo;
831 memset ( &execInfo,0,
sizeof ( execInfo ) );
832 execInfo.cbSize =
sizeof ( execInfo );
833 execInfo.fMask = SEE_MASK_FLAG_NO_UI;
834 execInfo.lpVerb = L
"open";
835 execInfo.lpFile = (LPCWSTR) path_.utf16();
836 execInfo.lpDirectory = (LPCWSTR) file_.utf16();
837 execInfo.lpParameters = (LPCWSTR) QString(
" < %1 > %2 2> %3").arg( scriptIn.fileName(), scriptOut.fileName(), scriptErr.fileName()).utf16();
838 result = ShellExecuteEx ( &execInfo );
851 result = system(QFile::encodeName(QString(
"%1 2> %2").arg(cmd, scriptErr.fileName())));
853 QString path_ = QDir::convertSeparators ( QFileInfo ( cmd ).absoluteFilePath() );
854 QString file_ = QFileInfo ( cmd ).fileName();
855 SHELLEXECUTEINFO execInfo;
856 memset ( &execInfo,0,
sizeof ( execInfo ) );
857 execInfo.cbSize =
sizeof ( execInfo );
858 execInfo.fMask = SEE_MASK_FLAG_NO_UI;
859 execInfo.lpVerb = L
"open";
860 execInfo.lpFile = (LPCWSTR) path_.utf16();
861 execInfo.lpDirectory = (LPCWSTR) file_.utf16();
862 execInfo.lpParameters = (LPCWSTR) QString(
" 2> %1").arg( scriptErr.fileName()).utf16();
863 result = ShellExecuteEx ( &execInfo );
877 QFile output(scriptErr.fileName());
878 if (output.open(QIODevice::ReadOnly)) {
879 QTextStream ts(&output);
880 ts.setCodec(QTextCodec::codecForName(
"UTF-8"));
881 while (!ts.atEnd()) {
882 QString line = ts.readLine();
883 log() <<
"[Script] " << line << endl;
889 log() << m_currentFilename <<
": !! An error occurred while running '" << cmd <<
"'" << endl;
898 scriptOut.setAutoRemove(
false);
899 log() <<
"Script output stored in " << scriptOut.fileName() << endl;
904 QStringList group = m_oldGroup;
905 QFile output(scriptOut.fileName());
906 if (output.open(QIODevice::ReadOnly)) {
907 QTextStream ts(&output);
908 ts.setCodec(QTextCodec::codecForName(
"UTF-8"));
909 while (!ts.atEnd()) {
910 QString line = ts.readLine();
911 if (line.startsWith(
'[')) {
913 }
else if (line.startsWith(QLatin1String(
"# DELETE "))) {
914 QString key = line.mid(9);
916 int j = key.lastIndexOf(
']') + 1;
924 log() << m_currentFilename <<
": Script removes " << m_oldFile <<
":" << group <<
":" << key << endl;
928 }
else if (line.startsWith(QLatin1String(
"# DELETEGROUP"))) {
929 QString str = line.mid(13).trimmed();
930 if (!str.isEmpty()) {
935 log() << m_currentFilename <<
": Script removes group " << m_oldFile <<
":" << group << endl;
942 KConfig scriptOutConfig(scriptOut.fileName(), KConfig::NoGlobals);
943 if (m_newGroup.isEmpty()) {
945 copyGroup(&scriptOutConfig, QString(), m_newConfig, QString());
950 copyGroup(srcCg, dstCg);
952 Q_FOREACH(
const QString &group, scriptOutConfig.groupList()) {
953 copyGroup(&scriptOutConfig, group, m_newConfig, group);
957 void KonfUpdate::resetOptions()
960 m_bOverwrite =
false;
965 extern "C" KDE_EXPORT
int kdemain(
int argc,
char **argv)
967 KCmdLineOptions options;
968 options.add(
"debug", ki18n(
"Keep output results from scripts"));
969 options.add(
"check <update-file>", ki18n(
"Check whether config file itself requires updating"));
970 options.add(
"+[file]", ki18n(
"File to read update instructions from"));
972 KAboutData aboutData(
"kconf_update", 0, ki18n(
"KConf Update"),
974 ki18n(
"KDE Tool for updating user configuration files"),
975 KAboutData::License_GPL,
976 ki18n(
"(c) 2001, Waldo Bastian"));
978 aboutData.addAuthor(ki18n(
"Waldo Bastian"), KLocalizedString(),
"bastian@kde.org");
980 KCmdLineArgs::init(argc, argv, &aboutData);
981 KCmdLineArgs::addCmdLineOptions(options);
983 KComponentData componentData(&aboutData);
985 KonfUpdate konfUpdate;
QStringList parseGroupString(const QString &_str, bool *ok, QString *error)
QTextStream & operator<<(QTextStream &stream, const QStringList &lst)
int kdemain(int argc, char **argv)
KConfigGroup openGroup(KConfig *config, const QStringList &_lst)