diff --git a/src/Core/Athlete.cpp b/src/Core/Athlete.cpp index bf1f729b4..e40adf91f 100644 --- a/src/Core/Athlete.cpp +++ b/src/Core/Athlete.cpp @@ -403,6 +403,7 @@ AthleteDirectoryStructure::AthleteDirectoryStructure(const QDir home){ athlete_logs = "logs"; athlete_quarantine = "quarantine"; athlete_planned = "planned"; + athlete_snippets = "snippets"; } @@ -429,6 +430,7 @@ AthleteDirectoryStructure::createAllSubdirs() { myhome.mkdir(athlete_temp); myhome.mkdir(athlete_quarantine); myhome.mkdir(athlete_planned); + myhome.mkdir(athlete_snippets); } @@ -448,7 +450,8 @@ AthleteDirectoryStructure::subDirsExist() { logs().exists() && temp().exists() && quarantine().exists()&& - planned().exists() + planned().exists() && + snippets().exists() ); } diff --git a/src/Core/Athlete.h b/src/Core/Athlete.h index ea400aecf..73ea7486a 100644 --- a/src/Core/Athlete.h +++ b/src/Core/Athlete.h @@ -193,6 +193,7 @@ class AthleteDirectoryStructure : public QObject { QDir temp() { return QDir(myhome.absolutePath()+"/"+athlete_temp);} QDir quarantine() { return QDir(myhome.absolutePath()+"/"+athlete_quarantine);} QDir planned() { return QDir(myhome.absolutePath()+"/"+athlete_planned);} + QDir snippets() { return QDir(myhome.absolutePath()+"/"+athlete_snippets);} QDir root() { return myhome; } // supporting functions to work with the subDirs @@ -218,6 +219,7 @@ class AthleteDirectoryStructure : public QObject { QString athlete_temp; QString athlete_quarantine; QString athlete_planned; + QString athlete_snippets; }; diff --git a/src/Core/DataFilter.cpp b/src/Core/DataFilter.cpp index 7329bf2cd..b7bddd32b 100644 --- a/src/Core/DataFilter.cpp +++ b/src/Core/DataFilter.cpp @@ -2397,7 +2397,7 @@ Result Leaf::eval(DataFilterRuntime *df, Leaf *leaf, float x, RideItem *m, RideF if (!f) return Result(0); // eek! // now run auto data processors - if (DataProcessorFactory::instance().autoProcess(f)) { + if (DataProcessorFactory::instance().autoProcess(f, "Auto", "UPDATE")) { // rideFile is now dirty! m->setDirty(true); } diff --git a/src/Core/RideCache.cpp b/src/Core/RideCache.cpp index c4d992e42..e05b49025 100644 --- a/src/Core/RideCache.cpp +++ b/src/Core/RideCache.cpp @@ -23,6 +23,7 @@ #include "RideFileCache.h" #include "RideCacheModel.h" #include "Specification.h" +#include "DataProcessor.h" #include "Route.h" @@ -296,6 +297,10 @@ RideCache::removeCurrentRide() return; } + // dataprocessor runs on "save" which is a short + // hand for add, update, delete + DataProcessorFactory::instance().autoProcess(todelete->ride(), "Save", "DELETE"); + // remove from the cache, before deleting it this is so // any aggregating functions no longer see it, when recalculating // during aride deleted operation diff --git a/src/Core/RideItem.cpp b/src/Core/RideItem.cpp index 4cad650d9..b601bcf6d 100644 --- a/src/Core/RideItem.cpp +++ b/src/Core/RideItem.cpp @@ -244,17 +244,19 @@ RideItem::setRide(RideFile *overwrite) RideFile *old = ride_; ride_ = overwrite; // overwrite - // connect up to new one - connect(ride_, SIGNAL(modified()), this, SLOT(modified())); - connect(ride_, SIGNAL(saved()), this, SLOT(saved())); - connect(ride_, SIGNAL(reverted()), this, SLOT(reverted())); + // connect up to new one - if its not null + if (ride_) { + connect(ride_, SIGNAL(modified()), this, SLOT(modified())); + connect(ride_, SIGNAL(saved()), this, SLOT(saved())); + connect(ride_, SIGNAL(reverted()), this, SLOT(reverted())); + + // update status + setDirty(true); + notifyRideDataChanged(); + } // don't bother with the old one any more - disconnect(old); - - // update status - setDirty(true); - notifyRideDataChanged(); + if (old) disconnect(old); //XXX SORRY ! memory leak XXX //XXX delete old; // now wipe it once referrers had chance to change diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 9e729921d..175f90981 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -203,6 +203,7 @@ #define GC_VERSION_USED "versionused" #define GC_SAFEEXIT "safeexit" #define GC_UPGRADE_FOLDER_SUCCESS "upgradesuccess/folder" // success tracking of folder upgrade stored on athlete level +#define GC_ATHLETE_SNIPPETID "snippetid" #define GC_LTM_LAST_DATE_RANGE "ltmwindow/lastDateRange" #define GC_LTM_AUTOFILTERS "ltmwindow/autofilters" diff --git a/src/FileIO/DataProcessor.cpp b/src/FileIO/DataProcessor.cpp index e53343477..d82ed94f7 100644 --- a/src/FileIO/DataProcessor.cpp +++ b/src/FileIO/DataProcessor.cpp @@ -41,8 +41,11 @@ DataProcessorFactory::registerProcessor(QString name, DataProcessor *processor) } bool -DataProcessorFactory::autoProcess(RideFile *ride) +DataProcessorFactory::autoProcess(RideFile *ride, QString mode, QString op) { + // mode will be either "Auto" for automatically run (at import, or data filter) + // or it will be "Save" for running just before we save + // check if autoProcess is allow at all if (!autoprocess) return false; @@ -54,8 +57,10 @@ DataProcessorFactory::autoProcess(RideFile *ride) while (i.hasNext()) { i.next(); QString configsetting = QString("dp/%1/apply").arg(i.key()); - if (appsettings->value(NULL, GC_QSETTINGS_GLOBAL_GENERAL+configsetting, "Manual").toString() == "Auto") - i.value()->postProcess(ride); + + // if we're being run manually, run all that are defined + if (appsettings->value(NULL, GC_QSETTINGS_GLOBAL_GENERAL+configsetting, "Manual").toString() == mode) + i.value()->postProcess(ride, NULL, op); } return changed; @@ -117,7 +122,7 @@ ManualDataProcessorDialog::okClicked() QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - if (ride && ride->ride() && processor->postProcess((RideFile *)ride->ride(), config) == true) { + if (ride && ride->ride() && processor->postProcess((RideFile *)ride->ride(), config, "UPDATE") == true) { context->notifyRideSelected(ride); // to remain compatible with rest of GC for now } diff --git a/src/FileIO/DataProcessor.h b/src/FileIO/DataProcessor.h index a0a4814e1..4e9d38528 100644 --- a/src/FileIO/DataProcessor.h +++ b/src/FileIO/DataProcessor.h @@ -75,7 +75,7 @@ class DataProcessor public: DataProcessor() {} virtual ~DataProcessor() {} - virtual bool postProcess(RideFile *, DataProcessorConfig*settings=0) = 0; + virtual bool postProcess(RideFile *, DataProcessorConfig*settings=0, QString op="") = 0; virtual DataProcessorConfig *processorConfig(QWidget *parent) = 0; virtual QString name() = 0; // Localized Name for user interface }; @@ -96,7 +96,7 @@ class DataProcessorFactory { static DataProcessorFactory &instance(); bool registerProcessor(QString name, DataProcessor *processor); QMap getProcessors() const { return processors; } - bool autoProcess(RideFile *); // run auto processes (after open rideFile) + bool autoProcess(RideFile *, QString mode, QString op); // run auto processes (after open rideFile) void setAutoProcessRule(bool b) { autoprocess = b; } // allows to switch autoprocess off (e.g. for Upgrades) }; diff --git a/src/FileIO/FixDeriveDistance.cpp b/src/FileIO/FixDeriveDistance.cpp index a623059c2..f053b2aa1 100644 --- a/src/FileIO/FixDeriveDistance.cpp +++ b/src/FileIO/FixDeriveDistance.cpp @@ -85,7 +85,7 @@ class FixDeriveDistance : public DataProcessor { ~FixDeriveDistance() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -105,7 +105,7 @@ double _deg2rad(double deg) { } bool -FixDeriveDistance::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixDeriveDistance::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); diff --git a/src/FileIO/FixDeriveHeadwind.cpp b/src/FileIO/FixDeriveHeadwind.cpp index 29d4762c1..24c5b838e 100644 --- a/src/FileIO/FixDeriveHeadwind.cpp +++ b/src/FileIO/FixDeriveHeadwind.cpp @@ -71,7 +71,7 @@ class FixDeriveHeadwind : public DataProcessor { ~FixDeriveHeadwind() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -87,7 +87,7 @@ class FixDeriveHeadwind : public DataProcessor { static bool FixDeriveHeadwindAdded = DataProcessorFactory::instance().registerProcessor(QString("Estimate Headwind Values"), new FixDeriveHeadwind()); bool -FixDeriveHeadwind::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixDeriveHeadwind::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); diff --git a/src/FileIO/FixDerivePower.cpp b/src/FileIO/FixDerivePower.cpp index 484d6ced6..17aff286f 100644 --- a/src/FileIO/FixDerivePower.cpp +++ b/src/FileIO/FixDerivePower.cpp @@ -146,7 +146,7 @@ class FixDerivePower : public DataProcessor { ~FixDerivePower() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -162,7 +162,7 @@ class FixDerivePower : public DataProcessor { static bool FixDerivePowerAdded = DataProcessorFactory::instance().registerProcessor(QString("Estimate Power Values"), new FixDerivePower()); bool -FixDerivePower::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixDerivePower::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // get settings double MBik; // Bike weight kg diff --git a/src/FileIO/FixDeriveTorque.cpp b/src/FileIO/FixDeriveTorque.cpp index 2a95c1c74..7ae1ddddd 100644 --- a/src/FileIO/FixDeriveTorque.cpp +++ b/src/FileIO/FixDeriveTorque.cpp @@ -85,7 +85,7 @@ class FixDeriveTorque : public DataProcessor { ~FixDeriveTorque() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -101,7 +101,7 @@ class FixDeriveTorque : public DataProcessor { static bool FixDeriveTorqueAdded = DataProcessorFactory::instance().registerProcessor(QString("Add Torque Values"), new FixDeriveTorque()); bool -FixDeriveTorque::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixDeriveTorque::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); static const double PI = 3.1415927f; diff --git a/src/FileIO/FixElevation.cpp b/src/FileIO/FixElevation.cpp index 5018df0f0..64d63a8be 100644 --- a/src/FileIO/FixElevation.cpp +++ b/src/FileIO/FixElevation.cpp @@ -83,7 +83,7 @@ class FixElevation : public DataProcessor { ~FixElevation() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -99,7 +99,7 @@ class FixElevation : public DataProcessor { static bool fixElevationAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Elevation errors"), new FixElevation()); bool -FixElevation::postProcess(RideFile *ride, DataProcessorConfig *) +FixElevation::postProcess(RideFile *ride, DataProcessorConfig *, QString op="") { // Cannot process without without GPS data if (!ride || ride->areDataPresent()->lat == false || ride->areDataPresent()->lon == false) diff --git a/src/FileIO/FixFreewheeling.cpp b/src/FileIO/FixFreewheeling.cpp index 7e1f7b83d..86d4052a4 100644 --- a/src/FileIO/FixFreewheeling.cpp +++ b/src/FileIO/FixFreewheeling.cpp @@ -68,7 +68,7 @@ class FixFreewheeling : public DataProcessor { ~FixFreewheeling() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -84,7 +84,7 @@ class FixFreewheeling : public DataProcessor { static bool FixFreewheelingAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Freewheeling"), new FixFreewheeling()); bool -FixFreewheeling::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixFreewheeling::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); diff --git a/src/FileIO/FixGPS.cpp b/src/FileIO/FixGPS.cpp index d5ed76b79..41bb6b425 100644 --- a/src/FileIO/FixGPS.cpp +++ b/src/FileIO/FixGPS.cpp @@ -61,7 +61,7 @@ class FixGPS : public DataProcessor { ~FixGPS() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -77,7 +77,7 @@ class FixGPS : public DataProcessor { static bool fixGPSAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix GPS errors"), new FixGPS()); bool -FixGPS::postProcess(RideFile *ride, DataProcessorConfig *) +FixGPS::postProcess(RideFile *ride, DataProcessorConfig *, QString op) { // ignore null or files without GPS data if (!ride || ride->areDataPresent()->lat == false || ride->areDataPresent()->lon == false) diff --git a/src/FileIO/FixGaps.cpp b/src/FileIO/FixGaps.cpp index f6dbd9958..0608983c7 100644 --- a/src/FileIO/FixGaps.cpp +++ b/src/FileIO/FixGaps.cpp @@ -118,7 +118,7 @@ class FixGaps : public DataProcessor { ~FixGaps() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -134,7 +134,7 @@ class FixGaps : public DataProcessor { static bool fixGapsAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Gaps in Recording"), new FixGaps()); bool -FixGaps::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixGaps::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // get settings double tolerance, stop; diff --git a/src/FileIO/FixHRSpikes.cpp b/src/FileIO/FixHRSpikes.cpp index 2b84afdd7..e43c989aa 100644 --- a/src/FileIO/FixHRSpikes.cpp +++ b/src/FileIO/FixHRSpikes.cpp @@ -103,7 +103,7 @@ class FixHRSpikes : public DataProcessor { ~FixHRSpikes() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -119,7 +119,7 @@ class FixHRSpikes : public DataProcessor { static bool fixHRSpikesAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix HR Spikes"), new FixHRSpikes()); bool -FixHRSpikes::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixHRSpikes::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // does this ride have heart rate data? if (ride->areDataPresent()->hr == false) return false; diff --git a/src/FileIO/FixLapSwim.cpp b/src/FileIO/FixLapSwim.cpp index f5cd344fb..21db018f8 100644 --- a/src/FileIO/FixLapSwim.cpp +++ b/src/FileIO/FixLapSwim.cpp @@ -98,7 +98,7 @@ class FixLapSwim : public DataProcessor { ~FixLapSwim() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -114,7 +114,7 @@ class FixLapSwim : public DataProcessor { static bool FixLapSwimAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Lap Swim"), new FixLapSwim()); bool -FixLapSwim::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixLapSwim::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // get settings double pl; diff --git a/src/FileIO/FixMoxy.cpp b/src/FileIO/FixMoxy.cpp index 3788a7d30..e45937e3c 100644 --- a/src/FileIO/FixMoxy.cpp +++ b/src/FileIO/FixMoxy.cpp @@ -97,7 +97,7 @@ class FixMoxy : public DataProcessor { ~FixMoxy() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -113,7 +113,7 @@ class FixMoxy : public DataProcessor { static bool FixMoxyAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Moxy"), new FixMoxy()); bool -FixMoxy::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixMoxy::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); bool isCad; diff --git a/src/FileIO/FixPower.cpp b/src/FileIO/FixPower.cpp index 3757e297a..29c0eee66 100644 --- a/src/FileIO/FixPower.cpp +++ b/src/FileIO/FixPower.cpp @@ -94,7 +94,7 @@ class FixPower : public DataProcessor { ~FixPower() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -110,7 +110,7 @@ class FixPower : public DataProcessor { static bool FixPowerAdded = DataProcessorFactory::instance().registerProcessor(QString("Adjust Power Values"), new FixPower()); bool -FixPower::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixPower::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // Lets do it then! QString tp; diff --git a/src/FileIO/FixRunningCadence.cpp b/src/FileIO/FixRunningCadence.cpp index 256c84004..22bcbb692 100644 --- a/src/FileIO/FixRunningCadence.cpp +++ b/src/FileIO/FixRunningCadence.cpp @@ -64,7 +64,7 @@ class FixRunningCadence : public DataProcessor { ~FixRunningCadence() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -80,7 +80,7 @@ class FixRunningCadence : public DataProcessor { static bool FixRunningCadenceAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Running Cadence"), new FixRunningCadence()); bool -FixRunningCadence::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixRunningCadence::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); // does this ride have cadence? diff --git a/src/FileIO/FixRunningPower.cpp b/src/FileIO/FixRunningPower.cpp index 8b1391a07..6541f053e 100644 --- a/src/FileIO/FixRunningPower.cpp +++ b/src/FileIO/FixRunningPower.cpp @@ -148,7 +148,7 @@ class FixRunningPower : public DataProcessor { ~FixRunningPower() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -164,7 +164,7 @@ class FixRunningPower : public DataProcessor { static bool FixRunningPowerAdded = DataProcessorFactory::instance().registerProcessor(QString("Estimate Running Power"), new FixRunningPower()); bool -FixRunningPower::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixRunningPower::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // get settings double MEquip; // Equipment weight kg diff --git a/src/FileIO/FixSmO2.cpp b/src/FileIO/FixSmO2.cpp index 13717c5b7..0d7c61022 100644 --- a/src/FileIO/FixSmO2.cpp +++ b/src/FileIO/FixSmO2.cpp @@ -68,7 +68,7 @@ class FixSmO2 : public DataProcessor { ~FixSmO2() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -84,7 +84,7 @@ class FixSmO2 : public DataProcessor { static bool fixSmO2Added = DataProcessorFactory::instance().registerProcessor(QString("Fix SmO2 Anomaly"), new FixSmO2()); bool -FixSmO2::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixSmO2::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { Q_UNUSED(config); // does this ride have power? diff --git a/src/FileIO/FixSpeed.cpp b/src/FileIO/FixSpeed.cpp index bbe49c4fe..c17e74426 100644 --- a/src/FileIO/FixSpeed.cpp +++ b/src/FileIO/FixSpeed.cpp @@ -94,7 +94,7 @@ class FixSpeed : public DataProcessor { ~FixSpeed() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -110,7 +110,7 @@ class FixSpeed : public DataProcessor { static bool FixSpeedAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Speed"), new FixSpeed()); bool -FixSpeed::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixSpeed::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // get settings int ma; diff --git a/src/FileIO/FixSpikes.cpp b/src/FileIO/FixSpikes.cpp index 94f0134b5..38b81d38c 100644 --- a/src/FileIO/FixSpikes.cpp +++ b/src/FileIO/FixSpikes.cpp @@ -119,7 +119,7 @@ class FixSpikes : public DataProcessor { ~FixSpikes() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -135,7 +135,7 @@ class FixSpikes : public DataProcessor { static bool fixSpikesAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Power Spikes"), new FixSpikes()); bool -FixSpikes::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixSpikes::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // does this ride have power? if (ride->areDataPresent()->watts == false) return false; diff --git a/src/FileIO/FixTorque.cpp b/src/FileIO/FixTorque.cpp index 2a2bf6d55..ba640bd55 100644 --- a/src/FileIO/FixTorque.cpp +++ b/src/FileIO/FixTorque.cpp @@ -92,7 +92,7 @@ class FixTorque : public DataProcessor { ~FixTorque() {} // the processor - bool postProcess(RideFile *, DataProcessorConfig* config); + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); // the config widget DataProcessorConfig* processorConfig(QWidget *parent) { @@ -108,7 +108,7 @@ class FixTorque : public DataProcessor { static bool fixTorqueAdded = DataProcessorFactory::instance().registerProcessor(QString("Adjust Torque Values"), new FixTorque()); bool -FixTorque::postProcess(RideFile *ride, DataProcessorConfig *config=0) +FixTorque::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") { // does this ride have torque? if (ride->areDataPresent()->nm == false) return false; diff --git a/src/FileIO/Snippets.cpp b/src/FileIO/Snippets.cpp new file mode 100644 index 000000000..73b4add24 --- /dev/null +++ b/src/FileIO/Snippets.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2016 Mark Liversedge (liversedge@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "DataProcessor.h" +#include "Athlete.h" +#include "Context.h" +#include "Settings.h" +#include "Units.h" +#include "HelpWhatsThis.h" +#include +#include + +// Config widget used by the Preferences/Options config panes +class Snippets; +class SnippetsConfig : public DataProcessorConfig +{ + Q_DECLARE_TR_FUNCTIONS(SnippetsConfig) + + friend class ::Snippets; + protected: + + public: + SnippetsConfig(QWidget *parent) : DataProcessorConfig(parent) { + + //HelpWhatsThis *help = new HelpWhatsThis(parent); + //parent->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::MenuBar_Edit_SnippetsInRecording)); + } + + QString explain() { + return(QString(tr("Dump metrics for the ride to Athlete_Home/Snippets"))); + } + + void readConfig() { + } + + void saveConfig() { + } +}; + + +// RideFile Dataprocessor -- dumps a journal file of metrics to the snippets folder +// useful to record metric changes as data is edited +// typically used by external databases +// +class Snippets : public DataProcessor { + Q_DECLARE_TR_FUNCTIONS(Snippets) + + public: + Snippets() {} + ~Snippets() {} + + // the processor + bool postProcess(RideFile *, DataProcessorConfig* config, QString op); + + // the config widget + DataProcessorConfig* processorConfig(QWidget *parent) { + return new SnippetsConfig(parent); + } + + // Localized Name + QString name() { + return (tr("Snippet export")); + } +}; + +static bool SnippetsAdded = DataProcessorFactory::instance().registerProcessor(QString("Snippet export"), new Snippets()); + +// how we write the ride time +#define DATETIME_FORMAT "yyyy/MM/dd hh:mm:ss' UTC'" + +// Escape special characters (JSON compliance) +static QString protect(const QString string) +{ + QString s = string; + s.replace("\\", "\\\\"); // backslash + s.replace("\"", "\\\""); // quote + s.replace("\t", "\\t"); // tab + s.replace("\n", "\\n"); // newline + s.replace("\r", "\\r"); // carriage-return + s.replace("\b", "\\b"); // backspace + s.replace("\f", "\\f"); // formfeed + s.replace("/", "\\/"); // solidus + + // add a trailing space to avoid conflicting with GC special tokens + s += " "; + + return s; +} + +bool +Snippets::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="") +{ + Q_UNUSED(config); + + // create a snippet file if its not null + if (!ride) return false; + + // whats the next counter? + qint64 journalid = appsettings->cvalue(ride->context->athlete->cyclist, GC_ATHLETE_SNIPPETID, 0).toLongLong(); + journalid ++; + QString filename = ride->context->athlete->home->snippets().absolutePath() + QString("/%1.json").arg(journalid, 9, 10, QChar('0')); + QFile outfile(filename); + + // open the file, fails are graceful but silent + if (outfile.open(QIODevice::WriteOnly)) { + + // setup streamer + QTextStream out(&outfile); + // unified codepage and BOM for identification on all platforms + out.setCodec("UTF-8"); + //out.setGenerateByteOrderMark(true); << make it easier to parse with no BOM + + out << "{\n\t\"" << op << "\": {\n"; + + // + // FIRST CLASS MEMBERS (IDENTIFIERS etc) + // + out << "\t\t\"STARTTIME\": \"" << protect(ride->startTime().toUTC().toString(DATETIME_FORMAT)) << "\",\n"; + out << "\t\t\"RECINTSECS\": " << ride->recIntSecs() << ",\n"; + out << "\t\t\"DEVICETYPE\": \"" << protect(ride->deviceType()) << "\",\n"; + out << "\t\t\"IDENTIFIER\": \"" << protect(ride->id()) << "\""; + + // + // TAGS + // + if (ride->tags().count()) { + + out << ",\n\t\t\"TAGS\": {\n"; + + QMap::const_iterator i; + for (i=ride->tags().constBegin(); i != ride->tags().constEnd(); i++) { + + out << "\t\t\t\"" << i.key() << "\": \"" << protect(i.value()) << "\""; + if (i+1 != ride->tags().constEnd()) out << ",\n"; + else out << "\n"; + } + + // end of the tags + out << "\t\t}"; + } + + // + // METRICS + // + + out << ",\n\t\t\"METRICS\": {\n"; + + // calculate metrics - we need to do this ourselves as we are working + // with ride data, not a rideitem, so we manufacture a rideitem too. sigh. + RideItem rideItem(ride, ride->context); + const RideMetricFactory &factory = RideMetricFactory::instance(); + QHash computed= RideMetric::computeMetrics(&rideItem, Specification(), factory.allMetrics()); + + // write them out + bool first = true; + QHashIterator i(computed); + while (i.hasNext()) { + i.next(); + double v =i.value()->value(); + + // clean bad values - we always write all metrics + if (std::isinf(v) || std::isnan(v)) v = 0; + + if (!first) out << ",\n"; + out << "\t\t\t\"" << i.value()->name() << "\": \"" << QString("%1").arg(v) << "\""; + + first = false; + } + out << "\n\t\t}"; + + // all done + out << "\n\t}\n}\n"; + + // before rideitem goes out of scope set ride to NULL + // so it doesn't get deleted ! + rideItem.setRide(NULL); + + // save the last journalid + appsettings->setCValue(ride->context->athlete->cyclist, GC_ATHLETE_SNIPPETID, journalid); + + // close the file + outfile.close(); + } + return true; +} diff --git a/src/Gui/Pages.cpp b/src/Gui/Pages.cpp index ab2d3ca71..36204e783 100644 --- a/src/Gui/Pages.cpp +++ b/src/Gui/Pages.cpp @@ -3646,11 +3646,14 @@ ProcessorPage::ProcessorPage(Context *context) : context(context) QComboBox *comboButton = new QComboBox(this); comboButton->addItem(tr("Manual")); comboButton->addItem(tr("Import")); + comboButton->addItem(tr("Save")); processorTree->setItemWidget(add, 1, comboButton); QString configsetting = QString("dp/%1/apply").arg(i.key()); if (appsettings->value(NULL, GC_QSETTINGS_GLOBAL_GENERAL+configsetting, "Manual").toString() == "Manual") comboButton->setCurrentIndex(0); + else if (appsettings->value(NULL, GC_QSETTINGS_GLOBAL_GENERAL+configsetting, "Save").toString() == "Save") + comboButton->setCurrentIndex(2); else comboButton->setCurrentIndex(1); @@ -3674,8 +3677,16 @@ ProcessorPage::saveClicked() for (int i=0; iinvisibleRootItem()->childCount(); i++) { // auto (which means run on import) or manual? QString configsetting = QString("dp/%1/apply").arg(processorTree->invisibleRootItem()->child(i)->text(3)); - QString apply = ((QComboBox*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 1)))->currentIndex() ? - "Auto" : "Manual"; + QString apply; + + // which mode is selected? + switch(((QComboBox*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 1)))->currentIndex()) { + default: + case 0: apply = "Manual"; break; + case 1: apply = "Auto"; break; + case 2: apply = "Save"; break; + } + appsettings->setValue(GC_QSETTINGS_GLOBAL_GENERAL+configsetting, apply); ((DataProcessorConfig*)(processorTree->itemWidget(processorTree->invisibleRootItem()->child(i), 2)))->saveConfig(); } diff --git a/src/Gui/RideImportWizard.cpp b/src/Gui/RideImportWizard.cpp index 5f033da10..72ae3bedf 100644 --- a/src/Gui/RideImportWizard.cpp +++ b/src/Gui/RideImportWizard.cpp @@ -1018,8 +1018,8 @@ RideImportWizard::abortClicked() // process linked defaults context->athlete->rideMetadata()->setLinkedDefaults(ride); - // run the processor first... - DataProcessorFactory::instance().autoProcess(ride); + // run the processor first... import + DataProcessorFactory::instance().autoProcess(ride, "Auto", "Import"); ride->recalculateDerivedSeries(); // serialize @@ -1050,6 +1050,9 @@ RideImportWizard::abortClicked() tableWidget->item(i,5)->setText(tr("Error - Import of activitiy file failed")); } + // now metrics have been calculated + DataProcessorFactory::instance().autoProcess(ride, "Save", "ADD"); + // clear delete ride; diff --git a/src/Gui/SaveDialogs.cpp b/src/Gui/SaveDialogs.cpp index 68e7a1c48..0042d79ae 100644 --- a/src/Gui/SaveDialogs.cpp +++ b/src/Gui/SaveDialogs.cpp @@ -26,6 +26,7 @@ #include "RideFileCommand.h" #include "Settings.h" #include "SaveDialogs.h" +#include "DataProcessor.h" //---------------------------------------------------------------------- // Utility functions to get and set WARN on CONVERT application setting @@ -168,6 +169,9 @@ MainWindow::saveSilent(Context *context, RideItem *rideItem) savedFile.setFileName(currentFile.fileName()); } + // run the data processors configured to run "on save" + DataProcessorFactory::instance().autoProcess(rideItem->ride(), "Save", "UPDATE"); + // update the change history QString log = rideItem->ride()->getTag("Change History", ""); log += tr("Changes on "); diff --git a/src/Metrics/WPrime.cpp b/src/Metrics/WPrime.cpp index e67c7654d..180928129 100644 --- a/src/Metrics/WPrime.cpp +++ b/src/Metrics/WPrime.cpp @@ -1287,7 +1287,7 @@ class WCPZoneTime4 : public WCPZoneTime { } void initialize () { - setName(tr("W4 W'bal Severe Fatigue")); + setName(tr("W4 Above CP W'bal Severe Fatigue")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); setDescription(tr("Time expended when Power is above CP and W' bal is above 75% of W'.")); diff --git a/src/src.pro b/src/src.pro index 7571ad877..46110a2d0 100644 --- a/src/src.pro +++ b/src/src.pro @@ -740,7 +740,7 @@ SOURCES += FileIO/AthleteBackup.cpp FileIO/Bin2RideFile.cpp FileIO/BinRideFile.c FileIO/QuarqRideFile.cpp FileIO/RawRideFile.cpp FileIO/RideAutoImportConfig.cpp \ FileIO/RideFileCache.cpp FileIO/RideFileCommand.cpp FileIO/RideFile.cpp FileIO/RideFileTableModel.cpp \ FileIO/Serial.cpp FileIO/SlfParser.cpp FileIO/SlfRideFile.cpp FileIO/SmfParser.cpp FileIO/SmfRideFile.cpp FileIO/SmlParser.cpp \ - FileIO/SmlRideFile.cpp FileIO/SrdRideFile.cpp FileIO/SrmRideFile.cpp FileIO/SyncRideFile.cpp \ + FileIO/SmlRideFile.cpp FileIO/Snippets.cpp FileIO/SrdRideFile.cpp FileIO/SrmRideFile.cpp FileIO/SyncRideFile.cpp \ FileIO/TacxCafRideFile.cpp FileIO/TcxParser.cpp FileIO/TcxRideFile.cpp FileIO/TxtRideFile.cpp FileIO/WkoRideFile.cpp \ FileIO/XDataDialog.cpp FileIO/XDataTableModel.cpp