3 * This file is part of the KDE libraries
4 * Copyright (c) 2001 Waldo Bastian <bastian@kde.org>
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.
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.
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.
21 #include <sys/types.h>
27 #include <QtCore/QDate>
28 #include <QtCore/QFile>
29 #include <QtCore/QTextStream>
30 #include <QtCore/QTextCodec>
33 #include <kconfiggroup.h>
35 #include <kcmdlineargs.h>
37 #include <kstandarddirs.h>
38 #include <kaboutdata.h>
39 #include <kcomponentdata.h>
40 #include <ktemporaryfile.h>
48 QStringList
findUpdateFiles(bool dirtyOnly
);
52 bool checkFile(const QString
&filename
);
53 void checkGotFile(const QString
&_file
, const QString
&id
);
55 bool updateFile(const QString
&filename
);
57 void gotId(const QString
&_id
);
58 void gotFile(const QString
&_file
);
59 void gotGroup(const QString
&_group
);
60 void gotRemoveGroup(const QString
&_group
);
61 void gotKey(const QString
&_key
);
62 void gotRemoveKey(const QString
&_key
);
65 void gotOptions(const QString
&_options
);
66 void gotScript(const QString
&_script
);
67 void gotScriptArguments(const QString
&_arguments
);
70 void copyGroup(KConfig
*cfg1
, const QString
&group1
,
71 KConfig
*cfg2
, const QString
&group2
);
75 QString currentFilename
;
84 KConfig
*oldConfig1
; // Config to read keys from.
85 KConfig
*oldConfig2
; // Config to delete keys from.
95 bool m_bUseConfigInfo
;
97 QTextStream
*m_textStream
;
103 KonfUpdate::KonfUpdate()
104 : m_textStream(0), m_file(0)
106 bool updateAll
= false;
111 config
= new KConfig("kconf_updaterc");
112 KConfigGroup
cg(config
, QString());
114 QStringList updateFiles
;
115 KCmdLineArgs
*args
=KCmdLineArgs::parsedArgs();
117 debug
= args
->isSet("debug");
119 m_bUseConfigInfo
= false;
120 if (args
->isSet("check"))
122 m_bUseConfigInfo
= true;
123 QString file
= KStandardDirs::locate("data", "kconf_update/"+args
->getOption("check"));
126 qWarning("File '%s' not found.", args
->getOption("check").toLocal8Bit().data());
127 log() << "File '" << args
->getOption("check") << "' passed on command line not found" << endl
;
130 updateFiles
.append(file
);
132 else if (args
->count())
134 for(int i
= 0; i
< args
->count(); i
++)
136 KUrl url
= args
->url(i
);
137 if (!url
.isLocalFile())
138 KCmdLineArgs::usageError(i18n("Only local files are supported."));
139 updateFiles
.append(url
.path());
144 if (cg
.readEntry("autoUpdateDisabled", false))
146 updateFiles
= findUpdateFiles(true);
150 for(QStringList::ConstIterator it
= updateFiles
.constBegin();
151 it
!= updateFiles
.constEnd();
157 if (updateAll
&& !cg
.readEntry("updateInfoAdded", false))
159 cg
.writeEntry("updateInfoAdded", true);
160 updateFiles
= findUpdateFiles(false);
162 for(QStringList::ConstIterator it
= updateFiles
.constBegin();
163 it
!= updateFiles
.constEnd();
172 KonfUpdate::~KonfUpdate()
184 QString file
= KStandardDirs::locateLocal("data", "kconf_update/log/update.log");
185 m_file
= new QFile(file
);
186 if (m_file
->open(QIODevice::WriteOnly
| QIODevice::Append
))
188 m_textStream
= new QTextStream(m_file
);
193 m_textStream
= new QTextStream(stderr
, QIODevice::WriteOnly
);
197 (*m_textStream
) << QDateTime::currentDateTime().toString( Qt::ISODate
) << " ";
199 return *m_textStream
;
202 QStringList
KonfUpdate::findUpdateFiles(bool dirtyOnly
)
205 const QStringList list
= KGlobal::dirs()->findAllResources("data", "kconf_update/*.upd",
206 KStandardDirs::NoDuplicates
);
207 for(QStringList::ConstIterator it
= list
.constBegin();
208 it
!= list
.constEnd();
212 KDE_struct_stat buff
;
213 if (KDE::stat(file
, &buff
) == 0)
215 int i
= file
.lastIndexOf('/');
217 file
= file
.mid(i
+1);
218 KConfigGroup
cg(config
, file
);
219 time_t ctime
= cg
.readEntry("ctime", 0);
220 time_t mtime
= cg
.readEntry("mtime", 0);
222 (ctime
!= buff
.st_ctime
) || (mtime
!= buff
.st_mtime
))
231 bool KonfUpdate::checkFile(const QString
&filename
)
233 currentFilename
= filename
;
234 int i
= currentFilename
.lastIndexOf('/');
236 currentFilename
= currentFilename
.mid(i
+1);
238 QFile
file(filename
);
239 if (!file
.open(QIODevice::ReadOnly
))
242 QTextStream
ts(&file
);
243 ts
.setCodec(QTextCodec::codecForName("ISO-8859-1"));
249 QString line
= ts
.readLine().trimmed();
251 if (line
.isEmpty() || (line
[0] == '#'))
253 if (line
.startsWith("Id="))
254 id
= currentFilename
+':'+line
.mid(3);
255 else if (line
.startsWith("File="))
256 checkGotFile(line
.mid(5), id
);
262 void KonfUpdate::checkGotFile(const QString
&_file
, const QString
&id
)
265 int i
= _file
.indexOf(',');
268 file
= _file
.trimmed();
272 file
= _file
.mid(i
+1).trimmed();
275 // qDebug("File %s, id %s", file.toLatin1().constData(), id.toLatin1().constData());
277 KConfig
cfg(file
, KConfig::SimpleConfig
);
278 KConfigGroup
cg(&cfg
, "$Version");
279 QStringList ids
= cg
.readEntry("update_info", QStringList());
280 if (ids
.contains(id
))
283 cg
.writeEntry("update_info", ids
);
290 * File=oldfile[,newfile]
292 * Group=oldgroup[,newgroup]
293 * RemoveGroup=oldgroup
294 * Options=[copy,][overwrite,]
295 * Key=oldkey[,newkey]
298 * Keys= [Options](AllKeys|(Key|RemoveKey)*)
299 * ScriptArguments=arguments
300 * Script=scriptfile[,interpreter]
303 * (Id,(File(Group,Keys)*)*)*
305 bool KonfUpdate::updateFile(const QString
&filename
)
307 currentFilename
= filename
;
308 int i
= currentFilename
.lastIndexOf('/');
310 currentFilename
= currentFilename
.mid(i
+1);
312 QFile
file(filename
);
313 if (!file
.open(QIODevice::ReadOnly
))
316 log() << "Checking update-file '" << filename
<< "' for new updates" << endl
;
318 QTextStream
ts(&file
);
319 ts
.setCodec(QTextCodec::codecForName("ISO-8859-1"));
324 m_line
= ts
.readLine().trimmed();
326 if (m_line
.isEmpty() || (m_line
[0] == '#'))
328 if (m_line
.startsWith("Id="))
329 gotId(m_line
.mid(3));
332 else if (m_line
.startsWith("Options="))
333 gotOptions(m_line
.mid(8));
334 else if (m_line
.startsWith("File="))
335 gotFile(m_line
.mid(5));
338 else if (m_line
.startsWith("Group="))
339 gotGroup(m_line
.mid(6));
340 else if (m_line
.startsWith("RemoveGroup="))
342 gotRemoveGroup(m_line
.mid(12));
345 else if (m_line
.startsWith("Script="))
347 gotScript(m_line
.mid(7));
350 else if (m_line
.startsWith("ScriptArguments="))
351 gotScriptArguments(m_line
.mid(16));
352 else if (m_line
.startsWith("Key="))
354 gotKey(m_line
.mid(4));
357 else if (m_line
.startsWith("RemoveKey="))
359 gotRemoveKey(m_line
.mid(10));
362 else if (m_line
== "AllKeys")
367 else if (m_line
== "AllGroups")
374 log() << currentFilename
<< ": parse error in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
380 KDE_struct_stat buff
;
381 KDE::stat(filename
, &buff
);
382 KConfigGroup
cg(config
, currentFilename
);
383 cg
.writeEntry("ctime", int(buff
.st_ctime
));
384 cg
.writeEntry("mtime", int(buff
.st_mtime
));
391 void KonfUpdate::gotId(const QString
&_id
)
393 if (!id
.isEmpty() && !skip
)
395 KConfigGroup
cg(config
, currentFilename
);
397 QStringList ids
= cg
.readEntry("done", QStringList());
398 if (!ids
.contains(id
))
401 cg
.writeEntry("done", ids
);
406 // Flush pending changes
408 KConfigGroup
cg(config
, currentFilename
);
410 QStringList ids
= cg
.readEntry("done", QStringList());
413 if (ids
.contains(_id
))
415 //qDebug("Id '%s' was already in done-list", _id.toLatin1().constData());
416 if (!m_bUseConfigInfo
)
425 if (m_bUseConfigInfo
)
426 log() << currentFilename
<< ": Checking update '" << _id
<< "'" << endl
;
428 log() << currentFilename
<< ": Found new update '" << _id
<< "'" << endl
;
432 void KonfUpdate::gotFile(const QString
&_file
)
437 if (!oldFile
.isEmpty())
443 KConfigGroup
cg(oldConfig2
, "$Version");
444 QStringList ids
= cg
.readEntry("update_info", QStringList());
445 QString cfg_id
= currentFilename
+ ':' + id
;
446 if (!ids
.contains(cfg_id
) && !skip
)
449 cg
.writeEntry("update_info", ids
);
455 QString file
= KStandardDirs::locateLocal("config", oldFile
);
456 KDE_struct_stat s_buf
;
457 if (KDE::stat(file
, &s_buf
) == 0)
459 if (s_buf
.st_size
== 0)
461 // Delete empty file.
468 if (!newFile
.isEmpty())
471 KConfigGroup
cg(newConfig
, "$Version");
472 QStringList ids
= cg
.readEntry("update_info", QStringList());
473 QString cfg_id
= currentFilename
+ ':' + id
;
474 if (!ids
.contains(cfg_id
) && !skip
)
477 cg
.writeEntry("update_info", ids
);
487 int i
= _file
.indexOf(',');
490 oldFile
= _file
.trimmed();
494 oldFile
= _file
.left(i
).trimmed();
495 newFile
= _file
.mid(i
+1).trimmed();
496 if (oldFile
== newFile
)
500 if (!oldFile
.isEmpty())
502 oldConfig2
= new KConfig(oldFile
, KConfig::NoGlobals
);
503 QString cfg_id
= currentFilename
+ ':' + id
;
504 KConfigGroup
cg(oldConfig2
, "$Version");
505 QStringList ids
= cg
.readEntry("update_info", QStringList());
506 if (ids
.contains(cfg_id
))
510 log() << currentFilename
<< ": Skipping update '" << id
<< "'" << endl
;
513 if (!newFile
.isEmpty())
515 newConfig
= new KConfig(newFile
, KConfig::NoGlobals
);
516 KConfigGroup
cg(newConfig
, "$Version");
517 ids
= cg
.readEntry("update_info", QStringList());
518 if (ids
.contains(cfg_id
))
521 log() << currentFilename
<< ": Skipping update '" << id
<< "'" << endl
;
526 newConfig
= oldConfig2
;
529 oldConfig1
= new KConfig(oldFile
, KConfig::NoGlobals
);
535 newFileName
= newFile
;
536 if (newFileName
.isEmpty())
537 newFileName
= oldFile
;
540 if( !oldFile
.isEmpty())
541 { // if File= is specified, it doesn't exist, is empty or contains only kconf_update's [$Version] group, skip
542 if( oldConfig1
!= NULL
543 && ( oldConfig1
->groupList().isEmpty()
544 || ( oldConfig1
->groupList().count() == 1 && oldConfig1
->groupList().first() == "$Version" )))
546 log() << currentFilename
<< ": File '" << oldFile
<< "' does not exist or empty, skipping" << endl
;
552 void KonfUpdate::gotGroup(const QString
&_group
)
554 int i
= _group
.indexOf(',');
557 oldGroup
= _group
.trimmed();
562 oldGroup
= _group
.left(i
).trimmed();
563 newGroup
= _group
.mid(i
+1).trimmed();
567 void KonfUpdate::gotRemoveGroup(const QString
&_group
)
569 oldGroup
= _group
.trimmed();
573 log() << currentFilename
<< ": !! RemoveGroup without previous File specification in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
577 if (!oldConfig1
->hasGroup(oldGroup
))
580 oldConfig2
->deleteGroup(oldGroup
);
581 log() << currentFilename
<< ": RemoveGroup removes group " << oldFile
<< ":" << oldGroup
<< endl
;
585 void KonfUpdate::gotKey(const QString
&_key
)
587 int i
= _key
.indexOf(',');
590 oldKey
= _key
.trimmed();
595 oldKey
= _key
.left(i
).trimmed();
596 newKey
= _key
.mid(i
+1).trimmed();
599 if (oldKey
.isEmpty() || newKey
.isEmpty())
601 log() << currentFilename
<< ": !! Key specifies invalid key in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
606 log() << currentFilename
<< ": !! Key without previous File specification in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
609 KConfigGroup
cg1( oldConfig1
, oldGroup
);
610 if (!cg1
.hasKey(oldKey
))
612 QString value
= cg1
.readEntry(oldKey
, QString());
613 KConfigGroup
newFGroup( newConfig
, newGroup
);
614 if (!m_bOverwrite
&& newFGroup
.hasKey(newKey
))
616 log() << currentFilename
<< ": Skipping " << newFileName
<< ":" << newGroup
<< ":" << newKey
<< ", already exists."<< endl
;
619 log() << currentFilename
<< ": Updating " << newFileName
<< ":" << newGroup
<< ":" << newKey
<< " to '" << value
<< "'" << endl
;
620 newFGroup
.writeEntry(newKey
, value
);
626 if ((oldConfig2
== newConfig
) &&
627 (oldGroup
== newGroup
) &&
629 return; // Don't delete!
630 KConfigGroup
oldGroup2( oldConfig2
, oldGroup
);
631 oldGroup2
.deleteEntry(oldKey
);
632 log() << currentFilename
<< ": Removing " << oldFile
<< ":" << oldGroup
<< ":" << oldKey
<< ", moved." << endl
;
633 /*if (oldConfig2->deleteGroup(oldGroup, KConfig::Normal)) { // Delete group if empty.
634 log() << currentFilename << ": Removing empty group " << oldFile << ":" << oldGroup << endl;
635 } (this should be automatic) */
638 void KonfUpdate::gotRemoveKey(const QString
&_key
)
640 oldKey
= _key
.trimmed();
642 if (oldKey
.isEmpty())
644 log() << currentFilename
<< ": !! RemoveKey specifies invalid key in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
650 log() << currentFilename
<< ": !! Key without previous File specification in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
654 KConfigGroup
cg1(oldConfig1
, oldGroup
);
655 if (!cg1
.hasKey(oldKey
))
657 log() << currentFilename
<< ": RemoveKey removes " << oldFile
<< ":" << oldGroup
<< ":" << oldKey
<< endl
;
660 KConfigGroup
cg2( oldConfig2
, oldGroup
);
661 cg2
.deleteEntry(oldKey
);
662 /*if (oldConfig2->deleteGroup(oldGroup, KConfig::Normal)) { // Delete group if empty.
663 log() << currentFilename << ": Removing empty group " << oldFile << ":" << oldGroup << endl;
664 } (this should be automatic)*/
667 void KonfUpdate::gotAllKeys()
671 log() << currentFilename
<< ": !! AllKeys without previous File specification in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
675 QMap
<QString
, QString
> list
= oldConfig1
->entryMap(oldGroup
);
676 for(QMap
<QString
, QString
>::Iterator it
= list
.begin();
677 it
!= list
.end(); ++it
)
683 void KonfUpdate::gotAllGroups()
687 log() << currentFilename
<< ": !! AllGroups without previous File specification in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
691 const QStringList allGroups
= oldConfig1
->groupList();
692 for(QStringList::ConstIterator it
= allGroups
.begin();
693 it
!= allGroups
.end(); ++it
)
701 void KonfUpdate::gotOptions(const QString
&_options
)
703 const QStringList options
= _options
.split(',');
704 for(QStringList::ConstIterator it
= options
.begin();
708 if ( (*it
).toLower().trimmed() == "copy")
711 if ( (*it
).toLower().trimmed() == "overwrite")
716 void KonfUpdate::copyGroup(KConfig
*cfg1
, const QString
&group1
,
717 KConfig
*cfg2
, const QString
&group2
)
719 KConfigGroup
cg1(cfg1
, group1
);
720 KConfigGroup
cg2(cfg2
, group2
);
721 QMap
<QString
, QString
> list
= cg1
.entryMap();
722 for(QMap
<QString
, QString
>::Iterator it
= list
.begin();
723 it
!= list
.end(); ++it
)
725 cg2
.writeEntry(it
.key(), cg1
.readEntry(it
.key(), QString()));
729 void KonfUpdate::gotScriptArguments(const QString
&_arguments
)
731 m_arguments
= _arguments
;
734 void KonfUpdate::gotScript(const QString
&_script
)
736 QString script
, interpreter
;
737 int i
= _script
.indexOf(',');
740 script
= _script
.trimmed();
744 script
= _script
.left(i
).trimmed();
745 interpreter
= _script
.mid(i
+1).trimmed();
749 if (script
.isEmpty())
751 log() << currentFilename
<< ": !! Script fails to specify filename in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
758 QString path
= KStandardDirs::locate("data","kconf_update/"+script
);
761 if (interpreter
.isEmpty())
762 path
= KStandardDirs::locate("lib", "kconf_update_bin/"+script
);
766 log() << currentFilename
<< ": !! Script '" << script
<< "' not found in line " << m_lineCount
<< " : '" << m_line
<< "'" << endl
;
772 if( !m_arguments
.isNull())
773 log() << currentFilename
<< ": Running script '" << script
<< "' with arguments '" << m_arguments
<< "'" << endl
;
775 log() << currentFilename
<< ": Running script '" << script
<< "'" << endl
;
778 if (interpreter
.isEmpty())
781 cmd
= interpreter
+ ' ' + path
;
783 if( !m_arguments
.isNull())
801 tmp1
.setAutoRemove(false);
802 log() << "Script input stored in " << tmp1
.fileName() << endl
;
804 KConfig
cfg(tmp1
.fileName(), KConfig::SimpleConfig
);
806 if (oldGroup
.isEmpty())
808 // Write all entries to tmpFile;
809 const QStringList grpList
= oldConfig1
->groupList();
810 for(QStringList::ConstIterator it
= grpList
.begin();
814 copyGroup(oldConfig1
, *it
, &cfg
, *it
);
819 copyGroup(oldConfig1
, oldGroup
, &cfg
, QString());
822 result
= system(QFile::encodeName(QString("%1 < %2 > %3 2> %4").arg(cmd
, tmp1
.fileName(), tmp2
.fileName(), tmp3
.fileName())));
827 result
= system(QFile::encodeName(QString("%1 2> %2").arg(cmd
, tmp3
.fileName())));
830 // Copy script stderr to log file
832 QFile
output(tmp3
.fileName());
833 if (output
.open(QIODevice::ReadOnly
))
835 QTextStream
ts( &output
);
836 ts
.setCodec(QTextCodec::codecForName("UTF-8"));
839 QString line
= ts
.readLine();
840 log() << "[Script] " << line
<< endl
;
847 log() << currentFilename
<< ": !! An error occurred while running '" << cmd
<< "'" << endl
;
852 return; // Nothing to merge
856 tmp2
.setAutoRemove(false);
857 log() << "Script output stored in " << tmp2
.fileName() << endl
;
860 // Deleting old entries
862 QString group
= oldGroup
;
863 QFile
output(tmp2
.fileName());
864 if (output
.open(QIODevice::ReadOnly
))
866 QTextStream
ts( &output
);
867 ts
.setCodec(QTextCodec::codecForName("UTF-8"));
870 QString line
= ts
.readLine();
871 if (line
.startsWith('['))
873 int j
= line
.indexOf(']')+1;
875 group
= line
.mid(1, j
-2);
877 else if (line
.startsWith("# DELETE "))
879 QString key
= line
.mid(9);
882 int j
= key
.indexOf(']')+1;
885 group
= key
.mid(1,j
-2);
889 KConfigGroup
cg(oldConfig2
, group
);
891 log() << currentFilename
<< ": Script removes " << oldFile
<< ":" << group
<< ":" << key
<< endl
;
892 /*if (oldConfig2->deleteGroup(group, KConfig::Normal)) { // Delete group if empty.
893 log() << currentFilename << ": Removing empty group " << oldFile << ":" << group << endl;
894 } (this should be automatic)*/
896 else if (line
.startsWith("# DELETEGROUP"))
898 QString key
= line
.mid(13).trimmed();
901 int j
= key
.indexOf(']')+1;
904 group
= key
.mid(1,j
-2);
907 oldConfig2
->deleteGroup(group
);
908 log() << currentFilename
<< ": Script removes group " << oldFile
<< ":" << group
<< endl
;
914 // Merging in new entries.
917 KConfig
*saveOldConfig1
= oldConfig1
;
918 QString saveOldGroup
= oldGroup
;
919 QString saveNewGroup
= newGroup
;
920 oldConfig1
= new KConfig(tmp2
.fileName(), KConfig::NoGlobals
);
923 const QStringList grpList
= oldConfig1
->groupList();
924 for(QStringList::ConstIterator it
= grpList
.begin();
929 if (oldGroup
!= "<default>")
933 gotAllKeys(); // Copy all keys
936 oldConfig1
= saveOldConfig1
;
937 oldGroup
= saveOldGroup
;
938 newGroup
= saveNewGroup
;
942 void KonfUpdate::resetOptions()
945 m_bOverwrite
= false;
950 extern "C" KDE_EXPORT
int kdemain(int argc
, char **argv
)
952 KCmdLineOptions options
;
953 options
.add("debug", ki18n("Keep output results from scripts"));
954 options
.add("check <update-file>", ki18n("Check whether config file itself requires updating"));
955 options
.add("+[file]", ki18n("File to read update instructions from"));
957 KAboutData
aboutData("kconf_update", 0, ki18n("KConf Update"),
959 ki18n("KDE Tool for updating user configuration files"),
960 KAboutData::License_GPL
,
961 ki18n("(c) 2001, Waldo Bastian"));
963 aboutData
.addAuthor(ki18n("Waldo Bastian"), KLocalizedString(), "bastian@kde.org");
965 KCmdLineArgs::init(argc
, argv
, &aboutData
);
966 KCmdLineArgs::addCmdLineOptions(options
);
968 KComponentData
componentData(&aboutData
);
970 KonfUpdate konfUpdate
;