adatabase.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. /*
  2. *openPilotLog - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020-2021 Felix Turowsky
  4. *
  5. *This program is free software: you can redistribute it and/or modify
  6. *it under the terms of the GNU General Public License as published by
  7. *the Free Software Foundation, either version 3 of the License, or
  8. *(at your option) any later version.
  9. *
  10. *This program is distributed in the hope that it will be useful,
  11. *but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. *GNU General Public License for more details.
  14. *
  15. *You should have received a copy of the GNU General Public License
  16. *along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. #include "adatabase.h"
  19. #include "src/functions/alog.h"
  20. #include "src/classes/astandardpaths.h"
  21. #include "src/opl.h"
  22. #include "src/functions/alog.h"
  23. const int ADatabase::minimumDatabaseRevision = 0;
  24. const QStringList ADatabase::userTableNames = {
  25. QStringLiteral("flights"),
  26. QStringLiteral("pilots"),
  27. QStringLiteral("tails")
  28. };
  29. const QStringList ADatabase::templateTableNames = {
  30. QStringLiteral("aircraft"),
  31. QStringLiteral("airports"),
  32. QStringLiteral("currencies"),
  33. QStringLiteral("changelog")
  34. };
  35. ADatabase* ADatabase::self = nullptr;
  36. ADatabase::ADatabase()
  37. : databaseFile(QFileInfo(AStandardPaths::directory(AStandardPaths::Database).
  38. absoluteFilePath(QStringLiteral("logbook.db"))))
  39. {}
  40. int ADatabase::dbRevision() const
  41. {
  42. return databaseRevision;
  43. }
  44. int ADatabase::checkDbVersion() const
  45. {
  46. QSqlQuery query(QStringLiteral("SELECT COUNT(*) FROM changelog"));
  47. query.next();
  48. return query.value(0).toInt();
  49. }
  50. int ADatabase::getMinimumDatabaseRevision()
  51. {
  52. return minimumDatabaseRevision;
  53. }
  54. QStringList ADatabase::getTemplateTableNames()
  55. {
  56. return templateTableNames;
  57. }
  58. UserDataState ADatabase::getUserDataState()
  59. {
  60. QSqlQuery q;
  61. q.prepare(QStringLiteral("SELECT COUNT (*) FROM tails"));
  62. q.exec();
  63. q.first();
  64. int tails = q.value(0).toInt();
  65. q.prepare(QStringLiteral("SELECT COUNT (*) FROM pilots"));
  66. q.exec();
  67. q.first();
  68. int pilots = q.value(0).toInt();
  69. return UserDataState(tails, pilots);
  70. }
  71. QStringList ADatabase::getUserTableNames()
  72. {
  73. return userTableNames;
  74. }
  75. const ColumnNames_T ADatabase::getTableColumns(TableName_T table_name) const
  76. {
  77. return tableColumns.value(table_name);
  78. }
  79. const TableNames_T ADatabase::getTableNames() const
  80. {
  81. return tableNames;
  82. }
  83. void ADatabase::updateLayout()
  84. {
  85. auto db = ADatabase::database();
  86. tableNames = db.tables();
  87. tableColumns.clear();
  88. for (const auto &table_name : qAsConst(tableNames)) {
  89. ColumnNames_T table_columns;
  90. QSqlRecord fields = db.record(table_name);
  91. for (int i = 0; i < fields.count(); i++) {
  92. table_columns.append(fields.field(i).name());
  93. }
  94. tableColumns.insert(table_name, table_columns);
  95. }
  96. emit dataBaseUpdated();
  97. }
  98. ADatabase* ADatabase::instance()
  99. {
  100. if(!self)
  101. self = new ADatabase();
  102. return self;
  103. }
  104. const QString ADatabase::sqliteVersion() const
  105. {
  106. QSqlQuery query;
  107. query.prepare(QStringLiteral("SELECT sqlite_version()"));
  108. query.exec();
  109. query.next();
  110. return query.value(0).toString();
  111. }
  112. bool ADatabase::connect()
  113. {
  114. if (!QSqlDatabase::isDriverAvailable(SQLITE_DRIVER)) {
  115. LOG << "Error: No SQLITE Driver availabe.";
  116. return false;
  117. }
  118. QSqlDatabase db = QSqlDatabase::addDatabase(SQLITE_DRIVER);
  119. db.setDatabaseName(databaseFile.absoluteFilePath());
  120. if (!db.open()) {
  121. LOG << QString("Unable to establish database connection.<br>The following error has ocurred:<br><br>%1")
  122. .arg(db.lastError().databaseText());
  123. lastError = db.lastError();
  124. return false;
  125. }
  126. LOG << "Database connection established.";
  127. // Enable foreign key restrictions
  128. QSqlQuery query;
  129. query.prepare(QStringLiteral("PRAGMA foreign_keys = ON;"));
  130. query.exec();
  131. updateLayout();
  132. databaseRevision = checkDbVersion();
  133. return true;
  134. }
  135. void ADatabase::disconnect()
  136. {
  137. auto db = ADatabase::database();
  138. db.close();
  139. LOG << "Database connection closed.";
  140. }
  141. QSqlDatabase ADatabase::database()
  142. {
  143. return QSqlDatabase::database(QStringLiteral("qt_sql_default_connection"));
  144. }
  145. bool ADatabase::commit(const AEntry &entry)
  146. {
  147. if (exists(entry)) {
  148. return update(entry);
  149. } else {
  150. return insert(entry);
  151. }
  152. }
  153. bool ADatabase::remove(const AEntry &entry)
  154. {
  155. if (!exists(entry)) {
  156. LOG << "Error: Database entry not found.";
  157. return false;
  158. }
  159. QString statement = QLatin1String("DELETE FROM ") + entry.getPosition().tableName
  160. + QLatin1String(" WHERE ROWID=?");
  161. QSqlQuery query;
  162. query.prepare(statement);
  163. query.addBindValue(entry.getPosition().rowId);
  164. if (query.exec())
  165. {
  166. LOG << "Entry " << entry.getPosition() << " removed.";
  167. emit dataBaseUpdated();
  168. return true;
  169. } else {
  170. DEB << "Unable to delete.";
  171. DEB << "Query: " << statement;
  172. DEB << "Query Error: " << query.lastError().text();
  173. lastError = query.lastError();
  174. return false;
  175. }
  176. }
  177. bool ADatabase::removeMany(QList<DataPosition> data_position_list)
  178. {
  179. int errorCount = 0;
  180. QSqlQuery query;
  181. query.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
  182. query.exec();
  183. for (const auto& data_position : data_position_list) {
  184. if (!exists(data_position)) {
  185. errorCount++;
  186. }
  187. QString statement = QLatin1String("DELETE FROM ") + data_position.tableName +
  188. QLatin1String(" WHERE ROWID=?");
  189. query.prepare(statement);
  190. query.addBindValue(data_position.rowId);
  191. if (!query.exec())
  192. errorCount++;
  193. }
  194. if (errorCount == 0) {
  195. query.prepare(QStringLiteral("COMMIT"));
  196. if(query.exec()) {
  197. emit dataBaseUpdated();
  198. LOG << "Transaction successfull.";
  199. return true;
  200. } else {
  201. LOG << "Transaction unsuccessful (Interrupted). Error count: "
  202. + QString::number(errorCount);
  203. DEB << query.lastError().text();
  204. lastError = query.lastError();
  205. return false;
  206. }
  207. } else {
  208. query.prepare(QStringLiteral("ROLLBACK"));
  209. query.exec();
  210. LOG << "Transaction unsuccessful (no changes have been made). Error count: "
  211. + QString::number(errorCount);
  212. return false;
  213. }
  214. }
  215. bool ADatabase::exists(const AEntry &entry)
  216. {
  217. if(entry.getPosition().rowId == 0)
  218. return false;
  219. //Check database for row id
  220. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + entry.getPosition().tableName
  221. + QLatin1String(" WHERE ROWID=?");
  222. QSqlQuery query;
  223. query.prepare(statement);
  224. query.addBindValue(entry.getPosition().rowId);
  225. query.setForwardOnly(true);
  226. query.exec();
  227. //this returns either 1 or 0 since row ids are unique
  228. if (!query.isActive()) {
  229. lastError = query.lastError();
  230. DEB << "Query Error: " << query.lastError().text() << statement;
  231. return false;
  232. }
  233. query.next();
  234. int rowId = query.value(0).toInt();
  235. if (rowId) {
  236. return true;
  237. } else {
  238. LOG << "Database entry not found.";
  239. return false;
  240. }
  241. }
  242. bool ADatabase::exists(DataPosition data_position)
  243. {
  244. if(data_position.rowId == 0)
  245. return false;
  246. //Check database for row id
  247. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + data_position.tableName
  248. + QLatin1String(" WHERE ROWID=?");
  249. QSqlQuery query;
  250. query.prepare(statement);
  251. query.addBindValue(data_position.rowId);
  252. query.setForwardOnly(true);
  253. query.exec();
  254. //this returns either 1 or 0 since row ids are unique
  255. if (!query.isActive()) {
  256. lastError = query.lastError();
  257. LOG << "Query Error: " << query.lastError().text() << statement;
  258. }
  259. query.next();
  260. int rowId = query.value(0).toInt();
  261. if (rowId) {
  262. return true;
  263. } else {
  264. LOG << "No entry exists at DataPosition: " << data_position.tableName << data_position.rowId;
  265. return false;
  266. }
  267. }
  268. bool ADatabase::clear()
  269. {
  270. QSqlQuery q;
  271. for (const auto &table_name : userTableNames) {
  272. q.prepare(QLatin1String("DELETE FROM ") + table_name);
  273. if (!q.exec()) {
  274. DEB << "Error: " << q.lastError().text();
  275. lastError = q.lastError();
  276. return false;
  277. }
  278. }
  279. return true;
  280. }
  281. bool ADatabase::update(const AEntry &updated_entry)
  282. {
  283. auto data = updated_entry.getData();
  284. QString statement = QLatin1String("UPDATE ") + updated_entry.getPosition().tableName + QLatin1String(" SET ");
  285. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  286. statement.append(i.key() + "=?,");
  287. }
  288. statement.chop(1);
  289. statement.append(QLatin1String(" WHERE ROWID=?"));
  290. QSqlQuery query;
  291. query.prepare(statement);
  292. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  293. if (i.value() == QVariant(QString()) || i.value() == 0) {
  294. query.addBindValue(QVariant(QVariant::String));
  295. } else {
  296. query.addBindValue(i.value());
  297. }
  298. }
  299. query.addBindValue(updated_entry.getPosition().rowId);
  300. if (query.exec())
  301. {
  302. DEB << "Entry successfully committed.";
  303. emit dataBaseUpdated();
  304. return true;
  305. } else {
  306. DEB << "Unable to commit.";
  307. DEB << "Query: " << statement;
  308. DEB << "Query Error: " << query.lastError().text();
  309. lastError = query.lastError();
  310. return false;
  311. }
  312. }
  313. bool ADatabase::insert(const AEntry &new_entry)
  314. {
  315. auto data = new_entry.getData();
  316. QString statement = QLatin1String("INSERT INTO ") + new_entry.getPosition().tableName + QLatin1String(" (");
  317. QMap<QString, QVariant>::iterator i;
  318. for (i = data.begin(); i != data.end(); ++i) {
  319. statement.append(i.key() + QLatin1Char(','));
  320. }
  321. statement.chop(1);
  322. statement += QLatin1String(") VALUES (");
  323. for (int i=0; i < data.size(); ++i) {
  324. statement += QLatin1String("?,");
  325. }
  326. statement.chop(1);
  327. statement += QLatin1Char(')');
  328. QSqlQuery query;
  329. query.prepare(statement);
  330. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  331. if (i.value() == QVariant(QString()) || i.value() == 0) {
  332. query.addBindValue(QVariant(QVariant::String));
  333. } else {
  334. query.addBindValue(i.value());
  335. }
  336. }
  337. //check result.
  338. if (query.exec())
  339. {
  340. DEB << "Entry successfully committed.";
  341. emit dataBaseUpdated();
  342. return true;
  343. } else {
  344. DEB << "Unable to commit.";
  345. DEB << "Query: " << statement;
  346. DEB << "Query Error: " << query.lastError().text();
  347. lastError = query.lastError();
  348. return false;
  349. }
  350. }
  351. RowData_T ADatabase::getEntryData(DataPosition data_position)
  352. {
  353. // check table exists
  354. if (!getTableNames().contains(data_position.tableName)) {
  355. DEB << data_position.tableName << " not a table in the database. Unable to retreive Entry data.";
  356. return RowData_T();
  357. }
  358. //Check Database for rowId
  359. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + data_position.tableName
  360. + QLatin1String(" WHERE ROWID=?");
  361. QSqlQuery check_query;
  362. check_query.prepare(statement);
  363. check_query.addBindValue(data_position.rowId);
  364. check_query.setForwardOnly(true);
  365. if (!check_query.exec()) {
  366. DEB << "SQL error: " << check_query.lastError().text();
  367. DEB << "Statement: " << statement;
  368. lastError = check_query.lastError();
  369. return RowData_T();
  370. }
  371. check_query.next();
  372. if (check_query.value(0).toInt() == 0) {
  373. LOG << "No Entry found for row id: " << data_position.rowId;
  374. return RowData_T();
  375. }
  376. // Retreive TableData
  377. statement = QLatin1String("SELECT * FROM ") + data_position.tableName
  378. + QLatin1String(" WHERE ROWID=?");
  379. QSqlQuery select_query;
  380. select_query.prepare(statement);
  381. select_query.addBindValue(data_position.rowId);
  382. select_query.setForwardOnly(true);
  383. if (!select_query.exec()) {
  384. DEB << "SQL error: " << select_query.lastError().text();
  385. DEB << "Statement: " << select_query.lastQuery();
  386. lastError = select_query.lastError();
  387. return {};
  388. }
  389. RowData_T entry_data;
  390. if(select_query.next()) { // retreive records
  391. auto r = select_query.record();
  392. for (int i = 0; i < r.count(); i++){
  393. if(!r.value(i).isNull()) {
  394. entry_data.insert(r.fieldName(i), r.value(i));
  395. }
  396. }
  397. }
  398. return entry_data;
  399. }
  400. AEntry ADatabase::getEntry(DataPosition data_position)
  401. {
  402. AEntry entry(data_position);
  403. entry.setData(getEntryData(data_position));
  404. return entry;
  405. }
  406. APilotEntry ADatabase::getPilotEntry(RowId_T row_id)
  407. {
  408. APilotEntry pilot_entry(row_id);
  409. pilot_entry.setData(getEntryData(pilot_entry.getPosition()));
  410. return pilot_entry;
  411. }
  412. ATailEntry ADatabase::getTailEntry(RowId_T row_id)
  413. {
  414. ATailEntry tail_entry(row_id);
  415. tail_entry.setData(getEntryData(tail_entry.getPosition()));
  416. return tail_entry;
  417. }
  418. AAircraftEntry ADatabase::getAircraftEntry(RowId_T row_id)
  419. {
  420. AAircraftEntry aircraft_entry(row_id);
  421. aircraft_entry.setData(getEntryData(aircraft_entry.getPosition()));
  422. return aircraft_entry;
  423. }
  424. AFlightEntry ADatabase::getFlightEntry(RowId_T row_id)
  425. {
  426. AFlightEntry flight_entry(row_id);
  427. flight_entry.setData(getEntryData(flight_entry.getPosition()));
  428. return flight_entry;
  429. }
  430. ACurrencyEntry ADatabase::getCurrencyEntry(ACurrencyEntry::CurrencyName currency_name)
  431. {
  432. ACurrencyEntry currency_entry(currency_name);
  433. currency_entry.setData(getEntryData(currency_entry.getPosition()));
  434. return currency_entry;
  435. }
  436. const QStringList ADatabase::getCompletionList(ADatabaseTarget target)
  437. {
  438. QString statement;
  439. switch (target) {
  440. case ADatabaseTarget::pilots:
  441. statement.append(QStringLiteral("SELECT lastname||', '||firstname FROM pilots"));
  442. break;
  443. case ADatabaseTarget::aircraft:
  444. statement.append(QStringLiteral("SELECT make||' '||model FROM aircraft WHERE model IS NOT NULL AND variant IS NULL "
  445. "UNION "
  446. "SELECT make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
  447. break;
  448. case ADatabaseTarget::airport_identifier_all:
  449. statement.append(QStringLiteral("SELECT icao FROM airports UNION SELECT iata FROM airports"));
  450. break;
  451. case ADatabaseTarget::registrations:
  452. statement.append(QStringLiteral("SELECT registration FROM tails"));
  453. break;
  454. case ADatabaseTarget::companies:
  455. statement.append(QStringLiteral("SELECT company FROM pilots"));
  456. break;
  457. default:
  458. DEB << "Not a valid completer target for this function.";
  459. return QStringList();
  460. }
  461. QSqlQuery query;
  462. query.prepare(statement);
  463. query.setForwardOnly(true);
  464. query.exec();
  465. if(!query.isActive()) {
  466. lastError = query.lastError();
  467. return QStringList();
  468. }
  469. QStringList completer_list;
  470. while (query.next())
  471. completer_list.append(query.value(0).toString());
  472. completer_list.sort();
  473. completer_list.removeAll(QString());
  474. completer_list.removeDuplicates();
  475. return completer_list;
  476. }
  477. const
  478. QMap<RowId_T, QString> ADatabase::getIdMap(ADatabaseTarget target)
  479. {
  480. QString statement;
  481. switch (target) {
  482. case ADatabaseTarget::pilots:
  483. statement.append(QStringLiteral("SELECT ROWID, lastname||', '||firstname FROM pilots"));
  484. break;
  485. case ADatabaseTarget::aircraft:
  486. statement.append(QStringLiteral("SELECT ROWID, make||' '||model FROM aircraft WHERE model IS NOT NULL AND variant IS NULL "
  487. "UNION "
  488. "SELECT ROWID, make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
  489. break;
  490. case ADatabaseTarget::airport_identifier_icao:
  491. statement.append(QStringLiteral("SELECT ROWID, icao FROM airports"));
  492. break;
  493. case ADatabaseTarget::airport_identifier_iata:
  494. statement.append(QStringLiteral("SELECT ROWID, iata FROM airports WHERE iata NOT NULL"));
  495. break;
  496. case ADatabaseTarget::airport_names:
  497. statement.append(QStringLiteral("SELECT ROWID, name FROM airports"));
  498. break;
  499. case ADatabaseTarget::tails:
  500. statement.append(QStringLiteral("SELECT ROWID, registration FROM tails"));
  501. break;
  502. default:
  503. DEB << "Not a valid completer target for this function.";
  504. return {};
  505. }
  506. QSqlQuery query;
  507. query.setForwardOnly(true);
  508. query.prepare(statement);
  509. query.exec();
  510. //auto query = QSqlQuery(statement);
  511. if (!query.isActive()) {
  512. DEB << "No result found. Check Query and Error.";
  513. DEB << "Query: " << statement;
  514. DEB << "Error: " << query.lastError().text();
  515. lastError = query.lastError();
  516. return {};
  517. }
  518. auto id_map = QMap<RowId_T, QString>();
  519. while (query.next())
  520. id_map.insert(query.value(0).toInt(), query.value(1).toString());
  521. return id_map;
  522. }
  523. RowId_T ADatabase::getLastEntry(ADatabaseTable table)
  524. {
  525. QString statement = QLatin1String("SELECT MAX(ROWID) FROM ");
  526. switch (table) {
  527. case ADatabaseTable::pilots:
  528. statement.append(Opl::Db::TABLE_PILOTS);
  529. break;
  530. case ADatabaseTable::aircraft:
  531. statement.append(Opl::Db::TABLE_AIRCRAFT);
  532. break;
  533. case ADatabaseTable::tails:
  534. statement.append(Opl::Db::TABLE_TAILS);
  535. break;
  536. default:
  537. DEB << "Not a valid completer target for this function.";
  538. return 0;
  539. }
  540. auto query = QSqlQuery(statement);
  541. if (query.first()) {
  542. return query.value(0).toInt();
  543. } else {
  544. LOG << "No entry found. (Database empty?)" << query.lastError().text();
  545. return 0;
  546. }
  547. }
  548. QList<RowId_T> ADatabase::getForeignKeyConstraints(RowId_T foreign_row_id, ADatabaseTable target)
  549. {
  550. QString statement = QLatin1String("SELECT ROWID FROM flights WHERE ");
  551. switch (target) {
  552. case ADatabaseTable::pilots:
  553. statement.append(QLatin1String("pic=?"));
  554. break;
  555. case ADatabaseTable::tails:
  556. statement.append(QLatin1String("acft=?"));
  557. break;
  558. default:
  559. DEB << "Not a valid target for this function.";
  560. return QList<int>();
  561. break;
  562. }
  563. QSqlQuery query;
  564. query.prepare(statement);
  565. query.addBindValue(foreign_row_id);
  566. query.exec();
  567. if (!query.isActive()) {
  568. lastError = query.lastError();
  569. DEB << "Error";
  570. DEB << statement;
  571. DEB << query.lastError().text();
  572. return QList<int>();
  573. }
  574. QList<int> row_ids;
  575. while (query.next()) {
  576. row_ids.append(query.value(0).toInt());
  577. }
  578. return row_ids;
  579. }
  580. APilotEntry ADatabase::resolveForeignPilot(RowId_T foreign_key)
  581. {
  582. return aDB->getPilotEntry(foreign_key);
  583. }
  584. ATailEntry ADatabase::resolveForeignTail(RowId_T foreign_key)
  585. {
  586. return aDB->getTailEntry(foreign_key);
  587. }
  588. QVector<QVariant> ADatabase::customQuery(QString statement, int return_values)
  589. {
  590. QSqlQuery query(statement);
  591. if(!query.exec()) {
  592. lastError = query.lastError();
  593. DEB << "Query Error: " << lastError.text();
  594. return {};
  595. }
  596. if (!query.first()) {
  597. LOG << "No result found. Check Query and Error.";
  598. DEB << "Error: " << query.lastError().text();
  599. DEB << "Statement: " << statement;
  600. return QVector<QVariant>();
  601. } else {
  602. query.first();
  603. query.previous();
  604. QVector<QVariant> result;
  605. while (query.next()) {
  606. for (int i = 0; i < return_values ; i++) {
  607. result.append(query.value(i));
  608. }
  609. }
  610. lastError = QString();
  611. return result;
  612. }
  613. }
  614. QMap<ADatabaseSummaryKey, QString> ADatabase::databaseSummary(const QString &db_path)
  615. {
  616. const QString connection_name = QStringLiteral("summary_connection");
  617. QMap<ADatabaseSummaryKey, QString> return_values;
  618. { // scope for a temporary database connection, ensures proper cleanup when removeDatabase() is called.
  619. //DEB << "Adding temporary connection to database:" << db_path;
  620. QSqlDatabase temp_database = QSqlDatabase::addDatabase(SQLITE_DRIVER, connection_name); // Don't use default connection
  621. temp_database.setDatabaseName(db_path);
  622. if (!temp_database.open())
  623. return {};
  624. QSqlQuery query(temp_database); // Query object using the temporary connection
  625. ADatabaseSummaryKey key; // Used among the queries for verbosity... and sanity
  626. const QVector<QPair<ADatabaseSummaryKey, QString>> key_table_pairs = {
  627. {ADatabaseSummaryKey::total_flights, QStringLiteral("flights")},
  628. {ADatabaseSummaryKey::total_tails, QStringLiteral("tails")},
  629. {ADatabaseSummaryKey::total_pilots, QStringLiteral("pilots")}
  630. };
  631. // retreive amount of flights, tails and pilots
  632. for (const auto & pair : key_table_pairs) {
  633. query.prepare(QLatin1String("SELECT COUNT (*) FROM ") + pair.second);
  634. query.exec();
  635. key = pair.first;
  636. if (query.first()){
  637. return_values[key] = query.value(0).toString();
  638. }
  639. else{
  640. return_values[key] = QString();
  641. }
  642. }
  643. // retreive date of last flight
  644. query.prepare(QStringLiteral("SELECT MAX(doft) FROM flights"));
  645. query.exec();
  646. key = ADatabaseSummaryKey::last_flight;
  647. if (query.first()){
  648. return_values[key] = query.value(0).toString();
  649. }
  650. else {
  651. return_values[key] = QString();
  652. }
  653. // retreive total flight time as a string "hh:mm"
  654. query.prepare(QStringLiteral("SELECT "
  655. "printf(\"%02d\",CAST(SUM(tblk) AS INT)/60)"
  656. "||':'||"
  657. "printf(\"%02d\",CAST(SUM(tblk) AS INT)%60) FROM flights"));
  658. key = ADatabaseSummaryKey::total_time;
  659. query.exec();
  660. if (query.first()){
  661. return_values[key] = query.value(0).toString();
  662. }
  663. else {
  664. return_values[key] = QString();
  665. }
  666. }
  667. QSqlDatabase::removeDatabase(connection_name); // cleanly removes temp connection without leaks since query+db are out of scope
  668. return return_values;
  669. }
  670. const QString ADatabase::databaseSummaryString(const QString &db_path)
  671. {
  672. auto summary_map = databaseSummary(db_path);
  673. QString out = QLatin1String("<table>");
  674. out.append(tr("<tr><td>Total Time: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_time]));
  675. out.append(tr("<tr><td>Last Flight: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::last_flight]));
  676. out.append(tr("<tr><td>Number of flights: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_flights]));
  677. out.append(tr("<tr><td>Number of aircraft: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_tails]));
  678. out.append(tr("<tr><td>Number of Pilots: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_pilots]));
  679. out.append("</table>");
  680. return out;
  681. }
  682. /*!
  683. * \brief ADatabase::createBackup copies the currently used database to an external backup location provided by the user
  684. * \param dest_file This is the full path and filename of where the backup will be created, e.g. 'home/Sully/myBackups/backupFromOpl.db'
  685. */
  686. bool ADatabase::createBackup(const QString& dest_file)
  687. {
  688. LOG << "Backing up current database to: " << dest_file;
  689. ADatabase::disconnect();
  690. QFile db_file(databaseFile.absoluteFilePath());
  691. //DEB << "File to Overwrite:" << db_file;
  692. if (!db_file.copy(dest_file)) {
  693. LOG << "Unable to backup old database:" << db_file.errorString();
  694. return false;
  695. }
  696. LOG << "Backed up old database as:" << dest_file;
  697. ADatabase::connect();
  698. emit connectionReset();
  699. return true;
  700. }
  701. QVector<RowData_T> ADatabase::getTable(ADatabaseTable table_name)
  702. {
  703. auto query_str = QStringLiteral("SELECT * FROM ");
  704. switch (table_name) {
  705. case ADatabaseTable::pilots:
  706. query_str.append(Opl::Db::TABLE_PILOTS);
  707. break;
  708. case ADatabaseTable::tails:
  709. query_str.append(Opl::Db::TABLE_TAILS);
  710. break;
  711. case ADatabaseTable::flights:
  712. query_str.append(Opl::Db::TABLE_FLIGHTS);
  713. break;
  714. case ADatabaseTable::currencies:
  715. query_str.append(Opl::Db::TABLE_CURRENCIES);
  716. default:
  717. break;
  718. }
  719. QSqlQuery q;
  720. q.prepare(query_str);
  721. q.setForwardOnly(true);
  722. if (!q.exec()) {
  723. LOG << "SQL error: " << q.lastError().text();
  724. LOG << "Statement: " << query_str;
  725. lastError = q.lastError();
  726. return {};
  727. }
  728. QVector<RowData_T> entry_data;
  729. while(q.next()) { // iterate through records
  730. auto r = q.record();
  731. //DEB << r;
  732. RowData_T row;
  733. for (int i = 0; i < r.count(); i++){
  734. if(!r.value(i).isNull()) {
  735. row.insert(r.fieldName(i), r.value(i));
  736. }
  737. }
  738. entry_data.append(row);
  739. }
  740. return entry_data;
  741. }
  742. /*!
  743. * \brief ADatabase::restoreBackup restores the database from a given backup file and replaces the currently active database.
  744. * \param backup_file This is the full path and filename of the backup, e.g. 'home/Sully/myBackups/backupFromOpl.db'
  745. */
  746. bool ADatabase::restoreBackup(const QString& backup_file)
  747. {
  748. LOG << "Restoring backup from file:" << backup_file;
  749. QString default_loc = databaseFile.absoluteFilePath();
  750. ADatabase::disconnect();
  751. QFile backup(backup_file);
  752. QFile current_db(default_loc);
  753. if (!current_db.rename(default_loc + QLatin1String(".tmp"))) { // move previously used db out of the way
  754. LOG << current_db.errorString() << "Unable to remove current db file";
  755. return false;
  756. }
  757. if (!backup.copy(default_loc))
  758. {
  759. LOG << backup.errorString() << "Could not copy" << backup.fileName() << "to" << databaseFile;
  760. // try to restore previously used db
  761. current_db.rename(default_loc);
  762. return false;
  763. }
  764. // backup has been restored, clean up the previously moved file
  765. current_db.remove();
  766. LOG << "Backup successfully restored!";
  767. ADatabase::connect();
  768. emit connectionReset();
  769. return true;
  770. }