kdelibs/kdecore/sycoca/ksycoca.cpp
Ivailo Monev abf1deffe0 kdecore: oops
automatic sycoca database was disabled while working on
70f9b2f953 for testing purpose

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-08-17 22:52:36 +03:00

505 lines
14 KiB
C++

/* This file is part of the KDE libraries
* Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org>
* Copyright (C) 2005-2009 David Faure <faure@kde.org>
* Copyright (C) 2008 Hamish Rodda <rodda@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation;
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
**/
#include "ksycoca.h"
#include "ksycoca_p.h"
#include "ksycocatype.h"
#include "ksycocafactory.h"
#include "kglobal.h"
#include "kde_file.h"
#include "kconfiggroup.h"
#include "ksharedconfig.h"
#include "kdebug.h"
#include "kstandarddirs.h"
#include <QtCore/QDataStream>
#include <QtCore/QCoreApplication>
#include <QtCore/QFile>
#include <QtCore/QBuffer>
#include <QProcess>
#include <QtDBus/QtDBus>
#include <QtCore/qthread.h>
#include <config.h>
#include <stdlib.h>
#include <fcntl.h>
#include "ksycocadevices_p.h"
static bool s_autoRebuild = true;
// The following limitations are in place:
// Maximum length of a single string: 8192 bytes
// Maximum length of a string list: 1024 strings
// Maximum number of entries: 8192
//
// The purpose of these limitations is to limit the impact
// of database corruption.
Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
KSycocaPrivate::KSycocaPrivate()
: databaseStatus(DatabaseNotOpen),
readError(false),
timeStamp(0),
m_databasePath(),
updateSig(0),
m_device(0)
{
m_sycocaStrategy = StrategyFile;
KConfigGroup config(KGlobal::config(), "KSycoca");
setStrategyFromString(config.readEntry("strategy"));
}
void KSycocaPrivate::setStrategyFromString(const QString& strategy) {
if (strategy == QLatin1String("file")) {
m_sycocaStrategy = StrategyFile;
} else if (!strategy.isEmpty()) {
kWarning(7011) << "Unknown sycoca strategy:" << strategy;
}
}
int KSycoca::version()
{
return KSYCOCA_VERSION;
}
class KSycocaSingleton
{
public:
KSycocaSingleton() { }
~KSycocaSingleton()
{
if (m_threadSycocas) {
delete m_threadSycocas;
m_threadSycocas = 0;
}
}
bool hasSycoca() const {
return (m_threadSycocas != 0);
}
KSycoca* sycoca() {
if (!m_threadSycocas) {
m_threadSycocas = new KSycoca();
}
return m_threadSycocas;
}
void setSycoca(KSycoca* s) {
m_threadSycocas = s;
}
private:
static thread_local KSycoca* m_threadSycocas;
};
thread_local KSycoca* KSycocaSingleton::m_threadSycocas = 0;
K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
// Read-only constructor
KSycoca::KSycoca()
: d(new KSycocaPrivate())
{
QDBusConnection::sessionBus().connect(
QString(), QString(),
QString::fromLatin1("org.kde.KSycoca"),
QString::fromLatin1("notifyDatabaseChanged"),
this, SLOT(notifyDatabaseChanged(QStringList))
);
}
bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound)
{
Q_ASSERT(databaseStatus == DatabaseNotOpen);
delete m_device; m_device = 0;
QString path = KSycoca::absoluteFilePath();
bool canRead = KDE::access(path, R_OK) == 0;
kDebug(7011) << "Trying to open ksycoca from" << path;
if (!canRead) {
path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
if (!path.isEmpty()) {
kDebug(7011) << "Trying to open global ksycoca from " << path;
canRead = KDE::access(path, R_OK) == 0;
}
}
bool result = true;
if (canRead) {
m_databasePath = path;
checkVersion();
} else { // No database file
kDebug(7011) << "Could not open ksycoca";
m_databasePath.clear();
databaseStatus = NoDatabase;
if (openDummyIfNotFound) {
// We open a dummy database instead.
//kDebug(7011) << "No database, opening a dummy one.";
m_sycocaStrategy = StrategyDummyBuffer;
(void)stream();
} else {
result = false;
}
}
return result;
}
KSycocaAbstractDevice* KSycocaPrivate::device()
{
if (m_device) {
return m_device;
}
Q_ASSERT(!m_databasePath.isEmpty());
KSycocaAbstractDevice* device = m_device;
if (m_sycocaStrategy == StrategyDummyBuffer) {
device = new KSycocaBufferDevice();
} else {
if (!device) {
device = new KSycocaFileDevice(m_databasePath);
if (!device->device()->open(QIODevice::ReadOnly)) {
kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
//delete device; device = 0; // this would crash in the return statement...
}
}
}
if (device) {
m_device = device;
}
return m_device;
}
QDataStream*& KSycocaPrivate::stream()
{
if (!m_device) {
if (databaseStatus == DatabaseNotOpen) {
checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
}
device(); // create m_device
}
return m_device->stream();
}
// Read-write constructor - only for KBuildSycoca
KSycoca::KSycoca(bool /* dummy */)
: d(new KSycocaPrivate)
{
// This instance was not created by the singleton, but by a direct call to new!
ksycocaInstance->setSycoca(this);
}
KSycoca* KSycoca::self()
{
KSycoca* s = ksycocaInstance->sycoca();
Q_ASSERT(s);
return s;
}
KSycoca::~KSycoca()
{
d->closeDatabase();
delete d;
//if (ksycocaInstance.exists()
// && ksycocaInstance->self == this)
// ksycocaInstance->self = 0;
}
bool KSycoca::isAvailable()
{
return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */);
}
void KSycocaPrivate::closeDatabase()
{
delete m_device;
m_device = 0;
// It is very important to delete all factories here
// since they cache information about the database file
// But other threads might be using them, so this class is
// refcounted, and deleted when the last thread is done with them
qDeleteAll(m_factories);
m_factories.clear();
databaseStatus = DatabaseNotOpen;
timeStamp = 0;
}
void KSycoca::addFactory(KSycocaFactory *factory)
{
d->addFactory(factory);
}
void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
{
d->changeList = changeList;
//kDebug(7011) << QThread::currentThread() << "got a notifyDatabaseChanged signal" << changeList;
// kbuildsycoca tells us the database file changed
// Close the database and forget all about what we knew
// The next call to any public method will recreate
// everything that's needed.
d->closeDatabase();
// Now notify applications
emit databaseChanged(changeList);
}
QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
{
QDataStream* str = stream();
Q_ASSERT(str);
//kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
str->device()->seek(offset);
qint32 aType;
*str >> aType;
type = KSycocaType(aType);
//kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
return str;
}
KSycocaFactoryList* KSycoca::factories()
{
return d->factories();
}
// Warning, checkVersion rewinds to the beginning of stream().
bool KSycocaPrivate::checkVersion()
{
QDataStream *m_str = device()->stream();
Q_ASSERT(m_str);
m_str->device()->seek(0);
qint32 aVersion = 0;
*m_str >> aVersion;
if (aVersion < KSYCOCA_VERSION) {
kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
databaseStatus = BadVersion;
return false;
}
databaseStatus = DatabaseOK;
return true;
}
// If it returns true, the database is valid and the stream has rewinded to the beginning
// and past the version number.
bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
{
if (databaseStatus == DatabaseOK) {
if (checkVersion()) {
// the version is ok, but must rewind the stream anyway
return true;
}
}
closeDatabase(); // close the dummy one
// Check if new database already available
if (openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
if (checkVersion()) {
// Database exists, and version is ok.
return true;
}
}
if (ifNotFound & IfNotFoundRecreate) {
// We simply need to run kbuildsycoca to recreate the sycoca file.
kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME;
if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0) {
kWarning(7011) << "Running KSycoca failed.";
}
closeDatabase(); // close the dummy one
// Ok, the new database should be here now, open it.
if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
kDebug(7011) << "Still no database...";
return false; // Still no database - uh oh
}
if (!checkVersion()) {
kDebug(7011) << "Still outdated...";
return false; // Still outdated - uh oh
}
// If kded is not running we need to launch it as it monitors for changes
static const QString kdedInterface = QString::fromLatin1("org.kde.kded");
QDBusConnectionInterface* sessionInterface = QDBusConnection::sessionBus().interface();
const bool kdedRunning = sessionInterface->isServiceRegistered(kdedInterface);
if (!kdedRunning) {
kDebug(7011) << "Launching kded";
sessionInterface->startService(kdedInterface);
}
return true;
}
return false;
}
QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
{
// Ensure we have a valid database (right version, and rewinded to beginning)
if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
return 0;
}
QDataStream* str = stream();
Q_ASSERT(str);
qint32 aId;
qint32 aOffset;
while (true) {
*str >> aId;
if (aId == 0) {
kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
break;
}
*str >> aOffset;
if (aId == id) {
//kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
str->device()->seek(aOffset);
return str;
}
}
return 0;
}
QString KSycoca::kfsstnd_prefixes()
{
// do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
return QString();
}
QDataStream* str = stream();
Q_ASSERT(str);
qint32 aId;
qint32 aOffset;
// skip factories offsets
while(true)
{
*str >> aId;
if (aId) {
*str >> aOffset;
} else {
// just read 0
break;
}
}
// We now point to the header
QString prefixes;
KSycocaEntry::read(*str, prefixes);
*str >> d->timeStamp;
KSycocaEntry::read(*str, d->language);
*str >> d->updateSig;
KSycocaEntry::read(*str, d->allResourceDirs);
return prefixes;
}
quint32 KSycoca::timeStamp()
{
if (!d->timeStamp) {
(void) kfsstnd_prefixes();
}
return d->timeStamp;
}
quint32 KSycoca::updateSignature()
{
if (!d->timeStamp) {
(void) kfsstnd_prefixes();
}
return d->updateSig;
}
QString KSycoca::absoluteFilePath(DatabaseType type)
{
if (type == GlobalDatabase) {
QString path = KGlobal::dirs()->findResource("services", QString::fromLatin1(KSYCOCA_FILENAME));
if (path.isEmpty())
return KGlobal::dirs()->saveLocation("services") + QString::fromLatin1(KSYCOCA_FILENAME);
return path;
}
return KGlobal::dirs()->saveLocation("cache") + QString::fromLatin1(KSYCOCA_FILENAME);
}
QString KSycoca::language()
{
if (d->language.isEmpty()) {
(void) kfsstnd_prefixes();
}
return d->language;
}
QStringList KSycoca::allResourceDirs()
{
if (!d->timeStamp) {
(void) kfsstnd_prefixes();
}
return d->allResourceDirs;
}
void KSycoca::flagError()
{
kWarning(7011) << "KSycoca database corruption!";
KSycocaPrivate* d = ksycocaInstance->sycoca()->d;
if (d->readError) {
return;
}
d->readError = true;
if (s_autoRebuild) {
// Rebuild the damned thing.
if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0) {
kWarning(7011) << "Running KSycoca failed.";
}
// Old comment, maybe not true anymore:
// Do not wait until the DBUS signal from kbuildsycoca here.
// It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor...
}
}
bool KSycoca::isBuilding()
{
return false;
}
void KSycoca::disableAutoRebuild()
{
s_autoRebuild = false;
}
QDataStream*& KSycoca::stream()
{
return d->stream();
}
void KSycoca::clearCaches()
{
if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca()) {
ksycocaInstance->sycoca()->d->closeDatabase();
}
}
#include "moc_ksycoca.cpp"