ctkPluginStorageSQL.cpp 32 KB

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