1/*
2 *
3 * This file is part of the KDE libraries
4 * Copyright (c) 2001 Waldo Bastian <bastian@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License version 2 as published by the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 **/
20
21#include <sys/types.h>
22#include <sys/stat.h>
23#include <unistd.h>
24#include <stdlib.h>
25#include <kde_file.h>
26
27#include <QtCore/QDate>
28#include <QtCore/QFile>
29#include <QtCore/QTextStream>
30#include <QtCore/QTextCodec>
31#ifdef _WIN32_WCE
32#include <QtCore/QDir>
33#endif
34
35#include <kconfig.h>
36#include <kconfiggroup.h>
37#include <kdebug.h>
38#include <klocale.h>
39#include <kcmdlineargs.h>
40#include <kglobal.h>
41#include <kstandarddirs.h>
42#include <kaboutdata.h>
43#include <kcomponentdata.h>
44#include <ktemporaryfile.h>
45#include <kurl.h>
46
47#include "kconfigutils.h"
48
49class KonfUpdate
50{
51public:
52 KonfUpdate();
53 ~KonfUpdate();
54 QStringList findUpdateFiles(bool dirtyOnly);
55
56 QTextStream &log();
57 QTextStream &logFileError();
58
59 bool checkFile(const QString &filename);
60 void checkGotFile(const QString &_file, const QString &id);
61
62 bool updateFile(const QString &filename);
63
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);
70 void gotAllKeys();
71 void gotAllGroups();
72 void gotOptions(const QString &_options);
73 void gotScript(const QString &_script);
74 void gotScriptArguments(const QString &_arguments);
75 void resetOptions();
76
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);
82
83 QStringList parseGroupString(const QString &_str);
84
85protected:
86 KConfig *m_config;
87 QString m_currentFilename;
88 bool m_skip;
89 bool m_skipFile;
90 bool m_debug;
91 QString m_id;
92
93 QString m_oldFile;
94 QString m_newFile;
95 QString m_newFileName;
96 KConfig *m_oldConfig1; // Config to read keys from.
97 KConfig *m_oldConfig2; // Config to delete keys from.
98 KConfig *m_newConfig;
99
100 QStringList m_oldGroup;
101 QStringList m_newGroup;
102
103 bool m_bCopy;
104 bool m_bOverwrite;
105 bool m_bUseConfigInfo;
106 QString m_arguments;
107 QTextStream *m_textStream;
108 QFile *m_file;
109 QString m_line;
110 int m_lineCount;
111};
112
113KonfUpdate::KonfUpdate()
114 : m_textStream(0), m_file(0)
115{
116 bool updateAll = false;
117 m_oldConfig1 = 0;
118 m_oldConfig2 = 0;
119 m_newConfig = 0;
120
121 m_config = new KConfig("kconf_updaterc");
122 KConfigGroup cg(m_config, QString());
123
124 QStringList updateFiles;
125 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
126
127 m_debug = args->isSet("debug");
128
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;
136 return;
137 }
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."));
144 }
145 updateFiles.append(url.toLocalFile());
146 }
147 } else {
148 if (cg.readEntry("autoUpdateDisabled", false))
149 return;
150 updateFiles = findUpdateFiles(true);
151 updateAll = true;
152 }
153
154 for (QStringList::ConstIterator it = updateFiles.constBegin();
155 it != updateFiles.constEnd();
156 ++it) {
157 updateFile(*it);
158 }
159
160 if (updateAll && !cg.readEntry("updateInfoAdded", false)) {
161 cg.writeEntry("updateInfoAdded", true);
162 updateFiles = findUpdateFiles(false);
163
164 for (QStringList::ConstIterator it = updateFiles.constBegin();
165 it != updateFiles.constEnd();
166 ++it) {
167 checkFile(*it);
168 }
169 updateFiles.clear();
170 }
171}
172
173KonfUpdate::~KonfUpdate()
174{
175 delete m_config;
176 delete m_file;
177 delete m_textStream;
178}
179
180QTextStream & operator<<(QTextStream & stream, const QStringList & lst)
181{
182 stream << lst.join(", ");
183 return stream;
184}
185
186QTextStream &
187KonfUpdate::log()
188{
189 if (!m_textStream) {
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);
194 } else {
195 // Error
196 m_textStream = new QTextStream(stderr, QIODevice::WriteOnly);
197 }
198 }
199
200 (*m_textStream) << QDateTime::currentDateTime().toString(Qt::ISODate) << " ";
201
202 return *m_textStream;
203}
204
205QTextStream &
206KonfUpdate::logFileError()
207{
208 return log() << m_currentFilename << ':' << m_lineCount << ":'" << m_line << "': ";
209}
210
211QStringList KonfUpdate::findUpdateFiles(bool dirtyOnly)
212{
213 QStringList result;
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();
218 ++it) {
219 QString file = *it;
220 KDE_struct_stat buff;
221 if (KDE::stat(file, &buff) == 0) {
222 int i = file.lastIndexOf('/');
223 if (i != -1) {
224 file = file.mid(i + 1);
225 }
226 KConfigGroup cg(m_config, file);
227 time_t ctime = cg.readEntry("ctime", 0);
228 time_t mtime = cg.readEntry("mtime", 0);
229 if (!dirtyOnly ||
230 (ctime != buff.st_ctime) || (mtime != buff.st_mtime)) {
231 result.append(*it);
232 }
233 }
234 }
235 return result;
236}
237
238bool KonfUpdate::checkFile(const QString &filename)
239{
240 m_currentFilename = filename;
241 int i = m_currentFilename.lastIndexOf('/');
242 if (i != -1) {
243 m_currentFilename = m_currentFilename.mid(i + 1);
244 }
245 m_skip = true;
246 QFile file(filename);
247 if (!file.open(QIODevice::ReadOnly)) {
248 return false;
249 }
250
251 QTextStream ts(&file);
252 ts.setCodec(QTextCodec::codecForName("ISO-8859-1"));
253 int lineCount = 0;
254 resetOptions();
255 QString id;
256 while (!ts.atEnd()) {
257 QString line = ts.readLine().trimmed();
258 lineCount++;
259 if (line.isEmpty() || (line[0] == '#')) {
260 continue;
261 }
262 if (line.startsWith("Id=")) {
263 id = m_currentFilename + ':' + line.mid(3);
264 } else if (line.startsWith("File=")) {
265 checkGotFile(line.mid(5), id);
266 }
267 }
268
269 return true;
270}
271
272void KonfUpdate::checkGotFile(const QString &_file, const QString &id)
273{
274 QString file;
275 int i = _file.indexOf(',');
276 if (i == -1) {
277 file = _file.trimmed();
278 } else {
279 file = _file.mid(i + 1).trimmed();
280 }
281
282// qDebug("File %s, id %s", file.toLatin1().constData(), id.toLatin1().constData());
283
284 KConfig cfg(file, KConfig::SimpleConfig);
285 KConfigGroup cg(&cfg, "$Version");
286 QStringList ids = cg.readEntry("update_info", QStringList());
287 if (ids.contains(id)) {
288 return;
289 }
290 ids.append(id);
291 cg.writeEntry("update_info", ids);
292}
293
294/**
295 * Syntax:
296 * # Comment
297 * Id=id
298 * File=oldfile[,newfile]
299 * AllGroups
300 * Group=oldgroup[,newgroup]
301 * RemoveGroup=oldgroup
302 * Options=[copy,][overwrite,]
303 * Key=oldkey[,newkey]
304 * RemoveKey=ldkey
305 * AllKeys
306 * Keys= [Options](AllKeys|(Key|RemoveKey)*)
307 * ScriptArguments=arguments
308 * Script=scriptfile[,interpreter]
309 *
310 * Sequence:
311 * (Id,(File(Group,Keys)*)*)*
312 **/
313bool KonfUpdate::updateFile(const QString &filename)
314{
315 m_currentFilename = filename;
316 int i = m_currentFilename.lastIndexOf('/');
317 if (i != -1) {
318 m_currentFilename = m_currentFilename.mid(i + 1);
319 }
320 m_skip = true;
321 QFile file(filename);
322 if (!file.open(QIODevice::ReadOnly)) {
323 return false;
324 }
325
326 log() << "Checking update-file '" << filename << "' for new updates" << endl;
327
328 QTextStream ts(&file);
329 ts.setCodec(QTextCodec::codecForName("ISO-8859-1"));
330 m_lineCount = 0;
331 resetOptions();
332 while (!ts.atEnd()) {
333 m_line = ts.readLine().trimmed();
334 m_lineCount++;
335 if (m_line.isEmpty() || (m_line[0] == '#')) {
336 continue;
337 }
338 if (m_line.startsWith(QLatin1String("Id="))) {
339 gotId(m_line.mid(3));
340 } else if (m_skip) {
341 continue;
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) {
347 continue;
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));
352 resetOptions();
353 } else if (m_line.startsWith(QLatin1String("Script="))) {
354 gotScript(m_line.mid(7));
355 resetOptions();
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));
360 resetOptions();
361 } else if (m_line.startsWith(QLatin1String("RemoveKey="))) {
362 gotRemoveKey(m_line.mid(10));
363 resetOptions();
364 } else if (m_line == "AllKeys") {
365 gotAllKeys();
366 resetOptions();
367 } else if (m_line == "AllGroups") {
368 gotAllGroups();
369 resetOptions();
370 } else {
371 logFileError() << "Parse error" << endl;
372 }
373 }
374 // Flush.
375 gotId(QString());
376
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));
382 cg.sync();
383 }
384 return true;
385}
386
387
388
389void KonfUpdate::gotId(const QString &_id)
390{
391 if (!m_id.isEmpty() && !m_skip) {
392 KConfigGroup cg(m_config, m_currentFilename);
393
394 QStringList ids = cg.readEntry("done", QStringList());
395 if (!ids.contains(m_id)) {
396 ids.append(m_id);
397 cg.writeEntry("done", ids);
398 cg.sync();
399 }
400 }
401
402 // Flush pending changes
403 gotFile(QString());
404 KConfigGroup cg(m_config, m_currentFilename);
405
406 QStringList ids = cg.readEntry("done", QStringList());
407 if (!_id.isEmpty()) {
408 if (ids.contains(_id)) {
409 //qDebug("Id '%s' was already in done-list", _id.toLatin1().constData());
410 if (!m_bUseConfigInfo) {
411 m_skip = true;
412 return;
413 }
414 }
415 m_skip = false;
416 m_skipFile = false;
417 m_id = _id;
418 if (m_bUseConfigInfo) {
419 log() << m_currentFilename << ": Checking update '" << _id << "'" << endl;
420 } else {
421 log() << m_currentFilename << ": Found new update '" << _id << "'" << endl;
422 }
423 }
424}
425
426void KonfUpdate::gotFile(const QString &_file)
427{
428 // Reset group
429 gotGroup(QString());
430
431 if (!m_oldFile.isEmpty()) {
432 // Close old file.
433 delete m_oldConfig1;
434 m_oldConfig1 = 0;
435
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) {
440 ids.append(cfg_id);
441 cg.writeEntry("update_info", ids);
442 }
443 cg.sync();
444 delete m_oldConfig2;
445 m_oldConfig2 = 0;
446
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) {
451 // Delete empty file.
452 QFile::remove(file);
453 }
454 }
455
456 m_oldFile.clear();
457 }
458 if (!m_newFile.isEmpty()) {
459 // Close new file.
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) {
464 ids.append(cfg_id);
465 cg.writeEntry("update_info", ids);
466 }
467 m_newConfig->sync();
468 delete m_newConfig;
469 m_newConfig = 0;
470
471 m_newFile.clear();
472 }
473 m_newConfig = 0;
474
475 int i = _file.indexOf(',');
476 if (i == -1) {
477 m_oldFile = _file.trimmed();
478 } else {
479 m_oldFile = _file.left(i).trimmed();
480 m_newFile = _file.mid(i + 1).trimmed();
481 if (m_oldFile == m_newFile) {
482 m_newFile.clear();
483 }
484 }
485
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)) {
492 m_skip = true;
493 m_newFile.clear();
494 log() << m_currentFilename << ": Skipping update '" << m_id << "'" << endl;
495 }
496
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)) {
502 m_skip = true;
503 log() << m_currentFilename << ": Skipping update '" << m_id << "'" << endl;
504 }
505 } else {
506 m_newConfig = m_oldConfig2;
507 }
508
509 m_oldConfig1 = new KConfig(m_oldFile, KConfig::NoGlobals);
510 } else {
511 m_newFile.clear();
512 }
513 m_newFileName = m_newFile;
514 if (m_newFileName.isEmpty()) {
515 m_newFileName = m_oldFile;
516 }
517
518 m_skipFile = false;
519 if (!m_oldFile.isEmpty()) { // if File= is specified, it doesn't exist, is empty or contains only kconf_update's [$Version] group, skip
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;
524 m_skipFile = true;
525 }
526 }
527}
528
529QStringList KonfUpdate::parseGroupString(const QString &str)
530{
531 bool ok;
532 QString error;
533 QStringList lst = KConfigUtils::parseGroupString(str, &ok, &error);
534 if (!ok) {
535 logFileError() << error;
536 }
537 return lst;
538}
539
540void KonfUpdate::gotGroup(const QString &_group)
541{
542 QString group = _group.trimmed();
543 if (group.isEmpty()) {
544 m_oldGroup = m_newGroup = QStringList();
545 return;
546 }
547
548 QStringList tokens = group.split(',');
549 m_oldGroup = parseGroupString(tokens.at(0));
550 if (tokens.count() == 1) {
551 m_newGroup = m_oldGroup;
552 } else {
553 m_newGroup = parseGroupString(tokens.at(1));
554 }
555}
556
557void KonfUpdate::gotRemoveGroup(const QString &_group)
558{
559 m_oldGroup = parseGroupString(_group);
560
561 if (!m_oldConfig1) {
562 logFileError() << "RemoveGroup without previous File specification" << endl;
563 return;
564 }
565
566 KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, m_oldGroup);
567 if (!cg.exists()) {
568 return;
569 }
570 // Delete group.
571 cg.deleteGroup();
572 log() << m_currentFilename << ": RemoveGroup removes group " << m_oldFile << ":" << m_oldGroup << endl;
573}
574
575
576void KonfUpdate::gotKey(const QString &_key)
577{
578 QString oldKey, newKey;
579 int i = _key.indexOf(',');
580 if (i == -1) {
581 oldKey = _key.trimmed();
582 newKey = oldKey;
583 } else {
584 oldKey = _key.left(i).trimmed();
585 newKey = _key.mid(i + 1).trimmed();
586 }
587
588 if (oldKey.isEmpty() || newKey.isEmpty()) {
589 logFileError() << "Key specifies invalid key" << endl;
590 return;
591 }
592 if (!m_oldConfig1) {
593 logFileError() << "Key without previous File specification" << endl;
594 return;
595 }
596 copyOrMoveKey(m_oldGroup, oldKey, m_newGroup, newKey);
597}
598
599void KonfUpdate::copyOrMoveKey(const QStringList &srcGroupPath, const QString &srcKey, const QStringList &dstGroupPath, const QString &dstKey)
600{
601 KConfigGroup dstCg = KConfigUtils::openGroup(m_newConfig, dstGroupPath);
602 if (!m_bOverwrite && dstCg.hasKey(dstKey)) {
603 log() << m_currentFilename << ": Skipping " << m_newFileName << ":" << dstCg.name() << ":" << dstKey << ", already exists." << endl;
604 return;
605 }
606
607 KConfigGroup srcCg = KConfigUtils::openGroup(m_oldConfig1, srcGroupPath);
608 if (!srcCg.hasKey(srcKey))
609 return;
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);
613
614 if (m_bCopy) {
615 return; // Done.
616 }
617
618 // Delete old entry
619 if (m_oldConfig2 == m_newConfig
620 && srcGroupPath == dstGroupPath
621 && srcKey == dstKey) {
622 return; // Don't delete!
623 }
624 KConfigGroup srcCg2 = KConfigUtils::openGroup(m_oldConfig2, srcGroupPath);
625 srcCg2.deleteEntry(srcKey);
626 log() << m_currentFilename << ": Removing " << m_oldFile << ":" << srcCg2.name() << ":" << srcKey << ", moved." << endl;
627}
628
629void KonfUpdate::copyOrMoveGroup(const QStringList &srcGroupPath, const QStringList &dstGroupPath)
630{
631 KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig1, srcGroupPath);
632
633 // Keys
634 Q_FOREACH(const QString &key, cg.keyList()) {
635 copyOrMoveKey(srcGroupPath, key, dstGroupPath, key);
636 }
637
638 // Subgroups
639 Q_FOREACH(const QString &group, cg.groupList()) {
640 QStringList groupPath = QStringList() << group;
641 copyOrMoveGroup(srcGroupPath + groupPath, dstGroupPath + groupPath);
642 }
643}
644
645void KonfUpdate::gotRemoveKey(const QString &_key)
646{
647 QString key = _key.trimmed();
648
649 if (key.isEmpty()) {
650 logFileError() << "RemoveKey specifies invalid key" << endl;
651 return;
652 }
653
654 if (!m_oldConfig1) {
655 logFileError() << "Key without previous File specification" << endl;
656 return;
657 }
658
659 KConfigGroup cg1 = KConfigUtils::openGroup(m_oldConfig1, m_oldGroup);
660 if (!cg1.hasKey(key)) {
661 return;
662 }
663 log() << m_currentFilename << ": RemoveKey removes " << m_oldFile << ":" << m_oldGroup << ":" << key << endl;
664
665 // Delete old entry
666 KConfigGroup cg2 = KConfigUtils::openGroup(m_oldConfig2, m_oldGroup);
667 cg2.deleteEntry(key);
668 /*if (m_oldConfig2->deleteGroup(m_oldGroup, KConfig::Normal)) { // Delete group if empty.
669 log() << m_currentFilename << ": Removing empty group " << m_oldFile << ":" << m_oldGroup << endl;
670 } (this should be automatic)*/
671}
672
673void KonfUpdate::gotAllKeys()
674{
675 if (!m_oldConfig1) {
676 logFileError() << "AllKeys without previous File specification" << endl;
677 return;
678 }
679
680 copyOrMoveGroup(m_oldGroup, m_newGroup);
681}
682
683void KonfUpdate::gotAllGroups()
684{
685 if (!m_oldConfig1) {
686 logFileError() << "AllGroups without previous File specification" << endl;
687 return;
688 }
689
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;
695 gotAllKeys();
696 }
697}
698
699void KonfUpdate::gotOptions(const QString &_options)
700{
701 const QStringList options = _options.split(',');
702 for (QStringList::ConstIterator it = options.begin();
703 it != options.end();
704 ++it) {
705 if ((*it).toLower().trimmed() == "copy") {
706 m_bCopy = true;
707 }
708
709 if ((*it).toLower().trimmed() == "overwrite") {
710 m_bOverwrite = true;
711 }
712 }
713}
714
715void KonfUpdate::copyGroup(const KConfigBase *cfg1, const QString &group1,
716 KConfigBase *cfg2, const QString &group2)
717{
718 KConfigGroup cg1(cfg1, group1);
719 KConfigGroup cg2(cfg2, group2);
720 copyGroup(cg1, cg2);
721}
722
723void KonfUpdate::copyGroup(const KConfigGroup &cg1, KConfigGroup &cg2)
724{
725 // Copy keys
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());
731 }
732 }
733
734 // Copy subgroups
735 Q_FOREACH(const QString &group, cg1.groupList()) {
736 copyGroup(&cg1, group, &cg2, group);
737 }
738}
739
740void KonfUpdate::gotScriptArguments(const QString &_arguments)
741{
742 m_arguments = _arguments;
743}
744
745void KonfUpdate::gotScript(const QString &_script)
746{
747 QString script, interpreter;
748 int i = _script.indexOf(',');
749 if (i == -1) {
750 script = _script.trimmed();
751 } else {
752 script = _script.left(i).trimmed();
753 interpreter = _script.mid(i + 1).trimmed();
754 }
755
756
757 if (script.isEmpty()) {
758 logFileError() << "Script fails to specify filename";
759 m_skip = true;
760 return;
761 }
762
763
764
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);
769 }
770
771 if (path.isEmpty()) {
772 logFileError() << "Script '" << script << "' not found" << endl;
773 m_skip = true;
774 return;
775 }
776 }
777
778 if (!m_arguments.isNull()) {
779 log() << m_currentFilename << ": Running script '" << script << "' with arguments '" << m_arguments << "'" << endl;
780 } else {
781 log() << m_currentFilename << ": Running script '" << script << "'" << endl;
782 }
783
784 QString cmd;
785 if (interpreter.isEmpty()) {
786 cmd = path;
787 } else {
788 cmd = interpreter + ' ' + path;
789 }
790
791 if (!m_arguments.isNull()) {
792 cmd += ' ';
793 cmd += m_arguments;
794 }
795
796 KTemporaryFile scriptIn;
797 scriptIn.open();
798 KTemporaryFile scriptOut;
799 scriptOut.open();
800 KTemporaryFile scriptErr;
801 scriptErr.open();
802
803 int result;
804 if (m_oldConfig1) {
805 if (m_debug) {
806 scriptIn.setAutoRemove(false);
807 log() << "Script input stored in " << scriptIn.fileName() << endl;
808 }
809 KConfig cfg(scriptIn.fileName(), KConfig::SimpleConfig);
810
811 if (m_oldGroup.isEmpty()) {
812 // Write all entries to tmpFile;
813 const QStringList grpList = m_oldConfig1->groupList();
814 for (QStringList::ConstIterator it = grpList.begin();
815 it != grpList.end();
816 ++it) {
817 copyGroup(m_oldConfig1, *it, &cfg, *it);
818 }
819 } else {
820 KConfigGroup cg1 = KConfigUtils::openGroup(m_oldConfig1, m_oldGroup);
821 KConfigGroup cg2(&cfg, QString());
822 copyGroup(cg1, cg2);
823 }
824 cfg.sync();
825#ifndef _WIN32_WCE
826 result = system(QFile::encodeName(QString("%1 < %2 > %3 2> %4").arg(cmd, scriptIn.fileName(), scriptOut.fileName(), scriptErr.fileName())));
827#else
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 );
839 if (result != 0)
840 {
841 result = 0;
842 }
843 else
844 {
845 result = -1;
846 }
847#endif
848 } else {
849 // No config file
850#ifndef _WIN32_WCE
851 result = system(QFile::encodeName(QString("%1 2> %2").arg(cmd, scriptErr.fileName())));
852#else
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 );
864 if (result != 0)
865 {
866 result = 0;
867 }
868 else
869 {
870 result = -1;
871 }
872#endif
873 }
874
875 // Copy script stderr to log file
876 {
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;
884 }
885 }
886 }
887
888 if (result) {
889 log() << m_currentFilename << ": !! An error occurred while running '" << cmd << "'" << endl;
890 return;
891 }
892
893 if (!m_oldConfig1) {
894 return; // Nothing to merge
895 }
896
897 if (m_debug) {
898 scriptOut.setAutoRemove(false);
899 log() << "Script output stored in " << scriptOut.fileName() << endl;
900 }
901
902 // Deleting old entries
903 {
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('[')) {
912 group = parseGroupString(line);
913 } else if (line.startsWith(QLatin1String("# DELETE "))) {
914 QString key = line.mid(9);
915 if (key[0] == '[') {
916 int j = key.lastIndexOf(']') + 1;
917 if (j > 0) {
918 group = parseGroupString(key.left(j));
919 key = key.mid(j);
920 }
921 }
922 KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, group);
923 cg.deleteEntry(key);
924 log() << m_currentFilename << ": Script removes " << m_oldFile << ":" << group << ":" << key << endl;
925 /*if (m_oldConfig2->deleteGroup(group, KConfig::Normal)) { // Delete group if empty.
926 log() << m_currentFilename << ": Removing empty group " << m_oldFile << ":" << group << endl;
927 } (this should be automatic)*/
928 } else if (line.startsWith(QLatin1String("# DELETEGROUP"))) {
929 QString str = line.mid(13).trimmed();
930 if (!str.isEmpty()) {
931 group = parseGroupString(str);
932 }
933 KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, group);
934 cg.deleteGroup();
935 log() << m_currentFilename << ": Script removes group " << m_oldFile << ":" << group << endl;
936 }
937 }
938 }
939 }
940
941 // Merging in new entries.
942 KConfig scriptOutConfig(scriptOut.fileName(), KConfig::NoGlobals);
943 if (m_newGroup.isEmpty()) {
944 // Copy "default" keys as members of "default" keys
945 copyGroup(&scriptOutConfig, QString(), m_newConfig, QString());
946 } else {
947 // Copy default keys as members of m_newGroup
948 KConfigGroup srcCg = KConfigUtils::openGroup(&scriptOutConfig, QStringList());
949 KConfigGroup dstCg = KConfigUtils::openGroup(m_newConfig, m_newGroup);
950 copyGroup(srcCg, dstCg);
951 }
952 Q_FOREACH(const QString &group, scriptOutConfig.groupList()) {
953 copyGroup(&scriptOutConfig, group, m_newConfig, group);
954 }
955}
956
957void KonfUpdate::resetOptions()
958{
959 m_bCopy = false;
960 m_bOverwrite = false;
961 m_arguments.clear();
962}
963
964
965extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
966{
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"));
971
972 KAboutData aboutData("kconf_update", 0, ki18n("KConf Update"),
973 "1.0.2",
974 ki18n("KDE Tool for updating user configuration files"),
975 KAboutData::License_GPL,
976 ki18n("(c) 2001, Waldo Bastian"));
977
978 aboutData.addAuthor(ki18n("Waldo Bastian"), KLocalizedString(), "bastian@kde.org");
979
980 KCmdLineArgs::init(argc, argv, &aboutData);
981 KCmdLineArgs::addCmdLineOptions(options);
982
983 KComponentData componentData(&aboutData);
984
985 KonfUpdate konfUpdate;
986
987 return 0;
988}
989