- Download ride dialog now shows progress better.

- cpint added to GUI
- cpint now handles interrupt properly, deleting partially-computed files
This commit is contained in:
Sean C. Rhea
2006-09-17 18:37:48 +00:00
parent 0c68f7846a
commit d8a1ece836
10 changed files with 446 additions and 104 deletions

View File

@@ -27,12 +27,20 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "cpint.h"
struct cpi_file_info *head;
static void
one_done_cb(const char *longdate)
canceled(int unused)
{
fprintf(stderr, "Compiling data for ride on %s...", longdate);
unused = 0;
if (head) {
fprintf(stderr, "calceled.\n");
unlink(head->outname);
}
exit(1);
}
int
@@ -44,7 +52,15 @@ main(int argc, char *argv[])
char *dir = ".";
if (argc > 1)
dir = argv[1];
update_cpi_files(dir, one_done_cb);
signal(SIGINT, canceled);
head = cpi_files_to_update(dir);
while (head) {
fprintf(stderr, "Processing ride file %s...", head->file);
fflush(stderr);
update_cpi_file(head, NULL, NULL);
fprintf(stderr, "done.\n");
head = head->next;
}
combine_cpi_files(dir, &bests, &bestlen);
for (i = 0; i < bestlen; ++i) {
if (bests[i] != 0)

168
src/gui/CpintPlot.cpp Normal file
View File

@@ -0,0 +1,168 @@
/*
* $Id: CpintPlot.cpp,v 1.2 2006/07/12 02:13:57 srhea Exp $
*
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
*
* 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 "CpintPlot.h"
extern "C" {
#include "cpint.h"
}
#include <qwt_data.h>
#include <qwt_legend.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_scale_engine.h>
CpintPlot::CpintPlot(QString p) : path(p), allCurve(NULL), thisCurve(NULL),
grid(NULL)
{
insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(Qt::white);
setAxisTitle(yLeft, "Average Power (watts)");
setAxisTitle(xBottom, "Interval Length (minutes)");
setAxisScaleEngine(xBottom, new QwtLog10ScaleEngine);
}
static int
cancel_cb(void *user_data)
{
CpintPlot *self = (CpintPlot*) user_data;
QCoreApplication::processEvents();
return self->progress->wasCanceled();
}
void
CpintPlot::calculate(QString fileName, QDateTime dateTime)
{
char *dir = strdup(path.toAscii().constData());
char *file = strdup(fileName.toAscii().constData());
if (grid == NULL) {
grid = new QwtPlotGrid();
grid->enableX(false);
QPen gridPen;
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
grid->attach(this);
}
if (allCurve == NULL) {
bool aborted = false;
struct cpi_file_info *head = cpi_files_to_update(dir);
int count = 0;
struct cpi_file_info *tmp = head;
while (tmp) {
++count;
tmp = tmp->next;
}
progress = new QProgressDialog(
QString(tr("Computing critical power intervals.\n"
"This may take a while.\n")),
tr("Abort"), 0, count, this);
int endingOffset = progress->labelText().size();
tmp = head;
progress->show();
count = 0;
while (tmp) {
QString existing = progress->labelText();
existing.chop(progress->labelText().size() - endingOffset);
progress->setLabelText(
existing + QString(tr("Processing %1...")).arg(tmp->file));
progress->setValue(count++);
update_cpi_file(tmp, cancel_cb, this);
QCoreApplication::processEvents();
if (progress->wasCanceled()) {
aborted = true;
break;
}
tmp = tmp->next;
}
free_cpi_file_info(head);
if (head && !aborted) {
QString existing = progress->labelText();
existing.chop(progress->labelText().size() - endingOffset);
progress->setValue(count++);
progress->setLabelText(existing
+ tr("Aggregating over all files."));
int i;
double *bests;
int bestlen;
combine_cpi_files(dir, &bests, &bestlen);
double *timeArray = new double[bestlen];
int maxNonZero = 0;
for (i = 0; i < bestlen; ++i) {
timeArray[i] = i * 0.021;
if (bests[i] > 0) maxNonZero = i;
}
if (maxNonZero > 1) {
allCurve = new QwtPlotCurve("All Rides");
allCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
allCurve->setPen(QPen(Qt::red));
allCurve->setData(timeArray + 1, bests + 1, maxNonZero - 1);
setAxisScale(xBottom, 0.021, maxNonZero * 0.021);
allCurve->attach(this);
}
delete [] timeArray;
free(bests);
}
delete progress;
progress = NULL;
}
if (allCurve) {
delete thisCurve;
int i;
double *bests;
int bestlen;
read_cpi_file(dir, file, &bests, &bestlen);
double *timeArray = new double[bestlen];
int maxNonZero = 0;
for (i = 0; i < bestlen; ++i) {
timeArray[i] = i * 0.021;
if (bests[i] > 0) maxNonZero = i;
}
if (maxNonZero > 1) {
thisCurve = new QwtPlotCurve(
dateTime.toString("ddd MMM d, yyyy h:mm AP"));
thisCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
thisCurve->setPen(QPen(Qt::green));
thisCurve->attach(this);
thisCurve->setData(timeArray + 1, bests + 1, maxNonZero - 1);
}
delete [] timeArray;
free(bests);
}
replot();
free(dir);
free(file);
}
void
CpintPlot::showGrid(int state)
{
assert(state != Qt::PartiallyChecked);
grid->setVisible(state == Qt::Checked);
replot();
}

53
src/gui/CpintPlot.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* $Id: CpintPlot.h,v 1.2 2006/07/12 02:13:57 srhea Exp $
*
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#ifndef _GC_CpintPlot_h
#define _GC_CpintPlot_h 1
#include <qwt_plot.h>
#include <QtGui>
class QwtPlotCurve;
class QwtPlotGrid;
class CpintPlot : public QwtPlot
{
Q_OBJECT
public:
CpintPlot(QString path);
QProgressDialog *progress;
public slots:
void showGrid(int state);
void calculate(QString fileName, QDateTime dateTime);
protected:
QString path;
QwtPlotCurve *allCurve;
QwtPlotCurve *thisCurve;
QwtPlotGrid *grid;
};
#endif // _GC_CpintPlot_h

View File

@@ -23,6 +23,7 @@
#include <QtGui>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#define MAX_DEVICES 10
@@ -165,7 +166,8 @@ DownloadRideDialog::time_cb(struct tm *time)
+ tr(" for writing: ") + strerror(errno));
reject();
}
label->setText(label->text() + tr("\nReading ride data..."));
label->setText(label->text() + tr("\nRide data read: "));
endingOffset = label->text().size();
}
timer->start(5000);
}
@@ -182,7 +184,13 @@ DownloadRideDialog::record_cb(unsigned char *buf)
for (int i = 0; i < 6; ++i)
fprintf(out, "%02x%s", buf[i], (i == 5) ? "\n" : " ");
if ((++blockCount % 256) == 0) {
label->setText(label->text() + ".");
QString existing = label->text();
existing.chop(existing.size() - endingOffset);
int minutes = (int) round(blockCount * 0.021);
existing.append(QString("%1:%2").arg(minutes / 60)
.arg(minutes % 60, 2, 10, QLatin1Char('0')));
label->setText(existing);
repaint();
}
timer->start(5000);
}
@@ -259,7 +267,7 @@ DownloadRideDialog::readData()
delete timer;
timer = NULL;
}
label->setText(label->text() + tr("done."));
// label->setText(label->text() + tr("done."));
QMessageBox::information(this, tr("Success"), tr("Download complete."));
fclose(out);
out = NULL;

View File

@@ -55,6 +55,7 @@ class DownloadRideDialog : public QDialog
QListWidget *listWidget;
QPushButton *downloadButton, *rescanButton, *cancelButton;
QLabel *label;
int endingOffset;
int fd;
FILE *out;
char outname[24];

View File

@@ -14,6 +14,7 @@ LIBS += -lz -framework Carbon
HEADERS += \
AllPlot.h \
ChooseCyclistDialog.h \
CpintPlot.h \
DownloadRideDialog.h \
MainWindow.h \
RawFile.h \
@@ -21,6 +22,7 @@ HEADERS += \
SOURCES += \
AllPlot.cpp \
ChooseCyclistDialog.cpp \
CpintPlot.cpp \
DownloadRideDialog.cpp \
MainWindow.cpp \
RawFile.cpp \

View File

@@ -21,6 +21,7 @@
#include "MainWindow.h"
#include "AllPlot.h"
#include "ChooseCyclistDialog.h"
#include "CpintPlot.h"
#include "DownloadRideDialog.h"
#include "RawFile.h"
#include "RideItem.h"
@@ -135,7 +136,7 @@ MainWindow::MainWindow(const QDir &home) :
window->setLayout(vlayout);
window->show();
tabWidget->addTab(window, "All-in-One Graph");
tabWidget->addTab(window, "Ride Plot");
splitter->addWidget(tabWidget);
QVariant splitterSizes = settings.value(GC_SETTINGS_SPLITTER_SIZES);
@@ -148,6 +149,9 @@ MainWindow::MainWindow(const QDir &home) :
splitter->setSizes(sizes);
}
cpintPlot = new CpintPlot(home.path());
tabWidget->addTab(cpintPlot, "Critical Power Plot");
connect(treeWidget, SIGNAL(itemSelectionChanged()),
this, SLOT(rideSelected()));
connect(splitter, SIGNAL(splitterMoved(int,int)),
@@ -166,6 +170,8 @@ MainWindow::MainWindow(const QDir &home) :
this, SLOT(setSmoothingFromSlider()));
connect(smoothLineEdit, SIGNAL(returnPressed()),
this, SLOT(setSmoothingFromLineEdit()));
connect(tabWidget, SIGNAL(currentChanged(int)),
this, SLOT(tabChanged(int)));
QMenu *fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(tr("&New..."), this,
@@ -246,6 +252,8 @@ MainWindow::rideSelected()
rideSummary->setHtml(ride->htmlSummary());
rideSummary->setAlignment(Qt::AlignCenter);
allPlot->setData(ride->raw);
if (tabWidget->currentIndex() == 2)
cpintPlot->calculate(ride->fileName, ride->dateTime);
return;
}
}
@@ -289,3 +297,18 @@ MainWindow::setSmoothingFromLineEdit()
}
}
void
MainWindow::tabChanged(int index)
{
if (index == 2) {
if (treeWidget->selectedItems().size() == 1) {
QTreeWidgetItem *which = treeWidget->selectedItems().first();
if (which->type() == RIDE_TYPE) {
RideItem *ride = (RideItem*) which;
cpintPlot->calculate(ride->fileName, ride->dateTime);
return;
}
}
}
}

View File

@@ -25,6 +25,7 @@
#include <QtGui>
class AllPlot;
class CpintPlot;
class MainWindow : public QMainWindow
{
@@ -46,6 +47,7 @@ class MainWindow : public QMainWindow
void downloadRide();
void setSmoothingFromSlider();
void setSmoothingFromLineEdit();
void tabChanged(int index);
private:
@@ -57,6 +59,7 @@ class MainWindow : public QMainWindow
QTabWidget *tabWidget;
QTextEdit *rideSummary;
AllPlot *allPlot;
CpintPlot *cpintPlot;
QSlider *smoothSlider;
QLineEdit *smoothLineEdit;
QTreeWidgetItem *allRides;

View File

@@ -23,10 +23,10 @@
#include <assert.h>
#include <dirent.h>
#include <math.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
#include "pt.h"
#include "cpint.h"
struct point
{
@@ -94,14 +94,78 @@ error_cb(const char *msg, void *context)
exit(1);
}
void
one_file(FILE *in, FILE *out)
struct cpi_file_info *
cpi_files_to_update(const char *dir)
{
DIR *dirp;
struct dirent *dp;
regex_t reg;
struct stat sbi, sbo;
char *inname, *outname;
struct cpi_file_info *head = NULL, *tail = NULL;
if (regcomp(&reg, "^([0-9][0-9][0-9][0-9])_([0-9][0-9])_([0-9][0-9])"
"_([0-9][0-9])_([0-9][0-9])_([0-9][0-9])\\.raw$", REG_EXTENDED))
assert(0);
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
int nmatch = 7;
regmatch_t *pmatch = (regmatch_t*) calloc(nmatch, sizeof(regmatch_t));
if (regexec(&reg, dp->d_name, nmatch, pmatch, 0) == 0) {
inname = malloc(strlen(dir) + 25);
outname = malloc(strlen(dir) + 25);
sprintf(inname, "%s/%s", dir, dp->d_name);
if (stat(inname, &sbi))
assert(0);
sprintf(outname, "%s/%s", dir, dp->d_name);
strcpy(outname + strlen(outname) - 4, ".cpi");
if ((stat(outname, &sbo)) || (sbo.st_mtime < sbi.st_mtime)) {
struct cpi_file_info *info = (struct cpi_file_info*)
malloc(sizeof(struct cpi_file_info));
info->file = strdup(dp->d_name);
info->inname = inname;
info->outname = outname;
info->pmatch = pmatch;
info->next = NULL;
if (head == NULL)
head = tail = info;
else {
tail->next = info;
tail = info;
}
}
else {
free(inname);
free(outname);
}
}
else {
free(pmatch);
}
}
closedir(dirp);
return head;
}
void
update_cpi_file(struct cpi_file_info *info,
int (*cancel_cb)(void *user_data),
void *user_data)
{
FILE *in, *out;
int canceled = 0;
double start_secs, prev_secs, dur_secs, avg, sum;
int dur_ints, i, total_intervals;
double *bests;
struct point *p, *q;
int progress_count = 0;
in = fopen(info->inname, "r");
assert(in);
out = fopen(info->outname, "w");
assert(out);
if (head) {
p = head;
while (p) { q = p; p = p->next; free(q); }
@@ -120,6 +184,12 @@ one_file(FILE *in, FILE *out)
for (p = head; p; p = p->next) {
sum = 0.0;
for (q = p; q; q = q->next) {
if (cancel_cb && (++progress_count % 1000 == 0)) {
if (cancel_cb(user_data)) {
canceled = 1;
goto done;
}
}
sum += (q->secs - prev_secs) * q->watts;
dur_secs = q->secs - start_secs;
dur_ints = secs_to_interval(dur_secs);
@@ -136,78 +206,32 @@ one_file(FILE *in, FILE *out)
fprintf(out, "%6.3f %3.0f\n", i * rec_int_ms / 1000.0 / 60.0,
round(bests[i]));
}
done:
fclose(in);
fclose(out);
if (canceled)
unlink(info->outname);
}
void
update_cpi_files(const char *dir, void (*one_done_cb)(const char *date))
free_cpi_file_info(struct cpi_file_info *head)
{
DIR *dirp;
struct dirent *dp;
regex_t reg;
struct stat sbi, sbo;
FILE *in, *out;
char *outname;
char year[5], mon[3], day[3], hour[3], min[3], sec[3];
char longdate[26];
struct tm time;
time_t t;
int nmatch = 7;
regmatch_t *pmatch = (regmatch_t*) calloc(nmatch, sizeof(regmatch_t));
if (regcomp(&reg, "^([0-9][0-9][0-9][0-9])_([0-9][0-9])_([0-9][0-9])"
"_([0-9][0-9])_([0-9][0-9])_([0-9][0-9])\\.raw$", REG_EXTENDED))
assert(0);
outname = malloc(strlen(dir) + 25);
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
if (regexec(&reg, dp->d_name, nmatch, pmatch, 0) == 0) {
if (stat(dp->d_name, &sbi))
assert(0);
sprintf(outname, "%s/%s", dir, dp->d_name);
strcpy(outname + strlen(outname) - 4, ".cpi");
if ((stat(outname, &sbo)) || (sbo.st_mtime < sbi.st_mtime)) {
strncpy(year, dp->d_name + pmatch[1].rm_so, 4); year[4] = '\0';
strncpy(mon, dp->d_name + pmatch[2].rm_so, 2); mon[2] = '\0';
strncpy(day, dp->d_name + pmatch[3].rm_so, 2); day[2] = '\0';
strncpy(hour, dp->d_name + pmatch[4].rm_so, 2); hour[2] = '\0';
strncpy(min, dp->d_name + pmatch[5].rm_so, 2); min[2] = '\0';
strncpy(sec, dp->d_name + pmatch[6].rm_so, 2); sec[2] = '\0';
memset(&time, 0, sizeof(time));
time.tm_year = atoi(year) - 1900;
time.tm_mon = atoi(mon) - 1;
time.tm_mday = atoi(day);
time.tm_hour = atoi(hour);
time.tm_min = atoi(min);
time.tm_sec = atoi(sec);
time.tm_isdst = -1;
t = mktime(&time);
assert(t != -1);
ctime_r(&t, longdate);
longdate[24] = '\0'; /* get rid of newline */
one_done_cb(longdate);
fflush(stderr);
in = fopen(dp->d_name, "r");
assert(in);
out = fopen(outname, "w");
assert(out);
one_file(in, out);
fclose(in);
fclose(out);
fprintf(stderr, "done.\n");
}
}
struct cpi_file_info *tmp;
while (head) {
free(head->file);
free(head->inname);
free(head->outname);
free(head->pmatch);
tmp = head;
head = head->next;
free(tmp);
}
closedir(dirp);
free(pmatch);
}
void
combine_cpi_files(const char *dir, double *bests[], int *bestlen)
static void
read_one(const char *inname, double *bests[], int *bestlen)
{
DIR *dirp;
struct dirent *dp;
FILE *in;
char line[40];
int lineno;
@@ -216,36 +240,63 @@ combine_cpi_files(const char *dir, double *bests[], int *bestlen)
double *tmp;
int interval;
*bestlen = 1000;
*bests = calloc(*bestlen, sizeof(double));
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
if (strcmp(".cpi", dp->d_name + dp->d_namlen - 4) == 0) {
in = fopen(dp->d_name, "r");
assert(in);
lineno = 1;
while (fgets(line, sizeof(line), in) != NULL) {
if (sscanf(line, "%lf %d\n", &mins, &watts) != 2) {
fprintf(stderr, "Bad match on line %d: %s", lineno, line);
exit(1);
}
interval = secs_to_interval(mins * 60.0);
while (interval >= *bestlen) {
tmp = calloc(*bestlen * 2, sizeof(double));
memcpy(tmp, *bests, *bestlen * sizeof(double));
free(*bests);
*bests = tmp;
*bestlen *= 2;
}
if ((*bests)[interval] < watts)
(*bests)[interval] = watts;
++lineno;
}
fclose(in);
in = fopen(inname, "r");
assert(in);
lineno = 1;
while (fgets(line, sizeof(line), in) != NULL) {
if (sscanf(line, "%lf %d\n", &mins, &watts) != 2) {
fprintf(stderr, "Bad match on line %d: %s", lineno, line);
exit(1);
}
interval = secs_to_interval(mins * 60.0);
while (interval >= *bestlen) {
tmp = calloc(*bestlen * 2, sizeof(double));
memcpy(tmp, *bests, *bestlen * sizeof(double));
free(*bests);
*bests = tmp;
*bestlen *= 2;
}
if ((*bests)[interval] < watts)
(*bests)[interval] = watts;
++lineno;
}
closedir(dirp);
fclose(in);
}
void
read_cpi_file(const char *dir, const char *raw, double *bests[], int *bestlen)
{
char *inname;
*bestlen = 1000;
*bests = calloc(*bestlen, sizeof(double));
inname = malloc(strlen(dir) + 25);
sprintf(inname, "%s/%s", dir, raw);
strcpy(inname + strlen(inname) - 4, ".cpi");
read_one(inname, bests, bestlen);
free(inname);
}
void
combine_cpi_files(const char *dir, double *bests[], int *bestlen)
{
DIR *dirp;
struct dirent *dp;
char *inname;
*bestlen = 1000;
*bests = calloc(*bestlen, sizeof(double));
inname = malloc(strlen(dir) + 25);
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
if (strcmp(".cpi", dp->d_name + dp->d_namlen - 4) == 0) {
sprintf(inname, "%s/%s", dir, dp->d_name);
read_one(inname, bests, bestlen);
}
}
closedir(dirp);
free(inname);
}

View File

@@ -21,8 +21,25 @@
#ifndef __cpint_h
#define __cpint_h 1
extern void update_cpi_files(const char *dir,
void (*one_done_cb)(const char *date));
#include <regex.h>
struct cpi_file_info {
char *file, *inname, *outname;
regmatch_t *pmatch;
struct cpi_file_info *next;
};
extern struct cpi_file_info * cpi_files_to_update(const char *dir);
extern void update_cpi_file(struct cpi_file_info *info,
int (*cancel_cb)(void *user_data),
void *user_data);
extern void free_cpi_file_info(struct cpi_file_info *head);
extern void read_cpi_file(const char *dir, const char *raw,
double *bests[], int *bestlen);
extern void combine_cpi_files(const char *dir, double *bests[], int *bestlen);
#endif /* __cpint_h */