Files
GoldenCheetah/src/DownloadRideDialog.cpp
2009-08-09 15:33:41 -07:00

288 lines
9.7 KiB
C++

/*
* $Id: DownloadRideDialog.cpp,v 1.4 2006/08/11 20:02:13 srhea Exp $
*
* Copyright (c) 2006-2008 Sean C. Rhea (srhea@srhea.net)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "DownloadRideDialog.h"
#include "MainWindow.h"
#include "PowerTapDevice.h"
#include "PowerTapUtil.h"
#include <assert.h>
#include <errno.h>
#include <QtGui>
#include <boost/bind.hpp>
DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow,
const QDir &home) :
mainWindow(mainWindow), home(home), cancelled(false),
downloadInProgress(false)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle("Download Ride Data");
listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
QLabel *availLabel = new QLabel(tr("Available devices:"), this);
QLabel *instructLabel = new QLabel(tr("Instructions:"), this);
label = new QLabel(this);
label->setIndent(10);
downloadButton = new QPushButton(tr("&Download"), this);
rescanButton = new QPushButton(tr("&Rescan"), this);
cancelButton = new QPushButton(tr("&Cancel"), this);
connect(downloadButton, SIGNAL(clicked()), this, SLOT(downloadClicked()));
connect(rescanButton, SIGNAL(clicked()), this, SLOT(scanCommPorts()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(listWidget,
SIGNAL(itemSelectionChanged()), this, SLOT(setReadyInstruct()));
connect(listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
this, SLOT(downloadClicked()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(downloadButton);
buttonLayout->addWidget(rescanButton);
buttonLayout->addWidget(cancelButton);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(availLabel);
mainLayout->addWidget(listWidget);
mainLayout->addWidget(instructLabel);
mainLayout->addWidget(label);
mainLayout->addLayout(buttonLayout);
scanCommPorts();
}
void
DownloadRideDialog::setReadyInstruct()
{
int selected = listWidget->selectedItems().size();
assert(selected <= 1);
if (selected == 0) {
if (listWidget->count() > 1) {
label->setText(tr("Select the device from the above list from\n"
"which you would like to download a ride."));
}
else {
label->setText(tr("No devices found. Make sure the PowerTap\n"
"unit is plugged into the computer's USB port,\n"
"then click \"Rescan\" to check again."));
}
downloadButton->setEnabled(false);
}
else {
label->setText(tr("Make sure the PowerTap unit is turned on,\n"
"and that the screen display says, \"Host\",\n"
"then click Download to begin downloading."));
downloadButton->setEnabled(true);
}
}
void
DownloadRideDialog::scanCommPorts()
{
listWidget->clear();
QString err;
devList = CommPort::listCommPorts(err);
if (err != "") {
QString msg = "Warning:\n\n" + err + "You may need to (re)install "
"the FTDI drivers before downloading.";
QMessageBox::warning(0, "Error Loading Device Drivers", msg,
QMessageBox::Ok, QMessageBox::NoButton);
}
for (int i = 0; i < devList.size(); ++i)
new QListWidgetItem(devList[i]->name(), listWidget);
if (listWidget->count() > 0) {
listWidget->setCurrentRow(0);
downloadButton->setFocus();
}
setReadyInstruct();
}
bool
DownloadRideDialog::statusCallback(const QString &statusText)
{
label->setText(statusText);
QCoreApplication::processEvents();
return !cancelled;
}
void
DownloadRideDialog::downloadClicked()
{
downloadButton->setEnabled(false);
rescanButton->setEnabled(false);
downloadInProgress = true;
CommPortPtr dev;
for (int i = 0; i < devList.size(); ++i) {
if (devList[i]->name() == listWidget->currentItem()->text()) {
dev = devList[i];
break;
}
}
assert(dev);
QString err;
QByteArray version;
if (!PowerTapDevice::download(
dev, version, records,
boost::bind(&DownloadRideDialog::statusCallback, this, _1), err))
{
if (cancelled) {
QMessageBox::information(this, tr("Download canceled"),
tr("Cancel clicked by user."));
cancelled = false;
}
else {
QMessageBox::information(this, tr("Download failed"), err);
}
downloadInProgress = false;
reject();
return;
}
QString filename, filepath;
struct tm time;
unsigned char *buf = records.data();
bool bIsVer81 = PowerTapUtil::is_Ver81(buf);
for (int i = 0; i < records.size(); i += 6) {
if (PowerTapUtil::is_time(buf + i, bIsVer81)) {
PowerTapUtil::unpack_time(buf + i, &time, bIsVer81);
char tmp[32];
sprintf(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 = tmp;
filepath = home.absolutePath() + "/" + filename;
FILE *out = fopen(filepath.toAscii().constData(), "r");
if (out) {
fclose(out);
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;
}
}
break;
}
}
QString tmpname;
{
// QTemporaryFile doesn't actually close the file on .close(); it
// closes the file when in its destructor. On Windows, we can't
// rename an open file. So let tmp go out of scope before calling
// rename.
QString tmpl = home.absoluteFilePath(".ptdl.XXXXXX");
QTemporaryFile tmp(tmpl);
tmp.setAutoRemove(false);
if (!tmp.open()) {
QMessageBox::critical(this, tr("Error"),
tr("Failed to create temporary file ")
+ tmpl + ": " + tmp.error());
reject();
return;
}
QTextStream os(&tmp);
os << hex;
os.setPadChar('0');
struct tm time;
bool time_set = false;
unsigned char *data = records.data();
for (int i = 0; i < records.size(); i += 6) {
if (data[i] == 0 && !bIsVer81)
continue;
for (int j = 0; j < 6; ++j) {
os.setFieldWidth(2);
os << data[i+j];
os.setFieldWidth(1);
os << ((j == 5) ? "\n" : " ");
}
if (!time_set && PowerTapUtil::is_time(data + i, bIsVer81)) {
PowerTapUtil::unpack_time(data + i, &time, bIsVer81);
time_set = true;
}
}
if (!time_set) {
QMessageBox::critical(this, tr("Error"),
tr("Failed to find ride time."));
tmp.setAutoRemove(true);
reject();
return;
}
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);
}
#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();
}
}
#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;
}
QMessageBox::information(this, tr("Success"), tr("Download complete."));
mainWindow->addRide(filename);
downloadInProgress = false;
accept();
}
void
DownloadRideDialog::cancelClicked()
{
if (!downloadInProgress)
reject();
else
cancelled = true;
}