subsurface/smtk-import/smartrak.c
Dirk Hohndel d577467f97 Core: introduce new subsurface-string header
First small step to shrinking dive.h.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-05-14 10:13:39 -07:00

994 lines
34 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* An .slg file is composed of a main table (Dives), a bunch of tables directly
* linked to Dives by their indexes (Site, Suit, Tank, etc) and another group of
* independent tables (Activity, Type, Gear, Fish ...) which connect with the dives
* via a related group of tables (ActivityRelation, TypeRelation ...) that contain
* just the dive index number and the related table index number.
* The data stored in the main group of tables are very extensive, going far beyond
* the actual scope of Subsurface in most of cases; e.g. Dives table keeps
* information which can be directly uploaded to DAN's database of dives, or Buddy
* table can include telephones, photo or, even, buddy mother's maiden name.
*
* Although these funcs are suposed to work in a standalone tool, will also work
* on main Subsurface import menu, by simply tweaking file.c and main_window.cpp
* to call smartrak_import()
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <mdbtools.h>
#include <stdarg.h>
#include <locale.h>
#if defined(WIN32) || defined(_WIN32)
#include <windows.h>
#endif
#include "core/dive.h"
#include "core/subsurface-string.h"
#include "core/gettext.h"
#include "core/divelist.h"
#include "core/libdivecomputer.h"
#include "core/divesite.h"
#include "core/membuffer.h"
/* SmartTrak version, constant for every single file */
int smtk_version;
int tanks;
/* Freeing temp char arrays utility */
static void smtk_free(char **array, int count)
{
int n;
for (n = 0; n < count; n++)
free(array[n]);
array = NULL;
}
/*
* There are AFAIK two versions of Smarttrak. The newer one supports trimix and up
* to 10 tanks. The other one just 3 tanks and no trimix but only nitrox. This is a
* problem for an automated parser which has to support both formats.
* In this solution I made an enum of fields with the same order they would have in
* a smarttrak db, and a tiny function which returns the number of the column where
* a field is expected to be, taking into account the different db formats .
*/
enum field_pos {IDX = 0, DIVENUM, _DATE, INTIME, INTVAL, DURATION, OUTTIME, DESATBEFORE, DESATAFTER, NOFLYBEFORE,
NOFLYAFTER, NOSTOPDECO, MAXDEPTH, VISIBILITY, WEIGHT, O2FRAC, HEFRAC, PSTART, PEND, AIRTEMP,
MINWATERTEMP, MAXWATERTEMP, SECFACT, ALARMS, MODE, REMARKS, DCNUMBER, DCMODEL, DIVETIMECOUNT, LOG,
PROFILE, SITEIDX, ALTIDX, SUITIDX, WEATHERIDX, SURFACEIDX, UNDERWATERIDX, TANKIDX};
/*
* Returns calculated column number depending on smartrak version, as there are more
* tanks (10) in later versions than in older (3).
* Older versions also lacks of 3 columns storing he fraction, one for each tank.
*/
static int coln(enum field_pos pos)
{
int amnd = (smtk_version < 10213) ? 3 : 0;
if (pos <= O2FRAC)
return pos;
if (pos >= AIRTEMP) {
pos += 4 * (tanks - 1) - amnd;
return pos;
}
switch (pos) {
case HEFRAC:
pos = O2FRAC + tanks;
return pos;
case PSTART:
pos = O2FRAC + 2 * tanks - amnd;
return pos;
case PEND:
pos = O2FRAC + 2 * tanks + 1 - amnd;
return pos;
}
}
/*
* Fills the date part of a tm structure with the string data obtained
* from smartrak in format "DD/MM/YY HH:MM:SS" where time is irrelevant.
*/
static void smtk_date_to_tm(char *d_buffer, struct tm *tm_date)
{
int n, d, m, y;
if ((d_buffer) && (!empty_string(d_buffer))) {
n = sscanf(d_buffer, "%d/%d/%d ", &m, &d, &y);
y = (y < 70) ? y + 100 : y;
if (n == 3) {
tm_date->tm_mday = d;
tm_date->tm_mon = m - 1;
tm_date->tm_year = y;
} else {
tm_date->tm_mday = tm_date->tm_mon = tm_date->tm_year = 0;
}
} else {
tm_date->tm_mday = tm_date->tm_mon = tm_date->tm_year = 0;
}
}
/*
* Fills the time part of a tm structure with the string data obtained
* from smartrak in format "DD/MM/YY HH:MM:SS" where date is irrelevant.
*/
static void smtk_time_to_tm(char *t_buffer, struct tm *tm_date)
{
int n, hr, min, sec;
if ((t_buffer) && (!empty_string(t_buffer))) {
n = sscanf(t_buffer, "%*[0-9/] %d:%d:%d ", &hr, &min, &sec);
if (n == 3) {
tm_date->tm_hour = hr;
tm_date->tm_min = min;
tm_date->tm_sec = sec;
} else {
tm_date->tm_hour = tm_date->tm_min = tm_date->tm_sec = 0;
}
} else {
tm_date->tm_hour = tm_date->tm_min = tm_date->tm_sec = 0;
}
tm_date->tm_isdst = -1;
}
/*
* Converts to seconds a time lapse returned from smartrak in string format
* "DD/MM/YY HH:MM:SS" where date is irrelevant.
* TODO: modify to support times > 24h where date means difference in days
* from 29/12/99.
*/
static unsigned int smtk_time_to_secs(char *t_buffer)
{
int n, hr, min, sec;
if (!empty_string(t_buffer)) {
n = sscanf(t_buffer, "%*[0-9/] %d:%d:%d ", &hr, &min, &sec);
return (n == 3) ? (((hr*60)+min)*60)+sec : 0;
} else {
return 0;
}
}
/*
* Emulate the non portable timegm() function.
* Based on timegm man page, changing setenv and unsetenv with putenv,
* because of portability issues.
*/
static time_t smtk_timegm(struct tm *tm)
{
time_t ret;
char *tz, *str;
tz = getenv("TZ");
putenv("TZ=");
tzset();
ret = mktime(tm);
if (tz) {
str = calloc(strlen(tz)+4, 1);
sprintf(str, "TZ=%s", tz);
putenv(str);
} else {
putenv("TZ");
}
tzset();
return ret;
}
/*
* Returns an opened table given its name and mdb. outcol and outbounder have to be allocated
* by the caller and are filled here.
*/
static MdbTableDef *smtk_open_table(MdbHandle *mdb, char *tablename, MdbColumn **outcol, char **outbounder)
{
MdbCatalogEntry *entry;
MdbTableDef *table;
int i;
entry = mdb_get_catalogentry_by_name(mdb, tablename);
if (!entry)
return NULL;
table = mdb_read_table(entry);
if (!table)
return NULL;
mdb_read_columns(table);
for (i = 0; i < table->num_cols; i++) {
outcol[i] = g_ptr_array_index(table->columns, i);
outbounder[i] = (char *) g_malloc(MDB_BIND_SIZE);
mdb_bind_column(table, i+1, outbounder[i], NULL);
}
mdb_rewind_table(table);
return table;
}
/*
* Utility function which returns the value from a given column in a given table,
* whose row equals the given idx string.
* Idx should be a numeric value, but all values obtained from mdbtools are strings,
* so let's compare strings instead of numbers to avoid unnecessary transforms.
*/
static char *smtk_value_by_idx(MdbHandle *mdb, char *tablename, int colnum, char *idx)
{
MdbCatalogEntry *entry;
MdbTableDef *table;
MdbColumn *idxcol, *valuecol;
char *bounder[MDB_MAX_COLS], *str = NULL;
int i = 0;
entry = mdb_get_catalogentry_by_name(mdb, tablename);
table = mdb_read_table(entry);
if (!table) {
report_error("[Error][smartrak_import]\t%s table doesn't exist\n", tablename);
return str;
}
mdb_read_columns(table);
idxcol = g_ptr_array_index(table->columns, 0);
valuecol = g_ptr_array_index(table->columns, colnum);
for (i = 0; i < table->num_cols; i++) {
bounder[i] = (char *) g_malloc(MDB_BIND_SIZE);
mdb_bind_column(table, i+1, bounder[i], NULL);
}
mdb_rewind_table(table);
for (i = 0; i < table->num_rows; i++) {
mdb_fetch_row(table);
if (!strcmp(idxcol->bind_ptr, idx)) {
str = copy_string(valuecol->bind_ptr);
break;
}
}
smtk_free(bounder, table->num_cols);
mdb_free_tabledef(table);
return str;
}
/*
* Utility function which joins three strings, being the second a separator string,
* usually a "\n". The third is a format string with an argument list.
* If the original string is NULL, then just returns the third.
* This is based in add_to_string() and add_to_string_va(), and, as its parents
* frees the original string.
*/
static char *smtk_concat_str(const char *orig, const char *sep, const char *fmt, ...)
{
char *str;
va_list args;
struct membuffer out = { 0 }, in = { 0 };
va_start(args, fmt);
put_vformat(&in, fmt, args);
if (orig != NULL) {
put_format(&out, "%s%s%s", orig, sep, mb_cstring(&in));
str = copy_string(mb_cstring(&out));
} else {
str = copy_string(mb_cstring(&in));
}
va_end(args);
free_buffer(&out);
free_buffer(&in);
free((void *)orig);
return str;
}
/*
* A site may be a wreck, which has its own table.
* Parse this table referred by the site idx. If found, put the different info items in
* Subsurface's dive_site notes.
* Wreck format:
* | Idx | SiteIdx | Text | Built | Sank | SankTime | Reason | ... | Notes | TrakId |
*/
static void smtk_wreck_site(MdbHandle *mdb, char *site_idx, struct dive_site *ds)
{
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS];
char *tmp = NULL, *notes = NULL;
int rc, i;
uint32_t d;
const char *wreck_fields[] = {QT_TRANSLATE_NOOP("gettextFromC", "Built"), QT_TRANSLATE_NOOP("gettextFromC", "Sank"), QT_TRANSLATE_NOOP("gettextFromC", "Sank Time"),
QT_TRANSLATE_NOOP("gettextFromC", "Reason"), QT_TRANSLATE_NOOP("gettextFromC", "Nationality"), QT_TRANSLATE_NOOP("gettextFromC", "Shipyard"),
QT_TRANSLATE_NOOP("gettextFromC", "ShipType"), QT_TRANSLATE_NOOP("gettextFromC", "Length"), QT_TRANSLATE_NOOP("gettextFromC", "Beam"),
QT_TRANSLATE_NOOP("gettextFromC", "Draught"), QT_TRANSLATE_NOOP("gettextFromC", "Displacement"), QT_TRANSLATE_NOOP("gettextFromC", "Cargo"),
QT_TRANSLATE_NOOP("gettextFromC", "Notes")};
table = smtk_open_table(mdb, "Wreck", col, bound_values);
/* Sanity check for table, unlikely but ... */
if (!table)
return;
/* Begin parsing. Write strings to notes only if available.*/
while (mdb_fetch_row(table)) {
if (!strcmp(col[1]->bind_ptr, site_idx)) {
notes = smtk_concat_str(notes, "\n", translate("gettextFromC", "Wreck Data"));
for (i = 3; i < 16; i++) {
switch (i) {
case 3:
case 4:
tmp = copy_string(col[i]->bind_ptr);
if (tmp)
notes = smtk_concat_str(notes, "\n", "%s: %s", wreck_fields[i - 3], strtok(tmp , " "));
free(tmp);
break;
case 5:
tmp = copy_string(col[i]->bind_ptr);
if (tmp)
notes = smtk_concat_str(notes, "\n", "%s: %s", wreck_fields[i - 3], strrchr(tmp, ' '));
free(tmp);
break;
case 6 ... 9:
case 14:
case 15:
tmp = copy_string(col[i]->bind_ptr);
if (tmp)
notes = smtk_concat_str(notes, "\n", "%s: %s", wreck_fields[i - 3], tmp);
free(tmp);
break;
default:
d = lrintl(strtold(col[i]->bind_ptr, NULL));
if (d)
notes = smtk_concat_str(notes, "\n", "%s: %d", wreck_fields[i - 3], d);
break;
}
}
ds->notes = smtk_concat_str(ds->notes, "\n", "%s", notes);
break;
}
}
/* Clean up and exit */
smtk_free(bound_values, table->num_cols);
mdb_free_tabledef(table);
free(notes);
}
/*
* Smartrak locations db is quite extensive. This builds a string joining some of
* the data in the style: "Country, State, Locality, Site" if this data are
* available. Uses two tables, Site and Location. Puts Altitude, Depth and Notes
* in Subsurface's dive site structure's notes if they are available.
* Site format:
* | Idx | Text | LocationIdx | WaterIdx | PlatformIdx | BottomIdx | Latitude | Longitude | Altitude | Depth | Notes | TrakId |
* Location format:
* | Idx | Text | Province | Country | Depth |
*/
static void smtk_build_location(MdbHandle *mdb, char *idx, timestamp_t when, uint32_t *location)
{
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS];
int i;
uint32_t d;
struct dive_site *ds;
degrees_t lat, lon;
char *str = NULL, *loc_idx = NULL, *site = NULL, *notes = NULL;
const char *site_fields[] = {QT_TRANSLATE_NOOP("gettextFromC", "Altitude"), QT_TRANSLATE_NOOP("gettextFromC", "Depth"),
QT_TRANSLATE_NOOP("gettextFromC", "Notes")};
/* Read data from Site table. Format notes for the dive site if any.*/
table = smtk_open_table(mdb, "Site", col, bound_values);
if (!table)
return;
for (i = 1; i <= atoi(idx); i++)
mdb_fetch_row(table);
loc_idx = copy_string(col[2]->bind_ptr);
site = copy_string(col[1]->bind_ptr);
lat.udeg = lrint(strtod(col[6]->bind_ptr, NULL) * 1000000);
lon.udeg = lrint(strtod(col[7]->bind_ptr, NULL) * 1000000);
for (i = 8; i < 11; i++) {
switch (i) {
case 8:
case 9:
d = lrintl(strtold(col[i]->bind_ptr, NULL));
if (d)
notes = smtk_concat_str(notes, "\n", "%s: %d m", site_fields[i - 8], d);
break;
case 10:
if (memcmp(col[i]->bind_ptr, "\0", 1))
notes = smtk_concat_str(notes, "\n", "%s: %s", site_fields[i - 8], col[i]->bind_ptr);
break;
}
}
smtk_free(bound_values, table->num_cols);
mdb_free_tabledef(table);
/* Read data from Location table, linked to Site by loc_idx */
table = smtk_open_table(mdb, "Location", col, bound_values);
mdb_rewind_table(table);
for (i = 1; i <= atoi(loc_idx); i++)
mdb_fetch_row(table);
/*
* Create a string for Subsurface's dive site structure with coordinates
* if available, if the site's name doesn't previously exists.
*/
if (memcmp(col[3]->bind_ptr, "\0", 1))
str = smtk_concat_str(str, ", ", "%s", col[3]->bind_ptr); // Country
if (memcmp(col[2]->bind_ptr, "\0", 1))
str = smtk_concat_str(str, ", ", "%s", col[2]->bind_ptr); // State - Province
if (memcmp(col[1]->bind_ptr, "\0", 1))
str = smtk_concat_str(str, ", ", "%s", col[1]->bind_ptr); // Locality
str = smtk_concat_str(str, ", ", "%s", site);
*location = get_dive_site_uuid_by_name(str, NULL);
if (*location == 0) {
if (lat.udeg == 0 && lon.udeg == 0)
*location = create_dive_site(str, when);
else
*location = create_dive_site_with_gps(str, lat, lon, when);
}
smtk_free(bound_values, table->num_cols);
/* Insert site notes */
ds = get_dive_site_by_uuid(*location);
ds->notes = copy_string(notes);
free(notes);
/* Check if we have a wreck */
smtk_wreck_site(mdb, idx, ds);
/* Clean up and exit */
mdb_free_tabledef(table);
free(loc_idx);
free(site);
free(str);
}
static void smtk_build_tank_info(MdbHandle *mdb, cylinder_t *tank, char *idx)
{
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS];
int i;
table = smtk_open_table(mdb, "Tank", col, bound_values);
if (!table)
return;
for (i = 1; i <= atoi(idx); i++)
mdb_fetch_row(table);
tank->type.description = copy_string(col[1]->bind_ptr);
tank->type.size.mliter = lrint(strtod(col[2]->bind_ptr, NULL) * 1000);
tank->type.workingpressure.mbar = lrint(strtod(col[4]->bind_ptr, NULL) * 1000);
smtk_free(bound_values, table->num_cols);
mdb_free_tabledef(table);
}
/*
* Under some circustances we can get the same tank from DC and from
* the smartrak DB. Will use this utility to check and clean .
*/
bool is_same_cylinder(cylinder_t *cyl_a, cylinder_t *cyl_b)
{
// different gasmixes (non zero)
if (cyl_a->gasmix.o2.permille - cyl_b->gasmix.o2.permille != 0 &&
cyl_a->gasmix.o2.permille != 0 &&
cyl_b->gasmix.o2.permille != 0)
return false;
// different start pressures (possible error 0.1 bar)
if (!(abs(cyl_a->start.mbar - cyl_b->start.mbar) <= 100))
return false;
// different end pressures (possible error 0.1 bar)
if (!(abs(cyl_a->end.mbar - cyl_b->end.mbar) <= 100))
return false;
// different names (none of them null)
if (!same_string(cyl_a->type.description, "---") &&
!same_string(cyl_b->type.description, "---") &&
!same_string(cyl_a->type.description, cyl_b->type.description))
return false;
// Cylinders are most probably the same
return true;
}
/*
* Next three functions were removed from dive.c just when I was going to use them
* for this import (see 16276faa). Will tweak them a bit and will use for our needs
* Macros are copied from dive.c
*/
#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? MIN(a->n, b->n) : (a->n) : (b->n)
static void merge_cylinder_type(cylinder_type_t *src, cylinder_type_t *dst)
{
if (!dst->size.mliter)
dst->size.mliter = src->size.mliter;
if (!dst->workingpressure.mbar)
dst->workingpressure.mbar = src->workingpressure.mbar;
if (!dst->description || same_string(dst->description, "---")) {
dst->description = src->description;
src->description = NULL;
}
}
static void merge_cylinder_mix(struct gasmix *src, struct gasmix *dst)
{
if (!dst->o2.permille)
*dst = *src;
}
static void merge_cylinder_info(cylinder_t *src, cylinder_t *dst)
{
merge_cylinder_type(&src->type, &dst->type);
merge_cylinder_mix(&src->gasmix, &dst->gasmix);
MERGE_MAX(dst, dst, src, start.mbar);
MERGE_MIN(dst, dst, src, end.mbar);
if (!dst->cylinder_use)
dst->cylinder_use = src->cylinder_use;
}
/*
* Remove unused tanks and merge cylinders if there are signs that
* they might be duplicated. Higher numbers are more prone to be unused,
* so will make the clean reverse order.
* When a used cylinder is found, check against previous one; if they are
* both the same, merge and delete the higher number (as lower numbers are
* most probably returned by libdivecomputer raw data parse.
*/
static int smtk_clean_cylinders(struct dive *d)
{
int i = tanks - 1;
cylinder_t *cyl, *base = &d->cylinder[0];
cyl = base + tanks - 1;
while (cyl != base) {
if (same_string(cyl->type.description, "---") && cyl->start.mbar == 0 && cyl->end.mbar == 0)
remove_cylinder(d, i);
else
if (is_same_cylinder(cyl, cyl - 1)) {
merge_cylinder_info(cyl, cyl - 1);
remove_cylinder(d, i);
}
cyl--;
i--;
}
}
/*
* Parses a relation table and fills a list with the relations for a dive idx.
* Returns the number of relations found for a given dive idx.
* Table relation format:
* | Diveidx | Idx |
*/
static int smtk_index_list(MdbHandle *mdb, char *table_name, char *dive_idx, int idx_list[])
{
int n = 0, i = 0;
MdbTableDef *table;
MdbColumn *cols[MDB_MAX_COLS];
char *bounders[MDB_MAX_COLS];
table = smtk_open_table(mdb, table_name, cols, bounders);
/* Sanity check */
if (!table)
return 0;
/* Parse the table searching for dive_idx */
while (mdb_fetch_row(table)) {
if (!strcmp(dive_idx, cols[0]->bind_ptr)) {
idx_list[n] = atoi(cols[1]->bind_ptr);
n++;
}
}
/* Clean up and exit */
smtk_free(bounders, table->num_cols);
mdb_free_tabledef(table);
return n;
}
/*
* Returns string with buddies names as registered in smartrak (may be a nickname).
* "Buddy" table is a buddies relation with lots and lots and lots of data (even buddy mother's
* maiden name ;-) ) most of them useless for a dive log. Let's just consider the nickname as main
* field and the full name if this exists and its construction is different from the nickname.
* Buddy table format:
* | Idx | Text (nickname) | Name | Firstname | Middlename | Title | Picture | Phone | ...
*/
static char *smtk_locate_buddy(MdbHandle *mdb, char *dive_idx)
{
char *str = NULL, *fullname = NULL, *bounder[MDB_MAX_COLS] = { NULL }, *buddies[256] = { NULL };
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
int i, n, rel[256] = { 0 };
n = smtk_index_list(mdb, "BuddyRelation", dive_idx, rel);
if (!n)
return str;
table = smtk_open_table(mdb, "Buddy", col, bounder);
if (!table)
return str;
/*
* Buddies in a single dive aren't (usually) a big number, so probably
* it's not a good idea to use a complex data structure and algorithm.
*/
while (mdb_fetch_row(table)) {
if (!empty_string(col[3]->bind_ptr))
fullname = smtk_concat_str(fullname, " ", "%s", col[3]->bind_ptr);
if (!empty_string(col[4]->bind_ptr))
fullname = smtk_concat_str(fullname, " ", "%s", col[4]->bind_ptr);
if (!empty_string(col[2]->bind_ptr))
fullname = smtk_concat_str(fullname, " ", "%s", col[2]->bind_ptr);
if (fullname && !same_string(col[1]->bind_ptr, fullname))
buddies[atoi(col[0]->bind_ptr)] = smtk_concat_str(buddies[atoi(col[0]->bind_ptr)], "", "%s (%s)", col[1]->bind_ptr, fullname);
else
buddies[atoi(col[0]->bind_ptr)] = smtk_concat_str(buddies[atoi(col[0]->bind_ptr)], "", "%s", col[1]->bind_ptr);
free(fullname);
fullname = NULL;
}
for (i = 0; i < n; i++)
str = smtk_concat_str(str, ", ", "%s", buddies[rel[i]]);
/* Clean up and exit */
smtk_free(buddies, 256);
smtk_free(bounder, MDB_MAX_COLS);
mdb_free_tabledef(table);
return str;
}
/* Parses the dive_type mdb tables and import the data into dive's
* taglist structure or notes. If there are tags that affects dive's dive_mode
* (SCR, CCR or so), set the dive mode too.
* The "tag" parameter is used to mark if we want this table to be imported
* into tags or into notes.
* Managed tables formats: Just consider Idx and Text
* Type:
* | Idx | Text | Default (bool)
* Activity:
* | Idx | Text | Default (bool)
* Gear:
* | Idx | Text | Vendor | Type | Typenum | Notes | Default (bool) | TrakId
* Fish:
* | Idx | Text | Name | Latin name | Typelength | Maxlength | Picture | Default (bool)| TrakId
*/
static void smtk_parse_relations(MdbHandle *mdb, struct dive *dive, char *dive_idx, char *table_name, char *rel_table_name, bool tag)
{
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS], *tmp = NULL, *types[64] = { NULL };
int i = 0, n = 0, rels[256] = { 0 };
n = smtk_index_list(mdb, rel_table_name, dive_idx, rels);
if (!n)
return;
table = smtk_open_table(mdb, table_name, col, bound_values);
if (!table)
return;
while (mdb_fetch_row(table))
types[atoi(col[0]->bind_ptr)] = copy_string(col[1]->bind_ptr);
for (i = 0; i < n; i++) {
if (tag)
taglist_add_tag(&dive->tag_list, types[rels[i]]);
else
tmp = smtk_concat_str(tmp, ", ", "%s", types[rels[i]]);
if (strstr(types[rels[i]], "SCR"))
dive->dc.divemode = PSCR;
else if (strstr(types[rels[i]], "CCR"))
dive->dc.divemode = CCR;
}
if (tmp)
dive->notes = smtk_concat_str(dive->notes, "\n", "Smartrak %s: %s", table_name, tmp);
free(tmp);
/* clean up and exit */
smtk_free(types, 64);
smtk_free(bound_values, table->num_cols);
mdb_free_tabledef(table);
}
/*
* Returns a pointer to a bookmark event in an event list if it exists for
* a given time. Return NULL otherwise.
*/
static struct event *find_bookmark(struct event *orig, unsigned int t)
{
struct event *ev = orig;
while (ev) {
if ((ev->time.seconds == t) && (ev->type == SAMPLE_EVENT_BOOKMARK))
return ev;
ev = ev->next;
}
return NULL;
}
/*
* Marker table is a mix between Type tables and Relations tables. Its format is
* | Dive Idx | Idx | Text | Type | XPos | YPos | XConnect | YConnect
* Type may be one of 1 = Text; 2 = DC; 3 = Tissue Data; 4 = Photo (0 most of time??)
* XPos is time in minutes during the dive (long int)
* YPos irelevant
* XConnect irelevant
* YConnect irelevant
*/
static void smtk_parse_bookmarks(MdbHandle *mdb, struct dive *d, char *dive_idx)
{
MdbTableDef *table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS], *tmp = NULL;
unsigned int time;
struct event *ev;
table = smtk_open_table(mdb, "Marker", col, bound_values);
if (!table) {
report_error("[smtk-import] Error - Couldn't open table 'Marker', dive %d", d->number);
return;
}
while (mdb_fetch_row(table)) {
if (same_string(col[0]->bind_ptr, dive_idx)) {
time = lrint(strtod(col[4]->bind_ptr, NULL) * 60);
tmp = strdup(col[2]->bind_ptr);
ev = find_bookmark(d->dc.events, time);
if (ev)
update_event_name(d, ev, tmp);
else
if (!add_event(&d->dc, time, SAMPLE_EVENT_BOOKMARK, 0, 0, tmp))
report_error("[smtk-import] Error - Couldn't add bookmark, dive %d, Name = %s",
d->number, tmp);
}
}
free(tmp);
smtk_free(bound_values, table->num_cols);
mdb_free_tabledef(table);
}
/*
* Returns a dc_descriptor_t structure based on dc model's number.
* This ensures the model pased to libdc_buffer_parser() is a supported model and avoids
* problems with shared model num devices by taking the family into account. The family
* is estimated elsewhere based in dive header length.
*/
static dc_descriptor_t *get_data_descriptor(int data_model, dc_family_t data_fam)
{
dc_descriptor_t *descriptor = NULL, *current = NULL;
dc_iterator_t *iterator = NULL;
dc_status_t rc;
rc = dc_descriptor_iterator(&iterator);
if (rc != DC_STATUS_SUCCESS) {
report_error("[Error][libdc]\t\t\tCreating the device descriptor iterator.\n");
return current;
}
while ((dc_iterator_next(iterator, &descriptor)) == DC_STATUS_SUCCESS) {
int desc_model = dc_descriptor_get_model(descriptor);
dc_family_t desc_fam = dc_descriptor_get_type(descriptor);
if (data_fam == DC_FAMILY_UWATEC_ALADIN) {
if (data_model == desc_model && data_fam == desc_fam) {
current = descriptor;
break;
}
} else if (data_model == desc_model && (desc_fam == DC_FAMILY_UWATEC_SMART ||
desc_fam == DC_FAMILY_UWATEC_MERIDIAN)) {
current = descriptor;
break;
}
dc_descriptor_free(descriptor);
}
dc_iterator_free(iterator);
return current;
}
/*
* Fills a device_data_t structure with known dc data.
* Completes a dc_descriptor_t structure with libdc's g_descriptors[] table so
* we ensure that data passed for parsing to libdc have a supported and known
* DC. dc_family_t is certainly known *only* if it is Aladin/Memomouse family
* otherwise it will be known after get_data_descriptor call.
*/
static int prepare_data(int data_model, dc_family_t dc_fam, device_data_t *dev_data)
{
dev_data->device = NULL;
dev_data->context = NULL;
dev_data->descriptor = get_data_descriptor(data_model, dc_fam);
if (dev_data->descriptor) {
dev_data->vendor = dc_descriptor_get_vendor(dev_data->descriptor);
dev_data->product = dc_descriptor_get_product(dev_data->descriptor);
dev_data->model = smtk_concat_str(dev_data->model, "", "%s %s", dev_data->vendor, dev_data->product);
return DC_STATUS_SUCCESS;
} else {
return DC_STATUS_UNSUPPORTED;
}
}
/*
* Returns a buffer prepared for libdc parsing.
* Aladin and memomouse dives were imported from datatrak, so they lack of a
* header. Creates a fake header for them and inserts the dc model where libdc
* expects it to be.
*/
static dc_status_t libdc_buffer_complete(device_data_t *dev_data, unsigned char *hdr_buffer, int hdr_length,
unsigned char *prf_buffer, int prf_length, unsigned char *compl_buf)
{
switch (dc_descriptor_get_type(dev_data->descriptor)) {
case DC_FAMILY_UWATEC_ALADIN:
case DC_FAMILY_UWATEC_MEMOMOUSE:
compl_buf[3] = (unsigned char) dc_descriptor_get_model(dev_data->descriptor);
memcpy(compl_buf+hdr_length, prf_buffer, prf_length);
break;
case DC_FAMILY_UWATEC_SMART:
case DC_FAMILY_UWATEC_MERIDIAN:
memcpy(compl_buf, hdr_buffer, hdr_length);
memcpy(compl_buf+hdr_length, prf_buffer, prf_length);
break;
default:
return -2;
}
return 0;
}
/*
* Main function.
* Two looping levels using a database for the first level ("Dives" table)
* and a clone of the first DB for the second level (each dive items). Using
* a DB clone is necessary as calling mdb_fetch_row() over different tables in
* a single DB breaks binded row data, and so would break the top loop.
*/
void smartrak_import(const char *file, struct dive_table *divetable)
{
MdbHandle *mdb, *mdb_clon;
MdbTableDef *mdb_table;
MdbColumn *col[MDB_MAX_COLS];
char *bound_values[MDB_MAX_COLS], *ver;
int i, dc_model;
// Set an european style locale to work date/time conversion
setlocale(LC_TIME, "POSIX");
mdb = mdb_open(file, MDB_NOFLAGS);
if (!mdb) {
report_error("[Error][smartrak_import]\tFile %s does not seem to be an Access database.", file);
return;
}
if (!mdb_read_catalog(mdb, MDB_TABLE)) {
report_error("[Error][smartrak_import]\tFile %s does not seem to be an Access database.", file);
return;
}
mdb_clon = mdb_clone_handle(mdb);
mdb_read_catalog(mdb_clon, MDB_TABLE);
/* Check Smarttrak version (different number of supported tanks, mixes and so) */
ver = smtk_value_by_idx(mdb_clon, "SmartTrak", 1, "1");
smtk_version = atoi(ver);
free(ver);
tanks = (smtk_version < 10213) ? 3 : 10;
mdb_table = smtk_open_table(mdb, "Dives", col, bound_values);
if (!mdb_table) {
report_error("[Error][smartrak_import]\tFile %s does not seem to be an SmartTrak file.", file);
return;
}
while (mdb_fetch_row(mdb_table)) {
device_data_t *devdata = calloc(1, sizeof(device_data_t));
dc_family_t dc_fam = DC_FAMILY_NULL;
unsigned char *prf_buffer, *hdr_buffer, *compl_buffer;
struct dive *smtkdive = alloc_dive();
struct tm *tm_date = malloc(sizeof(struct tm));
size_t hdr_length, prf_length;
dc_status_t rc = 0;
smtkdive->number = lrint(strtod(col[1]->bind_ptr, NULL));
/*
* If there is a DC model (no zero) try to create a buffer for the
* dive and parse it with libdivecomputer
*/
dc_model = lrint(strtod(col[coln(DCMODEL)]->bind_ptr, NULL)) & 0xFF;
if (dc_model) {
hdr_buffer = mdb_ole_read_full(mdb, col[coln(LOG)], &hdr_length);
if (hdr_length > 0 && hdr_length < 20) // We have a profile but it's imported from datatrak
dc_fam = DC_FAMILY_UWATEC_ALADIN;
rc = prepare_data(dc_model, dc_fam, devdata);
} else {
rc = DC_STATUS_NODEVICE;
}
smtkdive->dc.model = devdata->model;
smtkdive->dc.serial = copy_string(col[coln(DCNUMBER)]->bind_ptr);
if (rc == DC_STATUS_SUCCESS) {
prf_buffer = mdb_ole_read_full(mdb, col[coln(PROFILE)], &prf_length);
if (prf_length > 0) {
if (dc_descriptor_get_type(devdata->descriptor) == DC_FAMILY_UWATEC_ALADIN || dc_descriptor_get_type(devdata->descriptor) == DC_FAMILY_UWATEC_MEMOMOUSE)
hdr_length = 18;
compl_buffer = calloc(hdr_length+prf_length, sizeof(char));
rc = libdc_buffer_complete(devdata, hdr_buffer, hdr_length, prf_buffer, prf_length, compl_buffer);
if (rc != DC_STATUS_SUCCESS) {
report_error("[Error][smartrak_import]\t- %s - for dive %d", errmsg(rc), smtkdive->number);
} else {
rc = libdc_buffer_parser(smtkdive, devdata, compl_buffer, hdr_length + prf_length);
if (rc != DC_STATUS_SUCCESS)
report_error("[Error][libdc]\t\t- %s - for dive %d", errmsg(rc), smtkdive->number);
}
free(compl_buffer);
} else {
/* Dives without profile samples (usual in older aladin series) */
report_error("[Warning][smartrak_import]\t No profile for dive %d", smtkdive->number);
smtkdive->dc.duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs(col[coln(DURATION)]->bind_ptr);
smtkdive->dc.maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod(col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000);
}
free(hdr_buffer);
free(prf_buffer);
} else {
/* Manual dives or unknown DCs */
report_error("[Warning][smartrak_import]\t Manual or unknown dive computer for dive %d", smtkdive->number);
smtkdive->dc.duration.seconds = smtkdive->duration.seconds = smtk_time_to_secs(col[coln(DURATION)]->bind_ptr);
smtkdive->dc.maxdepth.mm = smtkdive->maxdepth.mm = lrint(strtod(col[coln(MAXDEPTH)]->bind_ptr, NULL) * 1000);
}
/*
* Cylinder and gasmixes completion.
* Revisit data under some circunstances, e.g. a start pressure = 0 may mean
* that dc doesn't support gas control, in this situation let's look into mdb data
*/
int pstartcol = coln(PSTART);
int o2fraccol = coln(O2FRAC);
int hefraccol = coln(HEFRAC);
int tankidxcol = coln(TANKIDX);
for (i = 0; i < tanks; i++) {
if (smtkdive->cylinder[i].start.mbar == 0)
smtkdive->cylinder[i].start.mbar = lrint(strtod(col[(i * 2) + pstartcol]->bind_ptr, NULL) * 1000);
/*
* If there is a start pressure ensure that end pressure is not zero as
* will be registered in DCs which only keep track of differential pressures,
* and collect the data registered by the user in mdb
*/
if (smtkdive->cylinder[i].end.mbar == 0 && smtkdive->cylinder[i].start.mbar != 0)
smtkdive->cylinder[i].end.mbar = lrint(strtod(col[(i * 2) + 1 + pstartcol]->bind_ptr, NULL) * 1000 ? : 1000);
if (smtkdive->cylinder[i].gasmix.o2.permille == 0)
smtkdive->cylinder[i].gasmix.o2.permille = lrint(strtod(col[i + o2fraccol]->bind_ptr, NULL) * 10);
if (smtk_version == 10213) {
if (smtkdive->cylinder[i].gasmix.he.permille == 0)
smtkdive->cylinder[i].gasmix.he.permille = lrint(strtod(col[i + hefraccol]->bind_ptr, NULL) * 10);
} else {
smtkdive->cylinder[i].gasmix.he.permille = 0;
}
smtk_build_tank_info(mdb_clon, &smtkdive->cylinder[i], col[i + tankidxcol]->bind_ptr);
}
/* Check for duplicated cylinders and clean them */
smtk_clean_cylinders(smtkdive);
/* Date issues with libdc parser - Take date time from mdb */
smtk_date_to_tm(col[coln(_DATE)]->bind_ptr, tm_date);
smtk_time_to_tm(col[coln(INTIME)]->bind_ptr, tm_date);
smtkdive->dc.when = smtkdive->when = smtk_timegm(tm_date);
free(tm_date);
smtkdive->dc.surfacetime.seconds = smtk_time_to_secs(col[coln(INTVAL)]->bind_ptr);
/* Data that user may have registered manually if not supported by DC, or not parsed */
if (!smtkdive->airtemp.mkelvin)
smtkdive->airtemp.mkelvin = C_to_mkelvin(lrint(strtod(col[coln(AIRTEMP)]->bind_ptr, NULL)));
if (!smtkdive->watertemp.mkelvin)
smtkdive->watertemp.mkelvin = smtkdive->mintemp.mkelvin = C_to_mkelvin(lrint(strtod(col[coln(MINWATERTEMP)]->bind_ptr, NULL)));
if (!smtkdive->maxtemp.mkelvin)
smtkdive->maxtemp.mkelvin = C_to_mkelvin(lrint(strtod(col[coln(MAXWATERTEMP)]->bind_ptr, NULL)));
/* No DC related data */
smtkdive->visibility = strtod(col[coln(VISIBILITY)]->bind_ptr, NULL) > 25 ? 5 : lrint(strtod(col[13]->bind_ptr, NULL) / 5);
smtkdive->weightsystem[0].weight.grams = lrint(strtod(col[coln(WEIGHT)]->bind_ptr, NULL) * 1000);
smtkdive->suit = smtk_value_by_idx(mdb_clon, "Suit", 1, col[coln(SUITIDX)]->bind_ptr);
smtk_build_location(mdb_clon, col[coln(SITEIDX)]->bind_ptr, smtkdive->when, &smtkdive->dive_site_uuid);
smtkdive->buddy = smtk_locate_buddy(mdb_clon, col[0]->bind_ptr);
smtk_parse_relations(mdb_clon, smtkdive, col[0]->bind_ptr, "Type", "TypeRelation", true);
smtk_parse_relations(mdb_clon, smtkdive, col[0]->bind_ptr, "Activity", "ActivityRelation", false);
smtk_parse_relations(mdb_clon, smtkdive, col[0]->bind_ptr, "Gear", "GearRelation", false);
smtk_parse_relations(mdb_clon, smtkdive, col[0]->bind_ptr, "Fish", "FishRelation", false);
smtk_parse_bookmarks(mdb_clon, smtkdive, col[0]->bind_ptr);
smtkdive->notes = smtk_concat_str(smtkdive->notes, "\n", "%s", col[coln(REMARKS)]->bind_ptr);
record_dive_to_table(smtkdive, divetable);
free(devdata);
}
smtk_free(bound_values, mdb_table->num_cols);
mdb_free_tabledef(mdb_table);
mdb_free_catalog(mdb_clon);
mdb->catalog = NULL;
mdb_close(mdb_clon);
mdb_close(mdb);
sort_table(divetable);
}