summaryrefslogtreecommitdiffstats
path: root/core/trip.c
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2019-05-31 16:09:14 +0200
committerGravatar Dirk Hohndel <dirk@hohndel.org>2019-06-19 13:11:10 -0700
commit7f4d9db962e73aa5d5089c43c99b78b3690ffb87 (patch)
tree87e143b80ed394fc321765885a75cac229bf9f18 /core/trip.c
parentf1c2cd375e295730d92e23093e44777baf838f1d (diff)
downloadsubsurface-7f4d9db962e73aa5d5089c43c99b78b3690ffb87.tar.gz
Cleanup: move trip-related functions into own translation unit
These functions were spread out over dive.c and divelist.c. Move them into their own file to make all this a bit less monolithic. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'core/trip.c')
-rw-r--r--core/trip.c353
1 files changed, 353 insertions, 0 deletions
diff --git a/core/trip.c b/core/trip.c
new file mode 100644
index 000000000..a864e8dfb
--- /dev/null
+++ b/core/trip.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "trip.h"
+#include "subsurface-string.h"
+#include "table.h"
+
+struct trip_table trip_table;
+
+#ifdef DEBUG_TRIP
+void dump_trip_list(void)
+{
+ dive_trip_t *trip;
+ int i = 0;
+ timestamp_t last_time = 0;
+
+ for (i = 0; i < trip_table.nr; ++i) {
+ struct tm tm;
+ trip = trip_table.trips[i];
+ utc_mkdate(trip_date(trip), &tm);
+ if (trip_date(trip) < last_time)
+ printf("\n\ntrip_table OUT OF ORDER!!!\n\n\n");
+ printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n",
+ trip->autogen ? "autogen " : "",
+ i + 1, trip->location,
+ tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ trip->dives.nr, trip);
+ last_time = trip_date(trip);
+ }
+ printf("-----\n");
+}
+#endif
+
+/* free resources associated with a trip structure */
+void free_trip(dive_trip_t *trip)
+{
+ if (trip) {
+ free(trip->location);
+ free(trip->notes);
+ free(trip->dives.dives);
+ free(trip);
+ }
+}
+
+/* Trip table functions */
+static MAKE_GET_IDX(trip_table, struct dive_trip *, trips)
+static MAKE_GROW_TABLE(trip_table, struct dive_trip *, trips)
+static MAKE_GET_INSERTION_INDEX(trip_table, struct dive_trip *, trips, trip_less_than)
+static MAKE_ADD_TO(trip_table, struct dive_trip *, trips)
+static MAKE_REMOVE_FROM(trip_table, trips)
+MAKE_SORT(trip_table, struct dive_trip *, trips, comp_trips)
+MAKE_REMOVE(trip_table, struct dive_trip *, trip)
+
+timestamp_t trip_date(const struct dive_trip *trip)
+{
+ if (!trip || trip->dives.nr == 0)
+ return 0;
+ return trip->dives.dives[0]->when;
+}
+
+timestamp_t trip_enddate(const struct dive_trip *trip)
+{
+ if (!trip || trip->dives.nr == 0)
+ return 0;
+ return dive_endtime(trip->dives.dives[trip->dives.nr - 1]);
+}
+
+/* check if we have a trip right before / after this dive */
+bool is_trip_before_after(const struct dive *dive, bool before)
+{
+ int idx = get_idx_by_uniq_id(dive->id);
+ if (before) {
+ if (idx > 0 && get_dive(idx - 1)->divetrip)
+ return true;
+ } else {
+ if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip)
+ return true;
+ }
+ return false;
+}
+
+/* Add dive to a trip. Caller is responsible for removing dive
+ * from trip beforehand. */
+void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
+{
+ if (dive->divetrip == trip)
+ return;
+ if (dive->divetrip)
+ fprintf(stderr, "Warning: adding dive to trip that has trip set\n");
+ insert_dive(&trip->dives, dive);
+ dive->divetrip = trip;
+}
+
+/* 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
+ * for removing the trip, if the trip->dives.nr went to 0.
+ */
+struct dive_trip *unregister_dive_from_trip(struct dive *dive)
+{
+ dive_trip_t *trip = dive->divetrip;
+
+ if (!trip)
+ return NULL;
+
+ remove_dive(dive, &trip->dives);
+ dive->divetrip = NULL;
+ return trip;
+}
+
+static void delete_trip(dive_trip_t *trip, struct trip_table *trip_table_arg)
+{
+ remove_trip(trip, trip_table_arg);
+ free_trip(trip);
+}
+
+void remove_dive_from_trip(struct dive *dive, struct trip_table *trip_table_arg)
+{
+ struct dive_trip *trip = unregister_dive_from_trip(dive);
+ if (trip && trip->dives.nr == 0)
+ delete_trip(trip, trip_table_arg);
+}
+
+dive_trip_t *alloc_trip(void)
+{
+ return calloc(1, sizeof(dive_trip_t));
+}
+
+/* insert the trip into the trip table */
+void insert_trip(dive_trip_t *dive_trip, struct trip_table *trip_table_arg)
+{
+ int idx = trip_table_get_insertion_index(trip_table_arg, dive_trip);
+ add_to_trip_table(trip_table_arg, idx, dive_trip);
+#ifdef DEBUG_TRIP
+ dump_trip_list();
+#endif
+}
+
+dive_trip_t *create_trip_from_dive(struct dive *dive)
+{
+ dive_trip_t *trip;
+
+ trip = alloc_trip();
+ trip->location = copy_string(get_dive_location(dive));
+
+ return trip;
+}
+
+dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive, struct trip_table *trip_table_arg)
+{
+ dive_trip_t *dive_trip = alloc_trip();
+
+ dive_trip = create_trip_from_dive(dive);
+
+ add_dive_to_trip(dive, dive_trip);
+ insert_trip(dive_trip, trip_table_arg);
+ return dive_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
+ * exist, allocate a new trip. The bool "*allocated" is set to true
+ * if a new trip was allocated.
+ */
+dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated)
+{
+ struct dive *d;
+ dive_trip_t *trip;
+ int i;
+
+ /* Find dive that is within TRIP_THRESHOLD of current dive */
+ for_each_dive(i, d) {
+ /* Check if we're past the range of possible dives */
+ if (d->when >= new_dive->when + TRIP_THRESHOLD)
+ break;
+
+ if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip) {
+ /* Found a dive with trip in the range */
+ *allocated = false;
+ return d->divetrip;
+ }
+ }
+
+ /* Didn't find a trip -> allocate a new one */
+ trip = create_trip_from_dive(new_dive);
+ trip->autogen = true;
+ *allocated = true;
+ return trip;
+}
+
+/*
+ * 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
+ * associated to. If the returned trip was newly allocated, the last bool
+ * 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.
+ */
+dive_trip_t *get_dives_to_autogroup(struct dive_table *table, int start, int *from, int *to, bool *allocated)
+{
+ int i;
+ struct dive *lastdive = NULL;
+
+ /* Find first dive that should be merged and remember any previous
+ * dive that could be merged into.
+ */
+ for (i = start; i < table->nr; i++) {
+ struct dive *dive = table->dives[i];
+ dive_trip_t *trip;
+
+ if (dive->divetrip) {
+ lastdive = dive;
+ 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 */
+ if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) {
+ /* allocate new trip */
+ trip = create_trip_from_dive(dive);
+ trip->autogen = true;
+ *allocated = true;
+ } else {
+ /* use trip of previous dive */
+ trip = lastdive->divetrip;
+ *allocated = false;
+ }
+
+ // Now, find all dives that will be added to this trip
+ lastdive = dive;
+ *from = i;
+ for (*to = *from + 1; *to < table->nr; (*to)++) {
+ dive = table->dives[*to];
+ if (dive->divetrip || dive->notrip ||
+ dive->when >= lastdive->when + TRIP_THRESHOLD)
+ break;
+ if (get_dive_location(dive) && !trip->location)
+ trip->location = copy_string(get_dive_location(dive));
+ lastdive = dive;
+ }
+ return trip;
+ }
+
+ /* Did not find anyhting - mark as end */
+ return NULL;
+}
+
+void deselect_dives_in_trip(struct dive_trip *trip)
+{
+ if (!trip)
+ return;
+ for (int i = 0; i < trip->dives.nr; ++i)
+ deselect_dive(trip->dives.dives[i]);
+}
+
+void select_dives_in_trip(struct dive_trip *trip)
+{
+ struct dive *dive;
+ if (!trip)
+ return;
+ for (int i = 0; i < trip->dives.nr; ++i) {
+ dive = trip->dives.dives[i];
+ if (!dive->hidden_by_filter)
+ select_dive(dive);
+ }
+}
+
+/* Out of two strings, copy the string that is not empty (if any). */
+static char *copy_non_empty_string(const char *a, const char *b)
+{
+ return copy_string(empty_string(b) ? a : b);
+}
+
+/* This combines the information of two trips, generating a
+ * new trip. To support undo, we have to preserve the old trips. */
+dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b)
+{
+ dive_trip_t *trip;
+
+ trip = alloc_trip();
+ trip->location = copy_non_empty_string(trip_a->location, trip_b->location);
+ trip->notes = copy_non_empty_string(trip_a->notes, trip_b->notes);
+
+ return trip;
+}
+
+void clear_trip_table(struct trip_table *table)
+{
+ for (int i = 0; i < table->nr; i++)
+ free_trip(table->trips[i]);
+ table->nr = 0;
+}
+
+/* Trips are compared according to the first dive in the trip. */
+int comp_trips(const struct dive_trip *a, const struct dive_trip *b)
+{
+ /* This should never happen, nevertheless don't crash on trips
+ * with no (or worse a negative number of) dives. */
+ if (a->dives.nr <= 0)
+ return b->dives.nr <= 0 ? 0 : -1;
+ if (b->dives.nr <= 0)
+ return 1;
+ return comp_dives(a->dives.dives[0], b->dives.dives[0]);
+}
+
+bool trip_less_than(const struct dive_trip *a, const struct dive_trip *b)
+{
+ return comp_trips(a, b) < 0;
+}
+
+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);
+}
+
+bool trip_is_single_day(const struct dive_trip *trip)
+{
+ if (trip->dives.nr <= 1)
+ return true;
+ return is_same_day(trip->dives.dives[0]->when,
+ trip->dives.dives[trip->dives.nr - 1]->when);
+}
+
+int trip_shown_dives(const struct dive_trip *trip)
+{
+ int res = 0;
+ for (int i = 0; i < trip->dives.nr; ++i) {
+ if (!trip->dives.dives[i]->hidden_by_filter)
+ res++;
+ }
+ return res;
+}