From dcded137de4796d8fde17580b9b676698eaabd9f Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Thu, 21 Jul 2011 10:16:58 +0200 Subject: [PATCH 01/10] gitignore lex/yacc generated files --- src/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) 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 From da7ae868ed93eccae3f762329c09fe53eb257807 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Sun, 24 Jul 2011 12:43:16 +0200 Subject: [PATCH 02/10] MacroDevice: use backwards compatible toTime_t toMSecsSinceEpoch requires QT 4.7. And as the caluclation doesnt require msec precision, toTime_t should be sufficient. Well, this is mostly, because I'm currently too lazy to update my QT... I guess, this doesn't have to be merged. --- src/SyncRideFile.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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; } From e1f2128e27de6235d53984b4ead23b11efdbb057 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Wed, 20 Jul 2011 21:05:14 +0200 Subject: [PATCH 03/10] CommPort: keep device names seperate CommPort::name used to prefix the actual device name/path with the actual device type to make it unique. This is used in DownloadRideDialog to map the device dropdown list to the actual device. This patch seperates name + device type to make the hack in SrmDevice a bit less ugly: srmio doesn't use the built in CommPort abstraction and needs the unmodified device names. This is still ugly, but I can't come up with anything better (... for now). --- src/CommPort.cpp | 15 +++++++++++++++ src/CommPort.h | 6 ++++++ src/D2XX.cpp | 4 ++-- src/DownloadRideDialog.cpp | 6 +++--- src/Serial.cpp | 6 +++--- src/SrmDevice.cpp | 8 +++----- 6 files changed, 32 insertions(+), 13 deletions(-) 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..aec13e46c 100644 --- a/src/CommPort.h +++ b/src/CommPort.h @@ -29,6 +29,7 @@ typedef boost::shared_ptr CommPortPtr; class CommPort { public: + CommPort( const QString type ) : commType( type ) {}; typedef QVector (*ListFunction)(QString &err); static bool addListFunction(ListFunction f); @@ -40,6 +41,11 @@ class CommPort 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..bf502da90 100644 --- a/src/D2XX.cpp +++ b/src/D2XX.cpp @@ -95,7 +95,7 @@ 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) + info(info), isOpen(false), CommPort( "D2XX" ) { } @@ -186,7 +186,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/DownloadRideDialog.cpp b/src/DownloadRideDialog.cpp index 484352a45..b0dee63a3 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -112,7 +112,7 @@ DownloadRideDialog::scanCommPorts() QMessageBox::Ok, QMessageBox::NoButton); } for (int i = 0; i < devList.size(); ++i) { - portCombo->addItem(devList[i]->name()); + portCombo->addItem(devList[i]->id()); // 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? @@ -144,7 +144,7 @@ DownloadRideDialog::downloadClicked() downloadInProgress = true; 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; } @@ -227,7 +227,7 @@ DownloadRideDialog::eraseClicked() downloadInProgress = true; 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; } diff --git a/src/Serial.cpp b/src/Serial.cpp index 480c53a21..6fe43d852 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -46,11 +46,11 @@ 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 @@ -309,7 +309,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/SrmDevice.cpp b/src/SrmDevice.cpp index 2d3da3e1a..ea53f6690 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -78,13 +78,11 @@ struct SrmioData : public boost::noncopyable 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(); + if ( dev->type() == "Serial" ) { + err = "SRM download not supported by device " + dev->id(); return false; } - path = rx.cap(1); + path = dev->name(); return true; } From 9a394552b36c300d456cdca37bbb66864d0587d4 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Wed, 20 Jul 2011 23:09:39 +0200 Subject: [PATCH 04/10] use new srmio API - allowing PC7 support srmio changed it's API completely. Adopted SrmDevice to use new API. As a first step, we keep using the "simple" API to save everything into a single file. While this works fine for PCV, this will run into limitations of the SRM file format, quite soon. This needs to be addressed in another step. --- src/SrmDevice.cpp | 257 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 191 insertions(+), 66 deletions(-) diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index ea53f6690..0ca1fd60e 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -23,8 +23,14 @@ #include #include #include +#include -static bool srmRegistered = Device::addDevice("SRM", new SrmDevice()); +#define tr(s) QObject::tr(s) + +static bool srm5Registered = Device::addDevice("SRM PCV", new SrmDevice( 5 )); +static bool srm7Registered = Device::addDevice("SRM PCVI/7", new SrmDevice( 7 )); + +static Device::StatusCallback cb; QString SrmDevice::downloadInstructions() const @@ -32,14 +38,13 @@ SrmDevice::downloadInstructions() const return ""; // no particular instructions for SRM } -static Device::StatusCallback cb; - -static void -logfunc(const char *msg) +static void progress( size_t total, size_t done, void *user_data ) { - // XXX: better log function - // fprintf(stderr, "%s\n", msg); - cb(QString(msg).left(40)); + (void)user_data; + + // XXX: better way to pass statusCallback - that's what we have + // "user_data" for... + cb( tr("progress: %1/%1").arg(done).arg(total) ); } static bool @@ -58,88 +63,208 @@ get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err) return true; } -struct SrmpcConn : public boost::noncopyable -{ - 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); } -}; - -struct SrmioData : public boost::noncopyable -{ - srm_data_t d; - SrmioData(srm_data_t d) : d(d) {} - ~SrmioData() { if (d) srm_data_free(d); } -}; - -static bool -dev2path(CommPortPtr dev, QString &path, QString &err) -{ - if ( dev->type() == "Serial" ) { - err = "SRM download not supported by device " + dev->id(); - return false; - } - path = dev->name(); - return true; -} - bool SrmDevice::download(CommPortPtr dev, const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err) { - // Totally ghetto, proof-of-concept integration with srmio. + srmio_io_t io( NULL ); + srmio_pc_t pc( NULL ); + srmio_data_t data( NULL ); + FILE *fh( NULL ); + QDateTime startTime; + 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); + + 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; + } + + } else { + err = tr("device type %1 is unsupported") + .arg(dev->type()); 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; + + 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( ! srmdata.d->cused ){ - err = "no data available"; - return false; + if( ! pc ){ + err = tr("failed to allocate Powercontrol handle: %1") + .arg(strerror(errno)); + goto fail; } - if (srm_data_write_srm7(srmdata.d, tmpname.toAscii().constData()) < 0) { - err = "Couldn't write to file " + tmpname + ": " + strerror(errno); - return false; + + statusCallback( tr("opening device %1").arg(dev->name()) ); + if( ! srmio_io_open(io ) ){ + err = tr("Couldn't open device %1: %2") + .arg(dev->name()) + .arg(strerror(errno)); + goto fail; } - QDateTime startTime; - startTime.setTime_t( srmdata.d->chunks[0]->time / 10 ); + + if( ! srmio_pc_set_device( pc, io ) ){ + err = tr("failed to set Powercontrol io handle: %1") + .arg(strerror(errno)); + goto fail; + } + + statusCallback( tr("initializing PowerControl...") ); + if( ! srmio_pc_open( pc ) ){ + err = tr("failed to initialize Powercontrol communication: %1") + .arg(strerror(errno)); + goto fail; + } + + data = srmio_data_new(); + if( ! data ){ + err = tr("failed to allocate data handle: %1") + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_pc_xfer_all( pc, data, progress, NULL ) ){ + err = tr( "failed to download data from Powercontrol: %1") + .arg(strerror(errno)); + goto fail; + } + + statusCallback( tr("got %1 records").arg(data->cused) ); + + if( ! data->cused ){ + err = tr("no data available"); + goto fail; + } + + fh = fopen( tmpname.toAscii().constData(), "w" ); + if( ! fh ){ + err = tr( "failed to open file %1: %2") + .arg(tmpname) + .arg(strerror(errno)); + goto fail; + } + + if( ! srmio_file_srm7_write(data, fh) ){ + err = tr("Couldn't write to file %1: %2") + .arg(tmpname) + .arg(strerror(errno)); + goto fail; + } + + startTime.setTime_t( 0.1 * data->chunks[0]->time ); filename = startTime.toString("yyyy_MM_dd_hh_mm_ss") + ".srm"; + srmio_data_free( data ); + srmio_pc_free( pc ); + srmio_io_free( io ); + fclose( fh ); return true; + +fail: + if( data ) srmio_data_free( data ); + if( pc ) srmio_pc_free( pc ); + if( io ) srmio_io_free( io ); + if( fh ) fclose( fh ); + return false; } void SrmDevice::cleanup(CommPortPtr dev) { - QString path, err; - if (!dev2path(dev, path, err)) - assert(false); + QString err; + srmio_io_t io( NULL ); + srmio_pc_t pc( NULL ); + 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."); + "&Erase", "&Cancel", "", 1, 1) != 0) + return; + + + 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)); + goto cleanup; } + + } else { + err = tr("device type %1 is unsupported") + .arg(dev->type()); + goto cleanup; } + + if( ! srmio_io_open(io ) ){ + err = tr("Couldn't open device %1: %2") + .arg(dev->name()) + .arg(strerror(errno)); + goto cleanup; + } + + 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 cleanup; + } + if( ! pc ){ + err = tr("failed to allocate Powercontrol handle: %1") + .arg(strerror(errno)); + goto cleanup; + } + + if( ! srmio_pc_set_device( pc, io ) ){ + err = tr("failed to set Powercontrol io handle: %1") + .arg(strerror(errno)); + goto cleanup; + } + + if( ! srmio_pc_open( pc ) ){ + err = tr("failed to initialize Powercontrol communication: %1") + .arg(strerror(errno)); + goto cleanup; + } + + if( ! srmio_pc_cmd_clear( pc ) ){ + err = tr("failed to clear Powercontrol memory: %1") + .arg(strerror(errno)); + goto cleanup; + } + +cleanup: + if( err.length() ) + QMessageBox::warning(0, "Error", err ); + + if( pc ) srmio_pc_free( pc ); + if( io ) srmio_io_free( io ); } From 5013235186973d68819484fa70bc6aa1977ea5ec Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Thu, 21 Jul 2011 23:09:24 +0200 Subject: [PATCH 05/10] CommPort: added isOpen method required to allow Devices to safely close CommPorts after use. This avoids problems when re-using a device without closing DownloadRideDialog. --- src/CommPort.h | 3 ++- src/D2XX.cpp | 22 ++++++++++++++-------- src/D2XX.h | 3 ++- src/Serial.cpp | 27 ++++++++++++++++----------- src/Serial.h | 3 ++- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/CommPort.h b/src/CommPort.h index aec13e46c..1f1ba62a6 100644 --- a/src/CommPort.h +++ b/src/CommPort.h @@ -35,7 +35,8 @@ class CommPort 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; diff --git a/src/D2XX.cpp b/src/D2XX.cpp index bf502da90..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" ) + 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) 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/Serial.cpp b/src/Serial.cpp index 6fe43d852..7358b96e8 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -46,7 +46,7 @@ bool SerialRegistered = CommPort::addListFunction(&Serial::myListCommPorts); #ifdef Q_OS_WIN32 -Serial::Serial(const QString &path) : CommPort("Serial"), path(path), isOpen(false) +Serial::Serial(const QString &path) : CommPort("Serial"), path(path), _isOpen(false) { } #else @@ -57,12 +57,17 @@ Serial::Serial(const QString &path) : CommPort("Serial"), path(path), fd(-1) 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 } 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); From 16d775741178d97012a458846d6990458e86bdc7 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Fri, 22 Jul 2011 00:15:24 +0200 Subject: [PATCH 06/10] split Device class to allow keeping download state right now there's just one object for each Device type througout the whole app. This forbids keeping actual state in the Device object during download/cleanup. This patch puts the list of supported Devices into a seperate class. Actual Device objects are now created dynamically. This is necessary for the upcoming Download changes. --- src/Device.cpp | 28 ++++++++++++++++++---------- src/Device.h | 35 ++++++++++++++++++++++++++++------- src/DownloadRideDialog.cpp | 21 ++++++++++++--------- src/MacroDevice.cpp | 14 ++++++++++---- src/MacroDevice.h | 14 +++++++++++--- src/PowerTapDevice.cpp | 12 +++++++++--- src/PowerTapDevice.h | 13 ++++++++++--- src/SrmDevice.cpp | 16 +++++++++------- src/SrmDevice.h | 25 +++++++++++++++++++++---- 9 files changed, 128 insertions(+), 50 deletions(-) diff --git a/src/Device.cpp b/src/Device.cpp index 9dba014f2..f60880000 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,34 @@ inline DevicesMap & devices() { if (devicesPtr == NULL) - devicesPtr = new QMap; + devicesPtr = new QMap; return *devicesPtr; } +Device::~Device() +{ + if( dev->isOpen() ) + dev->close(); +} + 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..8b51594e7 100644 --- a/src/Device.h +++ b/src/Device.h @@ -23,22 +23,43 @@ #include "CommPort.h" #include +struct Device; +typedef boost::shared_ptr DevicePtr; + struct Device { - virtual ~Device() {} + Device( CommPortPtr dev ) : dev( dev ) {}; + virtual ~Device(); typedef boost::function StatusCallback; - virtual QString downloadInstructions() const = 0; - virtual bool download(CommPortPtr dev, const QDir &tmpdir, + virtual bool download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err) = 0; - virtual void cleanup(CommPortPtr dev) { (void) dev; } - static QList deviceTypes(); - static Device &device(const QString &deviceType); - static bool addDevice(const QString &deviceType, Device *device); + virtual void cleanup() { (void) dev; }; + +protected: + CommPortPtr dev; + }; +struct Devices; +typedef boost::shared_ptr DevicesPtr; + +struct Devices +{ + virtual DevicePtr newDevice( CommPortPtr ) = 0; + + virtual bool canCleanup() { return false; }; + virtual QString downloadInstructions() { 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 b0dee63a3..943c83fd9 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -42,7 +42,7 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow, label->setIndent(10); deviceCombo = new QComboBox(this); - QList deviceTypes = Device::deviceTypes(); + QList deviceTypes = Devices::typeNames(); assert(deviceTypes.size() > 0); BOOST_FOREACH(QString device, deviceTypes) { deviceCombo->addItem(device); @@ -87,8 +87,8 @@ DownloadRideDialog::setReadyInstruct() eraseRideButton->setEnabled(false); } 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."); else @@ -152,9 +152,11 @@ DownloadRideDialog::downloadClicked() assert(dev); QString err; QString tmpname, filename; - Device &device = Device::device(deviceCombo->currentText()); - if (!device.download( - dev, home, tmpname, filename, + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + DevicePtr device = devtype->newDevice( dev ); + + if (!device->download( + home, tmpname, filename, boost::bind(&DownloadRideDialog::statusCallback, this, _1), err)) { if (cancelled) { @@ -212,7 +214,7 @@ DownloadRideDialog::downloadClicked() QMessageBox::information(this, tr("Success"), tr("Download complete.")); mainWindow->addRide(filename); - device.cleanup(dev); + device->cleanup(); downloadInProgress = false; accept(); @@ -233,8 +235,9 @@ DownloadRideDialog::eraseClicked() } } assert(dev); - Device &device = Device::device(deviceCombo->currentText()); - device.cleanup(dev); + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); + DevicePtr device = devtype->newDevice( dev ); + device->cleanup(); downloadInProgress = false; accept(); } diff --git a/src/MacroDevice.cpp b/src/MacroDevice.cpp index 5c553cf00..8a2f7880d 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,7 +91,7 @@ hexHex2Int(char c, char c2) } bool -MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, +MacroDevice::download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err) { @@ -239,7 +245,7 @@ MacroDevice::download(CommPortPtr dev, const QDir &tmpdir, } void -MacroDevice::cleanup(CommPortPtr dev){ +MacroDevice::cleanup(){ if (MACRO_DEBUG) printf("Erase all records on computer\n"); QString err; diff --git a/src/MacroDevice.h b/src/MacroDevice.h index 50edbc384..2a6476cee 100644 --- a/src/MacroDevice.h +++ b/src/MacroDevice.h @@ -6,15 +6,23 @@ 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, + virtual bool download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err); - virtual void cleanup(CommPortPtr dev); + virtual void cleanup(); }; class MacroPacket diff --git a/src/PowerTapDevice.cpp b/src/PowerTapDevice.cpp index 71f897be7..098fa59ed 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,7 +120,7 @@ readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err) } bool -PowerTapDevice::download(CommPortPtr dev, const QDir &tmpdir, +PowerTapDevice::download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err) { diff --git a/src/PowerTapDevice.h b/src/PowerTapDevice.h index 0ffdded5d..3a3025d59 100644 --- a/src/PowerTapDevice.h +++ b/src/PowerTapDevice.h @@ -20,13 +20,20 @@ #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, + PowerTapDevice( CommPortPtr dev ) : + Device( dev ) {}; + + virtual bool download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err); }; diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index 0ca1fd60e..c2e6a6962 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -27,15 +27,17 @@ #define tr(s) QObject::tr(s) -static bool srm5Registered = Device::addDevice("SRM PCV", new SrmDevice( 5 )); -static bool srm7Registered = Device::addDevice("SRM PCVI/7", new SrmDevice( 7 )); +static bool srm5Registered = + Devices::addType("SRM PCV", DevicesPtr(new SrmDevices( 5 )) ); +static bool srm7Registered = + Devices::addType("SRM PCVI/7", DevicesPtr(new SrmDevices( 7 ))); static Device::StatusCallback cb; -QString -SrmDevice::downloadInstructions() const +DevicePtr +SrmDevices::newDevice( CommPortPtr dev ) { - return ""; // no particular instructions for SRM + return DevicePtr( new SrmDevice( dev, protoVersion)); } static void progress( size_t total, size_t done, void *user_data ) @@ -64,7 +66,7 @@ get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err) } bool -SrmDevice::download(CommPortPtr dev, const QDir &tmpdir, +SrmDevice::download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err) { @@ -188,7 +190,7 @@ fail: } void -SrmDevice::cleanup(CommPortPtr dev) +SrmDevice::cleanup() { QString err; srmio_io_t io( NULL ); diff --git a/src/SrmDevice.h b/src/SrmDevice.h index dcfc8e722..33b91e402 100644 --- a/src/SrmDevice.h +++ b/src/SrmDevice.h @@ -20,16 +20,33 @@ #define _GC_SrmDevice_h 1 #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, + SrmDevice( CommPortPtr dev, int protoVersion ) : + Device( dev ), + protoVersion( protoVersion ) {}; + + virtual bool download( const QDir &tmpdir, QString &tmpname, QString &filename, StatusCallback statusCallback, QString &err); - virtual void cleanup(CommPortPtr dev); + + virtual void cleanup(); + +private: + int protoVersion; }; #endif // _GC_SrmDevice_h From 3466ced7d403d1a6fc749a0dcd763f544b04f348 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Thu, 21 Jul 2011 19:11:53 +0200 Subject: [PATCH 07/10] SrmDevice: re-use open code made srmio handles attributes of the SrmDevice class and added methods to allocate/open/close/free these handles. So download() + cleanup() can use the same code. As a goodie the handles stay open after download and the second initialization on cleanup() is skipped - saving some time. Well, assuming the caller reuses the same SrmDevice instance. SrmDevice destructor takes care of closing/freeing the srmio handles automagically. --- src/SrmDevice.cpp | 147 ++++++++++++++++++++++------------------------ src/SrmDevice.h | 12 +++- 2 files changed, 80 insertions(+), 79 deletions(-) diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index c2e6a6962..80b3f0b9c 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -65,22 +65,14 @@ get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err) return true; } -bool -SrmDevice::download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) +SrmDevice::~SrmDevice() { - srmio_io_t io( NULL ); - srmio_pc_t pc( NULL ); - srmio_data_t data( NULL ); - FILE *fh( NULL ); - QDateTime startTime; - - cb = statusCallback; - - if (!get_tmpname(tmpdir, tmpname, err)) - return false; + close(); +} +bool +SrmDevice::open( QString &err ) +{ if( dev->type() == "Serial" ){ io = srmio_ios_new( dev->name().toAscii().constData() ); if( ! io ){ @@ -116,7 +108,6 @@ SrmDevice::download( const QDir &tmpdir, goto fail; } - statusCallback( tr("opening device %1").arg(dev->name()) ); if( ! srmio_io_open(io ) ){ err = tr("Couldn't open device %1: %2") .arg(dev->name()) @@ -130,13 +121,66 @@ SrmDevice::download( const QDir &tmpdir, goto fail; } - statusCallback( tr("initializing PowerControl...") ); 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 ) +{ + if( pc ){ + srmio_pc_free( pc ); + pc = NULL; + } + + if( io ){ + srmio_io_free( io ); + io = NULL; + } + + is_open = false; + return true; +} + +bool +SrmDevice::download(const QDir &tmpdir, + QString &tmpname, QString &filename, + StatusCallback statusCallback, QString &err) +{ + srmio_data_t data( NULL ); + FILE *fh( NULL ); + QDateTime startTime; + + cb = statusCallback; + + if (!get_tmpname(tmpdir, tmpname, err)) + return false; + + if( ! is_open ){ + statusCallback( tr("opening device %1").arg(dev->name()) ); + if( ! open( err ) ) + return false; + } + data = srmio_data_new(); if( ! data ){ err = tr("failed to allocate data handle: %1") @@ -176,16 +220,13 @@ SrmDevice::download( const QDir &tmpdir, filename = startTime.toString("yyyy_MM_dd_hh_mm_ss") + ".srm"; srmio_data_free( data ); - srmio_pc_free( pc ); - srmio_io_free( io ); fclose( fh ); return true; fail: if( data ) srmio_data_free( data ); - if( pc ) srmio_pc_free( pc ); - if( io ) srmio_io_free( io ); if( fh ) fclose( fh ); + close(); return false; } @@ -193,8 +234,6 @@ void SrmDevice::cleanup() { QString err; - srmio_io_t io( NULL ); - srmio_pc_t pc( NULL ); if (QMessageBox::question(0, "Powercontrol", "Erase ride from device memory?", @@ -202,58 +241,9 @@ SrmDevice::cleanup() return; - 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)); + if( ! is_open ){ + if( ! open( err ) ) goto cleanup; - } - - } else { - err = tr("device type %1 is unsupported") - .arg(dev->type()); - goto cleanup; - } - - if( ! srmio_io_open(io ) ){ - err = tr("Couldn't open device %1: %2") - .arg(dev->name()) - .arg(strerror(errno)); - goto cleanup; - } - - 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 cleanup; - } - if( ! pc ){ - err = tr("failed to allocate Powercontrol handle: %1") - .arg(strerror(errno)); - goto cleanup; - } - - if( ! srmio_pc_set_device( pc, io ) ){ - err = tr("failed to set Powercontrol io handle: %1") - .arg(strerror(errno)); - goto cleanup; - } - - if( ! srmio_pc_open( pc ) ){ - err = tr("failed to initialize Powercontrol communication: %1") - .arg(strerror(errno)); - goto cleanup; } if( ! srmio_pc_cmd_clear( pc ) ){ @@ -262,11 +252,12 @@ SrmDevice::cleanup() goto cleanup; } -cleanup: - if( err.length() ) - QMessageBox::warning(0, "Error", err ); + return; - if( pc ) srmio_pc_free( pc ); - if( io ) srmio_io_free( io ); +cleanup: + if( err.length() ){ + QMessageBox::warning(0, "Error", err ); + close(); + } } diff --git a/src/SrmDevice.h b/src/SrmDevice.h index 33b91e402..3bdf2f9b4 100644 --- a/src/SrmDevice.h +++ b/src/SrmDevice.h @@ -18,6 +18,7 @@ #ifndef _GC_SrmDevice_h #define _GC_SrmDevice_h 1 +#include #include "GoldenCheetah.h" #include "Device.h" @@ -37,7 +38,10 @@ struct SrmDevice : public Device { SrmDevice( CommPortPtr dev, int protoVersion ) : Device( dev ), - protoVersion( protoVersion ) {}; + protoVersion( protoVersion ), + is_open( false ), + io( NULL ), pc( NULL ) { }; + ~SrmDevice(); virtual bool download( const QDir &tmpdir, QString &tmpname, QString &filename, @@ -47,6 +51,12 @@ struct SrmDevice : public Device 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 From 50fef04b755e3caaa117a0a8ed5983626e9ff01a Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Thu, 21 Jul 2011 23:02:37 +0200 Subject: [PATCH 08/10] allow download of multiple files - for PC7 The new powercontrols have a lot more memory and they allow you to selectively download the recorded "rides". Looking at srmwin, this seems to be the suggested way of operation. (i.e. record multiple workouts, download only the "new" ones). Furthermore, the SRM file format has some limits (timespan, total number of records), that make it inapropriate to store "all rides" into one file and split it later. So download now - tries to get a list of rides of the device - if it gets any, the user can get prompted to choose which to download. - let device download (selected/all) rides, split if necessary and return a list with tmp filename, start time, file extension. - download dialog builds new filename based on time, prompts user for overwriting when file exists and renames file. The download Dialog now stays open, so user can read the status messages and click "cleanup". This avoids many of the anoying message boxes we had in the Srm download. Cleanup's user interaction (confirmation, errors) was moved from the individual device to DownloadDialog, as well. --- src/Device.cpp | 25 +++ src/Device.h | 34 ++++- src/DownloadRideDialog.cpp | 301 +++++++++++++++++++++++++++---------- src/DownloadRideDialog.h | 25 ++- src/MacroDevice.cpp | 80 +++++----- src/MacroDevice.h | 9 +- src/PowerTapDevice.cpp | 59 ++++---- src/PowerTapDevice.h | 7 +- src/SrmDevice.cpp | 79 ++++++---- src/SrmDevice.h | 13 +- 10 files changed, 439 insertions(+), 193 deletions(-) diff --git a/src/Device.cpp b/src/Device.cpp index f60880000..77d7889f5 100644 --- a/src/Device.cpp +++ b/src/Device.cpp @@ -38,6 +38,31 @@ Device::~Device() 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 Devices::typeNames() { diff --git a/src/Device.h b/src/Device.h index 8b51594e7..6e8876330 100644 --- a/src/Device.h +++ b/src/Device.h @@ -23,6 +23,21 @@ #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; @@ -31,15 +46,24 @@ struct 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 bool preview( StatusCallback statusCallback, QString &err ); + virtual QList &rides(); virtual bool download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) = 0; + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) = 0; - virtual void cleanup() { (void) dev; }; + virtual bool cleanup( QString &err ); protected: + QList rideList; CommPortPtr dev; }; @@ -52,7 +76,7 @@ struct Devices virtual DevicePtr newDevice( CommPortPtr ) = 0; virtual bool canCleanup() { return false; }; - virtual QString downloadInstructions() { return ""; }; + virtual QString downloadInstructions() const { return ""; }; static QList typeNames(); diff --git a/src/DownloadRideDialog.cpp b/src/DownloadRideDialog.cpp index 943c83fd9..d8b751b84 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -26,20 +26,29 @@ #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 QLabel(this); + statusLabel->setIndent(10); + statusLabel->setTextFormat(Qt::PlainText); + // XXX: make statusLabel scrollable + + // 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 = Devices::typeNames(); @@ -47,30 +56,37 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow, 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 +95,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->setText(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 { DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); QString inst = devtype->downloadInstructions(); if (inst.size() == 0) - label->setText("Click Download to begin downloading."); + statusLabel->setText("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->setText(inst + ", \nthen click Download."); + + updateAction( actionIdle ); } } @@ -113,7 +129,7 @@ DownloadRideDialog::scanCommPorts() } for (int i = 0; i < devList.size(); ++i) { portCombo->addItem(devList[i]->id()); - // Hack: SRM PCV download cables use the PL2203 chipset. If the + // 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,20 +144,89 @@ 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->setText(statusLabel->text() + "\n" + 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->setText( "" ); + CommPortPtr dev; for (int i = 0; i < devList.size(); ++i) { if (devList[i]->id() == portCombo->currentText()) { @@ -151,13 +236,33 @@ DownloadRideDialog::downloadClicked() } assert(dev); QString err; - QString tmpname, filename; + QList files; + DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); DevicePtr device = devtype->newDevice( dev ); - if (!device->download( - home, tmpname, filename, - boost::bind(&DownloadRideDialog::statusCallback, this, _1), err)) + 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"), @@ -167,66 +272,90 @@ 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(); - - downloadInProgress = false; - accept(); + updateAction( actionIdle ); } void DownloadRideDialog::eraseClicked() { - downloadButton->setEnabled(false); - eraseRideButton->setEnabled(false); - rescanButton->setEnabled(false); - downloadInProgress = true; + updateAction( actionCleaning ); + + statusLabel->setText( "" ); + updateProgress( "" ); + CommPortPtr dev; for (int i = 0; i < devList.size(); ++i) { if (devList[i]->id() == portCombo->currentText()) { @@ -237,17 +366,35 @@ DownloadRideDialog::eraseClicked() assert(dev); DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); DevicePtr device = devtype->newDevice( dev ); - device->cleanup(); - downloadInProgress = false; - accept(); + + 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..86160860a 100644 --- a/src/DownloadRideDialog.h +++ b/src/DownloadRideDialog.h @@ -34,26 +34,41 @@ 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; + QLabel *statusLabel, *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 8a2f7880d..eae07f8d2 100644 --- a/src/MacroDevice.cpp +++ b/src/MacroDevice.cpp @@ -92,8 +92,11 @@ hexHex2Int(char c, char c2) bool MacroDevice::download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { if (MACRO_DEBUG) printf("download O-Synce Macro"); @@ -102,8 +105,7 @@ MacroDevice::download( 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); @@ -111,8 +113,7 @@ MacroDevice::download( 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; @@ -136,31 +137,12 @@ MacroDevice::download( 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); @@ -172,14 +154,37 @@ MacroDevice::download( 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; @@ -189,8 +194,7 @@ MacroDevice::download( 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; @@ -212,8 +216,11 @@ MacroDevice::download( 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; @@ -229,7 +236,6 @@ MacroDevice::download( const QDir &tmpdir, } - tmpname = tmp.fileName(); // after close(), tmp.fileName() is "" tmp.close(); dev->close(); @@ -244,12 +250,10 @@ MacroDevice::download( const QDir &tmpdir, return true; } -void -MacroDevice::cleanup(){ +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; } @@ -268,6 +272,8 @@ MacroDevice::cleanup(){ } dev->close(); + + return true; } // -------------------------------------- diff --git a/src/MacroDevice.h b/src/MacroDevice.h index 2a6476cee..4f0babdd4 100644 --- a/src/MacroDevice.h +++ b/src/MacroDevice.h @@ -19,10 +19,13 @@ struct MacroDevice : public Device Device( dev ) {}; virtual bool download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); - virtual void cleanup(); + virtual bool cleanup( QString &err ); }; class MacroPacket diff --git a/src/PowerTapDevice.cpp b/src/PowerTapDevice.cpp index 098fa59ed..55982de13 100644 --- a/src/PowerTapDevice.cpp +++ b/src/PowerTapDevice.cpp @@ -121,8 +121,11 @@ readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err) bool PowerTapDevice::download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { if (!dev->open(err)) { err = "ERROR: open failed: " + err; @@ -130,7 +133,6 @@ PowerTapDevice::download( const QDir &tmpdir, } // make several attempts at reading the version int attempts = 3; - QString cbtext; int veridx = -1; int version_len; char vbuf[256]; @@ -140,8 +142,8 @@ PowerTapDevice::download( 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; } @@ -172,8 +174,8 @@ PowerTapDevice::download( 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; } @@ -198,12 +200,11 @@ PowerTapDevice::download( 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); @@ -265,11 +266,11 @@ PowerTapDevice::download( 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; } @@ -285,11 +286,20 @@ PowerTapDevice::download( 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); @@ -304,7 +314,9 @@ PowerTapDevice::download( 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; } } @@ -313,21 +325,8 @@ PowerTapDevice::download( 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 3a3025d59..f67810cef 100644 --- a/src/PowerTapDevice.h +++ b/src/PowerTapDevice.h @@ -34,8 +34,11 @@ struct PowerTapDevice : public Device Device( dev ) {}; virtual bool download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); }; #endif // _GC_PowerTapDevice_h diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index 80b3f0b9c..d0f1bf474 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -32,21 +32,22 @@ static bool srm5Registered = static bool srm7Registered = Devices::addType("SRM PCVI/7", DevicesPtr(new SrmDevices( 7 ))); -static Device::StatusCallback cb; - DevicePtr SrmDevices::newDevice( CommPortPtr dev ) { return DevicePtr( new SrmDevice( dev, protoVersion)); } +static Device::ProgressCallback cb; + static void progress( size_t total, size_t done, void *user_data ) { (void)user_data; - // XXX: better way to pass statusCallback - that's what we have - // "user_data" for... - cb( tr("progress: %1/%1").arg(done).arg(total) ); + // XXX: hack, unnecessary when switching from srmio_pc_xfer_all + cb( tr("progress: %1/%2") + .arg(done) + .arg(total)); } static bool @@ -147,6 +148,8 @@ fail: bool SrmDevice::close( void ) { + rideList.clear(); + if( pc ){ srmio_pc_free( pc ); pc = NULL; @@ -162,17 +165,32 @@ SrmDevice::close( void ) } bool -SrmDevice::download(const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err) +SrmDevice::preview( StatusCallback statusCallback, QString &err ) +{ + (void) statusCallback; + (void) err; + // XXX add preview + return true; +} + +bool +SrmDevice::download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err) { srmio_data_t data( NULL ); FILE *fh( NULL ); QDateTime startTime; - cb = statusCallback; + cb = progressCallback; - if (!get_tmpname(tmpdir, tmpname, err)) + DeviceDownloadFile file; + file.extension = "srm"; + + if (!get_tmpname(tmpdir, file.name, err)) return false; if( ! is_open ){ @@ -188,6 +206,12 @@ SrmDevice::download(const QDir &tmpdir, goto fail; } + if( cancelCallback() ){ + err = tr("download cancelled"); + goto fail; + } + + // XXX use rideList, migrate from xfer_all to chunk_next if( ! srmio_pc_xfer_all( pc, data, progress, NULL ) ){ err = tr( "failed to download data from Powercontrol: %1") .arg(strerror(errno)); @@ -196,28 +220,33 @@ SrmDevice::download(const QDir &tmpdir, 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; } - fh = fopen( tmpname.toAscii().constData(), "w" ); + fh = fopen( file.name.toAscii().constData(), "w" ); if( ! fh ){ err = tr( "failed to open file %1: %2") - .arg(tmpname) + .arg(file.name) .arg(strerror(errno)); goto fail; } if( ! srmio_file_srm7_write(data, fh) ){ err = tr("Couldn't write to file %1: %2") - .arg(tmpname) + .arg(file.name) .arg(strerror(errno)); goto fail; } - startTime.setTime_t( 0.1 * data->chunks[0]->time ); - filename = startTime.toString("yyyy_MM_dd_hh_mm_ss") + ".srm"; + file.startTime.setTime_t( 0.1 * data->chunks[0]->time ); + files << file; srmio_data_free( data ); fclose( fh ); @@ -230,17 +259,9 @@ fail: return false; } -void -SrmDevice::cleanup() +bool +SrmDevice::cleanup( QString &err ) { - QString err; - - if (QMessageBox::question(0, "Powercontrol", - "Erase ride from device memory?", - "&Erase", "&Cancel", "", 1, 1) != 0) - return; - - if( ! is_open ){ if( ! open( err ) ) goto cleanup; @@ -252,12 +273,10 @@ SrmDevice::cleanup() goto cleanup; } - return; + return true; cleanup: - if( err.length() ){ - QMessageBox::warning(0, "Error", err ); - close(); - } + close(); + return false; } diff --git a/src/SrmDevice.h b/src/SrmDevice.h index 3bdf2f9b4..d7266b621 100644 --- a/src/SrmDevice.h +++ b/src/SrmDevice.h @@ -43,11 +43,16 @@ struct SrmDevice : public Device io( NULL ), pc( NULL ) { }; ~SrmDevice(); - virtual bool download( const QDir &tmpdir, - QString &tmpname, QString &filename, - StatusCallback statusCallback, QString &err); + virtual bool preview( StatusCallback statusCallback, QString &err ); - virtual void cleanup(); + virtual bool download( const QDir &tmpdir, + QList &files, + CancelCallback cancelCallback, + StatusCallback statusCallback, + ProgressCallback progressCallback, + QString &err); + + virtual bool cleanup( QString &err ); private: int protoVersion; From 07e100795743a146d14c1e2f82d478e96193ea22 Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Sun, 24 Jul 2011 17:10:54 +0200 Subject: [PATCH 09/10] Download: made status text scrollable Previous commits turned the status/"Instructions" label into a log. This allows to show a lot more information that simplify troubleshooting download issues. To make it behave a bit more as expected, this change turns the label into a readonly QTextEdit. --- src/DownloadRideDialog.cpp | 19 +++++++++---------- src/DownloadRideDialog.h | 3 ++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DownloadRideDialog.cpp b/src/DownloadRideDialog.cpp index d8b751b84..35ecfac12 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -39,10 +39,9 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow, portCombo = new QComboBox(this); - statusLabel = new QLabel(this); - statusLabel->setIndent(10); - statusLabel->setTextFormat(Qt::PlainText); - // XXX: make statusLabel scrollable + 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. @@ -98,7 +97,7 @@ DownloadRideDialog::setReadyInstruct() progressLabel->setText(""); if (portCombo->count() == 0) { - statusLabel->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.")); updateAction( actionMissing ); @@ -107,9 +106,9 @@ DownloadRideDialog::setReadyInstruct() DevicesPtr devtype = Devices::getType(deviceCombo->currentText()); QString inst = devtype->downloadInstructions(); if (inst.size() == 0) - statusLabel->setText("Click Download to begin downloading."); + statusLabel->setPlainText("Click Download to begin downloading."); else - statusLabel->setText(inst + ", \nthen click Download."); + statusLabel->setPlainText(inst + ", \nthen click Download."); updateAction( actionIdle ); } @@ -198,7 +197,7 @@ DownloadRideDialog::updateAction( downloadAction newAction ) void DownloadRideDialog::updateStatus(const QString &statusText) { - statusLabel->setText(statusLabel->text() + "\n" + statusText); + statusLabel->append(statusText); QCoreApplication::processEvents(); } @@ -225,7 +224,7 @@ DownloadRideDialog::downloadClicked() updateAction( actionDownload ); updateProgress( "" ); - statusLabel->setText( "" ); + statusLabel->setPlainText( "" ); CommPortPtr dev; for (int i = 0; i < devList.size(); ++i) { @@ -353,7 +352,7 @@ DownloadRideDialog::eraseClicked() { updateAction( actionCleaning ); - statusLabel->setText( "" ); + statusLabel->setPlainText( "" ); updateProgress( "" ); CommPortPtr dev; diff --git a/src/DownloadRideDialog.h b/src/DownloadRideDialog.h index 86160860a..252cc7193 100644 --- a/src/DownloadRideDialog.h +++ b/src/DownloadRideDialog.h @@ -54,7 +54,8 @@ class DownloadRideDialog : public QDialog QPushButton *downloadButton, *eraseRideButton, *rescanButton, *cancelButton, *closeButton; QComboBox *portCombo, *deviceCombo; - QLabel *statusLabel, *progressLabel; + QTextEdit *statusLabel; + QLabel *progressLabel; QVector devList; bool cancelled; From dbadd1b3946612f9bd1c9202765774e02742198c Mon Sep 17 00:00:00 2001 From: Rainer Clasen Date: Sun, 24 Jul 2011 12:19:40 +0200 Subject: [PATCH 10/10] SrmDevice: switched to full API implemented preview(). download() now gets only selected blocks. download() splits data into several files. --- src/SrmDevice.cpp | 303 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 262 insertions(+), 41 deletions(-) diff --git a/src/SrmDevice.cpp b/src/SrmDevice.cpp index d0f1bf474..26b3b79f0 100644 --- a/src/SrmDevice.cpp +++ b/src/SrmDevice.cpp @@ -38,18 +38,6 @@ SrmDevices::newDevice( CommPortPtr dev ) return DevicePtr( new SrmDevice( dev, protoVersion)); } -static Device::ProgressCallback cb; - -static void progress( size_t total, size_t done, void *user_data ) -{ - (void)user_data; - - // XXX: hack, unnecessary when switching from srmio_pc_xfer_all - cb( tr("progress: %1/%2") - .arg(done) - .arg(total)); -} - static bool get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err) { @@ -167,10 +155,50 @@ SrmDevice::close( void ) bool SrmDevice::preview( StatusCallback statusCallback, QString &err ) { - (void) statusCallback; - (void) err; - // XXX add preview + struct _srmio_pc_xfer_block_t block; + + if( ! is_open ){ + statusCallback( tr("opening device %1").arg(dev->name()) ); + if( ! open( err ) ) + 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; + } + + 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( ! srmio_pc_xfer_finish( pc ) ){ + err = tr("download failed: %1") + .arg(strerror(errno)); + goto fail; + } + + return true; + +fail: + rideList.clear(); + return false; } bool @@ -181,17 +209,18 @@ SrmDevice::download( const QDir &tmpdir, ProgressCallback progressCallback, QString &err) { + unsigned firmware; + srmio_io_baudrate_t baudrateId; + unsigned baudrate; + struct _srmio_pc_xfer_block_t block; srmio_data_t data( NULL ); - FILE *fh( NULL ); - QDateTime startTime; - - cb = progressCallback; - - DeviceDownloadFile file; - file.extension = "srm"; - - if (!get_tmpname(tmpdir, file.name, err)) - return false; + 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()) ); @@ -199,6 +228,33 @@ SrmDevice::download( const QDir &tmpdir, 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") @@ -211,9 +267,136 @@ SrmDevice::download( const QDir &tmpdir, goto fail; } - // XXX use rideList, migrate from xfer_all to chunk_next - if( ! srmio_pc_xfer_all( pc, data, progress, NULL ) ){ - err = tr( "failed to download data from Powercontrol: %1") + 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; } @@ -230,31 +413,69 @@ SrmDevice::download( const QDir &tmpdir, goto fail; } - fh = fopen( file.name.toAscii().constData(), "w" ); - if( ! fh ){ - err = tr( "failed to open file %1: %2") - .arg(file.name) + splitList = srmio_data_split( data, splitGap, 1000 ); + if( ! splitList ){ + err = tr("Couldn't split data: %1") .arg(strerror(errno)); goto fail; } - if( ! srmio_file_srm7_write(data, fh) ){ - err = tr("Couldn't write to file %1: %2") - .arg(file.name) - .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 ); + } - file.startTime.setTime_t( 0.1 * data->chunks[0]->time ); - files << file; + for( split = splitList; *split; ++split ) + srmio_data_free( *split ); + free(splitList); srmio_data_free( data ); - fclose( fh ); return true; +fail1: + srmio_pc_xfer_finish(pc); + fail: if( data ) srmio_data_free( data ); - if( fh ) fclose( fh ); + if( splitList ){ + for( split = splitList; *split; ++split ) + srmio_data_free( *split ); + free(splitList); + } close(); return false; }