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