1// -*- c-basic-offset: 3 -*-
2/* This file is part of the KDE libraries
3 * Copyright (C) 1999 David Faure <faure@kde.org>
4 * Copyright (C) 2002-2003 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 "kbuildsycoca.h"
22#include "ksycoca_p.h"
23#include "ksycocaresourcelist.h"
24#include "vfolder_menu.h"
25
26#include <config.h>
27#include <config-kded.h>
28
29#include <kservice.h>
30#include <kmimetype.h>
31#include "kbuildservicetypefactory.h"
32#include "kbuildmimetypefactory.h"
33#include "kbuildservicefactory.h"
34#include "kbuildservicegroupfactory.h"
35#include "kbuildprotocolinfofactory.h"
36#include "kctimefactory.h"
37#include <ktemporaryfile.h>
38#include <QtCore/QDataStream>
39#include <QtCore/QDir>
40#include <QtCore/QEventLoop>
41#include <QtCore/QFile>
42#include <QtCore/QTimer>
43#include <QtDBus/QtDBus>
44#include <errno.h>
45
46#include <assert.h>
47#include <kapplication.h>
48#include <kglobal.h>
49#include <kdebug.h>
50#include <kdirwatch.h>
51#include <kstandarddirs.h>
52#include <ksavefile.h>
53#include <klocale.h>
54#include <kaboutdata.h>
55#include <kcmdlineargs.h>
56#ifndef KBUILDSYCOCA_NO_KCRASH
57#include <kcrash.h>
58#endif
59#include <kmemfile.h>
60
61#include <stdlib.h>
62#include <unistd.h>
63#include <time.h>
64#include <memory> // auto_ptr
65
66typedef QHash<QString, KSycocaEntry::Ptr> KBSEntryDict;
67typedef QList<KSycocaEntry::List> KSycocaEntryListList;
68
69static quint32 newTimestamp = 0;
70
71static KBuildServiceFactory *g_serviceFactory = 0;
72static KBuildServiceGroupFactory *g_buildServiceGroupFactory = 0;
73static KSycocaFactory *g_currentFactory = 0;
74static KCTimeInfo *g_ctimeInfo = 0; // factory
75static KCTimeDict *g_ctimeDict = 0; // old timestamps
76static QByteArray g_resource = 0;
77static KBSEntryDict *g_currentEntryDict = 0;
78static KBSEntryDict *g_serviceGroupEntryDict = 0;
79static KSycocaEntryListList *g_allEntries = 0; // entries from existing ksycoca
80static QStringList *g_allResourceDirs = 0;
81static bool g_changed = false;
82static KSycocaEntry::List g_tempStorage;
83static VFolderMenu *g_vfolder = 0;
84
85static const char *cSycocaPath = 0;
86
87static bool bGlobalDatabase = false;
88static bool bMenuTest = false;
89
90void crashHandler(int)
91{
92 // If we crash while reading sycoca, we delete the database
93 // in an attempt to recover.
94 if (cSycocaPath)
95 unlink(cSycocaPath);
96}
97
98static QString sycocaPath()
99{
100 return KSycoca::absoluteFilePath(bGlobalDatabase ? KSycoca::GlobalDatabase : KSycoca::LocalDatabase);
101}
102
103KBuildSycoca::KBuildSycoca()
104 : KSycoca( true )
105{
106}
107
108KBuildSycoca::~KBuildSycoca()
109{
110
111}
112
113KSycocaEntry::Ptr KBuildSycoca::createEntry(const QString &file, bool addToFactory)
114{
115 quint32 timeStamp = g_ctimeInfo->dict()->ctime(file, g_resource);
116 if (!timeStamp)
117 {
118 timeStamp = KGlobal::dirs()->calcResourceHash( g_resource, file,
119 KStandardDirs::Recursive);
120 }
121 KSycocaEntry::Ptr entry;
122 if (g_allEntries)
123 {
124 assert(g_ctimeDict);
125 quint32 oldTimestamp = g_ctimeDict->ctime(file, g_resource);
126
127 if (timeStamp && (timeStamp == oldTimestamp))
128 {
129 // Re-use old entry
130 if (g_currentFactory == g_buildServiceGroupFactory) // Strip .directory from service-group entries
131 {
132 entry = g_currentEntryDict->value(file.left(file.length()-10));
133 } else {
134 entry = g_currentEntryDict->value(file);
135 }
136 // remove from g_ctimeDict; if g_ctimeDict is not empty
137 // after all files have been processed, it means
138 // some files were removed since last time
139 if (file.contains("fake"))
140 kDebug(7021) << g_resource << "reusing (and removing) old entry [\"fake\"] for:" << file;
141 g_ctimeDict->remove(file, g_resource);
142 }
143 else if (oldTimestamp)
144 {
145 g_changed = true;
146 g_ctimeDict->remove(file, g_resource);
147 kDebug(7021) << "modified:" << file << "in" << g_resource.constData();
148 }
149 else
150 {
151 g_changed = true;
152 kDebug(7021) << "new:" << file << "in" << g_resource.constData();
153 }
154 }
155 g_ctimeInfo->dict()->addCTime(file, g_resource, timeStamp);
156 if (!entry)
157 {
158 // Create a new entry
159 entry = g_currentFactory->createEntry( file, g_resource );
160 }
161 if ( entry && entry->isValid() )
162 {
163 if (addToFactory)
164 g_currentFactory->addEntry(entry);
165 else
166 g_tempStorage.append(entry);
167 return entry;
168 }
169 return KSycocaEntry::Ptr();
170}
171
172KService::Ptr KBuildSycoca::createService(const QString &path)
173{
174 KSycocaEntry::Ptr entry = createEntry(path, false);
175 return KService::Ptr::staticCast(entry);
176}
177
178// returns false if the database is up to date, true if it needs to be saved
179bool KBuildSycoca::build()
180{
181 typedef QLinkedList<KBSEntryDict *> KBSEntryDictList;
182 KBSEntryDictList entryDictList;
183 KBSEntryDict *serviceEntryDict = 0;
184
185 // Convert for each factory the entryList to a Dict.
186 int i = 0;
187 // For each factory
188 for (KSycocaFactoryList::Iterator factory = factories()->begin();
189 factory != factories()->end();
190 ++factory)
191 {
192 KBSEntryDict *entryDict = new KBSEntryDict;
193 if (g_allEntries)
194 {
195 const KSycocaEntry::List list = (*g_allEntries)[i++];
196 for( KSycocaEntry::List::const_iterator it = list.begin();
197 it != list.end();
198 ++it)
199 {
200 entryDict->insert( (*it)->entryPath(), *it );
201 }
202 }
203 if ((*factory) == g_serviceFactory)
204 serviceEntryDict = entryDict;
205 else if ((*factory) == g_buildServiceGroupFactory)
206 g_serviceGroupEntryDict = entryDict;
207 entryDictList.append(entryDict);
208 }
209
210 QStringList allResources; // we could use QSet<QString> - does order matter?
211 // For each factory
212 for (KSycocaFactoryList::Iterator factory = factories()->begin();
213 factory != factories()->end();
214 ++factory)
215 {
216 // For each resource the factory deals with
217 const KSycocaResourceList *list = (*factory)->resourceList();
218 if (!list) continue;
219
220 for( KSycocaResourceList::ConstIterator it1 = list->constBegin();
221 it1 != list->constEnd();
222 ++it1 )
223 {
224 KSycocaResource res = (*it1);
225 if (!allResources.contains(res.resource))
226 allResources.append(res.resource);
227 }
228 }
229
230 g_ctimeInfo = new KCTimeInfo(); // This is a build factory too, don't delete!!
231 bool uptodate = true;
232 // For all resources
233 for( QStringList::ConstIterator it1 = allResources.constBegin();
234 it1 != allResources.constEnd();
235 ++it1 )
236 {
237 g_changed = false;
238 g_resource = (*it1).toLatin1();
239
240 QStringList relFiles;
241
242 (void) KGlobal::dirs()->findAllResources( g_resource,
243 QString(),
244 KStandardDirs::Recursive |
245 KStandardDirs::NoDuplicates,
246 relFiles);
247
248
249 // Now find all factories that use this resource....
250 // For each factory
251 KBSEntryDictList::const_iterator ed_it = entryDictList.begin();
252 const KBSEntryDictList::const_iterator ed_end = entryDictList.end();
253 KSycocaFactoryList::const_iterator it = factories()->constBegin();
254 const KSycocaFactoryList::const_iterator end = factories()->constEnd();
255 for ( ; it != end; ++it, ++ed_it )
256 {
257 g_currentFactory = (*it);
258 // g_ctimeInfo gets created after the initial loop, so it has no entryDict.
259 g_currentEntryDict = ed_it == ed_end ? 0 : *ed_it;
260 // For each resource the factory deals with
261 const KSycocaResourceList *list = g_currentFactory->resourceList();
262 if (!list) continue;
263
264 for( KSycocaResourceList::ConstIterator it2 = list->constBegin();
265 it2 != list->constEnd();
266 ++it2 )
267 {
268 KSycocaResource res = (*it2);
269 if (res.resource != (*it1)) continue;
270
271 // For each file in the resource
272 for( QStringList::ConstIterator it3 = relFiles.constBegin();
273 it3 != relFiles.constEnd();
274 ++it3 )
275 {
276 // Check if file matches filter
277 if ((*it3).endsWith(res.extension))
278 createEntry(*it3, true);
279 }
280 }
281 }
282 if (g_changed || !g_allEntries)
283 {
284 uptodate = false;
285 //kDebug() << "CHANGED:" << g_resource;
286 m_changedResources.append(g_resource);
287 }
288 }
289
290 bool result = !uptodate || (g_ctimeDict && !g_ctimeDict->isEmpty());
291 if (g_ctimeDict && !g_ctimeDict->isEmpty()) {
292 //kDebug() << "Still in time dict:";
293 //g_ctimeDict->dump();
294 // ## It seems entries filtered out by vfolder are still in there,
295 // so we end up always saving ksycoca, i.e. this method never returns false
296
297 // Get the list of resources from which some files were deleted
298 const QStringList resources = g_ctimeDict->resourceList();
299 kDebug() << "Still in the time dict (i.e. deleted files)" << resources;
300 m_changedResources += resources;
301 }
302
303 if (result || bMenuTest)
304 {
305 g_resource = "apps";
306 g_currentFactory = g_serviceFactory;
307 g_currentEntryDict = serviceEntryDict;
308 g_changed = false;
309
310 g_vfolder = new VFolderMenu(g_serviceFactory, this);
311 if (!m_trackId.isEmpty())
312 g_vfolder->setTrackId(m_trackId);
313
314 VFolderMenu::SubMenu *kdeMenu = g_vfolder->parseMenu("applications.menu", true);
315
316 KServiceGroup::Ptr entry = g_buildServiceGroupFactory->addNew("/", kdeMenu->directoryFile, KServiceGroup::Ptr(), false);
317 entry->setLayoutInfo(kdeMenu->layoutList);
318 createMenu(QString(), QString(), kdeMenu);
319
320 (void) existingResourceDirs();
321 *g_allResourceDirs += g_vfolder->allDirectories();
322
323 if (g_changed || !g_allEntries)
324 {
325 uptodate = false;
326 //kDebug() << "CHANGED:" << g_resource;
327 m_changedResources.append(g_resource);
328 }
329 if (bMenuTest) {
330 result = false;
331 }
332 }
333
334 qDeleteAll(entryDictList);
335 return result;
336}
337
338void KBuildSycoca::createMenu(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *menu)
339{
340 QString caption = caption_;
341 QString name = name_;
342 foreach (VFolderMenu::SubMenu *subMenu, menu->subMenus)
343 {
344 QString subName = name+subMenu->name+'/';
345
346 QString directoryFile = subMenu->directoryFile;
347 if (directoryFile.isEmpty())
348 directoryFile = subName+".directory";
349 quint32 timeStamp = g_ctimeInfo->dict()->ctime(directoryFile, g_resource);
350 if (!timeStamp) {
351 timeStamp = KGlobal::dirs()->calcResourceHash( g_resource, directoryFile,
352 KStandardDirs::Recursive );
353 }
354
355 KServiceGroup::Ptr entry;
356 if (g_allEntries)
357 {
358 const quint32 oldTimestamp = g_ctimeDict->ctime(directoryFile, g_resource);
359
360 if (timeStamp && (timeStamp == oldTimestamp))
361 {
362 KSycocaEntry::Ptr group = g_serviceGroupEntryDict->value(subName);
363 if ( group )
364 {
365 entry = KServiceGroup::Ptr::staticCast( group );
366 if (entry->directoryEntryPath() != directoryFile)
367 entry = 0; // Can't reuse this one!
368 }
369 }
370 }
371 g_ctimeInfo->dict()->addCTime(directoryFile, g_resource, timeStamp);
372
373 entry = g_buildServiceGroupFactory->addNew(subName, subMenu->directoryFile, entry, subMenu->isDeleted);
374 entry->setLayoutInfo(subMenu->layoutList);
375 if (! (bMenuTest && entry->noDisplay()) )
376 createMenu(caption + entry->caption() + '/', subName, subMenu);
377 }
378 if (caption.isEmpty())
379 caption += '/';
380 if (name.isEmpty())
381 name += '/';
382 foreach (const KService::Ptr &p, menu->items)
383 {
384 if (bMenuTest)
385 {
386 if (!menu->isDeleted && !p->noDisplay())
387 printf("%s\t%s\t%s\n", qPrintable( caption ), qPrintable( p->menuId() ), qPrintable( KStandardDirs::locate("apps", p->entryPath() ) ) );
388 }
389 else
390 {
391 g_buildServiceGroupFactory->addNewEntryTo( name, p );
392 }
393 }
394}
395
396bool KBuildSycoca::recreate()
397{
398 QString path(sycocaPath());
399
400 // KSaveFile first writes to a temp file.
401 // Upon finalize() it moves the stuff to the right place.
402 KSaveFile database(path);
403 bool openedOK = database.open();
404 if (!openedOK && database.error() == QFile::PermissionsError && QFile::exists(path))
405 {
406 QFile::remove( path );
407 openedOK = database.open();
408 }
409 if (!openedOK)
410 {
411 fprintf(stderr, "kbuildsycoca4: ERROR creating database '%s'! %s\n",
412 path.toLocal8Bit().data(), database.errorString().toLocal8Bit().data());
413 return false;
414 }
415
416 QDataStream* str = new QDataStream(&database);
417 str->setVersion(QDataStream::Qt_3_1);
418
419 kDebug(7021).nospace() << "Recreating ksycoca file (" << path << ", version " << KSycoca::version() << ")";
420
421 // It is very important to build the servicetype one first
422 // Both are registered in KSycoca, no need to keep the pointers
423 KSycocaFactory *stf = new KBuildServiceTypeFactory;
424 KBuildMimeTypeFactory* mimeTypeFactory = new KBuildMimeTypeFactory;
425 g_buildServiceGroupFactory = new KBuildServiceGroupFactory();
426 g_serviceFactory = new KBuildServiceFactory(stf, mimeTypeFactory, g_buildServiceGroupFactory);
427 (void) new KBuildProtocolInfoFactory();
428
429 if( build()) // Parse dirs
430 {
431 save(str); // Save database
432 if (str->status() != QDataStream::Ok) // ######## TODO: does this detect write errors, e.g. disk full?
433 database.abort(); // Error
434 delete str;
435 str = 0;
436 if (!database.finalize())
437 {
438 fprintf(stderr, "kbuildsycoca4: ERROR writing database '%s'!\n", database.fileName().toLocal8Bit().data());
439 fprintf(stderr, "kbuildsycoca4: Disk full?\n");
440 return false;
441 }
442 }
443 else
444 {
445 delete str;
446 str = 0;
447 database.abort();
448 if (bMenuTest)
449 return true;
450 kDebug(7021) << "Database is up to date";
451 }
452
453 if (!bGlobalDatabase)
454 {
455 // update the timestamp file
456 QString stamppath = path + "stamp";
457 QFile ksycocastamp(stamppath);
458 ksycocastamp.open( QIODevice::WriteOnly );
459 QDataStream str( &ksycocastamp );
460 str.setVersion(QDataStream::Qt_3_1);
461 str << newTimestamp;
462 str << existingResourceDirs();
463 if (g_vfolder)
464 str << g_vfolder->allDirectories(); // Extra resource dirs
465 }
466 if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile)
467 KMemFile::fileContentsChanged(path);
468
469 return true;
470}
471
472void KBuildSycoca::save(QDataStream* str)
473{
474 // Write header (#pass 1)
475 str->device()->seek(0);
476
477 (*str) << (qint32) KSycoca::version();
478 KSycocaFactory * servicetypeFactory = 0;
479 KBuildMimeTypeFactory * mimeTypeFactory = 0;
480 KBuildServiceFactory * serviceFactory = 0;
481 for(KSycocaFactoryList::Iterator factory = factories()->begin();
482 factory != factories()->end();
483 ++factory)
484 {
485 qint32 aId;
486 qint32 aOffset;
487 aId = (*factory)->factoryId();
488 if ( aId == KST_KServiceTypeFactory )
489 servicetypeFactory = *factory;
490 else if ( aId == KST_KMimeTypeFactory )
491 mimeTypeFactory = static_cast<KBuildMimeTypeFactory *>( *factory );
492 else if ( aId == KST_KServiceFactory )
493 serviceFactory = static_cast<KBuildServiceFactory *>( *factory );
494 aOffset = (*factory)->offset(); // not set yet, so always 0
495 (*str) << aId;
496 (*str) << aOffset;
497 }
498 (*str) << (qint32) 0; // No more factories.
499 // Write KDEDIRS
500 (*str) << KGlobal::dirs()->kfsstnd_prefixes();
501 (*str) << newTimestamp;
502 (*str) << KGlobal::locale()->language();
503 (*str) << KGlobal::dirs()->calcResourceHash("services", "update_ksycoca",
504 KStandardDirs::Recursive );
505 (*str) << (*g_allResourceDirs);
506
507 // Calculate per-servicetype/mimetype data
508 serviceFactory->postProcessServices();
509
510 // Here so that it's the last debug message
511 kDebug(7021) << "Saving";
512
513 // Write factory data....
514 for(KSycocaFactoryList::Iterator factory = factories()->begin();
515 factory != factories()->end();
516 ++factory)
517 {
518 (*factory)->save(*str);
519 if (str->status() != QDataStream::Ok) // ######## TODO: does this detect write errors, e.g. disk full?
520 return; // error
521 }
522
523 int endOfData = str->device()->pos();
524
525 // Write header (#pass 2)
526 str->device()->seek(0);
527
528 (*str) << (qint32) KSycoca::version();
529 for(KSycocaFactoryList::Iterator factory = factories()->begin();
530 factory != factories()->end(); ++factory)
531 {
532 qint32 aId;
533 qint32 aOffset;
534 aId = (*factory)->factoryId();
535 aOffset = (*factory)->offset();
536 (*str) << aId;
537 (*str) << aOffset;
538 }
539 (*str) << (qint32) 0; // No more factories.
540
541 // Jump to end of database
542 str->device()->seek(endOfData);
543}
544
545bool KBuildSycoca::checkDirTimestamps( const QString& dirname, const QDateTime& stamp, bool top )
546{
547 if( top )
548 {
549 QFileInfo inf( dirname );
550 if( inf.lastModified() > stamp ) {
551 kDebug( 7021 ) << "timestamp changed:" << dirname;
552 return false;
553 }
554 }
555 QDir dir( dirname );
556 const QFileInfoList list = dir.entryInfoList( QDir::NoFilter, QDir::Unsorted );
557 if (list.isEmpty())
558 return true;
559
560 foreach ( const QFileInfo& fi, list ) {
561 if( fi.fileName() == "." || fi.fileName() == ".." )
562 continue;
563 if( fi.lastModified() > stamp )
564 {
565 kDebug( 7201 ) << "timestamp changed:" << fi.filePath();
566 return false;
567 }
568 if( fi.isDir() && !checkDirTimestamps( fi.filePath(), stamp, false ))
569 return false;
570 }
571 return true;
572}
573
574// check times of last modification of all files on which ksycoca depens,
575// and also their directories
576// if all of them are older than the timestamp in file ksycocastamp, this
577// means that there's no need to rebuild ksycoca
578bool KBuildSycoca::checkTimestamps( quint32 timestamp, const QStringList &dirs )
579{
580 kDebug( 7021 ) << "checking file timestamps";
581 QDateTime stamp;
582 stamp.setTime_t( timestamp );
583 for( QStringList::ConstIterator it = dirs.begin();
584 it != dirs.end();
585 ++it )
586 {
587 if( !checkDirTimestamps( *it, stamp, true ))
588 return false;
589 }
590 kDebug( 7021 ) << "timestamps check ok";
591 return true;
592}
593
594QStringList KBuildSycoca::existingResourceDirs()
595{
596 static QStringList* dirs = NULL;
597 if( dirs != NULL )
598 return *dirs;
599 dirs = new QStringList;
600 g_allResourceDirs = new QStringList;
601 // these are all resources cached by ksycoca
602 QStringList resources;
603 resources += KBuildServiceTypeFactory::resourceTypes();
604 resources += KBuildMimeTypeFactory::resourceTypes();
605 resources += KBuildServiceGroupFactory::resourceTypes();
606 resources += KBuildServiceFactory::resourceTypes();
607 resources += KBuildProtocolInfoFactory::resourceTypes();
608 while( !resources.empty())
609 {
610 QString res = resources.front();
611 *dirs += KGlobal::dirs()->resourceDirs( res.toLatin1());
612 resources.removeAll( res );
613 }
614
615 *g_allResourceDirs = *dirs;
616
617 for( QStringList::Iterator it = dirs->begin();
618 it != dirs->end(); )
619 {
620 QFileInfo inf( *it );
621 if( !inf.exists() || !inf.isReadable() )
622 it = dirs->erase( it );
623 else
624 ++it;
625 }
626 return *dirs;
627}
628
629static const char appFullName[] = "org.kde.kbuildsycoca";
630static const char appName[] = "kbuildsycoca4";
631static const char appVersion[] = "1.1";
632
633extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
634{
635 KAboutData d(appName, "kdelibs4", ki18n("KBuildSycoca"), appVersion,
636 ki18n("Rebuilds the system configuration cache."),
637 KAboutData::License_GPL, ki18n("(c) 1999-2002 KDE Developers"));
638 d.addAuthor(ki18n("David Faure"), ki18n("Author"), "faure@kde.org");
639 d.addAuthor(ki18n("Waldo Bastian"), ki18n("Author"), "bastian@kde.org");
640
641 KCmdLineOptions options;
642 options.add("nosignal", ki18n("Do not signal applications to update"));
643 options.add("noincremental", ki18n("Disable incremental update, re-read everything"));
644 options.add("checkstamps", ki18n("Check file timestamps"));
645 options.add("nocheckfiles", ki18n("Disable checking files (dangerous)"));
646 options.add("global", ki18n("Create global database"));
647 options.add("menutest", ki18n("Perform menu generation test run only"));
648 options.add("track <menu-id>", ki18n("Track menu id for debug purposes"));
649
650 KCmdLineArgs::init(argc, argv, &d);
651 KCmdLineArgs::addCmdLineOptions(options);
652 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
653 bGlobalDatabase = args->isSet("global");
654 bMenuTest = args->isSet("menutest");
655
656 if (bGlobalDatabase)
657 {
658 setenv("KDEHOME", "-", 1);
659 setenv("KDEROOTHOME", "-", 1);
660 }
661
662 QCoreApplication k(argc, argv);
663 KComponentData mainComponent(d);
664
665#ifndef KBUILDSYCOCA_NO_KCRASH
666 KCrash::setCrashHandler(KCrash::defaultCrashHandler);
667 KCrash::setEmergencySaveFunction(crashHandler);
668 KCrash::setApplicationName(QString(appName));
669#endif
670
671 // force generating of KLocale object. if not, the database will get
672 // be translated
673 KGlobal::locale();
674 KGlobal::dirs()->addResourceType("app-reg", 0, "share/application-registry" );
675
676 while(QDBusConnection::sessionBus().isConnected())
677 {
678 // kapp registered already, but with the PID in the name.
679 // We need to re-register without it, to detect already-running kbuildsycoca instances.
680 if (QDBusConnection::sessionBus().interface()->registerService(appFullName, QDBusConnectionInterface::QueueService)
681 != QDBusConnectionInterface::ServiceQueued)
682 {
683 break; // Go
684 }
685 fprintf(stderr, "Waiting for already running %s to finish.\n", appName);
686
687 QEventLoop eventLoop;
688 QObject::connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceRegistered(QString)),
689 &eventLoop, SLOT(quit()));
690 eventLoop.exec( QEventLoop::ExcludeUserInputEvents );
691 }
692 fprintf(stderr, "%s running...\n", appName);
693
694 bool checkfiles = bGlobalDatabase || args->isSet("checkfiles");
695
696 bool incremental = !bGlobalDatabase && args->isSet("incremental") && checkfiles;
697 if (incremental || !checkfiles)
698 {
699 KSycoca::disableAutoRebuild(); // Prevent deadlock
700 QString current_language = KGlobal::locale()->language();
701 QString ksycoca_language = KSycoca::self()->language();
702 quint32 current_update_sig = KGlobal::dirs()->calcResourceHash("services", "update_ksycoca",
703 KStandardDirs::Recursive );
704 quint32 ksycoca_update_sig = KSycoca::self()->updateSignature();
705 QString current_prefixes = KGlobal::dirs()->kfsstnd_prefixes();
706 QString ksycoca_prefixes = KSycoca::self()->kfsstnd_prefixes();
707
708 if ((current_update_sig != ksycoca_update_sig) ||
709 (current_language != ksycoca_language) ||
710 (current_prefixes != ksycoca_prefixes) ||
711 (KSycoca::self()->timeStamp() == 0))
712 {
713 incremental = false;
714 checkfiles = true;
715 KBuildSycoca::clearCaches();
716 }
717 }
718
719 bool checkstamps = incremental && args->isSet("checkstamps") && checkfiles;
720 quint32 filestamp = 0;
721 QStringList oldresourcedirs;
722 if( checkstamps && incremental )
723 {
724 QString path = sycocaPath()+"stamp";
725 QByteArray qPath = QFile::encodeName(path);
726 cSycocaPath = qPath.data(); // Delete timestamps on crash
727 QFile ksycocastamp(path);
728 if( ksycocastamp.open( QIODevice::ReadOnly ))
729 {
730 QDataStream str( &ksycocastamp );
731 str.setVersion(QDataStream::Qt_3_1);
732
733 if (!str.atEnd())
734 str >> filestamp;
735 if (!str.atEnd())
736 {
737 str >> oldresourcedirs;
738 if( oldresourcedirs != KBuildSycoca::existingResourceDirs())
739 checkstamps = false;
740 }
741 else
742 {
743 checkstamps = false;
744 }
745 if (!str.atEnd())
746 {
747 QStringList extraResourceDirs;
748 str >> extraResourceDirs;
749 oldresourcedirs += extraResourceDirs;
750 }
751 }
752 else
753 {
754 checkstamps = false;
755 }
756 cSycocaPath = 0;
757 }
758
759 newTimestamp = (quint32) time(0);
760 QStringList changedResources;
761
762 if( checkfiles && ( !checkstamps || !KBuildSycoca::checkTimestamps( filestamp, oldresourcedirs )))
763 {
764 QByteArray qSycocaPath = QFile::encodeName(sycocaPath());
765 cSycocaPath = qSycocaPath.data();
766
767 g_allEntries = 0;
768 g_ctimeDict = 0;
769 if (incremental)
770 {
771 kDebug(7021) << "Reusing existing ksycoca";
772 KSycoca::self();
773 KSycocaFactoryList *factories = new KSycocaFactoryList;
774 g_allEntries = new KSycocaEntryListList;
775 g_ctimeDict = new KCTimeDict;
776
777 // Must be in same order as in KBuildSycoca::recreate()!
778 factories->append( new KServiceTypeFactory );
779 factories->append( new KMimeTypeFactory );
780 factories->append( new KServiceGroupFactory );
781 factories->append( new KServiceFactory );
782 factories->append( new KProtocolInfoFactory );
783
784 // For each factory
785 for (KSycocaFactoryList::Iterator factory = factories->begin();
786 factory != factories->end(); ++factory)
787 {
788 const KSycocaEntry::List list = (*factory)->allEntries();
789 g_allEntries->append( list );
790 }
791 delete factories; factories = 0;
792 KCTimeInfo *ctimeInfo = new KCTimeInfo;
793 *g_ctimeDict = ctimeInfo->loadDict();
794 }
795 cSycocaPath = 0;
796
797 KBuildSycoca *sycoca = new KBuildSycoca; // Build data base (deletes oldSycoca)
798 if (args->isSet("track"))
799 sycoca->setTrackId(args->getOption("track"));
800 if (!sycoca->recreate()) {
801 return -1;
802 }
803 changedResources = sycoca->changedResources();
804
805 if (bGlobalDatabase)
806 {
807 // These directories may have been created with 0700 permission
808 // better delete them if they are empty
809 QString applnkDir = KGlobal::dirs()->saveLocation("apps", QString(), false);
810 ::rmdir(QFile::encodeName(applnkDir));
811 QString servicetypesDir = KGlobal::dirs()->saveLocation("servicetypes", QString(), false);
812 ::rmdir(QFile::encodeName(servicetypesDir));
813 }
814 }
815
816 if (args->isSet("signal"))
817 {
818 // Notify ALL applications that have a ksycoca object, using a signal
819 QDBusMessage signal = QDBusMessage::createSignal("/", "org.kde.KSycoca", "notifyDatabaseChanged" );
820 signal << changedResources;
821
822 if (QDBusConnection::sessionBus().isConnected()) {
823 kDebug() << "Emitting notifyDatabaseChanged" << changedResources;
824 QDBusConnection::sessionBus().send(signal);
825 qApp->processEvents(); // make sure the dbus signal is sent before we quit.
826 }
827 }
828
829 return 0;
830}
831
832#include "kbuildsycoca.moc"
833