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 | |
66 | typedef QHash<QString, KSycocaEntry::Ptr> KBSEntryDict; |
67 | typedef QList<KSycocaEntry::List> KSycocaEntryListList; |
68 | |
69 | static quint32 newTimestamp = 0; |
70 | |
71 | static KBuildServiceFactory *g_serviceFactory = 0; |
72 | static KBuildServiceGroupFactory *g_buildServiceGroupFactory = 0; |
73 | static KSycocaFactory *g_currentFactory = 0; |
74 | static KCTimeInfo *g_ctimeInfo = 0; // factory |
75 | static KCTimeDict *g_ctimeDict = 0; // old timestamps |
76 | static QByteArray g_resource = 0; |
77 | static KBSEntryDict *g_currentEntryDict = 0; |
78 | static KBSEntryDict *g_serviceGroupEntryDict = 0; |
79 | static KSycocaEntryListList *g_allEntries = 0; // entries from existing ksycoca |
80 | static QStringList *g_allResourceDirs = 0; |
81 | static bool g_changed = false; |
82 | static KSycocaEntry::List g_tempStorage; |
83 | static VFolderMenu *g_vfolder = 0; |
84 | |
85 | static const char *cSycocaPath = 0; |
86 | |
87 | static bool bGlobalDatabase = false; |
88 | static bool = false; |
89 | |
90 | void 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 | |
98 | static QString sycocaPath() |
99 | { |
100 | return KSycoca::absoluteFilePath(bGlobalDatabase ? KSycoca::GlobalDatabase : KSycoca::LocalDatabase); |
101 | } |
102 | |
103 | KBuildSycoca::KBuildSycoca() |
104 | : KSycoca( true ) |
105 | { |
106 | } |
107 | |
108 | KBuildSycoca::~KBuildSycoca() |
109 | { |
110 | |
111 | } |
112 | |
113 | KSycocaEntry::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 | |
172 | KService::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 |
179 | bool 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 * = 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 | |
338 | void KBuildSycoca::(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *) |
339 | { |
340 | QString caption = caption_; |
341 | QString name = name_; |
342 | foreach (VFolderMenu::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 | |
396 | bool 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 | |
472 | void 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 | |
545 | bool 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 |
578 | bool 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 | |
594 | QStringList 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 | |
629 | static const char appFullName[] = "org.kde.kbuildsycoca" ; |
630 | static const char appName[] = "kbuildsycoca4" ; |
631 | static const char appVersion[] = "1.1" ; |
632 | |
633 | extern "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 ; |
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 | |