PageRenderTime 61ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/src/DBAccess.cpp

https://github.com/olympum/GoldenCheetah
C++ | 1111 lines | 735 code | 168 blank | 208 comment | 232 complexity | 076ed70ddd7a908be74a4cd8ff0b7315 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, LGPL-2.1
  1. /*
  2. * Copyright (c) 2006 Justin Knotzke (jknotzke@shampoo.ca)
  3. * Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com)
  4. *
  5. * This program is free software; you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License as published by the Free
  7. * Software Foundation; either version 2 of the License, or (at your option)
  8. * any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful, but WITHOUT
  11. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. * more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc., 51
  17. * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include "MainWindow.h"
  20. #include "Athlete.h"
  21. #include "DBAccess.h"
  22. #include <QtSql>
  23. #include <QtGui>
  24. #include "RideFile.h"
  25. #include "Zones.h"
  26. #include "Settings.h"
  27. #include "RideItem.h"
  28. #include "IntervalItem.h"
  29. #include "RideMetric.h"
  30. #include "TimeUtils.h"
  31. #include <math.h>
  32. #include <QtXml/QtXml>
  33. #include <QFile>
  34. #include <QFileInfo>
  35. #include "SummaryMetrics.h"
  36. #include "RideMetadata.h"
  37. #include "SpecialFields.h"
  38. // DB Schema Version - YOU MUST UPDATE THIS IF THE SCHEMA VERSION CHANGES!!!
  39. // Schema version will change if a) the default metadata.xml is updated
  40. // or b) new metrics are added / old changed
  41. // or c) the metricDB tables structures change
  42. // Revision History
  43. // Rev Date Who What Changed
  44. //-----------------------------------------------------------------------
  45. //
  46. // ******* Prior to version 29 no revision history was maintained *******
  47. //
  48. // 29 5th Sep 2011 Mark Liversedge Added color to the ride fields
  49. // 30 8th Sep 2011 Mark Liversedge Metadata 'data' field for data present string
  50. // 31 22 Nov 2011 Mark Liversedge Added Average WPK metric
  51. // 32 9th Dec 2011 Damien Grauser Temperature data flag (metadata field 'Data')
  52. // 33 17 Dec 2011 Damien Grauser Added ResponseIndex and EfficiencyFactor
  53. // 34 15 Jan 2012 Mark Liversedge Added Average and Max Temperature and Metric->conversionSum()
  54. // 35 13 Feb 2012 Mark Liversedge Max/Avg Cadence adjusted conversion
  55. // 36 18 Feb 2012 Mark Liversedge Added Pace (min/mile) and 250m, 500m Pace metrics
  56. // 37 06 Apr 2012 Rainer Clasen Added non-zero average Power (watts)
  57. // 38 8th Jul 2012 Mark Liversedge Computes metrics for manual files now
  58. // 39 18 Aug 2012 Mark Liversedge New metric LRBalance
  59. // 40 20 Oct 2012 Mark Liversedge Lucene search/filter and checkbox metadata field
  60. // 41 27 Oct 2012 Mark Liversedge Lucene switched to StandardAnalyzer and search all texts by default
  61. // 42 03 Dec 2012 Mark Liversedge W/KG ridefilecache changes - force a rebuild.
  62. // 43 24 Jan 2012 Mark Liversedge TRIMP update
  63. // 44 19 Apr 2013 Mark Liversedge Aerobic Decoupling precision reduced to 1pt
  64. // 45 09 May 2013 Mark Liversedge Added 2,3,8 and 90m peak power for fatigue profiling
  65. // 46 13 May 2013 Mark Liversedge Handle absence of speed in metric calculations
  66. // 47 17 May 2013 Mark Liversedge Reimplementation of w/kg and ride->getWeight()
  67. // 48 22 May 2013 Mark Liversedge Removing local measures.xml, till v3.1
  68. // 49 29 Oct 2013 Mark Liversedge Added percentage time in zone
  69. // 50 29 Oct 2013 Mark Liversedge Added percentage time in heartrate zone
  70. // 51 05 Nov 2013 Mark Liversedge Added average aPower
  71. // 52 05 Nov 2013 Mark Liversedge Added EOA - Effect of Altitude
  72. // 53 18 Dec 2013 Mark Liversedge Added Fatigue Index (for power)
  73. // 54 07 Jan 2014 Mark Liversedge Revised Estimated VO2MAX metric formula
  74. // 55 20 Jan 2014 Mark Liversedge Added back Minimum W'bal metric and MaxMatch
  75. // 56 20 Jan 2014 Mark Liversedge Added W' TAU to be able to track it
  76. // 57 20 Jan 2014 Mark Liversedge Added W' Expenditure for total energy spent above CP
  77. // 58 23 Jan 2014 Mark Liversedge W' work rename and calculate without reference to WPrime class (speed)
  78. // 59 24 Jan 2014 Mark Liversedge Added Maximum W' exp which is same as W'bal bur expressed as used not left
  79. // 60 05 Feb 2014 Mark Liversedge Added Critical Power as a metric -- retrieves from settings for now
  80. // 61 15 Feb 2014 Mark Liversedge Fixed W' Work (for recintsecs not 1s!).
  81. // 62 06 Mar 2014 Mark Liversedge Fixed Fatigue Index to find peak then watch for decay, primarily useful in sprint intervals
  82. // 63 06 Mar 2014 Mark Liversedge Added Pacing Index AP as %age of Max Power
  83. // 64 17 Mar 2014 Mark Liversedge Added W' and CP work to PMC metrics
  84. // 65 17 Mar 2014 Mark Liversedge Added Aerobic TISS prototype
  85. // 66 18 Mar 2014 Mark Liversedge Updated aPower calculation
  86. // 67 22 Mar 2014 Mark Liversedge Added Anaerobic TISS prototype
  87. // 68 22 Mar 2014 Mark Liversedge Added dTISS prototype
  88. // 69 23 Mar 2014 Mark Liversedge Updated Gompertz constansts for An-TISS sigmoid
  89. // 70 27 Mar 2014 Mark Liversedge Add file CRC to refresh only if contents change (not just timestamps)
  90. // 71 14 Apr 2014 Mark Liversedge Added average lef/right vector metrics (Pedal Smoothness / Torque Effectiveness)
  91. // 72 24 Apr 2014 Mark Liversedge Andy Froncioni's faster algorithm for W' bal
  92. // 73 11 May 2014 Mark Liversedge Default color of 1,1,1 now uses CPLOTMARKER for ride color, change version to force rebuild
  93. // 74 20 May 2014 Mark Liversedge Added Athlete Weight
  94. // 75 25 May 2014 Mark Liversedge W' work calculation changed to only include energy above CP
  95. // 76 14 Jun 2014 Mark Liversedge Add new 'present' field that uses Data tag data
  96. // 77 18 Jun 2014 Mark Liversedge Add TSS per hour metric
  97. // 78 19 Jun 2014 Mark Liversedge Do not include zeroes in average L/R pedal smoothness/torque effectiveness
  98. // 79 20 Jun 2014 Mark Liversedge Change the way average temperature is handled
  99. // 80 13 Jul 2014 Mark Liversedge W' work + Below CP work = Work
  100. // 81 16 Aug 2014 Joern Rischmueller Added 'Elevation Loss'
  101. // 82 23 Aug 2014 Mark Liversedge Added W'bal Matches
  102. // 83 05 Sep 2014 Joern Rischmueller Added 'Time Carrying' and 'Elevation Gain Carrying'
  103. // 84 08 Sep 2014 Mark Liversedge Added HrPw Ratio
  104. // 85 09 Sep 2014 Mark Liversedge Added HrNp Ratio
  105. // 86 26 Sep 2014 Mark Liversedge Added isRun first class var
  106. // 87 11 Oct 2014 Mark Liversedge W'bal inegrator fixed up by Dave Waterworth
  107. // 88 14 Oct 2014 Mark Liversedge Pace Zone Metrics
  108. // 89 07 Nov 2014 Ale Martinez GOVSS
  109. // 90 08 Nov 2014 Mark Liversedge Update data flags for Moxy and Garmin Running Dynamics
  110. // 91 16 Nov 2014 Damien Grauser Do not include values if data not present in TimeInZone and HRTimeInZone
  111. // 92 21 Nov 2014 Mark Liversedge Added Watts:RPE ratio
  112. // 93 26 Nov 2014 Mark Liversedge Added Min, Max, Avg SmO2
  113. // 94 02 Dic 2014 Ale Martinez Added xPace
  114. int DBSchemaVersion = 94;
  115. DBAccess::DBAccess(Context* context) : context(context), db(NULL)
  116. {
  117. // check we have one and use built in if not there
  118. RideMetadata::readXML(":/xml/measures.xml", mkeywordDefinitions, mfieldDefinitions, mcolorfield);
  119. initDatabase(context->athlete->home->cache());
  120. }
  121. DBAccess::~DBAccess()
  122. {
  123. if (db) {
  124. db->close();
  125. delete db;
  126. QSqlDatabase::removeDatabase(sessionid);
  127. }
  128. }
  129. void
  130. DBAccess::initDatabase(QDir home)
  131. {
  132. sessionid = QString("%1").arg(context->athlete->cyclist);
  133. if(db && db->database(sessionid).isOpen()) return;
  134. // use different name for v3 metricDB to avoid constant rebuilding
  135. // when switching between v2 stable and v3 development builds
  136. db = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", sessionid));
  137. db->setDatabaseName(home.canonicalPath() + "/metricDBv3");
  138. if (!db->database(sessionid).isOpen()) {
  139. QMessageBox::critical(0, qApp->translate("DBAccess","Cannot open database"),
  140. qApp->translate("DBAccess","Unable to establish a database connection.\n"
  141. "This feature requires SQLite support. Please read "
  142. "the Qt SQL driver documentation for information how "
  143. "to build it.\n\n"
  144. "Click Cancel to exit."), QMessageBox::Cancel);
  145. } else {
  146. // create database - does nothing if its already there
  147. createDatabase();
  148. }
  149. }
  150. unsigned int
  151. DBAccess::computeFileCRC(QString filename)
  152. {
  153. QFile file(filename);
  154. QFileInfo fileinfo(file);
  155. // open file
  156. if (!file.open(QFile::ReadOnly)) return 0;
  157. // allocate space
  158. QScopedArrayPointer<char> data(new char[file.size()]);
  159. // read entire file into memory
  160. QDataStream *rawstream(new QDataStream(&file));
  161. rawstream->readRawData(&data[0], file.size());
  162. file.close();
  163. return qChecksum(&data[0], file.size());
  164. }
  165. bool DBAccess::createMetricsTable()
  166. {
  167. QSqlQuery query(db->database(sessionid));
  168. bool rc;
  169. bool createTables = true;
  170. // does the table exist?
  171. rc = query.exec("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
  172. if (rc) {
  173. while (query.next()) {
  174. QString table = query.value(0).toString();
  175. if (table == "metrics") {
  176. createTables = false;
  177. break;
  178. }
  179. }
  180. }
  181. // we need to create it!
  182. if (rc && createTables) {
  183. QString createMetricTable = "create table metrics (filename varchar primary key,"
  184. "identifier varchar,"
  185. "timestamp integer,"
  186. "crc integer,"
  187. "ride_date date,"
  188. "isRun integer,"
  189. "present varchar,"
  190. "color varchar,"
  191. "fingerprint integer";
  192. // Add columns for all the metric factory metrics
  193. const RideMetricFactory &factory = RideMetricFactory::instance();
  194. for (int i=0; i<factory.metricCount(); i++)
  195. createMetricTable += QString(", X%1 double").arg(factory.metricName(i));
  196. // And all the metadata texts
  197. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  198. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  199. createMetricTable += QString(", Z%1 varchar").arg(context->specialFields.makeTechName(field.name));
  200. }
  201. }
  202. // And all the metadata metrics
  203. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  204. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  205. createMetricTable += QString(", Z%1 double").arg(context->specialFields.makeTechName(field.name));
  206. }
  207. }
  208. createMetricTable += " )";
  209. rc = query.exec(createMetricTable);
  210. //if (!rc) qDebug()<<"create table failed!" << query.lastError();
  211. // add row to version database
  212. QString metadataXML = QString(context->athlete->home->config().absolutePath()) + "/metadata.xml";
  213. int metadatacrcnow = computeFileCRC(metadataXML);
  214. QDateTime timestamp = QDateTime::currentDateTime();
  215. // wipe current version row
  216. query.exec("DELETE FROM version where table_name = \"metrics\"");
  217. query.prepare("INSERT INTO version (table_name, schema_version, creation_date, metadata_crc ) values (?,?,?,?)");
  218. query.addBindValue("metrics");
  219. query.addBindValue(DBSchemaVersion);
  220. query.addBindValue(timestamp.toTime_t());
  221. query.addBindValue(metadatacrcnow);
  222. rc = query.exec();
  223. }
  224. return rc;
  225. }
  226. bool DBAccess::dropMetricTable()
  227. {
  228. QSqlQuery query("DROP TABLE metrics", db->database(sessionid));
  229. bool rc = query.exec();
  230. return rc;
  231. }
  232. bool DBAccess::createIntervalMetricsTable()
  233. {
  234. QSqlQuery query(db->database(sessionid));
  235. bool rc;
  236. bool createTables = true;
  237. // does the table exist?
  238. rc = query.exec("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
  239. if (rc) {
  240. while (query.next()) {
  241. QString table = query.value(0).toString();
  242. if (table == "interval_metrics") {
  243. createTables = false;
  244. break;
  245. }
  246. }
  247. }
  248. // we need to create it!
  249. if (rc && createTables) {
  250. QString createIntervalMetricTable = "create table interval_metrics (identifier varchar,"
  251. "filename varchar,"
  252. "timestamp integer,"
  253. "crc integer,"
  254. "ride_date date,"
  255. "type varchar,"
  256. "groupName varchar,"
  257. "name varchar,"
  258. "start integer,"
  259. "stop integer,"
  260. "color varchar,"
  261. "fingerprint integer";
  262. // Add columns for all the metric factory metrics
  263. const RideMetricFactory &factory = RideMetricFactory::instance();
  264. for (int i=0; i<factory.metricCount(); i++)
  265. createIntervalMetricTable += QString(", X%1 double").arg(factory.metricName(i));
  266. // And all the metadata texts from ride of the intervals
  267. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  268. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  269. createIntervalMetricTable += QString(", Z%1 varchar").arg(context->specialFields.makeTechName(field.name));
  270. }
  271. }
  272. // And all the metadata metrics from ride of the intervals
  273. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  274. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  275. createIntervalMetricTable += QString(", Z%1 double").arg(context->specialFields.makeTechName(field.name));
  276. }
  277. }
  278. createIntervalMetricTable += " )";
  279. rc = query.exec(createIntervalMetricTable);
  280. if (!rc) qDebug()<<"create table interval_metrics failed!" << query.lastError();
  281. // add row to version database
  282. QString metadataXML = QString(context->athlete->home->config().absolutePath()) + "/metadata.xml";
  283. int metadatacrcnow = computeFileCRC(metadataXML);
  284. QDateTime timestamp = QDateTime::currentDateTime();
  285. // wipe current version row
  286. query.exec("DELETE FROM version where table_name = \"interval_metrics\"");
  287. query.prepare("INSERT INTO version (table_name, schema_version, creation_date, metadata_crc ) values (?,?,?,?)");
  288. query.addBindValue("interval_metrics");
  289. query.addBindValue(DBSchemaVersion);
  290. query.addBindValue(timestamp.toTime_t());
  291. query.addBindValue(metadatacrcnow);
  292. rc = query.exec();
  293. }
  294. return rc;
  295. }
  296. bool DBAccess::dropIntervalMetricTable()
  297. {
  298. QSqlQuery query("DROP TABLE interval_metrics", db->database(sessionid));
  299. bool rc = query.exec();
  300. return rc;
  301. }
  302. bool DBAccess::createMeasuresTable()
  303. {
  304. QSqlQuery query(db->database(sessionid));
  305. bool rc;
  306. bool createTables = true;
  307. // does the table exist?
  308. rc = query.exec("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
  309. if (rc) {
  310. while (query.next()) {
  311. QString table = query.value(0).toString();
  312. if (table == "measures") {
  313. createTables = false;
  314. break;
  315. }
  316. }
  317. }
  318. // we need to create it!
  319. if (rc && createTables) {
  320. QString createMeasuresTable = "create table measures (timestamp integer primary key,"
  321. "measure_date date";
  322. // And all the metadata texts
  323. foreach(FieldDefinition field, mfieldDefinitions)
  324. if (field.type < 3 || field.type == 7) createMeasuresTable += QString(", Z%1 varchar").arg(context->specialFields.makeTechName(field.name));
  325. // And all the metadata measures
  326. foreach(FieldDefinition field, mfieldDefinitions)
  327. if (field.type == 3 || field.type == 4)
  328. createMeasuresTable += QString(", Z%1 double").arg(context->specialFields.makeTechName(field.name));
  329. createMeasuresTable += " )";
  330. rc = query.exec(createMeasuresTable);
  331. //if (!rc) qDebug()<<"create table failed!" << query.lastError();
  332. // add row to version database
  333. //QString measuresXML = QString(home.absolutePath()) + "/measures.xml";
  334. int measurescrcnow = 0; //computeFileCRC(measuresXML); //no crc since we don't allow definition
  335. QDateTime timestamp = QDateTime::currentDateTime();
  336. // wipe current version row
  337. rc = query.exec("DELETE FROM version where table_name = \"measures\";");
  338. // add row to version table
  339. query.prepare("INSERT INTO version (table_name, schema_version, creation_date, metadata_crc ) values (?,?,?,?)");
  340. query.addBindValue("measures");
  341. query.addBindValue(DBSchemaVersion);
  342. query.addBindValue(timestamp.toTime_t());
  343. query.addBindValue(measurescrcnow);
  344. rc = query.exec();
  345. }
  346. return rc;
  347. }
  348. bool DBAccess::dropMeasuresTable()
  349. {
  350. QSqlQuery query("DROP TABLE measures", db->database(sessionid));
  351. bool rc = query.exec();
  352. return rc;
  353. }
  354. bool DBAccess::createDatabase()
  355. {
  356. // check schema version and if missing recreate database
  357. checkDBVersion();
  358. // Ride metrics
  359. createMetricsTable();
  360. // Athlete measures
  361. createMeasuresTable();
  362. return true;
  363. }
  364. void DBAccess::checkDBVersion()
  365. {
  366. // get a CRC for metadata.xml
  367. QString metadataXML = QString(context->athlete->home->config().absolutePath()) + "/metadata.xml";
  368. int metadatacrcnow = computeFileCRC(metadataXML);
  369. // get a CRC for measures.xml
  370. //QString measuresXML = QString(home.absolutePath()) + "/measures.xml";
  371. int measurescrcnow = 0; //computeFileCRC(measuresXML);// we don't allow user to edit
  372. // can we get a version number?
  373. QSqlQuery query("SELECT table_name, schema_version, creation_date, metadata_crc from version;", db->database(sessionid));
  374. bool rc = query.exec();
  375. if (!rc) {
  376. // we couldn't read the version table properly
  377. // it must be out of date!!
  378. QSqlQuery dropM("DROP TABLE version", db->database(sessionid));
  379. dropM.exec();
  380. // recreate version table and add one entry
  381. QSqlQuery version("CREATE TABLE version ( table_name varchar primary key, schema_version integer, creation_date date, metadata_crc integer );", db->database(sessionid));
  382. version.exec();
  383. // wipe away whatever (if anything is there)
  384. dropMetricTable();
  385. dropIntervalMetricTable();
  386. dropMeasuresTable();
  387. // create afresh
  388. createMetricsTable();
  389. createIntervalMetricsTable();
  390. createMeasuresTable();
  391. return;
  392. }
  393. // ok we checked out ok, so lets adjust db schema to reflect
  394. // tne current version / crc
  395. bool dropMetric = true;
  396. bool dropIntervalMetric = true;
  397. bool dropMeasures = true;
  398. while (query.next()) {
  399. QString table_name = query.value(0).toString();
  400. int currentversion = query.value(1).toInt();
  401. //int creationdate = query.value(2).toInt(); // not relevant anymore, we use version/crc
  402. int crc = query.value(3).toInt();
  403. if (table_name == "metrics") {
  404. if (currentversion == DBSchemaVersion && crc == metadatacrcnow) {
  405. dropMetric = false;
  406. }
  407. }
  408. if (table_name == "interval_metrics") {
  409. if (currentversion == DBSchemaVersion && crc == metadatacrcnow) {
  410. dropIntervalMetric = false;
  411. }
  412. }
  413. if (table_name == "measures") {
  414. if (crc == measurescrcnow) {
  415. dropMeasures = false;
  416. }
  417. }
  418. }
  419. query.finish();
  420. // "metrics" table, is it up-to-date?
  421. if (dropMetric) {
  422. dropMetricTable();
  423. createMetricsTable();
  424. }
  425. // "interval_metrics" table, is it up-to-date?
  426. if (dropIntervalMetric) {
  427. dropIntervalMetricTable();
  428. createIntervalMetricsTable();
  429. }
  430. // "measures" table, is it up-to-date? - export - recreate - import ....
  431. // gets wiped away for now, will fix as part of v3.1
  432. if (dropMeasures) {
  433. dropMeasuresTable();
  434. createMeasuresTable();
  435. }
  436. }
  437. int DBAccess::getDBVersion()
  438. {
  439. int schema_version = -1;
  440. // can we get a version number?
  441. QSqlQuery query("SELECT schema_version from version;", db->database(sessionid));
  442. bool rc = query.exec();
  443. if (rc) {
  444. while (query.next()) {
  445. if (query.value(0).toInt() > schema_version)
  446. schema_version = query.value(0).toInt();
  447. }
  448. }
  449. query.finish();
  450. return schema_version;
  451. }
  452. /*----------------------------------------------------------------------
  453. * CRUD routines for Metrics table
  454. *----------------------------------------------------------------------*/
  455. bool DBAccess::importRide(SummaryMetrics *summaryMetrics, RideFile *ride, QColor color, unsigned long fingerprint, bool modify)
  456. {
  457. QSqlQuery query(db->database(sessionid));
  458. QDateTime timestamp = QDateTime::currentDateTime();
  459. if (modify) {
  460. // zap the current row
  461. query.prepare("DELETE FROM metrics WHERE filename = ?;");
  462. query.addBindValue(summaryMetrics->getFileName());
  463. query.exec();
  464. }
  465. // construct an insert statement
  466. QString insertStatement = "insert into metrics ( filename, identifier, crc, timestamp, ride_date, isRun, present, color, fingerprint ";
  467. const RideMetricFactory &factory = RideMetricFactory::instance();
  468. for (int i=0; i<factory.metricCount(); i++)
  469. insertStatement += QString(", X%1 ").arg(factory.metricName(i));
  470. // And all the metadata texts
  471. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  472. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  473. insertStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  474. }
  475. }
  476. // And all the metadata metrics
  477. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  478. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  479. insertStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  480. }
  481. }
  482. insertStatement += " ) values (?,?,?,?,?,?,?,?,?"; // filename, identifier, crc, timestamp, ride_date, isRun, present, color, fingerprint
  483. for (int i=0; i<factory.metricCount(); i++)
  484. insertStatement += ",?";
  485. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  486. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  487. insertStatement += ",?";
  488. }
  489. }
  490. insertStatement += ")";
  491. query.prepare(insertStatement);
  492. // filename, crc, timestamp, ride date
  493. QString fullPath = QString(context->athlete->home->activities().absolutePath()) + "/" + summaryMetrics->getFileName();
  494. query.addBindValue(summaryMetrics->getFileName());
  495. query.addBindValue(summaryMetrics->getId());
  496. query.addBindValue((int)computeFileCRC(fullPath));
  497. query.addBindValue(timestamp.toTime_t());
  498. query.addBindValue(summaryMetrics->getRideDate());
  499. query.addBindValue(summaryMetrics->isRun());
  500. query.addBindValue(ride->getTag("Data", ""));
  501. query.addBindValue(color.name());
  502. query.addBindValue((int)fingerprint);
  503. // values
  504. for (int i=0; i<factory.metricCount(); i++) {
  505. query.addBindValue(summaryMetrics->getForSymbol(factory.metricName(i)));
  506. }
  507. // And all the metadata texts
  508. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  509. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type ==7)) {
  510. query.addBindValue(ride->getTag(field.name, ""));
  511. }
  512. }
  513. // And all the metadata metrics
  514. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  515. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  516. query.addBindValue(ride->getTag(field.name, "0.0").toDouble());
  517. } else if (!context->specialFields.isMetric(field.name)) {
  518. if (field.name == "Recording Interval")
  519. query.addBindValue(ride->recIntSecs());
  520. }
  521. }
  522. // go do it!
  523. bool rc = query.exec();
  524. //if(!rc) qDebug() << query.lastError();
  525. return rc;
  526. }
  527. bool
  528. DBAccess::deleteRide(QString name)
  529. {
  530. QSqlQuery query(db->database(sessionid));
  531. query.prepare("DELETE FROM metrics WHERE filename = ?;");
  532. query.addBindValue(name);
  533. return query.exec();
  534. }
  535. QList<QDateTime> DBAccess::getAllDates()
  536. {
  537. QSqlQuery query("SELECT ride_date from metrics ORDER BY ride_date;", db->database(sessionid));
  538. QList<QDateTime> dates;
  539. query.exec();
  540. while(query.next())
  541. {
  542. QDateTime date = query.value(0).toDateTime();
  543. dates << date;
  544. }
  545. return dates;
  546. }
  547. bool
  548. DBAccess::getRide(QString filename, SummaryMetrics &summaryMetrics, QColor&color)
  549. {
  550. // lookup a ride by filename returning true/false if found
  551. bool found = false;
  552. // construct the select statement
  553. QString selectStatement = "SELECT filename, identifier, ride_date, isRun, color";
  554. const RideMetricFactory &factory = RideMetricFactory::instance();
  555. for (int i=0; i<factory.metricCount(); i++)
  556. selectStatement += QString(", X%1 ").arg(factory.metricName(i));
  557. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  558. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  559. selectStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  560. }
  561. }
  562. selectStatement += " FROM metrics where filename = :name;";
  563. // execute the select statement
  564. QSqlQuery query(selectStatement, db->database(sessionid));
  565. query.bindValue(":start", filename);
  566. query.exec();
  567. while(query.next())
  568. {
  569. found = true;
  570. // filename and date
  571. summaryMetrics.setFileName(query.value(0).toString());
  572. summaryMetrics.setId(query.value(1).toString());
  573. summaryMetrics.setRideDate(query.value(2).toDateTime());
  574. summaryMetrics.setIsRun(query.value(3).toInt());
  575. color = QColor(query.value(4).toString());
  576. // the values
  577. int i=0;
  578. for (; i<factory.metricCount(); i++)
  579. summaryMetrics.setForSymbol(factory.metricName(i), query.value(i+5).toDouble());
  580. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  581. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  582. QString underscored = field.name;
  583. summaryMetrics.setForSymbol(underscored.replace("_"," "), query.value(i+4).toDouble());
  584. i++;
  585. } else if (!context->specialFields.isMetric(field.name) && field.type < 3) {
  586. QString underscored = field.name;
  587. summaryMetrics.setText(underscored.replace("_"," "), query.value(i+4).toString());
  588. i++;
  589. }
  590. }
  591. }
  592. query.finish();
  593. return found;
  594. }
  595. QList<QString> DBAccess::getDistinctValues(FieldDefinition field)
  596. {
  597. QStringList returning;
  598. // what are we querying?
  599. QString fieldname = QString("Z%1").arg(context->specialFields.makeTechName(field.name));
  600. QString selectStatement = QString("SELECT DISTINCT(%1) FROM METRICS ORDER BY %1;").arg(fieldname);
  601. QSqlQuery query(db->database(sessionid));
  602. query.prepare(selectStatement);
  603. query.exec();
  604. while(query.next()) {
  605. returning << query.value(0).toString();
  606. }
  607. return returning;
  608. }
  609. QList<SummaryMetrics> DBAccess::getAllMetricsFor(QDateTime start, QDateTime end)
  610. {
  611. QList<SummaryMetrics> metrics;
  612. // null date range fetches all, but not currently used by application code
  613. // since it relies too heavily on the results of the QDateTime constructor
  614. if (start == QDateTime()) start = QDateTime::currentDateTime().addYears(-10);
  615. if (end == QDateTime()) end = QDateTime::currentDateTime().addYears(+10);
  616. // construct the select statement
  617. QString selectStatement = "SELECT filename, identifier, ride_date, isRun";
  618. const RideMetricFactory &factory = RideMetricFactory::instance();
  619. for (int i=0; i<factory.metricCount(); i++)
  620. selectStatement += QString(", X%1 ").arg(factory.metricName(i));
  621. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  622. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  623. selectStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  624. }
  625. }
  626. selectStatement += " FROM metrics where DATE(ride_date) >=DATE(:start) AND DATE(ride_date) <=DATE(:end) "
  627. " ORDER BY ride_date;";
  628. // execute the select statement
  629. QSqlQuery query(db->database(sessionid));
  630. query.prepare(selectStatement);
  631. query.bindValue(":start", start.date());
  632. query.bindValue(":end", end.date());
  633. query.exec();
  634. while(query.next())
  635. {
  636. SummaryMetrics summaryMetrics;
  637. // filename and date
  638. summaryMetrics.setFileName(query.value(0).toString());
  639. summaryMetrics.setId(query.value(1).toString());
  640. summaryMetrics.setRideDate(query.value(2).toDateTime());
  641. summaryMetrics.setIsRun(query.value(3).toInt());
  642. // the values
  643. int i=0;
  644. for (; i<factory.metricCount(); i++)
  645. summaryMetrics.setForSymbol(factory.metricName(i), query.value(i+4).toDouble());
  646. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  647. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  648. QString underscored = field.name;
  649. summaryMetrics.setForSymbol(underscored.replace("_"," "), query.value(i+4).toDouble());
  650. i++;
  651. } else if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  652. QString underscored = field.name;
  653. summaryMetrics.setText(underscored.replace("_"," "), query.value(i+4).toString());
  654. i++;
  655. }
  656. }
  657. metrics << summaryMetrics;
  658. }
  659. return metrics;
  660. }
  661. SummaryMetrics DBAccess::getRideMetrics(QString filename)
  662. {
  663. SummaryMetrics summaryMetrics;
  664. // construct the select statement
  665. QString selectStatement = "SELECT filename, identifier, isRun";
  666. const RideMetricFactory &factory = RideMetricFactory::instance();
  667. for (int i=0; i<factory.metricCount(); i++)
  668. selectStatement += QString(", X%1 ").arg(factory.metricName(i));
  669. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  670. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  671. selectStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  672. }
  673. }
  674. selectStatement += " FROM metrics WHERE filename = :filename ;";
  675. // execute the select statement
  676. QSqlQuery query(db->database(sessionid));
  677. query.prepare(selectStatement);
  678. query.bindValue(":filename", QVariant(filename));
  679. query.exec();
  680. while(query.next())
  681. {
  682. // filename and date
  683. summaryMetrics.setFileName(query.value(0).toString());
  684. summaryMetrics.setId(query.value(1).toString());
  685. summaryMetrics.setIsRun(query.value(2).toInt());
  686. // the values
  687. int i=0;
  688. for (; i<factory.metricCount(); i++)
  689. summaryMetrics.setForSymbol(factory.metricName(i), query.value(i+3).toDouble());
  690. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  691. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  692. QString underscored = field.name;
  693. summaryMetrics.setForSymbol(underscored.replace(" ","_"), query.value(i+3).toDouble());
  694. i++;
  695. } else if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  696. QString underscored = field.name;
  697. summaryMetrics.setText(underscored.replace("_"," "), query.value(i+3).toString());
  698. i++;
  699. }
  700. }
  701. }
  702. return summaryMetrics;
  703. }
  704. /*----------------------------------------------------------------------
  705. * CRUD routines for Measures table
  706. *----------------------------------------------------------------------*/
  707. bool DBAccess::importMeasure(SummaryMetrics *summaryMetrics)
  708. {
  709. QSqlQuery query(db->database(sessionid));
  710. // construct an insert statement
  711. QString insertStatement = "insert into measures (timestamp, measure_date";
  712. // And all the metadata texts
  713. foreach(FieldDefinition field, mfieldDefinitions) {
  714. if (field.type < 3 || field.type == 7) {
  715. insertStatement += QString(", Z%1 ").arg(msp.makeTechName(field.name));
  716. }
  717. }
  718. // And all the metadata metrics
  719. foreach(FieldDefinition field, mfieldDefinitions) {
  720. if (field.type == 3 || field.type == 4) {
  721. insertStatement += QString(", Z%1 ").arg(msp.makeTechName(field.name));
  722. }
  723. }
  724. insertStatement += " ) values (?,?"; // timestamp, measure_date
  725. foreach(FieldDefinition field, mfieldDefinitions) {
  726. if (field.type < 5 || field.type == 7) {
  727. insertStatement += ",?";
  728. }
  729. }
  730. insertStatement += ")";
  731. query.prepare(insertStatement);
  732. // timestamp and date
  733. query.addBindValue(summaryMetrics->getDateTime().toTime_t());
  734. query.addBindValue(summaryMetrics->getDateTime().date());
  735. // And all the text measures
  736. foreach(FieldDefinition field, mfieldDefinitions) {
  737. if (field.type < 3 || field.type == 7) {
  738. query.addBindValue(summaryMetrics->getText(field.name, ""));
  739. }
  740. }
  741. // And all the numeric measures
  742. foreach(FieldDefinition field, mfieldDefinitions) {
  743. if (field.type == 3 || field.type == 4) {
  744. query.addBindValue(summaryMetrics->getText(field.name, "nan").toDouble());
  745. }
  746. }
  747. // go do it!
  748. bool rc = query.exec();
  749. //if(!rc) qDebug() << query.lastError();
  750. return rc;
  751. }
  752. QList<SummaryMetrics> DBAccess::getAllMeasuresFor(QDateTime start, QDateTime end)
  753. {
  754. QList<SummaryMetrics> measures;
  755. // null date range fetches all, but not currently used by application code
  756. // since it relies too heavily on the results of the QDateTime constructor
  757. if (start == QDateTime()) start = QDateTime::currentDateTime().addYears(-10);
  758. if (end == QDateTime()) end = QDateTime::currentDateTime().addYears(+10);
  759. // construct the select statement
  760. QString selectStatement = "SELECT timestamp, measure_date";
  761. foreach(FieldDefinition field, mfieldDefinitions) {
  762. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  763. selectStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  764. }
  765. }
  766. selectStatement += " FROM measures where DATE(measure_date) >=DATE(:start) AND DATE(measure_date) <=DATE(:end) "
  767. " ORDER BY measure_date;";
  768. // execute the select statement
  769. QSqlQuery query(selectStatement, db->database(sessionid));
  770. query.prepare(selectStatement);
  771. query.bindValue(":start", start.date());
  772. query.bindValue(":end", end.date());
  773. query.exec();
  774. while(query.next())
  775. {
  776. SummaryMetrics add;
  777. // filename and date
  778. add.setDateTime(query.value(1).toDateTime());
  779. // the values
  780. int i=2;
  781. foreach(FieldDefinition field, mfieldDefinitions) {
  782. QString symbol = QString("%1_m").arg(field.name);
  783. if (field.type == 3 || field.type == 4) {
  784. add.setText(symbol, query.value(i).toString());
  785. i++;
  786. } else if (field.type < 3 || field.type == 7) {
  787. add.setText(symbol, query.value(i).toString());
  788. i++;
  789. }
  790. }
  791. measures << add;
  792. }
  793. return measures;
  794. }
  795. /*----------------------------------------------------------------------
  796. * CRUD routines for Interval Metrics table
  797. *----------------------------------------------------------------------*/
  798. bool DBAccess::importInterval(SummaryMetrics *summaryMetrics, IntervalItem *interval, QString type, QString groupName, QColor color, unsigned long fingerprint, bool modify)
  799. {
  800. QSqlQuery query(db->database(sessionid));
  801. QDateTime timestamp = QDateTime::currentDateTime();
  802. if (modify) {
  803. // zap the current row
  804. query.prepare("DELETE FROM interval_metrics WHERE filename = :filename AND type = :type AND groupName = :groupName AND start = :start;");
  805. query.bindValue(":filename", summaryMetrics->getFileName());
  806. query.bindValue(":type", type);
  807. query.bindValue(":groupName", groupName);
  808. query.bindValue(":start", interval->start);
  809. query.exec();
  810. }
  811. // construct an insert statement
  812. QString insertStatement = "insert into interval_metrics ( identifier, filename, crc, timestamp, ride_date, type, groupName, name, start, stop, color, fingerprint ";
  813. const RideMetricFactory &factory = RideMetricFactory::instance();
  814. for (int i=0; i<factory.metricCount(); i++)
  815. insertStatement += QString(", X%1 ").arg(factory.metricName(i));
  816. // And all the metadata texts
  817. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  818. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
  819. insertStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  820. }
  821. }
  822. // And all the metadata metrics
  823. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  824. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  825. insertStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  826. }
  827. }
  828. insertStatement += " ) values (?,?,?,?,?,?,?,?,?,?,?,?"; // identifier, filename, crc, timestamp, ride_date, type, groupName, name, start, stop, color, fingerprint
  829. for (int i=0; i<factory.metricCount(); i++)
  830. insertStatement += ",?";
  831. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  832. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  833. insertStatement += ",?";
  834. }
  835. }
  836. insertStatement += ");";
  837. query.prepare(insertStatement);
  838. // filename, crc, timestamp, ride date
  839. QString fullPath = QString(context->athlete->home->activities().absolutePath()) + "/" + summaryMetrics->getFileName();
  840. query.addBindValue(summaryMetrics->getFileName()+"-"+interval->displaySequence);
  841. query.addBindValue(summaryMetrics->getFileName());
  842. query.addBindValue((int)computeFileCRC(fullPath));
  843. query.addBindValue(timestamp.toTime_t());
  844. query.addBindValue(summaryMetrics->getRideDate());
  845. query.addBindValue(type); // type
  846. query.addBindValue(groupName); // groupName,
  847. query.addBindValue(interval->text(0)); // name,
  848. query.addBindValue(interval->start); // start,
  849. query.addBindValue(interval->stop); // stop,
  850. query.addBindValue(color.name());
  851. query.addBindValue((int)fingerprint);
  852. // values
  853. for (int i=0; i<factory.metricCount(); i++) {
  854. query.addBindValue(summaryMetrics->getForSymbol(factory.metricName(i)));
  855. }
  856. // And all the metadata texts
  857. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  858. if (!context->specialFields.isMetric(field.name) && (field.type < 3 || field.type ==7)) {
  859. query.addBindValue(interval->ride->getTag(field.name, ""));
  860. }
  861. }
  862. // And all the metadata metrics
  863. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  864. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  865. query.addBindValue(interval->ride->getTag(field.name, "0.0").toDouble());
  866. } else if (!context->specialFields.isMetric(field.name)) {
  867. if (field.name == "Recording Interval")
  868. query.addBindValue(interval->ride->recIntSecs());
  869. }
  870. }
  871. // go do it!
  872. bool rc = query.exec();
  873. if(!rc) qDebug() << query.lastError();
  874. return rc;
  875. }
  876. bool
  877. DBAccess::deleteIntervalsForRide(QString filename)
  878. {
  879. QSqlQuery query(db->database(sessionid));
  880. query.prepare("DELETE FROM interval_metrics WHERE filename = ?;");
  881. query.addBindValue(filename);
  882. return query.exec();
  883. }
  884. bool
  885. DBAccess::deleteIntervalsForTypeAndGroupName(QString type, QString groupName)
  886. {
  887. QSqlQuery query(db->database(sessionid));
  888. query.prepare("DELETE FROM interval_metrics WHERE type = :type AND groupName = :groupName;");
  889. query.addBindValue(type); // type
  890. query.addBindValue(groupName); // groupName,
  891. return query.exec();
  892. }
  893. bool
  894. DBAccess::getInterval(QString filename, QString type, QString groupName, int start, SummaryMetrics &summaryMetrics, QColor&color)
  895. {
  896. // lookup a ride by filename returning true/false if found
  897. bool found = false;
  898. // construct the select statement
  899. QString selectStatement = "SELECT identifier, filename, crc, type, groupName, color"; //identifier, filename, crc, timestamp, ride_date, type, groupName, name, start, stop
  900. const RideMetricFactory &factory = RideMetricFactory::instance();
  901. for (int i=0; i<factory.metricCount(); i++)
  902. selectStatement += QString(", X%1 ").arg(factory.metricName(i));
  903. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  904. if (!context->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
  905. selectStatement += QString(", Z%1 ").arg(context->specialFields.makeTechName(field.name));
  906. }
  907. }
  908. selectStatement += " FROM interval_metrics where filename = :filename AND type = :type AND groupName = :groupName AND start = :start;";
  909. // execute the select statement
  910. QSqlQuery query(selectStatement, db->database(sessionid));
  911. query.bindValue(":filename", filename);
  912. query.bindValue(":type", type);
  913. query.bindValue(":groupName", groupName);
  914. query.bindValue(":start", start);
  915. query.exec();
  916. while(query.next())
  917. {
  918. found = true;
  919. // filename and date
  920. summaryMetrics.setFileName(query.value(0).toString());
  921. summaryMetrics.setId(query.value(1).toString());
  922. summaryMetrics.setRideDate(query.value(2).toDateTime());
  923. summaryMetrics.setIsRun(query.value(3).toInt());
  924. color = QColor(query.value(4).toString());
  925. // the values
  926. int i=0;
  927. for (; i<factory.metricCount(); i++)
  928. summaryMetrics.setForSymbol(factory.metricName(i), query.value(i+5).toDouble());
  929. foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
  930. if (!context->specialFields.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
  931. QString underscored = field.name;
  932. summaryMetrics.setForSymbol(underscored.replace("_"," "), query.value(i+4).toDouble());
  933. i++;
  934. } else if (!context->specialFields.isMetric(field.name) && field.type < 3) {
  935. QString underscored = field.name;
  936. summaryMetrics.setText(underscored.replace("_"," "), query.value(i+4).toString());
  937. i++;
  938. }
  939. }
  940. }
  941. query.finish();
  942. return found;
  943. }