KConfig

kconf_update.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2001 Waldo Bastian <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include <config-kconf.h> // CMAKE_INSTALL_PREFIX
9 
10 #include <QDate>
11 #include <QFile>
12 #include <QTextStream>
13 #include <QTextCodec>
14 #include <QUrl>
15 #include <QTemporaryFile>
16 #include <QCoreApplication>
17 #include <QDir>
18 #include <QProcess>
19 #include <QDebug>
20 
21 #include <kconfig.h>
22 #include <kconfiggroup.h>
23 
24 #include <QStandardPaths>
25 #include <QCommandLineParser>
26 #include <QCommandLineOption>
27 
28 #include "kconf_update_debug.h"
29 #include "kconfigutils.h"
30 
31 // Convenience wrapper around qCDebug to prefix the output with metadata of
32 // the file.
33 #define qCDebugFile(CATEGORY) \
34  qCDebug(CATEGORY) << m_currentFilename << ':' << m_lineCount << ":'" << m_line << "': "
35 
36 class KonfUpdate
37 {
38 public:
39  KonfUpdate(QCommandLineParser *parser);
40  ~KonfUpdate();
41 
42  KonfUpdate(const KonfUpdate &) = delete;
43  KonfUpdate& operator=(const KonfUpdate &) = delete;
44 
45  QStringList findUpdateFiles(bool dirtyOnly);
46 
47  bool checkFile(const QString &filename);
48  void checkGotFile(const QString &_file, const QString &id);
49 
50  bool updateFile(const QString &filename);
51 
52  void gotId(const QString &_id);
53  void gotFile(const QString &_file);
54  void gotGroup(const QString &_group);
55  void gotRemoveGroup(const QString &_group);
56  void gotKey(const QString &_key);
57  void gotRemoveKey(const QString &_key);
58  void gotAllKeys();
59  void gotAllGroups();
60  void gotOptions(const QString &_options);
61  void gotScript(const QString &_script);
62  void gotScriptArguments(const QString &_arguments);
63  void resetOptions();
64 
65  void copyGroup(const KConfigBase *cfg1, const QString &group1,
66  KConfigBase *cfg2, const QString &group2);
67  void copyGroup(const KConfigGroup &cg1, KConfigGroup &cg2);
68  void copyOrMoveKey(const QStringList &srcGroupPath, const QString &srcKey, const QStringList &dstGroupPath, const QString &dstKey);
69  void copyOrMoveGroup(const QStringList &srcGroupPath, const QStringList &dstGroupPath);
70 
71  QStringList parseGroupString(const QString &_str);
72 
73 protected:
74  KConfig *m_config;
75  QString m_currentFilename;
76  bool m_skip;
77  bool m_skipFile;
78  bool m_debug;
79  QString m_id;
80 
81  QString m_oldFile;
82  QString m_newFile;
83  QString m_newFileName;
84  KConfig *m_oldConfig1; // Config to read keys from.
85  KConfig *m_oldConfig2; // Config to delete keys from.
86  KConfig *m_newConfig;
87 
88  QStringList m_oldGroup;
89  QStringList m_newGroup;
90 
91  bool m_bCopy;
92  bool m_bOverwrite;
93  bool m_bUseConfigInfo;
94  QString m_arguments;
95  QTextStream *m_textStream;
96  QFile *m_file;
97  QString m_line;
98  int m_lineCount;
99 };
100 
101 KonfUpdate::KonfUpdate(QCommandLineParser *parser)
102  : m_oldConfig1(nullptr), m_oldConfig2(nullptr), m_newConfig(nullptr),
103  m_bCopy(false), m_bOverwrite(false), m_textStream(nullptr),
104  m_file(nullptr), m_lineCount(-1)
105 {
106  bool updateAll = false;
107 
108  m_config = new KConfig(QStringLiteral("kconf_updaterc"));
109  KConfigGroup cg(m_config, QString());
110 
111  QStringList updateFiles;
112 
113  m_debug = parser->isSet(QStringLiteral("debug"));
114  if (m_debug) {
115  // The only way to enable debug reliably is through a filter rule.
116  // The category itself is const, so we can't just go around changing
117  // its mode. This can however be overridden by the environment, so
118  // we'll want to have a fallback warning if debug is not enabled
119  // after setting the filter.
120  QLoggingCategory::setFilterRules(QStringLiteral("%1.debug=true").arg(KCONF_UPDATE_LOG().categoryName()));
121  qDebug() << "Automatically enabled the debug logging category" << KCONF_UPDATE_LOG().categoryName();
122  if (!KCONF_UPDATE_LOG().isDebugEnabled()) {
123  qWarning("The debug logging category %s needs to be enabled manually to get debug output",
124  KCONF_UPDATE_LOG().categoryName());
125  }
126  }
127 
128  if (parser->isSet(QStringLiteral("testmode"))) {
130  }
131 
132  m_bUseConfigInfo = false;
133  if (parser->isSet(QStringLiteral("check"))) {
134  m_bUseConfigInfo = true;
135  const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kconf_update/" + parser->value(QStringLiteral("check")));
136  if (file.isEmpty()) {
137  qWarning("File '%s' not found.", parser->value(QStringLiteral("check")).toLocal8Bit().data());
138  qCDebug(KCONF_UPDATE_LOG) << "File" << parser->value(QStringLiteral("check")) << "passed on command line not found";
139  return;
140  }
141  updateFiles.append(file);
142  } else if (!parser->positionalArguments().isEmpty()) {
143  updateFiles += parser->positionalArguments();
144  } else {
145  if (cg.readEntry("autoUpdateDisabled", false)) {
146  return;
147  }
148  updateFiles = findUpdateFiles(true);
149  updateAll = true;
150  }
151 
152  for (const QString& file : qAsConst(updateFiles)) {
153  updateFile(file);
154  }
155 
156  if (updateAll && !cg.readEntry("updateInfoAdded", false)) {
157  cg.writeEntry("updateInfoAdded", true);
158  updateFiles = findUpdateFiles(false);
159 
160  for (QStringList::ConstIterator it = updateFiles.constBegin();
161  it != updateFiles.constEnd();
162  ++it) {
163  checkFile(*it);
164  }
165  updateFiles.clear();
166  }
167 }
168 
169 KonfUpdate::~KonfUpdate()
170 {
171  delete m_config;
172  delete m_file;
173  delete m_textStream;
174 }
175 
176 QStringList KonfUpdate::findUpdateFiles(bool dirtyOnly)
177 {
178  QStringList result;
179 
181  for (const QString &d : dirs) {
182  const QDir dir(d);
183 
184  const QStringList fileNames = dir.entryList(QStringList(QStringLiteral("*.upd")));
185  for (const QString &fileName : fileNames) {
186  const QString file = dir.filePath(fileName);
187  QFileInfo info(file);
188 
189  KConfigGroup cg(m_config, fileName);
190  const QDateTime ctime = QDateTime::fromSecsSinceEpoch(cg.readEntry("ctime", 0u));
191  const QDateTime mtime = QDateTime::fromSecsSinceEpoch(cg.readEntry("mtime", 0u));
192  if (!dirtyOnly ||
193  (ctime.isValid() && ctime != info.birthTime()) ||
194  mtime != info.lastModified()) {
195  result.append(file);
196  }
197  }
198  }
199  return result;
200 }
201 
202 bool KonfUpdate::checkFile(const QString &filename)
203 {
204  m_currentFilename = filename;
205  int i = m_currentFilename.lastIndexOf('/');
206  if (i != -1) {
207  m_currentFilename = m_currentFilename.mid(i + 1);
208  }
209  m_skip = true;
210  QFile file(filename);
211  if (!file.open(QIODevice::ReadOnly)) {
212  return false;
213  }
214 
215  QTextStream ts(&file);
216  ts.setCodec(QTextCodec::codecForName("ISO-8859-1"));
217  int lineCount = 0;
218  resetOptions();
219  QString id;
220  bool foundVersion = false;
221  while (!ts.atEnd()) {
222  const QString line = ts.readLine().trimmed();
223  if (line.startsWith(QLatin1String("Version=5"))) {
224  foundVersion = true;
225  }
226  ++lineCount;
227  if (line.isEmpty() || (line[0] == '#')) {
228  continue;
229  }
230  if (line.startsWith(QLatin1String("Id="))) {
231  if (!foundVersion) {
232  qCDebug(KCONF_UPDATE_LOG,
233  "Missing 'Version=5', file '%s' will be skipped.",
234  qUtf8Printable(filename));
235  return true;
236  }
237  id = m_currentFilename + ':' + line.mid(3);
238  } else if (line.startsWith(QLatin1String("File="))) {
239  checkGotFile(line.mid(5), id);
240  }
241  }
242 
243  return true;
244 }
245 
246 void KonfUpdate::checkGotFile(const QString &_file, const QString &id)
247 {
248  QString file;
249  int i = _file.indexOf(',');
250  if (i == -1) {
251  file = _file.trimmed();
252  } else {
253  file = _file.mid(i + 1).trimmed();
254  }
255 
256 // qDebug("File %s, id %s", file.toLatin1().constData(), id.toLatin1().constData());
257 
258  KConfig cfg(file, KConfig::SimpleConfig);
259  KConfigGroup cg = cfg.group("$Version");
260  QStringList ids = cg.readEntry("update_info", QStringList());
261  if (ids.contains(id)) {
262  return;
263  }
264  ids.append(id);
265  cg.writeEntry("update_info", ids);
266 }
267 
287 bool KonfUpdate::updateFile(const QString &filename)
288 {
289  m_currentFilename = filename;
290  int i = m_currentFilename.lastIndexOf('/');
291  if (i != -1) {
292  m_currentFilename = m_currentFilename.mid(i + 1);
293  }
294  m_skip = true;
295  QFile file(filename);
296  if (!file.open(QIODevice::ReadOnly)) {
297  return false;
298  }
299 
300  qCDebug(KCONF_UPDATE_LOG) << "Checking update-file" << filename << "for new updates";
301 
302  QTextStream ts(&file);
303  ts.setCodec(QTextCodec::codecForName("ISO-8859-1"));
304  m_lineCount = 0;
305  resetOptions();
306  bool foundVersion = false;
307  while (!ts.atEnd()) {
308  m_line = ts.readLine().trimmed();
309  if (m_line.startsWith(QLatin1String("Version=5"))) {
310  foundVersion = true;
311  }
312  m_lineCount++;
313  if (m_line.isEmpty() || (m_line[0] == QLatin1Char('#'))) {
314  continue;
315  }
316  if (m_line.startsWith(QLatin1String("Id="))) {
317  if (!foundVersion) {
318  qCDebug(KCONF_UPDATE_LOG,
319  "Missing 'Version=5', file '%s' will be skipped.",
320  qUtf8Printable(filename));
321  break;
322  }
323  gotId(m_line.mid(3));
324  } else if (m_skip) {
325  continue;
326  } else if (m_line.startsWith(QLatin1String("Options="))) {
327  gotOptions(m_line.mid(8));
328  } else if (m_line.startsWith(QLatin1String("File="))) {
329  gotFile(m_line.mid(5));
330  } else if (m_skipFile) {
331  continue;
332  } else if (m_line.startsWith(QLatin1String("Group="))) {
333  gotGroup(m_line.mid(6));
334  } else if (m_line.startsWith(QLatin1String("RemoveGroup="))) {
335  gotRemoveGroup(m_line.mid(12));
336  resetOptions();
337  } else if (m_line.startsWith(QLatin1String("Script="))) {
338  gotScript(m_line.mid(7));
339  resetOptions();
340  } else if (m_line.startsWith(QLatin1String("ScriptArguments="))) {
341  gotScriptArguments(m_line.mid(16));
342  } else if (m_line.startsWith(QLatin1String("Key="))) {
343  gotKey(m_line.mid(4));
344  resetOptions();
345  } else if (m_line.startsWith(QLatin1String("RemoveKey="))) {
346  gotRemoveKey(m_line.mid(10));
347  resetOptions();
348  } else if (m_line == QLatin1String("AllKeys")) {
349  gotAllKeys();
350  resetOptions();
351  } else if (m_line == QLatin1String("AllGroups")) {
352  gotAllGroups();
353  resetOptions();
354  } else {
355  qCDebugFile(KCONF_UPDATE_LOG) << "Parse error";
356  }
357  }
358  // Flush.
359  gotId(QString());
360 
361  QFileInfo info(filename);
362  KConfigGroup cg(m_config, m_currentFilename);
363  if (info.birthTime().isValid()) {
364  cg.writeEntry("ctime", info.birthTime().toSecsSinceEpoch());
365  }
366  cg.writeEntry("mtime", info.lastModified().toSecsSinceEpoch());
367  cg.sync();
368  return true;
369 }
370 
371 void KonfUpdate::gotId(const QString &_id)
372 {
373  if (!m_id.isEmpty() && !m_skip) {
374  KConfigGroup cg(m_config, m_currentFilename);
375 
376  QStringList ids = cg.readEntry("done", QStringList());
377  if (!ids.contains(m_id)) {
378  ids.append(m_id);
379  cg.writeEntry("done", ids);
380  cg.sync();
381  }
382  }
383 
384  // Flush pending changes
385  gotFile(QString());
386  KConfigGroup cg(m_config, m_currentFilename);
387 
388  QStringList ids = cg.readEntry("done", QStringList());
389  if (!_id.isEmpty()) {
390  if (ids.contains(_id)) {
391  //qDebug("Id '%s' was already in done-list", _id.toLatin1().constData());
392  if (!m_bUseConfigInfo) {
393  m_skip = true;
394  return;
395  }
396  }
397  m_skip = false;
398  m_skipFile = false;
399  m_id = _id;
400  if (m_bUseConfigInfo) {
401  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Checking update" << _id;
402  } else {
403  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Found new update" << _id;
404  }
405  }
406 }
407 
408 void KonfUpdate::gotFile(const QString &_file)
409 {
410  // Reset group
411  gotGroup(QString());
412 
413  if (!m_oldFile.isEmpty()) {
414  // Close old file.
415  delete m_oldConfig1;
416  m_oldConfig1 = nullptr;
417 
418  KConfigGroup cg(m_oldConfig2, "$Version");
419  QStringList ids = cg.readEntry("update_info", QStringList());
420  QString cfg_id = m_currentFilename + ':' + m_id;
421  if (!ids.contains(cfg_id) && !m_skip) {
422  ids.append(cfg_id);
423  cg.writeEntry("update_info", ids);
424  }
425  cg.sync();
426  delete m_oldConfig2;
427  m_oldConfig2 = nullptr;
428 
430  QFileInfo info(file);
431  if (info.exists() && info.size() == 0) {
432  // Delete empty file.
433  QFile::remove(file);
434  }
435 
436  m_oldFile.clear();
437  }
438  if (!m_newFile.isEmpty()) {
439  // Close new file.
440  KConfigGroup cg(m_newConfig, "$Version");
441  QStringList ids = cg.readEntry("update_info", QStringList());
442  QString cfg_id = m_currentFilename + ':' + m_id;
443  if (!ids.contains(cfg_id) && !m_skip) {
444  ids.append(cfg_id);
445  cg.writeEntry("update_info", ids);
446  }
447  m_newConfig->sync();
448  delete m_newConfig;
449  m_newConfig = nullptr;
450 
451  m_newFile.clear();
452  }
453  m_newConfig = nullptr;
454 
455  int i = _file.indexOf(',');
456  if (i == -1) {
457  m_oldFile = _file.trimmed();
458  } else {
459  m_oldFile = _file.left(i).trimmed();
460  m_newFile = _file.mid(i + 1).trimmed();
461  if (m_oldFile == m_newFile) {
462  m_newFile.clear();
463  }
464  }
465 
466  if (!m_oldFile.isEmpty()) {
467  m_oldConfig2 = new KConfig(m_oldFile, KConfig::NoGlobals);
468  QString cfg_id = m_currentFilename + ':' + m_id;
469  KConfigGroup cg(m_oldConfig2, "$Version");
470  QStringList ids = cg.readEntry("update_info", QStringList());
471  if (ids.contains(cfg_id)) {
472  m_skip = true;
473  m_newFile.clear();
474  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Skipping update" << m_id;
475  }
476 
477  if (!m_newFile.isEmpty()) {
478  m_newConfig = new KConfig(m_newFile, KConfig::NoGlobals);
479  KConfigGroup cg(m_newConfig, "$Version");
480  ids = cg.readEntry("update_info", QStringList());
481  if (ids.contains(cfg_id)) {
482  m_skip = true;
483  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Skipping update" << m_id;
484  }
485  } else {
486  m_newConfig = m_oldConfig2;
487  }
488 
489  m_oldConfig1 = new KConfig(m_oldFile, KConfig::NoGlobals);
490  } else {
491  m_newFile.clear();
492  }
493  m_newFileName = m_newFile;
494  if (m_newFileName.isEmpty()) {
495  m_newFileName = m_oldFile;
496  }
497 
498  m_skipFile = false;
499  if (!m_oldFile.isEmpty()) { // if File= is specified, it doesn't exist, is empty or contains only kconf_update's [$Version] group, skip
500  if (m_oldConfig1 != nullptr
501  && (m_oldConfig1->groupList().isEmpty()
502  || (m_oldConfig1->groupList().count() == 1 && m_oldConfig1->groupList().at(0) == QLatin1String("$Version")))) {
503  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": File" << m_oldFile << "does not exist or empty, skipping";
504  m_skipFile = true;
505  }
506  }
507 }
508 
509 QStringList KonfUpdate::parseGroupString(const QString &str)
510 {
511  bool ok;
512  QString error;
513  QStringList lst = KConfigUtils::parseGroupString(str, &ok, &error);
514  if (!ok) {
515  qCDebugFile(KCONF_UPDATE_LOG) << error;
516  }
517  return lst;
518 }
519 
520 void KonfUpdate::gotGroup(const QString &_group)
521 {
522  QString group = _group.trimmed();
523  if (group.isEmpty()) {
524  m_oldGroup = m_newGroup = QStringList();
525  return;
526  }
527 
528  QStringList tokens = group.split(',');
529  m_oldGroup = parseGroupString(tokens.at(0));
530  if (tokens.count() == 1) {
531  m_newGroup = m_oldGroup;
532  } else {
533  m_newGroup = parseGroupString(tokens.at(1));
534  }
535 }
536 
537 void KonfUpdate::gotRemoveGroup(const QString &_group)
538 {
539  m_oldGroup = parseGroupString(_group);
540 
541  if (!m_oldConfig1) {
542  qCDebugFile(KCONF_UPDATE_LOG) << "RemoveGroup without previous File specification";
543  return;
544  }
545 
546  KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, m_oldGroup);
547  if (!cg.exists()) {
548  return;
549  }
550  // Delete group.
551  cg.deleteGroup();
552  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": RemoveGroup removes group" << m_oldFile << ":" << m_oldGroup;
553 }
554 
555 void KonfUpdate::gotKey(const QString &_key)
556 {
557  QString oldKey, newKey;
558  int i = _key.indexOf(',');
559  if (i == -1) {
560  oldKey = _key.trimmed();
561  newKey = oldKey;
562  } else {
563  oldKey = _key.left(i).trimmed();
564  newKey = _key.mid(i + 1).trimmed();
565  }
566 
567  if (oldKey.isEmpty() || newKey.isEmpty()) {
568  qCDebugFile(KCONF_UPDATE_LOG) << "Key specifies invalid key";
569  return;
570  }
571  if (!m_oldConfig1) {
572  qCDebugFile(KCONF_UPDATE_LOG) << "Key without previous File specification";
573  return;
574  }
575  copyOrMoveKey(m_oldGroup, oldKey, m_newGroup, newKey);
576 }
577 
578 void KonfUpdate::copyOrMoveKey(const QStringList &srcGroupPath, const QString &srcKey, const QStringList &dstGroupPath, const QString &dstKey)
579 {
580  KConfigGroup dstCg = KConfigUtils::openGroup(m_newConfig, dstGroupPath);
581  if (!m_bOverwrite && dstCg.hasKey(dstKey)) {
582  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Skipping" << m_newFileName << ":" << dstCg.name() << ":" << dstKey << ", already exists.";
583  return;
584  }
585 
586  KConfigGroup srcCg = KConfigUtils::openGroup(m_oldConfig1, srcGroupPath);
587  if (!srcCg.hasKey(srcKey)) {
588  return;
589  }
590  QString value = srcCg.readEntry(srcKey, QString());
591  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Updating" << m_newFileName << ":" << dstCg.name() << ":" << dstKey << "to" << value;
592  dstCg.writeEntry(dstKey, value);
593 
594  if (m_bCopy) {
595  return; // Done.
596  }
597 
598  // Delete old entry
599  if (m_oldConfig2 == m_newConfig
600  && srcGroupPath == dstGroupPath
601  && srcKey == dstKey) {
602  return; // Don't delete!
603  }
604  KConfigGroup srcCg2 = KConfigUtils::openGroup(m_oldConfig2, srcGroupPath);
605  srcCg2.deleteEntry(srcKey);
606  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Removing" << m_oldFile << ":" << srcCg2.name() << ":" << srcKey << ", moved.";
607 }
608 
609 void KonfUpdate::copyOrMoveGroup(const QStringList &srcGroupPath, const QStringList &dstGroupPath)
610 {
611  KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig1, srcGroupPath);
612 
613  // Keys
614  const QStringList lstKeys = cg.keyList();
615  for (const QString &key : lstKeys) {
616  copyOrMoveKey(srcGroupPath, key, dstGroupPath, key);
617  }
618 
619  // Subgroups
620  const QStringList lstGroup = cg.groupList();
621  for (const QString &group : lstGroup) {
622  const QStringList groupPath(group);
623  copyOrMoveGroup(srcGroupPath + groupPath, dstGroupPath + groupPath);
624  }
625 }
626 
627 void KonfUpdate::gotRemoveKey(const QString &_key)
628 {
629  QString key = _key.trimmed();
630 
631  if (key.isEmpty()) {
632  qCDebugFile(KCONF_UPDATE_LOG) << "RemoveKey specifies invalid key";
633  return;
634  }
635 
636  if (!m_oldConfig1) {
637  qCDebugFile(KCONF_UPDATE_LOG) << "Key without previous File specification";
638  return;
639  }
640 
641  KConfigGroup cg1 = KConfigUtils::openGroup(m_oldConfig1, m_oldGroup);
642  if (!cg1.hasKey(key)) {
643  return;
644  }
645  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": RemoveKey removes" << m_oldFile << ":" << m_oldGroup << ":" << key;
646 
647  // Delete old entry
648  KConfigGroup cg2 = KConfigUtils::openGroup(m_oldConfig2, m_oldGroup);
649  cg2.deleteEntry(key);
650  /*if (m_oldConfig2->deleteGroup(m_oldGroup, KConfig::Normal)) { // Delete group if empty.
651  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Removing empty group " << m_oldFile << ":" << m_oldGroup;
652  } (this should be automatic)*/
653 }
654 
655 void KonfUpdate::gotAllKeys()
656 {
657  if (!m_oldConfig1) {
658  qCDebugFile(KCONF_UPDATE_LOG) << "AllKeys without previous File specification";
659  return;
660  }
661 
662  copyOrMoveGroup(m_oldGroup, m_newGroup);
663 }
664 
665 void KonfUpdate::gotAllGroups()
666 {
667  if (!m_oldConfig1) {
668  qCDebugFile(KCONF_UPDATE_LOG) << "AllGroups without previous File specification";
669  return;
670  }
671 
672  const QStringList allGroups = m_oldConfig1->groupList();
673  for (QStringList::ConstIterator it = allGroups.begin();
674  it != allGroups.end(); ++it) {
675  m_oldGroup = QStringList() << *it;
676  m_newGroup = m_oldGroup;
677  gotAllKeys();
678  }
679 }
680 
681 void KonfUpdate::gotOptions(const QString &_options)
682 {
683  const QStringList options = _options.split(',');
684  for (QStringList::ConstIterator it = options.begin();
685  it != options.end();
686  ++it) {
687  if ((*it).toLower().trimmed() == QLatin1String("copy")) {
688  m_bCopy = true;
689  }
690 
691  if ((*it).toLower().trimmed() == QLatin1String("overwrite")) {
692  m_bOverwrite = true;
693  }
694  }
695 }
696 
697 void KonfUpdate::copyGroup(const KConfigBase *cfg1, const QString &group1,
698  KConfigBase *cfg2, const QString &group2)
699 {
700  KConfigGroup cg2 = cfg2->group(group2);
701  copyGroup(cfg1->group(group1), cg2);
702 }
703 
704 void KonfUpdate::copyGroup(const KConfigGroup &cg1, KConfigGroup &cg2)
705 {
706  // Copy keys
709  it != list.constEnd(); ++it) {
710  if (m_bOverwrite || !cg2.hasKey(it.key())) {
711  cg2.writeEntry(it.key(), it.value());
712  }
713  }
714 
715  // Copy subgroups
716  const QStringList lstGroup = cg1.groupList();
717  for (const QString &group : lstGroup) {
718  copyGroup(&cg1, group, &cg2, group);
719  }
720 }
721 
722 void KonfUpdate::gotScriptArguments(const QString &_arguments)
723 {
724  m_arguments = _arguments;
725 }
726 
727 void KonfUpdate::gotScript(const QString &_script)
728 {
729  QString script, interpreter;
730  int i = _script.indexOf(',');
731  if (i == -1) {
732  script = _script.trimmed();
733  } else {
734  script = _script.left(i).trimmed();
735  interpreter = _script.mid(i + 1).trimmed();
736  }
737 
738  if (script.isEmpty()) {
739  qCDebugFile(KCONF_UPDATE_LOG) << "Script fails to specify filename";
740  m_skip = true;
741  return;
742  }
743 
745  if (path.isEmpty()) {
746  if (interpreter.isEmpty()) {
747  path = CMAKE_INSTALL_PREFIX "/" LIB_INSTALL_DIR "/kconf_update_bin/" + script;
748  if (!QFile::exists(path)) {
749  path = QStandardPaths::findExecutable(script);
750  }
751  }
752 
753  if (path.isEmpty()) {
754  qCDebugFile(KCONF_UPDATE_LOG) << "Script" << script << "not found";
755  m_skip = true;
756  return;
757  }
758  }
759 
760  if (!m_arguments.isNull()) {
761  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Running script" << script << "with arguments" << m_arguments;
762  } else {
763  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Running script" << script;
764  }
765 
766  QStringList args;
767  QString cmd;
768  if (interpreter.isEmpty()) {
769  cmd = path;
770  } else {
771  QString interpreterPath = QStandardPaths::findExecutable(interpreter);
772  if (interpreterPath.isEmpty()) {
773  qCDebugFile(KCONF_UPDATE_LOG) << "Cannot find interpreter" << interpreter;
774  m_skip = true;
775  return;
776  }
777  cmd = interpreterPath;
778  args << path;
779  }
780 
781  if (!m_arguments.isNull()) {
782  args += m_arguments;
783  }
784 
785  QTemporaryFile scriptIn;
786  scriptIn.open();
787  QTemporaryFile scriptOut;
788  scriptOut.open();
789 
790  int result;
791  QProcess proc;
793  proc.setStandardInputFile(scriptIn.fileName());
794  proc.setStandardOutputFile(scriptOut.fileName());
795  if (m_oldConfig1) {
796  if (m_debug) {
797  scriptIn.setAutoRemove(false);
798  qCDebug(KCONF_UPDATE_LOG) << "Script input stored in" << scriptIn.fileName();
799  }
800  KConfig cfg(scriptIn.fileName(), KConfig::SimpleConfig);
801 
802  if (m_oldGroup.isEmpty()) {
803  // Write all entries to tmpFile;
804  const QStringList grpList = m_oldConfig1->groupList();
805  for (QStringList::ConstIterator it = grpList.begin();
806  it != grpList.end();
807  ++it) {
808  copyGroup(m_oldConfig1, *it, &cfg, *it);
809  }
810  } else {
811  KConfigGroup cg1 = KConfigUtils::openGroup(m_oldConfig1, m_oldGroup);
812  KConfigGroup cg2(&cfg, QString());
813  copyGroup(cg1, cg2);
814  }
815  cfg.sync();
816  }
817 
818  qCDebug(KCONF_UPDATE_LOG) << "About to run" << cmd;
819  if (m_debug) {
820  QFile scriptFile(path);
821  if (scriptFile.open(QIODevice::ReadOnly)) {
822  qCDebug(KCONF_UPDATE_LOG) << "Script contents is:\n" << scriptFile.readAll();
823  }
824  }
825  proc.start(cmd, args);
826  if (!proc.waitForFinished(60000)) {
827  qCDebugFile(KCONF_UPDATE_LOG) << "update script did not terminate within 60 seconds:" << cmd;
828  m_skip = true;
829  return;
830  }
831  result = proc.exitCode();
832 
833  // Copy script stderr to log file
834  {
836  ts.setCodec(QTextCodec::codecForName("UTF-8"));
837  while (!ts.atEnd()) {
838  QString line = ts.readLine();
839  qCDebug(KCONF_UPDATE_LOG) << "[Script]" << line;
840  }
841  }
842  proc.close();
843 
844  if (result) {
845  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": !! An error occurred while running" << cmd;
846  return;
847  }
848 
849  qCDebug(KCONF_UPDATE_LOG) << "Successfully ran" << cmd;
850 
851  if (!m_oldConfig1) {
852  return; // Nothing to merge
853  }
854 
855  if (m_debug) {
856  scriptOut.setAutoRemove(false);
857  qCDebug(KCONF_UPDATE_LOG) << "Script output stored in" << scriptOut.fileName();
858  QFile output(scriptOut.fileName());
859  if (output.open(QIODevice::ReadOnly)) {
860  qCDebug(KCONF_UPDATE_LOG) << "Script output is:\n" << output.readAll();
861  }
862  }
863 
864  // Deleting old entries
865  {
866  QStringList group = m_oldGroup;
867  QFile output(scriptOut.fileName());
868  if (output.open(QIODevice::ReadOnly)) {
869  QTextStream ts(&output);
870  ts.setCodec(QTextCodec::codecForName("UTF-8"));
871  while (!ts.atEnd()) {
872  QString line = ts.readLine();
873  if (line.startsWith('[')) {
874  group = parseGroupString(line);
875  } else if (line.startsWith(QLatin1String("# DELETE "))) {
876  QString key = line.mid(9);
877  if (key[0] == '[') {
878  int j = key.lastIndexOf(']') + 1;
879  if (j > 0) {
880  group = parseGroupString(key.left(j));
881  key = key.mid(j);
882  }
883  }
884  KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, group);
885  cg.deleteEntry(key);
886  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Script removes" << m_oldFile << ":" << group << ":" << key;
887  /*if (m_oldConfig2->deleteGroup(group, KConfig::Normal)) { // Delete group if empty.
888  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Removing empty group " << m_oldFile << ":" << group;
889  } (this should be automatic)*/
890  } else if (line.startsWith(QLatin1String("# DELETEGROUP"))) {
891  QString str = line.mid(13).trimmed();
892  if (!str.isEmpty()) {
893  group = parseGroupString(str);
894  }
895  KConfigGroup cg = KConfigUtils::openGroup(m_oldConfig2, group);
896  cg.deleteGroup();
897  qCDebug(KCONF_UPDATE_LOG) << m_currentFilename << ": Script removes group" << m_oldFile << ":" << group;
898  }
899  }
900  }
901  }
902 
903  // Merging in new entries.
904  KConfig scriptOutConfig(scriptOut.fileName(), KConfig::NoGlobals);
905  if (m_newGroup.isEmpty()) {
906  // Copy "default" keys as members of "default" keys
907  copyGroup(&scriptOutConfig, QString(), m_newConfig, QString());
908  } else {
909  // Copy default keys as members of m_newGroup
910  KConfigGroup srcCg = KConfigUtils::openGroup(&scriptOutConfig, QStringList());
911  KConfigGroup dstCg = KConfigUtils::openGroup(m_newConfig, m_newGroup);
912  copyGroup(srcCg, dstCg);
913  }
914  const QStringList lstGroup = scriptOutConfig.groupList();
915  for (const QString &group : lstGroup) {
916  copyGroup(&scriptOutConfig, group, m_newConfig, group);
917  }
918 }
919 
920 void KonfUpdate::resetOptions()
921 {
922  m_bCopy = false;
923  m_bOverwrite = false;
924  m_arguments.clear();
925 }
926 
927 int main(int argc, char **argv)
928 {
929  QCoreApplication app(argc, argv);
930  app.setApplicationVersion(QStringLiteral("1.1"));
931 
932  QCommandLineParser parser;
933  parser.addVersionOption();
934  parser.setApplicationDescription(QCoreApplication::translate("main", "KDE Tool for updating user configuration files"));
935  parser.addHelpOption();
936  parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("debug"), QCoreApplication::translate("main", "Keep output results from scripts")));
937  parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("testmode"), QCoreApplication::translate("main", "For unit tests only: use test directories to stay away from the user's real files")));
938  parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("check"), QCoreApplication::translate("main", "Check whether config file itself requires updating"), QStringLiteral("update-file")));
939  parser.addPositionalArgument(QStringLiteral("files"), QCoreApplication::translate("main", "File(s) to read update instructions from"), QStringLiteral("[files...]"));
940 
941  // TODO aboutData.addAuthor(ki18n("Waldo Bastian"), KLocalizedString(), "[email protected]");
942 
943  parser.process(app);
944  KonfUpdate konfUpdate(&parser);
945 
946  return 0;
947 }
void setCodec(QTextCodec *codec)
void clear()
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool sync() override
QStringList positionalArguments() const const
QString writableLocation(QStandardPaths::StandardLocation type)
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
void setStandardInputFile(const QString &fileName)
bool remove()
QMap::const_iterator constBegin() const const
const T & at(int i) const const
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QCommandLineOption addVersionOption()
QString findExecutable(const QString &executableName, const QStringList &paths)
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
Just a single config file.
Definition: kconfig.h:86
KConfigGroup group(const QByteArray &group)
Returns an object for the named subgroup.
Definition: kconfigbase.cpp:31
void writeEntry(const QString &key, const QVariant &value, WriteConfigFlags pFlags=Normal)
Writes a value to the configuration object.
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
bool exists() const const
void deleteGroup(WriteConfigFlags pFlags=Normal)
Delete all entries in the entire group.
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void clear()
void deleteEntry(const QString &pKey, WriteConfigFlags pFlags=Normal)
Deletes the entry specified by pKey in the current group.
int count(const T &value) const const
void append(const T &value)
Cascade to system settings, but omit user&#39;s globals.
Definition: kconfig.h:88
void setStandardOutputFile(const QString &fileName, QIODevice::OpenMode mode)
bool exists() const
Check whether the containing KConfig object acutally contains a group with this name.
void setAutoRemove(bool b)
bool isEmpty() const const
bool isEmpty() const const
QString trimmed() const const
QMap::const_iterator constEnd() const const
void setFilterRules(const QString &rules)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray readAll()
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QCommandLineOption addHelpOption()
void setApplicationDescription(const QString &description)
bool isSet(const QString &name) const const
Interface to interact with configuration.
Definition: kconfigbase.h:30
QStringList keyList() const
Returns a list of keys this group contains.
virtual void close() override
QList::iterator end()
QByteArray toLocal8Bit() const const
QString name() const
The name of this group.
bool hasKey(const QString &key) const
Checks whether the key has an entry in this group.
virtual QString fileName() const const override
void process(const QStringList &arguments)
bool isValid() const const
void setProcessChannelMode(QProcess::ProcessChannelMode mode)
A class for one specific group in a KConfig object.
Definition: kconfiggroup.h:38
The central class of the KDE configuration data system.
Definition: kconfig.h:56
QString mid(int position, int n) const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QTextCodec * codecForName(const QByteArray &name)
typedef ConstIterator
bool addOption(const QCommandLineOption &option)
char * data()
QString left(int n) const const
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
void setTestModeEnabled(bool testMode)
T readEntry(const QString &key, const T &aDefault) const
Reads the value of an entry specified by pKey in the current group.
Definition: kconfiggroup.h:236
QString value(const QString &optionName) const const
QList::iterator begin()
int exitCode() const const
QStringList groupList() const override
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QMap< QString, QString > entryMap() const
Returns a map (tree) of entries for all entries in this group.
QByteArray readAllStandardError()
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
KStandardDirs * dirs()
bool waitForFinished(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jul 9 2020 22:47:24 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.