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 | |
49 | class KonfUpdate |
50 | { |
51 | public: |
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 | |
85 | protected: |
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 | |
113 | KonfUpdate::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 | |
173 | KonfUpdate::~KonfUpdate() |
174 | { |
175 | delete m_config; |
176 | delete m_file; |
177 | delete m_textStream; |
178 | } |
179 | |
180 | QTextStream & operator<<(QTextStream & stream, const QStringList & lst) |
181 | { |
182 | stream << lst.join(", " ); |
183 | return stream; |
184 | } |
185 | |
186 | QTextStream & |
187 | KonfUpdate::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 | |
205 | QTextStream & |
206 | KonfUpdate::logFileError() |
207 | { |
208 | return log() << m_currentFilename << ':' << m_lineCount << ":'" << m_line << "': " ; |
209 | } |
210 | |
211 | QStringList 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 | |
238 | bool 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 | |
272 | void 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 | **/ |
313 | bool 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 | |
389 | void 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 | |
426 | void 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 | |
529 | QStringList 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 | |
540 | void 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 | |
557 | void 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 | |
576 | void 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 | |
599 | void 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 | |
629 | void 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 | |
645 | void 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 | |
673 | void 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 | |
683 | void 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 | |
699 | void 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 | |
715 | void 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 | |
723 | void 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 | |
740 | void KonfUpdate::gotScriptArguments(const QString &_arguments) |
741 | { |
742 | m_arguments = _arguments; |
743 | } |
744 | |
745 | void 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 | |
957 | void KonfUpdate::resetOptions() |
958 | { |
959 | m_bCopy = false; |
960 | m_bOverwrite = false; |
961 | m_arguments.clear(); |
962 | } |
963 | |
964 | |
965 | extern "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 | |