From 809696d9b363be0fa0d8aa3b9b228b341f1e46d5 Mon Sep 17 00:00:00 2001 From: Joern Date: Sun, 12 Oct 2014 19:26:17 +0200 Subject: [PATCH] Auto/Stealth Ride Import when opening an Athlete ... import Ride Files automatically from a defined Directory per Athlete ... runs silently/without "Import Rides" Widget if the files can be imported/copied/... without error or warning ... Preferences->Athlete- defines ... the directory - per Athlete and- if the function is active at all (default is OFF) ... the error handling ... report back ALL errors and warnings by opening the RideImportWizard popup ... report back Errors and Warnings - but ignore the "File exists" warning (so that the import directory can be continously supplied with new files, without the need to remove the already imported ones) --- src/Athlete.cpp | 40 ++++++++++++++++++++++ src/Athlete.h | 4 +++ src/MainWindow.cpp | 11 +++++++ src/MainWindow.h | 3 ++ src/Pages.cpp | 35 ++++++++++++++++++++ src/Pages.h | 6 ++++ src/RideImportWizard.cpp | 71 +++++++++++++++++++++++++++++++++++++--- src/RideImportWizard.h | 3 ++ src/Settings.h | 2 ++ src/main.cpp | 2 ++ 10 files changed, 172 insertions(+), 5 deletions(-) diff --git a/src/Athlete.cpp b/src/Athlete.cpp index 915b767d3..40fc8e038 100644 --- a/src/Athlete.cpp +++ b/src/Athlete.cpp @@ -45,6 +45,8 @@ #include "LTMSettings.h" #include "Route.h" #include "RouteWindow.h" +#include "RideImportWizard.h" + #include "GcUpgrade.h" // upgrade wizard #include "GcCrashDialog.h" // recovering from a crash? @@ -501,3 +503,41 @@ Athlete::configChanged() } } } + +void +Athlete::importFilesWithoutDialog() { + + int importSettings = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTSETTINGS).toInt(); // default/unset = 0 + if (importSettings == 0) return; // no autoimport requested + + QVariant importDirQV = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTDIR, ""); + QString importDirectory = importDirQV.toString(); + if (importDirectory == "") return; // no implicit assumptions on the directory - only explicite is allowed + + // now get the files formats + const RideFileFactory &rff = RideFileFactory::instance(); + QStringList suffixList = rff.suffixes(); + suffixList.replaceInStrings(QRegExp("^"), "*."); + QStringList allFormats; + QFileInfoList fileInfos; + foreach(QString suffix, rff.suffixes()) + allFormats << QString("*.%1").arg(suffix); + + // and now the files for the GC formats / no SubDirs considered + QDir *importDir = new QDir (importDirectory); + if (!importDir->exists()) return; // directory might not be available (USB,..) + if (!importDir->isReadable()) return; // check if directory is readable, + + // now get the files with their full names + fileInfos = importDir->entryInfoList(allFormats, QDir::Files, QDir::NoSort); + if (!fileInfos.isEmpty()) { + QStringList fileNames; + foreach(QFileInfo f, fileInfos) { + fileNames.append(f.absoluteFilePath()); + } + RideImportWizard *import = new RideImportWizard(fileNames, context->athlete->home, context); + if (importSettings == 1) import->setDialogMode(RideImportWizard::allButDupFileErrors); + if (importSettings == 2) import->setDialogMode(RideImportWizard::allErrors); + import->process(); + } +} diff --git a/src/Athlete.h b/src/Athlete.h index a0fa60257..c5411d9a9 100644 --- a/src/Athlete.h +++ b/src/Athlete.h @@ -124,6 +124,9 @@ class Athlete : public QObject void notifySeasonsChanged() { seasonsChanged(); } void notifyNamedSearchesChanged() { namedSearchesChanged(); } + // import rides from athlete specific directory + void importFilesWithoutDialog(); + signals: void zonesChanged(); void seasonsChanged(); @@ -136,5 +139,6 @@ class Athlete : public QObject void updateRideFileIntervals(); void configChanged(); + }; #endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 917adf4b1..97094b282 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1387,6 +1387,7 @@ MainWindow::openWindow(QString name) // main window will register itself MainWindow *main = new MainWindow(home); main->show(); + main->ridesAutoImport(); } void @@ -1437,6 +1438,9 @@ MainWindow::openTab(QString name) showTabbar(true); setUpdatesEnabled(true); + + // now do the automatic ride file import + context->athlete->importFilesWithoutDialog(); } void @@ -1947,3 +1951,10 @@ MainWindow::addIntervals() currentTab->addIntervals(); } +void +MainWindow::ridesAutoImport() { + + currentTab->context->athlete->importFilesWithoutDialog(); + +} + diff --git a/src/MainWindow.h b/src/MainWindow.h index 924698cc7..5c3984287 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -189,6 +189,9 @@ class MainWindow : public QMainWindow void revertRide(); bool saveRideExitDialog(Context *); // save dirty rides on exit dialog + // autoload rides from athlete specific directory (preferences) + void ridesAutoImport(); + // save and restore state to context void saveGCState(Context *); void restoreGCState(Context *); diff --git a/src/Pages.cpp b/src/Pages.cpp index f85c9b91d..86ac00b3a 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -780,6 +780,20 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte avatarButton->setFixedHeight(140); avatarButton->setFixedWidth(140); + QVariant importDir = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTDIR, ""); + + importLabel = new QLabel(tr("Auto Import from")); + importDirectory = new QLineEdit; + importDirectory->setText(importDir.toString()); + importBrowseButton = new QPushButton(tr("Browse")); + importBrowseButton->setFixedWidth(120); + + importSetting = new QComboBox(); + importSetting->addItem("No auto import"); + importSetting->addItem("No duplicate file errors"); + importSetting->addItem("All errors"); + importSetting->setCurrentIndex(appsettings->cvalue(context->athlete->cyclist, GC_IMPORTSETTINGS).toInt()); // default/unset = 0 + Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter; grid->addWidget(nicklabel, 0, 0, alignment); @@ -797,11 +811,18 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte grid->addWidget(bio, 6, 0, 1, 4); grid->addWidget(avatarButton, 0, 2, 4, 2, Qt::AlignRight|Qt::AlignVCenter); + + grid->addWidget(importLabel, 7,0, alignment); + grid->addWidget(importDirectory, 7,1, alignment); + grid->addWidget(importBrowseButton, 7,2, alignment); + grid->addWidget(importSetting, 7,3, alignment); + all->addLayout(grid); all->addStretch(); connect (avatarButton, SIGNAL(clicked()), this, SLOT(chooseAvatar())); connect (unitCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int))); + connect (importBrowseButton, SIGNAL(clicked()), this, SLOT(browseImportDir())); } void @@ -832,6 +853,15 @@ RiderPage::unitChanged(int currentIndex) } } +void +RiderPage::browseImportDir() +{ + QString dir = QFileDialog::getExistingDirectory(this, tr("Select Import Directory"), + "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (dir != "") importDirectory->setText(dir); //only overwrite current dir, if a new was selected +} + + void RiderPage::saveClicked() { @@ -847,6 +877,11 @@ RiderPage::saveClicked() appsettings->setCValue(context->athlete->cyclist, GC_SEX, sex->currentIndex()); appsettings->setCValue(context->athlete->cyclist, GC_BIO, bio->toPlainText()); avatar.save(context->athlete->home.absolutePath() + "/" + "avatar.png", "PNG"); + + // save the import settings + appsettings->setCValue(context->athlete->cyclist, GC_IMPORTSETTINGS, importSetting->currentIndex()); + appsettings->setCValue(context->athlete->cyclist, GC_IMPORTDIR, importDirectory->text()); + } // diff --git a/src/Pages.h b/src/Pages.h index 6057f665b..36fa15e98 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -111,6 +111,7 @@ class RiderPage : public QWidget public slots: void chooseAvatar(); void unitChanged(int currentIndex); + void browseImportDir(); private: Context *context; @@ -124,6 +125,11 @@ class RiderPage : public QWidget QTextEdit *bio; QPushButton *avatarButton; QPixmap avatar; + QLineEdit *importDirectory; + QPushButton *importBrowseButton; + QLabel *importLabel; + QComboBox *importSetting; + }; class CredentialsPage : public QScrollArea diff --git a/src/RideImportWizard.cpp b/src/RideImportWizard.cpp index 7bee20b29..0392c2977 100644 --- a/src/RideImportWizard.cpp +++ b/src/RideImportWizard.cpp @@ -36,6 +36,7 @@ // drag and drop passes urls ... convert to a list of files and call main constructor RideImportWizard::RideImportWizard(QList *urls, QDir &home, Context *context, QWidget *parent) : QDialog(parent), context(context) { + dialogMode = standardDialog; setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); QList filenames; @@ -47,6 +48,7 @@ RideImportWizard::RideImportWizard(QList *urls, QDir &home, Context *conte RideImportWizard::RideImportWizard(QList files, QDir &home, Context *context, QWidget *parent) : QDialog(parent), context(context) { + dialogMode = standardDialog; init(files, home, context); } @@ -203,14 +205,23 @@ RideImportWizard::init(QList files, QDir &home, Context * /*mainWindow* tableWidget->adjustSize(); - // Refresh prior to running down the list & processing... - this->show(); } int RideImportWizard::process() { + // Refresh prior to running down the list & processing... + // do it here and not in Constructor, since "dialogMode" might have been changed + if (dialogMode == standardDialog) show(); + + // set if any error occurs in one of the stages, for the "non-Dialog" modes - to make the widget visible + // if an error in one of the steps has happened - first error is set after the first error has occured + // and the widget has been made visible (in case of noDialog mode)- from that point on, no need + // to "show()" the widget again + bool firstError = false; + bool sectionError = false; + // set progress bar limits - for each file we // will make 5 passes over the files // 1. checking it is a file ane readable @@ -244,11 +255,13 @@ RideImportWizard::process() } else { tableWidget->item(i,5)->setText(tr("Error - Unknown file type")); + sectionError = true; } } else { // Cannot open tableWidget->item(i,5)->setText(tr("Error - Not a valid file")); + sectionError = true; } progressBar->setValue(progressBar->value()+1); @@ -256,6 +269,11 @@ RideImportWizard::process() } if (aborted) { done(0); } + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && sectionError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + sectionError = false; // reset Error flag for next section + } repaint(); QApplication::processEvents(); @@ -380,8 +398,10 @@ RideImportWizard::process() // ride != NULL but !errors.isEmpty() means they're just warnings if (errors.isEmpty()) tableWidget->item(i,5)->setText(tr("Validated")); - else + else { tableWidget->item(i,5)->setText(tr("Warning - ") + errors.join(tr(" "))); + sectionError = true; + } // Set Date and Time if (ride->startTime().isNull()) { @@ -390,6 +410,7 @@ RideImportWizard::process() blanks[i] = true; tableWidget->item(i,1)->setText(tr("")); tableWidget->item(i,2)->setText(tr("")); + sectionError = true; } else { @@ -434,11 +455,17 @@ RideImportWizard::process() } else { // nope - can't handle this file tableWidget->item(i,5)->setText(tr("Error - ") + errors.join(tr(" "))); + sectionError = true; } } progressBar->setValue(progressBar->value()+1); QApplication::processEvents(); if (aborted) { done(0); } + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && sectionError && !firstError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + sectionError = false; // reset Error flag for next section + } this->repaint(); next:; @@ -463,6 +490,11 @@ RideImportWizard::process() progressBar->setValue(progressBar->value()+1); progressBar->repaint(); } + // dialog has to show up for this import to complete the data + if (needdates != 0 && (dialogMode == allErrors || dialogMode == allButDupFileErrors) && !firstError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + } // Wait for user to press save abortButton->setText(tr("Save")); @@ -490,6 +522,15 @@ RideImportWizard::process() } connect(tableWidget, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(activateSave())); + // if running in any "dialog only in error cases mode" and no Error have occured until now, + // we need to trigger the final saving step + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && !firstError) { + abortClicked(); // simulate user input - at this point "Abort" == "Save" + // in additional simulate the "Finish" user-click + context->athlete->isclean = false; + context->athlete->metricDB->refreshMetrics(); + } + return 0; } @@ -693,6 +734,9 @@ removeDuplicate(QString filename) void RideImportWizard::abortClicked() { + // similar procedure for "non-Dialog" processing to handle errors like in "process()" here + bool sectionError = false; + bool fileExistsError = false; // if done when labelled abort we kill off this dialog QString label = abortButton->text(); @@ -792,6 +836,7 @@ RideImportWizard::abortClicked() duplicates = findDuplicates(fulltarget); if (duplicates.count() && !overwriteFiles) { tableWidget->item(i,5)->setText(tr("Error - File exists")); + fileExistsError = true; } else { // wipe away the duplicate @@ -858,13 +903,17 @@ RideImportWizard::abortClicked() if (temp.rename(fulltarget)) { tableWidget->item(i,5)->setText(tr("File Overwritten")); //no need to add since its already there! - } else + } else { tableWidget->item(i,5)->setText(tr("Error - overwrite failed")); + sectionError = true; + } } else { tableWidget->item(i,5)->setText(tr("Error - overwrite failed")); + sectionError = true; } } else { tableWidget->item(i,5)->setText(tr("Error - File exists")); + fileExistsError = true; } } else { tableWidget->item(i,5)->setText(tr("Saving file...")); @@ -879,13 +928,19 @@ RideImportWizard::abortClicked() // BUT! Some charts/windows will hava snaffled away the ridefile // pointer which is now invalid so once all the rides have been imported // we need to select the last one... see below - } else + } else { tableWidget->item(i,5)->setText(tr("Error - copy failed")); + sectionError = true; + } } } QApplication::processEvents(); if (aborted) { done(0); } progressBar->setValue(progressBar->value()+1); + if (((sectionError || fileExistsError) && (dialogMode == allErrors)) || + ((sectionError && !fileExistsError) && (dialogMode == allButDupFileErrors))) { + show(); // make the widget visible in case an error was logged + } this->repaint(); } @@ -906,6 +961,12 @@ RideImportWizard::abortClicked() aborted = false; } + +void RideImportWizard::setDialogMode(int mode) { + + dialogMode = mode; +} + // clean up files RideImportWizard::~RideImportWizard() { diff --git a/src/RideImportWizard.h b/src/RideImportWizard.h index 3e5c67631..8fd892a82 100644 --- a/src/RideImportWizard.h +++ b/src/RideImportWizard.h @@ -46,6 +46,8 @@ public: RideImportWizard(QList files, QDir &home, Context *context, QWidget *parent = 0); ~RideImportWizard(); int process(); + void setDialogMode(int); // default is fullDialog + enum DialogMode { standardDialog, allErrors, allButDupFileErrors }; signals: @@ -62,6 +64,7 @@ private: QList blanks; // record of which have a RideFileReader returned date & time QDir home; // target directory bool aborted; + int dialogMode; // see enum QLabel *phaseLabel; QTableWidget *tableWidget; QProgressBar *progressBar; diff --git a/src/Settings.h b/src/Settings.h index a1307491f..3762f4096 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -56,6 +56,8 @@ #define GC_SEX "sex" #define GC_BIO "bio" #define GC_AVATAR "avatar" +#define GC_IMPORTDIR "importDir" +#define GC_IMPORTSETTINGS "importSettings" #define GC_SETTINGS_LAST_IMPORT_PATH "mainwindow/lastImportPath" #define GC_SETTINGS_LAST_WORKOUT_PATH "mainwindow/lastWorkoutPath" #define GC_LAST_DOWNLOAD_DEVICE "mainwindow/lastDownloadDevice" diff --git a/src/main.cpp b/src/main.cpp index 9bbb3baf4..abf6c774f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -286,6 +286,7 @@ main(int argc, char *argv[]) if (home.cd(cyclist)) { MainWindow *mainWindow = new MainWindow(home); mainWindow->show(); + mainWindow->ridesAutoImport(); home.cdUp(); anyOpened = true; } @@ -314,6 +315,7 @@ main(int argc, char *argv[]) // .. and open a mainwindow MainWindow *mainWindow = new MainWindow(home); mainWindow->show(); + mainWindow->ridesAutoImport(); } ret=application->exec();