ctkPluginDatabase.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. /*=============================================================================
  2. Library: CTK
  3. Copyright (c) 2010 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 "ctkPluginDatabase_p.h"
  16. #include "ctkPluginDatabaseException.h"
  17. #include "ctkPlugin.h"
  18. #include "ctkPluginException.h"
  19. #include "ctkPluginArchive_p.h"
  20. #include "ctkPluginStorage_p.h"
  21. #include <QApplication>
  22. #include <QFileInfo>
  23. #include <QUrl>
  24. #include <QDebug>
  25. //database name
  26. #define PLUGINDATABASE "pluginfw.db"
  27. //database table names
  28. #define PLUGINS_TABLE "Plugins"
  29. #define PLUGIN_RESOURCES_TABLE "PluginResources"
  30. //separator
  31. #define PLUGINDATABASE_PATH_SEPARATOR "//"
  32. namespace ctk {
  33. enum TBindIndexes
  34. {
  35. EBindIndex=0,
  36. EBindIndex1,
  37. EBindIndex2,
  38. EBindIndex3,
  39. EBindIndex4,
  40. EBindIndex5,
  41. EBindIndex6,
  42. EBindIndex7
  43. };
  44. PluginDatabase::PluginDatabase(PluginStorage* storage)
  45. :m_isDatabaseOpen(false), m_inTransaction(false), m_PluginStorage(storage)
  46. {
  47. }
  48. PluginDatabase::~PluginDatabase()
  49. {
  50. close();
  51. }
  52. void PluginDatabase::open()
  53. {
  54. if (m_isDatabaseOpen)
  55. return;
  56. QString path;
  57. //Create full path to database
  58. if(m_databasePath.isEmpty ())
  59. m_databasePath = getDatabasePath();
  60. path = m_databasePath;
  61. QFileInfo dbFileInfo(path);
  62. if (!dbFileInfo.dir().exists())
  63. {
  64. if(!QDir::root().mkpath(dbFileInfo.path()))
  65. {
  66. close();
  67. QString errorText("Could not create database directory: %1");
  68. throw PluginDatabaseException(errorText.arg(dbFileInfo.path()), PluginDatabaseException::DB_CREATE_DIR_ERROR);
  69. }
  70. }
  71. m_connectionName = dbFileInfo.completeBaseName();
  72. QSqlDatabase database;
  73. if (QSqlDatabase::contains(m_connectionName))
  74. {
  75. database = QSqlDatabase::database(m_connectionName);
  76. }
  77. else
  78. {
  79. database = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
  80. database.setDatabaseName(path);
  81. }
  82. if (!database.isValid())
  83. {
  84. close();
  85. throw PluginDatabaseException(QString("Invalid database connection: %1").arg(m_connectionName),
  86. PluginDatabaseException::DB_CONNECTION_INVALID);
  87. }
  88. //Create or open database
  89. if (!database.isOpen())
  90. {
  91. if (!database.open())
  92. {
  93. close();
  94. throw PluginDatabaseException(QString("Could not open database. ") + database.lastError().text(),
  95. PluginDatabaseException::DB_SQL_ERROR);
  96. }
  97. }
  98. m_isDatabaseOpen = true;
  99. //Check if the sqlite version supports foreign key constraints
  100. QSqlQuery query(database);
  101. if (!query.exec("PRAGMA foreign_keys"))
  102. {
  103. close();
  104. throw PluginDatabaseException(QString("Check for foreign key support failed."),
  105. PluginDatabaseException::DB_SQL_ERROR);
  106. }
  107. if (!query.next())
  108. {
  109. close();
  110. throw PluginDatabaseException(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"),
  111. PluginDatabaseException::DB_SQL_ERROR);
  112. }
  113. query.finish();
  114. query.clear();
  115. //Enable foreign key support
  116. if (!query.exec("PRAGMA foreign_keys = ON"))
  117. {
  118. close();
  119. throw PluginDatabaseException(QString("Enabling foreign key support failed."),
  120. PluginDatabaseException::DB_SQL_ERROR);
  121. }
  122. query.finish();
  123. //Check database structure (tables) and recreate tables if neccessary
  124. //If one of the tables is missing remove all tables and recreate them
  125. //This operation is required in order to avoid data coruption
  126. if (!checkTables())
  127. {
  128. if (dropTables())
  129. {
  130. createTables();
  131. }
  132. else
  133. {
  134. //dropTables() should've handled error message
  135. //and warning
  136. close();
  137. }
  138. }
  139. //Update database based on the recorded timestamps
  140. updateDB();
  141. }
  142. void PluginDatabase::updateDB()
  143. {
  144. checkConnection();
  145. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  146. QSqlQuery query(database);
  147. beginTransaction(&query, Write);
  148. QString statement = "SELECT ID, Location, LocalPath, Timestamp FROM Plugins WHERE State != ?";
  149. QList<QVariant> bindValues;
  150. bindValues.append(Plugin::UNINSTALLED);
  151. QList<qlonglong> outdatedIds;
  152. QList<QPair<QString,QString> > outdatedPlugins;
  153. try
  154. {
  155. executeQuery(&query, statement, bindValues);
  156. while (query.next())
  157. {
  158. QFileInfo pluginInfo(query.value(EBindIndex2).toString());
  159. if (pluginInfo.lastModified() > QDateTime::fromString(query.value(EBindIndex3).toString(), Qt::ISODate))
  160. {
  161. outdatedIds.append(query.value(EBindIndex).toLongLong());
  162. outdatedPlugins.append(qMakePair(query.value(EBindIndex1).toString(), query.value(EBindIndex2).toString()));
  163. }
  164. }
  165. }
  166. catch (...)
  167. {
  168. rollbackTransaction(&query);
  169. throw;
  170. }
  171. query.finish();
  172. query.clear();
  173. try
  174. {
  175. statement = "DELETE FROM Plugins WHERE ID=?";
  176. QListIterator<qlonglong> idIter(outdatedIds);
  177. while (idIter.hasNext())
  178. {
  179. bindValues.clear();
  180. bindValues.append(idIter.next());
  181. executeQuery(&query, statement, bindValues);
  182. }
  183. }
  184. catch (...)
  185. {
  186. rollbackTransaction(&query);
  187. throw;
  188. }
  189. commitTransaction(&query);
  190. QListIterator<QPair<QString,QString> > locationIter(outdatedPlugins);
  191. while (locationIter.hasNext())
  192. {
  193. const QPair<QString,QString>& locations = locationIter.next();
  194. insertPlugin(QUrl(locations.first), locations.second, false);
  195. }
  196. }
  197. PluginArchive* PluginDatabase::insertPlugin(const QUrl& location, const QString& localPath, bool createArchive)
  198. {
  199. checkConnection();
  200. // Assemble the data for the sql record
  201. QFileInfo fileInfo(localPath);
  202. const QString lastModified = fileInfo.lastModified().toString(Qt::ISODate);
  203. QString resourcePrefix = fileInfo.baseName();
  204. if (resourcePrefix.startsWith("lib"))
  205. {
  206. resourcePrefix = resourcePrefix.mid(3);
  207. }
  208. resourcePrefix.replace("_", ".");
  209. resourcePrefix = QString(":/") + resourcePrefix + "/";
  210. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  211. QSqlQuery query(database);
  212. beginTransaction(&query, Write);
  213. QString statement = "INSERT INTO Plugins(Location,LocalPath,State,Timestamp) VALUES(?,?,?,?)";
  214. QList<QVariant> bindValues;
  215. bindValues.append(location.toString());
  216. bindValues.append(localPath);
  217. bindValues.append(Plugin::INSTALLED);
  218. bindValues.append(lastModified);
  219. long pluginId = -1;
  220. try
  221. {
  222. executeQuery(&query, statement, bindValues);
  223. QVariant lastId = query.lastInsertId();
  224. if (lastId.isValid())
  225. {
  226. pluginId = lastId.toLongLong();
  227. }
  228. }
  229. catch (...)
  230. {
  231. rollbackTransaction(&query);
  232. throw;
  233. }
  234. // Load the plugin and cache the resources
  235. QPluginLoader pluginLoader;
  236. pluginLoader.setFileName(localPath);
  237. if (!pluginLoader.load())
  238. {
  239. rollbackTransaction(&query);
  240. throw PluginException(QString("The plugin could not be loaded: %1").arg(localPath));
  241. }
  242. QDirIterator dirIter(resourcePrefix, QDirIterator::Subdirectories);
  243. while (dirIter.hasNext())
  244. {
  245. QString resourcePath = dirIter.next();
  246. if (QFileInfo(resourcePath).isDir()) continue;
  247. QFile resourceFile(resourcePath);
  248. resourceFile.open(QIODevice::ReadOnly);
  249. QByteArray resourceData = resourceFile.readAll();
  250. resourceFile.close();
  251. statement = "INSERT INTO PluginResources(PluginID, ResourcePath, Resource) VALUES(?,?,?)";
  252. bindValues.clear();
  253. bindValues.append(QVariant::fromValue<qlonglong>(pluginId));
  254. bindValues.append(resourcePath.mid(resourcePrefix.size()-1));
  255. bindValues.append(resourceData);
  256. try
  257. {
  258. executeQuery(&query, statement, bindValues);
  259. }
  260. catch (...)
  261. {
  262. rollbackTransaction(&query);
  263. throw;
  264. }
  265. }
  266. pluginLoader.unload();
  267. try
  268. {
  269. commitTransaction(&query);
  270. if (createArchive)
  271. {
  272. PluginArchive* archive = new PluginArchive(m_PluginStorage, location, localPath,
  273. pluginId);
  274. return archive;
  275. }
  276. else return 0;
  277. }
  278. catch (...)
  279. {
  280. rollbackTransaction(&query);
  281. throw;
  282. }
  283. }
  284. QStringList PluginDatabase::findResourcesPath(long pluginId, const QString& path) const
  285. {
  286. checkConnection();
  287. QString statement = "SELECT SUBSTR(ResourcePath,?) FROM PluginResources WHERE PluginID=? AND SUBSTR(ResourcePath,1,?)=?";
  288. QString resourcePath = path.startsWith('/') ? path : QString("/") + path;
  289. if (!resourcePath.endsWith('/'))
  290. resourcePath += "/";
  291. QList<QVariant> bindValues;
  292. bindValues.append(resourcePath.size()+1);
  293. bindValues.append(qlonglong(pluginId));
  294. bindValues.append(resourcePath.size());
  295. bindValues.append(resourcePath);
  296. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  297. QSqlQuery query(database);
  298. executeQuery(&query, statement, bindValues);
  299. QStringList paths;
  300. while (query.next())
  301. {
  302. QString currPath = query.value(EBindIndex).toString();
  303. int slashIndex = currPath.indexOf('/');
  304. if (slashIndex > 0)
  305. {
  306. currPath = currPath.left(slashIndex+1);
  307. }
  308. paths << currPath;
  309. }
  310. return paths;
  311. }
  312. void PluginDatabase::removeArchive(const PluginArchive *pa)
  313. {
  314. checkConnection();
  315. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  316. QSqlQuery query(database);
  317. QString statement = "DELETE FROM Plugins WHERE ID=?";
  318. QList<QVariant> bindValues;
  319. bindValues.append(pa->getPluginId());
  320. executeQuery(&query, statement, bindValues);
  321. }
  322. void PluginDatabase::executeQuery(QSqlQuery *query, const QString &statement, const QList<QVariant> &bindValues) const
  323. {
  324. Q_ASSERT(query != 0);
  325. bool success = false;
  326. enum {Prepare =0 , Execute=1};
  327. for (int stage=Prepare; stage <= Execute; ++stage)
  328. {
  329. if ( stage == Prepare)
  330. success = query->prepare(statement);
  331. else // stage == Execute
  332. success = query->exec();
  333. if (!success)
  334. {
  335. QString errorText = "Problem: Could not %1 statement: %2\n"
  336. "Reason: %3\n"
  337. "Parameters: %4\n";
  338. QString parameters;
  339. if (bindValues.count() > 0)
  340. {
  341. for (int i = 0; i < bindValues.count(); ++i)
  342. {
  343. parameters.append(QString("\n\t[") + QString::number(i) + "]: " + bindValues.at(i).toString());
  344. }
  345. }
  346. else
  347. {
  348. parameters = "None";
  349. }
  350. PluginDatabaseException::Type errorType;
  351. int result = query->lastError().number();
  352. if (result == 26 || result == 11) //SQLILTE_NOTADB || SQLITE_CORRUPT
  353. {
  354. qWarning() << "PluginFramework:- Database file is corrupt or invalid:" << getDatabasePath();
  355. errorType = PluginDatabaseException::DB_FILE_INVALID;
  356. }
  357. else if (result == 8) //SQLITE_READONLY
  358. errorType = PluginDatabaseException::DB_WRITE_ERROR;
  359. else
  360. errorType = PluginDatabaseException::DB_SQL_ERROR;
  361. query->finish();
  362. query->clear();
  363. throw PluginDatabaseException(errorText.arg(stage == Prepare ? "prepare":"execute")
  364. .arg(statement).arg(query->lastError().text()).arg(parameters), errorType);
  365. }
  366. if (stage == Prepare)
  367. {
  368. foreach(const QVariant &bindValue, bindValues)
  369. query->addBindValue(bindValue);
  370. }
  371. }
  372. }
  373. void PluginDatabase::close()
  374. {
  375. if (m_isDatabaseOpen)
  376. {
  377. QSqlDatabase database = QSqlDatabase::database(m_connectionName, false);
  378. if (database.isValid())
  379. {
  380. if(database.isOpen())
  381. {
  382. database.close();
  383. m_isDatabaseOpen = false;
  384. return;
  385. }
  386. }
  387. else
  388. {
  389. throw PluginDatabaseException(QString("Problem closing database: Invalid connection %1").arg(m_connectionName));
  390. }
  391. }
  392. }
  393. void PluginDatabase::setDatabasePath(const QString &databasePath)
  394. {
  395. m_databasePath = QDir::toNativeSeparators(databasePath);
  396. }
  397. QString PluginDatabase::getDatabasePath() const
  398. {
  399. QString path;
  400. if(m_databasePath.isEmpty())
  401. {
  402. QSettings settings(QSettings::UserScope, "commontk", QApplication::applicationName());
  403. path = settings.value("PluginDB/Path").toString();
  404. if (path.isEmpty())
  405. {
  406. path = QDir::currentPath();
  407. if (path.lastIndexOf(PLUGINDATABASE_PATH_SEPARATOR) != path.length() -1)
  408. {
  409. path.append(PLUGINDATABASE_PATH_SEPARATOR);
  410. }
  411. path.append(PLUGINDATABASE);
  412. }
  413. path = QDir::toNativeSeparators(path);
  414. }
  415. else
  416. {
  417. path = m_databasePath;
  418. }
  419. return path;
  420. }
  421. QByteArray PluginDatabase::getPluginResource(long pluginId, const QString& res) const
  422. {
  423. checkConnection();
  424. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  425. QSqlQuery query(database);
  426. QString statement = "SELECT Resource FROM PluginResources WHERE PluginID=? AND ResourcePath=?";
  427. QString resourcePath = res.startsWith('/') ? res : QString("/") + res;
  428. QList<QVariant> bindValues;
  429. bindValues.append(qlonglong(pluginId));
  430. bindValues.append(resourcePath);
  431. executeQuery(&query, statement, bindValues);
  432. if (query.next())
  433. {
  434. return query.value(EBindIndex).toByteArray();
  435. }
  436. return QByteArray();
  437. }
  438. void PluginDatabase::createTables()
  439. {
  440. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  441. QSqlQuery query(database);
  442. //Begin Transaction
  443. beginTransaction(&query, Write);
  444. QString statement("CREATE TABLE Plugins("
  445. "ID INTEGER PRIMARY KEY,"
  446. "Location TEXT NOT NULL UNIQUE,"
  447. "LocalPath TEXT NOT NULL UNIQUE,"
  448. "State INTEGER NOT NULL,"
  449. "Timestamp TEXT NOT NULL)");
  450. try
  451. {
  452. executeQuery(&query, statement);
  453. }
  454. catch (...)
  455. {
  456. rollbackTransaction(&query);
  457. throw;
  458. }
  459. statement = "CREATE TABLE PluginResources("
  460. "PluginID INTEGER NOT NULL,"
  461. "ResourcePath TEXT NOT NULL, "
  462. "Resource BLOB NOT NULL,"
  463. "FOREIGN KEY(PluginID) REFERENCES Plugins(ID) ON DELETE CASCADE)";
  464. try
  465. {
  466. executeQuery(&query, statement);
  467. }
  468. catch (...)
  469. {
  470. rollbackTransaction(&query);
  471. throw;
  472. }
  473. try
  474. {
  475. commitTransaction(&query);
  476. }
  477. catch (...)
  478. {
  479. rollbackTransaction(&query);
  480. throw;
  481. }
  482. }
  483. bool PluginDatabase::checkTables() const
  484. {
  485. bool bTables(false);
  486. QStringList tables = QSqlDatabase::database(m_connectionName).tables();
  487. if (tables.contains(PLUGINS_TABLE)
  488. && tables.contains(PLUGIN_RESOURCES_TABLE))
  489. {
  490. bTables = true;
  491. }
  492. return bTables;
  493. }
  494. bool PluginDatabase::dropTables()
  495. {
  496. //Execute transaction for deleting the database tables
  497. QSqlDatabase database = QSqlDatabase::database(m_connectionName);
  498. QSqlQuery query(database);
  499. QStringList expectedTables;
  500. expectedTables << PLUGINS_TABLE << PLUGIN_RESOURCES_TABLE;
  501. if (database.tables().count() > 0)
  502. {
  503. beginTransaction(&query, Write);
  504. QStringList actualTables = database.tables();
  505. foreach(const QString expectedTable, expectedTables)
  506. {
  507. if (actualTables.contains(expectedTable))
  508. {
  509. try
  510. {
  511. executeQuery(&query, QString("DROP TABLE ") + expectedTable);
  512. }
  513. catch (...)
  514. {
  515. rollbackTransaction(&query);
  516. throw;
  517. }
  518. }
  519. try
  520. {
  521. commitTransaction(&query);
  522. }
  523. catch (...)
  524. {
  525. rollbackTransaction(&query);
  526. throw;
  527. }
  528. }
  529. }
  530. }
  531. bool PluginDatabase::isOpen() const
  532. {
  533. return m_isDatabaseOpen;
  534. }
  535. void PluginDatabase::checkConnection() const
  536. {
  537. if(!m_isDatabaseOpen)
  538. {
  539. throw PluginDatabaseException("Database not open.", PluginDatabaseException::DB_NOT_OPEN_ERROR);
  540. }
  541. if (!QSqlDatabase::database(m_connectionName).isValid())
  542. {
  543. throw PluginDatabaseException(QString("Database connection invalid: %1").arg(m_connectionName),
  544. PluginDatabaseException::DB_CONNECTION_INVALID);
  545. }
  546. }
  547. void PluginDatabase::beginTransaction(QSqlQuery *query, TransactionType type)
  548. {
  549. bool success;
  550. if (type == Read)
  551. success = query->exec(QLatin1String("BEGIN"));
  552. else
  553. success = query->exec(QLatin1String("BEGIN IMMEDIATE"));
  554. if (!success) {
  555. int result = query->lastError().number();
  556. if (result == 26 || result == 11) //SQLITE_NOTADB || SQLITE_CORRUPT
  557. {
  558. throw PluginDatabaseException(QString("PluginFramework: Database file is corrupt or invalid: %1").arg(getDatabasePath()),
  559. PluginDatabaseException::DB_FILE_INVALID);
  560. }
  561. else if (result == 8) //SQLITE_READONLY
  562. {
  563. throw PluginDatabaseException(QString("PluginFramework: Insufficient permissions to write to database: %1").arg(getDatabasePath()),
  564. PluginDatabaseException::DB_WRITE_ERROR);
  565. }
  566. else
  567. throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
  568. PluginDatabaseException::DB_SQL_ERROR);
  569. }
  570. }
  571. void PluginDatabase::commitTransaction(QSqlQuery *query)
  572. {
  573. Q_ASSERT(query != 0);
  574. query->finish();
  575. query->clear();
  576. if (!query->exec(QLatin1String("COMMIT")))
  577. {
  578. throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
  579. PluginDatabaseException::DB_SQL_ERROR);
  580. }
  581. }
  582. void PluginDatabase::rollbackTransaction(QSqlQuery *query)
  583. {
  584. Q_ASSERT(query !=0);
  585. query->finish();
  586. query->clear();
  587. if (!query->exec(QLatin1String("ROLLBACK")))
  588. {
  589. throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
  590. PluginDatabaseException::DB_SQL_ERROR);
  591. }
  592. }
  593. QList<PluginArchive*> PluginDatabase::getPluginArchives() const
  594. {
  595. checkConnection();
  596. QSqlQuery query(QSqlDatabase::database(m_connectionName));
  597. QString statement("SELECT ID, Location, LocalPath FROM Plugins WHERE State != ?");
  598. QList<QVariant> bindValues;
  599. bindValues.append(Plugin::UNINSTALLED);
  600. executeQuery(&query, statement, bindValues);
  601. QList<PluginArchive*> archives;
  602. while (query.next())
  603. {
  604. const long id = query.value(EBindIndex).toLongLong();
  605. const QUrl location(query.value(EBindIndex1).toString());
  606. const QString localPath(query.value(EBindIndex2).toString());
  607. if (id <= 0 || location.isEmpty() || localPath.isEmpty())
  608. {
  609. throw PluginDatabaseException(QString("Database integrity corrupted, row %1 contains empty values.").arg(id),
  610. PluginDatabaseException::DB_FILE_INVALID);
  611. }
  612. try
  613. {
  614. PluginArchive* pa = new PluginArchive(m_PluginStorage, location, localPath, id);
  615. archives.append(pa);
  616. }
  617. catch (const PluginException& exc)
  618. {
  619. qWarning() << exc;
  620. }
  621. }
  622. return archives;
  623. }
  624. }