ctkPluginStorageSQL.cpp 32 KB

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