From d8a1ece83686e0e095a9d8af4b46e87fcef6b71d Mon Sep 17 00:00:00 2001 From: "Sean C. Rhea" Date: Sun, 17 Sep 2006 18:37:48 +0000 Subject: [PATCH] - Download ride dialog now shows progress better. - cpint added to GUI - cpint now handles interrupt properly, deleting partially-computed files --- src/cmd/cpint.c | 22 ++- src/gui/CpintPlot.cpp | 168 +++++++++++++++++++++++ src/gui/CpintPlot.h | 53 ++++++++ src/gui/DownloadRideDialog.cpp | 14 +- src/gui/DownloadRideDialog.h | 1 + src/gui/GoldenCheetah.pro | 2 + src/gui/MainWindow.cpp | 25 +++- src/gui/MainWindow.h | 3 + src/lib/cpint.c | 241 ++++++++++++++++++++------------- src/lib/cpint.h | 21 ++- 10 files changed, 446 insertions(+), 104 deletions(-) create mode 100644 src/gui/CpintPlot.cpp create mode 100644 src/gui/CpintPlot.h diff --git a/src/cmd/cpint.c b/src/cmd/cpint.c index 260338b90..ad0120332 100644 --- a/src/cmd/cpint.c +++ b/src/cmd/cpint.c @@ -27,12 +27,20 @@ #include #include #include +#include #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) diff --git a/src/gui/CpintPlot.cpp b/src/gui/CpintPlot.cpp new file mode 100644 index 000000000..0ba346e88 --- /dev/null +++ b/src/gui/CpintPlot.cpp @@ -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 +#include +#include +#include +#include + +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(); +} + diff --git a/src/gui/CpintPlot.h b/src/gui/CpintPlot.h new file mode 100644 index 000000000..0b3b9a833 --- /dev/null +++ b/src/gui/CpintPlot.h @@ -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 +#include + +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 + diff --git a/src/gui/DownloadRideDialog.cpp b/src/gui/DownloadRideDialog.cpp index 96640e131..d80944b20 100644 --- a/src/gui/DownloadRideDialog.cpp +++ b/src/gui/DownloadRideDialog.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #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; diff --git a/src/gui/DownloadRideDialog.h b/src/gui/DownloadRideDialog.h index 20d33a8ac..0c3efb7a7 100644 --- a/src/gui/DownloadRideDialog.h +++ b/src/gui/DownloadRideDialog.h @@ -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]; diff --git a/src/gui/GoldenCheetah.pro b/src/gui/GoldenCheetah.pro index 23fdd273e..3476f214a 100644 --- a/src/gui/GoldenCheetah.pro +++ b/src/gui/GoldenCheetah.pro @@ -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 \ diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 40826b94a..99ea31ce3 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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; + } + } + } +} + diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index d408f7cb7..c9a80ab8d 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -25,6 +25,7 @@ #include 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; diff --git a/src/lib/cpint.c b/src/lib/cpint.c index 1f412fd32..e24021bc3 100644 --- a/src/lib/cpint.c +++ b/src/lib/cpint.c @@ -23,10 +23,10 @@ #include #include #include -#include #include #include #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(®, "^([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(®, 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(®, "^([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(®, 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); } diff --git a/src/lib/cpint.h b/src/lib/cpint.h index 330412f8e..9027a456f 100644 --- a/src/lib/cpint.h +++ b/src/lib/cpint.h @@ -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 + +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 */