diff options
-rw-r--r-- | core/divelist.c | 208 | ||||
-rw-r--r-- | core/divelist.h | 3 | ||||
-rw-r--r-- | desktop-widgets/divelogimportdialog.cpp | 13 | ||||
-rw-r--r-- | desktop-widgets/downloadfromdivecomputer.cpp | 28 | ||||
-rw-r--r-- | desktop-widgets/mainwindow.cpp | 5 | ||||
-rw-r--r-- | desktop-widgets/subsurfacewebservices.cpp | 5 | ||||
-rw-r--r-- | qt-models/diveimportedmodel.cpp | 24 | ||||
-rw-r--r-- | tests/testmerge.cpp | 18 | ||||
-rw-r--r-- | tests/testrenumber.cpp | 10 |
9 files changed, 203 insertions, 111 deletions
diff --git a/core/divelist.c b/core/divelist.c index 94adc92dc..41ebc4364 100644 --- a/core/divelist.c +++ b/core/divelist.c @@ -16,7 +16,7 @@ * void update_cylinder_related_info(struct dive *dive) * void dump_trip_list(void) * void insert_trip(dive_trip_t **dive_trip_p) - * void remove_dive_from_trip(struct dive *dive) + * void remove_dive_from_trip(struct dive *dive, bool was_autogen) * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) * void autogroup_dives(void) @@ -30,6 +30,7 @@ * void remove_autogen_trips() * void sort_table(struct dive_table *table) * bool is_trip_before_after(const struct dive *dive, bool before) + * void delete_dive_from_table(struct dive_table *table, int idx) */ #include <unistd.h> #include <stdio.h> @@ -917,21 +918,29 @@ void autogroup_dives(void) #endif } +/* Remove a dive from a dive table. This assumes that the + * dive was already removed from any trip and deselected. + * It simply shrinks the table and frees the trip */ +void delete_dive_from_table(struct dive_table *table, int idx) +{ + int i; + free_dive(table->dives[idx]); + for (i = idx; i < table->nr - 1; i++) + table->dives[i] = table->dives[i + 1]; + table->dives[--table->nr] = NULL; +} + /* this implements the mechanics of removing the dive from the table, * but doesn't deal with updating dive trips, etc */ void delete_single_dive(int idx) { - int i; struct dive *dive = get_dive(idx); if (!dive) return; /* this should never happen */ remove_dive_from_trip(dive, false); if (dive->selected) deselect_dive(idx); - for (i = idx; i < dive_table.nr - 1; i++) - dive_table.dives[i] = dive_table.dives[i + 1]; - dive_table.dives[--dive_table.nr] = NULL; - free_dive(dive); + delete_dive_from_table(&dive_table, idx); } struct dive **grow_dive_table(struct dive_table *table) @@ -1219,16 +1228,16 @@ void remove_autogen_trips() * what the numbers should be - in which case you need to do * a manual re-numbering. */ -static void try_to_renumber(struct dive *last, int preexisting) +static void try_to_renumber(int preexisting) { int i, nr; + struct dive *last = get_dive(preexisting - 1); /* - * If the new dives aren't all strictly at the end, - * we're going to expect the user to do a manual - * renumbering. + * If there was a last dive, but it didn't have + * a number, give up. */ - if (preexisting && get_dive(preexisting - 1) != last) + if (last && !last->number) return; /* @@ -1266,40 +1275,18 @@ void process_loaded_dives() sort_table(&dive_table); } -void process_imported_dives(bool prefer_imported) +/* + * Merge subsequent dives in a table, if mergeable. This assumes + * that the dives are neither selected, not part of a trip, as + * is the case of freshly imported dives. + */ +static void merge_imported_dives(struct dive_table *table) { int i; - int preexisting = dive_table.preexisting; - struct dive *last; - - /* If no dives were imported, don't bother doing anything */ - if (preexisting >= dive_table.nr) - return; - - /* check if we need a nickname for the divecomputer for newly downloaded dives; - * since we know they all came from the same divecomputer we just check for the - * first one */ - if (dive_table.dives[preexisting]->downloaded) - set_dc_nickname(dive_table.dives[preexisting]); - else - /* they aren't downloaded, so record / check all new ones */ - for (i = preexisting; i < dive_table.nr; i++) - set_dc_nickname(dive_table.dives[i]); - - for (i = preexisting; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = true; - - /* This does the right thing for -1: NULL */ - last = get_dive(preexisting - 1); - - sort_table(&dive_table); - - for (i = 1; i < dive_table.nr; i++) { - struct dive **pp = &dive_table.dives[i - 1]; - struct dive *prev = pp[0]; - struct dive *dive = pp[1]; + for (i = 1; i < table->nr; i++) { + struct dive *prev = table->dives[i - 1]; + struct dive *dive = table->dives[i]; struct dive *merged; - int id; /* only try to merge overlapping dives - or if one of the dives has * zero duration (that might be a gps marker from the webservice) */ @@ -1307,33 +1294,138 @@ void process_imported_dives(bool prefer_imported) dive_endtime(prev) < dive->when) continue; - merged = try_to_merge(prev, dive, prefer_imported); + merged = try_to_merge(prev, dive, false); if (!merged) continue; - // remember the earlier dive's id - id = prev->id; - - /* careful - we might free the dive that last points to. Oops... */ - if (last == prev || last == dive) - last = merged; + /* Overwrite the first of the two dives and remove the second */ + free_dive(prev); + table->dives[i - 1] = merged; + delete_dive_from_table(table, i); /* Redo the new 'i'th dive */ i--; - add_single_dive(i, merged); - delete_single_dive(i + 1); - delete_single_dive(i + 1); - // keep the id or the first dive for the merged dive - merged->id = id; } +} + +/* + * Try to merge a new dive into the dive at position idx. Return + * true on success. On success, the dive to add and the old dive + * will be deleted. On failure, they are untouched. + * If "prefer_imported" is true, use data of the new dive. + */ +static bool try_to_merge_into(struct dive *dive_to_add, int idx, bool prefer_imported) +{ + struct dive *old_dive = dive_table.dives[idx]; + struct dive_trip *trip = old_dive->divetrip; + struct dive *merged = try_to_merge(old_dive, dive_to_add, prefer_imported); + if (!merged) + return false; + + merged->id = old_dive->id; + merged->selected = old_dive->selected; + dive_table.dives[idx] = merged; + if (trip) { + remove_dive_from_trip(old_dive, false); + add_dive_to_trip(merged, trip); + } + free_dive(old_dive); + free_dive(dive_to_add); + + return true; +} + +/* + * Add imported dive to global dive table. Overlapping dives will + * be merged if possible. If prefer_imported is true, data of the + * new dives are prioritized in such a case. + * Note: the dives in import_table are consumed! On return import_table + * has size 0. + */ +void process_imported_dives(struct dive_table *import_table, bool prefer_imported) +{ + int i, j; + struct dive *old_dive, *merged; + int preexisting; + bool sequence_changed = false; + + /* If no dives were imported, don't bother doing anything */ + if (!import_table->nr) + return; + + /* check if we need a nickname for the divecomputer for newly downloaded dives; + * since we know they all came from the same divecomputer we just check for the + * first one */ + if (import_table->dives[0]->downloaded) + set_dc_nickname(import_table->dives[0]); + else + /* they aren't downloaded, so record / check all new ones */ + for (i = 0; i < import_table->nr; i++) + set_dc_nickname(import_table->dives[i]); + + /* Sort the table of dives to be imported and combine mergable dives */ + sort_table(import_table); + merge_imported_dives(import_table); + + for (i = 0; i < import_table->nr; i++) + import_table->dives[i]->downloaded = true; + + /* Merge newly imported dives into the dive table. + * Since both lists (old and new) are sorted, we can step + * through them concurrently and locate the insertions points. + * Once found, check if the new dive can be merged in the + * previous or next dive. + * Note that this doesn't consider pathological cases such as: + * - New dive "connects" two old dives (turn three into one). + * - New dive can not be merged into adjacent but some further dive. + */ + j = 0; /* Index in old dives */ + preexisting = dive_table.nr; /* Remember old size for renumbering */ + for (i = 0; i < import_table->nr; i++) { + struct dive *dive_to_add = import_table->dives[i]; + + /* Find insertion point. */ + while (j < dive_table.nr && dive_table.dives[j]->when < dive_to_add->when) + j++; + + /* Try to merge into previous dive. */ + if (j > 0 && dive_endtime(dive_table.dives[j - 1]) > dive_to_add->when) { + if (try_to_merge_into(dive_to_add, j - 1, prefer_imported)) + continue; + } + + /* That didn't merge into the previous dive. If we're + * at the end of the dive table, quit the loop and add + * all new dives at the end. */ + if (j >= dive_table.nr) + break; + + /* Try to merge into next dive. */ + if (dive_endtime(dive_to_add) > dive_table.dives[j]->when) { + if (try_to_merge_into(dive_to_add, j, prefer_imported)) + continue; + } + + /* We couldnt merge dives, add at the given position. */ + add_single_dive(j, dive_to_add); + j++; + sequence_changed = true; + } + + /* If there are still dives to add, add them at the end of the dive table. */ + for ( ; i < import_table->nr; i++) + add_single_dive(dive_table.nr, import_table->dives[i]); /* make sure no dives are still marked as downloaded */ - for (i = 1; i < dive_table.nr; i++) + for (i = 0; i < dive_table.nr; i++) dive_table.dives[i]->downloaded = false; - /* If there are dives in the table, are they numbered */ - if (!last || last->number) - try_to_renumber(last, preexisting); + /* we took care of all dives, clean up the import table */ + import_table->nr = 0; + + /* If the sequence wasn't changed, renumber */ + if (!sequence_changed) + try_to_renumber(preexisting); mark_divelist_changed(true); } diff --git a/core/divelist.h b/core/divelist.h index af47d0f23..885dbe67f 100644 --- a/core/divelist.h +++ b/core/divelist.h @@ -19,7 +19,7 @@ extern int init_decompression(struct deco_state *ds, struct dive *dive); /* divelist core logic functions */ extern void process_loaded_dives(); -extern void process_imported_dives(bool prefer_imported); +extern void process_imported_dives(struct dive_table *import_table, bool prefer_imported); extern char *get_dive_gas_string(struct dive *dive); struct dive **grow_dive_table(struct dive_table *table); @@ -43,6 +43,7 @@ extern struct dive *last_selected_dive(); extern bool is_trip_before_after(const struct dive *dive, bool before); extern void set_dive_nr_for_current_dive(); extern timestamp_t get_surface_interval(timestamp_t when); +extern void delete_dive_from_table(struct dive_table *table, int idx); int get_min_datafile_version(); void reset_min_datafile_version(); diff --git a/desktop-widgets/divelogimportdialog.cpp b/desktop-widgets/divelogimportdialog.cpp index 5df477349..874c33adc 100644 --- a/desktop-widgets/divelogimportdialog.cpp +++ b/desktop-widgets/divelogimportdialog.cpp @@ -896,14 +896,15 @@ int DiveLogImportDialog::parseTxtHeader(QString fileName, char **params, int pnr void DiveLogImportDialog::on_buttonBox_accepted() { + struct dive_table table = { 0 }; QStringList r = resultModel->result(); if (ui->knownImports->currentText() != "Manual import") { for (int i = 0; i < fileNames.size(); ++i) { if (ui->knownImports->currentText() == "Seabear CSV") { - parse_seabear_log(qPrintable(fileNames[i]), &dive_table); + parse_seabear_log(qPrintable(fileNames[i]), &table); } else if (ui->knownImports->currentText() == "Poseidon MkVI") { QPair<QString, QString> pair = poseidonFileNames(fileNames[i]); - parse_txt_file(qPrintable(pair.second), qPrintable(pair.first), &dive_table); + parse_txt_file(qPrintable(pair.second), qPrintable(pair.first), &table); } else { char *params[49]; int pnr = 0; @@ -920,7 +921,7 @@ void DiveLogImportDialog::on_buttonBox_accepted() pnr = setup_csv_params(r, params, pnr); parse_csv_file(qPrintable(fileNames[i]), params, pnr - 1, specialCSV.contains(ui->knownImports->currentIndex()) ? qPrintable(CSVApps[ui->knownImports->currentIndex()].name) : "csv", - &dive_table); + &table); } } } else { @@ -984,7 +985,7 @@ void DiveLogImportDialog::on_buttonBox_accepted() params[pnr++] = intdup(r.indexOf(tr("Rating"))); params[pnr++] = NULL; - parse_manual_file(qPrintable(fileNames[i]), params, pnr - 1, &dive_table); + parse_manual_file(qPrintable(fileNames[i]), params, pnr - 1, &table); } else { char *params[51]; int pnr = 0; @@ -1001,12 +1002,12 @@ void DiveLogImportDialog::on_buttonBox_accepted() pnr = setup_csv_params(r, params, pnr); parse_csv_file(qPrintable(fileNames[i]), params, pnr - 1, specialCSV.contains(ui->knownImports->currentIndex()) ? qPrintable(CSVApps[ui->knownImports->currentIndex()].name) : "csv", - &dive_table); + &table); } } } - process_imported_dives(false); + process_imported_dives(&table, false); MainWindow::instance()->refreshDisplay(); } diff --git a/desktop-widgets/downloadfromdivecomputer.cpp b/desktop-widgets/downloadfromdivecomputer.cpp index 561ff2998..46cb0418b 100644 --- a/desktop-widgets/downloadfromdivecomputer.cpp +++ b/desktop-widgets/downloadfromdivecomputer.cpp @@ -487,34 +487,30 @@ void DownloadFromDCWidget::on_cancel_clicked() void DownloadFromDCWidget::on_ok_clicked() { - struct dive *dive; - if (currentState != DONE && currentState != ERROR) return; - // record all the dives in the 'real' dive_table - for (int i = 0; i < downloadTable.nr; i++) { + // delete non-selected dives + int total = downloadTable.nr; + int j = 0; + for (int i = 0; i < total; i++) { if (diveImportedModel->data(diveImportedModel->index(i, 0), Qt::CheckStateRole) == Qt::Checked) - record_dive(downloadTable.dives[i]); + j++; else - clear_dive(downloadTable.dives[i]); - downloadTable.dives[i] = NULL; + delete_dive_from_table(&downloadTable, j); } - downloadTable.nr = 0; - int uniqId, idx; - // remember the last downloaded dive (on most dive computers this will be the chronologically - // first new dive) and select it again after processing all the dives MainWindow::instance()->dive_list()->unselectDives(); - dive = get_dive(dive_table.nr - 1); - if (dive != NULL) { - uniqId = get_dive(dive_table.nr - 1)->id; - process_imported_dives(preferDownloaded()); + if (downloadTable.nr > 0) { + // remember the last downloaded dive (on most dive computers this will be the chronologically + // first new dive) and select it again after processing all the dives + int uniqId = downloadTable.dives[downloadTable.nr - 1]->id; + process_imported_dives(&downloadTable, preferDownloaded()); // after process_imported_dives does any merging or resorting needed, we need // to recreate the model for the dive list so we can select the newest dive MainWindow::instance()->recreateDiveList(); - idx = get_idx_by_uniq_id(uniqId); + int idx = get_idx_by_uniq_id(uniqId); // this shouldn't be necessary - but there are reports that somehow existing dives stay selected // (but not visible as selected) MainWindow::instance()->dive_list()->unselectDives(); diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index e37c7a7bd..9049b741b 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1733,12 +1733,13 @@ void MainWindow::importFiles(const QStringList fileNames) return; QByteArray fileNamePtr; + struct dive_table table = { 0 }; for (int i = 0; i < fileNames.size(); ++i) { fileNamePtr = QFile::encodeName(fileNames.at(i)); - parse_file(fileNamePtr.data(), &dive_table); + parse_file(fileNamePtr.data(), &table); } - process_imported_dives(false); + process_imported_dives(&table, false); refreshDisplay(); } diff --git a/desktop-widgets/subsurfacewebservices.cpp b/desktop-widgets/subsurfacewebservices.cpp index a7586c128..693463bf1 100644 --- a/desktop-widgets/subsurfacewebservices.cpp +++ b/desktop-widgets/subsurfacewebservices.cpp @@ -767,8 +767,9 @@ void DivelogsDeWebServices::buttonClicked(QAbstractButton *button) break; } /* parse file and import dives */ - parse_file(QFile::encodeName(zipFile.fileName()), &dive_table); - process_imported_dives(false); + struct dive_table table = { 0 }; + parse_file(QFile::encodeName(zipFile.fileName()), &table); + process_imported_dives(&table, false); MainWindow::instance()->refreshDisplay(); /* store last entered user/pass in config */ diff --git a/qt-models/diveimportedmodel.cpp b/qt-models/diveimportedmodel.cpp index 48d9a1dcd..ebe803244 100644 --- a/qt-models/diveimportedmodel.cpp +++ b/qt-models/diveimportedmodel.cpp @@ -163,21 +163,17 @@ void DiveImportedModel::recordDives() // nothing to do, just exit return; - // walk the table of imported dives and record the ones that the user picked - // clearing out the table as we go - for (int i = 0; i < rowCount(); i++) { - struct dive *d = diveTable->dives[i]; - if (d && checkStates[i]) { - record_dive(d); - } else { - // we should free the dives that weren't recorded - clear_dive(d); - free(d); - } - diveTable->dives[i] = NULL; + // delete non-selected dives + int total = diveTable->nr; + int j = 0; + for (int i = 0; i < total; i++) { + if (checkStates[i]) + j++; + else + delete_dive_from_table(&downloadTable, j); } - diveTable->nr = 0; - process_imported_dives(true); + + process_imported_dives(diveTable, true); if (autogroup) autogroup_dives(); } diff --git a/tests/testmerge.cpp b/tests/testmerge.cpp index 50bfd6e50..a99fd65e7 100644 --- a/tests/testmerge.cpp +++ b/tests/testmerge.cpp @@ -21,10 +21,11 @@ void TestMerge::testMergeEmpty() /* * check that we correctly merge mixed cylinder dives */ - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &dive_table), 0); - process_imported_dives(false); - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &dive_table), 0); - process_imported_dives(false); + struct dive_table table = { 0 }; + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &table), 0); + process_imported_dives(&table, false); + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &table), 0); + process_imported_dives(&table, false); QCOMPARE(save_dives("./testmerge47+48.ssrf"), 0); QFile org(SUBSURFACE_TEST_DATA "/dives/test47+48.xml"); org.open(QFile::ReadOnly); @@ -44,10 +45,11 @@ void TestMerge::testMergeBackwards() /* * check that we correctly merge mixed cylinder dives */ - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &dive_table), 0); - process_imported_dives(false); - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &dive_table), 0); - process_imported_dives(false); + struct dive_table table = { 0 }; + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test48.xml", &table), 0); + process_imported_dives(&table, false); + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47.xml", &table), 0); + process_imported_dives(&table, false); QCOMPARE(save_dives("./testmerge47+48.ssrf"), 0); QFile org(SUBSURFACE_TEST_DATA "/dives/test47+48.xml"); org.open(QFile::ReadOnly); diff --git a/tests/testrenumber.cpp b/tests/testrenumber.cpp index 54d689004..bd9ddf0ca 100644 --- a/tests/testrenumber.cpp +++ b/tests/testrenumber.cpp @@ -14,8 +14,9 @@ void TestRenumber::setup() void TestRenumber::testMerge() { - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47b.xml", &dive_table), 0); - process_imported_dives(false); + struct dive_table table = { 0 }; + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47b.xml", &table), 0); + process_imported_dives(&table, false); QCOMPARE(dive_table.nr, 1); QCOMPARE(unsaved_changes(), 1); mark_divelist_changed(false); @@ -24,8 +25,9 @@ void TestRenumber::testMerge() void TestRenumber::testMergeAndAppend() { - QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47c.xml", &dive_table), 0); - process_imported_dives(false); + struct dive_table table = { 0 }; + QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test47c.xml", &table), 0); + process_imported_dives(&table, false); QCOMPARE(dive_table.nr, 2); QCOMPARE(unsaved_changes(), 1); struct dive *d = get_dive(1); |