subsurface/datatrak.c
Dirk Hohndel e03b553e80 Make created dive site uuid deterministic
Having random uuids seemed like a good idea, but there are several
situations where they really cause problems. One is merging dive file
imports from V2 logfiles. Another is testing such imports.

Instead of making the uuid random we now hash the name and add the
timestamp of the first dive associated with this dive site to the hash
(first in this context is "first encountered" with no guarantee that it is
the chronologically first). This way V2 imports create deterministic uuids
but uuid conflicts are still extremely unlikely, even if the user has
multiple dive sites with the same name.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2015-08-25 10:43:48 -07:00

683 lines
19 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "datatrak.h"
#include "dive.h"
#include "units.h"
#include "device.h"
#include "gettext.h"
extern struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc);
unsigned char lector_bytes[2], lector_word[4], tmp_1byte, *byte;
unsigned int tmp_2bytes;
char is_nitrox, is_O2, is_SCR;
unsigned long tmp_4bytes;
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 unsigned char *byte_to_bits(unsigned char byte)
{
unsigned char i, *bits = (unsigned char *)malloc(8);
for (i = 0; i < 8; i++)
bits[i] = byte & (1 << i);
return bits;
}
/*
* Datatrak stores the date in days since 01-01-1600, while Subsurface uses
* time_t (seconds since 00:00 01-01-1970). Function substracts
* (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 = 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;
}
/*
* Subsurface sample structure doesn't support the flags and alarms in the dt .log
* so will treat them as dc events.
*/
static struct sample *dtrak_profile(struct dive *dt_dive, FILE *archivo)
{
int i, j = 1, interval, o2percent = dt_dive->cylinder[0].gasmix.o2.permille / 10;
struct sample *sample = dt_dive->dc.sample;
struct divecomputer *dc = &dt_dive->dc;
for (i = 1; i <= dt_dive->dc.alloc_samples; i++) {
fread(&lector_bytes, 1, 2, archivo);
interval= 20 * (i + 1);
sample = add_sample(sample, interval, dc);
sample->depth.mm = (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0xFFC0) * 1000 / 410;
byte = byte_to_bits(two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0x003F);
if (byte[0] != 0)
sample->in_deco = true;
else
sample->in_deco = false;
if (byte[1] != 0)
add_event(dc, sample->time.seconds, 0, 0, 0, "rbt");
if (byte[2] != 0)
add_event(dc, sample->time.seconds, 0, 0, 0, "ascent");
if (byte[3] != 0)
add_event(dc, sample->time.seconds, 0, 0, 0, "ceiling");
if (byte[4] != 0)
add_event(dc, sample->time.seconds, 0, 0, 0, "workload");
if (byte[5] != 0)
add_event(dc, sample->time.seconds, 0, 0, 0, "transmitter");
if (j == 3) {
read_bytes(1);
if (is_O2) {
read_bytes(1);
o2percent = tmp_1byte;
}
j = 0;
}
free(byte);
// In commit 5f44fdd setpoint replaced po2, so although this is not necesarily CCR dive ...
if (is_O2)
sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100;
j++;
}
return sample;
}
/*
* Reads the header of a file and returns the header struct
* If it's not a DATATRAK file returns header zero initalized
*/
static dtrakheader read_file_header(FILE *archivo)
{
dtrakheader fileheader = { 0 };
const short headerbytes = 12;
unsigned char *lector = (unsigned char *)malloc(headerbytes);
fread(lector, 1, headerbytes, archivo);
if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) {
report_error(translate("gettextFromC", "Error: the file does not appear to be a DATATRAK divelog"));
free(lector);
return fileheader;
}
fileheader.header = (lector[0] << 8) + lector[1];
fileheader.dc_serial_1 = two_bytes_to_int(lector[2], lector[3]);
fileheader.dc_serial_2 = two_bytes_to_int(lector[4], lector[5]);
fileheader.divesNum = two_bytes_to_int(lector[7], lector[6]);
free(lector);
return fileheader;
}
/*
* Parses the dive extracting its data and filling a subsurface's dive structure
*/
static struct dive dt_dive_parser(FILE *archivo, struct dive *dt_dive)
{
unsigned char n;
int profile_length;
char *tmp_notes_str = NULL;
unsigned char *tmp_string1 = NULL,
*locality = NULL,
*dive_point = NULL;
char buffer[1024];
struct divecomputer *dc = &dt_dive->dc;
is_nitrox = is_O2 = is_SCR = 0;
/*
* Parse byte to byte till next dive entry
*/
n = 0;
fread(&lector_bytes[n], 1, 1, archivo);
while (lector_bytes[n] != 0xA0)
fread(&lector_bytes[n], 1, 1, archivo);
/*
* Found dive header 0xA000, verify second byte
*/
fread(&lector_bytes[n+1], 1, 1, archivo);
if (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) != 0xA000) {
printf("Error: byte = %4x\n", two_bytes_to_int(lector_bytes[0], lector_bytes[1]));
dt_dive = NULL;
return *dt_dive;
}
/*
* Begin parsing
* First, Date of dive, 4 bytes
*/
read_bytes(4);
/*
* Next, Time in minutes since 00:00
*/
read_bytes(2);
dt_dive->dc.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.
*/
snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point);
dt_dive->dive_site_uuid = get_dive_site_uuid_by_name(buffer, NULL);
if (dt_dive->dive_site_uuid == 0)
dt_dive->dive_site_uuid = create_dive_site(buffer, dt_dive->when);
free(locality);
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->dc.surface_pressure.mbar = 1013;
break;
case 2:
dt_dive->dc.surface_pressure.mbar = 932;
break;
case 3:
dt_dive->dc.surface_pressure.mbar = 828;
break;
case 4:
dt_dive->dc.surface_pressure.mbar = 735;
break;
default:
dt_dive->dc.surface_pressure.mbar = 1013;
}
/*
* Interval (minutes)
*/
read_bytes(2);
if (tmp_2bytes != 0x7FFF)
dt_dive->dc.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->tag_list = NULL;
read_bytes(1);
switch (tmp_1byte) {
case 1:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear")));
break;
case 2:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty")));
break;
case 3:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog")));
break;
case 4:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain")));
break;
case 5:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm")));
break;
case 6:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow")));
break;
default:
// unknown, do nothing
break;
}
/*
* Air Temperature
*/
read_bytes(2);
if (tmp_2bytes != 0x7FFF)
dt_dive->dc.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 = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit"));
break;
case 2:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty"));
break;
case 3:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi"));
break;
case 4:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit"));
break;
case 5:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit"));
break;
case 6:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit"));
break;
default:
// unknown, do nothing
break;
}
/*
* Tank, volume size in liter*100. And initialize gasmix to air (default).
* Dtrak don'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) {
dt_dive->cylinder[0].type.size.mliter = tmp_2bytes * 10;
dt_dive->cylinder[0].type.description = strdup("");
dt_dive->cylinder[0].start.mbar = 200000;
dt_dive->cylinder[0].gasmix.he.permille = 0;
dt_dive->cylinder[0].gasmix.o2.permille = 210;
dt_dive->cylinder[0].manually_added = true;
}
/*
* Maximum depth, in cm.
*/
read_bytes(2);
if (tmp_2bytes != 0x7FFF)
dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t)tmp_2bytes * 10;
/*
* Dive time in minutes.
*/
read_bytes(2);
if (tmp_2bytes != 0x7FFF)
dt_dive->duration.seconds = dt_dive->dc.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->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
else
dt_dive->watertemp.mkelvin = 0;
/*
* Air used in bar*100.
*/
read_bytes(2);
if (tmp_2bytes != 0x7FFF && dt_dive->cylinder[0].type.size.mliter)
dt_dive->cylinder[0].gas_used.mliter = dt_dive->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);
byte = byte_to_bits(tmp_1byte);
if (byte[2] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop")));
if (byte[3] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco")));
if (byte[4] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent")));
if (byte[5] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent")));
if (byte[6] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh")));
if (byte[7] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water")));
free(byte);
/*
* Dive Type 2 - Bit table, use tags again
*/
read_bytes(1);
byte = byte_to_bits(tmp_1byte);
if (byte[0] != 0) {
taglist_add_tag(&dt_dive->tag_list, strdup("nitrox"));
is_nitrox = 1;
}
if (byte[1] != 0) {
taglist_add_tag(&dt_dive->tag_list, strdup("rebreather"));
is_SCR = 1;
dt_dive->dc.divemode = PSCR;
}
free(byte);
/*
* Dive Activity 1 - Bit table, use tags again
*/
read_bytes(1);
byte = byte_to_bits(tmp_1byte);
if (byte[0] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing")));
if (byte[1] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive")));
if (byte[2] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor")));
if (byte[3] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction")));
if (byte[4] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night")));
if (byte[5] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave")));
if (byte[6] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice")));
if (byte[7] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search")));
free(byte);
/*
* Dive Activity 2 - Bit table, use tags again
*/
read_bytes(1);
byte = byte_to_bits(tmp_1byte);
if (byte[0] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck")));
if (byte[1] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river")));
if (byte[2] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift")));
if (byte[3] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo")));
if (byte[4] != 0)
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other")));
free(byte);
/*
* 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);
snprintf(buffer, sizeof(buffer), "%s: %s\n",
QT_TRANSLATE_NOOP("gettextFromC", "Other activities"),
tmp_string1);
tmp_notes_str = strdup(buffer);
free(tmp_string1);
}
/*
* Dive buddies
*/
read_bytes(1);
if (tmp_1byte != 0) {
read_string(tmp_string1);
dt_dive->buddy = strdup((char *)tmp_string1);
free(tmp_string1);
}
/*
* Dive notes
*/
read_bytes(1);
if (tmp_1byte != 0) {
read_string(tmp_string1);
int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s",
tmp_notes_str ? tmp_notes_str : "",
QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"),
tmp_string1);
dt_dive->notes = calloc((len +1), 1);
dt_dive->notes = memcpy(dt_dive->notes, buffer, len);
free(tmp_string1);
if (tmp_notes_str != NULL)
free(tmp_notes_str);
}
/*
* Alarms 1 - Bit table - Not in Subsurface, we use the profile
*/
read_bytes(1);
/*
* Alarms 2 - Bit table - Not in Subsurface, we use the profile
*/
read_bytes(1);
/*
* Dive number (in datatrak, after import user has to renumber)
*/
read_bytes(2);
dt_dive->number = tmp_2bytes;
/*
* Computer timestamp - Useless for Subsurface
*/
read_bytes(4);
/*
* Model - table - Not included 0x14, 0x24, 0x41, and 0x73
* known to exist, but not its model name - To add in the future.
* Strangely 0x00 serves for manually added dives and a dc too, at
* least in EXAMPLE.LOG file, shipped with the software.
*/
read_bytes(1);
switch (tmp_1byte) {
case 0x00:
dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive"));
break;
case 0x1C:
dt_dive->dc.model = strdup("Aladin Air");
break;
case 0x1D:
dt_dive->dc.model = strdup("Spiro Monitor 2 plus");
break;
case 0x1E:
dt_dive->dc.model = strdup("Aladin Sport");
break;
case 0x1F:
dt_dive->dc.model = strdup("Aladin Pro");
break;
case 0x34:
dt_dive->dc.model = strdup("Aladin Air X");
break;
case 0x3D:
dt_dive->dc.model = strdup("Spiro Monitor 2 plus");
break;
case 0x3F:
dt_dive->dc.model = strdup("Mares Genius");
break;
case 0x44:
dt_dive->dc.model = strdup("Aladin Air X");
break;
case 0x48:
dt_dive->dc.model = strdup("Spiro Monitor 3 Air");
break;
case 0xA4:
dt_dive->dc.model = strdup("Aladin Air X O2");
break;
case 0xB1:
dt_dive->dc.model = strdup("Citizen Hyper Aqualand");
break;
case 0xB2:
dt_dive->dc.model = strdup("Citizen ProMaster");
break;
case 0xB3:
dt_dive->dc.model = strdup("Mares Guardian");
break;
case 0xBC:
dt_dive->dc.model = strdup("Aladin Air X Nitrox");
break;
case 0xF4:
dt_dive->dc.model = strdup("Aladin Air X Nitrox");
break;
case 0xFF:
dt_dive->dc.model = strdup("Aladin Pro Nitrox");
break;
default:
dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown"));
break;
}
if ((tmp_1byte & 0xF0) == 0xF0)
is_nitrox = 1;
if ((tmp_1byte & 0xF0) == 0xA0)
is_O2 = 1;
/*
* Air usage, unknown use. Probably allows or deny manually entering gas
* comsumption based on dc model - Useless for Subsurface
*/
read_bytes(1);
fseek(archivo, 6, 1); // jump over 6 bytes whitout known use
/*
* Profile data length
*/
read_bytes(2);
profile_length = tmp_2bytes;
if (profile_length != 0) {
/*
* 8 x 2 bytes for the tissues saturation useless for subsurface
* and other 6 bytes without known use
*/
fseek(archivo, 22, 1);
if (is_nitrox || is_O2) {
/*
* CNS % (unsure) values table (only nitrox computers)
*/
read_bytes(1);
/*
* % O2 in nitrox mix - (only nitrox and O2 computers but differents)
*/
read_bytes(1);
if (is_nitrox) {
dt_dive->cylinder[0].gasmix.o2.permille =
(tmp_1byte & 0x0F ? 20.0 + 2 * (tmp_1byte & 0x0F) : 21.0) * 10;
} else {
dt_dive->cylinder[0].gasmix.o2.permille = tmp_1byte * 10;
read_bytes(1) // Jump over one byte, unknown use
}
}
/*
* profileLength = Nº bytes, need to know how many samples are there.
* 2bytes per sample plus another one each three samples. Also includes the
* bytes jumped over (22) and the nitrox (2) or O2 (3).
*/
int samplenum = is_O2 ? (profile_length - 25) * 3 / 8 : (profile_length - 24) * 3 / 7;
dc->events = calloc(samplenum, sizeof(struct event));
dc->alloc_samples = samplenum;
dc->samples = 0;
dc->sample = calloc(samplenum, sizeof(struct sample));
dtrak_profile(dt_dive, archivo);
}
/*
* Initialize some dive data not supported by Datatrak/WLog
*/
if (!strcmp(dt_dive->dc.model, "Manually entered dive"))
dt_dive->dc.deviceid = 0;
else
dt_dive->dc.deviceid = 0xffffffff;
create_device_node(dt_dive->dc.model, dt_dive->dc.deviceid, "", "", dt_dive->dc.model);
dt_dive->dc.next = NULL;
if (!is_SCR && dt_dive->cylinder[0].type.size.mliter) {
dt_dive->cylinder[0].end.mbar = dt_dive->cylinder[0].start.mbar -
((dt_dive->cylinder[0].gas_used.mliter / dt_dive->cylinder[0].type.size.mliter) * 1000);
}
return *dt_dive;
}
void datatrak_import(const char *file, struct dive_table *table)
{
FILE *archivo;
dtrakheader *fileheader = (dtrakheader *)malloc(sizeof(dtrakheader));
int i = 0;
if ((archivo = subsurface_fopen(file, "rb")) == NULL) {
report_error(translate("gettextFromC", "Error: couldn't open the file %s"), file);
free(fileheader);
return;
}
/*
* Verify fileheader, get number of dives in datatrak divelog
*/
*fileheader = read_file_header(archivo);
while (i < fileheader->divesNum) {
struct dive *ptdive = alloc_dive();
*ptdive = dt_dive_parser(archivo, ptdive);
if (!ptdive)
report_error(translate("gettextFromC", "Error: no dive"));
i++;
record_dive(ptdive);
}
taglist_cleanup(&g_tag_list);
fclose(archivo);
sort_table(table);
free(fileheader);
}