diff options
32 files changed, 425 insertions, 335 deletions
diff --git a/core/configuredivecomputerthreads.cpp b/core/configuredivecomputerthreads.cpp index 39c3b5c63..ff592b717 100644 --- a/core/configuredivecomputerthreads.cpp +++ b/core/configuredivecomputerthreads.cpp @@ -373,7 +373,6 @@ static dc_status_t read_ostc4_settings(dc_device_t *device, DeviceDetails *m_dev dc_event_progress_t progress; progress.current = 0; progress.maximum = 23; - unsigned char hardware[1]; EMIT_PROGRESS(); diff --git a/core/datatrak.c b/core/datatrak.c index eaf78b3b7..4896974e2 100644 --- a/core/datatrak.c +++ b/core/datatrak.c @@ -8,19 +8,17 @@ #include <stdio.h> #include <string.h> #include <time.h> - +#include "gettext.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); +#include "file.h" 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; +unsigned long tmp_4bytes, maxbuf; static unsigned int two_bytes_to_int(unsigned char x, unsigned char y) { @@ -89,116 +87,87 @@ static char *to_utf8(unsigned char *in_string) } /* - * Subsurface sample structure doesn't support the flags and alarms in the dt .log - * so will treat them as dc events. + * 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 struct sample *dtrak_profile(struct dive *dt_dive, FILE *archivo) +static int read_file_header(unsigned char *buffer) { - 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++) { - if (fread(&lector_bytes, 1, 2, archivo) != 2) - return sample; - 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, QT_TRANSLATE_NOOP("gettextFromC", "rbt")); - if (byte[2] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "ascent")); - if (byte[3] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "ceiling")); - if (byte[4] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "workload")); - if (byte[5] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "transmitter")); - if (j == 3) { - read_bytes(1); - if (is_O2) { - read_bytes(1); - o2percent = tmp_1byte; - } - j = 0; - } - free(byte); + int n = 0; - // In commit 5f44fdd setpoint replaced po2, so although this is not necessarily CCR dive ... - if (is_O2) - sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100; - j++; - } -bail: - return sample; + if (two_bytes_to_int(buffer[0], buffer[1]) == 0xA100) + n = two_bytes_to_int(buffer[7], buffer[6]); + return n; } /* - * Reads the header of a file and returns the header struct - * If it's not a DATATRAK file returns header zero initalized + * 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 dtrakheader read_file_header(FILE *archivo) +static int dtrak_prepare_data(int model, device_data_t *dev_data) { - dtrakheader fileheader = { 0 }; - const short headerbytes = 12; - unsigned char *lector = (unsigned char *)malloc(headerbytes); + dc_descriptor_t *d = NULL; + int i = 0; - if (fread(lector, 1, headerbytes, archivo) != headerbytes) { - free(lector); - return fileheader; - } - if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) { - report_error(translate("gettextFromC", "Error: the file does not appear to be a DATATRAK dive log")); - 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; + while (model != g_models[i].model_num && g_models[i].model_num != 0xEE) + i++; + dev_data->model = copy_string(g_models[i].name); + sscanf(g_models[i].name,"%m[A-Za-z] ", &dev_data->vendor); + dev_data->product = copy_string(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; } -#define CHECK(_func, _val) if ((_func) != (_val)) goto bail +/* + * 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 the dive extracting its data and filling a subsurface's dive structure + * 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. */ -bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) +unsigned char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive) { - unsigned char n; - int profile_length; + int rc, profile_length, n = 0, libdc_model; char *tmp_notes_str = NULL; unsigned char *tmp_string1 = NULL, *locality = NULL, - *dive_point = NULL; + *dive_point = NULL, + *compl_buffer, + *membuf = runner; char buffer[1024]; - struct divecomputer *dc = &dt_dive->dc; - - is_nitrox = is_O2 = is_SCR = 0; + device_data_t *devdata = calloc(1, sizeof(device_data_t)); /* - * Parse byte to byte till next dive entry + * Reset global variables for new dive */ - n = 0; - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); - while (lector_bytes[n] != 0xA0) - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); + is_nitrox = is_O2 = is_SCR = 0; /* - * Found dive header 0xA000, verify second byte + * Parse byte to byte till next dive entry */ - CHECK(fread(&lector_bytes[n+1], 1, 1, archivo), 1); - 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])); - return false; + while (membuf[0] != 0xA0 || membuf[1] != 0x00) { + JUMP(membuf, 1); } + JUMP(membuf, 2); /* * Begin parsing @@ -206,12 +175,10 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) */ 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); /* @@ -345,7 +312,7 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) /* * 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 + * Dtrak doesn't record init and end pressures, but consumed bar, so let's * init a default pressure of 200 bar. */ read_bytes(2); @@ -448,7 +415,6 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search"))); free(byte); - /* * Dive Activity 2 - Bit table, use tags again */ @@ -508,14 +474,9 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) } /* - * Alarms 1 - Bit table - Not in Subsurface, we use the profile + * Alarms 1 and Alarms2 - Bit tables - Not in Subsurface, we use the profile */ - read_bytes(1); - - /* - * Alarms 2 - Bit table - Not in Subsurface, we use the profile - */ - read_bytes(1); + JUMP(membuf, 2); /* * Dive number (in datatrak, after import user has to renumber) @@ -526,134 +487,71 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) /* * Computer timestamp - Useless for Subsurface */ - read_bytes(4); + JUMP(membuf, 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. + * 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) { - case 0x00: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive")); - break; - case 0x1C: - dt_dive->dc.model = strdup("Aladin Air"); + switch (tmp_1byte & 0xF0) { + case 0xF0: + is_nitrox = 1; 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"); + case 0xA0: + is_O2 = 1; break; default: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown")); + is_nitrox = 0; + is_O2 = 0; break; } - if ((tmp_1byte & 0xF0) == 0xF0) - is_nitrox = 1; - if ((tmp_1byte & 0xF0) == 0xA0) - is_O2 = 1; + 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->dc.model = copy_string(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. */ - read_bytes(1); - if (fseek(archivo, 6, 1) != 0) // jump over 6 bytes whitout known use - goto bail; + JUMP(membuf, 7); + /* * 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 - */ - if (fseek(archivo, 22, 1) != 0) + + /* + * 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); + free(devdata); goto bail; - 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 = - lrint((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 numerator = is_O2 ? (profile_length - 25) * 3 : (profile_length - 24) * 3; - int denominator = is_O2 ? 8 : 7; - int samplenum = (numerator / denominator) + (((numerator % denominator) != 0) ? 1 : 0); - - 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); + if (is_nitrox) + dt_dive->cylinder[0].gasmix.o2.permille = + lrint(membuf[23] & 0x0F ? 20.0 + 2 * (membuf[23] & 0x0F) : 21.0) * 10; + if (is_O2) + dt_dive->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 (!strcmp(dt_dive->dc.model, "Manually entered dive")) + if (!libdc_model) dt_dive->dc.deviceid = 0; else dt_dive->dc.deviceid = 0xffffffff; @@ -663,41 +561,51 @@ bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) 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 true; - + free(devdata); + return membuf; bail: - return false; + return NULL; } - -void datatrak_import(const char *file, struct dive_table *table) +/* + * Main function call from file.c memblock is allocated (and freed) there. + * If parsing is aborted due to errors, stores correctly parsed dives. + */ +int datatrak_import(struct memblock *mem, struct dive_table *table) { - FILE *archivo; - dtrakheader *fileheader = (dtrakheader *)malloc(sizeof(dtrakheader)); - int i = 0; + unsigned char *runner; + int i = 0, numdives = 0, rc = 0; + + maxbuf = (long) mem->buffer + mem->size; - 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, zero on error + numdives = read_file_header((unsigned char *)mem->buffer); + if (!numdives) { + report_error(translate("gettextFromC", "[Error] File is not a DataTrak file. Aborted")); + goto bail; } + // Point to the expected begining of 1st. dive data + runner = (unsigned char *)mem->buffer; + JUMP(runner, 12); - /* - * Verify fileheader, get number of dives in datatrak divelog - */ - *fileheader = read_file_header(archivo); - while (i < fileheader->divesNum) { + // Secuential parsing. Abort if received NULL from dt_dive_parser. + while ((i < numdives) && ((long) runner < maxbuf)) { struct dive *ptdive = alloc_dive(); - if (!dt_dive_parser(archivo, ptdive)) { + runner = dt_dive_parser(runner, ptdive); + if (runner == NULL) { report_error(translate("gettextFromC", "Error: no dive")); free(ptdive); + rc = 1; + goto out; } else { record_dive(ptdive); } i++; } +out: taglist_cleanup(&g_tag_list); - fclose(archivo); sort_table(table); - free(fileheader); + return rc; +bail: + return 1; } diff --git a/core/datatrak.h b/core/datatrak.h index a774c6018..7aea1741f 100644 --- a/core/datatrak.h +++ b/core/datatrak.h @@ -3,40 +3,71 @@ #define DATATRAK_HEADER_H #include <string.h> +#include "libdivecomputer.h" -typedef struct dtrakheader_ { - int header; //Must be 0xA100; - int divesNum; - int dc_serial_1; - int dc_serial_2; -} dtrakheader; +struct models_table_t { + int model_num; + int libdc_num; + const char *name; + dc_family_t type; +}; +/* + * Set of known models and equivalences with libdivecomputer. + * Not included 0x14, 0x24, 0x41, and 0x73 known to exist, but not its model name. + * Unknown model equivalence is set to Air X which should cover most profiles. + * Nitrox and 02 models seems to keep its number more seriously than earlier + * series and OEMs. Info for unknown models is always welcome. + */ +static const struct models_table_t g_models[] = { + {0x00, 0x00, "Manually entered dive", DC_FAMILY_NULL}, + {0x1B, 0x3F, "Uwatec Aladin Pro", DC_FAMILY_UWATEC_ALADIN}, + {0x1C, 0x1C, "Uwatec Aladin Air", DC_FAMILY_UWATEC_ALADIN}, + {0x1D, 0x3F, "Spiro Monitor 2 plus", DC_FAMILY_UWATEC_ALADIN}, + {0x1E, 0x3E, "Uwatec Aladin Sport", DC_FAMILY_UWATEC_ALADIN}, + {0x1F, 0x3F, "Uwatec Aladin Pro", DC_FAMILY_UWATEC_ALADIN}, + {0x34, 0x44, "Uwatec Aladin Air X/Z", DC_FAMILY_UWATEC_ALADIN}, + {0x3D, 0x3F, "Spiro Monitor 2 plus", DC_FAMILY_UWATEC_ALADIN}, + {0x3E, 0x3E, "Uwatec Aladin Sport", DC_FAMILY_UWATEC_ALADIN}, + {0x3F, 0x3F, "Uwatec Aladin Pro", DC_FAMILY_UWATEC_ALADIN}, + {0x44, 0x44, "Uwatec Aladin Air X/Z", DC_FAMILY_UWATEC_ALADIN}, + {0x48, 0x1C, "Spiro Monitor 3 Air", DC_FAMILY_UWATEC_ALADIN}, + {0xA4, 0xA4, "Uwatec Aladin Air X/Z O2", DC_FAMILY_UWATEC_ALADIN}, + {0xB1, 0x3E, "Citizen Hyper Aqualand", DC_FAMILY_UWATEC_ALADIN}, + {0xB2, 0x3F, "Citizen ProMaster", DC_FAMILY_UWATEC_ALADIN}, + {0xB3, 0x3F, "Mares Guardian", DC_FAMILY_UWATEC_ALADIN}, + {0xF4, 0xF4, "Uwatec Aladin Air X/Z Nitrox", DC_FAMILY_UWATEC_ALADIN}, + {0xFF, 0xFF, "Uwatec Aladin Pro Nitrox", DC_FAMILY_UWATEC_ALADIN}, + {0xEE, 0x44, "Uwatec Unknown model", DC_FAMILY_UWATEC_ALADIN}, +}; + +extern struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc); + +#define JUMP(_ptr, _n) if ((long) (_ptr += _n) > maxbuf) goto bail +#define CHECK(_ptr, _n) if ((long) _ptr + _n > maxbuf) goto bail #define read_bytes(_n) \ switch (_n) { \ case 1: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_1byte = lector_bytes[0]; \ + CHECK(membuf, _n); \ + tmp_1byte = membuf[0]; \ break; \ case 2: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_2bytes = two_bytes_to_int (lector_bytes[1], lector_bytes[0]); \ + CHECK(membuf, _n); \ + tmp_2bytes = two_bytes_to_int (membuf[1], membuf[0]); \ break; \ default: \ - if (fread (&lector_word, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_4bytes = four_bytes_to_long(lector_word[3], lector_word[2], lector_word[1], lector_word[0]); \ + CHECK(membuf, _n); \ + tmp_4bytes = four_bytes_to_long(membuf[3], membuf[2], membuf[1], membuf[0]); \ break; \ - } + } \ + JUMP(membuf, _n); #define read_string(_property) \ + CHECK(membuf, tmp_1byte); \ unsigned char *_property##tmp = (unsigned char *)calloc(tmp_1byte + 1, 1); \ - if (fread((char *)_property##tmp, 1, tmp_1byte, archivo) != tmp_1byte) { \ - free(_property##tmp); \ - goto bail; \ - } \ + _property##tmp = memcpy(_property##tmp, membuf, tmp_1byte);\ _property = (unsigned char *)strcat(to_utf8(_property##tmp), ""); \ - free(_property##tmp); + free(_property##tmp);\ + JUMP(membuf, tmp_1byte); #endif // DATATRAK_HEADER_H diff --git a/core/exif.cpp b/core/exif.cpp index 9d1643a0a..8c47a514f 100644 --- a/core/exif.cpp +++ b/core/exif.cpp @@ -420,23 +420,6 @@ int easyexif::EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) { if (!buf || len < 4) return PARSE_EXIF_ERROR_NO_JPEG; if (buf[0] != 0xFF || buf[1] != 0xD8) return PARSE_EXIF_ERROR_NO_JPEG; - // Sanity check: some cameras pad the JPEG image with null bytes at the end. - // Normally, we should able to find the JPEG end marker 0xFFD9 at the end - // of the image, but not always. As long as there are null/0xFF bytes at the - // end of the image buffer, keep decrementing len until an 0xFFD9 is found, - // or some other bytes are. If the first non-zero/0xFF bytes from the end are - // not 0xFFD9, then we can be reasonably sure that the buffer is not a JPEG. - while (len > 2) { - if (buf[len - 1] == 0 || buf[len - 1] == 0xFF) { - len--; - } else { - if (buf[len - 1] != 0xD9 || buf[len - 2] != 0xFF) { - return PARSE_EXIF_ERROR_NO_JPEG; - } else { - break; - } - } - } clear(); // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by diff --git a/core/file.c b/core/file.c index cde47cfef..7b24da8e7 100644 --- a/core/file.c +++ b/core/file.c @@ -534,8 +534,9 @@ int parse_file(const char *filename) /* DataTrak/Wlog */ if (fmt && !strcasecmp(fmt + 1, "LOG")) { - datatrak_import(filename, &dive_table); - return 0; + ret = datatrak_import(&mem, &dive_table); + free(mem.buffer); + return ret; } /* OSTCtools */ diff --git a/core/file.h b/core/file.h index 8c5647ed2..423d471f9 100644 --- a/core/file.h +++ b/core/file.h @@ -9,7 +9,7 @@ struct memblock { extern int try_to_open_cochran(const char *filename, struct memblock *mem); extern int try_to_open_liquivision(const char *filename, struct memblock *mem); -extern void datatrak_import(const char *file, struct dive_table *table); +extern int datatrak_import(struct memblock *mem, struct dive_table *table); extern void ostctools_import(const char *file, struct dive_table *table); #ifdef __cplusplus diff --git a/core/gpslocation.cpp b/core/gpslocation.cpp index 1c5d378cf..30b101419 100644 --- a/core/gpslocation.cpp +++ b/core/gpslocation.cpp @@ -46,6 +46,11 @@ GpsLocation *GpsLocation::instance() return m_Instance; } +bool GpsLocation::hasInstance() +{ + return m_Instance != NULL; +} + GpsLocation::~GpsLocation() { m_Instance = NULL; diff --git a/core/gpslocation.h b/core/gpslocation.h index 34e0708ff..9922997f1 100644 --- a/core/gpslocation.h +++ b/core/gpslocation.h @@ -27,6 +27,7 @@ public: GpsLocation(void (*showMsgCB)(const char *msg), QObject *parent); ~GpsLocation(); static GpsLocation *instance(); + static bool hasInstance(); bool applyLocations(); int getGpsNum() const; QString getUserid(QString user, QString passwd); diff --git a/core/helpers.h b/core/helpers.h index d694be941..a6e152cbf 100644 --- a/core/helpers.h +++ b/core/helpers.h @@ -36,7 +36,7 @@ int parseWeightToGrams(const QString &text); int parsePressureToMbar(const QString &text); int parseGasMixO2(const QString &text); int parseGasMixHE(const QString &text); -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText); +QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText, QString secondsText = "", bool isFreeDive = false); QString get_dive_date_string(timestamp_t when); QString get_short_dive_date_string(timestamp_t when); bool is_same_day (timestamp_t trip_when, timestamp_t dive_when); diff --git a/core/prefs-macros.h b/core/prefs-macros.h index bd1fc9a33..9208fb82e 100644 --- a/core/prefs-macros.h +++ b/core/prefs-macros.h @@ -11,6 +11,13 @@ else \ prefs.units.field = default_prefs.units.field +#define GET_UNIT3(name, field, f, l, type) \ + v = s.value(QString(name)); \ + if (v.isValid() && v.toInt() >= (f) && v.toInt() <= (l)) \ + prefs.units.field = (type)v.toInt(); \ + else \ + prefs.units.field = default_prefs.units.field + #define GET_BOOL(name, field) \ v = s.value(QString(name)); \ if (v.isValid()) \ diff --git a/core/qthelper.cpp b/core/qthelper.cpp index e5a046a05..45e402fc7 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -924,19 +924,23 @@ int parseGasMixHE(const QString &text) return he; } -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText) +QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText, QString secondsText, bool isFreeDive) { - int hrs, mins; + int hrs, mins, fullmins, secs; mins = (when + 59) / 60; + fullmins = when / 60; + secs = when - 60 * fullmins; hrs = mins / 60; - mins -= hrs * 60; QString displayTime; - if (hrs) - displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText); - else - displayTime = QString("%1%2").arg(mins).arg(minutesText); - + if (prefs.units.duration_units == units::ALWAYS_HOURS || (prefs.units.duration_units == units::MIXED && hrs)) { + mins -= hrs * 60; + displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(hourText == ":" ? "" : minutesText); + } else if (isFreeDive) { + displayTime = QString("%1%2%3%4").arg(fullmins).arg(minutesText).arg(secs, 2, 10, QChar('0')).arg(secondsText); + } else { + displayTime = QString("%1%2").arg(mins).arg(hourText == ":" ? "" : minutesText); + } return displayTime; } @@ -964,7 +968,7 @@ extern "C" const char *get_current_date() { QDateTime ts(QDateTime::currentDateTime());; QString current_date; - + current_date = loc.toString(ts, QString(prefs.date_format_short)); return strdup(current_date.toUtf8().data()); @@ -1173,9 +1177,18 @@ extern "C" void cache_picture(struct picture *picture) QtConcurrent::run(hashPicture, clone_picture(picture)); } +QStringList imageExtensionFilters() { + QStringList filters; + foreach (QString format, QImageReader::supportedImageFormats()) { + filters.append(QString("*.").append(format)); + } + return filters; +} + void learnImages(const QDir dir, int max_recursions) { - QStringList filters, files; + QStringList files; + QStringList filters = imageExtensionFilters(); if (max_recursions) { foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { @@ -1183,9 +1196,6 @@ void learnImages(const QDir dir, int max_recursions) } } - foreach (QString format, QImageReader::supportedImageFormats()) { - filters.append(QString("*.").append(format)); - } foreach (QString file, dir.entryList(filters, QDir::Files)) { files.append(dir.absoluteFilePath(file)); diff --git a/core/qthelper.h b/core/qthelper.h index 8d06ce93e..ff91b771a 100644 --- a/core/qthelper.h +++ b/core/qthelper.h @@ -46,6 +46,7 @@ extern "C" enum deco_mode decoMode(); extern "C" void subsurface_mkdir(const char *dir); void init_proxy(); QString getUUID(); +QStringList imageExtensionFilters(); char *intdup(int index); extern "C" int parse_seabear_header(const char *filename, char **params, int pnr); diff --git a/core/subsurface-qt/SettingsObjectWrapper.cpp b/core/subsurface-qt/SettingsObjectWrapper.cpp index 2752567ae..25161a904 100644 --- a/core/subsurface-qt/SettingsObjectWrapper.cpp +++ b/core/subsurface-qt/SettingsObjectWrapper.cpp @@ -1626,6 +1626,11 @@ int UnitsSettings::verticalSpeedTime() const return prefs.units.vertical_speed_time; } +int UnitsSettings::durationUnits() const +{ + return prefs.units.duration_units; +} + QString UnitsSettings::unitSystem() const { return prefs.unit_system == METRIC ? QStringLiteral("metric") @@ -1705,6 +1710,17 @@ void UnitsSettings::setVerticalSpeedTime(int value) emit verticalSpeedTimeChanged(value); } +void UnitsSettings::setDurationUnits(int value) +{ + if (value == prefs.units.duration_units) + return; + QSettings s; + s.beginGroup(group); + s.setValue("duration_units", value); + prefs.units.duration_units = (units::DURATION) value; + emit durationUnitChanged(value); +} + void UnitsSettings::setCoordinatesTraditional(bool value) { if (value == prefs.coordinates_traditional) @@ -2180,6 +2196,7 @@ void SettingsObjectWrapper::load() GET_UNIT("weight", weight, units::LBS, units::KG); } GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS); + GET_UNIT3("duration_units", duration_units, units::MIXED, units::ALWAYS_HOURS, units::DURATION); GET_BOOL("coordinates", coordinates_traditional); s.endGroup(); s.beginGroup("TecDetails"); diff --git a/core/subsurface-qt/SettingsObjectWrapper.h b/core/subsurface-qt/SettingsObjectWrapper.h index bb0e9db62..7116e682e 100644 --- a/core/subsurface-qt/SettingsObjectWrapper.h +++ b/core/subsurface-qt/SettingsObjectWrapper.h @@ -511,6 +511,7 @@ class UnitsSettings : public QObject { Q_PROPERTY(QString unit_system READ unitSystem WRITE setUnitSystem NOTIFY unitSystemChanged) Q_PROPERTY(bool coordinates_traditional READ coordinatesTraditional WRITE setCoordinatesTraditional NOTIFY coordinatesTraditionalChanged) Q_PROPERTY(int vertical_speed_time READ verticalSpeedTime WRITE setVerticalSpeedTime NOTIFY verticalSpeedTimeChanged) + Q_PROPERTY(int duration_units READ durationUnits WRITE setDurationUnits NOTIFY durationUnitChanged) public: UnitsSettings(QObject *parent = 0); @@ -520,6 +521,7 @@ public: int temperature() const; int weight() const; int verticalSpeedTime() const; + int durationUnits() const; QString unitSystem() const; bool coordinatesTraditional() const; @@ -530,6 +532,7 @@ public slots: void setTemperature(int value); void setWeight(int value); void setVerticalSpeedTime(int value); + void setDurationUnits(int value); void setUnitSystem(const QString& value); void setCoordinatesTraditional(bool value); @@ -542,6 +545,7 @@ signals: void verticalSpeedTimeChanged(int value); void unitSystemChanged(const QString& value); void coordinatesTraditionalChanged(bool value); + void durationUnitChanged(int value); private: const QString group = QStringLiteral("Units"); }; diff --git a/core/units.h b/core/units.h index c92c23d3a..7e4c2e7d2 100644 --- a/core/units.h +++ b/core/units.h @@ -255,6 +255,11 @@ struct units { SECONDS, MINUTES } vertical_speed_time; + enum DURATION { + MIXED, + MINUTES_ONLY, + ALWAYS_HOURS + } duration_units; }; /* @@ -264,15 +269,17 @@ struct units { * actually use. Similarly, C instead of Kelvin. * And kg instead of g. */ -#define SI_UNITS \ - { \ - .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, .vertical_speed_time = MINUTES \ - } - -#define IMPERIAL_UNITS \ - { \ - .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, .vertical_speed_time = MINUTES \ - } +#define SI_UNITS \ + { \ + .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, \ + .vertical_speed_time = MINUTES, .duration_units = MIXED \ + } + +#define IMPERIAL_UNITS \ + { \ + .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, \ + .vertical_speed_time = MINUTES, .duration_units = MIXED \ + } #ifdef __cplusplus } diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index 9c7c30578..a92afadbd 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -917,7 +917,12 @@ void DiveListView::shiftTimes() void DiveListView::loadImages() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open image files"), lastUsedImageDir(), tr("Image files (*.jpg *.jpeg *.pnm *.tif *.tiff)")); + QStringList filters = imageExtensionFilters(); + QStringList fileNames = QFileDialog::getOpenFileNames(this, + tr("Open image files"), + lastUsedImageDir(), + tr("Image files (%1)").arg(filters.join(" "))); + if (fileNames.isEmpty()) return; updateLastUsedImageDir(QFileInfo(fileNames[0]).dir().path()); diff --git a/desktop-widgets/divepicturewidget.cpp b/desktop-widgets/divepicturewidget.cpp index 92a61cba7..fcdd010da 100644 --- a/desktop-widgets/divepicturewidget.cpp +++ b/desktop-widgets/divepicturewidget.cpp @@ -31,7 +31,7 @@ void DivePictureWidget::doubleClicked(const QModelIndex &index) void DivePictureWidget::mousePressEvent(QMouseEvent *event) { - ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval()); + int doubleClickInterval = qApp->styleHints()->mouseDoubleClickInterval(); static qint64 lasttime = 0L; qint64 timestamp = QDateTime::currentDateTime().toMSecsSinceEpoch(); diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 9ab02cd88..d22e7cbb5 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -113,7 +113,6 @@ struct CurrSelected { QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option) - MainWindow *m = MainWindow::instance(); QComboBox *comboDelegate = new QComboBox(parent); comboDelegate->setModel(model); comboDelegate->setEditable(true); diff --git a/desktop-widgets/preferences/preferences_units.cpp b/desktop-widgets/preferences/preferences_units.cpp index 2717688a6..9ff6eb0af 100644 --- a/desktop-widgets/preferences/preferences_units.cpp +++ b/desktop-widgets/preferences/preferences_units.cpp @@ -40,6 +40,9 @@ void PreferencesUnits::refreshSettings() ui->vertical_speed_minutes->setChecked(prefs.units.vertical_speed_time == units::MINUTES); ui->vertical_speed_seconds->setChecked(prefs.units.vertical_speed_time == units::SECONDS); + ui->duration_mixed->setChecked(prefs.units.duration_units == units::MIXED); + ui->duration_no_hours->setChecked(prefs.units.duration_units == units::MINUTES_ONLY); + ui->duration_show_hours->setChecked(prefs.units.duration_units == units::ALWAYS_HOURS); } void PreferencesUnits::syncSettings() @@ -56,4 +59,5 @@ void PreferencesUnits::syncSettings() units->setWeight(ui->lbs->isChecked() ? units::LBS : units::KG); units->setVerticalSpeedTime(ui->vertical_speed_minutes->isChecked() ? units::MINUTES : units::SECONDS); units->setCoordinatesTraditional(ui->gpsTraditional->isChecked()); + units->setDurationUnits(ui->duration_mixed->isChecked() ? units::MIXED : (ui->duration_no_hours->isChecked() ? units::MINUTES_ONLY : units::ALWAYS_HOURS)); } diff --git a/desktop-widgets/preferences/preferences_units.ui b/desktop-widgets/preferences/preferences_units.ui index 4093181d4..49ef80a22 100644 --- a/desktop-widgets/preferences/preferences_units.ui +++ b/desktop-widgets/preferences/preferences_units.ui @@ -232,6 +232,43 @@ </widget> </item> <item> + <widget class="QGroupBox"> + <property name="title"> + <string>Duration units</string> + </property> + <layout class="QGridLayout"> + <item row="0" column="0"> + <widget class="QLabel" > + <property name="text"> + <string>Show hours in duration</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QRadioButton" name="duration_show_hours"> + <property name="text"> + <string>hh:mm (always)</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QRadioButton" name="duration_no_hours"> + <property name="text"> + <string>mm (always)</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QRadioButton" name="duration_mixed"> + <property name="text"> + <string>mm (for dives shorter than 1 hour), hh:mm (otherwise)</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="groupBox_11"> <property name="title"> <string>GPS coordinates</string> diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 8d3b94e8f..e6bbe87d9 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -19,6 +19,7 @@ #include "core/display.h" #include "profile-widget/profilewidget2.h" #include "desktop-widgets/undocommands.h" +#include "core/qthelper.h" class MinMaxAvgWidgetPrivate { public: @@ -309,7 +310,7 @@ void ShiftImageTimesDialog::syncCameraClicked() QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open image file"), DiveListView::lastUsedImageDir(), - tr("Image files (*.jpg *.jpeg *.pnm *.tif *.tiff)")); + tr("Image files (*.jpg *.jpeg)")); if (fileNames.isEmpty()) return; @@ -401,7 +402,10 @@ void ShiftImageTimesDialog::updateInvalid() // We've found invalid image timestamp = picture_get_timestamp(fileName.toUtf8().data()); time_first.setTime_t(timestamp + m_amount); - ui.invalidFilesText->append(fileName + " " + time_first.toString()); + if (timestamp == 0) + ui.invalidFilesText->append(fileName + " - " + tr("No Exif date/time found")); + else + ui.invalidFilesText->append(fileName + " - " + time_first.toString()); allValid = false; } diff --git a/desktop-widgets/subsurfacewebservices.cpp b/desktop-widgets/subsurfacewebservices.cpp index 2d8681a8a..1182af097 100644 --- a/desktop-widgets/subsurfacewebservices.cpp +++ b/desktop-widgets/subsurfacewebservices.cpp @@ -398,7 +398,8 @@ SubsurfaceWebServices::SubsurfaceWebServices(QWidget *parent, Qt::WindowFlags f) if (userid.isEmpty() && !same_string(prefs.cloud_storage_email, "") && - !same_string(prefs.cloud_storage_password, "")) + !same_string(prefs.cloud_storage_password, "") && + GpsLocation::hasInstance()) userid = GpsLocation::instance()->getUserid(prefs.cloud_storage_email, prefs.cloud_storage_password); ui.userID->setText(userid); diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 31b269b9b..14b5f2b7f 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -777,6 +777,8 @@ void MainTab::acceptChanges() MainWindow::instance()->dive_list()->unselectDives(); selected_dive = get_divenr(added_dive); amount_selected = 1; + // finally, make sure we get the tags + saveTags(); } else if (MainWindow::instance() && MainWindow::instance()->dive_list()->selectedTrips().count() == 1) { /* now figure out if things have changed */ if (displayedTrip.notes && !same_string(displayedTrip.notes, currentTrip->notes)) { diff --git a/dives/images/data_after_EOI.jpg b/dives/images/data_after_EOI.jpg Binary files differnew file mode 100755 index 000000000..62d6b152b --- /dev/null +++ b/dives/images/data_after_EOI.jpg diff --git a/wreck.jpg b/dives/images/wreck.jpg Binary files differindex 1ebfde9f0..1ebfde9f0 100644 --- a/wreck.jpg +++ b/dives/images/wreck.jpg diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index dadf81dfd..c57541ccd 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -342,22 +342,8 @@ int DiveItem::countPhotos(dive *dive) const QString DiveItem::displayDuration() const { - int hrs, mins, fullmins, secs; struct dive *dive = get_dive_by_uniq_id(diveId); - mins = (dive->duration.seconds + 59) / 60; - fullmins = dive->duration.seconds / 60; - secs = dive->duration.seconds - 60 * fullmins; - hrs = mins / 60; - mins -= hrs * 60; - - QString displayTime; - if (hrs) - displayTime = QString("%1:%2").arg(hrs).arg(mins, 2, 10, QChar('0')); - else if (mins < 15 || dive->dc.divemode == FREEDIVE) - displayTime = QString("%1m%2s").arg(fullmins).arg(secs, 2, 10, QChar('0')); - else - displayTime = QString("%1").arg(mins); - return displayTime; + return get_dive_duration_string(dive->duration.seconds, ":", "m", "s", dive->dc.divemode == FREEDIVE); } QString DiveItem::displayTemperature() const diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 294f26d05..2efacd706 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,7 +27,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # - test binaries dependencies (see TEST macro) set(WINDOWS_STAGING_TESTS ${CMAKE_BINARY_DIR}/staging_tests) install(DIRECTORY ${SUBSURFACE_SOURCE}/dives DESTINATION ${WINDOWS_STAGING_TESTS}) - install(FILES ${SUBSURFACE_SOURCE}/wreck.jpg DESTINATION ${WINDOWS_STAGING_TESTS}) # Check if we can run tests locally using wine # Add a fake test used to ensure data is deployed to WINDOWS_STAGING_TESTS before running diff --git a/tests/testparse.cpp b/tests/testparse.cpp index f9947fe95..9cea8c6c8 100644 --- a/tests/testparse.cpp +++ b/tests/testparse.cpp @@ -52,7 +52,7 @@ int TestParse::parseCSV(int units, std::string file) char *params[55]; int pnr = 0; - params[pnr++] = strdup(strdup("numberField")); + params[pnr++] = strdup("numberField"); params[pnr++] = intdup(0); params[pnr++] = strdup("dateField"); params[pnr++] = intdup(1); @@ -297,7 +297,7 @@ int TestParse::parseCSVmanual(int units, std::string file) char *params[55]; int pnr = 0; - params[pnr++] = strdup(strdup("numberField")); + params[pnr++] = strdup("numberField"); params[pnr++] = intdup(0); params[pnr++] = strdup("dateField"); params[pnr++] = intdup(1); @@ -370,9 +370,27 @@ void TestParse::exportCSVDiveDetails() clear_dive_file_data(); } +void TestParse::exportUDDF() +{ + parse_file(SUBSURFACE_TEST_DATA "/dives/test40.xml"); + + export_dives_xslt("testuddfexport.uddf", 0, 1, "uddf-export.xslt"); + + clear_dive_file_data(); + + parse_file("testuddfexport.uddf"); + export_dives_xslt("testuddfexport2.uddf", 0, 1, "uddf-export.xslt"); + + FILE_COMPARE("testuddfexport.uddf", + "testuddfexport2.uddf"); + + clear_dive_file_data(); +} + void TestParse::testExport() { exportCSVDiveDetails(); + exportUDDF(); } diff --git a/tests/testparse.h b/tests/testparse.h index 762b922e0..80c4dacfb 100644 --- a/tests/testparse.h +++ b/tests/testparse.h @@ -26,6 +26,7 @@ private slots: int parseCSVmanual(int, std::string); void exportCSVDiveDetails(); + void exportUDDF(); void testExport(); private: diff --git a/tests/testpicture.cpp b/tests/testpicture.cpp index 3021ece46..38e8e3d5b 100644 --- a/tests/testpicture.cpp +++ b/tests/testpicture.cpp @@ -15,28 +15,36 @@ void TestPicture::initTestCase() void TestPicture::addPicture() { struct dive *dive; - struct picture *pic; + struct picture *pic1, *pic2; verbose = 1; QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test44.xml"), 0); dive = get_dive(0); QVERIFY(dive != NULL); - pic = dive->picture_list; + pic1 = dive->picture_list; // So far no picture in dive - QVERIFY(pic == NULL); - - dive_create_picture(dive, SUBSURFACE_TEST_DATA "/wreck.jpg", 0, false); - pic = dive->picture_list; - // Now there is a picture - QVERIFY(pic != NULL); - // Appearing at time 21:01 - QVERIFY(pic->offset.seconds == 1261); - QVERIFY(pic->latitude.udeg == 47934500); - QVERIFY(pic->longitude.udeg == 11334500); - - QVERIFY(pic->hash == NULL); - learnHash(pic, hashFile(localFilePath(pic->filename))); - QCOMPARE(pic->hash, "929ad9499b7ae7a9e39ef63eb6c239604ac2adfa"); + QVERIFY(pic1 == NULL); + + dive_create_picture(dive, SUBSURFACE_TEST_DATA "/dives/images/wreck.jpg", 0, false); + dive_create_picture(dive, SUBSURFACE_TEST_DATA "/dives/images/data_after_EOI.jpg", 0, false); + pic1 = dive->picture_list; + pic2 = pic1->next; + // Now there are two picture2 + QVERIFY(pic1 != NULL); + QVERIFY(pic2 != NULL); + // 1st appearing at time 21:01 + // 2nd appearing at time 22:01 + QVERIFY(pic1->offset.seconds == 1261); + QVERIFY(pic1->latitude.udeg == 47934500); + QVERIFY(pic1->longitude.udeg == 11334500); + QVERIFY(pic2->offset.seconds == 1321); + + QVERIFY(pic1->hash == NULL); + QVERIFY(pic2->hash == NULL); + learnHash(pic1, hashFile(localFilePath(pic1->filename))); + learnHash(pic2, hashFile(localFilePath(pic2->filename))); + QCOMPARE(pic1->hash, "929ad9499b7ae7a9e39ef63eb6c239604ac2adfa"); + QCOMPARE(pic2->hash, "fa8bd48f8f24017a81e1204f52300bd98b43d4a7"); } diff --git a/xslt/uddf-export.xslt b/xslt/uddf-export.xslt index 53e1e84a7..bf4735f38 100644 --- a/xslt/uddf-export.xslt +++ b/xslt/uddf-export.xslt @@ -3,6 +3,7 @@ <xsl:include href="commonTemplates.xsl"/> <xsl:strip-space elements="*"/> <xsl:output method="xml" encoding="utf-8" indent="yes"/> + <xsl:param name="units" select="units"/> <xsl:key name="gases" match="cylinder" use="concat(substring-before(@o2, '.'), '/', substring-before(@he, '.'))" /> <xsl:key name="images" match="picture" use="concat(../../dive/@number|../dive/@number, ':', @filename, '@', @offset)" /> @@ -179,12 +180,33 @@ <profiledata> <xsl:for-each select="trip"> - <repetitiongroup id="{generate-id(.)}"> + <repetitiongroup> + <xsl:attribute name="id"> + <xsl:choose> + <xsl:when test="$test != ''"> + <xsl:value-of select="generate-id(.)" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="'testid1'" /> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + <xsl:apply-templates select="dive"/> </repetitiongroup> </xsl:for-each> <xsl:for-each select="dive"> - <repetitiongroup id="{generate-id(.)}"> + <repetitiongroup> + <xsl:attribute name="id"> + <xsl:choose> + <xsl:when test="string-length($units) = 0 or $units = 0"> + <xsl:value-of select="generate-id(.)" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="'testid2'" /> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> <xsl:apply-templates select="."/> </repetitiongroup> </xsl:for-each> @@ -246,7 +268,18 @@ </xsl:template> <xsl:template match="dive"> - <dive id="{generate-id(.)}" xmlns="http://www.streit.cc/uddf/3.2/"> + <dive xmlns="http://www.streit.cc/uddf/3.2/"> + <xsl:attribute name="id"> + <xsl:choose> + <xsl:when test="string-length($units) = 0 or $units = 0"> + <xsl:value-of select="generate-id(.)" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="'testid3'" /> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + <informationbeforedive> <xsl:variable name="buddylist"> @@ -505,7 +538,7 @@ </xsl:for-each> <depth> - <xsl:value-of select="substring-before(./@depth, ' ')"/> + <xsl:value-of select="round(substring-before(./@depth, ' ') * 100) div 100"/> </depth> <divetime> diff --git a/xslt/uddf.xslt b/xslt/uddf.xslt index 81435b301..e5c0f33f1 100644 --- a/xslt/uddf.xslt +++ b/xslt/uddf.xslt @@ -11,7 +11,16 @@ <xsl:template match="/"> <divelog program="subsurface-import" version="2"> <settings> - <divecomputerid deviceid="ffffffff"> + <divecomputerid> + <xsl:attribute name="deviceid"> + <xsl:value-of select="/uddf/diver/owner/equipment/divecomputer/@id|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/@id|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/@id" /> + </xsl:attribute> + <xsl:attribute name="model"> + <xsl:value-of select="/uddf/diver/owner/equipment/divecomputer/model|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/u:model|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/u1:model" /> + </xsl:attribute> + <xsl:attribute name="nickname"> + <xsl:value-of select="/uddf/diver/owner/equipment/divecomputer/name|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/u:name|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/u1:name" /> + </xsl:attribute> <xsl:choose> <xsl:when test="/UDDF/history != ''"> <xsl:apply-templates select="/UDDF/history"/> @@ -335,9 +344,19 @@ </xsl:for-each> - <divecomputer deviceid="ffffffff"> + <divecomputer> + <xsl:attribute name="deviceid"> + <xsl:value-of select="/uddf/diver/owner/equipment/divecomputer/@id|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/@id|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/@id" /> + </xsl:attribute> <xsl:attribute name="model"> - <xsl:value-of select="/uddf/generator/name|/u:uddf/u:generator/u:name|/u1:uddf/u1:generator/u1:name|/UDDF/history/modified/application/name"/> + <xsl:choose> + <xsl:when test="/uddf/diver/owner/equipment/divecomputer/model|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/u:model|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/u1:model != ''"> + <xsl:value-of select="/uddf/diver/owner/equipment/divecomputer/model|/u:uddf/u:diver/u:owner/u:equipment/u:divecomputer/u:model|/u1:uddf/u1:diver/u1:owner/u1:equipment/u1:divecomputer/u1:model" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="/uddf/generator/name|/u:uddf/u:generator/u:name|/u1:uddf/u1:generator/u1:name|/UDDF/history/modified/application/name"/> + </xsl:otherwise> + </xsl:choose> </xsl:attribute> <depth> |