diff --git a/src/.gitignore b/src/.gitignore index 2fb3b1f6d..7abeae936 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -20,6 +20,11 @@ qrc_application.cpp # ignore other object files *.o +# ignore lex/yacc generated files +*_lex.cpp +*_yacc.cpp +*_yacc.h + # ignore qmake-generated Makefile Makefile diff --git a/src/CommPort.cpp b/src/CommPort.cpp index a149c73de..b0ec9890c 100644 --- a/src/CommPort.cpp +++ b/src/CommPort.cpp @@ -48,3 +48,18 @@ CommPort::listCommPorts(QString &err) return result; } +QString +CommPort::type() const +{ + return commType; +} + +QString +CommPort::id() const +{ + QString id = commType + ": "; + id += name(); + return id; +} + + diff --git a/src/CommPort.h b/src/CommPort.h index a907d26a9..1f1ba62a6 100644 --- a/src/CommPort.h +++ b/src/CommPort.h @@ -29,17 +29,24 @@ typedef boost::shared_ptr CommPortPtr; class CommPort { public: + CommPort( const QString type ) : commType( type ) {}; typedef QVector (*ListFunction)(QString &err); static bool addListFunction(ListFunction f); static QVector listCommPorts(QString &err); - virtual ~CommPort() {} + virtual ~CommPort() {}; + virtual bool isOpen() = 0; virtual bool open(QString &err) = 0; virtual void close() = 0; virtual int read(void *buf, size_t nbyte, QString &err) = 0; virtual int write(void *buf, size_t nbyte, QString &err) = 0; virtual QString name() const = 0; + QString type( void ) const; + QString id( void ) const; + +private: + QString commType; }; diff --git a/src/D2XX.cpp b/src/D2XX.cpp index 10ea71cf2..598330d9a 100644 --- a/src/D2XX.cpp +++ b/src/D2XX.cpp @@ -95,27 +95,33 @@ static D2XXWrapper *lib; // singleton lib instance bool D2XXRegistered = CommPort::addListFunction(&D2XX::myListCommPorts); D2XX::D2XX(const FT_DEVICE_LIST_INFO_NODE &info) : - info(info), isOpen(false) + CommPort( "D2XX" ), info(info), _isOpen(false) { } D2XX::~D2XX() { - if (isOpen) + if (_isOpen) close(); } +bool +D2XX::isOpen() +{ + return _isOpen; +} + bool D2XX::open(QString &err) { - assert(!isOpen); + assert(!_isOpen); FT_STATUS ftStatus = lib->open_ex(info.Description, FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { err = QString("FT_Open: %1").arg(ftStatus); return false; } - isOpen = true; + _isOpen = true; ftStatus = lib->set_baud_rate(ftHandle, 9600); if (ftStatus != FT_OK) { err = QString("FT_SetBaudRate: %1").arg(ftStatus); @@ -142,15 +148,15 @@ D2XX::open(QString &err) void D2XX::close() { - assert(isOpen); + assert(_isOpen); lib->close(ftHandle); - isOpen = false; + _isOpen = false; } int D2XX::read(void *buf, size_t nbyte, QString &err) { - assert(isOpen); + assert(_isOpen); DWORD rxbytes; FT_STATUS ftStatus = lib->get_queue_status(ftHandle, &rxbytes); if (ftStatus != FT_OK) { @@ -174,7 +180,7 @@ D2XX::read(void *buf, size_t nbyte, QString &err) int D2XX::write(void *buf, size_t nbyte, QString &err) { - assert(isOpen); + assert(_isOpen); DWORD n; FT_STATUS ftStatus = lib->write(ftHandle, buf, nbyte, &n); if (ftStatus == FT_OK) @@ -186,7 +192,7 @@ D2XX::write(void *buf, size_t nbyte, QString &err) QString D2XX::name() const { - return QString("D2XX: ") + info.Description; + return info.Description; } QVector diff --git a/src/D2XX.h b/src/D2XX.h index 99d61a8ab..6183efa5b 100644 --- a/src/D2XX.h +++ b/src/D2XX.h @@ -33,7 +33,7 @@ class D2XX : public CommPort FT_DEVICE_LIST_INFO_NODE info; FT_HANDLE ftHandle; - bool isOpen; + bool _isOpen; D2XX(const FT_DEVICE_LIST_INFO_NODE &info); public: @@ -41,6 +41,7 @@ class D2XX : public CommPort static QVector myListCommPorts(QString &err); virtual ~D2XX(); + virtual bool isOpen(); virtual bool open(QString &err); virtual void close(); virtual int read(void *buf, size_t nbyte, QString &err); diff --git a/src/Device.cpp b/src/Device.cpp index 9dba014f2..77d7889f5 100644 --- a/src/Device.cpp +++ b/src/Device.cpp @@ -18,7 +18,9 @@ #include "Device.h" -typedef QMap DevicesMap; +#define tr(s) QObject::tr(s) + +typedef QMap DevicesMap; static DevicesMap *devicesPtr; @@ -26,28 +28,59 @@ inline DevicesMap & devices() { if (devicesPtr == NULL) - devicesPtr = new QMap; + devicesPtr = new QMap; return *devicesPtr; } +Device::~Device() +{ + if( dev->isOpen() ) + dev->close(); +} + +bool +Device::preview( StatusCallback statusCallback, QString &err ) +{ + (void) statusCallback; + (void) err; + + return true; +} + +QList &Device::rides() +{ + return rideList; +} + +bool +Device::cleanup( QString &err ) +{ + (void) dev; + + err = tr("cleanup is not supported"); + + return false; +} + + QList -Device::deviceTypes() +Devices::typeNames() { return devices().keys(); } -Device & -Device::device(const QString &deviceType) +DevicesPtr +Devices::getType(const QString &deviceTypeName ) { - assert(devices().contains(deviceType)); - return *devices().value(deviceType); + assert(devices().contains(deviceTypeName)); + return devices().value(deviceTypeName); } bool -Device::addDevice(const QString &deviceType, Device *device) +Devices::addType(const QString &deviceTypeName, DevicesPtr p ) { - assert(!devices().contains(deviceType)); - devices().insert(deviceType, device); + assert(!devices().contains(deviceTypeName)); + devices().insert(deviceTypeName, p); return true; } diff --git a/src/Device.h b/src/Device.h index 798f83c8b..6e8876330 100644 --- a/src/Device.h +++ b/src/Device.h @@ -23,22 +23,67 @@ #include "CommPort.h" #include +struct DeviceDownloadFile +{ + QString name; + QDateTime startTime; + QString extension; +}; + +struct DeviceRideItem +{ + bool wanted; + QDateTime startTime; + unsigned work; +}; +typedef boost::shared_ptr DeviceRideItemPtr; + +struct Device; +typedef boost::shared_ptr DevicePtr; + struct Device { - virtual ~Device() {} + Device( CommPortPtr dev ) : dev( dev ) {}; + virtual ~Device(); - typedef boost::function StatusCallback; + typedef boost::function CancelCallback; + typedef boost::function StatusCallback; + typedef boost::function ProgressCallback; - virtual QString downloadInstructions() const = 0; - virtual bool download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) = 0; - virtual void cleanup(CommPortPtr dev) { (void) dev; } + virtual bool preview( StatusCallback statusCallback, QString &err ); + virtual QList &rides(); + + virtual bool download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) = 0; + + virtual bool cleanup( QString &err ); + +protected: + QList rideList; + CommPortPtr dev; - static QList deviceTypes(); - static Device &device(const QString &deviceType); - static bool addDevice(const QString &deviceType, Device *device); }; +struct Devices; +typedef boost::shared_ptr DevicesPtr; + +struct Devices +{ + virtual DevicePtr newDevice( CommPortPtr ) = 0; + + virtual bool canCleanup() { return false; }; + virtual QString downloadInstructions() const { return ""; }; + + + static QList typeNames(); + static DevicesPtr getType(const QString &deviceTypeName ); + static bool addType(const QString &deviceTypeName, DevicesPtr p ); +}; + + #endif // _GC_Device_h diff --git a/src/DownloadRideDialog.cpp b/src/DownloadRideDialog.cpp index 484352a45..35ecfac12 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -26,51 +26,66 @@ #include #include #include +#include DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow, const QDir &home) : mainWindow(mainWindow), home(home), cancelled(false), - downloadInProgress(false) + action(actionIdle) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle("Download Ride Data"); portCombo = new QComboBox(this); - QLabel *instructLabel = new QLabel(tr("Instructions:"), this); - label = new QLabel(this); - label->setIndent(10); + + statusLabel = new QTextEdit(this); + statusLabel->setReadOnly(true); + statusLabel->setAcceptRichText(false); + + // would prefer a progress bar, but some devices (eg. PTap) don't give + // a hint about the total work, so this isn't possible. + progressLabel = new QLabel(this); + progressLabel->setIndent(10); + progressLabel->setTextFormat(Qt::PlainText); deviceCombo = new QComboBox(this); - QList deviceTypes = Device::deviceTypes(); + QList deviceTypes = Devices::typeNames(); assert(deviceTypes.size() > 0); BOOST_FOREACH(QString device, deviceTypes) { deviceCombo->addItem(device); } + connect(deviceCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(deviceChanged(QString))); downloadButton = new QPushButton(tr("&Download"), this); eraseRideButton = new QPushButton(tr("&Erase Ride(s)"), this); rescanButton = new QPushButton(tr("&Rescan"), this); cancelButton = new QPushButton(tr("&Cancel"), this); + closeButton = new QPushButton(tr("&Close"), this); + + downloadButton->setDefault( true ); connect(downloadButton, SIGNAL(clicked()), this, SLOT(downloadClicked())); connect(eraseRideButton, SIGNAL(clicked()), this, SLOT(eraseClicked())); connect(rescanButton, SIGNAL(clicked()), this, SLOT(scanCommPorts())); connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked())); + connect(closeButton, SIGNAL(clicked()), this, SLOT(closeClicked())); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(downloadButton); buttonLayout->addWidget(eraseRideButton); buttonLayout->addWidget(rescanButton); buttonLayout->addWidget(cancelButton); + buttonLayout->addWidget(closeButton); QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(new QLabel(tr("Select port:"), this)); - mainLayout->addWidget(portCombo); mainLayout->addWidget(new QLabel(tr("Select device type:"), this)); mainLayout->addWidget(deviceCombo); - mainLayout->addWidget(instructLabel); - mainLayout->addWidget(label); + mainLayout->addWidget(new QLabel(tr("Select port:"), this)); + mainLayout->addWidget(portCombo); + mainLayout->addWidget(new QLabel(tr("Info:"), this)); + mainLayout->addWidget(statusLabel, 1); + mainLayout->addWidget(progressLabel); mainLayout->addLayout(buttonLayout); scanCommPorts(); @@ -79,23 +94,23 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow, void DownloadRideDialog::setReadyInstruct() { + progressLabel->setText(""); + if (portCombo->count() == 0) { - label->setText(tr("No devices found. Make sure the device\n" + statusLabel->setPlainText(tr("No devices found. Make sure the device\n" "unit is plugged into the computer,\n" "then click \"Rescan\" to check again.")); - downloadButton->setEnabled(false); - eraseRideButton->setEnabled(false); + updateAction( actionMissing ); } else { - Device &device = Device::device(deviceCombo->currentText()); - QString inst = device.downloadInstructions(); + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + QString inst = devtype->downloadInstructions(); if (inst.size() == 0) - label->setText("Click Download to begin downloading."); + statusLabel->setPlainText("Click Download to begin downloading."); else - label->setText(inst + ", \nthen click Download."); - downloadButton->setEnabled(true); - if (deviceCombo->currentText() == "SRM") // only SRM supports erase ride for now - eraseRideButton->setEnabled(true); + statusLabel->setPlainText(inst + ", \nthen click Download."); + + updateAction( actionIdle ); } } @@ -112,8 +127,8 @@ DownloadRideDialog::scanCommPorts() QMessageBox::Ok, QMessageBox::NoButton); } for (int i = 0; i < devList.size(); ++i) { - portCombo->addItem(devList[i]->name()); - // Hack: SRM PCV download cables use the PL2203 chipset. If the + portCombo->addItem(devList[i]->id()); + // XXX Hack: SRM PCV download cables use the PL2203 chipset. If the // first device name contains "PL2303", then, we're probably dealing // with an SRM, so go ahead and select the SRM device. Generalize? if ((i == 0) && devList[i]->name().contains("PL2303")) { @@ -128,34 +143,125 @@ DownloadRideDialog::scanCommPorts() } bool -DownloadRideDialog::statusCallback(const QString &statusText) +DownloadRideDialog::isCancelled() { - label->setText(statusText); + return cancelled; +} + +void +DownloadRideDialog::updateAction( downloadAction newAction ) +{ + + switch( newAction ){ + case actionMissing: + downloadButton->setEnabled(false); + eraseRideButton->setEnabled(false); + rescanButton->setEnabled(true); + cancelButton->setEnabled(false); + closeButton->setEnabled(true); + portCombo->setEnabled(false); + deviceCombo->setEnabled(true); + break; + + case actionIdle: { + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + + downloadButton->setEnabled(true); + eraseRideButton->setEnabled(devtype->canCleanup()); + rescanButton->setEnabled(true); + cancelButton->setEnabled(false); + closeButton->setEnabled(true); + portCombo->setEnabled(true); + deviceCombo->setEnabled(true); + break; + } + + case actionDownload: + case actionCleaning: + downloadButton->setEnabled(false); + eraseRideButton->setEnabled(false); + rescanButton->setEnabled(false); + cancelButton->setEnabled(true); + closeButton->setEnabled(false); + portCombo->setEnabled(false); + deviceCombo->setEnabled(false); + break; + + } + + action = newAction; + cancelled = false; QCoreApplication::processEvents(); - return !cancelled; +} + +void +DownloadRideDialog::updateStatus(const QString &statusText) +{ + statusLabel->append(statusText); + QCoreApplication::processEvents(); +} + +void +DownloadRideDialog::updateProgress( const QString &progressText ) +{ + progressLabel->setText(progressText); + QCoreApplication::processEvents(); +} + + +void +DownloadRideDialog::deviceChanged( QString deviceType ) +{ + (void)deviceType; + + updateAction(action); + setReadyInstruct(); } void DownloadRideDialog::downloadClicked() { - downloadButton->setEnabled(false); - eraseRideButton->setEnabled(false); - rescanButton->setEnabled(false); - downloadInProgress = true; + updateAction( actionDownload ); + + updateProgress( "" ); + statusLabel->setPlainText( "" ); + CommPortPtr dev; for (int i = 0; i < devList.size(); ++i) { - if (devList[i]->name() == portCombo->currentText()) { + if (devList[i]->id() == portCombo->currentText()) { dev = devList[i]; break; } } assert(dev); QString err; - QString tmpname, filename; - Device &device = Device::device(deviceCombo->currentText()); - if (!device.download( - dev, home, tmpname, filename, - boost::bind(&DownloadRideDialog::statusCallback, this, _1), err)) + QList files; + + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + DevicePtr device = devtype->newDevice( dev ); + + if( ! device->preview( + boost::bind(&DownloadRideDialog::updateStatus, this, _1), + err ) ){ + + QMessageBox::information(this, tr("Preview failed"), err); + updateAction( actionIdle ); + return; + } + + QList &rides( device->rides() ); + if( ! rides.empty() ){ + // XXX: let user select, which rides he wants to download + for( int i = 0; i < rides.size(); ++i ){ + rides.at(i)->wanted = true; + } + } + + if (!device->download( home, files, + boost::bind(&DownloadRideDialog::isCancelled, this), + boost::bind(&DownloadRideDialog::updateStatus, this, _1), + boost::bind(&DownloadRideDialog::updateProgress, this, _1), + err)) { if (cancelled) { QMessageBox::information(this, tr("Download canceled"), @@ -165,86 +271,129 @@ DownloadRideDialog::downloadClicked() else { QMessageBox::information(this, tr("Download failed"), err); } - downloadInProgress = false; - reject(); + updateStatus(tr("Download failed")); + updateAction( actionIdle ); return; } - QString filepath = home.absolutePath() + "/" + filename; - if (QFile::exists(filepath)) { - if (QMessageBox::warning( - this, - tr("Ride Already Downloaded"), - tr("This ride appears to have already ") - + tr("been downloaded. Do you want to ") - + tr("overwrite the previous download?"), - tr("&Overwrite"), tr("&Cancel"), - QString(), 1, 1) == 1) { - reject(); - return; + updateProgress( "" ); + + int failures = 0; + for( int i = 0; i < files.size(); ++i ){ + QString filename( files.at(i).startTime + .toString("yyyy_MM_dd_hh_mm_ss") + + "." + files.at(i).extension ); + QString filepath( home.absoluteFilePath(filename) ); + + if (QFile::exists(filepath)) { + if (QMessageBox::warning( this, + tr("Ride Already Downloaded"), + tr("The ride starting at %1 appears to have already " + "been downloaded. Do you want to overwrite the " + "previous download?") + .arg(files.at(i).startTime.toString()), + tr("&Overwrite"), tr("&Skip"), + QString(), 1, 1) == 1) { + QFile::remove(files.at(i).name); + updateStatus(tr("skipped file %1") + .arg( files.at(i).name )); + continue; + } } - } #ifdef __WIN32__ - // Windows ::rename won't overwrite an existing file. - if (QFile::exists(filepath)) { - QFile old(filepath); - if (!old.remove()) { - QMessageBox::critical(this, tr("Error"), - tr("Failed to remove existing file ") - + filepath + ": " + old.error()); - QFile::remove(tmpname); - reject(); + // Windows ::rename won't overwrite an existing file. + if (QFile::exists(filepath)) { + QFile old(filepath); + if (!old.remove()) { + QMessageBox::critical(this, tr("Error"), + tr("Failed to remove existing file %1: %2") + .arg(filepath) + .arg(old.error()) ); + QFile::remove(files.at(i).name); + updateStatus(tr("failed to rename %1 to %2") + .arg( files.at(i).name ) + .arg( filename )); + ++failures; + continue; + } } - } #endif - // Use ::rename() instead of QFile::rename() to get forced overwrite. - if (rename(QFile::encodeName(tmpname), QFile::encodeName(filepath)) < 0) { - QMessageBox::critical(this, tr("Error"), - tr("Failed to rename ") + tmpname + tr(" to ") - + filepath + ": " + strerror(errno)); - QFile::remove(tmpname); - reject(); - return; + // Use ::rename() instead of QFile::rename() to get forced overwrite. + if (rename(QFile::encodeName(files.at(i).name), + QFile::encodeName(filepath)) < 0) { + + QMessageBox::critical(this, tr("Error"), + tr("Failed to rename %1 to %2: %3") + .arg(files.at(i).name) + .arg(filepath) + .arg(strerror(errno)) ); + updateStatus(tr("failed to rename %1 to %2") + .arg( files.at(i).name ) + .arg( filename )); + QFile::remove(files.at(i).name); + ++failures; + continue; + } + + QFile::remove(files.at(i).name); + mainWindow->addRide(filename); } - QMessageBox::information(this, tr("Success"), tr("Download complete.")); - mainWindow->addRide(filename); + if( ! failures ) + updateStatus( tr("download completed successfully") ); - device.cleanup(dev); - - downloadInProgress = false; - accept(); + updateAction( actionIdle ); } void DownloadRideDialog::eraseClicked() { - downloadButton->setEnabled(false); - eraseRideButton->setEnabled(false); - rescanButton->setEnabled(false); - downloadInProgress = true; + updateAction( actionCleaning ); + + statusLabel->setPlainText( "" ); + updateProgress( "" ); + CommPortPtr dev; for (int i = 0; i < devList.size(); ++i) { - if (devList[i]->name() == portCombo->currentText()) { + if (devList[i]->id() == portCombo->currentText()) { dev = devList[i]; break; } } assert(dev); - Device &device = Device::device(deviceCombo->currentText()); - device.cleanup(dev); - downloadInProgress = false; - accept(); + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + DevicePtr device = devtype->newDevice( dev ); + + QString err; + if( device->cleanup( err) ) + updateStatus( tr("cleaned data") ); + else + updateStatus( err ); + + updateAction( actionIdle ); } void DownloadRideDialog::cancelClicked() { - if (!downloadInProgress) - reject(); - else + switch( action ){ + case actionIdle: + case actionMissing: + // do nothing + break; + + default: cancelled = true; + break; + } } +void +DownloadRideDialog::closeClicked() +{ + accept(); +} + + diff --git a/src/DownloadRideDialog.h b/src/DownloadRideDialog.h index 20b03e5f2..252cc7193 100644 --- a/src/DownloadRideDialog.h +++ b/src/DownloadRideDialog.h @@ -34,26 +34,42 @@ class DownloadRideDialog : public QDialog public: DownloadRideDialog(MainWindow *mainWindow, const QDir &home); - void downloadFinished(); - bool statusCallback(const QString &statusText); + bool isCancelled(); + void updateStatus(const QString &statusText); + void updateProgress(const QString &progressText); private slots: void downloadClicked(); void eraseClicked(); void cancelClicked(); + void closeClicked(); void setReadyInstruct(); void scanCommPorts(); + void deviceChanged(QString); private: MainWindow *mainWindow; QDir home; - QPushButton *downloadButton, *eraseRideButton, *rescanButton, *cancelButton; + QPushButton *downloadButton, *eraseRideButton, *rescanButton, + *cancelButton, *closeButton; QComboBox *portCombo, *deviceCombo; - QLabel *label; + QTextEdit *statusLabel; + QLabel *progressLabel; QVector devList; - bool cancelled, downloadInProgress; + bool cancelled; + + typedef enum { + actionIdle, + actionMissing, + actionDownload, + actionCleaning, + } downloadAction; + + downloadAction action; + + void updateAction( downloadAction action ); }; #endif // _GC_DownloadRideDialog_h diff --git a/src/MacroDevice.cpp b/src/MacroDevice.cpp index 5c553cf00..eae07f8d2 100644 --- a/src/MacroDevice.cpp +++ b/src/MacroDevice.cpp @@ -41,15 +41,21 @@ #define LAST_PAGE 0xFA0A // LastPage number static bool macroRegistered = - Device::addDevice("O-Synce Macro PC-Link", new MacroDevice()); + Devices::addType("O-Synce Macro PC-Link", DevicesPtr(new MacroDevices()) ); QString -MacroDevice::downloadInstructions() const +MacroDevices::downloadInstructions() const { return ("Make sure the Macro unit is turned\n" "on and that its display says, \"PC Link\""); } +DevicePtr +MacroDevices::newDevice( CommPortPtr dev ) +{ + return DevicePtr( new MacroDevice( dev )); +} + static QString cEscape(char *buf, int len) { @@ -85,9 +91,12 @@ hexHex2Int(char c, char c2) } bool -MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) +MacroDevice::download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { if (MACRO_DEBUG) printf("download O-Synce Macro"); @@ -96,8 +105,7 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, return false; } - QString cbtext; - cbtext = "Request number of training..."; + statusCallback("Request number of training..."); if (MACRO_DEBUG) printf("Request number of training\n"); MacroPacket cmd(NUMBER_OF_TRAINING_REQUESTS); @@ -105,8 +113,7 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, if (!cmd.write(dev, err)) return false; - cbtext = "Reading number of training..."; - if (!statusCallback(cbtext)) + if (cancelCallback()) { err = "download cancelled"; return false; @@ -130,31 +137,12 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, return false; } - if (!statusCallback(cbtext)) + if (cancelCallback()) { err = "download cancelled"; return false; } - if (MACRO_DEBUG) printf("Acknowledge"); - cmd= MacroPacket(ACKNOWLEDGE); - cmd.addToPayload(response.command); - if (!cmd.write(dev, err)) return false; - - // filename from the first training - int sec = bcd2Int(response.payload.at(2)); - int min = bcd2Int(response.payload.at(3)); - int hour = bcd2Int(response.payload.at(4)); - int day = bcd2Int(response.payload.at(5)); - int month = hex2Int(response.payload.at(6)); - int year = bcd2Int(response.payload.at(7)); - - char *filename_tmp = new char[32]; - - sprintf(filename_tmp, "%04d_%02d_%02d_%02d_%02d_%02d.osyn", - year + 2000, month, day, hour, min, sec); - filename = filename_tmp; - // create temporary file QString tmpl = tmpdir.absoluteFilePath(".macrodl.XXXXXX"); QTemporaryFile tmp(tmpl); @@ -166,14 +154,37 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, return false; } + if (MACRO_DEBUG) printf("Acknowledge"); + cmd= MacroPacket(ACKNOWLEDGE); + cmd.addToPayload(response.command); + if (!cmd.write(dev, err)) return false; + + // timestamp from the first training + struct tm start; + start.tm_sec = bcd2Int(response.payload.at(2)); + start.tm_min = bcd2Int(response.payload.at(3)); + start.tm_hour = bcd2Int(response.payload.at(4)); + start.tm_mday = bcd2Int(response.payload.at(5)); + start.tm_mon = hex2Int(response.payload.at(6)) -1; + start.tm_year = bcd2Int(response.payload.at(7)) -100; + start.tm_isdst = -1; + + DeviceDownloadFile file; + file.extension = "osyn"; + file.name = tmp.fileName(); + file.startTime.setTime_t( mktime( &start )); + files.append(file); + QTextStream os(&tmp); os << hex; for (int i = 0; i < count; i++) { if (MACRO_DEBUG) printf("Request training %d\n",i); - cbtext = QString("Request datas of training %1 / %2...").arg(i+1).arg((int)count); - if (!statusCallback(cbtext)) + statusCallback( QString("Request datas of training %1 / %2...") + .arg(i+1).arg((int)count) ); + + if (cancelCallback()) { err = "download cancelled"; return false; @@ -183,8 +194,7 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, cmd.addToPayload(i); if (!cmd.write(dev, err)) return false; - cbtext = QString("Read datas of training %1 / %2...").arg(i+1).arg((int)count); - if (!statusCallback(cbtext)) + if (cancelCallback()) { err = "download cancelled"; return false; @@ -206,8 +216,11 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, //int training_flag = hex2Int(response2.payload.at(43)); tmp.write(response2.dataArray()); - cbtext = QString("Read datas of training %1 / %2... (%3 bytes)").arg(i+1).arg((int)count).arg(tmp.size()); - if (!statusCallback(cbtext)) + progressCallback( QString("training %1/%2... (%3 Bytes)") + .arg(i+1) + .arg((int)count) + .arg(tmp.size()) ); + if (cancelCallback()) { err = "download cancelled"; return false; @@ -223,7 +236,6 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, } - tmpname = tmp.fileName(); // after close(), tmp.fileName() is "" tmp.close(); dev->close(); @@ -238,12 +250,10 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, return true; } -void -MacroDevice::cleanup(CommPortPtr dev){ +bool +MacroDevice::cleanup( QString &err ){ if (MACRO_DEBUG) printf("Erase all records on computer\n"); - QString err; - if (!dev->open(err)) { err = "ERROR: open failed: " + err; } @@ -262,6 +272,8 @@ MacroDevice::cleanup(CommPortPtr dev){ } dev->close(); + + return true; } // -------------------------------------- diff --git a/src/MacroDevice.h b/src/MacroDevice.h index 50edbc384..4f0babdd4 100644 --- a/src/MacroDevice.h +++ b/src/MacroDevice.h @@ -6,15 +6,26 @@ class DeviceFileInfo; +struct MacroDevices : public Devices +{ + virtual DevicePtr newDevice( CommPortPtr dev ); + virtual QString downloadInstructions() const; + virtual bool canCleanup( void ) {return true; }; +}; + struct MacroDevice : public Device { - virtual QString downloadInstructions() const; + MacroDevice( CommPortPtr dev ) : + Device( dev ) {}; - virtual bool download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); + virtual bool download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); - virtual void cleanup(CommPortPtr dev); + virtual bool cleanup( QString &err ); }; class MacroPacket diff --git a/src/PowerTapDevice.cpp b/src/PowerTapDevice.cpp index 71f897be7..55982de13 100644 --- a/src/PowerTapDevice.cpp +++ b/src/PowerTapDevice.cpp @@ -23,10 +23,16 @@ #define PT_DEBUG false static bool powerTapRegistered = - Device::addDevice("PowerTap", new PowerTapDevice()); + Devices::addType("PowerTap", DevicesPtr(new PowerTapDevices()) ); + +DevicePtr +PowerTapDevices::newDevice( CommPortPtr dev ) +{ + return DevicePtr( new PowerTapDevice( dev )); +} QString -PowerTapDevice::downloadInstructions() const +PowerTapDevices::downloadInstructions() const { return ("Make sure the PowerTap unit is turned\n" "on and that its display says, \"Host\""); @@ -114,9 +120,12 @@ readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err) } bool -PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) +PowerTapDevice::download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { if (!dev->open(err)) { err = "ERROR: open failed: " + err; @@ -124,7 +133,6 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, } // make several attempts at reading the version int attempts = 3; - QString cbtext; int veridx = -1; int version_len; char vbuf[256]; @@ -134,8 +142,8 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, if (!doWrite(dev, 0x56, false, err)) // 'V' return false; - cbtext = "Reading version..."; - if (!statusCallback(cbtext)) { + statusCallback( "Reading version..." ); + if (cancelCallback()) { err = "download cancelled"; return false; } @@ -166,8 +174,8 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, bool hwecho = version.indexOf('V') < veridx; if (PT_DEBUG) printf("hwecho=%s\n", hwecho ? "true" : "false"); - cbtext += "done.\nReading header..."; - if (!statusCallback(cbtext)) { + statusCallback( "Reading header..." ); + if (cancelCallback()) { err = "download cancelled"; return false; } @@ -192,12 +200,11 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, for (size_t i = 0; i < sizeof(header); ++i) records.append(header[i]); - cbtext += "done.\nReading ride data...\n"; - if (!statusCallback(cbtext)) { + statusCallback( "Reading ride data..." ); + if (cancelCallback()) { err = "download cancelled"; return false; } - int cbtextlen = cbtext.length(); double recIntSecs = 0.0; fflush(stdout); @@ -259,11 +266,11 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, } if (recIntSecs != 0.0) { int min = (int) round(records.size() / 6 * recIntSecs); - cbtext.chop(cbtext.size() - cbtextlen); - cbtext.append(QString("Ride data read: %1:%2").arg(min / 60) - .arg(min % 60, 2, 10, QLatin1Char('0'))); + progressCallback( QString("progress: %1:%2") + .arg(min / 60) + .arg(min % 60, 2, 10, QLatin1Char('0'))); } - if (!statusCallback(cbtext)) { + if (cancelCallback()) { err = "download cancelled"; return false; } @@ -279,11 +286,20 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, + tmpl + ": " + tmp.error(); return false; } + // QTemporaryFile initially has permissions set to 0600. + // Make it readable by everyone. + tmp.setPermissions(tmp.permissions() + | QFile::ReadOwner | QFile::ReadUser + | QFile::ReadGroup | QFile::ReadOther); + + DeviceDownloadFile file; + file.extension = "raw"; + file.name = tmp.fileName(); + QTextStream os(&tmp); os << hex; os.setPadChar('0'); - struct tm time; bool time_set = false; unsigned char *data = records.data(); bool bIsVer81 = PowerTapUtil::is_Ver81(data); @@ -298,7 +314,9 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, os << ((j == 5) ? "\n" : " "); } if (!time_set && PowerTapUtil::is_time(data + i, bIsVer81)) { - PowerTapUtil::unpack_time(data + i, &time, bIsVer81); + struct tm time; + time_t timet = PowerTapUtil::unpack_time(data + i, &time, bIsVer81); + file.startTime.fromTime_t( timet ); time_set = true; } } @@ -307,21 +325,8 @@ PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, tmp.setAutoRemove(true); return false; } - tmpname = tmp.fileName(); // after close(), tmp.fileName() is "" - tmp.close(); - // QTemporaryFile initially has permissions set to 0600. - // Make it readable by everyone. - tmp.setPermissions(tmp.permissions() - | QFile::ReadOwner | QFile::ReadUser - | QFile::ReadGroup | QFile::ReadOther); - - char filename_tmp[32]; - sprintf(filename_tmp, "%04d_%02d_%02d_%02d_%02d_%02d.raw", - time.tm_year + 1900, time.tm_mon + 1, - time.tm_mday, time.tm_hour, time.tm_min, - time.tm_sec); - filename = filename_tmp; + files << file; return true; } diff --git a/src/PowerTapDevice.h b/src/PowerTapDevice.h index 0ffdded5d..f67810cef 100644 --- a/src/PowerTapDevice.h +++ b/src/PowerTapDevice.h @@ -20,15 +20,25 @@ #define _GC_PowerTapDevice_h 1 #include "GoldenCheetah.h" -#include "CommPort.h" #include "Device.h" +struct PowerTapDevices : public Devices +{ + virtual DevicePtr newDevice( CommPortPtr dev ); + virtual QString downloadInstructions() const; +}; + struct PowerTapDevice : public Device { - virtual QString downloadInstructions() const; - virtual bool download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); + PowerTapDevice( CommPortPtr dev ) : + Device( dev ) {}; + + virtual bool download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); }; #endif // _GC_PowerTapDevice_h diff --git a/src/Serial.cpp b/src/Serial.cpp index 480c53a21..7358b96e8 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -46,23 +46,28 @@ bool SerialRegistered = CommPort::addListFunction(&Serial::myListCommPorts); #ifdef Q_OS_WIN32 -Serial::Serial(const QString &path) : path(path), isOpen(false) +Serial::Serial(const QString &path) : CommPort("Serial"), path(path), _isOpen(false) { } #else -Serial::Serial(const QString &path) : path(path), fd(-1) +Serial::Serial(const QString &path) : CommPort("Serial"), path(path), fd(-1) { } #endif Serial::~Serial() { -#ifdef Q_OS_WIN32 - if (isOpen == true) { + if( isOpen() ) close(); - } +} + +bool +Serial::isOpen() +{ +#ifdef Q_OS_WIN32 + return _isOpen; #else - if (fd >= 0) close(); + return fd >= 0; #endif } @@ -129,9 +134,9 @@ Serial::open(QString &err) fd = CreateFile (deviceFilenameW, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_DELETE|FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (fd == INVALID_HANDLE_VALUE) return isOpen = false; + if (fd == INVALID_HANDLE_VALUE) return _isOpen = false; - if (GetCommState (fd, &deviceSettings) == false) return isOpen = false; + if (GetCommState (fd, &deviceSettings) == false) return _isOpen = false; // so we've opened the comm port lets set it up for deviceSettings.BaudRate = CBR_9600; @@ -148,7 +153,7 @@ Serial::open(QString &err) if (SetCommState(fd, &deviceSettings) == false) { CloseHandle(fd); - return isOpen = false; + return _isOpen = false; } timeouts.ReadIntervalTimeout = 0; @@ -158,7 +163,7 @@ Serial::open(QString &err) timeouts.WriteTotalTimeoutMultiplier = 0; SetCommTimeouts(fd, &timeouts); - return isOpen = true; + return _isOpen = true; #endif } @@ -170,9 +175,9 @@ Serial::close() ::close(fd); fd = -1; #else - if (isOpen == true) { + if (_isOpen == true) { CloseHandle(fd); - isOpen = false; + _isOpen = false; } #endif } @@ -309,7 +314,7 @@ Serial::write(void *buf, size_t nbyte, QString &err) QString Serial::name() const { - return QString("Serial: ") + path; + return path; } #ifndef Q_OS_WIN32 diff --git a/src/Serial.h b/src/Serial.h index 2716bdd75..ce2bea228 100644 --- a/src/Serial.h +++ b/src/Serial.h @@ -36,7 +36,7 @@ class Serial : public CommPort #ifndef Q_OS_WIN32 int fd; #else - bool isOpen; // don't rely on fd value to determine if + bool _isOpen; // don't rely on fd value to determine if // the COM port is open HANDLE fd; // file descriptor for reading from com3 #endif @@ -47,6 +47,7 @@ class Serial : public CommPort static QVector myListCommPorts(QString &err); virtual ~Serial(); + virtual bool isOpen(); virtual bool open(QString &err); virtual void close(); virtual int read(void *buf, size_t nbyte, QString &err); diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index 2d3da3e1a..26b3b79f0 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -23,23 +23,19 @@ #include #include #include +#include -static bool srmRegistered = Device::addDevice("SRM", new SrmDevice()); +#define tr(s) QObject::tr(s) -QString -SrmDevice::downloadInstructions() const +static bool srm5Registered = + Devices::addType("SRM PCV", DevicesPtr(new SrmDevices( 5 )) ); +static bool srm7Registered = + Devices::addType("SRM PCVI/7", DevicesPtr(new SrmDevices( 7 ))); + +DevicePtr +SrmDevices::newDevice( CommPortPtr dev ) { - return ""; // no particular instructions for SRM -} - -static Device::StatusCallback cb; - -static void -logfunc(const char *msg) -{ - // XXX: better log function - // fprintf(stderr, "%s\n", msg); - cb(QString(msg).left(40)); + return DevicePtr( new SrmDevice( dev, protoVersion)); } static bool @@ -58,90 +54,450 @@ get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err) return true; } -struct SrmpcConn : public boost::noncopyable +SrmDevice::~SrmDevice() { - srmpc_conn_t d; - SrmpcConn(const QString &path, srmpc_log_callback_t logfunc = NULL) { - int opt_force = 0; // setting this to 1 is potentially dangerous - d = srmpc_open(path.toAscii().constData(), opt_force, logfunc); - } - ~SrmpcConn() { if (d) srmpc_close(d); } -}; + close(); +} -struct SrmioData : public boost::noncopyable +bool +SrmDevice::open( QString &err ) { - srm_data_t d; - SrmioData(srm_data_t d) : d(d) {} - ~SrmioData() { if (d) srm_data_free(d); } -}; + if( dev->type() == "Serial" ){ + io = srmio_ios_new( dev->name().toAscii().constData() ); + if( ! io ){ + err = tr("failed to allocate device handle: %1") + .arg(strerror(errno)); + return false; + } -static bool -dev2path(CommPortPtr dev, QString &path, QString &err) -{ - // Read device path out of device name. Sketchy. - QRegExp rx("^Serial: (.+)$"); - if (!rx.exactMatch(dev->name())) { - err = "SRM download not supported by device " + dev->name(); + } else { + err = tr("device type %1 is unsupported") + .arg(dev->type()); return false; } - path = rx.cap(1); + + switch( protoVersion ){ + case 5: + pc = srmio_pc5_new(); + break; + + case 6: + case 7: + pc = srmio_pc7_new(); + break; + + default: + err = tr("unsupported SRM Protocl version: %1") + .arg(protoVersion); + goto fail; + } + if( ! pc ){ + err = tr("failed to allocate Powercontrol handle: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_io_open(io ) ){ + err = tr("Couldn't open device %1: %2") + .arg(dev->name()) + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_pc_set_device( pc, io ) ){ + err = tr("failed to set Powercontrol io handle: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_pc_open( pc ) ){ + err = tr("failed to initialize Powercontrol communication: %1") + .arg(strerror(errno)); + goto fail; + } + + is_open = true; + return true; + +fail: + if( pc ){ + srmio_pc_free( pc ); + pc = NULL; + } + + if( io ){ + srmio_io_free( io ); + io = NULL; + } + + return false; +} + +bool +SrmDevice::close( void ) +{ + rideList.clear(); + + if( pc ){ + srmio_pc_free( pc ); + pc = NULL; + } + + if( io ){ + srmio_io_free( io ); + io = NULL; + } + + is_open = false; return true; } bool -SrmDevice::download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) +SrmDevice::preview( StatusCallback statusCallback, QString &err ) { - // Totally ghetto, proof-of-concept integration with srmio. - cb = statusCallback; - QString path; - if (!dev2path(dev, path, err)) - return false; - if (!get_tmpname(tmpdir, tmpname, err)) - return false; - SrmpcConn srm(path, logfunc); - if (!srm.d) { - err = "Couldn't open device " + path + ": " + strerror(errno); - return false; + struct _srmio_pc_xfer_block_t block; + + if( ! is_open ){ + statusCallback( tr("opening device %1").arg(dev->name()) ); + if( ! open( err ) ) + return false; } - int opt_all = 0; // only get new data - int opt_fixup = 1; // fix bad data like srmwin.exe does - SrmioData srmdata(srmpc_get_data(srm.d, opt_all, opt_fixup)); - if (!srmdata.d) { - err = "srmpc_get_data failed: "; - err += strerror(errno); - return false; + + rideList.clear(); + + if( ! srmio_pc_can_preview( pc ) ) + // nothing to do + return true; + + if( ! srmio_pc_xfer_start( pc ) ){ + err = tr("failed to start download: %1") + .arg(strerror(errno)); + goto fail; } - if( ! srmdata.d->cused ){ - err = "no data available"; - return false; + + while( srmio_pc_xfer_block_next( pc, &block )){ + DeviceRideItemPtr ride( new DeviceRideItem ); + + ride->startTime.setTime_t( 0.1 * block.start ); + ride->work = block.total; + rideList.append( ride ); + + if( block.athlete ) + free( block.athlete ); + block.athlete = NULL; } - if (srm_data_write_srm7(srmdata.d, tmpname.toAscii().constData()) < 0) { - err = "Couldn't write to file " + tmpname + ": " + strerror(errno); - return false; + + if( ! srmio_pc_xfer_finish( pc ) ){ + err = tr("download failed: %1") + .arg(strerror(errno)); + goto fail; } - QDateTime startTime; - startTime.setTime_t( srmdata.d->chunks[0]->time / 10 ); - filename = startTime.toString("yyyy_MM_dd_hh_mm_ss") + ".srm"; + return true; + +fail: + rideList.clear(); + return false; } -void -SrmDevice::cleanup(CommPortPtr dev) +bool +SrmDevice::download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { - QString path, err; - if (!dev2path(dev, path, err)) - assert(false); - if (QMessageBox::question(0, "Powercontrol", - "Erase ride from device memory?", - "&Erase", "&Cancel", "", 1, 1) == 0) { - SrmpcConn srm(path); - if(!srm.d || (srmpc_clear_chunks(srm.d) < 0)) { - QMessageBox::warning(0, "Error", - "Error communicating with device."); - } + unsigned firmware; + srmio_io_baudrate_t baudrateId; + unsigned baudrate; + struct _srmio_pc_xfer_block_t block; + srmio_data_t data( NULL ); + srmio_data_t *splitList( NULL ); + srmio_data_t *split; + int mfirst( -1 ); + size_t block_cnt, block_num( 0 ); + size_t prog_sum( 0 ), prog_prev( 0 ); + size_t chunks_done( 0 ); + srmio_time_t splitGap( 72000 ); // 2h - XXX: make this configurable + + if( ! is_open ){ + statusCallback( tr("opening device %1").arg(dev->name()) ); + if( ! open( err ) ) + return false; } + + if( ! srmio_pc_get_version( pc, &firmware ) ){ + err = tr("failed to get firmware version: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_pc_get_baudrate( pc, &baudrateId ) ){ + err = tr("failed to get baud rate: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_io_baud2name( baudrateId, &baudrate ) ){ + err = tr("failed to get baud rate name: %1") + .arg(strerror(errno)); + } + + statusCallback(tr("found Powercontrol version 0x%1 using %2 baud") + .arg(firmware, 4, 16 ) + .arg(baudrate)); + + // fetch preview in case user didn't + if( srmio_pc_can_preview(pc) && rideList.size() == 0 ){ + if( ! preview( statusCallback, err ) ) + return false; + } + + data = srmio_data_new(); + if( ! data ){ + err = tr("failed to allocate data handle: %1") + .arg(strerror(errno)); + goto fail; + } + + if( cancelCallback() ){ + err = tr("download cancelled"); + goto fail; + } + + if( ! srmio_pc_xfer_start( pc )){ + err = tr("failed to start download: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_pc_xfer_get_blocks( pc, &block_cnt ) ){ + err = tr("failed to get number of data blocks: %1") + .arg(strerror(errno)); + goto fail1; + } + statusCallback(tr("found %1 ride blocks").arg(block_cnt)); + + for( int i = 0; i < rideList.size(); ++i ){ + if( rideList.at(i)->wanted ) + prog_sum += rideList.at(i)->work; + } + + while( srmio_pc_xfer_block_next( pc, &block )){ + bool wanted = false; + struct _srmio_chunk_t chunk; + bool is_int; + bool is_first; + size_t prog_total; + + if( rideList.empty() ){ + wanted = true; + + } else { + for( int i = 0; i < rideList.size(); ++i ){ + if( rideList.at(i)->startTime.toTime_t() == block.start / 10 ){ + wanted = rideList.at(i)->wanted; + break; + } + } + } + + if( ! wanted ){ + statusCallback(tr("skipping unselected ride block %1") + .arg(block_num +1)); + continue; + } + statusCallback(tr("downloading ride block %1/%2") + .arg(block_num +1) + .arg(block_cnt) ); + + data->slope = block.slope; + data->zeropos = block.zeropos; + data->circum = block.circum; + if( block.athlete ){ + if( data->athlete ) + free( data->athlete ); + data->athlete = strdup( block.athlete ); + } + + if( ! rideList.empty() ){ + prog_total = prog_sum; + + } else if( block_cnt == 1 ){ + prog_total = block.total; + + } else { + prog_total = block_cnt * 1000; + } + + while( srmio_pc_xfer_chunk_next( pc, &chunk, &is_int, &is_first ) ){ + if( cancelCallback() ){ + err = tr("download cancelled"); + goto fail1; + } + + if( chunks_done % 16 == 0 ){ + size_t block_done; + + srmio_pc_xfer_block_progress( pc, &block_done ); + if( ! rideList.empty() ){ + block_done += prog_prev; + + } else if( block_cnt == 1 ){ + // unchanged + + } else { + block_done = (double)block_num * 1000 + + 1000 * block.total / block_done; + } + + progressCallback( tr("progress: %1/%2") + .arg(block_done) + .arg(prog_total)); + } + + if( ! srmio_data_add_chunk( data, &chunk ) ) + goto fail1; + + ++chunks_done; + + /* finish previous marker */ + if( mfirst >= 0 && ( ! is_int || is_first ) ) + srmio_data_add_marker( data, mfirst, data->cused -1 ); + + /* start marker */ + if( is_first ){ + mfirst = (int)data->cused; + + } else if( ! is_int ){ + mfirst = -1; + + } + } + + /* finalize marker at block end */ + if( mfirst >= 0 ){ + srmio_data_add_marker( data, mfirst, data->cused -1 ); + mfirst = -1; + } + + if( ! rideList.empty() ) + prog_prev += block.total; + else + prog_prev += 1000; + + if( block.athlete ) + free( block.athlete ); + block.athlete = NULL; + + ++block_num; + } + + if( ! srmio_pc_xfer_finish( pc ) ){ + err = tr( "download failed: %1") + .arg(strerror(errno)); + goto fail; + } + + statusCallback( tr("got %1 records").arg(data->cused) ); + + if( cancelCallback() ){ + err = tr("download cancelled"); + goto fail; + } + + if( ! data->cused ){ + err = tr("no data available"); + goto fail; + } + + splitList = srmio_data_split( data, splitGap, 1000 ); + if( ! splitList ){ + err = tr("Couldn't split data: %1") + .arg(strerror(errno)); + goto fail; + } + + for( split = splitList; *split; ++split ){ + FILE *fh( NULL ); + srmio_time_t stime; + DeviceDownloadFile file; + + file.extension = "srm"; + + if (!get_tmpname(tmpdir, file.name, err)) + goto fail; + + if( ! srmio_data_time_start( *split, &stime ) ){ + err = tr("Couldn't get start time of data: %1") + .arg(strerror(errno)); + goto fail; + } + file.startTime.setTime_t( 0.1 * stime ); + + fh = fopen( file.name.toAscii().constData(), "w" ); + if( ! fh ){ + err = tr( "failed to open file %1: %2") + .arg(file.name) + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_file_srm7_write(*split, fh) ){ + err = tr("Couldn't write to file %1: %2") + .arg(file.name) + .arg(strerror(errno)); + fclose(fh); + goto fail; + } + + files.append(file); + + fclose( fh ); + + } + + for( split = splitList; *split; ++split ) + srmio_data_free( *split ); + free(splitList); + + srmio_data_free( data ); + return true; + +fail1: + srmio_pc_xfer_finish(pc); + +fail: + if( data ) srmio_data_free( data ); + if( splitList ){ + for( split = splitList; *split; ++split ) + srmio_data_free( *split ); + free(splitList); + } + close(); + return false; +} + +bool +SrmDevice::cleanup( QString &err ) +{ + if( ! is_open ){ + if( ! open( err ) ) + goto cleanup; + } + + if( ! srmio_pc_cmd_clear( pc ) ){ + err = tr("failed to clear Powercontrol memory: %1") + .arg(strerror(errno)); + goto cleanup; + } + + return true; + +cleanup: + close(); + return false; } diff --git a/src/SrmDevice.h b/src/SrmDevice.h index dcfc8e722..d7266b621 100644 --- a/src/SrmDevice.h +++ b/src/SrmDevice.h @@ -18,18 +18,50 @@ #ifndef _GC_SrmDevice_h #define _GC_SrmDevice_h 1 +#include #include "GoldenCheetah.h" -#include "CommPort.h" #include "Device.h" +struct SrmDevices : public Devices +{ + SrmDevices( int protoVersion ) : protoVersion( protoVersion ) {} + + virtual DevicePtr newDevice( CommPortPtr dev ); + virtual bool canCleanup( void ) {return true; }; + +private: + int protoVersion; +}; + struct SrmDevice : public Device { - virtual QString downloadInstructions() const; - virtual bool download(CommPortPtr dev, const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); - virtual void cleanup(CommPortPtr dev); + SrmDevice( CommPortPtr dev, int protoVersion ) : + Device( dev ), + protoVersion( protoVersion ), + is_open( false ), + io( NULL ), pc( NULL ) { }; + ~SrmDevice(); + + virtual bool preview( StatusCallback statusCallback, QString &err ); + + virtual bool download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); + + virtual bool cleanup( QString &err ); + +private: + int protoVersion; + bool is_open; + srmio_io_t io; + srmio_pc_t pc; + + bool open ( QString &err ); + bool close( void ); }; #endif // _GC_SrmDevice_h diff --git a/src/SyncRideFile.cpp b/src/SyncRideFile.cpp index 2c8adc48e..fb81d30a2 100644 --- a/src/SyncRideFile.cpp +++ b/src/SyncRideFile.cpp @@ -178,11 +178,12 @@ struct SyncFileReaderState QDateTime t = QDateTime(QDate(2000+year,month,day), QTime(hour,min,sec)); - if (secs == 0.0 || rideFile->startTime().toMSecsSinceEpoch()<0) { + if (secs == 0.0 || rideFile->startTime().toTime_t() == (unsigned int)-1 ) { + rideFile->setStartTime(t); } else { - int gap = (t.toMSecsSinceEpoch() - rideFile->startTime().toMSecsSinceEpoch())/1000 - secs; + int gap = (t.toTime_t() - rideFile->startTime().toTime_t()) - secs; secs += gap; }