File indexing completed on 2025-01-05 03:53:58

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-03-18
0007  * Description : Core database access wrapper.
0008  *
0009  * SPDX-FileCopyrightText: 2007-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "coredbaccess.h"
0017 
0018 // Qt includes
0019 
0020 #include <QMutex>
0021 #include <QMutexLocker>
0022 #include <QSqlDatabase>
0023 #include <QUuid>
0024 
0025 // KDE includes
0026 
0027 #include <klocalizedstring.h>
0028 
0029 // Local includes
0030 
0031 #include "digikam_debug.h"
0032 #include "coredb.h"
0033 #include "collectionscannerobserver.h"
0034 #include "iteminfodata.h"
0035 #include "iteminfocache.h"
0036 #include "coredbschemaupdater.h"
0037 #include "collectionmanager.h"
0038 #include "coredbwatch.h"
0039 #include "coredbbackend.h"
0040 #include "dbengineerrorhandler.h"
0041 #include "tagscache.h"
0042 #include "dbengineaccess.h"
0043 
0044 namespace Digikam
0045 {
0046 
0047 class Q_DECL_HIDDEN CoreDbAccessStaticPriv
0048 {
0049 public:
0050 
0051     CoreDbAccessStaticPriv()
0052         : backend              (nullptr),
0053           db                   (nullptr),
0054           databaseWatch        (nullptr),
0055           // Create a unique identifier for this application (as an application accessing a database
0056           applicationIdentifier(QUuid::createUuid()),
0057           initializing         (false)
0058     {
0059     };
0060 
0061     ~CoreDbAccessStaticPriv()
0062     {
0063     };
0064 
0065 public:
0066 
0067     CoreDbBackend*      backend;
0068     CoreDB*             db;
0069     CoreDbWatch*        databaseWatch;
0070     DbEngineParameters  parameters;
0071     DbEngineLocking     lock;
0072     QString             lastError;
0073     QUuid               applicationIdentifier;
0074 
0075     bool                initializing;
0076 };
0077 
0078 CoreDbAccessStaticPriv* CoreDbAccess::d = nullptr;
0079 
0080 // -----------------------------------------------------------------------------
0081 
0082 class Q_DECL_HIDDEN CoreDbAccessMutexLocker
0083 
0084 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0085 
0086     : public QMutexLocker<QRecursiveMutex>
0087 
0088 #else
0089 
0090     : public QMutexLocker
0091 
0092 #endif
0093 
0094 {
0095 public:
0096 
0097     explicit CoreDbAccessMutexLocker(CoreDbAccessStaticPriv* const dd)
0098         : QMutexLocker(&dd->lock.mutex),
0099           d           (dd)
0100     {
0101         d->lock.lockCount++;
0102     }
0103 
0104     ~CoreDbAccessMutexLocker()
0105     {
0106         d->lock.lockCount--;
0107     }
0108 
0109 public:
0110 
0111     CoreDbAccessStaticPriv* const d;
0112 };
0113 
0114 // -----------------------------------------------------------------------------
0115 
0116 CoreDbAccess::CoreDbAccess()
0117 {
0118     // You will want to call setParameters before constructing CoreDbAccess
0119 
0120     Q_ASSERT(d);
0121 
0122     d->lock.mutex.lock();
0123     d->lock.lockCount++;
0124 
0125     if (!d->backend->isOpen() && !d->initializing)
0126     {
0127         // avoid endless loops (e.g. recursing from CollectionManager)
0128 
0129         d->initializing = true;
0130 
0131         d->backend->open(d->parameters);
0132         d->databaseWatch->setDatabaseIdentifier(d->db->databaseUuid().toString());
0133         CollectionManager::instance()->refresh();
0134 
0135         d->initializing = false;
0136     }
0137 }
0138 
0139 CoreDbAccess::~CoreDbAccess()
0140 {
0141     d->lock.lockCount--;
0142     d->lock.mutex.unlock();
0143 }
0144 
0145 CoreDbAccess::CoreDbAccess(bool)
0146 {
0147     // private constructor, when mutex is locked and
0148     // backend should not be checked
0149 
0150     d->lock.mutex.lock();
0151     d->lock.lockCount++;
0152 }
0153 
0154 CoreDB* CoreDbAccess::db() const
0155 {
0156     return d->db;
0157 }
0158 
0159 CoreDbBackend* CoreDbAccess::backend() const
0160 {
0161     return d->backend;
0162 }
0163 
0164 CoreDbWatch* CoreDbAccess::databaseWatch()
0165 {
0166     if (d)
0167     {
0168         return d->databaseWatch;
0169     }
0170 
0171     return nullptr;
0172 }
0173 
0174 void CoreDbAccess::initDbEngineErrorHandler(DbEngineErrorHandler* const errorhandler)
0175 {
0176     if (!d || !d->backend)
0177     {
0178         qCDebug(DIGIKAM_COREDB_LOG) << "Core database: please set parameters before setting a database error handler";
0179         return;
0180     }
0181 
0182     d->backend->setDbEngineErrorHandler(errorhandler);
0183 }
0184 
0185 DbEngineParameters CoreDbAccess::parameters()
0186 {
0187     if (d)
0188     {
0189         return d->parameters;
0190     }
0191 
0192     return DbEngineParameters();
0193 }
0194 
0195 void CoreDbAccess::setParameters(const DbEngineParameters& parameters)
0196 {
0197     setParameters(parameters, DatabaseSlave);
0198 
0199     if (d->databaseWatch)
0200     {
0201         d->databaseWatch->doAnyProcessing();
0202     }
0203 }
0204 
0205 void CoreDbAccess::setParameters(const DbEngineParameters& parameters, ApplicationStatus status)
0206 {
0207     if (!d)
0208     {
0209         d = new CoreDbAccessStaticPriv();
0210     }
0211 
0212     CoreDbAccessMutexLocker lock(d);
0213 
0214     if (d->parameters == parameters)
0215     {
0216         return;
0217     }
0218 
0219     if (d->backend && d->backend->isOpen())
0220     {
0221         d->backend->close();
0222     }
0223 
0224     // Kill the old database error handler
0225 
0226     if (d->backend)
0227     {
0228         d->backend->setDbEngineErrorHandler(nullptr);
0229     }
0230 
0231     d->parameters = parameters;
0232 
0233     if (!d->databaseWatch)
0234     {
0235         d->databaseWatch = new CoreDbWatch();
0236         d->databaseWatch->setApplicationIdentifier(d->applicationIdentifier.toString());
0237 
0238         if (status == MainApplication)
0239         {
0240             d->databaseWatch->initializeRemote(CoreDbWatch::DatabaseMaster);
0241         }
0242         else
0243         {
0244             d->databaseWatch->initializeRemote(CoreDbWatch::DatabaseSlave);
0245         }
0246     }
0247 
0248     ItemInfoStatic::create();
0249 
0250     if (!d->backend || !d->backend->isCompatible(parameters))
0251     {
0252         delete d->db;
0253         delete d->backend;
0254         d->backend = new CoreDbBackend(&d->lock);
0255         d->backend->setCoreDbWatch(d->databaseWatch);
0256         d->db      = new CoreDB(d->backend);
0257         TagsCache::instance()->initialize();
0258     }
0259 
0260     d->databaseWatch->sendDatabaseChanged();
0261     ItemInfoStatic::cache()->invalidate();
0262     TagsCache::instance()->invalidate();
0263     d->databaseWatch->setDatabaseIdentifier(QString());
0264     CollectionManager::instance()->clearLocations();
0265 }
0266 
0267 bool CoreDbAccess::checkReadyForUse(InitializationObserver* const observer)
0268 {
0269     if (!DbEngineAccess::checkReadyForUse(d->lastError))
0270     {
0271         return false;
0272     }
0273 
0274     if (!DbEngineConfig::checkReadyForUse())
0275     {
0276         d->lastError = DbEngineConfig::errorMessage();
0277 
0278         // Make sure the application does not continue to run
0279 
0280         if (observer)
0281         {
0282             observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort);
0283         }
0284 
0285         return false;
0286     }
0287 
0288     // Create an object with private shortcut constructor
0289 
0290     CoreDbAccess access(false);
0291 
0292     if (!d->backend)
0293     {
0294         qCWarning(DIGIKAM_COREDB_LOG) << "Core database: no database backend available in checkReadyForUse. "
0295                                          "Did you call setParameters before?";
0296         return false;
0297     }
0298 
0299     if (d->backend->isReady())
0300     {
0301         return true;
0302     }
0303 
0304     // TODO: Implement a method to wait until the database is open
0305 
0306     if (!d->backend->isOpen())
0307     {
0308         if (!d->backend->open(d->parameters))
0309         {
0310             access.setLastError(i18n("Error opening database backend.\n%1",
0311                                 d->backend->lastError()));
0312             return false;
0313         }
0314     }
0315 
0316     // Avoid endless loops (if called methods create new CoreDbAccess objects)
0317 
0318     d->initializing = true;
0319 
0320     // Update schema
0321 
0322     CoreDbSchemaUpdater updater(access.db(), access.backend(), access.parameters());
0323     updater.setCoreDbAccess(&access);
0324     updater.setObserver(observer);
0325 
0326     // Check or set WAL mode for SQLite database from DbEngineParameters
0327 
0328     d->backend->checkOrSetWALMode();
0329 
0330     if (!d->backend->initSchema(&updater))
0331     {
0332         qCWarning(DIGIKAM_COREDB_LOG) << "Core database: cannot process schema initialization";
0333 
0334         access.setLastError(updater.getLastErrorMessage());
0335         d->initializing = false;
0336         return false;
0337     }
0338 
0339     // Set identifier again
0340 
0341     d->databaseWatch->setDatabaseIdentifier(d->db->databaseUuid().toString());
0342 
0343     // Initialize CollectionManager
0344 
0345     CollectionManager::instance()->refresh();
0346 
0347     d->initializing = false;
0348 
0349     return d->backend->isReady();
0350 }
0351 
0352 QString CoreDbAccess::lastError()
0353 {
0354     return d->lastError;
0355 }
0356 
0357 void CoreDbAccess::setLastError(const QString& error)
0358 {
0359     d->lastError = error;
0360 }
0361 
0362 void CoreDbAccess::cleanUpDatabase()
0363 {
0364     if (d)
0365     {
0366         CoreDbAccessMutexLocker locker(d);
0367 
0368         if (d->backend)
0369         {
0370             d->backend->close();
0371             delete d->db;
0372             delete d->backend;
0373             delete d->databaseWatch;
0374         }
0375     }
0376 
0377     ItemInfoStatic::destroy();
0378     delete d;
0379     d = nullptr;
0380 }
0381 
0382 // ----------------------------------------------------------------------
0383 
0384 CoreDbAccessUnlock::CoreDbAccessUnlock()
0385 {
0386     // acquire lock
0387 
0388     CoreDbAccess::d->lock.mutex.lock();
0389 
0390     // store lock count
0391 
0392     count = CoreDbAccess::d->lock.lockCount;
0393 
0394     // set lock count to 0
0395 
0396     CoreDbAccess::d->lock.lockCount = 0;
0397 
0398     // unlock
0399 
0400     for (int i = 0 ; i < count ; ++i)
0401     {
0402         CoreDbAccess::d->lock.mutex.unlock();
0403     }
0404 
0405     // drop lock acquired in first line. Mutex is now free.
0406 
0407     CoreDbAccess::d->lock.mutex.unlock();
0408 }
0409 
0410 CoreDbAccessUnlock::CoreDbAccessUnlock(CoreDbAccess* const)
0411 {
0412     // With the passed pointer, we have assured that the mutex is acquired
0413     // Store lock count
0414 
0415     count = CoreDbAccess::d->lock.lockCount;
0416 
0417     // set lock count to 0
0418 
0419     CoreDbAccess::d->lock.lockCount = 0;
0420 
0421     // unlock
0422 
0423     for (int i = 0 ; i < count ; ++i)
0424     {
0425         CoreDbAccess::d->lock.mutex.unlock();
0426     }
0427 
0428     // Mutex is now free
0429 }
0430 
0431 CoreDbAccessUnlock::~CoreDbAccessUnlock()
0432 {
0433     // lock as often as it was locked before
0434 
0435     for (int i = 0 ; i < count ; ++i)
0436     {
0437         CoreDbAccess::d->lock.mutex.lock();
0438     }
0439 
0440     // update lock count
0441 
0442     CoreDbAccess::d->lock.lockCount += count;
0443 }
0444 
0445 } // namespace Digikam