mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 13:10:19 +00:00
ebcbe5aedd
This adds watertemp and airtemp to the dive, populates them in fixup and uses them elsewhere in the code. WARNING: as a sideeffect we now edit the airtemp in the dive, but we never display this in the DIve Info notebook (as that always displays the data from the specific selected divecomputer). This is likely to cause confusion. It's consistent behavior, but... odd. This brings back the desire to have a view of "best data available" for a dive, in addition to the "per divecomputer" view. This would also allow us to consolidate the different pressure graphs we may be getting from different divecomputers (consider the case where you dive with multiple air integrated computers that are connected to different tanks - now we could have one profile with all the correct tank pressure plots overlayed - and the best available (or edited) data in the corresponding Dive Info notebook. This commit also fixes a few remaining accesses to the first divecomputer that fell through the cracks earlier and does a couple of other related cleanups. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
286 lines
6 KiB
C
286 lines
6 KiB
C
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "dive.h"
|
|
#include "file.h"
|
|
|
|
/* Crazy windows sh*t */
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
static int readfile(const char *filename, struct memblock *mem)
|
|
{
|
|
int ret, fd;
|
|
struct stat st;
|
|
char *buf;
|
|
|
|
mem->buffer = NULL;
|
|
mem->size = 0;
|
|
|
|
fd = g_open(filename, O_RDONLY | O_BINARY, 0);
|
|
if (fd < 0)
|
|
return fd;
|
|
ret = fstat(fd, &st);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = -EINVAL;
|
|
if (!S_ISREG(st.st_mode))
|
|
goto out;
|
|
ret = 0;
|
|
if (!st.st_size)
|
|
goto out;
|
|
buf = malloc(st.st_size+1);
|
|
ret = -1;
|
|
errno = ENOMEM;
|
|
if (!buf)
|
|
goto out;
|
|
mem->buffer = buf;
|
|
mem->size = st.st_size;
|
|
ret = read(fd, buf, mem->size);
|
|
if (ret < 0)
|
|
goto free;
|
|
buf[ret] = 0;
|
|
if (ret == mem->size)
|
|
goto out;
|
|
errno = EIO;
|
|
ret = -1;
|
|
free:
|
|
free(mem->buffer);
|
|
mem->buffer = NULL;
|
|
mem->size = 0;
|
|
out:
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef LIBZIP
|
|
#include <zip.h>
|
|
|
|
static void suunto_read(struct zip_file *file, GError **error)
|
|
{
|
|
int size = 1024, n, read = 0;
|
|
char *mem = malloc(size);
|
|
|
|
while ((n = zip_fread(file, mem+read, size-read)) > 0) {
|
|
read += n;
|
|
size = read * 3 / 2;
|
|
mem = realloc(mem, size);
|
|
}
|
|
parse_xml_buffer(_("SDE file"), mem, read, &dive_table, error);
|
|
free(mem);
|
|
}
|
|
#endif
|
|
|
|
static int try_to_open_suunto(const char *filename, struct memblock *mem, GError **error)
|
|
{
|
|
int success = 0;
|
|
#ifdef LIBZIP
|
|
/* Grr. libzip needs to re-open the file, it can't take a buffer */
|
|
struct zip *zip = zip_open(filename, ZIP_CHECKCONS, NULL);
|
|
|
|
if (zip) {
|
|
int index;
|
|
for (index = 0; ;index++) {
|
|
struct zip_file *file = zip_fopen_index(zip, index, 0);
|
|
if (!file)
|
|
break;
|
|
suunto_read(file, error);
|
|
zip_fclose(file);
|
|
success++;
|
|
}
|
|
zip_close(zip);
|
|
}
|
|
#endif
|
|
return success;
|
|
}
|
|
|
|
static timestamp_t parse_date(const char *date)
|
|
{
|
|
int hour, min, sec;
|
|
struct tm tm;
|
|
char *p;
|
|
|
|
memset(&tm, 0, sizeof(tm));
|
|
tm.tm_mday = strtol(date, &p, 10);
|
|
if (tm.tm_mday < 1 || tm.tm_mday > 31)
|
|
return 0;
|
|
for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
|
|
if (!memcmp(p, monthname(tm.tm_mon), 3))
|
|
break;
|
|
}
|
|
if (tm.tm_mon > 11)
|
|
return 0;
|
|
date = p+3;
|
|
tm.tm_year = strtol(date, &p, 10);
|
|
if (date == p)
|
|
return 0;
|
|
if (tm.tm_year < 70)
|
|
tm.tm_year += 2000;
|
|
if (tm.tm_year < 100)
|
|
tm.tm_year += 1900;
|
|
if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
|
|
return 0;
|
|
tm.tm_hour = hour;
|
|
tm.tm_min = min;
|
|
tm.tm_sec = sec;
|
|
return utc_mktime(&tm);
|
|
}
|
|
|
|
enum csv_format {
|
|
CSV_DEPTH, CSV_TEMP, CSV_PRESSURE
|
|
};
|
|
|
|
static void add_sample_data(struct sample *sample, enum csv_format type, double val)
|
|
{
|
|
switch (type) {
|
|
case CSV_DEPTH:
|
|
sample->depth.mm = feet_to_mm(val);
|
|
break;
|
|
case CSV_TEMP:
|
|
sample->temperature.mkelvin = F_to_mkelvin(val);
|
|
break;
|
|
case CSV_PRESSURE:
|
|
sample->cylinderpressure.mbar = psi_to_mbar(val*4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
|
|
*
|
|
* They start with eight comma-separated fields like:
|
|
*
|
|
* filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
|
|
* divenr: %d
|
|
* datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
|
|
* ??: 1
|
|
* serialnr??: {CCI134},{CCI207}
|
|
* computer??: {GeminiII},{CommanderIII}
|
|
* computer??: {GeminiII},{CommanderIII}
|
|
* ??: 1
|
|
*
|
|
* Followed by the data values (all comma-separated, all one long line).
|
|
*/
|
|
static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type)
|
|
{
|
|
char *p = mem->buffer;
|
|
char *header[8];
|
|
int i, time;
|
|
timestamp_t date;
|
|
struct dive *dive;
|
|
struct divecomputer *dc;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
header[i] = p;
|
|
p = strchr(p, ',');
|
|
if (!p)
|
|
return 0;
|
|
p++;
|
|
}
|
|
|
|
date = parse_date(header[2]);
|
|
if (!date)
|
|
return 0;
|
|
|
|
dive = alloc_dive();
|
|
dive->when = date;
|
|
dive->number = atoi(header[1]);
|
|
dc = &dive->dc;
|
|
|
|
time = 0;
|
|
for (;;) {
|
|
char *end;
|
|
double val;
|
|
struct sample *sample;
|
|
|
|
errno = 0;
|
|
val = g_ascii_strtod(p,&end);
|
|
if (end == p)
|
|
break;
|
|
if (errno)
|
|
break;
|
|
|
|
sample = prepare_sample(dc);
|
|
sample->time.seconds = time;
|
|
add_sample_data(sample, type, val);
|
|
finish_sample(dc);
|
|
|
|
time++;
|
|
dc->duration.seconds = time;
|
|
if (*end != ',')
|
|
break;
|
|
p = end+1;
|
|
}
|
|
record_dive(dive);
|
|
return 1;
|
|
}
|
|
|
|
static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, GError **error)
|
|
{
|
|
/* Suunto Dive Manager files: SDE */
|
|
if (!strcasecmp(fmt, "SDE"))
|
|
return try_to_open_suunto(filename, mem, error);
|
|
|
|
/* Truly nasty intentionally obfuscated Cochran Anal software */
|
|
if (!strcasecmp(fmt, "CAN"))
|
|
return try_to_open_cochran(filename, mem, error);
|
|
|
|
/* Cochran export comma-separated-value files */
|
|
if (!strcasecmp(fmt, "DPT"))
|
|
return try_to_open_csv(filename, mem, CSV_DEPTH);
|
|
if (!strcasecmp(fmt, "TMP"))
|
|
return try_to_open_csv(filename, mem, CSV_TEMP);
|
|
if (!strcasecmp(fmt, "HP1"))
|
|
return try_to_open_csv(filename, mem, CSV_PRESSURE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void parse_file_buffer(const char *filename, struct memblock *mem, GError **error)
|
|
{
|
|
char *fmt = strrchr(filename, '.');
|
|
if (fmt && open_by_filename(filename, fmt+1, mem, error))
|
|
return;
|
|
|
|
parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, error);
|
|
}
|
|
|
|
void parse_file(const char *filename, GError **error, gboolean possible_default_filename)
|
|
{
|
|
struct memblock mem;
|
|
|
|
if (readfile(filename, &mem) < 0) {
|
|
/* we don't want to display an error if this was the default file */
|
|
if (prefs.default_filename && ! strcmp(filename, prefs.default_filename))
|
|
return;
|
|
|
|
g_warning(_("Failed to read '%s'.\n"), filename);
|
|
if (error) {
|
|
*error = g_error_new(g_quark_from_string("subsurface"),
|
|
DIVE_ERROR_PARSE,
|
|
_("Failed to read '%s'"),
|
|
filename);
|
|
}
|
|
|
|
/*
|
|
* We do *not* want to leave the old default_filename
|
|
* just because the open failed.
|
|
*/
|
|
if (possible_default_filename)
|
|
set_filename(filename, TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
if (possible_default_filename)
|
|
set_filename(filename, TRUE);
|
|
|
|
parse_file_buffer(filename, &mem, error);
|
|
free(mem.buffer);
|
|
}
|