ctkPluginStorageSQL.cpp 32 KB

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