2019-05-31 14:09:14 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
|
|
|
|
#include "trip.h"
|
2020-05-01 11:43:52 +00:00
|
|
|
#include "dive.h"
|
core: introduce divelog structure
The parser API was very annoying, as a number of tables
to-be-filled were passed in as pointers. The goal of this
commit is to collect all these tables in a single struct.
This should make it (more or less) clear what is actually
written into the divelog files.
Moreover, it should now be rather easy to search for
instances, where the global logfile is accessed (and it
turns out that there are many!).
The divelog struct does not contain the tables as substructs,
but only collects pointers. The idea is that the "divelog.h"
file can be included without all the other files describing
the numerous tables.
To make it easier to use from C++ parts of the code, the
struct implements a constructor and a destructor. Sadly,
we can't use smart pointers, since the pointers are accessed
from C code. Therfore the constructor and destructor are
quite complex.
The whole commit is large, but was mostly an automatic
conversion.
One oddity of note: the divelog structure also contains
the "autogroup" flag, since that is saved in the divelog.
This actually fixes a bug: Before, when importing dives
from a different log, the autogroup flag was overwritten.
This was probably not intended and does not happen anymore.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2022-11-08 20:31:08 +00:00
|
|
|
#include "divelog.h"
|
2024-06-02 15:06:18 +00:00
|
|
|
#include "errorhelper.h"
|
|
|
|
#include "range.h"
|
2020-05-01 12:07:59 +00:00
|
|
|
#include "subsurface-time.h"
|
2019-05-31 14:09:14 +00:00
|
|
|
#include "subsurface-string.h"
|
2019-11-24 14:02:34 +00:00
|
|
|
#include "selection.h"
|
2019-05-31 14:09:14 +00:00
|
|
|
|
2024-05-31 15:15:47 +00:00
|
|
|
dive_trip::dive_trip() : id(dive_getUniqID())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-06-02 15:06:18 +00:00
|
|
|
dive_trip::~dive_trip() = default;
|
2024-05-31 15:15:47 +00:00
|
|
|
|
2024-06-08 13:28:16 +00:00
|
|
|
timestamp_t dive_trip::date() const
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-08 13:28:16 +00:00
|
|
|
if (dives.empty())
|
2019-05-31 14:09:14 +00:00
|
|
|
return 0;
|
2024-06-08 13:28:16 +00:00
|
|
|
return dives[0]->when;
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
2024-06-01 20:05:57 +00:00
|
|
|
static timestamp_t trip_enddate(const struct dive_trip &trip)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-02 15:06:18 +00:00
|
|
|
if (trip.dives.empty())
|
2019-05-31 14:09:14 +00:00
|
|
|
return 0;
|
2024-06-05 15:02:40 +00:00
|
|
|
return trip.dives.back()->endtime();
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Add dive to a trip. Caller is responsible for removing dive
|
|
|
|
* from trip beforehand. */
|
2024-06-08 13:28:16 +00:00
|
|
|
void dive_trip::add_dive(struct dive *dive)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-08 13:28:16 +00:00
|
|
|
if (dive->divetrip == this)
|
2019-05-31 14:09:14 +00:00
|
|
|
return;
|
|
|
|
if (dive->divetrip)
|
2024-06-07 08:25:09 +00:00
|
|
|
report_info("Warning: adding dive to trip, which already has a trip set");
|
2024-06-08 13:28:16 +00:00
|
|
|
range_insert_sorted(dives, dive, comp_dives_ptr);
|
|
|
|
dive->divetrip = this;
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* remove a dive from the trip it's associated to, but don't delete the
|
|
|
|
* trip if this was the last dive in the trip. the caller is responsible
|
2024-06-02 15:06:18 +00:00
|
|
|
* for removing the trip, if the trip->dives.size() went to 0.
|
2019-05-31 14:09:14 +00:00
|
|
|
*/
|
|
|
|
struct dive_trip *unregister_dive_from_trip(struct dive *dive)
|
|
|
|
{
|
2024-05-31 15:15:47 +00:00
|
|
|
dive_trip *trip = dive->divetrip;
|
2019-05-31 14:09:14 +00:00
|
|
|
|
|
|
|
if (!trip)
|
|
|
|
return NULL;
|
|
|
|
|
2024-06-02 15:06:18 +00:00
|
|
|
range_remove(trip->dives, dive);
|
2019-05-31 14:09:14 +00:00
|
|
|
dive->divetrip = NULL;
|
|
|
|
return trip;
|
|
|
|
}
|
|
|
|
|
2024-06-01 20:05:57 +00:00
|
|
|
std::unique_ptr<dive_trip> create_trip_from_dive(const struct dive *dive)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-01 20:05:57 +00:00
|
|
|
auto trip = std::make_unique<dive_trip>();
|
2024-06-30 18:55:34 +00:00
|
|
|
trip->location = dive->get_location();
|
2019-05-31 14:09:14 +00:00
|
|
|
|
|
|
|
return trip;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* random threshold: three days without diving -> new trip
|
|
|
|
* this works very well for people who usually dive as part of a trip and don't
|
|
|
|
* regularly dive at a local facility; this is why trips are an optional feature */
|
|
|
|
#define TRIP_THRESHOLD 3600 * 24 * 3
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find a trip a new dive should be autogrouped with. If no such trips
|
2024-06-01 20:05:57 +00:00
|
|
|
* exist, allocate a new trip. A unique_ptr is returned if a new trip
|
|
|
|
* was allocated. The caller has to store it.
|
2019-05-31 14:09:14 +00:00
|
|
|
*/
|
2024-06-07 08:25:09 +00:00
|
|
|
std::pair<dive_trip *, std::unique_ptr<dive_trip>> get_trip_for_new_dive(const struct divelog &log, const struct dive *new_dive)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
|
|
|
/* Find dive that is within TRIP_THRESHOLD of current dive */
|
2024-06-07 08:25:09 +00:00
|
|
|
for (auto &d: log.dives) {
|
2019-05-31 14:09:14 +00:00
|
|
|
/* Check if we're past the range of possible dives */
|
|
|
|
if (d->when >= new_dive->when + TRIP_THRESHOLD)
|
|
|
|
break;
|
|
|
|
|
2024-06-01 20:05:57 +00:00
|
|
|
if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip)
|
|
|
|
return { d->divetrip, nullptr }; /* Found a dive with trip in the range */
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Didn't find a trip -> allocate a new one */
|
2024-06-01 20:05:57 +00:00
|
|
|
auto trip = create_trip_from_dive(new_dive);
|
2019-05-31 14:09:14 +00:00
|
|
|
trip->autogen = true;
|
2024-06-01 20:05:57 +00:00
|
|
|
auto t = trip.get();
|
|
|
|
return { t, std::move(trip) };
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-06-28 05:33:51 +00:00
|
|
|
/* Check if two trips overlap time-wise up to trip threshold. */
|
2024-06-01 20:05:57 +00:00
|
|
|
bool trips_overlap(const struct dive_trip &t1, const struct dive_trip &t2)
|
2019-06-28 05:33:51 +00:00
|
|
|
{
|
|
|
|
/* First, handle the empty-trip cases. */
|
2024-06-02 15:06:18 +00:00
|
|
|
if (t1.dives.empty() || t2.dives.empty())
|
2019-06-28 05:33:51 +00:00
|
|
|
return 0;
|
|
|
|
|
2024-06-08 13:28:16 +00:00
|
|
|
if (t1.date() < t2.date())
|
|
|
|
return trip_enddate(t1) + TRIP_THRESHOLD >= t2.date();
|
2019-06-28 05:33:51 +00:00
|
|
|
else
|
2024-06-08 13:28:16 +00:00
|
|
|
return trip_enddate(t2) + TRIP_THRESHOLD >= t1.date();
|
2019-06-28 05:33:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-31 14:09:14 +00:00
|
|
|
/*
|
|
|
|
* Collect dives for auto-grouping. Pass in first dive which should be checked.
|
|
|
|
* Returns range of dives that should be autogrouped and trip it should be
|
2024-06-01 20:05:57 +00:00
|
|
|
* associated to. If the returned trip was newly allocated, a std::unique_ptr<>
|
|
|
|
* to the trip is returned.
|
2019-05-31 14:09:14 +00:00
|
|
|
* is set to true. Caller still has to register it in the system. Note
|
|
|
|
* whereas this looks complicated - it is needed by the undo-system, which
|
|
|
|
* manually injects the new trips. If there are no dives to be autogrouped,
|
|
|
|
* return NULL.
|
|
|
|
*/
|
2024-06-07 08:25:09 +00:00
|
|
|
std::vector<dives_to_autogroup_result> get_dives_to_autogroup(const struct dive_table &table)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-01 20:05:57 +00:00
|
|
|
std::vector<dives_to_autogroup_result> res;
|
2019-05-31 14:09:14 +00:00
|
|
|
struct dive *lastdive = NULL;
|
|
|
|
|
|
|
|
/* Find first dive that should be merged and remember any previous
|
|
|
|
* dive that could be merged into.
|
|
|
|
*/
|
2024-06-07 08:25:09 +00:00
|
|
|
for (size_t i = 0; i < table.size(); ++i) {
|
|
|
|
auto &dive = table[i];
|
2019-05-31 14:09:14 +00:00
|
|
|
|
|
|
|
if (dive->divetrip) {
|
2024-06-07 08:25:09 +00:00
|
|
|
lastdive = dive.get();
|
2019-05-31 14:09:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only consider dives that have not been explicitly removed from
|
|
|
|
* a dive trip by the user. */
|
|
|
|
if (dive->notrip) {
|
|
|
|
lastdive = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We found a dive, let's see if we have to allocate a new trip */
|
2024-06-01 20:05:57 +00:00
|
|
|
std::unique_ptr<dive_trip> allocated;
|
2024-06-07 08:25:09 +00:00
|
|
|
dive_trip *trip;
|
2019-05-31 14:09:14 +00:00
|
|
|
if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) {
|
|
|
|
/* allocate new trip */
|
2024-06-07 08:25:09 +00:00
|
|
|
allocated = create_trip_from_dive(dive.get());
|
2024-06-01 20:05:57 +00:00
|
|
|
allocated->autogen = true;
|
|
|
|
trip = allocated.get();
|
2019-05-31 14:09:14 +00:00
|
|
|
} else {
|
|
|
|
/* use trip of previous dive */
|
|
|
|
trip = lastdive->divetrip;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, find all dives that will be added to this trip
|
2024-06-07 08:25:09 +00:00
|
|
|
lastdive = dive.get();
|
|
|
|
size_t to;
|
|
|
|
for (to = i + 1; to < table.size(); to++) {
|
|
|
|
auto &dive = table[to];
|
2019-05-31 14:09:14 +00:00
|
|
|
if (dive->divetrip || dive->notrip ||
|
|
|
|
dive->when >= lastdive->when + TRIP_THRESHOLD)
|
|
|
|
break;
|
2024-05-31 15:15:47 +00:00
|
|
|
if (trip->location.empty())
|
2024-06-30 18:55:34 +00:00
|
|
|
trip->location = dive->get_location();
|
2024-06-07 08:25:09 +00:00
|
|
|
lastdive = dive.get();
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
2024-06-01 20:05:57 +00:00
|
|
|
res.push_back({ i, to, trip, std::move(allocated) });
|
|
|
|
i = to - 1;
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
2024-06-01 20:05:57 +00:00
|
|
|
return res;
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 15:15:47 +00:00
|
|
|
/* Out of two strings, get the string that is not empty (if any). */
|
|
|
|
static std::string non_empty_string(const std::string &a, const std::string &b)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-05-31 15:15:47 +00:00
|
|
|
return b.empty() ? a : b;
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* This combines the information of two trips, generating a
|
|
|
|
* new trip. To support undo, we have to preserve the old trips. */
|
2024-06-01 20:05:57 +00:00
|
|
|
std::unique_ptr<dive_trip> combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-01 20:05:57 +00:00
|
|
|
auto trip = std::make_unique<dive_trip>();
|
2019-05-31 14:09:14 +00:00
|
|
|
|
2024-05-31 15:15:47 +00:00
|
|
|
trip->location = non_empty_string(trip_a->location, trip_b->location);
|
|
|
|
trip->notes = non_empty_string(trip_a->notes, trip_b->notes);
|
2019-05-31 14:09:14 +00:00
|
|
|
|
|
|
|
return trip;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Trips are compared according to the first dive in the trip. */
|
2024-06-01 20:05:57 +00:00
|
|
|
int comp_trips(const struct dive_trip &a, const struct dive_trip &b)
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-01 20:05:57 +00:00
|
|
|
// To make sure that trips never compare equal, compare by
|
|
|
|
// address if both are empty.
|
|
|
|
if (&a == &b)
|
|
|
|
return 0; // reflexivity. shouldn't happen.
|
2024-06-02 15:06:18 +00:00
|
|
|
if (a.dives.empty() && b.dives.empty())
|
2024-06-01 20:05:57 +00:00
|
|
|
return &a < &b ? -1 : 1;
|
2024-06-02 15:06:18 +00:00
|
|
|
if (a.dives.empty())
|
2024-06-01 20:05:57 +00:00
|
|
|
return -1;
|
2024-06-02 15:06:18 +00:00
|
|
|
if (b.dives.empty())
|
2019-05-31 14:09:14 +00:00
|
|
|
return 1;
|
2024-06-07 08:25:09 +00:00
|
|
|
return comp_dives(*a.dives[0], *b.dives[0]);
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_same_day(timestamp_t trip_when, timestamp_t dive_when)
|
|
|
|
{
|
|
|
|
static timestamp_t twhen = (timestamp_t) 0;
|
|
|
|
static struct tm tmt;
|
|
|
|
struct tm tmd;
|
|
|
|
|
|
|
|
utc_mkdate(dive_when, &tmd);
|
|
|
|
|
|
|
|
if (twhen != trip_when) {
|
|
|
|
twhen = trip_when;
|
|
|
|
utc_mkdate(twhen, &tmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year);
|
|
|
|
}
|
|
|
|
|
2024-06-08 13:28:16 +00:00
|
|
|
bool dive_trip::is_single_day() const
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-08 13:28:16 +00:00
|
|
|
if (dives.size() <= 1)
|
2019-05-31 14:09:14 +00:00
|
|
|
return true;
|
2024-06-08 13:28:16 +00:00
|
|
|
return is_same_day(dives.front()->when, dives.back()->when);
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|
|
|
|
|
2024-06-08 13:28:16 +00:00
|
|
|
int dive_trip::shown_dives() const
|
2019-05-31 14:09:14 +00:00
|
|
|
{
|
2024-06-08 13:28:16 +00:00
|
|
|
return std::count_if(dives.begin(), dives.end(),
|
2024-06-02 15:06:18 +00:00
|
|
|
[](const dive *d) { return !d->hidden_by_filter; });
|
|
|
|
}
|
|
|
|
|
|
|
|
void dive_trip::sort_dives()
|
|
|
|
{
|
2024-06-07 08:25:09 +00:00
|
|
|
std::sort(dives.begin(), dives.end(), [] (dive *d1, dive *d2) { return comp_dives(*d1, *d2) < 0; });
|
2019-05-31 14:09:14 +00:00
|
|
|
}
|