mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 05:00:20 +00:00
ae81b42fe2
Thise makes initialization of unit types more palatable. For example: surface.time = sample.time - duration_t { .seconds = 20 }; => surface.time = sample.time - 20_sec; delta_depth.mm = feet_to_mm(1.0); // 1ft => delta_depth = 1_ft; get_cylinderid_at_time(..., { .seconds = 20 * 60 + 1 })); => get_cylinderid_at_time(..., 20_min + 1_sec)); Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
710 lines
20 KiB
C++
710 lines
20 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
#ifdef __clang__
|
|
// Clang has a bug on zero-initialization of C structs.
|
|
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include "gettext.h"
|
|
#include "datatrak.h"
|
|
#include "subsurface-string.h"
|
|
#include "units.h"
|
|
#include "device.h"
|
|
#include "file.h"
|
|
#include "format.h"
|
|
#include "divesite.h"
|
|
#include "dive.h"
|
|
#include "divelog.h"
|
|
#include "errorhelper.h"
|
|
#include "tag.h"
|
|
|
|
static unsigned int two_bytes_to_int(unsigned char x, unsigned char y)
|
|
{
|
|
return (x << 8) + y;
|
|
}
|
|
|
|
static unsigned long four_bytes_to_long(unsigned char x, unsigned char y, unsigned char z, unsigned char t)
|
|
{
|
|
return ((long)x << 24) + ((long)y << 16) + ((long)z << 8) + (long)t;
|
|
}
|
|
|
|
static bool bit_set(unsigned char byte, int bit)
|
|
{
|
|
return byte & (1 << bit);
|
|
}
|
|
|
|
/*
|
|
* Datatrak stores the date in days since 01-01-1600, while Subsurface uses
|
|
* time_t (seconds since 00:00 01-01-1970). Function subtracts
|
|
* (1970 - 1600) * 365,2425 = 135139,725 to our date variable, getting the
|
|
* days since Epoch.
|
|
*/
|
|
static time_t date_time_to_ssrfc(unsigned long date, int time)
|
|
{
|
|
time_t tmp;
|
|
tmp = (date - 135140) * 86400 + time * 60;
|
|
return tmp;
|
|
}
|
|
|
|
static unsigned char to_8859(unsigned char char_cp850)
|
|
{
|
|
static const unsigned char char_8859[46] = { 0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7,
|
|
0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5,
|
|
0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9,
|
|
0xff, 0xd6, 0xdc, 0xf8, 0xa3, 0xd8, 0xd7, 0x66,
|
|
0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xaa, 0xba,
|
|
0xbf, 0xae, 0xac, 0xbd, 0xbc, 0xa1 };
|
|
return char_8859[char_cp850 - 0x80];
|
|
}
|
|
|
|
static char *to_utf8(unsigned char *in_string)
|
|
{
|
|
int outlen, inlen, i = 0, j = 0;
|
|
inlen = strlen((char *)in_string);
|
|
outlen = inlen * 2 + 1;
|
|
|
|
char *out_string = (char *)calloc(outlen, 1);
|
|
for (i = 0; i < inlen; i++) {
|
|
if (in_string[i] < 127) {
|
|
out_string[j] = in_string[i];
|
|
} else {
|
|
if (in_string[i] > 127 && in_string[i] <= 173)
|
|
in_string[i] = to_8859(in_string[i]);
|
|
out_string[j] = (in_string[i] >> 6) | 0xC0;
|
|
j++;
|
|
out_string[j] = (in_string[i] & 0x3F) | 0x80;
|
|
}
|
|
j++;
|
|
}
|
|
out_string[j + 1] = '\0';
|
|
return out_string;
|
|
}
|
|
|
|
/*
|
|
* Reads the header of a datatrak buffer and returns the number of
|
|
* dives; zero on error (meaning this isn't a datatrak file).
|
|
* All other info in the header is useless for Subsurface.
|
|
*/
|
|
static int read_file_header(unsigned char *buffer)
|
|
{
|
|
int n = 0;
|
|
|
|
if (two_bytes_to_int(buffer[0], buffer[1]) == 0xA100)
|
|
n = two_bytes_to_int(buffer[7], buffer[6]);
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Fills a device_data_t structure based on the info from g_models table, using
|
|
* the dc's model number as start point.
|
|
* Returns libdc's equivalent model number (also from g_models) or zero if
|
|
* this a manual dive.
|
|
*/
|
|
static int dtrak_prepare_data(int model, device_data_t &dev_data)
|
|
{
|
|
dc_descriptor_t *d = NULL;
|
|
int i = 0;
|
|
|
|
while (model != g_models[i].model_num && g_models[i].model_num != 0xEE)
|
|
i++;
|
|
dev_data.model = g_models[i].name;
|
|
dev_data.vendor.clear();
|
|
for (const char *s = g_models[i].name; isalpha(*s); ++s)
|
|
dev_data.vendor += *s;
|
|
dev_data.product = strchr(g_models[i].name, ' ') + 1;
|
|
|
|
d = get_descriptor(g_models[i].type, g_models[i].libdc_num);
|
|
if (d)
|
|
dev_data.descriptor = d;
|
|
else
|
|
return 0;
|
|
return g_models[i].libdc_num;
|
|
}
|
|
|
|
/*
|
|
* Return a default name for a tank based on it's size.
|
|
* Just get the first in the user's list for given size.
|
|
* Reaching the end of the list means there is no tank of this size.
|
|
*/
|
|
static std::string cyl_type_by_size(int size)
|
|
{
|
|
auto it = std::find_if(tank_info_table.begin(), tank_info_table.end(),
|
|
[size] (const tank_info &info) { return info.ml == size; });
|
|
return it != tank_info_table.end() ? it->name : std::string();
|
|
}
|
|
|
|
/*
|
|
* Reads the size of a datatrak profile from actual position in buffer *ptr,
|
|
* zero padds it with a faked header and inserts the model number for
|
|
* libdivecomputer parsing. Puts the completed buffer in a pre-allocated
|
|
* compl_buffer, and returns status.
|
|
*/
|
|
static dc_status_t dt_libdc_buffer(unsigned char *ptr, int prf_length, int dc_model, unsigned char *compl_buffer)
|
|
{
|
|
if (compl_buffer == NULL)
|
|
return DC_STATUS_NOMEMORY;
|
|
compl_buffer[3] = (unsigned char) dc_model;
|
|
memcpy(compl_buffer + 18, ptr, prf_length);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Parses a mem buffer extracting its data and filling a subsurface's dive structure.
|
|
* Returns a pointer to last position in buffer, or NULL on failure.
|
|
*/
|
|
static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct divelog *log, char *maxbuf)
|
|
{
|
|
int rc, profile_length, libdc_model;
|
|
unsigned char *tmp_string1 = NULL,
|
|
*locality = NULL,
|
|
*dive_point = NULL,
|
|
*compl_buffer,
|
|
*membuf = runner;
|
|
unsigned char tmp_1byte;
|
|
unsigned int tmp_2bytes;
|
|
unsigned long tmp_4bytes;
|
|
std::string tmp_notes_str;
|
|
char is_nitrox = 0, is_O2 = 0, is_SCR = 0;
|
|
|
|
device_data_t devdata;
|
|
devdata.log = log;
|
|
|
|
/*
|
|
* Parse byte to byte till next dive entry
|
|
*/
|
|
while (membuf[0] != 0xA0 || membuf[1] != 0x00) {
|
|
JUMP(membuf, 1);
|
|
}
|
|
JUMP(membuf, 2);
|
|
|
|
/*
|
|
* Begin parsing
|
|
* First, Date of dive, 4 bytes
|
|
*/
|
|
read_bytes(4);
|
|
|
|
/*
|
|
* Next, Time in minutes since 00:00
|
|
*/
|
|
read_bytes(2);
|
|
dt_dive->dcs[0].when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes);
|
|
|
|
/*
|
|
* Now, Locality, 1st byte is long of string, rest is string
|
|
*/
|
|
read_bytes(1);
|
|
read_string(locality);
|
|
|
|
/*
|
|
* Next, Dive point, defined as Locality
|
|
*/
|
|
read_bytes(1);
|
|
read_string(dive_point);
|
|
|
|
/*
|
|
* Subsurface only have a location variable, so we have to merge DTrak's
|
|
* Locality and Dive points.
|
|
*/
|
|
{
|
|
std::string buffer2 = std::string((char *)locality) + " " + (char *)dive_point;
|
|
struct dive_site *ds = log->sites.get_by_name(buffer2);
|
|
if (!ds)
|
|
ds = log->sites.create(buffer2);
|
|
ds->add_dive(dt_dive);
|
|
}
|
|
free(locality);
|
|
locality = NULL;
|
|
free(dive_point);
|
|
|
|
/*
|
|
* Altitude. Don't exist in Subsurface, the equivalent would be
|
|
* surface air pressure which can, be calculated from altitude.
|
|
* As dtrak registers altitude intervals, we, arbitrarily, choose
|
|
* the lower altitude/pressure equivalence for each segment. So
|
|
*
|
|
* Datatrak table * Conversion formula:
|
|
* *
|
|
* byte = 1 0 - 700 m * P = P0 * exp(-(g * M * h ) / (R * T0))
|
|
* byte = 2 700 - 1700m * P0 = sealevel pressure = 101325 Pa
|
|
* byte = 3 1700 - 2700 m * g = grav. acceleration = 9,80665 m/s²
|
|
* byte = 4 2700 - * m * M = molar mass (dry air) = 0,0289644 Kg/mol
|
|
* * h = altitude over sea level (m)
|
|
* * R = Universal gas constant = 8,31447 J/(mol*K)
|
|
* * T0 = sea level standard temperature = 288,15 K
|
|
*/
|
|
read_bytes(1);
|
|
switch (tmp_1byte) {
|
|
case 1:
|
|
dt_dive->dcs[0].surface_pressure = 1_atm;
|
|
break;
|
|
case 2:
|
|
dt_dive->dcs[0].surface_pressure = 932_mbar;
|
|
break;
|
|
case 3:
|
|
dt_dive->dcs[0].surface_pressure = 828_mbar;
|
|
break;
|
|
case 4:
|
|
dt_dive->dcs[0].surface_pressure = 735_mbar;
|
|
break;
|
|
default:
|
|
dt_dive->dcs[0].surface_pressure = 1_atm;
|
|
}
|
|
|
|
/*
|
|
* Interval (minutes)
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF)
|
|
dt_dive->dcs[0].surfacetime.seconds = (uint32_t) tmp_2bytes * 60;
|
|
|
|
/*
|
|
* Weather, values table, 0 to 6
|
|
* Subsurface don't have this record but we can use tags
|
|
*/
|
|
dt_dive->tags.clear();
|
|
read_bytes(1);
|
|
switch (tmp_1byte) {
|
|
case 1:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "clear"));
|
|
break;
|
|
case 2:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "misty"));
|
|
break;
|
|
case 3:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fog"));
|
|
break;
|
|
case 4:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "rain"));
|
|
break;
|
|
case 5:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "storm"));
|
|
break;
|
|
case 6:
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "snow"));
|
|
break;
|
|
default:
|
|
// unknown, do nothing
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Air Temperature
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF)
|
|
dt_dive->dcs[0].airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
|
|
|
/*
|
|
* Dive suit, values table, 0 to 6
|
|
*/
|
|
read_bytes(1);
|
|
switch (tmp_1byte) {
|
|
case 1:
|
|
dt_dive->suit = "No suit";
|
|
break;
|
|
case 2:
|
|
dt_dive->suit = "Shorty";
|
|
break;
|
|
case 3:
|
|
dt_dive->suit = "Combi";
|
|
break;
|
|
case 4:
|
|
dt_dive->suit = "Wet suit";
|
|
break;
|
|
case 5:
|
|
dt_dive->suit = "Semidry suit";
|
|
break;
|
|
case 6:
|
|
dt_dive->suit = "Dry suit";
|
|
break;
|
|
default:
|
|
// unknown, do nothing
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Tank, volume size in liter*100. And initialize gasmix to air (default).
|
|
* Dtrak doesn't record init and end pressures, but consumed bar, so let's
|
|
* init a default pressure of 200 bar.
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF) {
|
|
cylinder_t cyl;
|
|
cyl.type.size.mliter = tmp_2bytes * 10;
|
|
cyl.type.description = cyl_type_by_size(tmp_2bytes * 10);
|
|
cyl.start = 200_bar;
|
|
cyl.gasmix.he = 0_percent;
|
|
cyl.gasmix.o2 = 21_percent;
|
|
cyl.manually_added = true;
|
|
dt_dive->cylinders.push_back(std::move(cyl));
|
|
}
|
|
|
|
/*
|
|
* Maximum depth, in cm.
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF)
|
|
dt_dive->maxdepth.mm = dt_dive->dcs[0].maxdepth.mm = (int32_t)tmp_2bytes * 10;
|
|
|
|
/*
|
|
* Dive time in minutes.
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF)
|
|
dt_dive->duration.seconds = dt_dive->dcs[0].duration.seconds = (uint32_t)tmp_2bytes * 60;
|
|
|
|
/*
|
|
* Minimum water temperature in C*100. If unknown, set it to 0K which
|
|
* is subsurface's value for "unknown"
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7fff)
|
|
dt_dive->watertemp.mkelvin = dt_dive->dcs[0].watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
|
|
else
|
|
dt_dive->watertemp = 0_K;
|
|
|
|
/*
|
|
* Air used in bar*100.
|
|
*/
|
|
read_bytes(2);
|
|
if (tmp_2bytes != 0x7FFF && dt_dive->cylinders.size() > 0)
|
|
dt_dive->get_cylinder(0)->gas_used.mliter = lrint(dt_dive->get_cylinder(0)->type.size.mliter * (tmp_2bytes / 100.0));
|
|
|
|
/*
|
|
* Dive Type 1 - Bit table. Subsurface don't have this record, but
|
|
* will use tags. Bits 0 and 1 are not used. Reuse coincident tags.
|
|
*/
|
|
read_bytes(1);
|
|
if (bit_set(tmp_1byte, 2))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "no stop"));
|
|
if (bit_set(tmp_1byte, 3))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "deco"));
|
|
if (bit_set(tmp_1byte, 4))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "single ascent"));
|
|
if (bit_set(tmp_1byte, 5))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "multiple ascent"));
|
|
if (bit_set(tmp_1byte, 6))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fresh water"));
|
|
if (bit_set(tmp_1byte, 7))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "salt water"));
|
|
|
|
/*
|
|
* Dive Type 2 - Bit table, use tags again
|
|
*/
|
|
read_bytes(1);
|
|
if (bit_set(tmp_1byte, 0)) {
|
|
taglist_add_tag(dt_dive->tags, "nitrox");
|
|
is_nitrox = 1;
|
|
}
|
|
if (bit_set(tmp_1byte, 1)) {
|
|
taglist_add_tag(dt_dive->tags, "rebreather");
|
|
is_SCR = 1;
|
|
dt_dive->dcs[0].divemode = PSCR;
|
|
}
|
|
|
|
/*
|
|
* Dive Activity 1 - Bit table, use tags again
|
|
*/
|
|
read_bytes(1);
|
|
if (bit_set(tmp_1byte, 0))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "sight seeing"));
|
|
if (bit_set(tmp_1byte, 1))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "club dive"));
|
|
if (bit_set(tmp_1byte, 2))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instructor"));
|
|
if (bit_set(tmp_1byte, 3))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instruction"));
|
|
if (bit_set(tmp_1byte, 4))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "night"));
|
|
if (bit_set(tmp_1byte, 5))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "cave"));
|
|
if (bit_set(tmp_1byte, 6))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "ice"));
|
|
if (bit_set(tmp_1byte, 7))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "search"));
|
|
|
|
/*
|
|
* Dive Activity 2 - Bit table, use tags again
|
|
*/
|
|
read_bytes(1);
|
|
if (bit_set(tmp_1byte, 0))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "wreck"));
|
|
if (bit_set(tmp_1byte, 1))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "river"));
|
|
if (bit_set(tmp_1byte, 2))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "drift"));
|
|
if (bit_set(tmp_1byte, 3))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "photo"));
|
|
if (bit_set(tmp_1byte, 4))
|
|
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "other"));
|
|
|
|
/*
|
|
* Other activities - String 1st byte = long
|
|
* Will put this in dive notes before the true notes
|
|
*/
|
|
read_bytes(1);
|
|
if (tmp_1byte != 0) {
|
|
read_string(tmp_string1);
|
|
tmp_notes_str= format_string_std("%s: %s\n",
|
|
translate("gettextFromC", "Other activities"),
|
|
tmp_string1);
|
|
free(tmp_string1);
|
|
}
|
|
|
|
/*
|
|
* Dive buddies
|
|
*/
|
|
read_bytes(1);
|
|
if (tmp_1byte != 0) {
|
|
read_string(tmp_string1);
|
|
dt_dive->buddy = (const char *)tmp_string1;
|
|
free(tmp_string1);
|
|
}
|
|
|
|
/*
|
|
* Dive notes
|
|
*/
|
|
read_bytes(1);
|
|
if (tmp_1byte != 0) {
|
|
read_string(tmp_string1);
|
|
dt_dive->notes = format_string_std("%s%s:\n%s",
|
|
tmp_notes_str.c_str(),
|
|
translate("gettextFromC", "Datatrak/Wlog notes"),
|
|
tmp_string1);
|
|
free(tmp_string1);
|
|
}
|
|
|
|
/*
|
|
* Alarms 1 and Alarms2 - Bit tables - Not in Subsurface, we use the profile
|
|
*/
|
|
JUMP(membuf, 2);
|
|
|
|
/*
|
|
* Dive number (in datatrak, after import user has to renumber)
|
|
*/
|
|
read_bytes(2);
|
|
dt_dive->number = tmp_2bytes;
|
|
|
|
/*
|
|
* Computer timestamp - Useless for Subsurface
|
|
*/
|
|
JUMP(membuf, 4);
|
|
|
|
/*
|
|
* Model number to check against equivalence with libdivecomputer table.
|
|
* The number also defines if the model is nitrox or O2 capable.
|
|
*/
|
|
read_bytes(1);
|
|
switch (tmp_1byte & 0xF0) {
|
|
case 0xF0:
|
|
is_nitrox = 1;
|
|
break;
|
|
case 0xA0:
|
|
is_O2 = 1;
|
|
break;
|
|
default:
|
|
is_nitrox = 0;
|
|
is_O2 = 0;
|
|
break;
|
|
}
|
|
libdc_model = dtrak_prepare_data(tmp_1byte, devdata);
|
|
if (!libdc_model)
|
|
report_error(translate("gettextFromC", "[Warning] Manual dive # %d\n"), dt_dive->number);
|
|
dt_dive->dcs[0].model = devdata.model;
|
|
|
|
/*
|
|
* Air usage, unknown use. Probably allows or deny manually entering gas
|
|
* comsumption based on dc model - Useless for Subsurface
|
|
* And 6 bytes without known use.
|
|
*/
|
|
JUMP(membuf, 7);
|
|
|
|
/*
|
|
* Profile data length
|
|
*/
|
|
read_bytes(2);
|
|
profile_length = tmp_2bytes;
|
|
|
|
/*
|
|
* Profile parsing, only if we have a profile and a dc model.
|
|
* If just a profile, skip parsing and seek the buffer to the end of dive.
|
|
*/
|
|
if (profile_length != 0 && libdc_model != 0) {
|
|
compl_buffer = (unsigned char *) calloc(18 + profile_length, 1);
|
|
rc = dt_libdc_buffer(membuf, profile_length, libdc_model, compl_buffer);
|
|
if (rc == DC_STATUS_SUCCESS) {
|
|
libdc_buffer_parser(dt_dive, &devdata, compl_buffer, profile_length + 18);
|
|
} else {
|
|
report_error(translate("gettextFromC", "[Error] Out of memory for dive %d. Abort parsing."), dt_dive->number);
|
|
free(compl_buffer);
|
|
goto bail;
|
|
}
|
|
if (is_nitrox && dt_dive->cylinders.size() > 0)
|
|
dt_dive->get_cylinder(0)->gasmix.o2.permille =
|
|
lrint(membuf[23] & 0x0F ? 20.0 + 2 * (membuf[23] & 0x0F) : 21.0) * 10;
|
|
if (is_O2 && dt_dive->cylinders.size() > 0)
|
|
dt_dive->get_cylinder(0)->gasmix.o2.permille = membuf[23] * 10;
|
|
free(compl_buffer);
|
|
}
|
|
JUMP(membuf, profile_length);
|
|
|
|
/*
|
|
* Initialize some dive data not supported by Datatrak/WLog
|
|
*/
|
|
if (!libdc_model)
|
|
dt_dive->dcs[0].deviceid = 0;
|
|
else
|
|
dt_dive->dcs[0].deviceid = 0xffffffff;
|
|
if (!is_SCR && dt_dive->cylinders.size() > 0) {
|
|
dt_dive->get_cylinder(0)->end.mbar = dt_dive->get_cylinder(0)->start.mbar -
|
|
((dt_dive->get_cylinder(0)->gas_used.mliter / dt_dive->get_cylinder(0)->type.size.mliter) * 1000);
|
|
}
|
|
return (char *)membuf;
|
|
bail:
|
|
free(locality);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Parses the header of the .add file, returns the number of dives in
|
|
* the archive (must be the same than number of dives in .log file).
|
|
*/
|
|
static int wlog_header_parser (std::string &mem)
|
|
{
|
|
int tmp;
|
|
unsigned char *runner = (unsigned char *) mem.data();
|
|
if (!runner)
|
|
return -1;
|
|
if (!memcmp(runner, "\x52\x02", 2)) {
|
|
runner += 8;
|
|
tmp = (int) two_bytes_to_int(runner[1], runner[0]);
|
|
return tmp;
|
|
} else {
|
|
fprintf(stderr, "Error, not a Wlog .add file\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#define NOTES_LENGTH 256
|
|
#define SUIT_LENGTH 26
|
|
static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dcount)
|
|
{
|
|
int tmp = 0, offset = 12 + (dcount * 850),
|
|
pos_weight = offset + 256,
|
|
pos_viz = offset + 258,
|
|
pos_tank_init = offset + 266,
|
|
pos_suit = offset + 268;
|
|
char *wlog_notes = NULL, *wlog_suit = NULL;
|
|
unsigned char *runner = (unsigned char *) wl_mem.data();
|
|
|
|
/*
|
|
* Extended notes string. Fixed length 256 bytes. 0 padded if not complete
|
|
*/
|
|
if (*(runner + offset)) {
|
|
char wlog_notes_temp[NOTES_LENGTH + 1];
|
|
wlog_notes_temp[NOTES_LENGTH] = 0;
|
|
(void)memcpy(wlog_notes_temp, runner + offset, NOTES_LENGTH);
|
|
wlog_notes = to_utf8((unsigned char *) wlog_notes_temp);
|
|
}
|
|
if (wlog_notes)
|
|
dt_dive->notes += wlog_notes;
|
|
|
|
/*
|
|
* Weight in Kg * 100
|
|
*/
|
|
tmp = (int) two_bytes_to_int(runner[pos_weight + 1], runner[pos_weight]);
|
|
if (tmp != 0x7fff) {
|
|
weightsystem_t ws = { {.grams = tmp * 10}, translate("gettextFromC", "unknown"), false };
|
|
dt_dive->weightsystems.push_back(std::move(ws));
|
|
}
|
|
|
|
/*
|
|
* Visibility in m * 100. Arbitrarily choosed to be 5 stars if >= 25m and
|
|
* then assign a star for each 5 meters, resulting 0 stars if < 5 m
|
|
*/
|
|
tmp = (int) two_bytes_to_int(runner[pos_viz + 1], runner[pos_viz]);
|
|
if (tmp != 0x7fff) {
|
|
tmp = tmp > 2500 ? 2500 / 100 : tmp / 100;
|
|
dt_dive->visibility = (int) floor(tmp / 5);
|
|
}
|
|
|
|
/*
|
|
* Tank initial pressure in bar * 100
|
|
* If we know initial pressure, rework end pressure.
|
|
*/
|
|
tmp = (int) two_bytes_to_int(runner[pos_tank_init + 1], runner[pos_tank_init]);
|
|
if (tmp != 0x7fff) {
|
|
dt_dive->get_cylinder(0)->start.mbar = tmp * 10;
|
|
dt_dive->get_cylinder(0)->end.mbar = dt_dive->get_cylinder(0)->start.mbar - lrint(dt_dive->get_cylinder(0)->gas_used.mliter / dt_dive->get_cylinder(0)->type.size.mliter) * 1000;
|
|
}
|
|
|
|
/*
|
|
* Dive suit, fixed length of 26 bytes, zero padded if shorter.
|
|
* Expected to be preferred by the user if he did the work of setting it.
|
|
*/
|
|
if (*(runner + pos_suit)) {
|
|
char wlog_suit_temp[SUIT_LENGTH + 1];
|
|
wlog_suit_temp[SUIT_LENGTH] = 0;
|
|
(void)memcpy(wlog_suit_temp, runner + pos_suit, SUIT_LENGTH);
|
|
wlog_suit = to_utf8((unsigned char *) wlog_suit_temp);
|
|
}
|
|
if (wlog_suit)
|
|
dt_dive->suit = wlog_suit;
|
|
free(wlog_suit);
|
|
}
|
|
|
|
/*
|
|
* Main function call from file.c data is allocated (and freed) there.
|
|
* If parsing is aborted due to errors, stores correctly parsed dives.
|
|
*/
|
|
int datatrak_import(std::string &mem, std::string &wl_mem, struct divelog *log)
|
|
{
|
|
char *runner;
|
|
int i = 0, numdives = 0, rc = 0;
|
|
|
|
char *maxbuf = mem.data() + mem.size();
|
|
|
|
// Verify fileheader, get number of dives in datatrak divelog, zero on error
|
|
numdives = read_file_header((unsigned char *)mem.data());
|
|
if (!numdives) {
|
|
report_error("%s", translate("gettextFromC", "[Error] File is not a DataTrak file. Aborted"));
|
|
goto bail;
|
|
}
|
|
// Verify WLog .add file, Beginning sequence and Nº dives
|
|
if(!wl_mem.empty()) {
|
|
int compl_dives_n = wlog_header_parser(wl_mem);
|
|
if (compl_dives_n != numdives) {
|
|
report_error("ERROR: Not the same number of dives in .log %d and .add file %d.\nWill not parse .add file", numdives , compl_dives_n);
|
|
wl_mem.clear();
|
|
}
|
|
}
|
|
// Point to the expected begining of 1st. dive data
|
|
runner = mem.data();
|
|
JUMP(runner, 12);
|
|
|
|
// Sequential parsing. Abort if received NULL from dt_dive_parser.
|
|
while ((i < numdives) && (runner < maxbuf)) {
|
|
auto ptdive = std::make_unique<dive>();
|
|
|
|
runner = dt_dive_parser((unsigned char *)runner, ptdive.get(), log, maxbuf);
|
|
if (!wl_mem.empty())
|
|
wlog_compl_parser(wl_mem, ptdive.get(), i);
|
|
if (runner == NULL) {
|
|
report_error("%s", translate("gettextFromC", "Error: no dive"));
|
|
rc = 1;
|
|
goto out;
|
|
} else {
|
|
log->dives.record_dive(std::move(ptdive));
|
|
}
|
|
i++;
|
|
}
|
|
out:
|
|
log->dives.sort();
|
|
return rc;
|
|
bail:
|
|
return 1;
|
|
}
|