From 67136b3cdf804df80984d35f9fec88845af87278 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Date: Sun, 5 Mar 2023 16:26:25 -0300 Subject: [PATCH] Enable Python DPs use Metadata in automatic mode (#4336) Data Processors running on import are applied before the activity is added to RideCache and metrics are computed, this behavior is by design, likely to optimize resource usage on bulk import. So activityMetrics API is not available; a new getTag API was added for this case and setTag/delTag/hasTag changed to work in this context too. When the Python fix is executed on activities already in the cache either via Edit menu, Filters or other Python Fix changes are notified via the corresponding RideItem. Fixes #4095 [publish binaries] --- src/Charts/PythonChart.cpp | 2 +- src/FileIO/FixPyDataProcessor.cpp | 13 +- src/FileIO/FixPyRunner.cpp | 7 +- src/FileIO/FixPyRunner.h | 4 +- src/Python/PythonEmbed.h | 4 +- src/Python/SIP/Bindings.cpp | 80 +++++++----- src/Python/SIP/Bindings.h | 1 + src/Python/SIP/goldencheetah.sip | 1 + src/Python/SIP/sipAPIgoldencheetah.h | 130 ++++++++++---------- src/Python/SIP/sipgoldencheetahBindings.cpp | 37 +++++- src/Python/SIP/sipgoldencheetahcmodule.cpp | 1 + 11 files changed, 178 insertions(+), 102 deletions(-) diff --git a/src/Charts/PythonChart.cpp b/src/Charts/PythonChart.cpp index f47065a49..1c9b1442f 100644 --- a/src/Charts/PythonChart.cpp +++ b/src/Charts/PythonChart.cpp @@ -202,7 +202,7 @@ void PythonConsole::keyPressEvent(QKeyEvent *e) bool readOnly = pythonHost->readOnly(); QList editedRideFiles; python->cancelled = false; - python->runline(ScriptContext(context, nullptr, true, readOnly, &editedRideFiles), line); + python->runline(ScriptContext(context, nullptr, nullptr, true, readOnly, &editedRideFiles), line); // finish up commands on edited rides foreach (RideFile *f, editedRideFiles) { diff --git a/src/FileIO/FixPyDataProcessor.cpp b/src/FileIO/FixPyDataProcessor.cpp index b965cefbe..61504fbd7 100644 --- a/src/FileIO/FixPyDataProcessor.cpp +++ b/src/FileIO/FixPyDataProcessor.cpp @@ -1,5 +1,6 @@ #include "FixPyDataProcessor.h" #include "FixPyRunner.h" +#include "Athlete.h" // Config widget used by the Preferences/Options config panes class FixPyDataProcessorConfig : public DataProcessorConfig @@ -24,7 +25,17 @@ bool FixPyDataProcessor::postProcess(RideFile *rideFile, DataProcessorConfig *se QString errText; bool useNewThread = op != "PYTHON"; Context* context = (rideFile) ? rideFile->context : nullptr; - FixPyRunner pyRunner(context, rideFile, useNewThread); + RideItem* rideItem = nullptr; + if (context && rideFile) { + // get RideItem from RideFile for Python functions using it + foreach(RideItem *item, context->athlete->rideCache->rides()) { + if (item->dateTime == rideFile->startTime()) { + rideItem = item; + break; + } + } + } + FixPyRunner pyRunner(context, rideFile, rideItem, useNewThread); return pyRunner.run(pyScript->source, pyScript->iniKey, errText) == 0; } diff --git a/src/FileIO/FixPyRunner.cpp b/src/FileIO/FixPyRunner.cpp index 8afb89246..2afe47d42 100644 --- a/src/FileIO/FixPyRunner.cpp +++ b/src/FileIO/FixPyRunner.cpp @@ -5,8 +5,8 @@ #include "PythonEmbed.h" #include "RideFileCommand.h" -FixPyRunner::FixPyRunner(Context *context, RideFile *rideFile, bool useNewThread) - : context(context), rideFile(rideFile), useNewThread(useNewThread) +FixPyRunner::FixPyRunner(Context *context, RideFile *rideFile, RideItem *rideItem, bool useNewThread) + : context(context), rideFile(rideFile), rideItem(rideItem), useNewThread(useNewThread) { } @@ -34,6 +34,7 @@ int FixPyRunner::run(QString source, QString scriptKey, QString &errText) FixPyRunParams params; params.context = context; params.rideFile = rideFile; + params.rideItem = rideItem; params.script = QString(line); if (useNewThread) { @@ -75,7 +76,7 @@ void FixPyRunner::execScript(FixPyRunParams *params) QList editedRideFiles; python->canvas = NULL; python->chart = NULL; - python->runline(ScriptContext(params->context, params->rideFile, false, + python->runline(ScriptContext(params->context, params->rideFile, params->rideItem, false, false, &editedRideFiles), params->script); // finish up commands on edited rides diff --git a/src/FileIO/FixPyRunner.h b/src/FileIO/FixPyRunner.h index 3fb125317..431b2b457 100644 --- a/src/FileIO/FixPyRunner.h +++ b/src/FileIO/FixPyRunner.h @@ -11,6 +11,7 @@ struct FixPyRunParams { Context *context; RideFile *rideFile; + RideItem *rideItem; QString script; }; @@ -19,7 +20,7 @@ class FixPyRunner : public QObject Q_OBJECT public: - FixPyRunner(Context *context = nullptr, RideFile *rideFile = nullptr, bool useNewThread = true); + FixPyRunner(Context *context = nullptr, RideFile *rideFile = nullptr, RideItem *rideItem = nullptr, bool useNewThread = true); int run(QString source, QString scriptKey, QString &errText); static void execScript(FixPyRunParams *params); @@ -27,6 +28,7 @@ public: private: Context *context; RideFile *rideFile; + RideItem *rideItem; bool useNewThread; }; diff --git a/src/Python/PythonEmbed.h b/src/Python/PythonEmbed.h index bfb9c8ec9..e69ab6807 100644 --- a/src/Python/PythonEmbed.h +++ b/src/Python/PythonEmbed.h @@ -43,9 +43,9 @@ class ScriptContext { interactiveShell(interactiveShell), readOnly(true), editedRideFiles(NULL) {} // read/write ctor - ScriptContext(Context *context, RideFile *rideFile, bool interactiveShell, + ScriptContext(Context *context, RideFile *rideFile, RideItem *item, bool interactiveShell, bool readOnly, QList *editedRideFiles) - : context(context), item(NULL), rideFile(rideFile), metrics(NULL), spec(), + : context(context), item(item), rideFile(rideFile), metrics(NULL), spec(), interactiveShell(interactiveShell), readOnly(readOnly), editedRideFiles(editedRideFiles) {} // default ctor diff --git a/src/Python/SIP/Bindings.cpp b/src/Python/SIP/Bindings.cpp index d777b0c02..5daa9706c 100644 --- a/src/Python/SIP/Bindings.cpp +++ b/src/Python/SIP/Bindings.cpp @@ -1630,11 +1630,7 @@ Bindings::setTag(QString name, QString value, PyObject *activity) const Context *context = python->contexts.value(threadid()).context; if (context == nullptr) return false; - RideItem* m = fromDateTime(activity); - if (m == nullptr) m = const_cast(context->currentRideItem()); - if (m == nullptr) return false; - - RideFile *f = m->ride(); + RideFile *f = selectRideFile(activity); if (f == nullptr) return false; name = name.replace("_"," "); @@ -1656,10 +1652,16 @@ Bindings::setTag(QString name, QString value, PyObject *activity) const f->setTag(name, value); } - // rideFile is now dirty! - m->setDirty(true); - // get refresh done, coz overrides state has changed - m->notifyRideMetadataChanged(); + // Notify changes if activity is already in rideCache + RideItem* m = fromDateTime(activity); + if (m == nullptr) m = python->contexts.value(threadid()).item; + if (m == nullptr) m = context->rideItem(); + if (m && m->dateTime == f->startTime()) { + // rideFile is now dirty! + m->setDirty(true); + // get refresh done, coz overrides state has changed + m->notifyRideMetadataChanged(); + } return true; } @@ -1673,23 +1675,25 @@ Bindings::delTag(QString name, PyObject *activity) const Context *context = python->contexts.value(threadid()).context; if (context == nullptr) return false; - RideItem* m = fromDateTime(activity); - if (m == nullptr) m = const_cast(context->currentRideItem()); - if (m == nullptr) return false; - - RideFile *f = m->ride(); + RideFile *f = selectRideFile(activity); if (f == nullptr) return false; + RideItem* m = fromDateTime(activity); + if (m == nullptr) m = python->contexts.value(threadid()).item; + if (m == nullptr) m = context->rideItem(); + name = name.replace("_"," "); if (GlobalContext::context()->specialFields.isMetric(name)) { if (f->metricOverrides.remove(GlobalContext::context()->specialFields.metricSymbol(name))) { - // rideFile is now dirty! - m->setDirty(true); - // get refresh done, coz overrides state has changed - m->notifyRideMetadataChanged(); - + // Notify changes if activity is already in rideCache + if (m && m->dateTime == f->startTime()) { + // rideFile is now dirty! + m->setDirty(true); + // get refresh done, coz overrides state has changed + m->notifyRideMetadataChanged(); + } return true; } return false; @@ -1698,11 +1702,13 @@ Bindings::delTag(QString name, PyObject *activity) const if (f->removeTag(name)) { - // rideFile is now dirty! - m->setDirty(true); - // get refresh done, coz overrides state has changed - m->notifyRideMetadataChanged(); - + // Notify changes if activity is already in rideCache + if (m && m->dateTime == f->startTime()) { + // rideFile is now dirty! + m->setDirty(true); + // get refresh done, coz overrides state has changed + m->notifyRideMetadataChanged(); + } return true; } return false; @@ -1715,15 +1721,31 @@ Bindings::hasTag(QString name, PyObject *activity) const Context *context = python->contexts.value(threadid()).context; if (context == nullptr) return false; - RideItem* m = fromDateTime(activity); - if (m == nullptr) m = const_cast(context->currentRideItem()); - if (m == nullptr) return false; + RideFile *f = selectRideFile(activity); + if (f == nullptr) return false; name = name.replace("_"," "); if (GlobalContext::context()->specialFields.isMetric(name)) { - return m->overrides_.contains(GlobalContext::context()->specialFields.metricSymbol(name)); + return f->metricOverrides.contains(GlobalContext::context()->specialFields.metricSymbol(name)); } else { - return m->hasText(name); + return f->tags().contains(name); + } +} + +QString +Bindings::getTag(QString name, PyObject *activity) const +{ + Context *context = python->contexts.value(threadid()).context; + if (context == nullptr) return QString(); + + RideFile *f = selectRideFile(activity); + if (f == nullptr) return QString(); + + name = name.replace("_"," "); + if (GlobalContext::context()->specialFields.isMetric(name)) { + return f->metricOverrides[GlobalContext::context()->specialFields.metricSymbol(name)]["value"]; + } else { + return f->getTag(name, ""); } } diff --git a/src/Python/SIP/Bindings.h b/src/Python/SIP/Bindings.h index 78be092ca..24679ca65 100644 --- a/src/Python/SIP/Bindings.h +++ b/src/Python/SIP/Bindings.h @@ -119,6 +119,7 @@ class Bindings { bool setTag(QString name, QString value, PyObject *activity = NULL) const; bool delTag(QString name, PyObject *activity = NULL) const; bool hasTag(QString name, PyObject *activity = NULL) const; + QString getTag(QString name, PyObject *activity = NULL) const; // working with charts bool configChart(QString title, int type, bool animate, int pos, bool stack, int orientation) const; diff --git a/src/Python/SIP/goldencheetah.sip b/src/Python/SIP/goldencheetah.sip index 78e68049a..5b64db02d 100644 --- a/src/Python/SIP/goldencheetah.sip +++ b/src/Python/SIP/goldencheetah.sip @@ -390,6 +390,7 @@ public: bool setTag(QString name, QString value, PyObject *activity = NULL) const; bool delTag(QString name, PyObject *activity = NULL) const; bool hasTag(QString name, PyObject *activity = NULL) const; + QString getTag(QString name, PyObject *activity = NULL) const; // working with qt charts bool configChart(QString title, int type, bool animate, int legpos, bool stack, int orientation) const; diff --git a/src/Python/SIP/sipAPIgoldencheetah.h b/src/Python/SIP/sipAPIgoldencheetah.h index 036a07cea..bbe12ccd8 100644 --- a/src/Python/SIP/sipAPIgoldencheetah.h +++ b/src/Python/SIP/sipAPIgoldencheetah.h @@ -139,76 +139,78 @@ #define sipName_labels &sipStrings_goldencheetah[410] #define sipNameNr_legpos 688 #define sipName_legpos &sipStrings_goldencheetah[688] -#define sipNameNr_hasTag 695 -#define sipName_hasTag &sipStrings_goldencheetah[695] -#define sipNameNr_delTag 702 -#define sipName_delTag &sipStrings_goldencheetah[702] -#define sipNameNr_setTag 709 -#define sipName_setTag &sipStrings_goldencheetah[709] -#define sipNameNr_metric 716 -#define sipName_metric &sipStrings_goldencheetah[716] +#define sipNameNr_getTag 695 +#define sipName_getTag &sipStrings_goldencheetah[695] +#define sipNameNr_hasTag 702 +#define sipName_hasTag &sipStrings_goldencheetah[702] +#define sipNameNr_delTag 709 +#define sipName_delTag &sipStrings_goldencheetah[709] +#define sipNameNr_setTag 716 +#define sipName_setTag &sipStrings_goldencheetah[716] +#define sipNameNr_metric 723 +#define sipName_metric &sipStrings_goldencheetah[723] #define sipNameNr_series 565 #define sipName_series &sipStrings_goldencheetah[565] -#define sipNameNr_season 723 -#define sipName_season &sipStrings_goldencheetah[723] -#define sipNameNr_filter 730 -#define sipName_filter &sipStrings_goldencheetah[730] -#define sipNameNr_result 737 -#define sipName_result &sipStrings_goldencheetah[737] -#define sipNameNr_remove 744 -#define sipName_remove &sipStrings_goldencheetah[744] -#define sipNameNr_append 751 -#define sipName_append &sipStrings_goldencheetah[751] -#define sipNameNr_align 758 -#define sipName_align &sipStrings_goldencheetah[758] +#define sipNameNr_season 730 +#define sipName_season &sipStrings_goldencheetah[730] +#define sipNameNr_filter 737 +#define sipName_filter &sipStrings_goldencheetah[737] +#define sipNameNr_result 744 +#define sipName_result &sipStrings_goldencheetah[744] +#define sipNameNr_remove 751 +#define sipName_remove &sipStrings_goldencheetah[751] +#define sipNameNr_append 758 +#define sipName_append &sipStrings_goldencheetah[758] +#define sipNameNr_align 765 +#define sipName_align &sipStrings_goldencheetah[765] #define sipNameNr_color 400 #define sipName_color &sipStrings_goldencheetah[400] -#define sipNameNr_yname 764 -#define sipName_yname &sipStrings_goldencheetah[764] -#define sipNameNr_xname 770 -#define sipName_xname &sipStrings_goldencheetah[770] -#define sipNameNr_stack 776 -#define sipName_stack &sipStrings_goldencheetah[776] -#define sipNameNr_title 782 -#define sipName_title &sipStrings_goldencheetah[782] -#define sipNameNr_index 788 -#define sipName_index &sipStrings_goldencheetah[788] -#define sipNameNr_group 794 -#define sipName_group &sipStrings_goldencheetah[794] -#define sipNameNr_xdata 800 -#define sipName_xdata &sipStrings_goldencheetah[800] -#define sipNameNr_sport 806 -#define sipName_sport &sipStrings_goldencheetah[806] -#define sipNameNr_value 812 -#define sipName_value &sipStrings_goldencheetah[812] -#define sipNameNr_build 818 -#define sipName_build &sipStrings_goldencheetah[818] -#define sipNameNr_fill 824 -#define sipName_fill &sipStrings_goldencheetah[824] -#define sipNameNr_line 829 -#define sipName_line &sipStrings_goldencheetah[829] -#define sipNameNr_join 834 -#define sipName_join &sipStrings_goldencheetah[834] -#define sipNameNr_name 765 -#define sipName_name &sipStrings_goldencheetah[765] -#define sipNameNr_type 839 -#define sipName_type &sipStrings_goldencheetah[839] -#define sipNameNr_date 844 -#define sipName_date &sipStrings_goldencheetah[844] -#define sipNameNr_log 849 -#define sipName_log &sipStrings_goldencheetah[849] +#define sipNameNr_yname 771 +#define sipName_yname &sipStrings_goldencheetah[771] +#define sipNameNr_xname 777 +#define sipName_xname &sipStrings_goldencheetah[777] +#define sipNameNr_stack 783 +#define sipName_stack &sipStrings_goldencheetah[783] +#define sipNameNr_title 789 +#define sipName_title &sipStrings_goldencheetah[789] +#define sipNameNr_index 795 +#define sipName_index &sipStrings_goldencheetah[795] +#define sipNameNr_group 801 +#define sipName_group &sipStrings_goldencheetah[801] +#define sipNameNr_xdata 807 +#define sipName_xdata &sipStrings_goldencheetah[807] +#define sipNameNr_sport 813 +#define sipName_sport &sipStrings_goldencheetah[813] +#define sipNameNr_value 819 +#define sipName_value &sipStrings_goldencheetah[819] +#define sipNameNr_build 825 +#define sipName_build &sipStrings_goldencheetah[825] +#define sipNameNr_fill 831 +#define sipName_fill &sipStrings_goldencheetah[831] +#define sipNameNr_line 836 +#define sipName_line &sipStrings_goldencheetah[836] +#define sipNameNr_join 841 +#define sipName_join &sipStrings_goldencheetah[841] +#define sipNameNr_name 772 +#define sipName_name &sipStrings_goldencheetah[772] +#define sipNameNr_type 846 +#define sipName_type &sipStrings_goldencheetah[846] +#define sipNameNr_date 851 +#define sipName_date &sipStrings_goldencheetah[851] +#define sipNameNr_log 856 +#define sipName_log &sipStrings_goldencheetah[856] #define sipNameNr_max 120 #define sipName_max &sipStrings_goldencheetah[120] -#define sipNameNr_min 853 -#define sipName_min &sipStrings_goldencheetah[853] -#define sipNameNr_all 857 -#define sipName_all &sipStrings_goldencheetah[857] -#define sipNameNr_url 861 -#define sipName_url &sipStrings_goldencheetah[861] -#define sipNameNr_s2 865 -#define sipName_s2 &sipStrings_goldencheetah[865] -#define sipNameNr_s1 868 -#define sipName_s1 &sipStrings_goldencheetah[868] +#define sipNameNr_min 860 +#define sipName_min &sipStrings_goldencheetah[860] +#define sipNameNr_all 864 +#define sipName_all &sipStrings_goldencheetah[864] +#define sipNameNr_url 868 +#define sipName_url &sipStrings_goldencheetah[868] +#define sipNameNr_s2 872 +#define sipName_s2 &sipStrings_goldencheetah[872] +#define sipNameNr_s1 875 +#define sipName_s1 &sipStrings_goldencheetah[875] #define sipMalloc sipAPI_goldencheetah->api_malloc #define sipFree sipAPI_goldencheetah->api_free diff --git a/src/Python/SIP/sipgoldencheetahBindings.cpp b/src/Python/SIP/sipgoldencheetahBindings.cpp index e65dfeb9a..3d73aab4a 100644 --- a/src/Python/SIP/sipgoldencheetahBindings.cpp +++ b/src/Python/SIP/sipgoldencheetahBindings.cpp @@ -1182,6 +1182,40 @@ static PyObject *meth_Bindings_hasTag(PyObject *sipSelf, PyObject *sipArgs, PyOb } +extern "C" {static PyObject *meth_Bindings_getTag(PyObject *, PyObject *, PyObject *);} +static PyObject *meth_Bindings_getTag(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds) +{ + PyObject *sipParseErr = NULL; + + { + ::QString* a0; + int a0State = 0; + PyObject * a1 = 0; + const ::Bindings *sipCpp; + + static const char *sipKwdList[] = { + sipName_name, + sipName_activity, + }; + + if (sipParseKwdArgs(&sipParseErr, sipArgs, sipKwds, sipKwdList, NULL, "BJ1|P0", &sipSelf, sipType_Bindings, &sipCpp, sipType_QString,&a0, &a0State, &a1)) + { + ::QString*sipRes; + + sipRes = new ::QString(sipCpp->getTag(*a0,a1)); + sipReleaseType(a0,sipType_QString,a0State); + + return sipConvertFromNewType(sipRes,sipType_QString,NULL); + } + } + + /* Raise an exception if the arguments couldn't be parsed. */ + sipNoMethod(sipParseErr, sipName_Bindings, sipName_getTag, NULL); + + return NULL; +} + + extern "C" {static PyObject *meth_Bindings_configChart(PyObject *, PyObject *, PyObject *);} static PyObject *meth_Bindings_configChart(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds) { @@ -1482,6 +1516,7 @@ static PyMethodDef methods_Bindings[] = { {SIP_MLNAME_CAST(sipName_delTag), (PyCFunction)meth_Bindings_delTag, METH_VARARGS|METH_KEYWORDS, NULL}, {SIP_MLNAME_CAST(sipName_deleteActivitySample), (PyCFunction)meth_Bindings_deleteActivitySample, METH_VARARGS|METH_KEYWORDS, NULL}, {SIP_MLNAME_CAST(sipName_deleteSeries), (PyCFunction)meth_Bindings_deleteSeries, METH_VARARGS|METH_KEYWORDS, NULL}, + {SIP_MLNAME_CAST(sipName_getTag), (PyCFunction)meth_Bindings_getTag, METH_VARARGS|METH_KEYWORDS, NULL}, {SIP_MLNAME_CAST(sipName_hasTag), (PyCFunction)meth_Bindings_hasTag, METH_VARARGS|METH_KEYWORDS, NULL}, {SIP_MLNAME_CAST(sipName_intervalType), (PyCFunction)meth_Bindings_intervalType, METH_VARARGS|METH_KEYWORDS, NULL}, {SIP_MLNAME_CAST(sipName_metrics), (PyCFunction)meth_Bindings_metrics, METH_VARARGS|METH_KEYWORDS, NULL}, @@ -1522,7 +1557,7 @@ sipClassTypeDef sipTypeDef_goldencheetah_Bindings = { { sipNameNr_Bindings, {0, 0, 1}, - 39, methods_Bindings, + 40, methods_Bindings, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, diff --git a/src/Python/SIP/sipgoldencheetahcmodule.cpp b/src/Python/SIP/sipgoldencheetahcmodule.cpp index c19d337fa..335318156 100644 --- a/src/Python/SIP/sipgoldencheetahcmodule.cpp +++ b/src/Python/SIP/sipgoldencheetahcmodule.cpp @@ -80,6 +80,7 @@ const char sipStrings_goldencheetah[] = { 's', 'y', 'm', 'b', 'o', 'l', 0, 'c', 'o', 'l', 'o', 'r', 's', 0, 'l', 'e', 'g', 'p', 'o', 's', 0, + 'g', 'e', 't', 'T', 'a', 'g', 0, 'h', 'a', 's', 'T', 'a', 'g', 0, 'd', 'e', 'l', 'T', 'a', 'g', 0, 's', 'e', 't', 'T', 'a', 'g', 0,