diff --git a/src/ErgDBDownloadDialog.cpp b/src/ErgDBDownloadDialog.cpp index b3a26e19d..dfeaa4d43 100644 --- a/src/ErgDBDownloadDialog.cpp +++ b/src/ErgDBDownloadDialog.cpp @@ -17,6 +17,7 @@ */ #include "ErgDBDownloadDialog.h" +#include "TrainDB.h" ErgDBDownloadDialog::ErgDBDownloadDialog(MainWindow *main) : QDialog(main), main(main) { @@ -151,6 +152,9 @@ ErgDBDownloadDialog::downloadFiles() // what format to export as? QString type = RideFileFactory::instance().writeSuffixes().at(0); + // for library updating, transactional for 10x performance + trainDB->startLUW(); + // loop through the table and export all selected for(int i=0; iinvisibleRootItem()->childCount(); i++) { @@ -158,7 +162,10 @@ ErgDBDownloadDialog::downloadFiles() QApplication::processEvents(); // did they? - if (aborted == true) return; // user aborted! + if (aborted == true) { + trainDB->endLUW(); // need to commit whatever was copied. + return; // user aborted! + } QTreeWidgetItem *current = files->invisibleRootItem()->child(i); @@ -180,7 +187,6 @@ ErgDBDownloadDialog::downloadFiles() // open success? if (p->isValid()) { - delete p; // free memory! if (QFile(filename).exists()) { @@ -188,6 +194,7 @@ ErgDBDownloadDialog::downloadFiles() // skip existing files current->setText(5, "Exists already"); QApplication::processEvents(); fails++; + delete p; // free memory! continue; } else { @@ -210,12 +217,15 @@ ErgDBDownloadDialog::downloadFiles() downloads++; current->setText(5, "Saved"); QApplication::processEvents(); + trainDB->importWorkout(filename, p); // add to library + } else { fails++; current->setText(5, "Write failed"); QApplication::processEvents(); } + delete p; // free memory! // couldn't parse } else { @@ -228,4 +238,6 @@ ErgDBDownloadDialog::downloadFiles() } } + // for library updating, transactional for 10x performance + trainDB->endLUW(); } diff --git a/src/Library.cpp b/src/Library.cpp index 6eda2f529..c2854b44f 100644 --- a/src/Library.cpp +++ b/src/Library.cpp @@ -375,6 +375,9 @@ LibrarySearchDialog::removeDirectory() void LibrarySearchDialog::updateDB() { + // wipe away all user data before updating + trainDB->rebuildDB(); + trainDB->startLUW(); // workouts @@ -435,6 +438,24 @@ LibrarySearch::run() // is a workout? if (findWorkout && ErgFile::isWorkout(name)) emit foundWorkout(name); } + + // Now check and re-add references, if there are any + // these are files which were drag-n-dropped into the + // GC train window, but which were referenced not + // copied into the workout directory. + Library *l = Library::findLibrary("Media Library"); + if (l) { + foreach(QString r, l->refs) { + + if (!QFile(r).exists()) continue; + + // is a video? + if (findMedia && helper.isMedia(r)) emit foundVideo(r); + // is a workout? + if (findWorkout && ErgFile::isWorkout(r)) emit foundWorkout(r); + } + } + emit done(); }; diff --git a/src/Library.h b/src/Library.h index 258c06788..97c4eabdc 100644 --- a/src/Library.h +++ b/src/Library.h @@ -34,6 +34,7 @@ class Library public: QString name; // e.g. Media Library QList paths; // array of search paths for files in this library + QList refs; // array of drag-n-dropped files referenced not copied static void initialise(QDir); // init static Library *findLibrary(QString); diff --git a/src/LibraryParser.cpp b/src/LibraryParser.cpp index 3566234bb..08a53095b 100644 --- a/src/LibraryParser.cpp +++ b/src/LibraryParser.cpp @@ -40,6 +40,11 @@ bool LibraryParser::endElement( const QString&, const QString&, const QString &q if (qName == "path") { library->paths.append(buffer.trimmed()); } + + // a reference + if (qName == "ref") { + library->refs.append(buffer.trimmed()); + } return TRUE; } @@ -87,6 +92,10 @@ LibraryParser::serialize(QDir home) foreach(QString p, l->paths) out << QString("\t%1\n").arg(p); + // paths... + foreach(QString r, l->refs) + out << QString("\t%1\n").arg(r); + // end document out << "\n"; } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 29ee75016..a452838d9 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -806,9 +806,10 @@ MainWindow::MainWindow(const QDir &home) : optionsMenu->addAction(tr("Get &Zeo Data..."), this, SLOT (downloadMeasuresFromZeo())); optionsMenu->addSeparator(); - optionsMenu->addAction(tr("Workout Wizard"), this, SLOT(showWorkoutWizard())); - optionsMenu->addAction(tr("Get Workouts from ErgDB"), this, SLOT(downloadErgDB())); - optionsMenu->addAction(tr("Manage Media/Workout Library"), this, SLOT(manageLibrary())); + optionsMenu->addAction(tr("Create a new workout..."), this, SLOT(showWorkoutWizard())); + optionsMenu->addAction(tr("Download workouts from ErgDB..."), this, SLOT(downloadErgDB())); + optionsMenu->addAction(tr("Import workouts or videos..."), this, SLOT(importWorkout())); + optionsMenu->addAction(tr("Scan disk for videos and workouts..."), this, SLOT(manageLibrary())); #ifdef GC_HAVE_ICAL optionsMenu->addSeparator(); @@ -1556,9 +1557,13 @@ MainWindow::dropEvent(QDropEvent *event) QList urls = event->mimeData()->urls(); if (urls.isEmpty()) return; - // We have something to process then - RideImportWizard *dialog = new RideImportWizard (&urls, home, this); - dialog->process(); // do it! + if (currentWindow != trainWindow) { + // We have something to process then + RideImportWizard *dialog = new RideImportWizard (&urls, home, this); + dialog->process(); // do it! + } else { + //XXX hook for workout importer HERE + } return; } @@ -1959,6 +1964,36 @@ MainWindow::uploadTtb() } } +/*---------------------------------------------------------------------- + * Import Workout from Disk + *--------------------------------------------------------------------*/ +void +MainWindow::importWorkout() +{ + // go look at last place we imported workouts from... + QVariant lastDirVar = appsettings->value(this, GC_SETTINGS_LAST_WORKOUT_PATH); + QString lastDir = (lastDirVar != QVariant()) + ? lastDirVar.toString() : QDir::homePath(); + + // anything for now, we could add filters later + QStringList allFormats; + allFormats << "All files (*.*)"; + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Import from File"), lastDir, + allFormats.join(";;")); + + // lets process them + if (!fileNames.isEmpty()) { + + // save away last place we looked + lastDir = QFileInfo(fileNames.front()).absolutePath(); + appsettings->setValue(GC_SETTINGS_LAST_WORKOUT_PATH, lastDir); + + QStringList fileNamesCopy = fileNames; // QT doc says iterate over a copy + + // import them via the workoutimporter + //XXX hook for workout importer HERE + } +} /*---------------------------------------------------------------------- * ErgDB *--------------------------------------------------------------------*/ diff --git a/src/MainWindow.h b/src/MainWindow.h index 517d59831..b054129e6 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -272,6 +272,7 @@ class MainWindow : public QMainWindow void uploadCalendar(); // upload ride to calendar #endif void importFile(); + void importWorkout(); void findBestIntervals(); void addIntervals(); void addIntervalForPowerPeaksForSecs(RideFile *ride, int windowSizeSecs, QString name); diff --git a/src/Settings.h b/src/Settings.h index 5ed24558d..ec196d23d 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -55,6 +55,7 @@ #define GC_BIO "bio" #define GC_AVATAR "avatar" #define GC_SETTINGS_LAST_IMPORT_PATH "mainwindow/lastImportPath" +#define GC_SETTINGS_LAST_WORKOUT_PATH "mainwindow/lastWorkoutPath" #define GC_LAST_DOWNLOAD_DEVICE "mainwindow/lastDownloadDevice" #define GC_LAST_DOWNLOAD_PORT "mainwindow/lastDownloadPort" #define GC_CRANKLENGTH "crankLength" diff --git a/src/TrainDB.cpp b/src/TrainDB.cpp index 799ef27f7..022174f8a 100644 --- a/src/TrainDB.cpp +++ b/src/TrainDB.cpp @@ -70,6 +70,18 @@ TrainDB::initDatabase(QDir home) } } +// rebuild effectively drops and recreates all tables +// but not the version table, since its about deleting +// user data (e.g. when rescanning their hard disk) +void +TrainDB::rebuildDB() +{ + dropWorkoutTable(); + createWorkoutTable(); + dropVideoTable(); + createVideoTable(); +} + bool TrainDB::createVideoTable() { QSqlQuery query(dbconn); @@ -149,12 +161,14 @@ bool TrainDB::createWorkoutTable() rc = query.exec(createMetricTable); + // adding a space at the front of string to make manual mode always + // appear first in a sorted list is a bit of a hack, but works ok QString manualErg = QString("INSERT INTO workouts (filepath, filename) values (\"//1\", \"%1\");") - .arg(tr("Manual Erg Mode")); + .arg(tr(" Manual Erg Mode")); rc = query.exec(manualErg); QString manualCrs = QString("INSERT INTO workouts (filepath, filename) values (\"//2\", \"%1\");") - .arg(tr("Manual Slope Mode")); + .arg(tr(" Manual Slope Mode")); rc = query.exec(manualCrs); // add row to version database diff --git a/src/TrainDB.h b/src/TrainDB.h index 502ee5651..13e4f7f63 100644 --- a/src/TrainDB.h +++ b/src/TrainDB.h @@ -52,6 +52,9 @@ class TrainDB : public QObject bool importWorkout(QString pathname, ErgFile *ergFile); bool importVideo(QString pathname); //XXX simple for now + // drop and recreate tables + void rebuildDB(); + signals: void dataChanged(); diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index ecc281e99..941275fb7 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -86,8 +86,13 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h videoModel->select(); while (videoModel->canFetchMore(QModelIndex())) videoModel->fetchMore(QModelIndex()); + vsortModel = new QSortFilterProxyModel(this); + vsortModel->setSourceModel(videoModel); + vsortModel->setDynamicSortFilter(true); + vsortModel->sort(1, Qt::AscendingOrder); //filename order + mediaTree = new QTreeView; - mediaTree->setModel(videoModel); + mediaTree->setModel(vsortModel); // hide unwanted columns and header for(int i=0; iheader()->count(); i++) @@ -95,7 +100,7 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h mediaTree->setColumnHidden(1, false); // show filename mediaTree->header()->hide(); - mediaTree->setSortingEnabled(true); + mediaTree->setSortingEnabled(false); mediaTree->setAlternatingRowColors(false); mediaTree->setEditTriggers(QAbstractItemView::NoEditTriggers); // read-only mediaTree->expandAll(); @@ -132,8 +137,13 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h workoutModel->select(); while (workoutModel->canFetchMore(QModelIndex())) workoutModel->fetchMore(QModelIndex()); + sortModel = new QSortFilterProxyModel(this); + sortModel->setSourceModel(workoutModel); + sortModel->setDynamicSortFilter(true); + sortModel->sort(1, Qt::AscendingOrder); //filename order + workoutTree = new QTreeView; - workoutTree->setModel(workoutModel); + workoutTree->setModel(sortModel); // hide unwanted columns and header for(int i=0; iheader()->count(); i++) @@ -503,7 +513,8 @@ void TrainTool::workoutTreeWidgetSelectionChanged() { QModelIndex current = workoutTree->currentIndex(); - QString filename = workoutModel->data(workoutModel->index(current.row(), 0), Qt::DisplayRole).toString(); + QModelIndex target = sortModel->mapToSource(current); + QString filename = workoutModel->data(workoutModel->index(target.row(), 0), Qt::DisplayRole).toString(); // wip away the current selected workout if (ergFile) { @@ -511,8 +522,13 @@ TrainTool::workoutTreeWidgetSelectionChanged() ergFile = NULL; } + if (filename == "") { + main->notifyErgFileSelected(NULL); + return; + } + // is it the auto mode? - int index = current.row(); + int index = target.row(); if (index == 0) { // ergo mode main->notifyErgFileSelected(NULL); @@ -585,8 +601,10 @@ TrainTool::listWorkoutFiles(const QDir &dir) const void TrainTool::mediaTreeWidgetSelectionChanged() { + QModelIndex current = mediaTree->currentIndex(); - QString filename = videoModel->data(videoModel->index(current.row(), 0), Qt::DisplayRole).toString(); + QModelIndex target = vsortModel->mapToSource(current); + QString filename = videoModel->data(videoModel->index(target.row(), 0), Qt::DisplayRole).toString(); main->notifyMediaSelected(filename); } diff --git a/src/TrainTool.h b/src/TrainTool.h index dcf98bec2..2e901509c 100644 --- a/src/TrainTool.h +++ b/src/TrainTool.h @@ -182,6 +182,8 @@ class TrainTool : public GcWindow QTreeWidget *deviceTree; QTreeWidget *serverTree; QTreeView *workoutTree; + QSortFilterProxyModel *sortModel; // sorting workout list + QSortFilterProxyModel *vsortModel; // sorting video list QTreeView *mediaTree; QTreeWidgetItem *allServers;