ctkPluginStorageSQL.cpp 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  1. /*=============================================================================
  2. Library: CTK
  3. Copyright (c) German Cancer Research Center,
  4. Division of Medical and Biological Informatics
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. =============================================================================*/
  15. #include "ctkPluginStorageSQL_p.h"
  16. #include "ctkPluginDatabaseException.h"
  17. #include "ctkPlugin.h"
  18. #include "ctkPluginConstants.h"
  19. #include "ctkPluginException.h"
  20. #include "ctkPluginArchiveSQL_p.h"
  21. #include "ctkPluginStorage_p.h"
  22. #include "ctkPluginFrameworkUtil_p.h"
  23. #include "ctkPluginFrameworkContext_p.h"
  24. #include "ctkServiceException.h"
  25. #include <QFileInfo>
  26. #include <QUrl>
  27. #include <QThread>
  28. //database table names
  29. #define PLUGINS_TABLE "Plugins"
  30. #define PLUGIN_RESOURCES_TABLE "PluginResources"
  31. //----------------------------------------------------------------------------
  32. enum TBindIndexes
  33. {
  34. EBindIndex=0,
  35. EBindIndex1,
  36. EBindIndex2,
  37. EBindIndex3,
  38. EBindIndex4,
  39. EBindIndex5,
  40. EBindIndex6,
  41. EBindIndex7
  42. };
  43. //----------------------------------------------------------------------------
  44. ctkPluginStorageSQL::ctkPluginStorageSQL(ctkPluginFrameworkContext *framework)
  45. : m_framework(framework)
  46. , m_nextFreeId(-1)
  47. {
  48. // See if we have a storage database
  49. m_databasePath = ctkPluginFrameworkUtil::getFileStorage(framework, "").absoluteFilePath("plugins.db");
  50. this->open();
  51. restorePluginArchives();
  52. }
  53. //----------------------------------------------------------------------------
  54. ctkPluginStorageSQL::~ctkPluginStorageSQL()
  55. {
  56. close();
  57. }
  58. //----------------------------------------------------------------------------
  59. QSqlDatabase ctkPluginStorageSQL::getConnection(bool create) const
  60. {
  61. if (m_connectionNames.hasLocalData() && QSqlDatabase::contains(m_connectionNames.localData()))
  62. {
  63. return QSqlDatabase::database(m_connectionNames.localData());
  64. }
  65. if (!create)
  66. {
  67. throw ctkPluginDatabaseException(QString("No database connection."),
  68. ctkPluginDatabaseException::DB_NOT_OPEN_ERROR);
  69. }
  70. m_connectionNames.setLocalData(getConnectionName());
  71. QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", m_connectionNames.localData());
  72. database.setDatabaseName(getDatabasePath());
  73. if (!database.isValid())
  74. {
  75. close();
  76. throw ctkPluginDatabaseException(QString("Invalid database connection: %1").arg(m_connectionNames.localData()),
  77. ctkPluginDatabaseException::DB_CONNECTION_INVALID);
  78. }
  79. if (!database.open())
  80. {
  81. close();
  82. throw ctkPluginDatabaseException(QString("Could not open database connection: %1 (%2)").arg(m_connectionNames.localData()).arg(database.lastError().text()),
  83. ctkPluginDatabaseException::DB_SQL_ERROR);
  84. }
  85. return database;
  86. }
  87. //----------------------------------------------------------------------------
  88. QString ctkPluginStorageSQL::getConnectionName() const
  89. {
  90. QString connectionName = QFileInfo(getDatabasePath()).completeBaseName();
  91. connectionName += QString("_0x%1").arg(reinterpret_cast<quintptr>(QThread::currentThread()), 2 * QT_POINTER_SIZE, 16, QLatin1Char('0'));
  92. return connectionName;
  93. }
  94. //----------------------------------------------------------------------------
  95. void ctkPluginStorageSQL::open()
  96. {
  97. QString path;
  98. //Create full path to database
  99. if(m_databasePath.isEmpty ())
  100. m_databasePath = getDatabasePath();
  101. path = m_databasePath;
  102. QFileInfo dbFileInfo(path);
  103. if (!dbFileInfo.dir().exists())
  104. {
  105. if(!QDir::root().mkpath(dbFileInfo.path()))
  106. {
  107. close();
  108. QString errorText("Could not create database directory: %1");
  109. throw ctkPluginDatabaseException(errorText.arg(dbFileInfo.path()), ctkPluginDatabaseException::DB_CREATE_DIR_ERROR);
  110. }
  111. }
  112. QSqlDatabase database = getConnection();
  113. //Check if the sqlite version supports foreign key constraints
  114. QSqlQuery query(database);
  115. if (!query.exec("PRAGMA foreign_keys"))
  116. {
  117. close();
  118. throw ctkPluginDatabaseException(QString("Check for foreign key support failed."),
  119. ctkPluginDatabaseException::DB_SQL_ERROR);
  120. }
  121. if (!query.next())
  122. {
  123. close();
  124. throw ctkPluginDatabaseException(QString("SQLite db does not support foreign keys. It is either older than 3.6.19 or was compiled with SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER"),
  125. ctkPluginDatabaseException::DB_SQL_ERROR);
  126. }
  127. query.finish();
  128. query.clear();
  129. //Enable foreign key support
  130. if (!query.exec("PRAGMA foreign_keys = ON"))
  131. {
  132. close();
  133. throw ctkPluginDatabaseException(QString("Enabling foreign key support failed."),
  134. ctkPluginDatabaseException::DB_SQL_ERROR);
  135. }
  136. query.finish();
  137. //Check database structure (tables) and recreate tables if neccessary
  138. //If one of the tables is missing remove all tables and recreate them
  139. //This operation is required in order to avoid data coruption
  140. if (!checkTables())
  141. {
  142. if (dropTables())
  143. {
  144. createTables();
  145. }
  146. else
  147. {
  148. //dropTables() should've handled error message
  149. //and warning
  150. close();
  151. }
  152. }
  153. // silently remove any plugin marked as uninstalled
  154. cleanupDB();
  155. //Update database based on the recorded timestamps
  156. updateDB();
  157. initNextFreeIds();
  158. }
  159. //----------------------------------------------------------------------------
  160. void ctkPluginStorageSQL::initNextFreeIds()
  161. {
  162. QSqlDatabase database = getConnection();
  163. QSqlQuery query(database);
  164. QString statement = "SELECT ID,MAX(Generation) FROM " PLUGINS_TABLE " GROUP BY ID";
  165. executeQuery(&query, statement);
  166. while (query.next())
  167. {
  168. m_generations.insert(query.value(EBindIndex).toInt(),
  169. query.value(EBindIndex1).toInt()+1);
  170. }
  171. query.finish();
  172. query.clear();
  173. statement = "SELECT MAX(ID) FROM " PLUGINS_TABLE;
  174. executeQuery(&query, statement);
  175. QVariant id = query.next() ? query.value(EBindIndex) : QVariant();
  176. if (id.isValid())
  177. {
  178. m_nextFreeId = id.toInt() + 1;
  179. }
  180. else
  181. {
  182. m_nextFreeId = 1;
  183. }
  184. }
  185. //----------------------------------------------------------------------------
  186. void ctkPluginStorageSQL::cleanupDB()
  187. {
  188. QSqlDatabase database = getConnection();
  189. QSqlQuery query(database);
  190. beginTransaction(&query, Write);
  191. try
  192. {
  193. // remove all plug-ins marked as UNINSTALLED
  194. QString statement = "DELETE FROM " PLUGINS_TABLE " WHERE StartLevel==-2";
  195. executeQuery(&query, statement);
  196. // remove all old plug-in generations
  197. statement = "DELETE FROM " PLUGINS_TABLE
  198. " WHERE K NOT IN (SELECT K FROM (SELECT K, MAX(Generation) FROM " PLUGINS_TABLE " GROUP BY ID))";
  199. }
  200. catch (...)
  201. {
  202. rollbackTransaction(&query);
  203. throw;
  204. }
  205. commitTransaction(&query);
  206. }
  207. //----------------------------------------------------------------------------
  208. void ctkPluginStorageSQL::updateDB()
  209. {
  210. QSqlDatabase database = getConnection();
  211. QSqlQuery query(database);
  212. beginTransaction(&query, Write);
  213. // 1. Get the state information of all plug-ins (it is assumed that
  214. // plug-ins marked as UNINSTALLED (startlevel == -2) are already removed
  215. QString statement = "SELECT ID,MAX(Generation),Location,LocalPath,Timestamp,StartLevel,AutoStart,K "
  216. "FROM " PLUGINS_TABLE " GROUP BY ID";
  217. QList<int> outdatedIds;
  218. QList<QSharedPointer<ctkPluginArchiveSQL> > updatedPluginArchives;
  219. try
  220. {
  221. executeQuery(&query, statement);
  222. // 2. Check the timestamp for each plug-in
  223. while (query.next())
  224. {
  225. QFileInfo pluginInfo(query.value(EBindIndex3).toString());
  226. QDateTime pluginLastModified = pluginInfo.lastModified();
  227. // Make sure the QDateTime has the same accuracy as the one in the database
  228. pluginLastModified = getQDateTimeFromString(getStringFromQDateTime(pluginLastModified));
  229. if (pluginLastModified > getQDateTimeFromString(query.value(EBindIndex4).toString()))
  230. {
  231. QSharedPointer<ctkPluginArchiveSQL> updatedPA(
  232. new ctkPluginArchiveSQL(this,
  233. query.value(EBindIndex2).toUrl(), // plug-in location url
  234. query.value(EBindIndex3).toString(), // plugin local path
  235. query.value(EBindIndex).toInt(), // plugin id
  236. query.value(EBindIndex5).toInt(), // start level
  237. QDateTime(), // last modififed
  238. query.value(EBindIndex6).toInt()) // auto start setting
  239. );
  240. updatedPA->key = query.value(EBindIndex7).toInt();
  241. updatedPluginArchives << updatedPA;
  242. // remember the plug-in ids for deletion
  243. outdatedIds << query.value(EBindIndex).toInt();
  244. }
  245. }
  246. }
  247. catch (...)
  248. {
  249. rollbackTransaction(&query);
  250. throw;
  251. }
  252. query.finish();
  253. query.clear();
  254. if (!outdatedIds.isEmpty())
  255. {
  256. // 3. Remove all traces from outdated plug-in data. Due to cascaded delete,
  257. // it is sufficient to remove the records from the main table
  258. statement = "DELETE FROM " PLUGINS_TABLE " WHERE ID IN (%1)";
  259. QString idStr;
  260. foreach(int id, outdatedIds)
  261. {
  262. idStr += QString::number(id) + ",";
  263. }
  264. idStr.chop(1);
  265. try
  266. {
  267. executeQuery(&query, statement.arg(idStr));
  268. }
  269. catch (...)
  270. {
  271. rollbackTransaction(&query);
  272. throw;
  273. }
  274. query.finish();
  275. query.clear();
  276. try
  277. {
  278. foreach (QSharedPointer<ctkPluginArchiveSQL> updatedPA, updatedPluginArchives)
  279. {
  280. insertArchive(updatedPA, &query);
  281. }
  282. }
  283. catch (...)
  284. {
  285. rollbackTransaction(&query);
  286. throw;
  287. }
  288. }
  289. commitTransaction(&query);
  290. }
  291. //----------------------------------------------------------------------------
  292. QLibrary::LoadHints ctkPluginStorageSQL::getPluginLoadHints() const
  293. {
  294. if (m_framework->props.contains(ctkPluginConstants::FRAMEWORK_PLUGIN_LOAD_HINTS))
  295. {
  296. QVariant loadHintsVariant = m_framework->props[ctkPluginConstants::FRAMEWORK_PLUGIN_LOAD_HINTS];
  297. if (loadHintsVariant.isValid())
  298. {
  299. return loadHintsVariant.value<QLibrary::LoadHints>();
  300. }
  301. }
  302. return QLibrary::LoadHints(0);
  303. }
  304. //----------------------------------------------------------------------------
  305. QSharedPointer<ctkPluginArchive> ctkPluginStorageSQL::insertPlugin(const QUrl& location, const QString& localPath)
  306. {
  307. QMutexLocker lock(&m_archivesLock);
  308. QFileInfo fileInfo(localPath);
  309. if (!fileInfo.exists())
  310. {
  311. throw ctkInvalidArgumentException(localPath + " does not exist");
  312. }
  313. const QString libTimestamp = getStringFromQDateTime(fileInfo.lastModified());
  314. QSharedPointer<ctkPluginArchiveSQL> archive(new ctkPluginArchiveSQL(this, location, localPath,
  315. m_nextFreeId++));
  316. try
  317. {
  318. insertArchive(archive);
  319. m_archives << archive;
  320. return archive;
  321. }
  322. catch(...)
  323. {
  324. m_nextFreeId--;
  325. throw;
  326. }
  327. }
  328. //----------------------------------------------------------------------------
  329. void ctkPluginStorageSQL::insertArchive(QSharedPointer<ctkPluginArchiveSQL> pa)
  330. {
  331. QSqlDatabase database = getConnection();
  332. QSqlQuery query(database);
  333. beginTransaction(&query, Write);
  334. try
  335. {
  336. insertArchive(pa, &query);
  337. }
  338. catch (...)
  339. {
  340. rollbackTransaction(&query);
  341. throw;
  342. }
  343. commitTransaction(&query);
  344. }
  345. //----------------------------------------------------------------------------
  346. void ctkPluginStorageSQL::insertArchive(QSharedPointer<ctkPluginArchiveSQL> pa, QSqlQuery* query)
  347. {
  348. QFileInfo fileInfo(pa->getLibLocation());
  349. QString libTimestamp = getStringFromQDateTime(fileInfo.lastModified());
  350. QString resourcePrefix = fileInfo.baseName();
  351. if (resourcePrefix.startsWith("lib"))
  352. {
  353. resourcePrefix = resourcePrefix.mid(3);
  354. }
  355. resourcePrefix.replace("_", ".");
  356. resourcePrefix = QString(":/") + resourcePrefix + "/";
  357. // Load the plugin and cache the resources
  358. QPluginLoader pluginLoader;
  359. pluginLoader.setLoadHints(getPluginLoadHints());
  360. pluginLoader.setFileName(pa->getLibLocation());
  361. if (!pluginLoader.load())
  362. {
  363. ctkPluginException exc(QString("The plugin \"%1\" could not be loaded: %2").arg(pa->getLibLocation())
  364. .arg(pluginLoader.errorString()));
  365. throw exc;
  366. }
  367. QFile manifestResource(resourcePrefix + "META-INF/MANIFEST.MF");
  368. manifestResource.open(QIODevice::ReadOnly);
  369. QByteArray manifest = manifestResource.readAll();
  370. manifestResource.close();
  371. // Finally, complete the ctkPluginArchive information by reading the MANIFEST.MF resource
  372. pa->readManifest(manifest);
  373. // Assemble the data for the sql records
  374. QString version = pa->getAttribute(ctkPluginConstants::PLUGIN_VERSION);
  375. if (version.isEmpty()) version = "na";
  376. QString statement = "INSERT INTO " PLUGINS_TABLE " (ID,Generation,Location,LocalPath,SymbolicName,Version,LastModified,Timestamp,StartLevel,AutoStart) "
  377. "VALUES (?,?,?,?,?,?,?,?,?,?)";
  378. QList<QVariant> bindValues;
  379. bindValues << pa->getPluginId();
  380. bindValues << pa->getPluginGeneration();
  381. bindValues << pa->getPluginLocation();
  382. bindValues << pa->getLibLocation();
  383. bindValues << pa->getAttribute(ctkPluginConstants::PLUGIN_SYMBOLICNAME);
  384. bindValues << version;
  385. bindValues << "na";
  386. bindValues << libTimestamp;
  387. bindValues << pa->getStartLevel();
  388. bindValues << pa->getAutostartSetting();
  389. executeQuery(query, statement, bindValues);
  390. pa->key = query->lastInsertId().toInt();
  391. // Write the plug-in resource data into the database
  392. QDirIterator dirIter(resourcePrefix, QDirIterator::Subdirectories);
  393. while (dirIter.hasNext())
  394. {
  395. QString resourcePath = dirIter.next();
  396. if (QFileInfo(resourcePath).isDir()) continue;
  397. QFile resourceFile(resourcePath);
  398. resourceFile.open(QIODevice::ReadOnly);
  399. QByteArray resourceData = resourceFile.readAll();
  400. resourceFile.close();
  401. statement = "INSERT INTO " PLUGIN_RESOURCES_TABLE " (K,ResourcePath,Resource) VALUES(?,?,?)";
  402. bindValues.clear();
  403. bindValues << pa->key;
  404. bindValues << resourcePath.mid(resourcePrefix.size()-1);
  405. bindValues << resourceData;
  406. executeQuery(query, statement, bindValues);
  407. }
  408. pluginLoader.unload();
  409. }
  410. //----------------------------------------------------------------------------
  411. QSharedPointer<ctkPluginArchive> ctkPluginStorageSQL::updatePluginArchive(QSharedPointer<ctkPluginArchive> old,
  412. const QUrl& updateLocation,
  413. const QString& localPath)
  414. {
  415. QSharedPointer<ctkPluginArchive> newPA(new ctkPluginArchiveSQL(
  416. qSharedPointerCast<ctkPluginArchiveSQL>(old),
  417. m_generations[old->getPluginId()]++,
  418. updateLocation, localPath)
  419. );
  420. return newPA;
  421. }
  422. void ctkPluginStorageSQL::replacePluginArchive(QSharedPointer<ctkPluginArchive> oldPA, QSharedPointer<ctkPluginArchive> newPA)
  423. {
  424. QMutexLocker lock(&m_archivesLock);
  425. int pos;
  426. long id = oldPA->getPluginId();
  427. pos = find(id);
  428. if (pos >= m_archives.size() || m_archives[pos] != oldPA)
  429. {
  430. throw ctkRuntimeException(QString("replacePluginArchive: Old plugin archive not found, pos=").append(pos));
  431. }
  432. QSqlDatabase database = getConnection();
  433. QSqlQuery query(database);
  434. beginTransaction(&query, Write);
  435. try
  436. {
  437. removeArchiveFromDB(static_cast<ctkPluginArchiveSQL*>(oldPA.data()), &query);
  438. insertArchive(qSharedPointerCast<ctkPluginArchiveSQL>(newPA), &query);
  439. commitTransaction(&query);
  440. m_archives[pos] = newPA;
  441. }
  442. catch (const ctkRuntimeException& re)
  443. {
  444. rollbackTransaction(&query);
  445. qWarning() << "Removing plug-in archive failed:" << re;
  446. throw;
  447. }
  448. catch (...)
  449. {
  450. rollbackTransaction(&query);
  451. throw;
  452. }
  453. }
  454. //----------------------------------------------------------------------------
  455. bool ctkPluginStorageSQL::removeArchive(ctkPluginArchiveSQL* pa)
  456. {
  457. QSqlDatabase database = getConnection();
  458. QSqlQuery query(database);
  459. beginTransaction(&query, Write);
  460. try
  461. {
  462. removeArchiveFromDB(pa, &query);
  463. commitTransaction(&query);
  464. QMutexLocker lock(&m_archivesLock);
  465. int idx = find(pa);
  466. if (idx >= 0 && idx < m_archives.size())
  467. {
  468. m_archives.removeAt(idx);
  469. }
  470. return true;
  471. }
  472. catch (const ctkRuntimeException& re)
  473. {
  474. rollbackTransaction(&query);
  475. qWarning() << "Removing plug-in archive failed:" << re;
  476. }
  477. catch (...)
  478. {
  479. qWarning() << "Removing plug-in archive failed: Unexpected exception";
  480. rollbackTransaction(&query);
  481. }
  482. return false;
  483. }
  484. //----------------------------------------------------------------------------
  485. bool ctkPluginStorageSQL::removeArchive(QSharedPointer<ctkPluginArchive> pa)
  486. {
  487. return removeArchive(static_cast<ctkPluginArchiveSQL*>(pa.data()));
  488. }
  489. //----------------------------------------------------------------------------
  490. void ctkPluginStorageSQL::removeArchiveFromDB(ctkPluginArchiveSQL* pa, QSqlQuery* query)
  491. {
  492. QString statement = "DELETE FROM " PLUGINS_TABLE " WHERE K=?";
  493. QList<QVariant> bindValues;
  494. bindValues.append(pa->key);
  495. executeQuery(query, statement, bindValues);
  496. }
  497. QList<QSharedPointer<ctkPluginArchive> > ctkPluginStorageSQL::getAllPluginArchives() const
  498. {
  499. return m_archives;
  500. }
  501. QList<QString> ctkPluginStorageSQL::getStartOnLaunchPlugins() const
  502. {
  503. QList<QString> res;
  504. foreach(QSharedPointer<ctkPluginArchive> pa, m_archives)
  505. {
  506. if (pa->getAutostartSetting() != -1)
  507. {
  508. res.push_back(pa->getPluginLocation().toString());
  509. }
  510. }
  511. return res;
  512. }
  513. //----------------------------------------------------------------------------
  514. void ctkPluginStorageSQL::setStartLevel(int key, int startLevel)
  515. {
  516. QSqlDatabase database = getConnection();
  517. QSqlQuery query(database);
  518. QString statement = "UPDATE " PLUGINS_TABLE " SET StartLevel=? WHERE K=?";
  519. QList<QVariant> bindValues;
  520. bindValues.append(startLevel);
  521. bindValues.append(key);
  522. executeQuery(&query, statement, bindValues);
  523. }
  524. //----------------------------------------------------------------------------
  525. void ctkPluginStorageSQL::setLastModified(int key, const QDateTime& lastModified)
  526. {
  527. QSqlDatabase database = getConnection();
  528. QSqlQuery query(database);
  529. QString statement = "UPDATE " PLUGINS_TABLE " SET LastModified=? WHERE K=?";
  530. QList<QVariant> bindValues;
  531. bindValues.append(getStringFromQDateTime(lastModified));
  532. bindValues.append(key);
  533. executeQuery(&query, statement, bindValues);
  534. }
  535. //----------------------------------------------------------------------------
  536. void ctkPluginStorageSQL::setAutostartSetting(int key, int autostart)
  537. {
  538. QSqlDatabase database = getConnection();
  539. QSqlQuery query(database);
  540. QString statement = "UPDATE " PLUGINS_TABLE " SET AutoStart=? WHERE K=?";
  541. QList<QVariant> bindValues;
  542. bindValues.append(autostart);
  543. bindValues.append(key);
  544. executeQuery(&query, statement, bindValues);
  545. }
  546. //----------------------------------------------------------------------------
  547. QStringList ctkPluginStorageSQL::findResourcesPath(int archiveKey, const QString& path) const
  548. {
  549. QSqlDatabase database = getConnection();
  550. QSqlQuery query(database);
  551. QString statement = "SELECT SUBSTR(ResourcePath,?) FROM PluginResources WHERE K=? AND SUBSTR(ResourcePath,1,?)=?";
  552. QString resourcePath = path.startsWith('/') ? path : QString("/") + path;
  553. if (!resourcePath.endsWith('/'))
  554. resourcePath += "/";
  555. QList<QVariant> bindValues;
  556. bindValues.append(resourcePath.size()+1);
  557. bindValues.append(archiveKey);
  558. bindValues.append(resourcePath.size());
  559. bindValues.append(resourcePath);
  560. executeQuery(&query, statement, bindValues);
  561. QSet<QString> paths;
  562. while (query.next())
  563. {
  564. QString currPath = query.value(EBindIndex).toString();
  565. QStringList components = currPath.split('/', QString::SkipEmptyParts);
  566. if (components.size() == 1)
  567. {
  568. paths << components.front();
  569. }
  570. else if (components.size() == 2)
  571. {
  572. paths << components.front() + "/";
  573. }
  574. }
  575. return paths.toList();
  576. }
  577. //----------------------------------------------------------------------------
  578. void ctkPluginStorageSQL::executeQuery(QSqlQuery *query, const QString &statement, const QList<QVariant> &bindValues) const
  579. {
  580. Q_ASSERT(query != 0);
  581. bool success = false;
  582. enum {Prepare =0 , Execute=1};
  583. for (int stage=Prepare; stage <= Execute; ++stage)
  584. {
  585. if ( stage == Prepare)
  586. success = query->prepare(statement);
  587. else // stage == Execute
  588. success = query->exec();
  589. if (!success)
  590. {
  591. QString errorText = "Problem: Could not %1 statement: %2\n"
  592. "Reason: %3\n"
  593. "Parameters: %4\n";
  594. QString parameters;
  595. if (bindValues.count() > 0)
  596. {
  597. for (int i = 0; i < bindValues.count(); ++i)
  598. {
  599. parameters.append(QString("\n\t[") + QString::number(i) + "]: " + bindValues.at(i).toString());
  600. }
  601. }
  602. else
  603. {
  604. parameters = "None";
  605. }
  606. ctkPluginDatabaseException::Type errorType;
  607. int result = query->lastError().number();
  608. if (result == 26 || result == 11) //SQLILTE_NOTADB || SQLITE_CORRUPT
  609. {
  610. qWarning() << "ctkPluginFramework:- Database file is corrupt or invalid:" << getDatabasePath();
  611. errorType = ctkPluginDatabaseException::DB_FILE_INVALID;
  612. }
  613. else if (result == 8) //SQLITE_READONLY
  614. errorType = ctkPluginDatabaseException::DB_WRITE_ERROR;
  615. else
  616. errorType = ctkPluginDatabaseException::DB_SQL_ERROR;
  617. query->finish();
  618. query->clear();
  619. throw ctkPluginDatabaseException(errorText.arg(stage == Prepare ? "prepare":"execute")
  620. .arg(statement).arg(query->lastError().text()).arg(parameters), errorType);
  621. }
  622. if (stage == Prepare)
  623. {
  624. foreach(const QVariant &bindValue, bindValues)
  625. query->addBindValue(bindValue);
  626. }
  627. }
  628. }
  629. //----------------------------------------------------------------------------
  630. void ctkPluginStorageSQL::close()
  631. {
  632. const_cast<const ctkPluginStorageSQL*>(this)->close();
  633. }
  634. //----------------------------------------------------------------------------
  635. void ctkPluginStorageSQL::close() const
  636. {
  637. if (isOpen())
  638. {
  639. QSqlDatabase database = getConnection(false);
  640. if (database.isValid())
  641. {
  642. if(database.isOpen())
  643. {
  644. database.close();
  645. return;
  646. }
  647. }
  648. else
  649. {
  650. throw ctkPluginDatabaseException(QString("Problem closing database: Invalid connection %1").arg(m_connectionNames.localData()));
  651. }
  652. }
  653. }
  654. //----------------------------------------------------------------------------
  655. void ctkPluginStorageSQL::setDatabasePath(const QString &databasePath)
  656. {
  657. m_databasePath = QDir::toNativeSeparators(databasePath);
  658. }
  659. //----------------------------------------------------------------------------
  660. QString ctkPluginStorageSQL::getDatabasePath() const
  661. {
  662. QString path = m_databasePath;
  663. if(path.isEmpty())
  664. {
  665. path = QDir::homePath() + "/ctkpluginfw/plugins.db";
  666. qWarning() << "No database path set. Using default:" << path;
  667. }
  668. path = QDir::toNativeSeparators(path);
  669. qDebug() << "Using database:" << path;
  670. return path;
  671. }
  672. //----------------------------------------------------------------------------
  673. QByteArray ctkPluginStorageSQL::getPluginResource(int key, const QString& res) const
  674. {
  675. QSqlDatabase database = getConnection();
  676. QSqlQuery query(database);
  677. QString statement = "SELECT Resource FROM PluginResources WHERE K=? AND ResourcePath=?";
  678. QString resourcePath = res.startsWith('/') ? res : QString("/") + res;
  679. QList<QVariant> bindValues;
  680. bindValues.append(key);
  681. bindValues.append(resourcePath);
  682. executeQuery(&query, statement, bindValues);
  683. if (query.next())
  684. {
  685. return query.value(EBindIndex).toByteArray();
  686. }
  687. return QByteArray();
  688. }
  689. //----------------------------------------------------------------------------
  690. void ctkPluginStorageSQL::createTables()
  691. {
  692. QSqlDatabase database = getConnection();
  693. QSqlQuery query(database);
  694. //Begin Transaction
  695. beginTransaction(&query, Write);
  696. QString statement("CREATE TABLE " PLUGINS_TABLE " ("
  697. "K INTEGER PRIMARY KEY,"
  698. "ID INTEGER NOT NULL,"
  699. "Generation INTEGER NOT NULL,"
  700. "Location TEXT NOT NULL,"
  701. "LocalPath TEXT NOT NULL,"
  702. "SymbolicName TEXT NOT NULL,"
  703. "Version TEXT NOT NULL,"
  704. "LastModified TEXT NOT NULL,"
  705. "Timestamp TEXT NOT NULL,"
  706. "StartLevel INTEGER NOT NULL,"
  707. "AutoStart INTEGER NOT NULL)");
  708. try
  709. {
  710. executeQuery(&query, statement);
  711. }
  712. catch (...)
  713. {
  714. rollbackTransaction(&query);
  715. throw;
  716. }
  717. statement = "CREATE TABLE " PLUGIN_RESOURCES_TABLE " ("
  718. "K INTEGER NOT NULL,"
  719. "ResourcePath TEXT NOT NULL,"
  720. "Resource BLOB NOT NULL,"
  721. "FOREIGN KEY(K) REFERENCES " PLUGINS_TABLE "(K) ON DELETE CASCADE)";
  722. try
  723. {
  724. executeQuery(&query, statement);
  725. }
  726. catch (...)
  727. {
  728. rollbackTransaction(&query);
  729. throw;
  730. }
  731. try
  732. {
  733. commitTransaction(&query);
  734. }
  735. catch (...)
  736. {
  737. rollbackTransaction(&query);
  738. throw;
  739. }
  740. }
  741. //----------------------------------------------------------------------------
  742. bool ctkPluginStorageSQL::checkTables() const
  743. {
  744. QSqlDatabase database = getConnection();
  745. bool bTables(false);
  746. QStringList tables = database.tables();
  747. if (tables.contains(PLUGINS_TABLE) &&
  748. tables.contains(PLUGIN_RESOURCES_TABLE))
  749. {
  750. bTables = true;
  751. }
  752. return bTables;
  753. }
  754. //----------------------------------------------------------------------------
  755. bool ctkPluginStorageSQL::dropTables()
  756. {
  757. //Execute transaction for deleting the database tables
  758. QSqlDatabase database = getConnection();
  759. QSqlQuery query(database);
  760. QStringList expectedTables;
  761. expectedTables << PLUGINS_TABLE << PLUGIN_RESOURCES_TABLE;
  762. if (database.tables().count() > 0)
  763. {
  764. beginTransaction(&query, Write);
  765. QStringList actualTables = database.tables();
  766. foreach(const QString expectedTable, expectedTables)
  767. {
  768. if (actualTables.contains(expectedTable))
  769. {
  770. try
  771. {
  772. executeQuery(&query, QString("DROP TABLE ") + expectedTable);
  773. }
  774. catch (...)
  775. {
  776. rollbackTransaction(&query);
  777. throw;
  778. }
  779. }
  780. try
  781. {
  782. commitTransaction(&query);
  783. }
  784. catch (...)
  785. {
  786. rollbackTransaction(&query);
  787. throw;
  788. }
  789. }
  790. }
  791. return true;
  792. }
  793. //----------------------------------------------------------------------------
  794. bool ctkPluginStorageSQL::isOpen() const
  795. {
  796. try
  797. {
  798. getConnection(false);
  799. }
  800. catch (const ctkPluginDatabaseException&)
  801. {
  802. return false;
  803. }
  804. return true;
  805. }
  806. int ctkPluginStorageSQL::find(long id) const
  807. {
  808. int lb = 0;
  809. int ub = m_archives.size() - 1;
  810. int x = 0;
  811. while (lb < ub)
  812. {
  813. x = (lb + ub) / 2;
  814. long xid = m_archives[x]->getPluginId();
  815. if (id == xid)
  816. {
  817. return x;
  818. }
  819. else if (id < xid)
  820. {
  821. ub = x;
  822. }
  823. else
  824. {
  825. lb = x+1;
  826. }
  827. }
  828. if (lb < m_archives.size() && m_archives[lb]->getPluginId() < id)
  829. {
  830. return lb + 1;
  831. }
  832. return lb;
  833. }
  834. //----------------------------------------------------------------------------
  835. int ctkPluginStorageSQL::find(ctkPluginArchive *pa) const
  836. {
  837. for (int i = 0; i < m_archives.size(); ++i)
  838. {
  839. if (m_archives[i].data() == pa) return i;
  840. }
  841. return -1;
  842. }
  843. //----------------------------------------------------------------------------
  844. void ctkPluginStorageSQL::beginTransaction(QSqlQuery *query, TransactionType type)
  845. {
  846. bool success;
  847. if (type == Read)
  848. success = query->exec(QLatin1String("BEGIN"));
  849. else
  850. success = query->exec(QLatin1String("BEGIN IMMEDIATE"));
  851. if (!success) {
  852. int result = query->lastError().number();
  853. if (result == 26 || result == 11) //SQLITE_NOTADB || SQLITE_CORRUPT
  854. {
  855. throw ctkPluginDatabaseException(QString("ctkPluginFramework: Database file is corrupt or invalid: %1").arg(getDatabasePath()),
  856. ctkPluginDatabaseException::DB_FILE_INVALID);
  857. }
  858. else if (result == 8) //SQLITE_READONLY
  859. {
  860. throw ctkPluginDatabaseException(QString("ctkPluginFramework: Insufficient permissions to write to database: %1").arg(getDatabasePath()),
  861. ctkPluginDatabaseException::DB_WRITE_ERROR);
  862. }
  863. else
  864. throw ctkPluginDatabaseException(QString("ctkPluginFramework: ") + query->lastError().text(),
  865. ctkPluginDatabaseException::DB_SQL_ERROR);
  866. }
  867. }
  868. //----------------------------------------------------------------------------
  869. void ctkPluginStorageSQL::commitTransaction(QSqlQuery *query)
  870. {
  871. Q_ASSERT(query != 0);
  872. query->finish();
  873. query->clear();
  874. if (!query->exec(QLatin1String("COMMIT")))
  875. {
  876. throw ctkPluginDatabaseException(QString("ctkPluginFramework: ") + query->lastError().text(),
  877. ctkPluginDatabaseException::DB_SQL_ERROR);
  878. }
  879. }
  880. //----------------------------------------------------------------------------
  881. void ctkPluginStorageSQL::rollbackTransaction(QSqlQuery *query)
  882. {
  883. Q_ASSERT(query !=0);
  884. query->finish();
  885. query->clear();
  886. if (!query->exec(QLatin1String("ROLLBACK")))
  887. {
  888. throw ctkPluginDatabaseException(QString("ctkPluginFramework: ") + query->lastError().text(),
  889. ctkPluginDatabaseException::DB_SQL_ERROR);
  890. }
  891. }
  892. //----------------------------------------------------------------------------
  893. void ctkPluginStorageSQL::restorePluginArchives()
  894. {
  895. QSqlDatabase database = getConnection();
  896. QSqlQuery query(database);
  897. QString statement = "SELECT ID, Location, LocalPath, StartLevel, LastModified, AutoStart, K, MAX(Generation)"
  898. " FROM " PLUGINS_TABLE " WHERE StartLevel != -2 GROUP BY ID"
  899. " ORDER BY ID";
  900. executeQuery(&query, statement);
  901. while (query.next())
  902. {
  903. const long id = query.value(EBindIndex).toLongLong();
  904. const QUrl location(query.value(EBindIndex1).toString());
  905. const QString localPath(query.value(EBindIndex2).toString());
  906. if (id <= 0 || location.isEmpty() || localPath.isEmpty())
  907. {
  908. throw ctkPluginDatabaseException(QString("Database integrity corrupted, row %1 contains empty values.").arg(id),
  909. ctkPluginDatabaseException::DB_FILE_INVALID);
  910. }
  911. const int startLevel = query.value(EBindIndex3).toInt();
  912. const QDateTime lastModified = getQDateTimeFromString(query.value(EBindIndex4).toString());
  913. const int autoStart = query.value(EBindIndex5).toInt();
  914. try
  915. {
  916. QSharedPointer<ctkPluginArchiveSQL> pa(new ctkPluginArchiveSQL(this, location, localPath, id,
  917. startLevel, lastModified, autoStart));
  918. pa->key = query.value(EBindIndex6).toInt();
  919. pa->readManifest();
  920. m_archives.append(pa);
  921. }
  922. catch (const ctkPluginException& exc)
  923. {
  924. qWarning() << exc;
  925. }
  926. }
  927. }
  928. //----------------------------------------------------------------------------
  929. QString ctkPluginStorageSQL::getStringFromQDateTime(const QDateTime& dateTime) const
  930. {
  931. return dateTime.toString(Qt::ISODate);
  932. }
  933. //----------------------------------------------------------------------------
  934. QDateTime ctkPluginStorageSQL::getQDateTimeFromString(const QString& dateTimeString) const
  935. {
  936. return QDateTime::fromString(dateTimeString, Qt::ISODate);
  937. }