File indexing completed on 2024-05-12 05:44:35
0001 /*************************************************************************** 0002 * Copyright (C) 2005-2009 by Rajko Albrecht ral@alwins-world.de * 0003 * https://kde.org/applications/development/org.kde.kdesvn * 0004 * * 0005 * This program is free software; you can redistribute it and/or * 0006 * modify it under the terms of the GNU Lesser General Public * 0007 * License as published by the Free Software Foundation; either * 0008 * version 2.1 of the License, or (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 0013 * Lesser General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU Lesser General Public * 0016 * License along with this program (in the file LGPL.txt); if not, * 0017 * write to the Free Software Foundation, Inc., 51 Franklin St, * 0018 * Fifth Floor, Boston, MA 02110-1301 USA * 0019 * * 0020 * This software consists of voluntary contributions made by many * 0021 * individuals. For exact contribution history, see the revision * 0022 * history and logs, available at https://commits.kde.org/kdesvn. * 0023 ***************************************************************************/ 0024 #include "LogCache.h" 0025 0026 #include <QDebug> 0027 #include <QDir> 0028 #include <QMap> 0029 #include <QMutex> 0030 #include <QSqlDatabase> 0031 #include <QSqlError> 0032 #include <QSqlQuery> 0033 #include <QThreadStorage> 0034 #include <QVariant> 0035 0036 #include "svnqt/cache/DatabaseException.h" 0037 #include "svnqt/path.h" 0038 0039 static QString SQLTYPE() 0040 { 0041 return QStringLiteral("QSQLITE"); 0042 } 0043 static QString SQLMAIN() 0044 { 0045 return QStringLiteral("logmain-logcache"); 0046 } 0047 static QString SQLMAINTABLE() 0048 { 0049 return QStringLiteral("logdb"); 0050 } 0051 static QString SQLTMPDB() 0052 { 0053 return QStringLiteral("tmpdb"); 0054 } 0055 static QString SQLREPOSPARAMETER() 0056 { 0057 return QStringLiteral("repoparameter"); 0058 } 0059 static QString SQLSTATUS() 0060 { 0061 return QStringLiteral("logstatus"); 0062 } 0063 0064 namespace svn 0065 { 0066 namespace cache 0067 { 0068 0069 LogCache *LogCache::mSelf = nullptr; 0070 0071 class ThreadDBStore 0072 { 0073 public: 0074 ThreadDBStore() 0075 { 0076 m_DB = QSqlDatabase(); 0077 } 0078 ~ThreadDBStore() 0079 { 0080 m_DB.commit(); 0081 m_DB.close(); 0082 m_DB = QSqlDatabase(); 0083 for (const QString &dbName : reposCacheNames) { 0084 if (QSqlDatabase::database(dbName).isOpen()) { 0085 QSqlDatabase::database(dbName).commit(); 0086 QSqlDatabase::database(dbName).close(); 0087 } 0088 QSqlDatabase::removeDatabase(dbName); 0089 } 0090 QSqlDatabase::removeDatabase(key); 0091 } 0092 0093 void deleteDb(const QString &path) 0094 { 0095 for (auto it = reposCacheNames.begin(); it != reposCacheNames.end(); ++it) { 0096 QSqlDatabase _db = QSqlDatabase::database(it.value()); 0097 if (_db.databaseName() == path) { 0098 qDebug() << "Removing database " << _db.databaseName() << Qt::endl; 0099 if (_db.isOpen()) { 0100 _db.commit(); 0101 _db.close(); 0102 } 0103 _db = QSqlDatabase(); 0104 QSqlDatabase::removeDatabase(it.value()); 0105 reposCacheNames.erase(it); 0106 break; 0107 } 0108 } 0109 } 0110 QSqlDatabase m_DB; 0111 QString key; 0112 QMap<QString, QString> reposCacheNames; 0113 }; 0114 0115 class LogCacheData 0116 { 0117 protected: 0118 QMutex m_singleDbMutex; 0119 0120 public: 0121 LogCacheData() 0122 { 0123 } 0124 ~LogCacheData() 0125 { 0126 if (m_mainDB.hasLocalData()) { 0127 m_mainDB.localData()->m_DB.close(); 0128 m_mainDB.setLocalData(nullptr); 0129 } 0130 } 0131 0132 QString idToPath(const QString &id) const 0133 { 0134 return m_BasePath + QLatin1Char('/') + id + QLatin1String(".db"); 0135 } 0136 0137 bool deleteRepository(const QString &aRepository) 0138 { 0139 const QString id = getReposId(aRepository); 0140 0141 static const QString s_q(QLatin1String("delete from ") + SQLREPOSPARAMETER() + QLatin1String(" where id = ?")); 0142 static const QString r_q(QLatin1String("delete from ") + SQLMAINTABLE() + QLatin1String(" where id = ?")); 0143 QSqlDatabase mainDB = getMainDB(); 0144 if (!mainDB.isValid()) { 0145 qWarning("Failed to open main database."); 0146 return false; 0147 } 0148 qDebug() << m_mainDB.localData()->reposCacheNames; 0149 m_mainDB.localData()->deleteDb(idToPath(id)); 0150 qDebug() << m_mainDB.localData()->reposCacheNames; 0151 QFile fi(idToPath(id)); 0152 if (fi.exists()) { 0153 if (!fi.remove()) { 0154 qWarning() << "Could not delete " << fi.fileName(); 0155 return false; 0156 } 0157 } 0158 qDebug() << "Removed " << fi.fileName() << Qt::endl; 0159 mainDB.transaction(); 0160 QSqlQuery _q(mainDB); 0161 _q.prepare(s_q); 0162 _q.bindValue(0, id); 0163 if (!_q.exec()) { 0164 qDebug() << "Error delete value: " << _q.lastError().text() << "(" << _q.lastQuery() << ")"; 0165 _q.finish(); 0166 mainDB.rollback(); 0167 return false; 0168 } 0169 _q.prepare(r_q); 0170 _q.bindValue(0, id); 0171 if (!_q.exec()) { 0172 qDebug() << "Error delete value: " << _q.lastError().text() << "(" << _q.lastQuery() << ")"; 0173 _q.finish(); 0174 mainDB.rollback(); 0175 return false; 0176 } 0177 mainDB.commit(); 0178 return true; 0179 } 0180 0181 bool checkReposDb(QSqlDatabase aDb) 0182 { 0183 if (!aDb.open()) { 0184 return false; 0185 } 0186 0187 QSqlQuery _q(aDb); 0188 QStringList list = aDb.tables(); 0189 0190 aDb.transaction(); 0191 if (!list.contains(QStringLiteral("logentries"))) { 0192 _q.exec( 0193 QStringLiteral("CREATE TABLE \"logentries\" (\"idx\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \"revision\" INTEGER UNIQUE,\"date\" " 0194 "INTEGER,\"author\" TEXT, \"message\" TEXT)")); 0195 } 0196 if (!list.contains(QStringLiteral("changeditems"))) { 0197 _q.exec( 0198 QStringLiteral("CREATE TABLE \"changeditems\" (\"idx\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \"revision\" INTEGER,\"changeditem\" " 0199 "TEXT,\"action\" TEXT,\"copyfrom\" TEXT,\"copyfromrev\" INTEGER, UNIQUE(revision,changeditem,action))")); 0200 } 0201 if (!list.contains(QStringLiteral("mergeditems"))) { 0202 _q.exec(QStringLiteral("CREATE TABLE \"mergeditems\" (\"revision\" INTEGER,\"mergeditems\" TEXT, PRIMARY KEY(revision))")); 0203 } 0204 if (!list.contains(QStringLiteral("dbversion"))) { 0205 _q.exec(QStringLiteral("CREATE TABLE \"dbversion\" (\"version\" INTEGER)")); 0206 qDebug() << _q.lastError(); 0207 _q.exec(QStringLiteral("INSERT INTO \"dbversion\" (version) VALUES(0)")); 0208 } 0209 aDb.commit(); 0210 list = aDb.tables(); 0211 if (!list.contains(QStringLiteral("logentries")) || !list.contains(QStringLiteral("changeditems")) || !list.contains(QStringLiteral("mergeditems")) 0212 || !list.contains(QStringLiteral("dbversion"))) { 0213 qDebug() << "lists: " << list; 0214 return false; 0215 } 0216 _q.exec(QStringLiteral("SELECT VERSION from dbversion limit 1")); 0217 if (_q.lastError().type() == QSqlError::NoError && _q.next()) { 0218 int version = _q.value(0).toInt(); 0219 if (version == 0) { 0220 _q.exec(QStringLiteral("create index if not exists main.authorindex on logentries(author)")); 0221 if (_q.lastError().type() != QSqlError::NoError) { 0222 qDebug() << _q.lastError(); 0223 } else { 0224 _q.exec(QStringLiteral("UPDATE dbversion SET VERSION=1")); 0225 } 0226 ++version; 0227 } 0228 if (version == 1) { 0229 _q.exec(QStringLiteral("create index if not exists main.dateindex on logentries(date)")); 0230 if (_q.lastError().type() != QSqlError::NoError) { 0231 qDebug() << _q.lastError(); 0232 } else { 0233 _q.exec(QStringLiteral("UPDATE dbversion SET VERSION=2")); 0234 } 0235 ++version; 0236 } 0237 } else { 0238 qDebug() << "Select: " << _q.lastError(); 0239 } 0240 return true; 0241 } 0242 0243 QString createReposDB(const svn::Path &reposroot) 0244 { 0245 QMutexLocker locker(&m_singleDbMutex); 0246 0247 QSqlDatabase _mdb = getMainDB(); 0248 0249 _mdb.transaction(); 0250 QSqlQuery query(_mdb); 0251 QString q(QLatin1String("insert into ") + SQLMAINTABLE() + QLatin1String(" (reposroot) VALUES('") + reposroot.path() + QLatin1String("')")); 0252 0253 if (!query.exec(q)) { 0254 return QString(); 0255 } 0256 0257 _mdb.commit(); 0258 query.prepare(reposSelect()); 0259 query.bindValue(0, reposroot.native()); 0260 QString db; 0261 if (query.exec() && query.next()) { 0262 db = query.value(0).toString(); 0263 } else { 0264 // qDebug() << "Error select_01: " << query.lastError().text() << "(" << query.lastQuery() << ")"; 0265 } 0266 if (!db.isEmpty()) { 0267 QString fulldb = idToPath(db); 0268 QSqlDatabase _db = QSqlDatabase::addDatabase(SQLTYPE(), SQLTMPDB()); 0269 _db.setDatabaseName(fulldb); 0270 if (!checkReposDb(_db)) { } 0271 _db = QSqlDatabase(); 0272 QSqlDatabase::removeDatabase(SQLTMPDB()); 0273 } 0274 return db; 0275 } 0276 0277 QString getReposId(const svn::Path &reposroot) 0278 { 0279 if (!getMainDB().isValid()) { 0280 return QString(); 0281 } 0282 QSqlQuery c(getMainDB()); 0283 c.prepare(reposSelect()); 0284 c.bindValue(0, reposroot.native()); 0285 0286 // only the first one 0287 if (c.exec() && c.next()) { 0288 return c.value(0).toString(); 0289 } 0290 return QString(); 0291 } 0292 0293 QSqlDatabase getReposDB(const svn::Path &reposroot) 0294 { 0295 if (!getMainDB().isValid()) { 0296 return QSqlDatabase(); 0297 } 0298 QString dbFile = getReposId(reposroot); 0299 0300 if (dbFile.isEmpty()) { 0301 dbFile = createReposDB(reposroot); 0302 if (dbFile.isEmpty()) { 0303 return QSqlDatabase(); 0304 } 0305 } 0306 if (m_mainDB.localData()->reposCacheNames.find(dbFile) != m_mainDB.localData()->reposCacheNames.end()) { 0307 QSqlDatabase db = QSqlDatabase::database(m_mainDB.localData()->reposCacheNames.value(dbFile)); 0308 checkReposDb(db); 0309 return db; 0310 } 0311 unsigned i = 0; 0312 QString _key = dbFile; 0313 while (QSqlDatabase::contains(_key)) { 0314 _key = QStringLiteral("%1-%2").arg(dbFile).arg(i++); 0315 } 0316 const QString fulldb = idToPath(dbFile); 0317 QSqlDatabase db = QSqlDatabase::addDatabase(SQLTYPE(), _key); 0318 db.setDatabaseName(fulldb); 0319 if (!checkReposDb(db)) { 0320 db = QSqlDatabase(); 0321 } else { 0322 m_mainDB.localData()->reposCacheNames[dbFile] = _key; 0323 } 0324 return db; 0325 } 0326 0327 QSqlDatabase getMainDB() const 0328 { 0329 if (!m_mainDB.hasLocalData()) { 0330 unsigned i = 0; 0331 QString _key = SQLMAIN(); 0332 while (QSqlDatabase::contains(_key)) { 0333 _key = QStringLiteral("%1-%2").arg(SQLMAIN()).arg(i++); 0334 } 0335 QSqlDatabase db = QSqlDatabase::addDatabase(SQLTYPE(), _key); 0336 db.setDatabaseName(m_BasePath + QLatin1String("/maindb.db")); 0337 if (db.open()) { 0338 m_mainDB.setLocalData(new ThreadDBStore); 0339 m_mainDB.localData()->key = _key; 0340 m_mainDB.localData()->m_DB = db; 0341 } 0342 } 0343 if (m_mainDB.hasLocalData()) { 0344 return m_mainDB.localData()->m_DB; 0345 } else { 0346 return QSqlDatabase(); 0347 } 0348 } 0349 QString m_BasePath; 0350 0351 mutable QThreadStorage<ThreadDBStore *> m_mainDB; 0352 0353 static QString reposSelect() 0354 { 0355 return QStringLiteral("SELECT id from ") + SQLMAINTABLE() + QStringLiteral(" where reposroot=? ORDER by id DESC"); 0356 } 0357 }; 0358 0359 /*! 0360 \fn svn::cache::LogCache::LogCache() 0361 */ 0362 LogCache::LogCache() 0363 : m_BasePath(QDir::homePath() + QLatin1String("/.svnqt")) 0364 { 0365 setupCachePath(); 0366 } 0367 0368 LogCache::LogCache(const QString &aBasePath) 0369 { 0370 delete mSelf; 0371 mSelf = this; 0372 if (aBasePath.isEmpty()) { 0373 m_BasePath = QDir::homePath() + QLatin1String("/.svnqt"); 0374 } else { 0375 m_BasePath = aBasePath; 0376 } 0377 setupCachePath(); 0378 } 0379 0380 LogCache::~LogCache() 0381 { 0382 } 0383 0384 /*! 0385 \fn svn::cache::LogCache::setupCachePath() 0386 */ 0387 void LogCache::setupCachePath() 0388 { 0389 m_CacheData.reset(new LogCacheData); 0390 m_CacheData->m_BasePath = m_BasePath; 0391 QDir d; 0392 if (!d.exists(m_BasePath)) { 0393 d.mkdir(m_BasePath); 0394 } 0395 m_BasePath = m_BasePath + QLatin1Char('/') + QLatin1String("logcache"); 0396 if (!d.exists(m_BasePath)) { 0397 d.mkdir(m_BasePath); 0398 } 0399 m_CacheData->m_BasePath = m_BasePath; 0400 if (d.exists(m_BasePath)) { 0401 setupMainDb(); 0402 } 0403 } 0404 0405 void LogCache::setupMainDb() 0406 { 0407 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0408 if (!mainDB.isValid()) { 0409 qWarning("Failed to open main database."); 0410 } else { 0411 const QStringList list = mainDB.tables(); 0412 QSqlQuery q(mainDB); 0413 if (!list.contains(SQLSTATUS())) { 0414 mainDB.transaction(); 0415 if (q.exec(QLatin1String("CREATE TABLE \"") + SQLSTATUS() + QLatin1String("\" (\"key\" TEXT PRIMARY KEY NOT NULL, \"value\" TEXT);"))) { 0416 q.exec(QLatin1String("INSERT INTO \"") + SQLSTATUS() + QLatin1String("\" (key,value) values(\"version\",\"0\");")); 0417 } 0418 mainDB.commit(); 0419 } 0420 int version = databaseVersion(); 0421 if (version == 0) { 0422 mainDB.transaction(); 0423 if (!list.contains(SQLMAINTABLE())) { 0424 q.exec(QLatin1String("CREATE TABLE IF NOT EXISTS \"") + SQLMAINTABLE() 0425 + QLatin1String("\" (\"reposroot\" TEXT,\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL);")); 0426 } /* else { 0427 q.exec("CREATE TABLE IF NOT EXISTS \""+QString(SQLMAINTABLE)+"new\" (\"reposroot\" TEXT,\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL);"); 0428 q.exec("insert into \""+QString(SQLMAINTABLE)+"new\" select \"reposroot\",\"id\" from \""+QString(SQLMAINTABLE)+"\");"); 0429 q.exec("drop table \""+QString(SQLMAINTABLE)+"\";"); 0430 q.exec("alter table \""+QString(SQLMAINTABLE)+"new\" to \""+QString(SQLMAINTABLE)+"\";"); 0431 }*/ 0432 ++version; 0433 } 0434 if (version == 1) { 0435 mainDB.transaction(); 0436 if (!q.exec(QLatin1String("CREATE TABLE IF NOT EXISTS \"") + SQLREPOSPARAMETER() 0437 + QLatin1String("\" (\"id\" INTEGER NOT NULL, \"parameter\" TEXT, \"value\" TEXT, PRIMARY KEY(\"id\",\"parameter\"));"))) { 0438 qDebug() << "Error create: " << q.lastError().text() << "(" << q.lastQuery() << ")"; 0439 } 0440 mainDB.commit(); 0441 ++version; 0442 } 0443 databaseVersion(version); 0444 } 0445 } 0446 0447 void LogCache::databaseVersion(int newversion) 0448 { 0449 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0450 if (!mainDB.isValid()) { 0451 return; 0452 } 0453 static const QString _qs(QLatin1String("update \"") + SQLSTATUS() + QLatin1String("\" SET value = ? WHERE \"key\" = \"version\"")); 0454 QSqlQuery cur(mainDB); 0455 cur.prepare(_qs); 0456 cur.bindValue(0, newversion); 0457 if (!cur.exec()) { 0458 qDebug() << "Error set version: " << cur.lastError().text() << "(" << cur.lastQuery() << ")"; 0459 } 0460 } 0461 0462 int LogCache::databaseVersion() const 0463 { 0464 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0465 if (!mainDB.isValid()) { 0466 return -1; 0467 } 0468 static const QString _qs(QLatin1String("select value from \"") + SQLSTATUS() + QLatin1String("\" WHERE \"key\" = \"version\"")); 0469 QSqlQuery cur(mainDB); 0470 cur.prepare(_qs); 0471 if (!cur.exec()) { 0472 qDebug() << "Error select version: " << cur.lastError().text() << "(" << cur.lastQuery() << ")"; 0473 return -1; 0474 } 0475 if (cur.isActive() && cur.next()) { 0476 // qDebug("Sel result: %s",_q.value(0).toString().toUtf8().data()); 0477 return cur.value(0).toInt(); 0478 } 0479 return -1; 0480 } 0481 0482 QVariant LogCache::getRepositoryParameter(const svn::Path &repository, const QString &key) const 0483 { 0484 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0485 if (!mainDB.isValid()) { 0486 return QVariant(); 0487 } 0488 static const QString qs(QLatin1String("select \"value\",\"repoparameter\".\"parameter\" as \"key\" from \"") + SQLREPOSPARAMETER() 0489 + QLatin1String("\" INNER JOIN \"") + SQLMAINTABLE() + QLatin1String("\" ON (\"") + SQLREPOSPARAMETER() 0490 + QLatin1String("\".id = \"") + SQLMAINTABLE() + QLatin1String("\".id and \"") + SQLMAINTABLE() 0491 + QLatin1String("\".reposroot = ?) WHERE \"parameter\" = ?;")); 0492 QSqlQuery cur(mainDB); 0493 cur.prepare(qs); 0494 cur.bindValue(0, repository.native()); 0495 cur.bindValue(1, key); 0496 if (!cur.exec()) { 0497 qWarning() << "Error select: " << cur.lastError().text() << "(" << cur.lastQuery() << ")"; 0498 return QVariant(); 0499 } 0500 if (cur.isActive() && cur.next()) { 0501 return cur.value(0); 0502 } 0503 return QVariant(); 0504 } 0505 0506 bool LogCache::setRepositoryParameter(const svn::Path &repository, const QString &key, const QVariant &value) 0507 { 0508 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0509 if (!mainDB.isValid()) { 0510 return false; 0511 } 0512 QString id = m_CacheData->getReposId(repository); 0513 if (id.isEmpty()) { 0514 return false; 0515 } 0516 static const QString qs(QLatin1String("INSERT OR REPLACE INTO \"") + SQLREPOSPARAMETER() 0517 + QLatin1String("\" (\"id\",\"parameter\",\"value\") values (\"%1\",\"%2\",?);")); 0518 static const QString dqs(QLatin1String("DELETE FROM \"") + SQLREPOSPARAMETER() + QLatin1String("\" WHERE \"id\"=? and \"parameter\" = ?")); 0519 mainDB.transaction(); 0520 QSqlQuery cur(mainDB); 0521 if (value.isValid()) { 0522 QString _qs = qs.arg(id, key); //.arg(value.toByteArray()); 0523 cur.prepare(_qs); 0524 cur.bindValue(0, value); 0525 if (!cur.exec()) { 0526 qDebug() << "Error insert new value: " << cur.lastError().text() << "(" << cur.lastQuery() << ")"; 0527 cur.finish(); 0528 mainDB.rollback(); 0529 return false; 0530 } 0531 } else { 0532 cur.prepare(dqs); 0533 cur.bindValue(0, id); 0534 cur.bindValue(1, key); 0535 if (!cur.exec()) { 0536 qDebug() << "Error delete value: " << cur.lastError().text() << "(" << cur.lastQuery() << ")"; 0537 cur.finish(); 0538 mainDB.rollback(); 0539 return false; 0540 } 0541 } 0542 mainDB.commit(); 0543 return true; 0544 } 0545 0546 } 0547 } 0548 0549 /*! 0550 \fn svn::cache::LogCache::self() 0551 */ 0552 svn::cache::LogCache *svn::cache::LogCache::self() 0553 { 0554 if (!mSelf) { 0555 mSelf = new LogCache(); 0556 } 0557 return mSelf; 0558 } 0559 0560 /*! 0561 \fn svn::cache::LogCache::reposDb() 0562 */ 0563 QSqlDatabase svn::cache::LogCache::reposDb(const QString &aRepository) 0564 { 0565 // //qDebug("reposDB"); 0566 return m_CacheData->getReposDB(aRepository); 0567 } 0568 0569 /*! 0570 \fn svn::cache::LogCache::cachedRepositories()const 0571 */ 0572 QStringList svn::cache::LogCache::cachedRepositories() const 0573 { 0574 static const QString s_q(QLatin1String("select \"reposroot\" from ") + SQLMAINTABLE() + QLatin1String(" order by reposroot")); 0575 QSqlDatabase mainDB = m_CacheData->getMainDB(); 0576 QStringList _res; 0577 if (!mainDB.isValid()) { 0578 qWarning("Failed to open main database."); 0579 return _res; 0580 } 0581 QSqlQuery cur(mainDB); 0582 cur.prepare(s_q); 0583 if (!cur.exec()) { 0584 throw svn::cache::DatabaseException(QLatin1String("Could not retrieve values: ") + cur.lastError().text()); 0585 } 0586 while (cur.next()) { 0587 _res.append(cur.value(0).toString()); 0588 } 0589 0590 return _res; 0591 } 0592 0593 bool svn::cache::LogCache::valid() const 0594 { 0595 return m_CacheData->getMainDB().isValid(); 0596 } 0597 0598 bool svn::cache::LogCache::deleteRepository(const QString &aRepository) 0599 { 0600 return m_CacheData->deleteRepository(aRepository); 0601 }